blob: c62e7816d54864d317b1c646c00c18bc78b97ead [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 Hutchings59747002009-04-29 08:34:44 +000034#include <linux/mdio.h>
35
36static u32 mii_get_an(struct mii_if_info *mii, u16 addr)
37{
38 u32 result = 0;
39 int advert;
40
41 advert = mii->mdio_read(mii->dev, mii->phy_id, addr);
42 if (advert & LPA_LPACK)
43 result |= ADVERTISED_Autoneg;
44 if (advert & ADVERTISE_10HALF)
45 result |= ADVERTISED_10baseT_Half;
46 if (advert & ADVERTISE_10FULL)
47 result |= ADVERTISED_10baseT_Full;
48 if (advert & ADVERTISE_100HALF)
49 result |= ADVERTISED_100baseT_Half;
50 if (advert & ADVERTISE_100FULL)
51 result |= ADVERTISED_100baseT_Full;
artpol2b5a4ac2011-04-27 17:49:14 +000052 if (advert & ADVERTISE_PAUSE_CAP)
53 result |= ADVERTISED_Pause;
54 if (advert & ADVERTISE_PAUSE_ASYM)
55 result |= ADVERTISED_Asym_Pause;
Ben Hutchings59747002009-04-29 08:34:44 +000056
57 return result;
58}
Linus Torvalds1da177e2005-04-16 15:20:36 -070059
Randy Dunlap32684ec2007-04-06 11:08:24 -070060/**
61 * mii_ethtool_gset - get settings that are specified in @ecmd
62 * @mii: MII interface
63 * @ecmd: requested ethtool_cmd
64 *
David Decotigny8ae6dac2011-04-27 18:32:38 +000065 * The @ecmd parameter is expected to have been cleared before calling
66 * mii_ethtool_gset().
67 *
Randy Dunlap32684ec2007-04-06 11:08:24 -070068 * Returns 0 for success, negative on error.
69 */
Linus Torvalds1da177e2005-04-16 15:20:36 -070070int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
71{
72 struct net_device *dev = mii->dev;
Ben Hutchings59747002009-04-29 08:34:44 +000073 u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
74 u32 nego;
Linus Torvalds1da177e2005-04-16 15:20:36 -070075
76 ecmd->supported =
77 (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
78 SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
79 SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
80 if (mii->supports_gmii)
81 ecmd->supported |= SUPPORTED_1000baseT_Half |
82 SUPPORTED_1000baseT_Full;
83
84 /* only supports twisted-pair */
85 ecmd->port = PORT_MII;
86
87 /* only supports internal transceiver */
88 ecmd->transceiver = XCVR_INTERNAL;
89
90 /* this isn't fully supported at higher layers */
91 ecmd->phy_address = mii->phy_id;
Ben Hutchings59747002009-04-29 08:34:44 +000092 ecmd->mdio_support = MDIO_SUPPORTS_C22;
Linus Torvalds1da177e2005-04-16 15:20:36 -070093
94 ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
Linus Torvalds1da177e2005-04-16 15:20:36 -070095
96 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Ben Hutchings59747002009-04-29 08:34:44 +000097 bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
Linus Torvalds1da177e2005-04-16 15:20:36 -070098 if (mii->supports_gmii) {
Ben Hutchings59747002009-04-29 08:34:44 +000099 ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
100 stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700101 }
102 if (bmcr & BMCR_ANENABLE) {
103 ecmd->advertising |= ADVERTISED_Autoneg;
104 ecmd->autoneg = AUTONEG_ENABLE;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400105
Ben Hutchings59747002009-04-29 08:34:44 +0000106 ecmd->advertising |= mii_get_an(mii, MII_ADVERTISE);
107 if (ctrl1000 & ADVERTISE_1000HALF)
108 ecmd->advertising |= ADVERTISED_1000baseT_Half;
109 if (ctrl1000 & ADVERTISE_1000FULL)
110 ecmd->advertising |= ADVERTISED_1000baseT_Full;
111
112 if (bmsr & BMSR_ANEGCOMPLETE) {
113 ecmd->lp_advertising = mii_get_an(mii, MII_LPA);
114 if (stat1000 & LPA_1000HALF)
115 ecmd->lp_advertising |=
116 ADVERTISED_1000baseT_Half;
117 if (stat1000 & LPA_1000FULL)
118 ecmd->lp_advertising |=
119 ADVERTISED_1000baseT_Full;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700120 } else {
Ben Hutchings59747002009-04-29 08:34:44 +0000121 ecmd->lp_advertising = 0;
122 }
123
124 nego = ecmd->advertising & ecmd->lp_advertising;
125
126 if (nego & (ADVERTISED_1000baseT_Full |
127 ADVERTISED_1000baseT_Half)) {
David Decotigny70739492011-04-27 18:32:40 +0000128 ethtool_cmd_speed_set(ecmd, SPEED_1000);
Ben Hutchings59747002009-04-29 08:34:44 +0000129 ecmd->duplex = !!(nego & ADVERTISED_1000baseT_Full);
130 } else if (nego & (ADVERTISED_100baseT_Full |
131 ADVERTISED_100baseT_Half)) {
David Decotigny70739492011-04-27 18:32:40 +0000132 ethtool_cmd_speed_set(ecmd, SPEED_100);
Ben Hutchings59747002009-04-29 08:34:44 +0000133 ecmd->duplex = !!(nego & ADVERTISED_100baseT_Full);
134 } else {
David Decotigny70739492011-04-27 18:32:40 +0000135 ethtool_cmd_speed_set(ecmd, SPEED_10);
Ben Hutchings59747002009-04-29 08:34:44 +0000136 ecmd->duplex = !!(nego & ADVERTISED_10baseT_Full);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700137 }
138 } else {
139 ecmd->autoneg = AUTONEG_DISABLE;
140
David Decotigny70739492011-04-27 18:32:40 +0000141 ethtool_cmd_speed_set(ecmd,
142 ((bmcr & BMCR_SPEED1000 &&
143 (bmcr & BMCR_SPEED100) == 0) ?
144 SPEED_1000 :
145 ((bmcr & BMCR_SPEED100) ?
146 SPEED_100 : SPEED_10)));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700147 ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
148 }
149
Ben Hutchings59747002009-04-29 08:34:44 +0000150 mii->full_duplex = ecmd->duplex;
151
Linus Torvalds1da177e2005-04-16 15:20:36 -0700152 /* ignore maxtxpkt, maxrxpkt for now */
153
154 return 0;
155}
156
Randy Dunlap32684ec2007-04-06 11:08:24 -0700157/**
158 * mii_ethtool_sset - set settings that are specified in @ecmd
159 * @mii: MII interface
160 * @ecmd: requested ethtool_cmd
161 *
162 * Returns 0 for success, negative on error.
163 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700164int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
165{
166 struct net_device *dev = mii->dev;
David Decotigny25db0332011-04-27 18:32:39 +0000167 u32 speed = ethtool_cmd_speed(ecmd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700168
David Decotigny25db0332011-04-27 18:32:39 +0000169 if (speed != SPEED_10 &&
170 speed != SPEED_100 &&
171 speed != SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700172 return -EINVAL;
173 if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
174 return -EINVAL;
175 if (ecmd->port != PORT_MII)
176 return -EINVAL;
177 if (ecmd->transceiver != XCVR_INTERNAL)
178 return -EINVAL;
179 if (ecmd->phy_address != mii->phy_id)
180 return -EINVAL;
181 if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
182 return -EINVAL;
David Decotigny25db0332011-04-27 18:32:39 +0000183 if ((speed == SPEED_1000) && (!mii->supports_gmii))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700184 return -EINVAL;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400185
Linus Torvalds1da177e2005-04-16 15:20:36 -0700186 /* ignore supported, maxtxpkt, maxrxpkt */
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400187
Linus Torvalds1da177e2005-04-16 15:20:36 -0700188 if (ecmd->autoneg == AUTONEG_ENABLE) {
189 u32 bmcr, advert, tmp;
190 u32 advert2 = 0, tmp2 = 0;
191
192 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
193 ADVERTISED_10baseT_Full |
194 ADVERTISED_100baseT_Half |
195 ADVERTISED_100baseT_Full |
196 ADVERTISED_1000baseT_Half |
197 ADVERTISED_1000baseT_Full)) == 0)
198 return -EINVAL;
199
200 /* advertise only what has been requested */
201 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
202 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
203 if (mii->supports_gmii) {
204 advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
205 tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
206 }
207 if (ecmd->advertising & ADVERTISED_10baseT_Half)
208 tmp |= ADVERTISE_10HALF;
209 if (ecmd->advertising & ADVERTISED_10baseT_Full)
210 tmp |= ADVERTISE_10FULL;
211 if (ecmd->advertising & ADVERTISED_100baseT_Half)
212 tmp |= ADVERTISE_100HALF;
213 if (ecmd->advertising & ADVERTISED_100baseT_Full)
214 tmp |= ADVERTISE_100FULL;
215 if (mii->supports_gmii) {
216 if (ecmd->advertising & ADVERTISED_1000baseT_Half)
217 tmp2 |= ADVERTISE_1000HALF;
218 if (ecmd->advertising & ADVERTISED_1000baseT_Full)
219 tmp2 |= ADVERTISE_1000FULL;
220 }
221 if (advert != tmp) {
222 mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
223 mii->advertising = tmp;
224 }
225 if ((mii->supports_gmii) && (advert2 != tmp2))
226 mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400227
Linus Torvalds1da177e2005-04-16 15:20:36 -0700228 /* turn on autonegotiation, and force a renegotiate */
229 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
230 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
231 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
232
233 mii->force_media = 0;
234 } else {
235 u32 bmcr, tmp;
236
237 /* turn off auto negotiation, set speed and duplexity */
238 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400239 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
Linus Torvalds1da177e2005-04-16 15:20:36 -0700240 BMCR_SPEED1000 | BMCR_FULLDPLX);
David Decotigny25db0332011-04-27 18:32:39 +0000241 if (speed == SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700242 tmp |= BMCR_SPEED1000;
David Decotigny25db0332011-04-27 18:32:39 +0000243 else if (speed == SPEED_100)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700244 tmp |= BMCR_SPEED100;
245 if (ecmd->duplex == DUPLEX_FULL) {
246 tmp |= BMCR_FULLDPLX;
247 mii->full_duplex = 1;
248 } else
249 mii->full_duplex = 0;
250 if (bmcr != tmp)
251 mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
252
253 mii->force_media = 1;
254 }
255 return 0;
256}
257
Randy Dunlap32684ec2007-04-06 11:08:24 -0700258/**
259 * mii_check_gmii_support - check if the MII supports Gb interfaces
260 * @mii: the MII interface
261 */
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700262int mii_check_gmii_support(struct mii_if_info *mii)
263{
264 int reg;
265
266 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
267 if (reg & BMSR_ESTATEN) {
268 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
269 if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
270 return 1;
271 }
272
273 return 0;
274}
275
Randy Dunlap32684ec2007-04-06 11:08:24 -0700276/**
277 * mii_link_ok - is link status up/ok
278 * @mii: the MII interface
279 *
280 * Returns 1 if the MII reports link status up/ok, 0 otherwise.
281 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700282int mii_link_ok (struct mii_if_info *mii)
283{
284 /* first, a dummy read, needed to latch some MII phys */
285 mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
286 if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
287 return 1;
288 return 0;
289}
290
Randy Dunlap32684ec2007-04-06 11:08:24 -0700291/**
292 * mii_nway_restart - restart NWay (autonegotiation) for this interface
293 * @mii: the MII interface
294 *
295 * Returns 0 on success, negative on error.
296 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700297int mii_nway_restart (struct mii_if_info *mii)
298{
299 int bmcr;
300 int r = -EINVAL;
301
302 /* if autoneg is off, it's an error */
303 bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
304
305 if (bmcr & BMCR_ANENABLE) {
306 bmcr |= BMCR_ANRESTART;
307 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
308 r = 0;
309 }
310
311 return r;
312}
313
Randy Dunlap32684ec2007-04-06 11:08:24 -0700314/**
315 * mii_check_link - check MII link status
316 * @mii: MII interface
317 *
318 * If the link status changed (previous != current), call
319 * netif_carrier_on() if current link status is Up or call
320 * netif_carrier_off() if current link status is Down.
321 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700322void mii_check_link (struct mii_if_info *mii)
323{
324 int cur_link = mii_link_ok(mii);
325 int prev_link = netif_carrier_ok(mii->dev);
326
327 if (cur_link && !prev_link)
328 netif_carrier_on(mii->dev);
329 else if (prev_link && !cur_link)
330 netif_carrier_off(mii->dev);
331}
332
Randy Dunlap32684ec2007-04-06 11:08:24 -0700333/**
334 * mii_check_media - check the MII interface for a duplex change
335 * @mii: the MII interface
336 * @ok_to_print: OK to print link up/down messages
337 * @init_media: OK to save duplex mode in @mii
338 *
339 * Returns 1 if the duplex mode changed, 0 if not.
340 * If the media type is forced, always returns 0.
341 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700342unsigned int mii_check_media (struct mii_if_info *mii,
343 unsigned int ok_to_print,
344 unsigned int init_media)
345{
346 unsigned int old_carrier, new_carrier;
347 int advertise, lpa, media, duplex;
348 int lpa2 = 0;
349
350 /* if forced media, go no further */
351 if (mii->force_media)
352 return 0; /* duplex did not change */
353
354 /* check current and old link status */
355 old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
356 new_carrier = (unsigned int) mii_link_ok(mii);
357
358 /* if carrier state did not change, this is a "bounce",
359 * just exit as everything is already set correctly
360 */
361 if ((!init_media) && (old_carrier == new_carrier))
362 return 0; /* duplex did not change */
363
364 /* no carrier, nothing much to do */
365 if (!new_carrier) {
366 netif_carrier_off(mii->dev);
367 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800368 netdev_info(mii->dev, "link down\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700369 return 0; /* duplex did not change */
370 }
371
372 /*
373 * we have carrier, see who's on the other end
374 */
375 netif_carrier_on(mii->dev);
376
377 /* get MII advertise and LPA values */
378 if ((!init_media) && (mii->advertising))
379 advertise = mii->advertising;
380 else {
381 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
382 mii->advertising = advertise;
383 }
384 lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
385 if (mii->supports_gmii)
386 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
387
388 /* figure out media and duplex from advertise and LPA values */
389 media = mii_nway_result(lpa & advertise);
390 duplex = (media & ADVERTISE_FULL) ? 1 : 0;
391 if (lpa2 & LPA_1000FULL)
392 duplex = 1;
393
394 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800395 netdev_info(mii->dev, "link up, %uMbps, %s-duplex, lpa 0x%04X\n",
396 lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 :
397 media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ?
398 100 : 10,
399 duplex ? "full" : "half",
400 lpa);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700401
402 if ((init_media) || (mii->full_duplex != duplex)) {
403 mii->full_duplex = duplex;
404 return 1; /* duplex changed */
405 }
406
407 return 0; /* duplex did not change */
408}
409
Randy Dunlap32684ec2007-04-06 11:08:24 -0700410/**
411 * generic_mii_ioctl - main MII ioctl interface
412 * @mii_if: the MII interface
413 * @mii_data: MII ioctl data structure
414 * @cmd: MII ioctl command
415 * @duplex_chg_out: pointer to @duplex_changed status if there was no
416 * ioctl error
417 *
418 * Returns 0 on success, negative on error.
419 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700420int generic_mii_ioctl(struct mii_if_info *mii_if,
421 struct mii_ioctl_data *mii_data, int cmd,
422 unsigned int *duplex_chg_out)
423{
424 int rc = 0;
425 unsigned int duplex_changed = 0;
426
427 if (duplex_chg_out)
428 *duplex_chg_out = 0;
429
430 mii_data->phy_id &= mii_if->phy_id_mask;
431 mii_data->reg_num &= mii_if->reg_num_mask;
432
433 switch(cmd) {
434 case SIOCGMIIPHY:
435 mii_data->phy_id = mii_if->phy_id;
436 /* fall through */
437
438 case SIOCGMIIREG:
439 mii_data->val_out =
440 mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
441 mii_data->reg_num);
442 break;
443
444 case SIOCSMIIREG: {
445 u16 val = mii_data->val_in;
446
Linus Torvalds1da177e2005-04-16 15:20:36 -0700447 if (mii_data->phy_id == mii_if->phy_id) {
448 switch(mii_data->reg_num) {
449 case MII_BMCR: {
450 unsigned int new_duplex = 0;
451 if (val & (BMCR_RESET|BMCR_ANENABLE))
452 mii_if->force_media = 0;
453 else
454 mii_if->force_media = 1;
455 if (mii_if->force_media &&
456 (val & BMCR_FULLDPLX))
457 new_duplex = 1;
458 if (mii_if->full_duplex != new_duplex) {
459 duplex_changed = 1;
460 mii_if->full_duplex = new_duplex;
461 }
462 break;
463 }
464 case MII_ADVERTISE:
465 mii_if->advertising = val;
466 break;
467 default:
468 /* do nothing */
469 break;
470 }
471 }
472
473 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
474 mii_data->reg_num, val);
475 break;
476 }
477
478 default:
479 rc = -EOPNOTSUPP;
480 break;
481 }
482
483 if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
484 *duplex_chg_out = 1;
485
486 return rc;
487}
488
489MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
490MODULE_DESCRIPTION ("MII hardware support library");
491MODULE_LICENSE("GPL");
492
493EXPORT_SYMBOL(mii_link_ok);
494EXPORT_SYMBOL(mii_nway_restart);
495EXPORT_SYMBOL(mii_ethtool_gset);
496EXPORT_SYMBOL(mii_ethtool_sset);
497EXPORT_SYMBOL(mii_check_link);
498EXPORT_SYMBOL(mii_check_media);
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700499EXPORT_SYMBOL(mii_check_gmii_support);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700500EXPORT_SYMBOL(generic_mii_ioctl);
501