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