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
mii_ethtool_gset(struct mii_if_info * mii,struct ethtool_cmd * ecmd)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
mii_ethtool_sset(struct mii_if_info * mii,struct ethtool_cmd * ecmd)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
mii_link_ok(struct mii_if_info * mii)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
mii_nway_restart(struct mii_if_info * mii)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
mii_check_link(struct mii_if_info * mii)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
mii_check_media(struct mii_if_info * mii,unsigned int ok_to_print,unsigned int init_media)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
generic_mii_ioctl(struct mii_if_info * mii_if,struct mii_ioctl_data * mii_data,int cmd,unsigned int * duplex_chg_out)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
354