setup enviroment for compilation
[linux-2.4.21-pre4.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 int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
37 {
38         struct net_device *dev = mii->dev;
39         u32 advert, bmcr, lpa, nego;
40
41         ecmd->supported =
42             (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
43              SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
44              SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
45
46         /* only supports twisted-pair */
47         ecmd->port = PORT_MII;
48
49         /* only supports internal transceiver */
50         ecmd->transceiver = XCVR_INTERNAL;
51
52         /* this isn't fully supported at higher layers */
53         ecmd->phy_address = mii->phy_id;
54
55         ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
56         advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
57         if (advert & ADVERTISE_10HALF)
58                 ecmd->advertising |= ADVERTISED_10baseT_Half;
59         if (advert & ADVERTISE_10FULL)
60                 ecmd->advertising |= ADVERTISED_10baseT_Full;
61         if (advert & ADVERTISE_100HALF)
62                 ecmd->advertising |= ADVERTISED_100baseT_Half;
63         if (advert & ADVERTISE_100FULL)
64                 ecmd->advertising |= ADVERTISED_100baseT_Full;
65
66         bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
67         lpa = mii->mdio_read(dev, mii->phy_id, MII_LPA);
68         if (bmcr & BMCR_ANENABLE) {
69                 ecmd->advertising |= ADVERTISED_Autoneg;
70                 ecmd->autoneg = AUTONEG_ENABLE;
71                 
72                 nego = mii_nway_result(advert & lpa);
73                 if (nego == LPA_100FULL || nego == LPA_100HALF)
74                         ecmd->speed = SPEED_100;
75                 else
76                         ecmd->speed = SPEED_10;
77                 if (nego == LPA_100FULL || nego == LPA_10FULL) {
78                         ecmd->duplex = DUPLEX_FULL;
79                         mii->full_duplex = 1;
80                 } else {
81                         ecmd->duplex = DUPLEX_HALF;
82                         mii->full_duplex = 0;
83                 }
84         } else {
85                 ecmd->autoneg = AUTONEG_DISABLE;
86
87                 ecmd->speed = (bmcr & BMCR_SPEED100) ? SPEED_100 : SPEED_10;
88                 ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
89         }
90
91         /* ignore maxtxpkt, maxrxpkt for now */
92
93         return 0;
94 }
95
96 int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
97 {
98         struct net_device *dev = mii->dev;
99
100         if (ecmd->speed != SPEED_10 && ecmd->speed != SPEED_100)
101                 return -EINVAL;
102         if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
103                 return -EINVAL;
104         if (ecmd->port != PORT_MII)
105                 return -EINVAL;
106         if (ecmd->transceiver != XCVR_INTERNAL)
107                 return -EINVAL;
108         if (ecmd->phy_address != mii->phy_id)
109                 return -EINVAL;
110         if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
111                 return -EINVAL;
112                                   
113         /* ignore supported, maxtxpkt, maxrxpkt */
114         
115         if (ecmd->autoneg == AUTONEG_ENABLE) {
116                 u32 bmcr, advert, tmp;
117
118                 if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
119                                           ADVERTISED_10baseT_Full |
120                                           ADVERTISED_100baseT_Half |
121                                           ADVERTISED_100baseT_Full)) == 0)
122                         return -EINVAL;
123
124                 /* advertise only what has been requested */
125                 advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
126                 tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
127                 if (ecmd->advertising & ADVERTISED_10baseT_Half)
128                         tmp |= ADVERTISE_10HALF;
129                 if (ecmd->advertising & ADVERTISED_10baseT_Full)
130                         tmp |= ADVERTISE_10FULL;
131                 if (ecmd->advertising & ADVERTISED_100baseT_Half)
132                         tmp |= ADVERTISE_100HALF;
133                 if (ecmd->advertising & ADVERTISED_100baseT_Full)
134                         tmp |= ADVERTISE_100FULL;
135                 if (advert != tmp) {
136                         mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
137                         mii->advertising = tmp;
138                 }
139                 
140                 /* turn on autonegotiation, and force a renegotiate */
141                 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
142                 bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
143                 mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
144
145                 mii->force_media = 0;
146         } else {
147                 u32 bmcr, tmp;
148
149                 /* turn off auto negotiation, set speed and duplexity */
150                 bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
151                 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 | BMCR_FULLDPLX);
152                 if (ecmd->speed == SPEED_100)
153                         tmp |= BMCR_SPEED100;
154                 if (ecmd->duplex == DUPLEX_FULL) {
155                         tmp |= BMCR_FULLDPLX;
156                         mii->full_duplex = 1;
157                 } else
158                         mii->full_duplex = 0;
159                 if (bmcr != tmp)
160                         mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
161
162                 mii->force_media = 1;
163         }
164         return 0;
165 }
166
167 int mii_link_ok (struct mii_if_info *mii)
168 {
169         /* first, a dummy read, needed to latch some MII phys */
170         mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
171         if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
172                 return 1;
173         return 0;
174 }
175
176 int mii_nway_restart (struct mii_if_info *mii)
177 {
178         int bmcr;
179         int r = -EINVAL;
180
181         /* if autoneg is off, it's an error */
182         bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
183
184         if (bmcr & BMCR_ANENABLE) {
185                 bmcr |= BMCR_ANRESTART;
186                 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
187                 r = 0;
188         }
189
190         return r;
191 }
192
193 void mii_check_link (struct mii_if_info *mii)
194 {
195         int cur_link = mii_link_ok(mii);
196         int prev_link = netif_carrier_ok(mii->dev);
197
198         if (cur_link && !prev_link)
199                 netif_carrier_on(mii->dev);
200         else if (prev_link && !cur_link)
201                 netif_carrier_off(mii->dev);
202 }
203
204 unsigned int mii_check_media (struct mii_if_info *mii,
205                               unsigned int ok_to_print,
206                               unsigned int init_media)
207 {
208         unsigned int old_carrier, new_carrier;
209         int advertise, lpa, media, duplex;
210
211         /* if forced media, go no further */
212         if (mii->force_media)
213                 return 0; /* duplex did not change */
214
215         /* check current and old link status */
216         old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
217         new_carrier = (unsigned int) mii_link_ok(mii);
218
219         /* if carrier state did not change, this is a "bounce",
220          * just exit as everything is already set correctly
221          */
222         if ((!init_media) && (old_carrier == new_carrier))
223                 return 0; /* duplex did not change */
224
225         /* no carrier, nothing much to do */
226         if (!new_carrier) {
227                 netif_carrier_off(mii->dev);
228                 if (ok_to_print)
229                         printk(KERN_INFO "%s: link down\n", mii->dev->name);
230                 return 0; /* duplex did not change */
231         }
232
233         /*
234          * we have carrier, see who's on the other end
235          */
236         netif_carrier_on(mii->dev);
237
238         /* get MII advertise and LPA values */
239         if ((!init_media) && (mii->advertising))
240                 advertise = mii->advertising;
241         else {
242                 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
243                 mii->advertising = advertise;
244         }
245         lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
246
247         /* figure out media and duplex from advertise and LPA values */
248         media = mii_nway_result(lpa & advertise);
249         duplex = (media & ADVERTISE_FULL) ? 1 : 0;
250
251         if (ok_to_print)
252                 printk(KERN_INFO "%s: link up, %sMbps, %s-duplex, lpa 0x%04X\n",
253                        mii->dev->name,
254                        media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ?
255                                 "100" : "10",
256                        duplex ? "full" : "half",
257                        lpa);
258
259         if ((init_media) || (mii->full_duplex != duplex)) {
260                 mii->full_duplex = duplex;
261                 return 1; /* duplex changed */
262         }
263
264         return 0; /* duplex did not change */
265 }
266
267 int generic_mii_ioctl(struct mii_if_info *mii_if,
268                       struct mii_ioctl_data *mii_data, int cmd,
269                       unsigned int *duplex_chg_out)
270 {
271         int rc = 0;
272         unsigned int duplex_changed = 0;
273
274         if (duplex_chg_out)
275                 *duplex_chg_out = 0;
276
277         mii_data->phy_id &= mii_if->phy_id_mask;
278         mii_data->reg_num &= mii_if->reg_num_mask;
279
280         switch(cmd) {
281         case SIOCDEVPRIVATE:    /* binary compat, remove in 2.5 */
282         case SIOCGMIIPHY:
283                 mii_data->phy_id = mii_if->phy_id;
284                 /* fall through */
285
286         case SIOCDEVPRIVATE + 1:/* binary compat, remove in 2.5 */
287         case SIOCGMIIREG:
288                 mii_data->val_out =
289                         mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
290                                           mii_data->reg_num);
291                 break;
292
293         case SIOCDEVPRIVATE + 2:/* binary compat, remove in 2.5 */
294         case SIOCSMIIREG: {
295                 u16 val = mii_data->val_in;
296
297                 if (!capable(CAP_NET_ADMIN))
298                         return -EPERM;
299
300                 if (mii_data->phy_id == mii_if->phy_id) {
301                         switch(mii_data->reg_num) {
302                         case MII_BMCR: {
303                                 unsigned int new_duplex = 0;
304                                 if (val & (BMCR_RESET|BMCR_ANENABLE))
305                                         mii_if->force_media = 0;
306                                 else
307                                         mii_if->force_media = 1;
308                                 if (mii_if->force_media &&
309                                     (val & BMCR_FULLDPLX))
310                                         new_duplex = 1;
311                                 if (mii_if->full_duplex != new_duplex) {
312                                         duplex_changed = 1;
313                                         mii_if->full_duplex = new_duplex;
314                                 }
315                                 break;
316                         }
317                         case MII_ADVERTISE:
318                                 mii_if->advertising = val;
319                                 break;
320                         default:
321                                 /* do nothing */
322                                 break;
323                         }
324                 }
325
326                 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
327                                    mii_data->reg_num, val);
328                 break;
329         }
330
331         default:
332                 rc = -EOPNOTSUPP;
333                 break;
334         }
335
336         if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
337                 *duplex_chg_out = 1;
338
339         return rc;
340 }
341
342 MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
343 MODULE_DESCRIPTION ("MII hardware support library");
344 MODULE_LICENSE("GPL");
345
346 EXPORT_SYMBOL(mii_link_ok);
347 EXPORT_SYMBOL(mii_nway_restart);
348 EXPORT_SYMBOL(mii_ethtool_gset);
349 EXPORT_SYMBOL(mii_ethtool_sset);
350 EXPORT_SYMBOL(mii_check_link);
351 EXPORT_SYMBOL(mii_check_media);
352 EXPORT_SYMBOL(generic_mii_ioctl);
353