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