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 | ||
92 | void stmmac_ethtool_getdrvinfo(struct net_device *dev, | |
93 | struct ethtool_drvinfo *info) | |
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 | ||
107 | int stmmac_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd) | |
108 | { | |
109 | struct stmmac_priv *priv = netdev_priv(dev); | |
110 | struct phy_device *phy = priv->phydev; | |
111 | int rc; | |
112 | if (phy == NULL) { | |
113 | pr_err("%s: %s: PHY is not registered\n", | |
114 | __func__, dev->name); | |
115 | return -ENODEV; | |
116 | } | |
117 | if (!netif_running(dev)) { | |
118 | pr_err("%s: interface is disabled: we cannot track " | |
119 | "link speed / duplex setting\n", dev->name); | |
120 | return -EBUSY; | |
121 | } | |
122 | cmd->transceiver = XCVR_INTERNAL; | |
123 | spin_lock_irq(&priv->lock); | |
124 | rc = phy_ethtool_gset(phy, cmd); | |
125 | spin_unlock_irq(&priv->lock); | |
126 | return rc; | |
127 | } | |
128 | ||
129 | int stmmac_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd) | |
130 | { | |
131 | struct stmmac_priv *priv = netdev_priv(dev); | |
132 | struct phy_device *phy = priv->phydev; | |
133 | int rc; | |
134 | ||
135 | spin_lock(&priv->lock); | |
136 | rc = phy_ethtool_sset(phy, cmd); | |
137 | spin_unlock(&priv->lock); | |
138 | ||
139 | return rc; | |
140 | } | |
141 | ||
142 | u32 stmmac_ethtool_getmsglevel(struct net_device *dev) | |
143 | { | |
144 | struct stmmac_priv *priv = netdev_priv(dev); | |
145 | return priv->msg_enable; | |
146 | } | |
147 | ||
148 | void stmmac_ethtool_setmsglevel(struct net_device *dev, u32 level) | |
149 | { | |
150 | struct stmmac_priv *priv = netdev_priv(dev); | |
151 | priv->msg_enable = level; | |
152 | ||
153 | } | |
154 | ||
155 | int stmmac_check_if_running(struct net_device *dev) | |
156 | { | |
157 | if (!netif_running(dev)) | |
158 | return -EBUSY; | |
159 | return 0; | |
160 | } | |
161 | ||
162 | int stmmac_ethtool_get_regs_len(struct net_device *dev) | |
163 | { | |
164 | return REG_SPACE_SIZE; | |
165 | } | |
166 | ||
167 | void stmmac_ethtool_gregs(struct net_device *dev, | |
168 | struct ethtool_regs *regs, void *space) | |
169 | { | |
170 | int i; | |
171 | u32 *reg_space = (u32 *) space; | |
172 | ||
173 | struct stmmac_priv *priv = netdev_priv(dev); | |
174 | ||
175 | memset(reg_space, 0x0, REG_SPACE_SIZE); | |
176 | ||
177 | if (!priv->is_gmac) { | |
178 | /* MAC registers */ | |
179 | for (i = 0; i < 12; i++) | |
180 | reg_space[i] = readl(dev->base_addr + (i * 4)); | |
181 | /* DMA registers */ | |
182 | for (i = 0; i < 9; i++) | |
183 | reg_space[i + 12] = | |
184 | readl(dev->base_addr + (DMA_BUS_MODE + (i * 4))); | |
185 | reg_space[22] = readl(dev->base_addr + DMA_CUR_TX_BUF_ADDR); | |
186 | reg_space[23] = readl(dev->base_addr + DMA_CUR_RX_BUF_ADDR); | |
187 | } else { | |
188 | /* MAC registers */ | |
189 | for (i = 0; i < 55; i++) | |
190 | reg_space[i] = readl(dev->base_addr + (i * 4)); | |
191 | /* DMA registers */ | |
192 | for (i = 0; i < 22; i++) | |
193 | reg_space[i + 55] = | |
194 | readl(dev->base_addr + (DMA_BUS_MODE + (i * 4))); | |
195 | } | |
47dd7a54 GC |
196 | } |
197 | ||
198 | int stmmac_ethtool_set_tx_csum(struct net_device *netdev, u32 data) | |
199 | { | |
200 | if (data) | |
201 | netdev->features |= NETIF_F_HW_CSUM; | |
202 | else | |
203 | netdev->features &= ~NETIF_F_HW_CSUM; | |
204 | ||
205 | return 0; | |
206 | } | |
207 | ||
208 | u32 stmmac_ethtool_get_rx_csum(struct net_device *dev) | |
209 | { | |
210 | struct stmmac_priv *priv = netdev_priv(dev); | |
211 | ||
212 | return priv->rx_csum; | |
213 | } | |
214 | ||
215 | static void | |
216 | stmmac_get_pauseparam(struct net_device *netdev, | |
217 | struct ethtool_pauseparam *pause) | |
218 | { | |
219 | struct stmmac_priv *priv = netdev_priv(netdev); | |
220 | ||
221 | spin_lock(&priv->lock); | |
222 | ||
223 | pause->rx_pause = 0; | |
224 | pause->tx_pause = 0; | |
225 | pause->autoneg = priv->phydev->autoneg; | |
226 | ||
227 | if (priv->flow_ctrl & FLOW_RX) | |
228 | pause->rx_pause = 1; | |
229 | if (priv->flow_ctrl & FLOW_TX) | |
230 | pause->tx_pause = 1; | |
231 | ||
232 | spin_unlock(&priv->lock); | |
47dd7a54 GC |
233 | } |
234 | ||
235 | static int | |
236 | stmmac_set_pauseparam(struct net_device *netdev, | |
237 | struct ethtool_pauseparam *pause) | |
238 | { | |
239 | struct stmmac_priv *priv = netdev_priv(netdev); | |
240 | struct phy_device *phy = priv->phydev; | |
241 | int new_pause = FLOW_OFF; | |
242 | int ret = 0; | |
243 | ||
244 | spin_lock(&priv->lock); | |
245 | ||
246 | if (pause->rx_pause) | |
247 | new_pause |= FLOW_RX; | |
248 | if (pause->tx_pause) | |
249 | new_pause |= FLOW_TX; | |
250 | ||
251 | priv->flow_ctrl = new_pause; | |
252 | ||
253 | if (phy->autoneg) { | |
254 | if (netif_running(netdev)) { | |
255 | struct ethtool_cmd cmd; | |
256 | /* auto-negotiation automatically restarted */ | |
257 | cmd.cmd = ETHTOOL_NWAY_RST; | |
258 | cmd.supported = phy->supported; | |
259 | cmd.advertising = phy->advertising; | |
260 | cmd.autoneg = phy->autoneg; | |
261 | cmd.speed = phy->speed; | |
262 | cmd.duplex = phy->duplex; | |
263 | cmd.phy_address = phy->addr; | |
264 | ret = phy_ethtool_sset(phy, &cmd); | |
265 | } | |
266 | } else { | |
267 | unsigned long ioaddr = netdev->base_addr; | |
db98a0b0 GC |
268 | priv->hw->mac->flow_ctrl(ioaddr, phy->duplex, |
269 | priv->flow_ctrl, priv->pause); | |
47dd7a54 GC |
270 | } |
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); | |
279 | unsigned long ioaddr = dev->base_addr; | |
280 | int i; | |
281 | ||
282 | /* Update HW stats if supported */ | |
db98a0b0 GC |
283 | priv->hw->dma->dma_diagnostic_fr(&dev->stats, (void *) &priv->xstats, |
284 | ioaddr); | |
47dd7a54 GC |
285 | |
286 | for (i = 0; i < STMMAC_STATS_LEN; i++) { | |
287 | char *p = (char *)priv + stmmac_gstrings_stats[i].stat_offset; | |
288 | data[i] = (stmmac_gstrings_stats[i].sizeof_stat == | |
289 | sizeof(u64)) ? (*(u64 *)p) : (*(u32 *)p); | |
290 | } | |
47dd7a54 GC |
291 | } |
292 | ||
293 | static int stmmac_get_sset_count(struct net_device *netdev, int sset) | |
294 | { | |
295 | switch (sset) { | |
296 | case ETH_SS_STATS: | |
297 | return STMMAC_STATS_LEN; | |
298 | default: | |
299 | return -EOPNOTSUPP; | |
300 | } | |
301 | } | |
302 | ||
303 | static void stmmac_get_strings(struct net_device *dev, u32 stringset, u8 *data) | |
304 | { | |
305 | int i; | |
306 | u8 *p = data; | |
307 | ||
308 | switch (stringset) { | |
309 | case ETH_SS_STATS: | |
310 | for (i = 0; i < STMMAC_STATS_LEN; i++) { | |
311 | memcpy(p, stmmac_gstrings_stats[i].stat_string, | |
312 | ETH_GSTRING_LEN); | |
313 | p += ETH_GSTRING_LEN; | |
314 | } | |
315 | break; | |
316 | default: | |
317 | WARN_ON(1); | |
318 | break; | |
319 | } | |
47dd7a54 GC |
320 | } |
321 | ||
322 | /* Currently only support WOL through Magic packet. */ | |
323 | static void stmmac_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) | |
324 | { | |
325 | struct stmmac_priv *priv = netdev_priv(dev); | |
326 | ||
327 | spin_lock_irq(&priv->lock); | |
328 | if (priv->wolenabled == PMT_SUPPORTED) { | |
329 | wol->supported = WAKE_MAGIC; | |
330 | wol->wolopts = priv->wolopts; | |
331 | } | |
332 | spin_unlock_irq(&priv->lock); | |
333 | } | |
334 | ||
335 | static int stmmac_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) | |
336 | { | |
337 | struct stmmac_priv *priv = netdev_priv(dev); | |
338 | u32 support = WAKE_MAGIC; | |
339 | ||
340 | if (priv->wolenabled == PMT_NOT_SUPPORTED) | |
341 | return -EINVAL; | |
342 | ||
343 | if (wol->wolopts & ~support) | |
344 | return -EINVAL; | |
345 | ||
346 | if (wol->wolopts == 0) | |
347 | device_set_wakeup_enable(priv->device, 0); | |
348 | else | |
349 | device_set_wakeup_enable(priv->device, 1); | |
350 | ||
351 | spin_lock_irq(&priv->lock); | |
352 | priv->wolopts = wol->wolopts; | |
353 | spin_unlock_irq(&priv->lock); | |
354 | ||
355 | return 0; | |
356 | } | |
357 | ||
358 | static struct ethtool_ops stmmac_ethtool_ops = { | |
359 | .begin = stmmac_check_if_running, | |
360 | .get_drvinfo = stmmac_ethtool_getdrvinfo, | |
361 | .get_settings = stmmac_ethtool_getsettings, | |
362 | .set_settings = stmmac_ethtool_setsettings, | |
363 | .get_msglevel = stmmac_ethtool_getmsglevel, | |
364 | .set_msglevel = stmmac_ethtool_setmsglevel, | |
365 | .get_regs = stmmac_ethtool_gregs, | |
366 | .get_regs_len = stmmac_ethtool_get_regs_len, | |
367 | .get_link = ethtool_op_get_link, | |
368 | .get_rx_csum = stmmac_ethtool_get_rx_csum, | |
369 | .get_tx_csum = ethtool_op_get_tx_csum, | |
370 | .set_tx_csum = stmmac_ethtool_set_tx_csum, | |
371 | .get_sg = ethtool_op_get_sg, | |
372 | .set_sg = ethtool_op_set_sg, | |
373 | .get_pauseparam = stmmac_get_pauseparam, | |
374 | .set_pauseparam = stmmac_set_pauseparam, | |
375 | .get_ethtool_stats = stmmac_get_ethtool_stats, | |
376 | .get_strings = stmmac_get_strings, | |
377 | .get_wol = stmmac_get_wol, | |
378 | .set_wol = stmmac_set_wol, | |
379 | .get_sset_count = stmmac_get_sset_count, | |
380 | #ifdef NETIF_F_TSO | |
381 | .get_tso = ethtool_op_get_tso, | |
382 | .set_tso = ethtool_op_set_tso, | |
383 | #endif | |
384 | }; | |
385 | ||
386 | void stmmac_set_ethtool_ops(struct net_device *netdev) | |
387 | { | |
388 | SET_ETHTOOL_OPS(netdev, &stmmac_ethtool_ops); | |
389 | } |