blob: 2912a34f597bd9316a710eec336ef6c6680af23f [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;
Jeff Garzik6aa20a22006-09-13 13:24:59 -040086
Linus Torvalds1da177e2005-04-16 15:20:36 -070087 nego = mii_nway_result(advert & lpa);
Jeff Garzik6aa20a22006-09-13 13:24:59 -040088 if ((bmcr2 & (ADVERTISE_1000HALF | ADVERTISE_1000FULL)) &
Linus Torvalds1da177e2005-04-16 15:20:36 -070089 (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
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400106 ecmd->speed = ((bmcr & BMCR_SPEED1000 &&
Linus Torvalds1da177e2005-04-16 15:20:36 -0700107 (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
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400121 if (ecmd->speed != SPEED_10 &&
122 ecmd->speed != SPEED_100 &&
Linus Torvalds1da177e2005-04-16 15:20:36 -0700123 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;
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400137
Linus Torvalds1da177e2005-04-16 15:20:36 -0700138 /* ignore supported, maxtxpkt, maxrxpkt */
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400139
Linus Torvalds1da177e2005-04-16 15:20:36 -0700140 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);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400179
Linus Torvalds1da177e2005-04-16 15:20:36 -0700180 /* 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);
Jeff Garzik6aa20a22006-09-13 13:24:59 -0400191 tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
Linus Torvalds1da177e2005-04-16 15:20:36 -0700192 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
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700210int 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
Linus Torvalds1da177e2005-04-16 15:20:36 -0700224int 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
233int 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
250void 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
261unsigned 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
329int 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
401MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
402MODULE_DESCRIPTION ("MII hardware support library");
403MODULE_LICENSE("GPL");
404
405EXPORT_SYMBOL(mii_link_ok);
406EXPORT_SYMBOL(mii_nway_restart);
407EXPORT_SYMBOL(mii_ethtool_gset);
408EXPORT_SYMBOL(mii_ethtool_sset);
409EXPORT_SYMBOL(mii_check_link);
410EXPORT_SYMBOL(mii_check_media);
Dale Farnsworth43ec6e92005-08-23 10:30:29 -0700411EXPORT_SYMBOL(mii_check_gmii_support);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700412EXPORT_SYMBOL(generic_mii_ioctl);
413