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