blob: 92056051f2691cd3fe94fa82603cc2a9849511f9 [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>
34#include <linux/mii.h>
35
Randy Dunlap32684ec2007-04-06 11:08:24 -070036/**
37 * mii_ethtool_gset - get settings that are specified in @ecmd
38 * @mii: MII interface
39 * @ecmd: requested ethtool_cmd
40 *
41 * Returns 0 for success, negative on error.
42 */
Linus Torvalds1da177e2005-04-16 15:20:36 -070043int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
44{
45 struct net_device *dev = mii->dev;
46 u32 advert, bmcr, lpa, nego;
47 u32 advert2 = 0, bmcr2 = 0, lpa2 = 0;
48
49 ecmd->supported =
50 (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
51 SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
52 SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
53 if (mii->supports_gmii)
54 ecmd->supported |= SUPPORTED_1000baseT_Half |
55 SUPPORTED_1000baseT_Full;
56
57 /* only supports twisted-pair */
58 ecmd->port = PORT_MII;
59
60 /* only supports internal transceiver */
61 ecmd->transceiver = XCVR_INTERNAL;
62
63 /* this isn't fully supported at higher layers */
64 ecmd->phy_address = mii->phy_id;
65
66 ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
67 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
68 if (mii->supports_gmii)
69 advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
70
71 if (advert & ADVERTISE_10HALF)
72 ecmd->advertising |= ADVERTISED_10baseT_Half;
73 if (advert & ADVERTISE_10FULL)
74 ecmd->advertising |= ADVERTISED_10baseT_Full;
75 if (advert & ADVERTISE_100HALF)
76 ecmd->advertising |= ADVERTISED_100baseT_Half;
77 if (advert & ADVERTISE_100FULL)
78 ecmd->advertising |= ADVERTISED_100baseT_Full;
79 if (advert2 & ADVERTISE_1000HALF)
80 ecmd->advertising |= ADVERTISED_1000baseT_Half;
81 if (advert2 & ADVERTISE_1000FULL)
82 ecmd->advertising |= ADVERTISED_1000baseT_Full;
83
84 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
85 lpa = mii->mdio_read(dev, mii->phy_id, MII_LPA);
86 if (mii->supports_gmii) {
87 bmcr2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
88 lpa2 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
89 }
90 if (bmcr & BMCR_ANENABLE) {
91 ecmd->advertising |= ADVERTISED_Autoneg;
92 ecmd->autoneg = AUTONEG_ENABLE;
Jeff Garzik6aa20a22006-09-13 13:24:59 -040093
Linus Torvalds1da177e2005-04-16 15:20:36 -070094 nego = mii_nway_result(advert & lpa);
Jeff Garzik6aa20a22006-09-13 13:24:59 -040095 if ((bmcr2 & (ADVERTISE_1000HALF | ADVERTISE_1000FULL)) &
Linus Torvalds1da177e2005-04-16 15:20:36 -070096 (lpa2 >> 2))
97 ecmd->speed = SPEED_1000;
98 else if (nego == LPA_100FULL || nego == LPA_100HALF)
99 ecmd->speed = SPEED_100;
100 else
101 ecmd->speed = SPEED_10;
102 if ((lpa2 & LPA_1000FULL) || nego == LPA_100FULL ||
103 nego == LPA_10FULL) {
104 ecmd->duplex = DUPLEX_FULL;
105 mii->full_duplex = 1;
106 } else {
107 ecmd->duplex = DUPLEX_HALF;
108 mii->full_duplex = 0;
109 }
110 } else {
111 ecmd->autoneg = AUTONEG_DISABLE;
112
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400113 ecmd->speed = ((bmcr & BMCR_SPEED1000 &&
Linus Torvalds1da177e2005-04-16 15:20:36 -0700114 (bmcr & BMCR_SPEED100) == 0) ? SPEED_1000 :
115 (bmcr & BMCR_SPEED100) ? SPEED_100 : SPEED_10);
116 ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
117 }
118
119 /* ignore maxtxpkt, maxrxpkt for now */
120
121 return 0;
122}
123
Randy Dunlap32684ec2007-04-06 11:08:24 -0700124/**
125 * mii_ethtool_sset - set settings that are specified in @ecmd
126 * @mii: MII interface
127 * @ecmd: requested ethtool_cmd
128 *
129 * Returns 0 for success, negative on error.
130 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
132{
133 struct net_device *dev = mii->dev;
134
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400135 if (ecmd->speed != SPEED_10 &&
136 ecmd->speed != SPEED_100 &&
Linus Torvalds1da177e2005-04-16 15:20:36 -0700137 ecmd->speed != SPEED_1000)
138 return -EINVAL;
139 if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
140 return -EINVAL;
141 if (ecmd->port != PORT_MII)
142 return -EINVAL;
143 if (ecmd->transceiver != XCVR_INTERNAL)
144 return -EINVAL;
145 if (ecmd->phy_address != mii->phy_id)
146 return -EINVAL;
147 if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
148 return -EINVAL;
149 if ((ecmd->speed == SPEED_1000) && (!mii->supports_gmii))
150 return -EINVAL;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400151
Linus Torvalds1da177e2005-04-16 15:20:36 -0700152 /* ignore supported, maxtxpkt, maxrxpkt */
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400153
Linus Torvalds1da177e2005-04-16 15:20:36 -0700154 if (ecmd->autoneg == AUTONEG_ENABLE) {
155 u32 bmcr, advert, tmp;
156 u32 advert2 = 0, tmp2 = 0;
157
158 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
159 ADVERTISED_10baseT_Full |
160 ADVERTISED_100baseT_Half |
161 ADVERTISED_100baseT_Full |
162 ADVERTISED_1000baseT_Half |
163 ADVERTISED_1000baseT_Full)) == 0)
164 return -EINVAL;
165
166 /* advertise only what has been requested */
167 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
168 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
169 if (mii->supports_gmii) {
170 advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
171 tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
172 }
173 if (ecmd->advertising & ADVERTISED_10baseT_Half)
174 tmp |= ADVERTISE_10HALF;
175 if (ecmd->advertising & ADVERTISED_10baseT_Full)
176 tmp |= ADVERTISE_10FULL;
177 if (ecmd->advertising & ADVERTISED_100baseT_Half)
178 tmp |= ADVERTISE_100HALF;
179 if (ecmd->advertising & ADVERTISED_100baseT_Full)
180 tmp |= ADVERTISE_100FULL;
181 if (mii->supports_gmii) {
182 if (ecmd->advertising & ADVERTISED_1000baseT_Half)
183 tmp2 |= ADVERTISE_1000HALF;
184 if (ecmd->advertising & ADVERTISED_1000baseT_Full)
185 tmp2 |= ADVERTISE_1000FULL;
186 }
187 if (advert != tmp) {
188 mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
189 mii->advertising = tmp;
190 }
191 if ((mii->supports_gmii) && (advert2 != tmp2))
192 mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400193
Linus Torvalds1da177e2005-04-16 15:20:36 -0700194 /* turn on autonegotiation, and force a renegotiate */
195 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
196 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
197 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
198
199 mii->force_media = 0;
200 } else {
201 u32 bmcr, tmp;
202
203 /* turn off auto negotiation, set speed and duplexity */
204 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400205 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
Linus Torvalds1da177e2005-04-16 15:20:36 -0700206 BMCR_SPEED1000 | BMCR_FULLDPLX);
207 if (ecmd->speed == SPEED_1000)
208 tmp |= BMCR_SPEED1000;
209 else if (ecmd->speed == SPEED_100)
210 tmp |= BMCR_SPEED100;
211 if (ecmd->duplex == DUPLEX_FULL) {
212 tmp |= BMCR_FULLDPLX;
213 mii->full_duplex = 1;
214 } else
215 mii->full_duplex = 0;
216 if (bmcr != tmp)
217 mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
218
219 mii->force_media = 1;
220 }
221 return 0;
222}
223
Randy Dunlap32684ec2007-04-06 11:08:24 -0700224/**
225 * mii_check_gmii_support - check if the MII supports Gb interfaces
226 * @mii: the MII interface
227 */
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700228int mii_check_gmii_support(struct mii_if_info *mii)
229{
230 int reg;
231
232 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
233 if (reg & BMSR_ESTATEN) {
234 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
235 if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
236 return 1;
237 }
238
239 return 0;
240}
241
Randy Dunlap32684ec2007-04-06 11:08:24 -0700242/**
243 * mii_link_ok - is link status up/ok
244 * @mii: the MII interface
245 *
246 * Returns 1 if the MII reports link status up/ok, 0 otherwise.
247 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700248int mii_link_ok (struct mii_if_info *mii)
249{
250 /* first, a dummy read, needed to latch some MII phys */
251 mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
252 if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
253 return 1;
254 return 0;
255}
256
Randy Dunlap32684ec2007-04-06 11:08:24 -0700257/**
258 * mii_nway_restart - restart NWay (autonegotiation) for this interface
259 * @mii: the MII interface
260 *
261 * Returns 0 on success, negative on error.
262 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700263int mii_nway_restart (struct mii_if_info *mii)
264{
265 int bmcr;
266 int r = -EINVAL;
267
268 /* if autoneg is off, it's an error */
269 bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
270
271 if (bmcr & BMCR_ANENABLE) {
272 bmcr |= BMCR_ANRESTART;
273 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
274 r = 0;
275 }
276
277 return r;
278}
279
Randy Dunlap32684ec2007-04-06 11:08:24 -0700280/**
281 * mii_check_link - check MII link status
282 * @mii: MII interface
283 *
284 * If the link status changed (previous != current), call
285 * netif_carrier_on() if current link status is Up or call
286 * netif_carrier_off() if current link status is Down.
287 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700288void mii_check_link (struct mii_if_info *mii)
289{
290 int cur_link = mii_link_ok(mii);
291 int prev_link = netif_carrier_ok(mii->dev);
292
293 if (cur_link && !prev_link)
294 netif_carrier_on(mii->dev);
295 else if (prev_link && !cur_link)
296 netif_carrier_off(mii->dev);
297}
298
Randy Dunlap32684ec2007-04-06 11:08:24 -0700299/**
300 * mii_check_media - check the MII interface for a duplex change
301 * @mii: the MII interface
302 * @ok_to_print: OK to print link up/down messages
303 * @init_media: OK to save duplex mode in @mii
304 *
305 * Returns 1 if the duplex mode changed, 0 if not.
306 * If the media type is forced, always returns 0.
307 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700308unsigned int mii_check_media (struct mii_if_info *mii,
309 unsigned int ok_to_print,
310 unsigned int init_media)
311{
312 unsigned int old_carrier, new_carrier;
313 int advertise, lpa, media, duplex;
314 int lpa2 = 0;
315
316 /* if forced media, go no further */
317 if (mii->force_media)
318 return 0; /* duplex did not change */
319
320 /* check current and old link status */
321 old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
322 new_carrier = (unsigned int) mii_link_ok(mii);
323
324 /* if carrier state did not change, this is a "bounce",
325 * just exit as everything is already set correctly
326 */
327 if ((!init_media) && (old_carrier == new_carrier))
328 return 0; /* duplex did not change */
329
330 /* no carrier, nothing much to do */
331 if (!new_carrier) {
332 netif_carrier_off(mii->dev);
333 if (ok_to_print)
334 printk(KERN_INFO "%s: link down\n", mii->dev->name);
335 return 0; /* duplex did not change */
336 }
337
338 /*
339 * we have carrier, see who's on the other end
340 */
341 netif_carrier_on(mii->dev);
342
343 /* get MII advertise and LPA values */
344 if ((!init_media) && (mii->advertising))
345 advertise = mii->advertising;
346 else {
347 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
348 mii->advertising = advertise;
349 }
350 lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
351 if (mii->supports_gmii)
352 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
353
354 /* figure out media and duplex from advertise and LPA values */
355 media = mii_nway_result(lpa & advertise);
356 duplex = (media & ADVERTISE_FULL) ? 1 : 0;
357 if (lpa2 & LPA_1000FULL)
358 duplex = 1;
359
360 if (ok_to_print)
361 printk(KERN_INFO "%s: link up, %sMbps, %s-duplex, lpa 0x%04X\n",
362 mii->dev->name,
363 lpa2 & (LPA_1000FULL | LPA_1000HALF) ? "1000" :
364 media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ? "100" : "10",
365 duplex ? "full" : "half",
366 lpa);
367
368 if ((init_media) || (mii->full_duplex != duplex)) {
369 mii->full_duplex = duplex;
370 return 1; /* duplex changed */
371 }
372
373 return 0; /* duplex did not change */
374}
375
Randy Dunlap32684ec2007-04-06 11:08:24 -0700376/**
377 * generic_mii_ioctl - main MII ioctl interface
378 * @mii_if: the MII interface
379 * @mii_data: MII ioctl data structure
380 * @cmd: MII ioctl command
381 * @duplex_chg_out: pointer to @duplex_changed status if there was no
382 * ioctl error
383 *
384 * Returns 0 on success, negative on error.
385 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700386int generic_mii_ioctl(struct mii_if_info *mii_if,
387 struct mii_ioctl_data *mii_data, int cmd,
388 unsigned int *duplex_chg_out)
389{
390 int rc = 0;
391 unsigned int duplex_changed = 0;
392
393 if (duplex_chg_out)
394 *duplex_chg_out = 0;
395
396 mii_data->phy_id &= mii_if->phy_id_mask;
397 mii_data->reg_num &= mii_if->reg_num_mask;
398
399 switch(cmd) {
400 case SIOCGMIIPHY:
401 mii_data->phy_id = mii_if->phy_id;
402 /* fall through */
403
404 case SIOCGMIIREG:
405 mii_data->val_out =
406 mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
407 mii_data->reg_num);
408 break;
409
410 case SIOCSMIIREG: {
411 u16 val = mii_data->val_in;
412
413 if (!capable(CAP_NET_ADMIN))
414 return -EPERM;
415
416 if (mii_data->phy_id == mii_if->phy_id) {
417 switch(mii_data->reg_num) {
418 case MII_BMCR: {
419 unsigned int new_duplex = 0;
420 if (val & (BMCR_RESET|BMCR_ANENABLE))
421 mii_if->force_media = 0;
422 else
423 mii_if->force_media = 1;
424 if (mii_if->force_media &&
425 (val & BMCR_FULLDPLX))
426 new_duplex = 1;
427 if (mii_if->full_duplex != new_duplex) {
428 duplex_changed = 1;
429 mii_if->full_duplex = new_duplex;
430 }
431 break;
432 }
433 case MII_ADVERTISE:
434 mii_if->advertising = val;
435 break;
436 default:
437 /* do nothing */
438 break;
439 }
440 }
441
442 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
443 mii_data->reg_num, val);
444 break;
445 }
446
447 default:
448 rc = -EOPNOTSUPP;
449 break;
450 }
451
452 if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
453 *duplex_chg_out = 1;
454
455 return rc;
456}
457
458MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
459MODULE_DESCRIPTION ("MII hardware support library");
460MODULE_LICENSE("GPL");
461
462EXPORT_SYMBOL(mii_link_ok);
463EXPORT_SYMBOL(mii_nway_restart);
464EXPORT_SYMBOL(mii_ethtool_gset);
465EXPORT_SYMBOL(mii_ethtool_sset);
466EXPORT_SYMBOL(mii_check_link);
467EXPORT_SYMBOL(mii_check_media);
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700468EXPORT_SYMBOL(mii_check_gmii_support);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700469EXPORT_SYMBOL(generic_mii_ioctl);
470