blob: 0443546fc4274fe18850606f007d301b993ce744 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2
3 mii.c: MII interface library
4
5 Maintained by Jeff Garzik <jgarzik@pobox.com>
6 Copyright 2001,2002 Jeff Garzik
7
8 Various code came from myson803.c and other files by
9 Donald Becker. Copyright:
10
11 Written 1998-2002 by Donald Becker.
12
13 This software may be used and distributed according
14 to the terms of the GNU General Public License (GPL),
15 incorporated herein by reference. Drivers based on
16 or derived from this code fall under the GPL and must
17 retain the authorship, copyright and license notice.
18 This file is not a complete program and may only be
19 used when the entire operating system is licensed
20 under the GPL.
21
22 The author may be reached as becker@scyld.com, or C/O
23 Scyld Computing Corporation
24 410 Severn Ave., Suite 210
25 Annapolis MD 21403
26
27
28 */
29
30#include <linux/kernel.h>
31#include <linux/module.h>
32#include <linux/netdevice.h>
33#include <linux/ethtool.h>
Ben Hutchings9c4df532012-02-29 14:26:22 +000034#include <linux/mii.h>
Ben Hutchings59747002009-04-29 08:34:44 +000035
36static u32 mii_get_an(struct mii_if_info *mii, u16 addr)
37{
Ben Hutchings59747002009-04-29 08:34:44 +000038 int advert;
39
40 advert = mii->mdio_read(mii->dev, mii->phy_id, addr);
Ben Hutchings59747002009-04-29 08:34:44 +000041
Matt Carlson37f07022011-11-17 14:30:55 +000042 return mii_lpa_to_ethtool_lpa_t(advert);
Ben Hutchings59747002009-04-29 08:34:44 +000043}
Linus Torvalds1da177e2005-04-16 15:20:36 -070044
Randy Dunlap32684ec2007-04-06 11:08:24 -070045/**
46 * mii_ethtool_gset - get settings that are specified in @ecmd
47 * @mii: MII interface
48 * @ecmd: requested ethtool_cmd
49 *
David Decotigny8ae6daca2011-04-27 18:32:38 +000050 * The @ecmd parameter is expected to have been cleared before calling
51 * mii_ethtool_gset().
52 *
Randy Dunlap32684ec2007-04-06 11:08:24 -070053 * Returns 0 for success, negative on error.
54 */
Linus Torvalds1da177e2005-04-16 15:20:36 -070055int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
56{
57 struct net_device *dev = mii->dev;
Ben Hutchings59747002009-04-29 08:34:44 +000058 u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
59 u32 nego;
Linus Torvalds1da177e2005-04-16 15:20:36 -070060
61 ecmd->supported =
62 (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
63 SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
64 SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
65 if (mii->supports_gmii)
66 ecmd->supported |= SUPPORTED_1000baseT_Half |
67 SUPPORTED_1000baseT_Full;
68
69 /* only supports twisted-pair */
70 ecmd->port = PORT_MII;
71
72 /* only supports internal transceiver */
73 ecmd->transceiver = XCVR_INTERNAL;
74
75 /* this isn't fully supported at higher layers */
76 ecmd->phy_address = mii->phy_id;
Ben Hutchings9c4df532012-02-29 14:26:22 +000077 ecmd->mdio_support = ETH_MDIO_SUPPORTS_C22;
Linus Torvalds1da177e2005-04-16 15:20:36 -070078
79 ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
Linus Torvalds1da177e2005-04-16 15:20:36 -070080
81 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Ben Hutchings59747002009-04-29 08:34:44 +000082 bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
Linus Torvalds1da177e2005-04-16 15:20:36 -070083 if (mii->supports_gmii) {
Ben Hutchings59747002009-04-29 08:34:44 +000084 ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
85 stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
Linus Torvalds1da177e2005-04-16 15:20:36 -070086 }
87 if (bmcr & BMCR_ANENABLE) {
88 ecmd->advertising |= ADVERTISED_Autoneg;
89 ecmd->autoneg = AUTONEG_ENABLE;
Jeff Garzik6aa20a22006-09-13 13:24:59 -040090
Ben Hutchings59747002009-04-29 08:34:44 +000091 ecmd->advertising |= mii_get_an(mii, MII_ADVERTISE);
Matt Carlson28011cf2011-11-16 18:36:59 -050092 if (mii->supports_gmii)
Matt Carlson37f07022011-11-17 14:30:55 +000093 ecmd->advertising |=
94 mii_ctrl1000_to_ethtool_adv_t(ctrl1000);
Ben Hutchings59747002009-04-29 08:34:44 +000095
96 if (bmsr & BMSR_ANEGCOMPLETE) {
97 ecmd->lp_advertising = mii_get_an(mii, MII_LPA);
Matt Carlson28011cf2011-11-16 18:36:59 -050098 ecmd->lp_advertising |=
Matt Carlson37f07022011-11-17 14:30:55 +000099 mii_stat1000_to_ethtool_lpa_t(stat1000);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700100 } else {
Ben Hutchings59747002009-04-29 08:34:44 +0000101 ecmd->lp_advertising = 0;
102 }
103
104 nego = ecmd->advertising & ecmd->lp_advertising;
105
106 if (nego & (ADVERTISED_1000baseT_Full |
107 ADVERTISED_1000baseT_Half)) {
David Decotigny70739492011-04-27 18:32:40 +0000108 ethtool_cmd_speed_set(ecmd, SPEED_1000);
Ben Hutchings59747002009-04-29 08:34:44 +0000109 ecmd->duplex = !!(nego & ADVERTISED_1000baseT_Full);
110 } else if (nego & (ADVERTISED_100baseT_Full |
111 ADVERTISED_100baseT_Half)) {
David Decotigny70739492011-04-27 18:32:40 +0000112 ethtool_cmd_speed_set(ecmd, SPEED_100);
Ben Hutchings59747002009-04-29 08:34:44 +0000113 ecmd->duplex = !!(nego & ADVERTISED_100baseT_Full);
114 } else {
David Decotigny70739492011-04-27 18:32:40 +0000115 ethtool_cmd_speed_set(ecmd, SPEED_10);
Ben Hutchings59747002009-04-29 08:34:44 +0000116 ecmd->duplex = !!(nego & ADVERTISED_10baseT_Full);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700117 }
118 } else {
119 ecmd->autoneg = AUTONEG_DISABLE;
120
David Decotigny70739492011-04-27 18:32:40 +0000121 ethtool_cmd_speed_set(ecmd,
122 ((bmcr & BMCR_SPEED1000 &&
123 (bmcr & BMCR_SPEED100) == 0) ?
124 SPEED_1000 :
125 ((bmcr & BMCR_SPEED100) ?
126 SPEED_100 : SPEED_10)));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127 ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
128 }
129
Ben Hutchings59747002009-04-29 08:34:44 +0000130 mii->full_duplex = ecmd->duplex;
131
Linus Torvalds1da177e2005-04-16 15:20:36 -0700132 /* ignore maxtxpkt, maxrxpkt for now */
133
134 return 0;
135}
136
Randy Dunlap32684ec2007-04-06 11:08:24 -0700137/**
Philippe Reynesbc8ee592016-11-01 16:32:25 +0100138 * mii_ethtool_get_link_ksettings - get settings that are specified in @cmd
139 * @mii: MII interface
140 * @cmd: requested ethtool_link_ksettings
141 *
142 * The @cmd parameter is expected to have been cleared before calling
143 * mii_ethtool_get_link_ksettings().
144 *
145 * Returns 0 for success, negative on error.
146 */
147int mii_ethtool_get_link_ksettings(struct mii_if_info *mii,
148 struct ethtool_link_ksettings *cmd)
149{
150 struct net_device *dev = mii->dev;
151 u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
152 u32 nego, supported, advertising, lp_advertising;
153
154 supported = (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
155 SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
156 SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
157 if (mii->supports_gmii)
158 supported |= SUPPORTED_1000baseT_Half |
159 SUPPORTED_1000baseT_Full;
160
161 /* only supports twisted-pair */
162 cmd->base.port = PORT_MII;
163
164 /* this isn't fully supported at higher layers */
165 cmd->base.phy_address = mii->phy_id;
166 cmd->base.mdio_support = ETH_MDIO_SUPPORTS_C22;
167
168 advertising = ADVERTISED_TP | ADVERTISED_MII;
169
170 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
171 bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
172 if (mii->supports_gmii) {
173 ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
174 stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
175 }
176 if (bmcr & BMCR_ANENABLE) {
177 advertising |= ADVERTISED_Autoneg;
178 cmd->base.autoneg = AUTONEG_ENABLE;
179
180 advertising |= mii_get_an(mii, MII_ADVERTISE);
181 if (mii->supports_gmii)
182 advertising |= mii_ctrl1000_to_ethtool_adv_t(ctrl1000);
183
184 if (bmsr & BMSR_ANEGCOMPLETE) {
185 lp_advertising = mii_get_an(mii, MII_LPA);
186 lp_advertising |=
187 mii_stat1000_to_ethtool_lpa_t(stat1000);
188 } else {
189 lp_advertising = 0;
190 }
191
192 nego = advertising & lp_advertising;
193
194 if (nego & (ADVERTISED_1000baseT_Full |
195 ADVERTISED_1000baseT_Half)) {
196 cmd->base.speed = SPEED_1000;
197 cmd->base.duplex = !!(nego & ADVERTISED_1000baseT_Full);
198 } else if (nego & (ADVERTISED_100baseT_Full |
199 ADVERTISED_100baseT_Half)) {
200 cmd->base.speed = SPEED_100;
201 cmd->base.duplex = !!(nego & ADVERTISED_100baseT_Full);
202 } else {
203 cmd->base.speed = SPEED_10;
204 cmd->base.duplex = !!(nego & ADVERTISED_10baseT_Full);
205 }
206 } else {
207 cmd->base.autoneg = AUTONEG_DISABLE;
208
209 cmd->base.speed = ((bmcr & BMCR_SPEED1000 &&
210 (bmcr & BMCR_SPEED100) == 0) ?
211 SPEED_1000 :
212 ((bmcr & BMCR_SPEED100) ?
213 SPEED_100 : SPEED_10));
214 cmd->base.duplex = (bmcr & BMCR_FULLDPLX) ?
215 DUPLEX_FULL : DUPLEX_HALF;
216 }
217
218 mii->full_duplex = cmd->base.duplex;
219
220 ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported,
221 supported);
222 ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising,
223 advertising);
224 ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.lp_advertising,
225 lp_advertising);
226
227 /* ignore maxtxpkt, maxrxpkt for now */
228
229 return 0;
230}
231
232/**
Randy Dunlap32684ec2007-04-06 11:08:24 -0700233 * mii_ethtool_sset - set settings that are specified in @ecmd
234 * @mii: MII interface
235 * @ecmd: requested ethtool_cmd
236 *
237 * Returns 0 for success, negative on error.
238 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700239int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
240{
241 struct net_device *dev = mii->dev;
David Decotigny25db0332011-04-27 18:32:39 +0000242 u32 speed = ethtool_cmd_speed(ecmd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700243
David Decotigny25db0332011-04-27 18:32:39 +0000244 if (speed != SPEED_10 &&
245 speed != SPEED_100 &&
246 speed != SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700247 return -EINVAL;
248 if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
249 return -EINVAL;
250 if (ecmd->port != PORT_MII)
251 return -EINVAL;
252 if (ecmd->transceiver != XCVR_INTERNAL)
253 return -EINVAL;
254 if (ecmd->phy_address != mii->phy_id)
255 return -EINVAL;
256 if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
257 return -EINVAL;
David Decotigny25db0332011-04-27 18:32:39 +0000258 if ((speed == SPEED_1000) && (!mii->supports_gmii))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700259 return -EINVAL;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400260
Linus Torvalds1da177e2005-04-16 15:20:36 -0700261 /* ignore supported, maxtxpkt, maxrxpkt */
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400262
Linus Torvalds1da177e2005-04-16 15:20:36 -0700263 if (ecmd->autoneg == AUTONEG_ENABLE) {
264 u32 bmcr, advert, tmp;
265 u32 advert2 = 0, tmp2 = 0;
266
267 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
268 ADVERTISED_10baseT_Full |
269 ADVERTISED_100baseT_Half |
270 ADVERTISED_100baseT_Full |
271 ADVERTISED_1000baseT_Half |
272 ADVERTISED_1000baseT_Full)) == 0)
273 return -EINVAL;
274
275 /* advertise only what has been requested */
276 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
277 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
278 if (mii->supports_gmii) {
279 advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
280 tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
281 }
Matt Carlson37f07022011-11-17 14:30:55 +0000282 tmp |= ethtool_adv_to_mii_adv_t(ecmd->advertising);
Matt Carlson28011cf2011-11-16 18:36:59 -0500283
284 if (mii->supports_gmii)
Matt Carlson37f07022011-11-17 14:30:55 +0000285 tmp2 |=
286 ethtool_adv_to_mii_ctrl1000_t(ecmd->advertising);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700287 if (advert != tmp) {
288 mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
289 mii->advertising = tmp;
290 }
291 if ((mii->supports_gmii) && (advert2 != tmp2))
292 mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400293
Linus Torvalds1da177e2005-04-16 15:20:36 -0700294 /* turn on autonegotiation, and force a renegotiate */
295 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
296 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
297 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
298
299 mii->force_media = 0;
300 } else {
301 u32 bmcr, tmp;
302
303 /* turn off auto negotiation, set speed and duplexity */
304 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400305 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
Linus Torvalds1da177e2005-04-16 15:20:36 -0700306 BMCR_SPEED1000 | BMCR_FULLDPLX);
David Decotigny25db0332011-04-27 18:32:39 +0000307 if (speed == SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700308 tmp |= BMCR_SPEED1000;
David Decotigny25db0332011-04-27 18:32:39 +0000309 else if (speed == SPEED_100)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700310 tmp |= BMCR_SPEED100;
311 if (ecmd->duplex == DUPLEX_FULL) {
312 tmp |= BMCR_FULLDPLX;
313 mii->full_duplex = 1;
314 } else
315 mii->full_duplex = 0;
316 if (bmcr != tmp)
317 mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
318
319 mii->force_media = 1;
320 }
321 return 0;
322}
323
Randy Dunlap32684ec2007-04-06 11:08:24 -0700324/**
Philippe Reynesbc8ee592016-11-01 16:32:25 +0100325 * mii_ethtool_set_link_ksettings - set settings that are specified in @cmd
326 * @mii: MII interfaces
327 * @cmd: requested ethtool_link_ksettings
328 *
329 * Returns 0 for success, negative on error.
330 */
331int mii_ethtool_set_link_ksettings(struct mii_if_info *mii,
332 const struct ethtool_link_ksettings *cmd)
333{
334 struct net_device *dev = mii->dev;
335 u32 speed = cmd->base.speed;
336
337 if (speed != SPEED_10 &&
338 speed != SPEED_100 &&
339 speed != SPEED_1000)
340 return -EINVAL;
341 if (cmd->base.duplex != DUPLEX_HALF && cmd->base.duplex != DUPLEX_FULL)
342 return -EINVAL;
343 if (cmd->base.port != PORT_MII)
344 return -EINVAL;
345 if (cmd->base.phy_address != mii->phy_id)
346 return -EINVAL;
347 if (cmd->base.autoneg != AUTONEG_DISABLE &&
348 cmd->base.autoneg != AUTONEG_ENABLE)
349 return -EINVAL;
350 if ((speed == SPEED_1000) && (!mii->supports_gmii))
351 return -EINVAL;
352
353 /* ignore supported, maxtxpkt, maxrxpkt */
354
355 if (cmd->base.autoneg == AUTONEG_ENABLE) {
356 u32 bmcr, advert, tmp;
357 u32 advert2 = 0, tmp2 = 0;
358 u32 advertising;
359
360 ethtool_convert_link_mode_to_legacy_u32(
361 &advertising, cmd->link_modes.advertising);
362
363 if ((advertising & (ADVERTISED_10baseT_Half |
364 ADVERTISED_10baseT_Full |
365 ADVERTISED_100baseT_Half |
366 ADVERTISED_100baseT_Full |
367 ADVERTISED_1000baseT_Half |
368 ADVERTISED_1000baseT_Full)) == 0)
369 return -EINVAL;
370
371 /* advertise only what has been requested */
372 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
373 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
374 if (mii->supports_gmii) {
375 advert2 = mii->mdio_read(dev, mii->phy_id,
376 MII_CTRL1000);
377 tmp2 = advert2 &
378 ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
379 }
380 tmp |= ethtool_adv_to_mii_adv_t(advertising);
381
382 if (mii->supports_gmii)
383 tmp2 |= ethtool_adv_to_mii_ctrl1000_t(advertising);
384 if (advert != tmp) {
385 mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
386 mii->advertising = tmp;
387 }
388 if ((mii->supports_gmii) && (advert2 != tmp2))
389 mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
390
391 /* turn on autonegotiation, and force a renegotiate */
392 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
393 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
394 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
395
396 mii->force_media = 0;
397 } else {
398 u32 bmcr, tmp;
399
400 /* turn off auto negotiation, set speed and duplexity */
401 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
402 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
403 BMCR_SPEED1000 | BMCR_FULLDPLX);
404 if (speed == SPEED_1000)
405 tmp |= BMCR_SPEED1000;
406 else if (speed == SPEED_100)
407 tmp |= BMCR_SPEED100;
408 if (cmd->base.duplex == DUPLEX_FULL) {
409 tmp |= BMCR_FULLDPLX;
410 mii->full_duplex = 1;
411 } else {
412 mii->full_duplex = 0;
413 }
414 if (bmcr != tmp)
415 mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
416
417 mii->force_media = 1;
418 }
419 return 0;
420}
421
422/**
Randy Dunlap32684ec2007-04-06 11:08:24 -0700423 * mii_check_gmii_support - check if the MII supports Gb interfaces
424 * @mii: the MII interface
425 */
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700426int mii_check_gmii_support(struct mii_if_info *mii)
427{
428 int reg;
429
430 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
431 if (reg & BMSR_ESTATEN) {
432 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
433 if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
434 return 1;
435 }
436
437 return 0;
438}
439
Randy Dunlap32684ec2007-04-06 11:08:24 -0700440/**
441 * mii_link_ok - is link status up/ok
442 * @mii: the MII interface
443 *
444 * Returns 1 if the MII reports link status up/ok, 0 otherwise.
445 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700446int mii_link_ok (struct mii_if_info *mii)
447{
448 /* first, a dummy read, needed to latch some MII phys */
449 mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
450 if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
451 return 1;
452 return 0;
453}
454
Randy Dunlap32684ec2007-04-06 11:08:24 -0700455/**
456 * mii_nway_restart - restart NWay (autonegotiation) for this interface
457 * @mii: the MII interface
458 *
459 * Returns 0 on success, negative on error.
460 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700461int mii_nway_restart (struct mii_if_info *mii)
462{
463 int bmcr;
464 int r = -EINVAL;
465
466 /* if autoneg is off, it's an error */
467 bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
468
469 if (bmcr & BMCR_ANENABLE) {
470 bmcr |= BMCR_ANRESTART;
471 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
472 r = 0;
473 }
474
475 return r;
476}
477
Randy Dunlap32684ec2007-04-06 11:08:24 -0700478/**
479 * mii_check_link - check MII link status
480 * @mii: MII interface
481 *
482 * If the link status changed (previous != current), call
483 * netif_carrier_on() if current link status is Up or call
484 * netif_carrier_off() if current link status is Down.
485 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700486void mii_check_link (struct mii_if_info *mii)
487{
488 int cur_link = mii_link_ok(mii);
489 int prev_link = netif_carrier_ok(mii->dev);
490
491 if (cur_link && !prev_link)
492 netif_carrier_on(mii->dev);
493 else if (prev_link && !cur_link)
494 netif_carrier_off(mii->dev);
495}
496
Randy Dunlap32684ec2007-04-06 11:08:24 -0700497/**
Ben Hutchings5bdc7382015-01-16 17:55:35 +0000498 * mii_check_media - check the MII interface for a carrier/speed/duplex change
Randy Dunlap32684ec2007-04-06 11:08:24 -0700499 * @mii: the MII interface
500 * @ok_to_print: OK to print link up/down messages
501 * @init_media: OK to save duplex mode in @mii
502 *
503 * Returns 1 if the duplex mode changed, 0 if not.
504 * If the media type is forced, always returns 0.
505 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700506unsigned int mii_check_media (struct mii_if_info *mii,
507 unsigned int ok_to_print,
508 unsigned int init_media)
509{
510 unsigned int old_carrier, new_carrier;
511 int advertise, lpa, media, duplex;
512 int lpa2 = 0;
513
Linus Torvalds1da177e2005-04-16 15:20:36 -0700514 /* check current and old link status */
515 old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
516 new_carrier = (unsigned int) mii_link_ok(mii);
517
518 /* if carrier state did not change, this is a "bounce",
519 * just exit as everything is already set correctly
520 */
521 if ((!init_media) && (old_carrier == new_carrier))
522 return 0; /* duplex did not change */
523
524 /* no carrier, nothing much to do */
525 if (!new_carrier) {
526 netif_carrier_off(mii->dev);
527 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800528 netdev_info(mii->dev, "link down\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700529 return 0; /* duplex did not change */
530 }
531
532 /*
533 * we have carrier, see who's on the other end
534 */
535 netif_carrier_on(mii->dev);
536
Ben Hutchings5bdc7382015-01-16 17:55:35 +0000537 if (mii->force_media) {
538 if (ok_to_print)
539 netdev_info(mii->dev, "link up\n");
540 return 0; /* duplex did not change */
541 }
542
Linus Torvalds1da177e2005-04-16 15:20:36 -0700543 /* get MII advertise and LPA values */
544 if ((!init_media) && (mii->advertising))
545 advertise = mii->advertising;
546 else {
547 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
548 mii->advertising = advertise;
549 }
550 lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
551 if (mii->supports_gmii)
552 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
553
554 /* figure out media and duplex from advertise and LPA values */
555 media = mii_nway_result(lpa & advertise);
556 duplex = (media & ADVERTISE_FULL) ? 1 : 0;
557 if (lpa2 & LPA_1000FULL)
558 duplex = 1;
559
560 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800561 netdev_info(mii->dev, "link up, %uMbps, %s-duplex, lpa 0x%04X\n",
562 lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 :
563 media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ?
564 100 : 10,
565 duplex ? "full" : "half",
566 lpa);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700567
568 if ((init_media) || (mii->full_duplex != duplex)) {
569 mii->full_duplex = duplex;
570 return 1; /* duplex changed */
571 }
572
573 return 0; /* duplex did not change */
574}
575
Randy Dunlap32684ec2007-04-06 11:08:24 -0700576/**
577 * generic_mii_ioctl - main MII ioctl interface
578 * @mii_if: the MII interface
579 * @mii_data: MII ioctl data structure
580 * @cmd: MII ioctl command
581 * @duplex_chg_out: pointer to @duplex_changed status if there was no
582 * ioctl error
583 *
584 * Returns 0 on success, negative on error.
585 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700586int generic_mii_ioctl(struct mii_if_info *mii_if,
587 struct mii_ioctl_data *mii_data, int cmd,
588 unsigned int *duplex_chg_out)
589{
590 int rc = 0;
591 unsigned int duplex_changed = 0;
592
593 if (duplex_chg_out)
594 *duplex_chg_out = 0;
595
596 mii_data->phy_id &= mii_if->phy_id_mask;
597 mii_data->reg_num &= mii_if->reg_num_mask;
598
599 switch(cmd) {
600 case SIOCGMIIPHY:
601 mii_data->phy_id = mii_if->phy_id;
602 /* fall through */
603
604 case SIOCGMIIREG:
605 mii_data->val_out =
606 mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
607 mii_data->reg_num);
608 break;
609
610 case SIOCSMIIREG: {
611 u16 val = mii_data->val_in;
612
Linus Torvalds1da177e2005-04-16 15:20:36 -0700613 if (mii_data->phy_id == mii_if->phy_id) {
614 switch(mii_data->reg_num) {
615 case MII_BMCR: {
616 unsigned int new_duplex = 0;
617 if (val & (BMCR_RESET|BMCR_ANENABLE))
618 mii_if->force_media = 0;
619 else
620 mii_if->force_media = 1;
621 if (mii_if->force_media &&
622 (val & BMCR_FULLDPLX))
623 new_duplex = 1;
624 if (mii_if->full_duplex != new_duplex) {
625 duplex_changed = 1;
626 mii_if->full_duplex = new_duplex;
627 }
628 break;
629 }
630 case MII_ADVERTISE:
631 mii_if->advertising = val;
632 break;
633 default:
634 /* do nothing */
635 break;
636 }
637 }
638
639 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
640 mii_data->reg_num, val);
641 break;
642 }
643
644 default:
645 rc = -EOPNOTSUPP;
646 break;
647 }
648
649 if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
650 *duplex_chg_out = 1;
651
652 return rc;
653}
654
655MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
656MODULE_DESCRIPTION ("MII hardware support library");
657MODULE_LICENSE("GPL");
658
659EXPORT_SYMBOL(mii_link_ok);
660EXPORT_SYMBOL(mii_nway_restart);
661EXPORT_SYMBOL(mii_ethtool_gset);
Philippe Reynesbc8ee592016-11-01 16:32:25 +0100662EXPORT_SYMBOL(mii_ethtool_get_link_ksettings);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700663EXPORT_SYMBOL(mii_ethtool_sset);
Philippe Reynesbc8ee592016-11-01 16:32:25 +0100664EXPORT_SYMBOL(mii_ethtool_set_link_ksettings);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700665EXPORT_SYMBOL(mii_check_link);
666EXPORT_SYMBOL(mii_check_media);
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700667EXPORT_SYMBOL(mii_check_gmii_support);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700668EXPORT_SYMBOL(generic_mii_ioctl);
669