]>
Commit | Line | Data |
---|---|---|
47dd7a54 GC |
1 | /******************************************************************************* |
2 | STMMAC Ethtool support | |
3 | ||
4 | Copyright (C) 2007-2009 STMicroelectronics Ltd | |
5 | ||
6 | This program is free software; you can redistribute it and/or modify it | |
7 | under the terms and conditions of the GNU General Public License, | |
8 | version 2, as published by the Free Software Foundation. | |
9 | ||
10 | This program is distributed in the hope it will be useful, but WITHOUT | |
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | more details. | |
14 | ||
15 | You should have received a copy of the GNU General Public License along with | |
16 | this program; if not, write to the Free Software Foundation, Inc., | |
17 | 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | |
18 | ||
19 | The full GNU General Public License is included in this distribution in | |
20 | the file called "COPYING". | |
21 | ||
22 | Author: Giuseppe Cavallaro <peppe.cavallaro@st.com> | |
23 | *******************************************************************************/ | |
24 | ||
25 | #include <linux/etherdevice.h> | |
26 | #include <linux/ethtool.h> | |
27 | #include <linux/mii.h> | |
28 | #include <linux/phy.h> | |
29 | ||
30 | #include "stmmac.h" | |
aec7ff27 | 31 | #include "dwmac_dma.h" |
47dd7a54 GC |
32 | |
33 | #define REG_SPACE_SIZE 0x1054 | |
34 | #define MAC100_ETHTOOL_NAME "st_mac100" | |
35 | #define GMAC_ETHTOOL_NAME "st_gmac" | |
36 | ||
37 | struct stmmac_stats { | |
38 | char stat_string[ETH_GSTRING_LEN]; | |
39 | int sizeof_stat; | |
40 | int stat_offset; | |
41 | }; | |
42 | ||
43 | #define STMMAC_STAT(m) \ | |
44 | { #m, FIELD_SIZEOF(struct stmmac_extra_stats, m), \ | |
45 | offsetof(struct stmmac_priv, xstats.m)} | |
46 | ||
47 | static const struct stmmac_stats stmmac_gstrings_stats[] = { | |
48 | STMMAC_STAT(tx_underflow), | |
49 | STMMAC_STAT(tx_carrier), | |
50 | STMMAC_STAT(tx_losscarrier), | |
51 | STMMAC_STAT(tx_heartbeat), | |
52 | STMMAC_STAT(tx_deferred), | |
53 | STMMAC_STAT(tx_vlan), | |
54 | STMMAC_STAT(rx_vlan), | |
55 | STMMAC_STAT(tx_jabber), | |
56 | STMMAC_STAT(tx_frame_flushed), | |
57 | STMMAC_STAT(tx_payload_error), | |
58 | STMMAC_STAT(tx_ip_header_error), | |
59 | STMMAC_STAT(rx_desc), | |
60 | STMMAC_STAT(rx_partial), | |
61 | STMMAC_STAT(rx_runt), | |
62 | STMMAC_STAT(rx_toolong), | |
63 | STMMAC_STAT(rx_collision), | |
64 | STMMAC_STAT(rx_crc), | |
1b924032 | 65 | STMMAC_STAT(rx_length), |
47dd7a54 GC |
66 | STMMAC_STAT(rx_mii), |
67 | STMMAC_STAT(rx_multicast), | |
68 | STMMAC_STAT(rx_gmac_overflow), | |
69 | STMMAC_STAT(rx_watchdog), | |
70 | STMMAC_STAT(da_rx_filter_fail), | |
71 | STMMAC_STAT(sa_rx_filter_fail), | |
72 | STMMAC_STAT(rx_missed_cntr), | |
73 | STMMAC_STAT(rx_overflow_cntr), | |
74 | STMMAC_STAT(tx_undeflow_irq), | |
75 | STMMAC_STAT(tx_process_stopped_irq), | |
76 | STMMAC_STAT(tx_jabber_irq), | |
77 | STMMAC_STAT(rx_overflow_irq), | |
78 | STMMAC_STAT(rx_buf_unav_irq), | |
79 | STMMAC_STAT(rx_process_stopped_irq), | |
80 | STMMAC_STAT(rx_watchdog_irq), | |
81 | STMMAC_STAT(tx_early_irq), | |
82 | STMMAC_STAT(fatal_bus_error_irq), | |
83 | STMMAC_STAT(threshold), | |
84 | STMMAC_STAT(tx_pkt_n), | |
85 | STMMAC_STAT(rx_pkt_n), | |
86 | STMMAC_STAT(poll_n), | |
87 | STMMAC_STAT(sched_timer_n), | |
88 | STMMAC_STAT(normal_irq_n), | |
89 | }; | |
90 | #define STMMAC_STATS_LEN ARRAY_SIZE(stmmac_gstrings_stats) | |
91 | ||
8ee17ae6 | 92 | static void stmmac_ethtool_getdrvinfo(struct net_device *dev, |
93 | struct ethtool_drvinfo *info) | |
47dd7a54 GC |
94 | { |
95 | struct stmmac_priv *priv = netdev_priv(dev); | |
96 | ||
97 | if (!priv->is_gmac) | |
98 | strcpy(info->driver, MAC100_ETHTOOL_NAME); | |
99 | else | |
100 | strcpy(info->driver, GMAC_ETHTOOL_NAME); | |
101 | ||
102 | strcpy(info->version, DRV_MODULE_VERSION); | |
103 | info->fw_version[0] = '\0'; | |
104 | info->n_stats = STMMAC_STATS_LEN; | |
47dd7a54 GC |
105 | } |
106 | ||
8ee17ae6 | 107 | static int stmmac_ethtool_getsettings(struct net_device *dev, |
108 | struct ethtool_cmd *cmd) | |
47dd7a54 GC |
109 | { |
110 | struct stmmac_priv *priv = netdev_priv(dev); | |
111 | struct phy_device *phy = priv->phydev; | |
112 | int rc; | |
113 | if (phy == NULL) { | |
114 | pr_err("%s: %s: PHY is not registered\n", | |
115 | __func__, dev->name); | |
116 | return -ENODEV; | |
117 | } | |
118 | if (!netif_running(dev)) { | |
119 | pr_err("%s: interface is disabled: we cannot track " | |
120 | "link speed / duplex setting\n", dev->name); | |
121 | return -EBUSY; | |
122 | } | |
123 | cmd->transceiver = XCVR_INTERNAL; | |
124 | spin_lock_irq(&priv->lock); | |
125 | rc = phy_ethtool_gset(phy, cmd); | |
126 | spin_unlock_irq(&priv->lock); | |
127 | return rc; | |
128 | } | |
129 | ||
8ee17ae6 | 130 | static int stmmac_ethtool_setsettings(struct net_device *dev, |
131 | struct ethtool_cmd *cmd) | |
47dd7a54 GC |
132 | { |
133 | struct stmmac_priv *priv = netdev_priv(dev); | |
134 | struct phy_device *phy = priv->phydev; | |
135 | int rc; | |
136 | ||
137 | spin_lock(&priv->lock); | |
138 | rc = phy_ethtool_sset(phy, cmd); | |
139 | spin_unlock(&priv->lock); | |
140 | ||
141 | return rc; | |
142 | } | |
143 | ||
8ee17ae6 | 144 | static u32 stmmac_ethtool_getmsglevel(struct net_device *dev) |
47dd7a54 GC |
145 | { |
146 | struct stmmac_priv *priv = netdev_priv(dev); | |
147 | return priv->msg_enable; | |
148 | } | |
149 | ||
8ee17ae6 | 150 | static void stmmac_ethtool_setmsglevel(struct net_device *dev, u32 level) |
47dd7a54 GC |
151 | { |
152 | struct stmmac_priv *priv = netdev_priv(dev); | |
153 | priv->msg_enable = level; | |
154 | ||
155 | } | |
156 | ||
8ee17ae6 | 157 | static int stmmac_check_if_running(struct net_device *dev) |
47dd7a54 GC |
158 | { |
159 | if (!netif_running(dev)) | |
160 | return -EBUSY; | |
161 | return 0; | |
162 | } | |
163 | ||
8ee17ae6 | 164 | static int stmmac_ethtool_get_regs_len(struct net_device *dev) |
47dd7a54 GC |
165 | { |
166 | return REG_SPACE_SIZE; | |
167 | } | |
168 | ||
8ee17ae6 | 169 | static void stmmac_ethtool_gregs(struct net_device *dev, |
47dd7a54 GC |
170 | struct ethtool_regs *regs, void *space) |
171 | { | |
172 | int i; | |
173 | u32 *reg_space = (u32 *) space; | |
174 | ||
175 | struct stmmac_priv *priv = netdev_priv(dev); | |
176 | ||
177 | memset(reg_space, 0x0, REG_SPACE_SIZE); | |
178 | ||
179 | if (!priv->is_gmac) { | |
180 | /* MAC registers */ | |
181 | for (i = 0; i < 12; i++) | |
ad01b7d4 | 182 | reg_space[i] = readl(priv->ioaddr + (i * 4)); |
47dd7a54 GC |
183 | /* DMA registers */ |
184 | for (i = 0; i < 9; i++) | |
185 | reg_space[i + 12] = | |
ad01b7d4 GC |
186 | readl(priv->ioaddr + (DMA_BUS_MODE + (i * 4))); |
187 | reg_space[22] = readl(priv->ioaddr + DMA_CUR_TX_BUF_ADDR); | |
188 | reg_space[23] = readl(priv->ioaddr + DMA_CUR_RX_BUF_ADDR); | |
47dd7a54 GC |
189 | } else { |
190 | /* MAC registers */ | |
191 | for (i = 0; i < 55; i++) | |
ad01b7d4 | 192 | reg_space[i] = readl(priv->ioaddr + (i * 4)); |
47dd7a54 GC |
193 | /* DMA registers */ |
194 | for (i = 0; i < 22; i++) | |
195 | reg_space[i + 55] = | |
ad01b7d4 | 196 | readl(priv->ioaddr + (DMA_BUS_MODE + (i * 4))); |
47dd7a54 | 197 | } |
47dd7a54 GC |
198 | } |
199 | ||
8ee17ae6 | 200 | static int stmmac_ethtool_set_tx_csum(struct net_device *netdev, u32 data) |
47dd7a54 GC |
201 | { |
202 | if (data) | |
203 | netdev->features |= NETIF_F_HW_CSUM; | |
204 | else | |
205 | netdev->features &= ~NETIF_F_HW_CSUM; | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
8ee17ae6 | 210 | static u32 stmmac_ethtool_get_rx_csum(struct net_device *dev) |
47dd7a54 GC |
211 | { |
212 | struct stmmac_priv *priv = netdev_priv(dev); | |
213 | ||
ebbb293f | 214 | return priv->rx_coe; |
47dd7a54 GC |
215 | } |
216 | ||
217 | static void | |
218 | stmmac_get_pauseparam(struct net_device *netdev, | |
219 | struct ethtool_pauseparam *pause) | |
220 | { | |
221 | struct stmmac_priv *priv = netdev_priv(netdev); | |
222 | ||
223 | spin_lock(&priv->lock); | |
224 | ||
225 | pause->rx_pause = 0; | |
226 | pause->tx_pause = 0; | |
227 | pause->autoneg = priv->phydev->autoneg; | |
228 | ||
229 | if (priv->flow_ctrl & FLOW_RX) | |
230 | pause->rx_pause = 1; | |
231 | if (priv->flow_ctrl & FLOW_TX) | |
232 | pause->tx_pause = 1; | |
233 | ||
234 | spin_unlock(&priv->lock); | |
47dd7a54 GC |
235 | } |
236 | ||
237 | static int | |
238 | stmmac_set_pauseparam(struct net_device *netdev, | |
239 | struct ethtool_pauseparam *pause) | |
240 | { | |
241 | struct stmmac_priv *priv = netdev_priv(netdev); | |
242 | struct phy_device *phy = priv->phydev; | |
243 | int new_pause = FLOW_OFF; | |
244 | int ret = 0; | |
245 | ||
246 | spin_lock(&priv->lock); | |
247 | ||
248 | if (pause->rx_pause) | |
249 | new_pause |= FLOW_RX; | |
250 | if (pause->tx_pause) | |
251 | new_pause |= FLOW_TX; | |
252 | ||
253 | priv->flow_ctrl = new_pause; | |
254 | ||
255 | if (phy->autoneg) { | |
256 | if (netif_running(netdev)) { | |
257 | struct ethtool_cmd cmd; | |
258 | /* auto-negotiation automatically restarted */ | |
259 | cmd.cmd = ETHTOOL_NWAY_RST; | |
260 | cmd.supported = phy->supported; | |
261 | cmd.advertising = phy->advertising; | |
262 | cmd.autoneg = phy->autoneg; | |
263 | cmd.speed = phy->speed; | |
264 | cmd.duplex = phy->duplex; | |
265 | cmd.phy_address = phy->addr; | |
266 | ret = phy_ethtool_sset(phy, &cmd); | |
267 | } | |
ad01b7d4 GC |
268 | } else |
269 | priv->hw->mac->flow_ctrl(priv->ioaddr, phy->duplex, | |
db98a0b0 | 270 | priv->flow_ctrl, priv->pause); |
47dd7a54 GC |
271 | spin_unlock(&priv->lock); |
272 | return ret; | |
273 | } | |
274 | ||
275 | static void stmmac_get_ethtool_stats(struct net_device *dev, | |
276 | struct ethtool_stats *dummy, u64 *data) | |
277 | { | |
278 | struct stmmac_priv *priv = netdev_priv(dev); | |
47dd7a54 GC |
279 | int i; |
280 | ||
281 | /* Update HW stats if supported */ | |
db98a0b0 | 282 | priv->hw->dma->dma_diagnostic_fr(&dev->stats, (void *) &priv->xstats, |
ad01b7d4 | 283 | priv->ioaddr); |
47dd7a54 GC |
284 | |
285 | for (i = 0; i < STMMAC_STATS_LEN; i++) { | |
286 | char *p = (char *)priv + stmmac_gstrings_stats[i].stat_offset; | |
287 | data[i] = (stmmac_gstrings_stats[i].sizeof_stat == | |
288 | sizeof(u64)) ? (*(u64 *)p) : (*(u32 *)p); | |
289 | } | |
47dd7a54 GC |
290 | } |
291 | ||
292 | static int stmmac_get_sset_count(struct net_device *netdev, int sset) | |
293 | { | |
294 | switch (sset) { | |
295 | case ETH_SS_STATS: | |
296 | return STMMAC_STATS_LEN; | |
297 | default: | |
298 | return -EOPNOTSUPP; | |
299 | } | |
300 | } | |
301 | ||
302 | static void stmmac_get_strings(struct net_device *dev, u32 stringset, u8 *data) | |
303 | { | |
304 | int i; | |
305 | u8 *p = data; | |
306 | ||
307 | switch (stringset) { | |
308 | case ETH_SS_STATS: | |
309 | for (i = 0; i < STMMAC_STATS_LEN; i++) { | |
310 | memcpy(p, stmmac_gstrings_stats[i].stat_string, | |
311 | ETH_GSTRING_LEN); | |
312 | p += ETH_GSTRING_LEN; | |
313 | } | |
314 | break; | |
315 | default: | |
316 | WARN_ON(1); | |
317 | break; | |
318 | } | |
47dd7a54 GC |
319 | } |
320 | ||
321 | /* Currently only support WOL through Magic packet. */ | |
322 | static void stmmac_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) | |
323 | { | |
324 | struct stmmac_priv *priv = netdev_priv(dev); | |
325 | ||
326 | spin_lock_irq(&priv->lock); | |
543876c9 | 327 | if (device_can_wakeup(priv->device)) { |
47dd7a54 GC |
328 | wol->supported = WAKE_MAGIC; |
329 | wol->wolopts = priv->wolopts; | |
330 | } | |
331 | spin_unlock_irq(&priv->lock); | |
332 | } | |
333 | ||
334 | static int stmmac_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) | |
335 | { | |
336 | struct stmmac_priv *priv = netdev_priv(dev); | |
337 | u32 support = WAKE_MAGIC; | |
338 | ||
543876c9 | 339 | if (!device_can_wakeup(priv->device)) |
47dd7a54 GC |
340 | return -EINVAL; |
341 | ||
342 | if (wol->wolopts & ~support) | |
343 | return -EINVAL; | |
344 | ||
543876c9 GC |
345 | if (wol->wolopts) { |
346 | pr_info("stmmac: wakeup enable\n"); | |
47dd7a54 | 347 | device_set_wakeup_enable(priv->device, 1); |
543876c9 GC |
348 | enable_irq_wake(dev->irq); |
349 | } else { | |
350 | device_set_wakeup_enable(priv->device, 0); | |
351 | disable_irq_wake(dev->irq); | |
352 | } | |
47dd7a54 GC |
353 | |
354 | spin_lock_irq(&priv->lock); | |
355 | priv->wolopts = wol->wolopts; | |
356 | spin_unlock_irq(&priv->lock); | |
357 | ||
358 | return 0; | |
359 | } | |
360 | ||
361 | static struct ethtool_ops stmmac_ethtool_ops = { | |
362 | .begin = stmmac_check_if_running, | |
363 | .get_drvinfo = stmmac_ethtool_getdrvinfo, | |
364 | .get_settings = stmmac_ethtool_getsettings, | |
365 | .set_settings = stmmac_ethtool_setsettings, | |
366 | .get_msglevel = stmmac_ethtool_getmsglevel, | |
367 | .set_msglevel = stmmac_ethtool_setmsglevel, | |
368 | .get_regs = stmmac_ethtool_gregs, | |
369 | .get_regs_len = stmmac_ethtool_get_regs_len, | |
370 | .get_link = ethtool_op_get_link, | |
371 | .get_rx_csum = stmmac_ethtool_get_rx_csum, | |
372 | .get_tx_csum = ethtool_op_get_tx_csum, | |
373 | .set_tx_csum = stmmac_ethtool_set_tx_csum, | |
374 | .get_sg = ethtool_op_get_sg, | |
375 | .set_sg = ethtool_op_set_sg, | |
376 | .get_pauseparam = stmmac_get_pauseparam, | |
377 | .set_pauseparam = stmmac_set_pauseparam, | |
378 | .get_ethtool_stats = stmmac_get_ethtool_stats, | |
379 | .get_strings = stmmac_get_strings, | |
380 | .get_wol = stmmac_get_wol, | |
381 | .set_wol = stmmac_set_wol, | |
382 | .get_sset_count = stmmac_get_sset_count, | |
47dd7a54 GC |
383 | .get_tso = ethtool_op_get_tso, |
384 | .set_tso = ethtool_op_set_tso, | |
47dd7a54 GC |
385 | }; |
386 | ||
387 | void stmmac_set_ethtool_ops(struct net_device *netdev) | |
388 | { | |
389 | SET_ETHTOOL_OPS(netdev, &stmmac_ethtool_ops); | |
390 | } |