Linux-libre 5.3.12-gnu
[librecmc/linux-libre.git] / drivers / staging / fsl-dpaa2 / ethsw / ethsw-ethtool.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * DPAA2 Ethernet Switch ethtool support
4  *
5  * Copyright 2014-2016 Freescale Semiconductor Inc.
6  * Copyright 2017-2018 NXP
7  *
8  */
9
10 #include "ethsw.h"
11
12 static struct {
13         enum dpsw_counter id;
14         char name[ETH_GSTRING_LEN];
15 } ethsw_ethtool_counters[] =  {
16         {DPSW_CNT_ING_FRAME,            "rx frames"},
17         {DPSW_CNT_ING_BYTE,             "rx bytes"},
18         {DPSW_CNT_ING_FLTR_FRAME,       "rx filtered frames"},
19         {DPSW_CNT_ING_FRAME_DISCARD,    "rx discarded frames"},
20         {DPSW_CNT_ING_BCAST_FRAME,      "rx b-cast frames"},
21         {DPSW_CNT_ING_BCAST_BYTES,      "rx b-cast bytes"},
22         {DPSW_CNT_ING_MCAST_FRAME,      "rx m-cast frames"},
23         {DPSW_CNT_ING_MCAST_BYTE,       "rx m-cast bytes"},
24         {DPSW_CNT_EGR_FRAME,            "tx frames"},
25         {DPSW_CNT_EGR_BYTE,             "tx bytes"},
26         {DPSW_CNT_EGR_FRAME_DISCARD,    "tx discarded frames"},
27
28 };
29
30 #define ETHSW_NUM_COUNTERS      ARRAY_SIZE(ethsw_ethtool_counters)
31
32 static void ethsw_get_drvinfo(struct net_device *netdev,
33                               struct ethtool_drvinfo *drvinfo)
34 {
35         struct ethsw_port_priv *port_priv = netdev_priv(netdev);
36         u16 version_major, version_minor;
37         int err;
38
39         strlcpy(drvinfo->driver, KBUILD_MODNAME, sizeof(drvinfo->driver));
40
41         err = dpsw_get_api_version(port_priv->ethsw_data->mc_io, 0,
42                                    &version_major,
43                                    &version_minor);
44         if (err)
45                 strlcpy(drvinfo->fw_version, "N/A",
46                         sizeof(drvinfo->fw_version));
47         else
48                 snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version),
49                          "%u.%u", version_major, version_minor);
50
51         strlcpy(drvinfo->bus_info, dev_name(netdev->dev.parent->parent),
52                 sizeof(drvinfo->bus_info));
53 }
54
55 static int
56 ethsw_get_link_ksettings(struct net_device *netdev,
57                          struct ethtool_link_ksettings *link_ksettings)
58 {
59         struct ethsw_port_priv *port_priv = netdev_priv(netdev);
60         struct dpsw_link_state state = {0};
61         int err = 0;
62
63         err = dpsw_if_get_link_state(port_priv->ethsw_data->mc_io, 0,
64                                      port_priv->ethsw_data->dpsw_handle,
65                                      port_priv->idx,
66                                      &state);
67         if (err) {
68                 netdev_err(netdev, "ERROR %d getting link state", err);
69                 goto out;
70         }
71
72         /* At the moment, we have no way of interrogating the DPMAC
73          * from the DPSW side or there may not exist a DPMAC at all.
74          * Report only autoneg state, duplexity and speed.
75          */
76         if (state.options & DPSW_LINK_OPT_AUTONEG)
77                 link_ksettings->base.autoneg = AUTONEG_ENABLE;
78         if (!(state.options & DPSW_LINK_OPT_HALF_DUPLEX))
79                 link_ksettings->base.duplex = DUPLEX_FULL;
80         link_ksettings->base.speed = state.rate;
81
82 out:
83         return err;
84 }
85
86 static int
87 ethsw_set_link_ksettings(struct net_device *netdev,
88                          const struct ethtool_link_ksettings *link_ksettings)
89 {
90         struct ethsw_port_priv *port_priv = netdev_priv(netdev);
91         struct dpsw_link_cfg cfg = {0};
92         int err = 0;
93
94         netdev_dbg(netdev, "Setting link parameters...");
95
96         /* Due to a temporary MC limitation, the DPSW port must be down
97          * in order to be able to change link settings. Taking steps to let
98          * the user know that.
99          */
100         if (netif_running(netdev)) {
101                 netdev_info(netdev, "Sorry, interface must be brought down first.\n");
102                 return -EACCES;
103         }
104
105         cfg.rate = link_ksettings->base.speed;
106         if (link_ksettings->base.autoneg == AUTONEG_ENABLE)
107                 cfg.options |= DPSW_LINK_OPT_AUTONEG;
108         else
109                 cfg.options &= ~DPSW_LINK_OPT_AUTONEG;
110         if (link_ksettings->base.duplex  == DUPLEX_HALF)
111                 cfg.options |= DPSW_LINK_OPT_HALF_DUPLEX;
112         else
113                 cfg.options &= ~DPSW_LINK_OPT_HALF_DUPLEX;
114
115         err = dpsw_if_set_link_cfg(port_priv->ethsw_data->mc_io, 0,
116                                    port_priv->ethsw_data->dpsw_handle,
117                                    port_priv->idx,
118                                    &cfg);
119         if (err)
120                 /* ethtool will be loud enough if we return an error; no point
121                  * in putting our own error message on the console by default
122                  */
123                 netdev_dbg(netdev, "ERROR %d setting link cfg", err);
124
125         return err;
126 }
127
128 static int ethsw_ethtool_get_sset_count(struct net_device *dev, int sset)
129 {
130         switch (sset) {
131         case ETH_SS_STATS:
132                 return ETHSW_NUM_COUNTERS;
133         default:
134                 return -EOPNOTSUPP;
135         }
136 }
137
138 static void ethsw_ethtool_get_strings(struct net_device *netdev,
139                                       u32 stringset, u8 *data)
140 {
141         int i;
142
143         switch (stringset) {
144         case ETH_SS_STATS:
145                 for (i = 0; i < ETHSW_NUM_COUNTERS; i++)
146                         memcpy(data + i * ETH_GSTRING_LEN,
147                                ethsw_ethtool_counters[i].name, ETH_GSTRING_LEN);
148                 break;
149         }
150 }
151
152 static void ethsw_ethtool_get_stats(struct net_device *netdev,
153                                     struct ethtool_stats *stats,
154                                     u64 *data)
155 {
156         struct ethsw_port_priv *port_priv = netdev_priv(netdev);
157         int i, err;
158
159         memset(data, 0,
160                sizeof(u64) * ETHSW_NUM_COUNTERS);
161
162         for (i = 0; i < ETHSW_NUM_COUNTERS; i++) {
163                 err = dpsw_if_get_counter(port_priv->ethsw_data->mc_io, 0,
164                                           port_priv->ethsw_data->dpsw_handle,
165                                           port_priv->idx,
166                                           ethsw_ethtool_counters[i].id,
167                                           &data[i]);
168                 if (err)
169                         netdev_err(netdev, "dpsw_if_get_counter[%s] err %d\n",
170                                    ethsw_ethtool_counters[i].name, err);
171         }
172 }
173
174 const struct ethtool_ops ethsw_port_ethtool_ops = {
175         .get_drvinfo            = ethsw_get_drvinfo,
176         .get_link               = ethtool_op_get_link,
177         .get_link_ksettings     = ethsw_get_link_ksettings,
178         .set_link_ksettings     = ethsw_set_link_ksettings,
179         .get_strings            = ethsw_ethtool_get_strings,
180         .get_ethtool_stats      = ethsw_ethtool_get_stats,
181         .get_sset_count         = ethsw_ethtool_get_sset_count,
182 };