diff options
Diffstat (limited to 'drivers/net/mii.c')
-rw-r--r-- | drivers/net/mii.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/drivers/net/mii.c b/drivers/net/mii.c new file mode 100644 index 000000000000..c33cb3dc942b --- /dev/null +++ b/drivers/net/mii.c | |||
@@ -0,0 +1,398 @@ | |||
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_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 | |||
219 | int 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 | |||
236 | void 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 | |||
247 | unsigned 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 | |||
315 | int 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 | |||
387 | MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>"); | ||
388 | MODULE_DESCRIPTION ("MII hardware support library"); | ||
389 | MODULE_LICENSE("GPL"); | ||
390 | |||
391 | EXPORT_SYMBOL(mii_link_ok); | ||
392 | EXPORT_SYMBOL(mii_nway_restart); | ||
393 | EXPORT_SYMBOL(mii_ethtool_gset); | ||
394 | EXPORT_SYMBOL(mii_ethtool_sset); | ||
395 | EXPORT_SYMBOL(mii_check_link); | ||
396 | EXPORT_SYMBOL(mii_check_media); | ||
397 | EXPORT_SYMBOL(generic_mii_ioctl); | ||
398 | |||