]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/net/mii.c
6d953c53eed6c86d9c6db39704a374ee5fac1bde
[karo-tx-linux.git] / drivers / net / mii.c
1 /*
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
36 static u32 mii_get_an(struct mii_if_info *mii, u16 addr)
37 {
38         int advert;
39
40         advert = mii->mdio_read(mii->dev, mii->phy_id, addr);
41
42         return mii_lpa_to_ethtool_lpa_t(advert);
43 }
44
45 /**
46  * mii_ethtool_gset - get settings that are specified in @ecmd
47  * @mii: MII interface
48  * @ecmd: requested ethtool_cmd
49  *
50  * The @ecmd parameter is expected to have been cleared before calling
51  * mii_ethtool_gset().
52  *
53  * Returns 0 for success, negative on error.
54  */
55 int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
56 {
57         struct net_device *dev = mii->dev;
58         u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
59         u32 nego;
60
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;
77         ecmd->mdio_support = ETH_MDIO_SUPPORTS_C22;
78
79         ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
80
81         bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
82         bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
83         if (mii->supports_gmii) {
84                 ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
85                 stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
86         }
87         if (bmcr & BMCR_ANENABLE) {
88                 ecmd->advertising |= ADVERTISED_Autoneg;
89                 ecmd->autoneg = AUTONEG_ENABLE;
90
91                 ecmd->advertising |= mii_get_an(mii, MII_ADVERTISE);
92                 if (mii->supports_gmii)
93                         ecmd->advertising |=
94                                         mii_ctrl1000_to_ethtool_adv_t(ctrl1000);
95
96                 if (bmsr & BMSR_ANEGCOMPLETE) {
97                         ecmd->lp_advertising = mii_get_an(mii, MII_LPA);
98                         ecmd->lp_advertising |=
99                                         mii_stat1000_to_ethtool_lpa_t(stat1000);
100                 } else {
101                         ecmd->lp_advertising = 0;
102                 }
103
104                 nego = ecmd->advertising & ecmd->lp_advertising;
105
106                 if (nego & (ADVERTISED_1000baseT_Full |
107                             ADVERTISED_1000baseT_Half)) {
108                         ethtool_cmd_speed_set(ecmd, SPEED_1000);
109                         ecmd->duplex = !!(nego & ADVERTISED_1000baseT_Full);
110                 } else if (nego & (ADVERTISED_100baseT_Full |
111                                    ADVERTISED_100baseT_Half)) {
112                         ethtool_cmd_speed_set(ecmd, SPEED_100);
113                         ecmd->duplex = !!(nego & ADVERTISED_100baseT_Full);
114                 } else {
115                         ethtool_cmd_speed_set(ecmd, SPEED_10);
116                         ecmd->duplex = !!(nego & ADVERTISED_10baseT_Full);
117                 }
118         } else {
119                 ecmd->autoneg = AUTONEG_DISABLE;
120
121                 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)));
127                 ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
128         }
129
130         mii->full_duplex = ecmd->duplex;
131
132         /* ignore maxtxpkt, maxrxpkt for now */
133
134         return 0;
135 }
136
137 /**
138  * mii_ethtool_get_link_ksettings - get settings that are specified in @cmd
139  * @mii: MII interface
140  * @cmd: requested ethtool_link_ksettings
141  *
142  * The @cmd parameter is expected to have been cleared before calling
143  * mii_ethtool_get_link_ksettings().
144  *
145  * Returns 0 for success, negative on error.
146  */
147 int mii_ethtool_get_link_ksettings(struct mii_if_info *mii,
148                                    struct ethtool_link_ksettings *cmd)
149 {
150         struct net_device *dev = mii->dev;
151         u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
152         u32 nego, supported, advertising, lp_advertising;
153
154         supported = (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
155                      SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
156                      SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
157         if (mii->supports_gmii)
158                 supported |= SUPPORTED_1000baseT_Half |
159                         SUPPORTED_1000baseT_Full;
160
161         /* only supports twisted-pair */
162         cmd->base.port = PORT_MII;
163
164         /* this isn't fully supported at higher layers */
165         cmd->base.phy_address = mii->phy_id;
166         cmd->base.mdio_support = ETH_MDIO_SUPPORTS_C22;
167
168         advertising = ADVERTISED_TP | ADVERTISED_MII;
169
170         bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
171         bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
172         if (mii->supports_gmii) {
173                 ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
174                 stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
175         }
176         if (bmcr & BMCR_ANENABLE) {
177                 advertising |= ADVERTISED_Autoneg;
178                 cmd->base.autoneg = AUTONEG_ENABLE;
179
180                 advertising |= mii_get_an(mii, MII_ADVERTISE);
181                 if (mii->supports_gmii)
182                         advertising |= mii_ctrl1000_to_ethtool_adv_t(ctrl1000);
183
184                 if (bmsr & BMSR_ANEGCOMPLETE) {
185                         lp_advertising = mii_get_an(mii, MII_LPA);
186                         lp_advertising |=
187                                         mii_stat1000_to_ethtool_lpa_t(stat1000);
188                 } else {
189                         lp_advertising = 0;
190                 }
191
192                 nego = advertising & lp_advertising;
193
194                 if (nego & (ADVERTISED_1000baseT_Full |
195                             ADVERTISED_1000baseT_Half)) {
196                         cmd->base.speed = SPEED_1000;
197                         cmd->base.duplex = !!(nego & ADVERTISED_1000baseT_Full);
198                 } else if (nego & (ADVERTISED_100baseT_Full |
199                                    ADVERTISED_100baseT_Half)) {
200                         cmd->base.speed = SPEED_100;
201                         cmd->base.duplex = !!(nego & ADVERTISED_100baseT_Full);
202                 } else {
203                         cmd->base.speed = SPEED_10;
204                         cmd->base.duplex = !!(nego & ADVERTISED_10baseT_Full);
205                 }
206         } else {
207                 cmd->base.autoneg = AUTONEG_DISABLE;
208
209                 cmd->base.speed = ((bmcr & BMCR_SPEED1000 &&
210                                     (bmcr & BMCR_SPEED100) == 0) ?
211                                    SPEED_1000 :
212                                    ((bmcr & BMCR_SPEED100) ?
213                                     SPEED_100 : SPEED_10));
214                 cmd->base.duplex = (bmcr & BMCR_FULLDPLX) ?
215                         DUPLEX_FULL : DUPLEX_HALF;
216
217                 lp_advertising = 0;
218         }
219
220         mii->full_duplex = cmd->base.duplex;
221
222         ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported,
223                                                 supported);
224         ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising,
225                                                 advertising);
226         ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.lp_advertising,
227                                                 lp_advertising);
228
229         /* ignore maxtxpkt, maxrxpkt for now */
230
231         return 0;
232 }
233
234 /**
235  * mii_ethtool_sset - set settings that are specified in @ecmd
236  * @mii: MII interface
237  * @ecmd: requested ethtool_cmd
238  *
239  * Returns 0 for success, negative on error.
240  */
241 int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
242 {
243         struct net_device *dev = mii->dev;
244         u32 speed = ethtool_cmd_speed(ecmd);
245
246         if (speed != SPEED_10 &&
247             speed != SPEED_100 &&
248             speed != SPEED_1000)
249                 return -EINVAL;
250         if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
251                 return -EINVAL;
252         if (ecmd->port != PORT_MII)
253                 return -EINVAL;
254         if (ecmd->transceiver != XCVR_INTERNAL)
255                 return -EINVAL;
256         if (ecmd->phy_address != mii->phy_id)
257                 return -EINVAL;
258         if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
259                 return -EINVAL;
260         if ((speed == SPEED_1000) && (!mii->supports_gmii))
261                 return -EINVAL;
262
263         /* ignore supported, maxtxpkt, maxrxpkt */
264
265         if (ecmd->autoneg == AUTONEG_ENABLE) {
266                 u32 bmcr, advert, tmp;
267                 u32 advert2 = 0, tmp2 = 0;
268
269                 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
270                                           ADVERTISED_10baseT_Full |
271                                           ADVERTISED_100baseT_Half |
272                                           ADVERTISED_100baseT_Full |
273                                           ADVERTISED_1000baseT_Half |
274                                           ADVERTISED_1000baseT_Full)) == 0)
275                         return -EINVAL;
276
277                 /* advertise only what has been requested */
278                 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
279                 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
280                 if (mii->supports_gmii) {
281                         advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
282                         tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
283                 }
284                 tmp |= ethtool_adv_to_mii_adv_t(ecmd->advertising);
285
286                 if (mii->supports_gmii)
287                         tmp2 |=
288                               ethtool_adv_to_mii_ctrl1000_t(ecmd->advertising);
289                 if (advert != tmp) {
290                         mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
291                         mii->advertising = tmp;
292                 }
293                 if ((mii->supports_gmii) && (advert2 != tmp2))
294                         mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
295
296                 /* turn on autonegotiation, and force a renegotiate */
297                 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
298                 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
299                 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
300
301                 mii->force_media = 0;
302         } else {
303                 u32 bmcr, tmp;
304
305                 /* turn off auto negotiation, set speed and duplexity */
306                 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
307                 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
308                                BMCR_SPEED1000 | BMCR_FULLDPLX);
309                 if (speed == SPEED_1000)
310                         tmp |= BMCR_SPEED1000;
311                 else if (speed == SPEED_100)
312                         tmp |= BMCR_SPEED100;
313                 if (ecmd->duplex == DUPLEX_FULL) {
314                         tmp |= BMCR_FULLDPLX;
315                         mii->full_duplex = 1;
316                 } else
317                         mii->full_duplex = 0;
318                 if (bmcr != tmp)
319                         mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
320
321                 mii->force_media = 1;
322         }
323         return 0;
324 }
325
326 /**
327  * mii_ethtool_set_link_ksettings - set settings that are specified in @cmd
328  * @mii: MII interfaces
329  * @cmd: requested ethtool_link_ksettings
330  *
331  * Returns 0 for success, negative on error.
332  */
333 int mii_ethtool_set_link_ksettings(struct mii_if_info *mii,
334                                    const struct ethtool_link_ksettings *cmd)
335 {
336         struct net_device *dev = mii->dev;
337         u32 speed = cmd->base.speed;
338
339         if (speed != SPEED_10 &&
340             speed != SPEED_100 &&
341             speed != SPEED_1000)
342                 return -EINVAL;
343         if (cmd->base.duplex != DUPLEX_HALF && cmd->base.duplex != DUPLEX_FULL)
344                 return -EINVAL;
345         if (cmd->base.port != PORT_MII)
346                 return -EINVAL;
347         if (cmd->base.phy_address != mii->phy_id)
348                 return -EINVAL;
349         if (cmd->base.autoneg != AUTONEG_DISABLE &&
350             cmd->base.autoneg != AUTONEG_ENABLE)
351                 return -EINVAL;
352         if ((speed == SPEED_1000) && (!mii->supports_gmii))
353                 return -EINVAL;
354
355         /* ignore supported, maxtxpkt, maxrxpkt */
356
357         if (cmd->base.autoneg == AUTONEG_ENABLE) {
358                 u32 bmcr, advert, tmp;
359                 u32 advert2 = 0, tmp2 = 0;
360                 u32 advertising;
361
362                 ethtool_convert_link_mode_to_legacy_u32(
363                         &advertising, cmd->link_modes.advertising);
364
365                 if ((advertising & (ADVERTISED_10baseT_Half |
366                                     ADVERTISED_10baseT_Full |
367                                     ADVERTISED_100baseT_Half |
368                                     ADVERTISED_100baseT_Full |
369                                     ADVERTISED_1000baseT_Half |
370                                     ADVERTISED_1000baseT_Full)) == 0)
371                         return -EINVAL;
372
373                 /* advertise only what has been requested */
374                 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
375                 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
376                 if (mii->supports_gmii) {
377                         advert2 = mii->mdio_read(dev, mii->phy_id,
378                                                  MII_CTRL1000);
379                         tmp2 = advert2 &
380                                 ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
381                 }
382                 tmp |= ethtool_adv_to_mii_adv_t(advertising);
383
384                 if (mii->supports_gmii)
385                         tmp2 |= ethtool_adv_to_mii_ctrl1000_t(advertising);
386                 if (advert != tmp) {
387                         mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
388                         mii->advertising = tmp;
389                 }
390                 if ((mii->supports_gmii) && (advert2 != tmp2))
391                         mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
392
393                 /* turn on autonegotiation, and force a renegotiate */
394                 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
395                 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
396                 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
397
398                 mii->force_media = 0;
399         } else {
400                 u32 bmcr, tmp;
401
402                 /* turn off auto negotiation, set speed and duplexity */
403                 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
404                 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
405                                BMCR_SPEED1000 | BMCR_FULLDPLX);
406                 if (speed == SPEED_1000)
407                         tmp |= BMCR_SPEED1000;
408                 else if (speed == SPEED_100)
409                         tmp |= BMCR_SPEED100;
410                 if (cmd->base.duplex == DUPLEX_FULL) {
411                         tmp |= BMCR_FULLDPLX;
412                         mii->full_duplex = 1;
413                 } else {
414                         mii->full_duplex = 0;
415                 }
416                 if (bmcr != tmp)
417                         mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
418
419                 mii->force_media = 1;
420         }
421         return 0;
422 }
423
424 /**
425  * mii_check_gmii_support - check if the MII supports Gb interfaces
426  * @mii: the MII interface
427  */
428 int mii_check_gmii_support(struct mii_if_info *mii)
429 {
430         int reg;
431
432         reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
433         if (reg & BMSR_ESTATEN) {
434                 reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
435                 if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
436                         return 1;
437         }
438
439         return 0;
440 }
441
442 /**
443  * mii_link_ok - is link status up/ok
444  * @mii: the MII interface
445  *
446  * Returns 1 if the MII reports link status up/ok, 0 otherwise.
447  */
448 int mii_link_ok (struct mii_if_info *mii)
449 {
450         /* first, a dummy read, needed to latch some MII phys */
451         mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
452         if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
453                 return 1;
454         return 0;
455 }
456
457 /**
458  * mii_nway_restart - restart NWay (autonegotiation) for this interface
459  * @mii: the MII interface
460  *
461  * Returns 0 on success, negative on error.
462  */
463 int mii_nway_restart (struct mii_if_info *mii)
464 {
465         int bmcr;
466         int r = -EINVAL;
467
468         /* if autoneg is off, it's an error */
469         bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
470
471         if (bmcr & BMCR_ANENABLE) {
472                 bmcr |= BMCR_ANRESTART;
473                 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
474                 r = 0;
475         }
476
477         return r;
478 }
479
480 /**
481  * mii_check_link - check MII link status
482  * @mii: MII interface
483  *
484  * If the link status changed (previous != current), call
485  * netif_carrier_on() if current link status is Up or call
486  * netif_carrier_off() if current link status is Down.
487  */
488 void mii_check_link (struct mii_if_info *mii)
489 {
490         int cur_link = mii_link_ok(mii);
491         int prev_link = netif_carrier_ok(mii->dev);
492
493         if (cur_link && !prev_link)
494                 netif_carrier_on(mii->dev);
495         else if (prev_link && !cur_link)
496                 netif_carrier_off(mii->dev);
497 }
498
499 /**
500  * mii_check_media - check the MII interface for a carrier/speed/duplex change
501  * @mii: the MII interface
502  * @ok_to_print: OK to print link up/down messages
503  * @init_media: OK to save duplex mode in @mii
504  *
505  * Returns 1 if the duplex mode changed, 0 if not.
506  * If the media type is forced, always returns 0.
507  */
508 unsigned int mii_check_media (struct mii_if_info *mii,
509                               unsigned int ok_to_print,
510                               unsigned int init_media)
511 {
512         unsigned int old_carrier, new_carrier;
513         int advertise, lpa, media, duplex;
514         int lpa2 = 0;
515
516         /* check current and old link status */
517         old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
518         new_carrier = (unsigned int) mii_link_ok(mii);
519
520         /* if carrier state did not change, this is a "bounce",
521          * just exit as everything is already set correctly
522          */
523         if ((!init_media) && (old_carrier == new_carrier))
524                 return 0; /* duplex did not change */
525
526         /* no carrier, nothing much to do */
527         if (!new_carrier) {
528                 netif_carrier_off(mii->dev);
529                 if (ok_to_print)
530                         netdev_info(mii->dev, "link down\n");
531                 return 0; /* duplex did not change */
532         }
533
534         /*
535          * we have carrier, see who's on the other end
536          */
537         netif_carrier_on(mii->dev);
538
539         if (mii->force_media) {
540                 if (ok_to_print)
541                         netdev_info(mii->dev, "link up\n");
542                 return 0; /* duplex did not change */
543         }
544
545         /* get MII advertise and LPA values */
546         if ((!init_media) && (mii->advertising))
547                 advertise = mii->advertising;
548         else {
549                 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
550                 mii->advertising = advertise;
551         }
552         lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
553         if (mii->supports_gmii)
554                 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
555
556         /* figure out media and duplex from advertise and LPA values */
557         media = mii_nway_result(lpa & advertise);
558         duplex = (media & ADVERTISE_FULL) ? 1 : 0;
559         if (lpa2 & LPA_1000FULL)
560                 duplex = 1;
561
562         if (ok_to_print)
563                 netdev_info(mii->dev, "link up, %uMbps, %s-duplex, lpa 0x%04X\n",
564                             lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 :
565                             media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ?
566                             100 : 10,
567                             duplex ? "full" : "half",
568                             lpa);
569
570         if ((init_media) || (mii->full_duplex != duplex)) {
571                 mii->full_duplex = duplex;
572                 return 1; /* duplex changed */
573         }
574
575         return 0; /* duplex did not change */
576 }
577
578 /**
579  * generic_mii_ioctl - main MII ioctl interface
580  * @mii_if: the MII interface
581  * @mii_data: MII ioctl data structure
582  * @cmd: MII ioctl command
583  * @duplex_chg_out: pointer to @duplex_changed status if there was no
584  *      ioctl error
585  *
586  * Returns 0 on success, negative on error.
587  */
588 int generic_mii_ioctl(struct mii_if_info *mii_if,
589                       struct mii_ioctl_data *mii_data, int cmd,
590                       unsigned int *duplex_chg_out)
591 {
592         int rc = 0;
593         unsigned int duplex_changed = 0;
594
595         if (duplex_chg_out)
596                 *duplex_chg_out = 0;
597
598         mii_data->phy_id &= mii_if->phy_id_mask;
599         mii_data->reg_num &= mii_if->reg_num_mask;
600
601         switch(cmd) {
602         case SIOCGMIIPHY:
603                 mii_data->phy_id = mii_if->phy_id;
604                 /* fall through */
605
606         case SIOCGMIIREG:
607                 mii_data->val_out =
608                         mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
609                                           mii_data->reg_num);
610                 break;
611
612         case SIOCSMIIREG: {
613                 u16 val = mii_data->val_in;
614
615                 if (mii_data->phy_id == mii_if->phy_id) {
616                         switch(mii_data->reg_num) {
617                         case MII_BMCR: {
618                                 unsigned int new_duplex = 0;
619                                 if (val & (BMCR_RESET|BMCR_ANENABLE))
620                                         mii_if->force_media = 0;
621                                 else
622                                         mii_if->force_media = 1;
623                                 if (mii_if->force_media &&
624                                     (val & BMCR_FULLDPLX))
625                                         new_duplex = 1;
626                                 if (mii_if->full_duplex != new_duplex) {
627                                         duplex_changed = 1;
628                                         mii_if->full_duplex = new_duplex;
629                                 }
630                                 break;
631                         }
632                         case MII_ADVERTISE:
633                                 mii_if->advertising = val;
634                                 break;
635                         default:
636                                 /* do nothing */
637                                 break;
638                         }
639                 }
640
641                 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
642                                    mii_data->reg_num, val);
643                 break;
644         }
645
646         default:
647                 rc = -EOPNOTSUPP;
648                 break;
649         }
650
651         if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
652                 *duplex_chg_out = 1;
653
654         return rc;
655 }
656
657 MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
658 MODULE_DESCRIPTION ("MII hardware support library");
659 MODULE_LICENSE("GPL");
660
661 EXPORT_SYMBOL(mii_link_ok);
662 EXPORT_SYMBOL(mii_nway_restart);
663 EXPORT_SYMBOL(mii_ethtool_gset);
664 EXPORT_SYMBOL(mii_ethtool_get_link_ksettings);
665 EXPORT_SYMBOL(mii_ethtool_sset);
666 EXPORT_SYMBOL(mii_ethtool_set_link_ksettings);
667 EXPORT_SYMBOL(mii_check_link);
668 EXPORT_SYMBOL(mii_check_media);
669 EXPORT_SYMBOL(mii_check_gmii_support);
670 EXPORT_SYMBOL(generic_mii_ioctl);
671