blob: 05acca78f63a9a6ce294c49a902a65485b7aed71 [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;
160
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400161 if (ecmd->speed != SPEED_10 &&
162 ecmd->speed != SPEED_100 &&
Linus Torvalds1da177e2005-04-16 15:20:36 -0700163 ecmd->speed != SPEED_1000)
164 return -EINVAL;
165 if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
166 return -EINVAL;
167 if (ecmd->port != PORT_MII)
168 return -EINVAL;
169 if (ecmd->transceiver != XCVR_INTERNAL)
170 return -EINVAL;
171 if (ecmd->phy_address != mii->phy_id)
172 return -EINVAL;
173 if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
174 return -EINVAL;
175 if ((ecmd->speed == SPEED_1000) && (!mii->supports_gmii))
176 return -EINVAL;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400177
Linus Torvalds1da177e2005-04-16 15:20:36 -0700178 /* ignore supported, maxtxpkt, maxrxpkt */
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400179
Linus Torvalds1da177e2005-04-16 15:20:36 -0700180 if (ecmd->autoneg == AUTONEG_ENABLE) {
181 u32 bmcr, advert, tmp;
182 u32 advert2 = 0, tmp2 = 0;
183
184 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
185 ADVERTISED_10baseT_Full |
186 ADVERTISED_100baseT_Half |
187 ADVERTISED_100baseT_Full |
188 ADVERTISED_1000baseT_Half |
189 ADVERTISED_1000baseT_Full)) == 0)
190 return -EINVAL;
191
192 /* advertise only what has been requested */
193 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
194 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
195 if (mii->supports_gmii) {
196 advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
197 tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
198 }
199 if (ecmd->advertising & ADVERTISED_10baseT_Half)
200 tmp |= ADVERTISE_10HALF;
201 if (ecmd->advertising & ADVERTISED_10baseT_Full)
202 tmp |= ADVERTISE_10FULL;
203 if (ecmd->advertising & ADVERTISED_100baseT_Half)
204 tmp |= ADVERTISE_100HALF;
205 if (ecmd->advertising & ADVERTISED_100baseT_Full)
206 tmp |= ADVERTISE_100FULL;
207 if (mii->supports_gmii) {
208 if (ecmd->advertising & ADVERTISED_1000baseT_Half)
209 tmp2 |= ADVERTISE_1000HALF;
210 if (ecmd->advertising & ADVERTISED_1000baseT_Full)
211 tmp2 |= ADVERTISE_1000FULL;
212 }
213 if (advert != tmp) {
214 mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
215 mii->advertising = tmp;
216 }
217 if ((mii->supports_gmii) && (advert2 != tmp2))
218 mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400219
Linus Torvalds1da177e2005-04-16 15:20:36 -0700220 /* turn on autonegotiation, and force a renegotiate */
221 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
222 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
223 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
224
225 mii->force_media = 0;
226 } else {
227 u32 bmcr, tmp;
228
229 /* turn off auto negotiation, set speed and duplexity */
230 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400231 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
Linus Torvalds1da177e2005-04-16 15:20:36 -0700232 BMCR_SPEED1000 | BMCR_FULLDPLX);
233 if (ecmd->speed == SPEED_1000)
234 tmp |= BMCR_SPEED1000;
235 else if (ecmd->speed == SPEED_100)
236 tmp |= BMCR_SPEED100;
237 if (ecmd->duplex == DUPLEX_FULL) {
238 tmp |= BMCR_FULLDPLX;
239 mii->full_duplex = 1;
240 } else
241 mii->full_duplex = 0;
242 if (bmcr != tmp)
243 mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
244
245 mii->force_media = 1;
246 }
247 return 0;
248}
249
Randy Dunlap32684ec2007-04-06 11:08:24 -0700250/**
251 * mii_check_gmii_support - check if the MII supports Gb interfaces
252 * @mii: the MII interface
253 */
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700254int mii_check_gmii_support(struct mii_if_info *mii)
255{
256 int reg;
257
258 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
259 if (reg & BMSR_ESTATEN) {
260 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
261 if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
262 return 1;
263 }
264
265 return 0;
266}
267
Randy Dunlap32684ec2007-04-06 11:08:24 -0700268/**
269 * mii_link_ok - is link status up/ok
270 * @mii: the MII interface
271 *
272 * Returns 1 if the MII reports link status up/ok, 0 otherwise.
273 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700274int mii_link_ok (struct mii_if_info *mii)
275{
276 /* first, a dummy read, needed to latch some MII phys */
277 mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
278 if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
279 return 1;
280 return 0;
281}
282
Randy Dunlap32684ec2007-04-06 11:08:24 -0700283/**
284 * mii_nway_restart - restart NWay (autonegotiation) for this interface
285 * @mii: the MII interface
286 *
287 * Returns 0 on success, negative on error.
288 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700289int mii_nway_restart (struct mii_if_info *mii)
290{
291 int bmcr;
292 int r = -EINVAL;
293
294 /* if autoneg is off, it's an error */
295 bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
296
297 if (bmcr & BMCR_ANENABLE) {
298 bmcr |= BMCR_ANRESTART;
299 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
300 r = 0;
301 }
302
303 return r;
304}
305
Randy Dunlap32684ec2007-04-06 11:08:24 -0700306/**
307 * mii_check_link - check MII link status
308 * @mii: MII interface
309 *
310 * If the link status changed (previous != current), call
311 * netif_carrier_on() if current link status is Up or call
312 * netif_carrier_off() if current link status is Down.
313 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700314void mii_check_link (struct mii_if_info *mii)
315{
316 int cur_link = mii_link_ok(mii);
317 int prev_link = netif_carrier_ok(mii->dev);
318
319 if (cur_link && !prev_link)
320 netif_carrier_on(mii->dev);
321 else if (prev_link && !cur_link)
322 netif_carrier_off(mii->dev);
323}
324
Randy Dunlap32684ec2007-04-06 11:08:24 -0700325/**
326 * mii_check_media - check the MII interface for a duplex change
327 * @mii: the MII interface
328 * @ok_to_print: OK to print link up/down messages
329 * @init_media: OK to save duplex mode in @mii
330 *
331 * Returns 1 if the duplex mode changed, 0 if not.
332 * If the media type is forced, always returns 0.
333 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700334unsigned int mii_check_media (struct mii_if_info *mii,
335 unsigned int ok_to_print,
336 unsigned int init_media)
337{
338 unsigned int old_carrier, new_carrier;
339 int advertise, lpa, media, duplex;
340 int lpa2 = 0;
341
342 /* if forced media, go no further */
343 if (mii->force_media)
344 return 0; /* duplex did not change */
345
346 /* check current and old link status */
347 old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
348 new_carrier = (unsigned int) mii_link_ok(mii);
349
350 /* if carrier state did not change, this is a "bounce",
351 * just exit as everything is already set correctly
352 */
353 if ((!init_media) && (old_carrier == new_carrier))
354 return 0; /* duplex did not change */
355
356 /* no carrier, nothing much to do */
357 if (!new_carrier) {
358 netif_carrier_off(mii->dev);
359 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800360 netdev_info(mii->dev, "link down\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700361 return 0; /* duplex did not change */
362 }
363
364 /*
365 * we have carrier, see who's on the other end
366 */
367 netif_carrier_on(mii->dev);
368
369 /* get MII advertise and LPA values */
370 if ((!init_media) && (mii->advertising))
371 advertise = mii->advertising;
372 else {
373 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
374 mii->advertising = advertise;
375 }
376 lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
377 if (mii->supports_gmii)
378 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
379
380 /* figure out media and duplex from advertise and LPA values */
381 media = mii_nway_result(lpa & advertise);
382 duplex = (media & ADVERTISE_FULL) ? 1 : 0;
383 if (lpa2 & LPA_1000FULL)
384 duplex = 1;
385
386 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800387 netdev_info(mii->dev, "link up, %uMbps, %s-duplex, lpa 0x%04X\n",
388 lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 :
389 media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ?
390 100 : 10,
391 duplex ? "full" : "half",
392 lpa);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700393
394 if ((init_media) || (mii->full_duplex != duplex)) {
395 mii->full_duplex = duplex;
396 return 1; /* duplex changed */
397 }
398
399 return 0; /* duplex did not change */
400}
401
Randy Dunlap32684ec2007-04-06 11:08:24 -0700402/**
403 * generic_mii_ioctl - main MII ioctl interface
404 * @mii_if: the MII interface
405 * @mii_data: MII ioctl data structure
406 * @cmd: MII ioctl command
407 * @duplex_chg_out: pointer to @duplex_changed status if there was no
408 * ioctl error
409 *
410 * Returns 0 on success, negative on error.
411 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700412int generic_mii_ioctl(struct mii_if_info *mii_if,
413 struct mii_ioctl_data *mii_data, int cmd,
414 unsigned int *duplex_chg_out)
415{
416 int rc = 0;
417 unsigned int duplex_changed = 0;
418
419 if (duplex_chg_out)
420 *duplex_chg_out = 0;
421
422 mii_data->phy_id &= mii_if->phy_id_mask;
423 mii_data->reg_num &= mii_if->reg_num_mask;
424
425 switch(cmd) {
426 case SIOCGMIIPHY:
427 mii_data->phy_id = mii_if->phy_id;
428 /* fall through */
429
430 case SIOCGMIIREG:
431 mii_data->val_out =
432 mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
433 mii_data->reg_num);
434 break;
435
436 case SIOCSMIIREG: {
437 u16 val = mii_data->val_in;
438
Linus Torvalds1da177e2005-04-16 15:20:36 -0700439 if (mii_data->phy_id == mii_if->phy_id) {
440 switch(mii_data->reg_num) {
441 case MII_BMCR: {
442 unsigned int new_duplex = 0;
443 if (val & (BMCR_RESET|BMCR_ANENABLE))
444 mii_if->force_media = 0;
445 else
446 mii_if->force_media = 1;
447 if (mii_if->force_media &&
448 (val & BMCR_FULLDPLX))
449 new_duplex = 1;
450 if (mii_if->full_duplex != new_duplex) {
451 duplex_changed = 1;
452 mii_if->full_duplex = new_duplex;
453 }
454 break;
455 }
456 case MII_ADVERTISE:
457 mii_if->advertising = val;
458 break;
459 default:
460 /* do nothing */
461 break;
462 }
463 }
464
465 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
466 mii_data->reg_num, val);
467 break;
468 }
469
470 default:
471 rc = -EOPNOTSUPP;
472 break;
473 }
474
475 if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
476 *duplex_chg_out = 1;
477
478 return rc;
479}
480
481MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
482MODULE_DESCRIPTION ("MII hardware support library");
483MODULE_LICENSE("GPL");
484
485EXPORT_SYMBOL(mii_link_ok);
486EXPORT_SYMBOL(mii_nway_restart);
487EXPORT_SYMBOL(mii_ethtool_gset);
488EXPORT_SYMBOL(mii_ethtool_sset);
489EXPORT_SYMBOL(mii_check_link);
490EXPORT_SYMBOL(mii_check_media);
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700491EXPORT_SYMBOL(mii_check_gmii_support);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700492EXPORT_SYMBOL(generic_mii_ioctl);
493