blob: d0a29627271331cded0fe4020ce2b81ce6aac009 [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;
Ben Hutchings59747002009-04-29 08:34:44 +000044
Matt Carlson28011cf2011-11-16 18:36:59 -050045 return result | mii_adv_to_ethtool_100bt(advert);
Ben Hutchings59747002009-04-29 08:34:44 +000046}
Linus Torvalds1da177e2005-04-16 15:20:36 -070047
Randy Dunlap32684ec2007-04-06 11:08:24 -070048/**
49 * mii_ethtool_gset - get settings that are specified in @ecmd
50 * @mii: MII interface
51 * @ecmd: requested ethtool_cmd
52 *
David Decotigny8ae6dac2011-04-27 18:32:38 +000053 * The @ecmd parameter is expected to have been cleared before calling
54 * mii_ethtool_gset().
55 *
Randy Dunlap32684ec2007-04-06 11:08:24 -070056 * Returns 0 for success, negative on error.
57 */
Linus Torvalds1da177e2005-04-16 15:20:36 -070058int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
59{
60 struct net_device *dev = mii->dev;
Ben Hutchings59747002009-04-29 08:34:44 +000061 u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
62 u32 nego;
Linus Torvalds1da177e2005-04-16 15:20:36 -070063
64 ecmd->supported =
65 (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
66 SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
67 SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
68 if (mii->supports_gmii)
69 ecmd->supported |= SUPPORTED_1000baseT_Half |
70 SUPPORTED_1000baseT_Full;
71
72 /* only supports twisted-pair */
73 ecmd->port = PORT_MII;
74
75 /* only supports internal transceiver */
76 ecmd->transceiver = XCVR_INTERNAL;
77
78 /* this isn't fully supported at higher layers */
79 ecmd->phy_address = mii->phy_id;
Ben Hutchings59747002009-04-29 08:34:44 +000080 ecmd->mdio_support = MDIO_SUPPORTS_C22;
Linus Torvalds1da177e2005-04-16 15:20:36 -070081
82 ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
Linus Torvalds1da177e2005-04-16 15:20:36 -070083
84 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Ben Hutchings59747002009-04-29 08:34:44 +000085 bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
Linus Torvalds1da177e2005-04-16 15:20:36 -070086 if (mii->supports_gmii) {
Ben Hutchings59747002009-04-29 08:34:44 +000087 ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
88 stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
Linus Torvalds1da177e2005-04-16 15:20:36 -070089 }
90 if (bmcr & BMCR_ANENABLE) {
91 ecmd->advertising |= ADVERTISED_Autoneg;
92 ecmd->autoneg = AUTONEG_ENABLE;
Jeff Garzik6aa20a22006-09-13 13:24:59 -040093
Ben Hutchings59747002009-04-29 08:34:44 +000094 ecmd->advertising |= mii_get_an(mii, MII_ADVERTISE);
Matt Carlson28011cf2011-11-16 18:36:59 -050095 if (mii->supports_gmii)
96 ecmd->advertising |= mii_adv_to_ethtool_1000T(ctrl1000);
Ben Hutchings59747002009-04-29 08:34:44 +000097
98 if (bmsr & BMSR_ANEGCOMPLETE) {
99 ecmd->lp_advertising = mii_get_an(mii, MII_LPA);
Matt Carlson28011cf2011-11-16 18:36:59 -0500100 ecmd->lp_advertising |=
101 mii_lpa_to_ethtool_1000T(stat1000);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700102 } else {
Ben Hutchings59747002009-04-29 08:34:44 +0000103 ecmd->lp_advertising = 0;
104 }
105
106 nego = ecmd->advertising & ecmd->lp_advertising;
107
108 if (nego & (ADVERTISED_1000baseT_Full |
109 ADVERTISED_1000baseT_Half)) {
David Decotigny70739492011-04-27 18:32:40 +0000110 ethtool_cmd_speed_set(ecmd, SPEED_1000);
Ben Hutchings59747002009-04-29 08:34:44 +0000111 ecmd->duplex = !!(nego & ADVERTISED_1000baseT_Full);
112 } else if (nego & (ADVERTISED_100baseT_Full |
113 ADVERTISED_100baseT_Half)) {
David Decotigny70739492011-04-27 18:32:40 +0000114 ethtool_cmd_speed_set(ecmd, SPEED_100);
Ben Hutchings59747002009-04-29 08:34:44 +0000115 ecmd->duplex = !!(nego & ADVERTISED_100baseT_Full);
116 } else {
David Decotigny70739492011-04-27 18:32:40 +0000117 ethtool_cmd_speed_set(ecmd, SPEED_10);
Ben Hutchings59747002009-04-29 08:34:44 +0000118 ecmd->duplex = !!(nego & ADVERTISED_10baseT_Full);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700119 }
120 } else {
121 ecmd->autoneg = AUTONEG_DISABLE;
122
David Decotigny70739492011-04-27 18:32:40 +0000123 ethtool_cmd_speed_set(ecmd,
124 ((bmcr & BMCR_SPEED1000 &&
125 (bmcr & BMCR_SPEED100) == 0) ?
126 SPEED_1000 :
127 ((bmcr & BMCR_SPEED100) ?
128 SPEED_100 : SPEED_10)));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700129 ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
130 }
131
Ben Hutchings59747002009-04-29 08:34:44 +0000132 mii->full_duplex = ecmd->duplex;
133
Linus Torvalds1da177e2005-04-16 15:20:36 -0700134 /* ignore maxtxpkt, maxrxpkt for now */
135
136 return 0;
137}
138
Randy Dunlap32684ec2007-04-06 11:08:24 -0700139/**
140 * mii_ethtool_sset - set settings that are specified in @ecmd
141 * @mii: MII interface
142 * @ecmd: requested ethtool_cmd
143 *
144 * Returns 0 for success, negative on error.
145 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700146int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
147{
148 struct net_device *dev = mii->dev;
David Decotigny25db0332011-04-27 18:32:39 +0000149 u32 speed = ethtool_cmd_speed(ecmd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700150
David Decotigny25db0332011-04-27 18:32:39 +0000151 if (speed != SPEED_10 &&
152 speed != SPEED_100 &&
153 speed != SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700154 return -EINVAL;
155 if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
156 return -EINVAL;
157 if (ecmd->port != PORT_MII)
158 return -EINVAL;
159 if (ecmd->transceiver != XCVR_INTERNAL)
160 return -EINVAL;
161 if (ecmd->phy_address != mii->phy_id)
162 return -EINVAL;
163 if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
164 return -EINVAL;
David Decotigny25db0332011-04-27 18:32:39 +0000165 if ((speed == SPEED_1000) && (!mii->supports_gmii))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700166 return -EINVAL;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400167
Linus Torvalds1da177e2005-04-16 15:20:36 -0700168 /* ignore supported, maxtxpkt, maxrxpkt */
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400169
Linus Torvalds1da177e2005-04-16 15:20:36 -0700170 if (ecmd->autoneg == AUTONEG_ENABLE) {
171 u32 bmcr, advert, tmp;
172 u32 advert2 = 0, tmp2 = 0;
173
174 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
175 ADVERTISED_10baseT_Full |
176 ADVERTISED_100baseT_Half |
177 ADVERTISED_100baseT_Full |
178 ADVERTISED_1000baseT_Half |
179 ADVERTISED_1000baseT_Full)) == 0)
180 return -EINVAL;
181
182 /* advertise only what has been requested */
183 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
184 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
185 if (mii->supports_gmii) {
186 advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
187 tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
188 }
Matt Carlson28011cf2011-11-16 18:36:59 -0500189 tmp |= ethtool_adv_to_mii_100bt(ecmd->advertising);
190
191 if (mii->supports_gmii)
192 tmp2 |= ethtool_adv_to_mii_1000T(ecmd->advertising);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700193 if (advert != tmp) {
194 mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
195 mii->advertising = tmp;
196 }
197 if ((mii->supports_gmii) && (advert2 != tmp2))
198 mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400199
Linus Torvalds1da177e2005-04-16 15:20:36 -0700200 /* turn on autonegotiation, and force a renegotiate */
201 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
202 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
203 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
204
205 mii->force_media = 0;
206 } else {
207 u32 bmcr, tmp;
208
209 /* turn off auto negotiation, set speed and duplexity */
210 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400211 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
Linus Torvalds1da177e2005-04-16 15:20:36 -0700212 BMCR_SPEED1000 | BMCR_FULLDPLX);
David Decotigny25db0332011-04-27 18:32:39 +0000213 if (speed == SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700214 tmp |= BMCR_SPEED1000;
David Decotigny25db0332011-04-27 18:32:39 +0000215 else if (speed == SPEED_100)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700216 tmp |= BMCR_SPEED100;
217 if (ecmd->duplex == DUPLEX_FULL) {
218 tmp |= BMCR_FULLDPLX;
219 mii->full_duplex = 1;
220 } else
221 mii->full_duplex = 0;
222 if (bmcr != tmp)
223 mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
224
225 mii->force_media = 1;
226 }
227 return 0;
228}
229
Randy Dunlap32684ec2007-04-06 11:08:24 -0700230/**
231 * mii_check_gmii_support - check if the MII supports Gb interfaces
232 * @mii: the MII interface
233 */
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700234int mii_check_gmii_support(struct mii_if_info *mii)
235{
236 int reg;
237
238 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
239 if (reg & BMSR_ESTATEN) {
240 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
241 if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
242 return 1;
243 }
244
245 return 0;
246}
247
Randy Dunlap32684ec2007-04-06 11:08:24 -0700248/**
249 * mii_link_ok - is link status up/ok
250 * @mii: the MII interface
251 *
252 * Returns 1 if the MII reports link status up/ok, 0 otherwise.
253 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700254int mii_link_ok (struct mii_if_info *mii)
255{
256 /* first, a dummy read, needed to latch some MII phys */
257 mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
258 if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
259 return 1;
260 return 0;
261}
262
Randy Dunlap32684ec2007-04-06 11:08:24 -0700263/**
264 * mii_nway_restart - restart NWay (autonegotiation) for this interface
265 * @mii: the MII interface
266 *
267 * Returns 0 on success, negative on error.
268 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700269int mii_nway_restart (struct mii_if_info *mii)
270{
271 int bmcr;
272 int r = -EINVAL;
273
274 /* if autoneg is off, it's an error */
275 bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
276
277 if (bmcr & BMCR_ANENABLE) {
278 bmcr |= BMCR_ANRESTART;
279 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
280 r = 0;
281 }
282
283 return r;
284}
285
Randy Dunlap32684ec2007-04-06 11:08:24 -0700286/**
287 * mii_check_link - check MII link status
288 * @mii: MII interface
289 *
290 * If the link status changed (previous != current), call
291 * netif_carrier_on() if current link status is Up or call
292 * netif_carrier_off() if current link status is Down.
293 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700294void mii_check_link (struct mii_if_info *mii)
295{
296 int cur_link = mii_link_ok(mii);
297 int prev_link = netif_carrier_ok(mii->dev);
298
299 if (cur_link && !prev_link)
300 netif_carrier_on(mii->dev);
301 else if (prev_link && !cur_link)
302 netif_carrier_off(mii->dev);
303}
304
Randy Dunlap32684ec2007-04-06 11:08:24 -0700305/**
306 * mii_check_media - check the MII interface for a duplex change
307 * @mii: the MII interface
308 * @ok_to_print: OK to print link up/down messages
309 * @init_media: OK to save duplex mode in @mii
310 *
311 * Returns 1 if the duplex mode changed, 0 if not.
312 * If the media type is forced, always returns 0.
313 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700314unsigned int mii_check_media (struct mii_if_info *mii,
315 unsigned int ok_to_print,
316 unsigned int init_media)
317{
318 unsigned int old_carrier, new_carrier;
319 int advertise, lpa, media, duplex;
320 int lpa2 = 0;
321
322 /* if forced media, go no further */
323 if (mii->force_media)
324 return 0; /* duplex did not change */
325
326 /* check current and old link status */
327 old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
328 new_carrier = (unsigned int) mii_link_ok(mii);
329
330 /* if carrier state did not change, this is a "bounce",
331 * just exit as everything is already set correctly
332 */
333 if ((!init_media) && (old_carrier == new_carrier))
334 return 0; /* duplex did not change */
335
336 /* no carrier, nothing much to do */
337 if (!new_carrier) {
338 netif_carrier_off(mii->dev);
339 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800340 netdev_info(mii->dev, "link down\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700341 return 0; /* duplex did not change */
342 }
343
344 /*
345 * we have carrier, see who's on the other end
346 */
347 netif_carrier_on(mii->dev);
348
349 /* get MII advertise and LPA values */
350 if ((!init_media) && (mii->advertising))
351 advertise = mii->advertising;
352 else {
353 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
354 mii->advertising = advertise;
355 }
356 lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
357 if (mii->supports_gmii)
358 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
359
360 /* figure out media and duplex from advertise and LPA values */
361 media = mii_nway_result(lpa & advertise);
362 duplex = (media & ADVERTISE_FULL) ? 1 : 0;
363 if (lpa2 & LPA_1000FULL)
364 duplex = 1;
365
366 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800367 netdev_info(mii->dev, "link up, %uMbps, %s-duplex, lpa 0x%04X\n",
368 lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 :
369 media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ?
370 100 : 10,
371 duplex ? "full" : "half",
372 lpa);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700373
374 if ((init_media) || (mii->full_duplex != duplex)) {
375 mii->full_duplex = duplex;
376 return 1; /* duplex changed */
377 }
378
379 return 0; /* duplex did not change */
380}
381
Randy Dunlap32684ec2007-04-06 11:08:24 -0700382/**
383 * generic_mii_ioctl - main MII ioctl interface
384 * @mii_if: the MII interface
385 * @mii_data: MII ioctl data structure
386 * @cmd: MII ioctl command
387 * @duplex_chg_out: pointer to @duplex_changed status if there was no
388 * ioctl error
389 *
390 * Returns 0 on success, negative on error.
391 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700392int generic_mii_ioctl(struct mii_if_info *mii_if,
393 struct mii_ioctl_data *mii_data, int cmd,
394 unsigned int *duplex_chg_out)
395{
396 int rc = 0;
397 unsigned int duplex_changed = 0;
398
399 if (duplex_chg_out)
400 *duplex_chg_out = 0;
401
402 mii_data->phy_id &= mii_if->phy_id_mask;
403 mii_data->reg_num &= mii_if->reg_num_mask;
404
405 switch(cmd) {
406 case SIOCGMIIPHY:
407 mii_data->phy_id = mii_if->phy_id;
408 /* fall through */
409
410 case SIOCGMIIREG:
411 mii_data->val_out =
412 mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
413 mii_data->reg_num);
414 break;
415
416 case SIOCSMIIREG: {
417 u16 val = mii_data->val_in;
418
Linus Torvalds1da177e2005-04-16 15:20:36 -0700419 if (mii_data->phy_id == mii_if->phy_id) {
420 switch(mii_data->reg_num) {
421 case MII_BMCR: {
422 unsigned int new_duplex = 0;
423 if (val & (BMCR_RESET|BMCR_ANENABLE))
424 mii_if->force_media = 0;
425 else
426 mii_if->force_media = 1;
427 if (mii_if->force_media &&
428 (val & BMCR_FULLDPLX))
429 new_duplex = 1;
430 if (mii_if->full_duplex != new_duplex) {
431 duplex_changed = 1;
432 mii_if->full_duplex = new_duplex;
433 }
434 break;
435 }
436 case MII_ADVERTISE:
437 mii_if->advertising = val;
438 break;
439 default:
440 /* do nothing */
441 break;
442 }
443 }
444
445 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
446 mii_data->reg_num, val);
447 break;
448 }
449
450 default:
451 rc = -EOPNOTSUPP;
452 break;
453 }
454
455 if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
456 *duplex_chg_out = 1;
457
458 return rc;
459}
460
461MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
462MODULE_DESCRIPTION ("MII hardware support library");
463MODULE_LICENSE("GPL");
464
465EXPORT_SYMBOL(mii_link_ok);
466EXPORT_SYMBOL(mii_nway_restart);
467EXPORT_SYMBOL(mii_ethtool_gset);
468EXPORT_SYMBOL(mii_ethtool_sset);
469EXPORT_SYMBOL(mii_check_link);
470EXPORT_SYMBOL(mii_check_media);
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700471EXPORT_SYMBOL(mii_check_gmii_support);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700472EXPORT_SYMBOL(generic_mii_ioctl);
473