blob: 210b2b164b303b9283e11c5d7802571f108ef29c [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 *
61 * Returns 0 for success, negative on error.
62 */
Linus Torvalds1da177e2005-04-16 15:20:36 -070063int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
64{
65 struct net_device *dev = mii->dev;
Ben Hutchings59747002009-04-29 08:34:44 +000066 u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
67 u32 nego;
Linus Torvalds1da177e2005-04-16 15:20:36 -070068
69 ecmd->supported =
70 (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
71 SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
72 SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
73 if (mii->supports_gmii)
74 ecmd->supported |= SUPPORTED_1000baseT_Half |
75 SUPPORTED_1000baseT_Full;
76
77 /* only supports twisted-pair */
78 ecmd->port = PORT_MII;
79
80 /* only supports internal transceiver */
81 ecmd->transceiver = XCVR_INTERNAL;
82
83 /* this isn't fully supported at higher layers */
84 ecmd->phy_address = mii->phy_id;
Ben Hutchings59747002009-04-29 08:34:44 +000085 ecmd->mdio_support = MDIO_SUPPORTS_C22;
Linus Torvalds1da177e2005-04-16 15:20:36 -070086
87 ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
Linus Torvalds1da177e2005-04-16 15:20:36 -070088
89 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Ben Hutchings59747002009-04-29 08:34:44 +000090 bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
Linus Torvalds1da177e2005-04-16 15:20:36 -070091 if (mii->supports_gmii) {
Ben Hutchings59747002009-04-29 08:34:44 +000092 ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
93 stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
Linus Torvalds1da177e2005-04-16 15:20:36 -070094 }
95 if (bmcr & BMCR_ANENABLE) {
96 ecmd->advertising |= ADVERTISED_Autoneg;
97 ecmd->autoneg = AUTONEG_ENABLE;
Jeff Garzik6aa20a22006-09-13 13:24:59 -040098
Ben Hutchings59747002009-04-29 08:34:44 +000099 ecmd->advertising |= mii_get_an(mii, MII_ADVERTISE);
100 if (ctrl1000 & ADVERTISE_1000HALF)
101 ecmd->advertising |= ADVERTISED_1000baseT_Half;
102 if (ctrl1000 & ADVERTISE_1000FULL)
103 ecmd->advertising |= ADVERTISED_1000baseT_Full;
104
105 if (bmsr & BMSR_ANEGCOMPLETE) {
106 ecmd->lp_advertising = mii_get_an(mii, MII_LPA);
107 if (stat1000 & LPA_1000HALF)
108 ecmd->lp_advertising |=
109 ADVERTISED_1000baseT_Half;
110 if (stat1000 & LPA_1000FULL)
111 ecmd->lp_advertising |=
112 ADVERTISED_1000baseT_Full;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700113 } else {
Ben Hutchings59747002009-04-29 08:34:44 +0000114 ecmd->lp_advertising = 0;
115 }
116
117 nego = ecmd->advertising & ecmd->lp_advertising;
118
119 if (nego & (ADVERTISED_1000baseT_Full |
120 ADVERTISED_1000baseT_Half)) {
121 ecmd->speed = SPEED_1000;
122 ecmd->duplex = !!(nego & ADVERTISED_1000baseT_Full);
123 } else if (nego & (ADVERTISED_100baseT_Full |
124 ADVERTISED_100baseT_Half)) {
125 ecmd->speed = SPEED_100;
126 ecmd->duplex = !!(nego & ADVERTISED_100baseT_Full);
127 } else {
128 ecmd->speed = SPEED_10;
129 ecmd->duplex = !!(nego & ADVERTISED_10baseT_Full);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700130 }
131 } else {
132 ecmd->autoneg = AUTONEG_DISABLE;
133
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400134 ecmd->speed = ((bmcr & BMCR_SPEED1000 &&
Linus Torvalds1da177e2005-04-16 15:20:36 -0700135 (bmcr & BMCR_SPEED100) == 0) ? SPEED_1000 :
136 (bmcr & BMCR_SPEED100) ? SPEED_100 : SPEED_10);
137 ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
138 }
139
Ben Hutchings59747002009-04-29 08:34:44 +0000140 mii->full_duplex = ecmd->duplex;
141
Linus Torvalds1da177e2005-04-16 15:20:36 -0700142 /* ignore maxtxpkt, maxrxpkt for now */
143
144 return 0;
145}
146
Randy Dunlap32684ec2007-04-06 11:08:24 -0700147/**
148 * mii_ethtool_sset - set settings that are specified in @ecmd
149 * @mii: MII interface
150 * @ecmd: requested ethtool_cmd
151 *
152 * Returns 0 for success, negative on error.
153 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700154int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
155{
156 struct net_device *dev = mii->dev;
157
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400158 if (ecmd->speed != SPEED_10 &&
159 ecmd->speed != SPEED_100 &&
Linus Torvalds1da177e2005-04-16 15:20:36 -0700160 ecmd->speed != SPEED_1000)
161 return -EINVAL;
162 if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
163 return -EINVAL;
164 if (ecmd->port != PORT_MII)
165 return -EINVAL;
166 if (ecmd->transceiver != XCVR_INTERNAL)
167 return -EINVAL;
168 if (ecmd->phy_address != mii->phy_id)
169 return -EINVAL;
170 if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
171 return -EINVAL;
172 if ((ecmd->speed == SPEED_1000) && (!mii->supports_gmii))
173 return -EINVAL;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400174
Linus Torvalds1da177e2005-04-16 15:20:36 -0700175 /* ignore supported, maxtxpkt, maxrxpkt */
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400176
Linus Torvalds1da177e2005-04-16 15:20:36 -0700177 if (ecmd->autoneg == AUTONEG_ENABLE) {
178 u32 bmcr, advert, tmp;
179 u32 advert2 = 0, tmp2 = 0;
180
181 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
182 ADVERTISED_10baseT_Full |
183 ADVERTISED_100baseT_Half |
184 ADVERTISED_100baseT_Full |
185 ADVERTISED_1000baseT_Half |
186 ADVERTISED_1000baseT_Full)) == 0)
187 return -EINVAL;
188
189 /* advertise only what has been requested */
190 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
191 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
192 if (mii->supports_gmii) {
193 advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
194 tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
195 }
196 if (ecmd->advertising & ADVERTISED_10baseT_Half)
197 tmp |= ADVERTISE_10HALF;
198 if (ecmd->advertising & ADVERTISED_10baseT_Full)
199 tmp |= ADVERTISE_10FULL;
200 if (ecmd->advertising & ADVERTISED_100baseT_Half)
201 tmp |= ADVERTISE_100HALF;
202 if (ecmd->advertising & ADVERTISED_100baseT_Full)
203 tmp |= ADVERTISE_100FULL;
204 if (mii->supports_gmii) {
205 if (ecmd->advertising & ADVERTISED_1000baseT_Half)
206 tmp2 |= ADVERTISE_1000HALF;
207 if (ecmd->advertising & ADVERTISED_1000baseT_Full)
208 tmp2 |= ADVERTISE_1000FULL;
209 }
210 if (advert != tmp) {
211 mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
212 mii->advertising = tmp;
213 }
214 if ((mii->supports_gmii) && (advert2 != tmp2))
215 mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400216
Linus Torvalds1da177e2005-04-16 15:20:36 -0700217 /* turn on autonegotiation, and force a renegotiate */
218 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
219 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
220 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
221
222 mii->force_media = 0;
223 } else {
224 u32 bmcr, tmp;
225
226 /* turn off auto negotiation, set speed and duplexity */
227 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400228 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229 BMCR_SPEED1000 | BMCR_FULLDPLX);
230 if (ecmd->speed == SPEED_1000)
231 tmp |= BMCR_SPEED1000;
232 else if (ecmd->speed == SPEED_100)
233 tmp |= BMCR_SPEED100;
234 if (ecmd->duplex == DUPLEX_FULL) {
235 tmp |= BMCR_FULLDPLX;
236 mii->full_duplex = 1;
237 } else
238 mii->full_duplex = 0;
239 if (bmcr != tmp)
240 mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
241
242 mii->force_media = 1;
243 }
244 return 0;
245}
246
Randy Dunlap32684ec2007-04-06 11:08:24 -0700247/**
248 * mii_check_gmii_support - check if the MII supports Gb interfaces
249 * @mii: the MII interface
250 */
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700251int mii_check_gmii_support(struct mii_if_info *mii)
252{
253 int reg;
254
255 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
256 if (reg & BMSR_ESTATEN) {
257 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
258 if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
259 return 1;
260 }
261
262 return 0;
263}
264
Randy Dunlap32684ec2007-04-06 11:08:24 -0700265/**
266 * mii_link_ok - is link status up/ok
267 * @mii: the MII interface
268 *
269 * Returns 1 if the MII reports link status up/ok, 0 otherwise.
270 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700271int mii_link_ok (struct mii_if_info *mii)
272{
273 /* first, a dummy read, needed to latch some MII phys */
274 mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
275 if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
276 return 1;
277 return 0;
278}
279
Randy Dunlap32684ec2007-04-06 11:08:24 -0700280/**
281 * mii_nway_restart - restart NWay (autonegotiation) for this interface
282 * @mii: the MII interface
283 *
284 * Returns 0 on success, negative on error.
285 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700286int mii_nway_restart (struct mii_if_info *mii)
287{
288 int bmcr;
289 int r = -EINVAL;
290
291 /* if autoneg is off, it's an error */
292 bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
293
294 if (bmcr & BMCR_ANENABLE) {
295 bmcr |= BMCR_ANRESTART;
296 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
297 r = 0;
298 }
299
300 return r;
301}
302
Randy Dunlap32684ec2007-04-06 11:08:24 -0700303/**
304 * mii_check_link - check MII link status
305 * @mii: MII interface
306 *
307 * If the link status changed (previous != current), call
308 * netif_carrier_on() if current link status is Up or call
309 * netif_carrier_off() if current link status is Down.
310 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700311void mii_check_link (struct mii_if_info *mii)
312{
313 int cur_link = mii_link_ok(mii);
314 int prev_link = netif_carrier_ok(mii->dev);
315
316 if (cur_link && !prev_link)
317 netif_carrier_on(mii->dev);
318 else if (prev_link && !cur_link)
319 netif_carrier_off(mii->dev);
320}
321
Randy Dunlap32684ec2007-04-06 11:08:24 -0700322/**
323 * mii_check_media - check the MII interface for a duplex change
324 * @mii: the MII interface
325 * @ok_to_print: OK to print link up/down messages
326 * @init_media: OK to save duplex mode in @mii
327 *
328 * Returns 1 if the duplex mode changed, 0 if not.
329 * If the media type is forced, always returns 0.
330 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700331unsigned int mii_check_media (struct mii_if_info *mii,
332 unsigned int ok_to_print,
333 unsigned int init_media)
334{
335 unsigned int old_carrier, new_carrier;
336 int advertise, lpa, media, duplex;
337 int lpa2 = 0;
338
339 /* if forced media, go no further */
340 if (mii->force_media)
341 return 0; /* duplex did not change */
342
343 /* check current and old link status */
344 old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
345 new_carrier = (unsigned int) mii_link_ok(mii);
346
347 /* if carrier state did not change, this is a "bounce",
348 * just exit as everything is already set correctly
349 */
350 if ((!init_media) && (old_carrier == new_carrier))
351 return 0; /* duplex did not change */
352
353 /* no carrier, nothing much to do */
354 if (!new_carrier) {
355 netif_carrier_off(mii->dev);
356 if (ok_to_print)
357 printk(KERN_INFO "%s: link down\n", mii->dev->name);
358 return 0; /* duplex did not change */
359 }
360
361 /*
362 * we have carrier, see who's on the other end
363 */
364 netif_carrier_on(mii->dev);
365
366 /* get MII advertise and LPA values */
367 if ((!init_media) && (mii->advertising))
368 advertise = mii->advertising;
369 else {
370 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
371 mii->advertising = advertise;
372 }
373 lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
374 if (mii->supports_gmii)
375 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
376
377 /* figure out media and duplex from advertise and LPA values */
378 media = mii_nway_result(lpa & advertise);
379 duplex = (media & ADVERTISE_FULL) ? 1 : 0;
380 if (lpa2 & LPA_1000FULL)
381 duplex = 1;
382
383 if (ok_to_print)
384 printk(KERN_INFO "%s: link up, %sMbps, %s-duplex, lpa 0x%04X\n",
385 mii->dev->name,
386 lpa2 & (LPA_1000FULL | LPA_1000HALF) ? "1000" :
387 media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ? "100" : "10",
388 duplex ? "full" : "half",
389 lpa);
390
391 if ((init_media) || (mii->full_duplex != duplex)) {
392 mii->full_duplex = duplex;
393 return 1; /* duplex changed */
394 }
395
396 return 0; /* duplex did not change */
397}
398
Randy Dunlap32684ec2007-04-06 11:08:24 -0700399/**
400 * generic_mii_ioctl - main MII ioctl interface
401 * @mii_if: the MII interface
402 * @mii_data: MII ioctl data structure
403 * @cmd: MII ioctl command
404 * @duplex_chg_out: pointer to @duplex_changed status if there was no
405 * ioctl error
406 *
407 * Returns 0 on success, negative on error.
408 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700409int generic_mii_ioctl(struct mii_if_info *mii_if,
410 struct mii_ioctl_data *mii_data, int cmd,
411 unsigned int *duplex_chg_out)
412{
413 int rc = 0;
414 unsigned int duplex_changed = 0;
415
416 if (duplex_chg_out)
417 *duplex_chg_out = 0;
418
419 mii_data->phy_id &= mii_if->phy_id_mask;
420 mii_data->reg_num &= mii_if->reg_num_mask;
421
422 switch(cmd) {
423 case SIOCGMIIPHY:
424 mii_data->phy_id = mii_if->phy_id;
425 /* fall through */
426
427 case SIOCGMIIREG:
428 mii_data->val_out =
429 mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
430 mii_data->reg_num);
431 break;
432
433 case SIOCSMIIREG: {
434 u16 val = mii_data->val_in;
435
Linus Torvalds1da177e2005-04-16 15:20:36 -0700436 if (mii_data->phy_id == mii_if->phy_id) {
437 switch(mii_data->reg_num) {
438 case MII_BMCR: {
439 unsigned int new_duplex = 0;
440 if (val & (BMCR_RESET|BMCR_ANENABLE))
441 mii_if->force_media = 0;
442 else
443 mii_if->force_media = 1;
444 if (mii_if->force_media &&
445 (val & BMCR_FULLDPLX))
446 new_duplex = 1;
447 if (mii_if->full_duplex != new_duplex) {
448 duplex_changed = 1;
449 mii_if->full_duplex = new_duplex;
450 }
451 break;
452 }
453 case MII_ADVERTISE:
454 mii_if->advertising = val;
455 break;
456 default:
457 /* do nothing */
458 break;
459 }
460 }
461
462 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
463 mii_data->reg_num, val);
464 break;
465 }
466
467 default:
468 rc = -EOPNOTSUPP;
469 break;
470 }
471
472 if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
473 *duplex_chg_out = 1;
474
475 return rc;
476}
477
478MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
479MODULE_DESCRIPTION ("MII hardware support library");
480MODULE_LICENSE("GPL");
481
482EXPORT_SYMBOL(mii_link_ok);
483EXPORT_SYMBOL(mii_nway_restart);
484EXPORT_SYMBOL(mii_ethtool_gset);
485EXPORT_SYMBOL(mii_ethtool_sset);
486EXPORT_SYMBOL(mii_check_link);
487EXPORT_SYMBOL(mii_check_media);
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700488EXPORT_SYMBOL(mii_check_gmii_support);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700489EXPORT_SYMBOL(generic_mii_ioctl);
490