blob: c33cb3dc942b713f993f7ae75f5bede82c5cacc9 [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
36int 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
117int 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
210int mii_link_ok (struct mii_if_info *mii)
211{
212 /* first, a dummy read, needed to latch some MII phys */
213 mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
214 if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
215 return 1;
216 return 0;
217}
218
219int mii_nway_restart (struct mii_if_info *mii)
220{
221 int bmcr;
222 int r = -EINVAL;
223
224 /* if autoneg is off, it's an error */
225 bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
226
227 if (bmcr & BMCR_ANENABLE) {
228 bmcr |= BMCR_ANRESTART;
229 mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
230 r = 0;
231 }
232
233 return r;
234}
235
236void mii_check_link (struct mii_if_info *mii)
237{
238 int cur_link = mii_link_ok(mii);
239 int prev_link = netif_carrier_ok(mii->dev);
240
241 if (cur_link && !prev_link)
242 netif_carrier_on(mii->dev);
243 else if (prev_link && !cur_link)
244 netif_carrier_off(mii->dev);
245}
246
247unsigned int mii_check_media (struct mii_if_info *mii,
248 unsigned int ok_to_print,
249 unsigned int init_media)
250{
251 unsigned int old_carrier, new_carrier;
252 int advertise, lpa, media, duplex;
253 int lpa2 = 0;
254
255 /* if forced media, go no further */
256 if (mii->force_media)
257 return 0; /* duplex did not change */
258
259 /* check current and old link status */
260 old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
261 new_carrier = (unsigned int) mii_link_ok(mii);
262
263 /* if carrier state did not change, this is a "bounce",
264 * just exit as everything is already set correctly
265 */
266 if ((!init_media) && (old_carrier == new_carrier))
267 return 0; /* duplex did not change */
268
269 /* no carrier, nothing much to do */
270 if (!new_carrier) {
271 netif_carrier_off(mii->dev);
272 if (ok_to_print)
273 printk(KERN_INFO "%s: link down\n", mii->dev->name);
274 return 0; /* duplex did not change */
275 }
276
277 /*
278 * we have carrier, see who's on the other end
279 */
280 netif_carrier_on(mii->dev);
281
282 /* get MII advertise and LPA values */
283 if ((!init_media) && (mii->advertising))
284 advertise = mii->advertising;
285 else {
286 advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
287 mii->advertising = advertise;
288 }
289 lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
290 if (mii->supports_gmii)
291 lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
292
293 /* figure out media and duplex from advertise and LPA values */
294 media = mii_nway_result(lpa & advertise);
295 duplex = (media & ADVERTISE_FULL) ? 1 : 0;
296 if (lpa2 & LPA_1000FULL)
297 duplex = 1;
298
299 if (ok_to_print)
300 printk(KERN_INFO "%s: link up, %sMbps, %s-duplex, lpa 0x%04X\n",
301 mii->dev->name,
302 lpa2 & (LPA_1000FULL | LPA_1000HALF) ? "1000" :
303 media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ? "100" : "10",
304 duplex ? "full" : "half",
305 lpa);
306
307 if ((init_media) || (mii->full_duplex != duplex)) {
308 mii->full_duplex = duplex;
309 return 1; /* duplex changed */
310 }
311
312 return 0; /* duplex did not change */
313}
314
315int generic_mii_ioctl(struct mii_if_info *mii_if,
316 struct mii_ioctl_data *mii_data, int cmd,
317 unsigned int *duplex_chg_out)
318{
319 int rc = 0;
320 unsigned int duplex_changed = 0;
321
322 if (duplex_chg_out)
323 *duplex_chg_out = 0;
324
325 mii_data->phy_id &= mii_if->phy_id_mask;
326 mii_data->reg_num &= mii_if->reg_num_mask;
327
328 switch(cmd) {
329 case SIOCGMIIPHY:
330 mii_data->phy_id = mii_if->phy_id;
331 /* fall through */
332
333 case SIOCGMIIREG:
334 mii_data->val_out =
335 mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
336 mii_data->reg_num);
337 break;
338
339 case SIOCSMIIREG: {
340 u16 val = mii_data->val_in;
341
342 if (!capable(CAP_NET_ADMIN))
343 return -EPERM;
344
345 if (mii_data->phy_id == mii_if->phy_id) {
346 switch(mii_data->reg_num) {
347 case MII_BMCR: {
348 unsigned int new_duplex = 0;
349 if (val & (BMCR_RESET|BMCR_ANENABLE))
350 mii_if->force_media = 0;
351 else
352 mii_if->force_media = 1;
353 if (mii_if->force_media &&
354 (val & BMCR_FULLDPLX))
355 new_duplex = 1;
356 if (mii_if->full_duplex != new_duplex) {
357 duplex_changed = 1;
358 mii_if->full_duplex = new_duplex;
359 }
360 break;
361 }
362 case MII_ADVERTISE:
363 mii_if->advertising = val;
364 break;
365 default:
366 /* do nothing */
367 break;
368 }
369 }
370
371 mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
372 mii_data->reg_num, val);
373 break;
374 }
375
376 default:
377 rc = -EOPNOTSUPP;
378 break;
379 }
380
381 if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
382 *duplex_chg_out = 1;
383
384 return rc;
385}
386
387MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
388MODULE_DESCRIPTION ("MII hardware support library");
389MODULE_LICENSE("GPL");
390
391EXPORT_SYMBOL(mii_link_ok);
392EXPORT_SYMBOL(mii_nway_restart);
393EXPORT_SYMBOL(mii_ethtool_gset);
394EXPORT_SYMBOL(mii_ethtool_sset);
395EXPORT_SYMBOL(mii_check_link);
396EXPORT_SYMBOL(mii_check_media);
397EXPORT_SYMBOL(generic_mii_ioctl);
398