blob: 4a99c391903790af8ce6b727918455d90415842b [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 Hutchings9c4df532012-02-29 14:26:22 +000034#include <linux/mii.h>
Ben Hutchings59747002009-04-29 08:34:44 +000035
36static u32 mii_get_an(struct mii_if_info *mii, u16 addr)
37{
Ben Hutchings59747002009-04-29 08:34:44 +000038 int advert;
39
40 advert = mii->mdio_read(mii->dev, mii->phy_id, addr);
Ben Hutchings59747002009-04-29 08:34:44 +000041
Matt Carlson37f07022011-11-17 14:30:55 +000042 return mii_lpa_to_ethtool_lpa_t(advert);
Ben Hutchings59747002009-04-29 08:34:44 +000043}
Linus Torvalds1da177e2005-04-16 15:20:36 -070044
Randy Dunlap32684ec2007-04-06 11:08:24 -070045/**
46 * mii_ethtool_gset - get settings that are specified in @ecmd
47 * @mii: MII interface
48 * @ecmd: requested ethtool_cmd
49 *
David Decotigny8ae6dac2011-04-27 18:32:38 +000050 * The @ecmd parameter is expected to have been cleared before calling
51 * mii_ethtool_gset().
52 *
Randy Dunlap32684ec2007-04-06 11:08:24 -070053 * Returns 0 for success, negative on error.
54 */
Linus Torvalds1da177e2005-04-16 15:20:36 -070055int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
56{
57 struct net_device *dev = mii->dev;
Ben Hutchings59747002009-04-29 08:34:44 +000058 u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
59 u32 nego;
Linus Torvalds1da177e2005-04-16 15:20:36 -070060
61 ecmd->supported =
62 (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
63 SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
64 SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
65 if (mii->supports_gmii)
66 ecmd->supported |= SUPPORTED_1000baseT_Half |
67 SUPPORTED_1000baseT_Full;
68
69 /* only supports twisted-pair */
70 ecmd->port = PORT_MII;
71
72 /* only supports internal transceiver */
73 ecmd->transceiver = XCVR_INTERNAL;
74
75 /* this isn't fully supported at higher layers */
76 ecmd->phy_address = mii->phy_id;
Ben Hutchings9c4df532012-02-29 14:26:22 +000077 ecmd->mdio_support = ETH_MDIO_SUPPORTS_C22;
Linus Torvalds1da177e2005-04-16 15:20:36 -070078
79 ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
Linus Torvalds1da177e2005-04-16 15:20:36 -070080
81 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Ben Hutchings59747002009-04-29 08:34:44 +000082 bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
Linus Torvalds1da177e2005-04-16 15:20:36 -070083 if (mii->supports_gmii) {
Ben Hutchings59747002009-04-29 08:34:44 +000084 ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
85 stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
Linus Torvalds1da177e2005-04-16 15:20:36 -070086 }
87 if (bmcr & BMCR_ANENABLE) {
88 ecmd->advertising |= ADVERTISED_Autoneg;
89 ecmd->autoneg = AUTONEG_ENABLE;
Jeff Garzik6aa20a22006-09-13 13:24:59 -040090
Ben Hutchings59747002009-04-29 08:34:44 +000091 ecmd->advertising |= mii_get_an(mii, MII_ADVERTISE);
Matt Carlson28011cf2011-11-16 18:36:59 -050092 if (mii->supports_gmii)
Matt Carlson37f07022011-11-17 14:30:55 +000093 ecmd->advertising |=
94 mii_ctrl1000_to_ethtool_adv_t(ctrl1000);
Ben Hutchings59747002009-04-29 08:34:44 +000095
96 if (bmsr & BMSR_ANEGCOMPLETE) {
97 ecmd->lp_advertising = mii_get_an(mii, MII_LPA);
Matt Carlson28011cf2011-11-16 18:36:59 -050098 ecmd->lp_advertising |=
Matt Carlson37f07022011-11-17 14:30:55 +000099 mii_stat1000_to_ethtool_lpa_t(stat1000);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700100 } else {
Ben Hutchings59747002009-04-29 08:34:44 +0000101 ecmd->lp_advertising = 0;
102 }
103
104 nego = ecmd->advertising & ecmd->lp_advertising;
105
106 if (nego & (ADVERTISED_1000baseT_Full |
107 ADVERTISED_1000baseT_Half)) {
David Decotigny70739492011-04-27 18:32:40 +0000108 ethtool_cmd_speed_set(ecmd, SPEED_1000);
Ben Hutchings59747002009-04-29 08:34:44 +0000109 ecmd->duplex = !!(nego & ADVERTISED_1000baseT_Full);
110 } else if (nego & (ADVERTISED_100baseT_Full |
111 ADVERTISED_100baseT_Half)) {
David Decotigny70739492011-04-27 18:32:40 +0000112 ethtool_cmd_speed_set(ecmd, SPEED_100);
Ben Hutchings59747002009-04-29 08:34:44 +0000113 ecmd->duplex = !!(nego & ADVERTISED_100baseT_Full);
114 } else {
David Decotigny70739492011-04-27 18:32:40 +0000115 ethtool_cmd_speed_set(ecmd, SPEED_10);
Ben Hutchings59747002009-04-29 08:34:44 +0000116 ecmd->duplex = !!(nego & ADVERTISED_10baseT_Full);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700117 }
118 } else {
119 ecmd->autoneg = AUTONEG_DISABLE;
120
David Decotigny70739492011-04-27 18:32:40 +0000121 ethtool_cmd_speed_set(ecmd,
122 ((bmcr & BMCR_SPEED1000 &&
123 (bmcr & BMCR_SPEED100) == 0) ?
124 SPEED_1000 :
125 ((bmcr & BMCR_SPEED100) ?
126 SPEED_100 : SPEED_10)));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127 ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
128 }
129
Ben Hutchings59747002009-04-29 08:34:44 +0000130 mii->full_duplex = ecmd->duplex;
131
Linus Torvalds1da177e2005-04-16 15:20:36 -0700132 /* ignore maxtxpkt, maxrxpkt for now */
133
134 return 0;
135}
136
Randy Dunlap32684ec2007-04-06 11:08:24 -0700137/**
138 * mii_ethtool_sset - set settings that are specified in @ecmd
139 * @mii: MII interface
140 * @ecmd: requested ethtool_cmd
141 *
142 * Returns 0 for success, negative on error.
143 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700144int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
145{
146 struct net_device *dev = mii->dev;
David Decotigny25db0332011-04-27 18:32:39 +0000147 u32 speed = ethtool_cmd_speed(ecmd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700148
David Decotigny25db0332011-04-27 18:32:39 +0000149 if (speed != SPEED_10 &&
150 speed != SPEED_100 &&
151 speed != SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700152 return -EINVAL;
153 if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
154 return -EINVAL;
155 if (ecmd->port != PORT_MII)
156 return -EINVAL;
157 if (ecmd->transceiver != XCVR_INTERNAL)
158 return -EINVAL;
159 if (ecmd->phy_address != mii->phy_id)
160 return -EINVAL;
161 if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
162 return -EINVAL;
David Decotigny25db0332011-04-27 18:32:39 +0000163 if ((speed == SPEED_1000) && (!mii->supports_gmii))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700164 return -EINVAL;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400165
Linus Torvalds1da177e2005-04-16 15:20:36 -0700166 /* ignore supported, maxtxpkt, maxrxpkt */
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400167
Linus Torvalds1da177e2005-04-16 15:20:36 -0700168 if (ecmd->autoneg == AUTONEG_ENABLE) {
169 u32 bmcr, advert, tmp;
170 u32 advert2 = 0, tmp2 = 0;
171
172 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
173 ADVERTISED_10baseT_Full |
174 ADVERTISED_100baseT_Half |
175 ADVERTISED_100baseT_Full |
176 ADVERTISED_1000baseT_Half |
177 ADVERTISED_1000baseT_Full)) == 0)
178 return -EINVAL;
179
180 /* advertise only what has been requested */
181 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
182 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
183 if (mii->supports_gmii) {
184 advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
185 tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
186 }
Matt Carlson37f07022011-11-17 14:30:55 +0000187 tmp |= ethtool_adv_to_mii_adv_t(ecmd->advertising);
Matt Carlson28011cf2011-11-16 18:36:59 -0500188
189 if (mii->supports_gmii)
Matt Carlson37f07022011-11-17 14:30:55 +0000190 tmp2 |=
191 ethtool_adv_to_mii_ctrl1000_t(ecmd->advertising);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700192 if (advert != tmp) {
193 mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
194 mii->advertising = tmp;
195 }
196 if ((mii->supports_gmii) && (advert2 != tmp2))
197 mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400198
Linus Torvalds1da177e2005-04-16 15:20:36 -0700199 /* turn on autonegotiation, and force a renegotiate */
200 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
201 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
202 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
203
204 mii->force_media = 0;
205 } else {
206 u32 bmcr, tmp;
207
208 /* turn off auto negotiation, set speed and duplexity */
209 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400210 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
Linus Torvalds1da177e2005-04-16 15:20:36 -0700211 BMCR_SPEED1000 | BMCR_FULLDPLX);
David Decotigny25db0332011-04-27 18:32:39 +0000212 if (speed == SPEED_1000)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700213 tmp |= BMCR_SPEED1000;
David Decotigny25db0332011-04-27 18:32:39 +0000214 else if (speed == SPEED_100)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700215 tmp |= BMCR_SPEED100;
216 if (ecmd->duplex == DUPLEX_FULL) {
217 tmp |= BMCR_FULLDPLX;
218 mii->full_duplex = 1;
219 } else
220 mii->full_duplex = 0;
221 if (bmcr != tmp)
222 mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
223
224 mii->force_media = 1;
225 }
226 return 0;
227}
228
Randy Dunlap32684ec2007-04-06 11:08:24 -0700229/**
230 * mii_check_gmii_support - check if the MII supports Gb interfaces
231 * @mii: the MII interface
232 */
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700233int mii_check_gmii_support(struct mii_if_info *mii)
234{
235 int reg;
236
237 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
238 if (reg & BMSR_ESTATEN) {
239 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
240 if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
241 return 1;
242 }
243
244 return 0;
245}
246
Randy Dunlap32684ec2007-04-06 11:08:24 -0700247/**
248 * mii_link_ok - is link status up/ok
249 * @mii: the MII interface
250 *
251 * Returns 1 if the MII reports link status up/ok, 0 otherwise.
252 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700253int mii_link_ok (struct mii_if_info *mii)
254{
255 /* first, a dummy read, needed to latch some MII phys */
256 mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
257 if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
258 return 1;
259 return 0;
260}
261
Randy Dunlap32684ec2007-04-06 11:08:24 -0700262/**
263 * mii_nway_restart - restart NWay (autonegotiation) for this interface
264 * @mii: the MII interface
265 *
266 * Returns 0 on success, negative on error.
267 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700268int mii_nway_restart (struct mii_if_info *mii)
269{
270 int bmcr;
271 int r = -EINVAL;
272
273 /* if autoneg is off, it's an error */
274 bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
275
276 if (bmcr & BMCR_ANENABLE) {
277 bmcr |= BMCR_ANRESTART;
278 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
279 r = 0;
280 }
281
282 return r;
283}
284
Randy Dunlap32684ec2007-04-06 11:08:24 -0700285/**
286 * mii_check_link - check MII link status
287 * @mii: MII interface
288 *
289 * If the link status changed (previous != current), call
290 * netif_carrier_on() if current link status is Up or call
291 * netif_carrier_off() if current link status is Down.
292 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700293void mii_check_link (struct mii_if_info *mii)
294{
295 int cur_link = mii_link_ok(mii);
296 int prev_link = netif_carrier_ok(mii->dev);
297
298 if (cur_link && !prev_link)
299 netif_carrier_on(mii->dev);
300 else if (prev_link && !cur_link)
301 netif_carrier_off(mii->dev);
302}
303
Randy Dunlap32684ec2007-04-06 11:08:24 -0700304/**
305 * mii_check_media - check the MII interface for a duplex change
306 * @mii: the MII interface
307 * @ok_to_print: OK to print link up/down messages
308 * @init_media: OK to save duplex mode in @mii
309 *
310 * Returns 1 if the duplex mode changed, 0 if not.
311 * If the media type is forced, always returns 0.
312 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700313unsigned int mii_check_media (struct mii_if_info *mii,
314 unsigned int ok_to_print,
315 unsigned int init_media)
316{
317 unsigned int old_carrier, new_carrier;
318 int advertise, lpa, media, duplex;
319 int lpa2 = 0;
320
321 /* if forced media, go no further */
322 if (mii->force_media)
323 return 0; /* duplex did not change */
324
325 /* check current and old link status */
326 old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
327 new_carrier = (unsigned int) mii_link_ok(mii);
328
329 /* if carrier state did not change, this is a "bounce",
330 * just exit as everything is already set correctly
331 */
332 if ((!init_media) && (old_carrier == new_carrier))
333 return 0; /* duplex did not change */
334
335 /* no carrier, nothing much to do */
336 if (!new_carrier) {
337 netif_carrier_off(mii->dev);
338 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800339 netdev_info(mii->dev, "link down\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340 return 0; /* duplex did not change */
341 }
342
343 /*
344 * we have carrier, see who's on the other end
345 */
346 netif_carrier_on(mii->dev);
347
348 /* get MII advertise and LPA values */
349 if ((!init_media) && (mii->advertising))
350 advertise = mii->advertising;
351 else {
352 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
353 mii->advertising = advertise;
354 }
355 lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
356 if (mii->supports_gmii)
357 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
358
359 /* figure out media and duplex from advertise and LPA values */
360 media = mii_nway_result(lpa & advertise);
361 duplex = (media & ADVERTISE_FULL) ? 1 : 0;
362 if (lpa2 & LPA_1000FULL)
363 duplex = 1;
364
365 if (ok_to_print)
Joe Perches967faf32011-03-03 12:55:08 -0800366 netdev_info(mii->dev, "link up, %uMbps, %s-duplex, lpa 0x%04X\n",
367 lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 :
368 media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ?
369 100 : 10,
370 duplex ? "full" : "half",
371 lpa);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700372
373 if ((init_media) || (mii->full_duplex != duplex)) {
374 mii->full_duplex = duplex;
375 return 1; /* duplex changed */
376 }
377
378 return 0; /* duplex did not change */
379}
380
Randy Dunlap32684ec2007-04-06 11:08:24 -0700381/**
382 * generic_mii_ioctl - main MII ioctl interface
383 * @mii_if: the MII interface
384 * @mii_data: MII ioctl data structure
385 * @cmd: MII ioctl command
386 * @duplex_chg_out: pointer to @duplex_changed status if there was no
387 * ioctl error
388 *
389 * Returns 0 on success, negative on error.
390 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700391int generic_mii_ioctl(struct mii_if_info *mii_if,
392 struct mii_ioctl_data *mii_data, int cmd,
393 unsigned int *duplex_chg_out)
394{
395 int rc = 0;
396 unsigned int duplex_changed = 0;
397
398 if (duplex_chg_out)
399 *duplex_chg_out = 0;
400
401 mii_data->phy_id &= mii_if->phy_id_mask;
402 mii_data->reg_num &= mii_if->reg_num_mask;
403
404 switch(cmd) {
405 case SIOCGMIIPHY:
406 mii_data->phy_id = mii_if->phy_id;
407 /* fall through */
408
409 case SIOCGMIIREG:
410 mii_data->val_out =
411 mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
412 mii_data->reg_num);
413 break;
414
415 case SIOCSMIIREG: {
416 u16 val = mii_data->val_in;
417
Linus Torvalds1da177e2005-04-16 15:20:36 -0700418 if (mii_data->phy_id == mii_if->phy_id) {
419 switch(mii_data->reg_num) {
420 case MII_BMCR: {
421 unsigned int new_duplex = 0;
422 if (val & (BMCR_RESET|BMCR_ANENABLE))
423 mii_if->force_media = 0;
424 else
425 mii_if->force_media = 1;
426 if (mii_if->force_media &&
427 (val & BMCR_FULLDPLX))
428 new_duplex = 1;
429 if (mii_if->full_duplex != new_duplex) {
430 duplex_changed = 1;
431 mii_if->full_duplex = new_duplex;
432 }
433 break;
434 }
435 case MII_ADVERTISE:
436 mii_if->advertising = val;
437 break;
438 default:
439 /* do nothing */
440 break;
441 }
442 }
443
444 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
445 mii_data->reg_num, val);
446 break;
447 }
448
449 default:
450 rc = -EOPNOTSUPP;
451 break;
452 }
453
454 if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
455 *duplex_chg_out = 1;
456
457 return rc;
458}
459
460MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
461MODULE_DESCRIPTION ("MII hardware support library");
462MODULE_LICENSE("GPL");
463
464EXPORT_SYMBOL(mii_link_ok);
465EXPORT_SYMBOL(mii_nway_restart);
466EXPORT_SYMBOL(mii_ethtool_gset);
467EXPORT_SYMBOL(mii_ethtool_sset);
468EXPORT_SYMBOL(mii_check_link);
469EXPORT_SYMBOL(mii_check_media);
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700470EXPORT_SYMBOL(mii_check_gmii_support);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700471EXPORT_SYMBOL(generic_mii_ioctl);
472