blob: e8198edeaa761712aa7840ea290c19a9e8b5613c [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;
52
53 return result;
54}
Linus Torvalds1da177e2005-04-16 15:20:36 -070055
Randy Dunlap32684ec2007-04-06 11:08:24 -070056/**
57 * mii_ethtool_gset - get settings that are specified in @ecmd
58 * @mii: MII interface
59 * @ecmd: requested ethtool_cmd
60 *
David Decotigny8ae6dac2011-04-27 18:32:38 +000061 * The @ecmd parameter is expected to have been cleared before calling
62 * mii_ethtool_gset().
63 *
Randy Dunlap32684ec2007-04-06 11:08:24 -070064 * Returns 0 for success, negative on error.
65 */
Linus Torvalds1da177e2005-04-16 15:20:36 -070066int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
67{
68 struct net_device *dev = mii->dev;
Ben Hutchings59747002009-04-29 08:34:44 +000069 u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
70 u32 nego;
Linus Torvalds1da177e2005-04-16 15:20:36 -070071
72 ecmd->supported =
73 (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
74 SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
75 SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
76 if (mii->supports_gmii)
77 ecmd->supported |= SUPPORTED_1000baseT_Half |
78 SUPPORTED_1000baseT_Full;
79
80 /* only supports twisted-pair */
81 ecmd->port = PORT_MII;
82
83 /* only supports internal transceiver */
84 ecmd->transceiver = XCVR_INTERNAL;
85
86 /* this isn't fully supported at higher layers */
87 ecmd->phy_address = mii->phy_id;
Ben Hutchings59747002009-04-29 08:34:44 +000088 ecmd->mdio_support = MDIO_SUPPORTS_C22;
Linus Torvalds1da177e2005-04-16 15:20:36 -070089
90 ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
Linus Torvalds1da177e2005-04-16 15:20:36 -070091
92 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Ben Hutchings59747002009-04-29 08:34:44 +000093 bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
Linus Torvalds1da177e2005-04-16 15:20:36 -070094 if (mii->supports_gmii) {
Ben Hutchings59747002009-04-29 08:34:44 +000095 ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
96 stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
Linus Torvalds1da177e2005-04-16 15:20:36 -070097 }
98 if (bmcr & BMCR_ANENABLE) {
99 ecmd->advertising |= ADVERTISED_Autoneg;
100 ecmd->autoneg = AUTONEG_ENABLE;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400101
Ben Hutchings59747002009-04-29 08:34:44 +0000102 ecmd->advertising |= mii_get_an(mii, MII_ADVERTISE);
103 if (ctrl1000 & ADVERTISE_1000HALF)
104 ecmd->advertising |= ADVERTISED_1000baseT_Half;
105 if (ctrl1000 & ADVERTISE_1000FULL)
106 ecmd->advertising |= ADVERTISED_1000baseT_Full;
107
108 if (bmsr & BMSR_ANEGCOMPLETE) {
109 ecmd->lp_advertising = mii_get_an(mii, MII_LPA);
110 if (stat1000 & LPA_1000HALF)
111 ecmd->lp_advertising |=
112 ADVERTISED_1000baseT_Half;
113 if (stat1000 & LPA_1000FULL)
114 ecmd->lp_advertising |=
115 ADVERTISED_1000baseT_Full;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116 } else {
Ben Hutchings59747002009-04-29 08:34:44 +0000117 ecmd->lp_advertising = 0;
118 }
119
120 nego = ecmd->advertising & ecmd->lp_advertising;
121
122 if (nego & (ADVERTISED_1000baseT_Full |
123 ADVERTISED_1000baseT_Half)) {
124 ecmd->speed = SPEED_1000;
125 ecmd->duplex = !!(nego & ADVERTISED_1000baseT_Full);
126 } else if (nego & (ADVERTISED_100baseT_Full |
127 ADVERTISED_100baseT_Half)) {
128 ecmd->speed = SPEED_100;
129 ecmd->duplex = !!(nego & ADVERTISED_100baseT_Full);
130 } else {
131 ecmd->speed = SPEED_10;
132 ecmd->duplex = !!(nego & ADVERTISED_10baseT_Full);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700133 }
134 } else {
135 ecmd->autoneg = AUTONEG_DISABLE;
136
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400137 ecmd->speed = ((bmcr & BMCR_SPEED1000 &&
Linus Torvalds1da177e2005-04-16 15:20:36 -0700138 (bmcr & BMCR_SPEED100) == 0) ? SPEED_1000 :
139 (bmcr & BMCR_SPEED100) ? SPEED_100 : SPEED_10);
140 ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
141 }
142
Ben Hutchings59747002009-04-29 08:34:44 +0000143 mii->full_duplex = ecmd->duplex;
144
Linus Torvalds1da177e2005-04-16 15:20:36 -0700145 /* ignore maxtxpkt, maxrxpkt for now */
146
147 return 0;
148}
149
Randy Dunlap32684ec2007-04-06 11:08:24 -0700150/**
151 * mii_ethtool_sset - set settings that are specified in @ecmd
152 * @mii: MII interface
153 * @ecmd: requested ethtool_cmd
154 *
155 * Returns 0 for success, negative on error.
156 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700157int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
158{
159 struct net_device *dev = mii->dev;
David Decotigny25db0332011-04-27 18:32:39 +0000160 u32 speed = ethtool_cmd_speed(ecmd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700161
David Decotigny25db0332011-04-27 18:32:39 +0000162 if (speed != SPEED_10 &&
163 speed != SPEED_100 &&
164 speed != SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700165 return -EINVAL;
166 if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
167 return -EINVAL;
168 if (ecmd->port != PORT_MII)
169 return -EINVAL;
170 if (ecmd->transceiver != XCVR_INTERNAL)
171 return -EINVAL;
172 if (ecmd->phy_address != mii->phy_id)
173 return -EINVAL;
174 if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
175 return -EINVAL;
David Decotigny25db0332011-04-27 18:32:39 +0000176 if ((speed == SPEED_1000) && (!mii->supports_gmii))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700177 return -EINVAL;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400178
Linus Torvalds1da177e2005-04-16 15:20:36 -0700179 /* ignore supported, maxtxpkt, maxrxpkt */
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400180
Linus Torvalds1da177e2005-04-16 15:20:36 -0700181 if (ecmd->autoneg == AUTONEG_ENABLE) {
182 u32 bmcr, advert, tmp;
183 u32 advert2 = 0, tmp2 = 0;
184
185 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
186 ADVERTISED_10baseT_Full |
187 ADVERTISED_100baseT_Half |
188 ADVERTISED_100baseT_Full |
189 ADVERTISED_1000baseT_Half |
190 ADVERTISED_1000baseT_Full)) == 0)
191 return -EINVAL;
192
193 /* advertise only what has been requested */
194 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
195 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
196 if (mii->supports_gmii) {
197 advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
198 tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
199 }
200 if (ecmd->advertising & ADVERTISED_10baseT_Half)
201 tmp |= ADVERTISE_10HALF;
202 if (ecmd->advertising & ADVERTISED_10baseT_Full)
203 tmp |= ADVERTISE_10FULL;
204 if (ecmd->advertising & ADVERTISED_100baseT_Half)
205 tmp |= ADVERTISE_100HALF;
206 if (ecmd->advertising & ADVERTISED_100baseT_Full)
207 tmp |= ADVERTISE_100FULL;
208 if (mii->supports_gmii) {
209 if (ecmd->advertising & ADVERTISED_1000baseT_Half)
210 tmp2 |= ADVERTISE_1000HALF;
211 if (ecmd->advertising & ADVERTISED_1000baseT_Full)
212 tmp2 |= ADVERTISE_1000FULL;
213 }
214 if (advert != tmp) {
215 mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
216 mii->advertising = tmp;
217 }
218 if ((mii->supports_gmii) && (advert2 != tmp2))
219 mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400220
Linus Torvalds1da177e2005-04-16 15:20:36 -0700221 /* turn on autonegotiation, and force a renegotiate */
222 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
223 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
224 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
225
226 mii->force_media = 0;
227 } else {
228 u32 bmcr, tmp;
229
230 /* turn off auto negotiation, set speed and duplexity */
231 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400232 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
Linus Torvalds1da177e2005-04-16 15:20:36 -0700233 BMCR_SPEED1000 | BMCR_FULLDPLX);
David Decotigny25db0332011-04-27 18:32:39 +0000234 if (speed == SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700235 tmp |= BMCR_SPEED1000;
David Decotigny25db0332011-04-27 18:32:39 +0000236 else if (speed == SPEED_100)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700237 tmp |= BMCR_SPEED100;
238 if (ecmd->duplex == DUPLEX_FULL) {
239 tmp |= BMCR_FULLDPLX;
240 mii->full_duplex = 1;
241 } else
242 mii->full_duplex = 0;
243 if (bmcr != tmp)
244 mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
245
246 mii->force_media = 1;
247 }
248 return 0;
249}
250
Randy Dunlap32684ec2007-04-06 11:08:24 -0700251/**
252 * mii_check_gmii_support - check if the MII supports Gb interfaces
253 * @mii: the MII interface
254 */
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700255int mii_check_gmii_support(struct mii_if_info *mii)
256{
257 int reg;
258
259 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
260 if (reg & BMSR_ESTATEN) {
261 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
262 if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
263 return 1;
264 }
265
266 return 0;
267}
268
Randy Dunlap32684ec2007-04-06 11:08:24 -0700269/**
270 * mii_link_ok - is link status up/ok
271 * @mii: the MII interface
272 *
273 * Returns 1 if the MII reports link status up/ok, 0 otherwise.
274 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700275int mii_link_ok (struct mii_if_info *mii)
276{
277 /* first, a dummy read, needed to latch some MII phys */
278 mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
279 if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
280 return 1;
281 return 0;
282}
283
Randy Dunlap32684ec2007-04-06 11:08:24 -0700284/**
285 * mii_nway_restart - restart NWay (autonegotiation) for this interface
286 * @mii: the MII interface
287 *
288 * Returns 0 on success, negative on error.
289 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700290int mii_nway_restart (struct mii_if_info *mii)
291{
292 int bmcr;
293 int r = -EINVAL;
294
295 /* if autoneg is off, it's an error */
296 bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
297
298 if (bmcr & BMCR_ANENABLE) {
299 bmcr |= BMCR_ANRESTART;
300 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
301 r = 0;
302 }
303
304 return r;
305}
306
Randy Dunlap32684ec2007-04-06 11:08:24 -0700307/**
308 * mii_check_link - check MII link status
309 * @mii: MII interface
310 *
311 * If the link status changed (previous != current), call
312 * netif_carrier_on() if current link status is Up or call
313 * netif_carrier_off() if current link status is Down.
314 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700315void mii_check_link (struct mii_if_info *mii)
316{
317 int cur_link = mii_link_ok(mii);
318 int prev_link = netif_carrier_ok(mii->dev);
319
320 if (cur_link && !prev_link)
321 netif_carrier_on(mii->dev);
322 else if (prev_link && !cur_link)
323 netif_carrier_off(mii->dev);
324}
325
Randy Dunlap32684ec2007-04-06 11:08:24 -0700326/**
327 * mii_check_media - check the MII interface for a duplex change
328 * @mii: the MII interface
329 * @ok_to_print: OK to print link up/down messages
330 * @init_media: OK to save duplex mode in @mii
331 *
332 * Returns 1 if the duplex mode changed, 0 if not.
333 * If the media type is forced, always returns 0.
334 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700335unsigned int mii_check_media (struct mii_if_info *mii,
336 unsigned int ok_to_print,
337 unsigned int init_media)
338{
339 unsigned int old_carrier, new_carrier;
340 int advertise, lpa, media, duplex;
341 int lpa2 = 0;
342
343 /* if forced media, go no further */
344 if (mii->force_media)
345 return 0; /* duplex did not change */
346
347 /* check current and old link status */
348 old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
349 new_carrier = (unsigned int) mii_link_ok(mii);
350
351 /* if carrier state did not change, this is a "bounce",
352 * just exit as everything is already set correctly
353 */
354 if ((!init_media) && (old_carrier == new_carrier))
355 return 0; /* duplex did not change */
356
357 /* no carrier, nothing much to do */
358 if (!new_carrier) {
359 netif_carrier_off(mii->dev);
360 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800361 netdev_info(mii->dev, "link down\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700362 return 0; /* duplex did not change */
363 }
364
365 /*
366 * we have carrier, see who's on the other end
367 */
368 netif_carrier_on(mii->dev);
369
370 /* get MII advertise and LPA values */
371 if ((!init_media) && (mii->advertising))
372 advertise = mii->advertising;
373 else {
374 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
375 mii->advertising = advertise;
376 }
377 lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
378 if (mii->supports_gmii)
379 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
380
381 /* figure out media and duplex from advertise and LPA values */
382 media = mii_nway_result(lpa & advertise);
383 duplex = (media & ADVERTISE_FULL) ? 1 : 0;
384 if (lpa2 & LPA_1000FULL)
385 duplex = 1;
386
387 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800388 netdev_info(mii->dev, "link up, %uMbps, %s-duplex, lpa 0x%04X\n",
389 lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 :
390 media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ?
391 100 : 10,
392 duplex ? "full" : "half",
393 lpa);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700394
395 if ((init_media) || (mii->full_duplex != duplex)) {
396 mii->full_duplex = duplex;
397 return 1; /* duplex changed */
398 }
399
400 return 0; /* duplex did not change */
401}
402
Randy Dunlap32684ec2007-04-06 11:08:24 -0700403/**
404 * generic_mii_ioctl - main MII ioctl interface
405 * @mii_if: the MII interface
406 * @mii_data: MII ioctl data structure
407 * @cmd: MII ioctl command
408 * @duplex_chg_out: pointer to @duplex_changed status if there was no
409 * ioctl error
410 *
411 * Returns 0 on success, negative on error.
412 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700413int generic_mii_ioctl(struct mii_if_info *mii_if,
414 struct mii_ioctl_data *mii_data, int cmd,
415 unsigned int *duplex_chg_out)
416{
417 int rc = 0;
418 unsigned int duplex_changed = 0;
419
420 if (duplex_chg_out)
421 *duplex_chg_out = 0;
422
423 mii_data->phy_id &= mii_if->phy_id_mask;
424 mii_data->reg_num &= mii_if->reg_num_mask;
425
426 switch(cmd) {
427 case SIOCGMIIPHY:
428 mii_data->phy_id = mii_if->phy_id;
429 /* fall through */
430
431 case SIOCGMIIREG:
432 mii_data->val_out =
433 mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
434 mii_data->reg_num);
435 break;
436
437 case SIOCSMIIREG: {
438 u16 val = mii_data->val_in;
439
Linus Torvalds1da177e2005-04-16 15:20:36 -0700440 if (mii_data->phy_id == mii_if->phy_id) {
441 switch(mii_data->reg_num) {
442 case MII_BMCR: {
443 unsigned int new_duplex = 0;
444 if (val & (BMCR_RESET|BMCR_ANENABLE))
445 mii_if->force_media = 0;
446 else
447 mii_if->force_media = 1;
448 if (mii_if->force_media &&
449 (val & BMCR_FULLDPLX))
450 new_duplex = 1;
451 if (mii_if->full_duplex != new_duplex) {
452 duplex_changed = 1;
453 mii_if->full_duplex = new_duplex;
454 }
455 break;
456 }
457 case MII_ADVERTISE:
458 mii_if->advertising = val;
459 break;
460 default:
461 /* do nothing */
462 break;
463 }
464 }
465
466 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
467 mii_data->reg_num, val);
468 break;
469 }
470
471 default:
472 rc = -EOPNOTSUPP;
473 break;
474 }
475
476 if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
477 *duplex_chg_out = 1;
478
479 return rc;
480}
481
482MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
483MODULE_DESCRIPTION ("MII hardware support library");
484MODULE_LICENSE("GPL");
485
486EXPORT_SYMBOL(mii_link_ok);
487EXPORT_SYMBOL(mii_nway_restart);
488EXPORT_SYMBOL(mii_ethtool_gset);
489EXPORT_SYMBOL(mii_ethtool_sset);
490EXPORT_SYMBOL(mii_check_link);
491EXPORT_SYMBOL(mii_check_media);
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700492EXPORT_SYMBOL(mii_check_gmii_support);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700493EXPORT_SYMBOL(generic_mii_ioctl);
494