xref: /openbmc/linux/drivers/net/mii.c (revision 7fe2f639)
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/mdio.h>
35 
36 static u32 mii_get_an(struct mii_if_info *mii, u16 addr)
37 {
38 	u32 result = 0;
39 	int advert;
40 
41 	advert = mii->mdio_read(mii->dev, mii->phy_id, addr);
42 	if (advert & LPA_LPACK)
43 		result |= ADVERTISED_Autoneg;
44 	if (advert & ADVERTISE_10HALF)
45 		result |= ADVERTISED_10baseT_Half;
46 	if (advert & ADVERTISE_10FULL)
47 		result |= ADVERTISED_10baseT_Full;
48 	if (advert & ADVERTISE_100HALF)
49 		result |= ADVERTISED_100baseT_Half;
50 	if (advert & ADVERTISE_100FULL)
51 		result |= ADVERTISED_100baseT_Full;
52 	if (advert & ADVERTISE_PAUSE_CAP)
53 		result |= ADVERTISED_Pause;
54 	if (advert & ADVERTISE_PAUSE_ASYM)
55 		result |= ADVERTISED_Asym_Pause;
56 
57 	return result;
58 }
59 
60 /**
61  * mii_ethtool_gset - get settings that are specified in @ecmd
62  * @mii: MII interface
63  * @ecmd: requested ethtool_cmd
64  *
65  * The @ecmd parameter is expected to have been cleared before calling
66  * mii_ethtool_gset().
67  *
68  * Returns 0 for success, negative on error.
69  */
70 int mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
71 {
72 	struct net_device *dev = mii->dev;
73 	u16 bmcr, bmsr, ctrl1000 = 0, stat1000 = 0;
74 	u32 nego;
75 
76 	ecmd->supported =
77 	    (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
78 	     SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
79 	     SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
80 	if (mii->supports_gmii)
81 		ecmd->supported |= SUPPORTED_1000baseT_Half |
82 			SUPPORTED_1000baseT_Full;
83 
84 	/* only supports twisted-pair */
85 	ecmd->port = PORT_MII;
86 
87 	/* only supports internal transceiver */
88 	ecmd->transceiver = XCVR_INTERNAL;
89 
90 	/* this isn't fully supported at higher layers */
91 	ecmd->phy_address = mii->phy_id;
92 	ecmd->mdio_support = MDIO_SUPPORTS_C22;
93 
94 	ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII;
95 
96 	bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
97 	bmsr = mii->mdio_read(dev, mii->phy_id, MII_BMSR);
98 	if (mii->supports_gmii) {
99  		ctrl1000 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
100 		stat1000 = mii->mdio_read(dev, mii->phy_id, MII_STAT1000);
101 	}
102 	if (bmcr & BMCR_ANENABLE) {
103 		ecmd->advertising |= ADVERTISED_Autoneg;
104 		ecmd->autoneg = AUTONEG_ENABLE;
105 
106 		ecmd->advertising |= mii_get_an(mii, MII_ADVERTISE);
107 		if (ctrl1000 & ADVERTISE_1000HALF)
108 			ecmd->advertising |= ADVERTISED_1000baseT_Half;
109 		if (ctrl1000 & ADVERTISE_1000FULL)
110 			ecmd->advertising |= ADVERTISED_1000baseT_Full;
111 
112 		if (bmsr & BMSR_ANEGCOMPLETE) {
113 			ecmd->lp_advertising = mii_get_an(mii, MII_LPA);
114 			if (stat1000 & LPA_1000HALF)
115 				ecmd->lp_advertising |=
116 					ADVERTISED_1000baseT_Half;
117 			if (stat1000 & LPA_1000FULL)
118 				ecmd->lp_advertising |=
119 					ADVERTISED_1000baseT_Full;
120 		} else {
121 			ecmd->lp_advertising = 0;
122 		}
123 
124 		nego = ecmd->advertising & ecmd->lp_advertising;
125 
126 		if (nego & (ADVERTISED_1000baseT_Full |
127 			    ADVERTISED_1000baseT_Half)) {
128 			ethtool_cmd_speed_set(ecmd, SPEED_1000);
129 			ecmd->duplex = !!(nego & ADVERTISED_1000baseT_Full);
130 		} else if (nego & (ADVERTISED_100baseT_Full |
131 				   ADVERTISED_100baseT_Half)) {
132 			ethtool_cmd_speed_set(ecmd, SPEED_100);
133 			ecmd->duplex = !!(nego & ADVERTISED_100baseT_Full);
134 		} else {
135 			ethtool_cmd_speed_set(ecmd, SPEED_10);
136 			ecmd->duplex = !!(nego & ADVERTISED_10baseT_Full);
137 		}
138 	} else {
139 		ecmd->autoneg = AUTONEG_DISABLE;
140 
141 		ethtool_cmd_speed_set(ecmd,
142 				      ((bmcr & BMCR_SPEED1000 &&
143 					(bmcr & BMCR_SPEED100) == 0) ?
144 				       SPEED_1000 :
145 				       ((bmcr & BMCR_SPEED100) ?
146 					SPEED_100 : SPEED_10)));
147 		ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
148 	}
149 
150 	mii->full_duplex = ecmd->duplex;
151 
152 	/* ignore maxtxpkt, maxrxpkt for now */
153 
154 	return 0;
155 }
156 
157 /**
158  * mii_ethtool_sset - set settings that are specified in @ecmd
159  * @mii: MII interface
160  * @ecmd: requested ethtool_cmd
161  *
162  * Returns 0 for success, negative on error.
163  */
164 int mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd)
165 {
166 	struct net_device *dev = mii->dev;
167 	u32 speed = ethtool_cmd_speed(ecmd);
168 
169 	if (speed != SPEED_10 &&
170 	    speed != SPEED_100 &&
171 	    speed != SPEED_1000)
172 		return -EINVAL;
173 	if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
174 		return -EINVAL;
175 	if (ecmd->port != PORT_MII)
176 		return -EINVAL;
177 	if (ecmd->transceiver != XCVR_INTERNAL)
178 		return -EINVAL;
179 	if (ecmd->phy_address != mii->phy_id)
180 		return -EINVAL;
181 	if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE)
182 		return -EINVAL;
183 	if ((speed == SPEED_1000) && (!mii->supports_gmii))
184 		return -EINVAL;
185 
186 	/* ignore supported, maxtxpkt, maxrxpkt */
187 
188 	if (ecmd->autoneg == AUTONEG_ENABLE) {
189 		u32 bmcr, advert, tmp;
190 		u32 advert2 = 0, tmp2 = 0;
191 
192 		if ((ecmd->advertising & (ADVERTISED_10baseT_Half |
193 					  ADVERTISED_10baseT_Full |
194 					  ADVERTISED_100baseT_Half |
195 					  ADVERTISED_100baseT_Full |
196 					  ADVERTISED_1000baseT_Half |
197 					  ADVERTISED_1000baseT_Full)) == 0)
198 			return -EINVAL;
199 
200 		/* advertise only what has been requested */
201 		advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE);
202 		tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
203 		if (mii->supports_gmii) {
204 			advert2 = mii->mdio_read(dev, mii->phy_id, MII_CTRL1000);
205 			tmp2 = advert2 & ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
206 		}
207 		if (ecmd->advertising & ADVERTISED_10baseT_Half)
208 			tmp |= ADVERTISE_10HALF;
209 		if (ecmd->advertising & ADVERTISED_10baseT_Full)
210 			tmp |= ADVERTISE_10FULL;
211 		if (ecmd->advertising & ADVERTISED_100baseT_Half)
212 			tmp |= ADVERTISE_100HALF;
213 		if (ecmd->advertising & ADVERTISED_100baseT_Full)
214 			tmp |= ADVERTISE_100FULL;
215 		if (mii->supports_gmii) {
216 			if (ecmd->advertising & ADVERTISED_1000baseT_Half)
217 				tmp2 |= ADVERTISE_1000HALF;
218 			if (ecmd->advertising & ADVERTISED_1000baseT_Full)
219 				tmp2 |= ADVERTISE_1000FULL;
220 		}
221 		if (advert != tmp) {
222 			mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp);
223 			mii->advertising = tmp;
224 		}
225 		if ((mii->supports_gmii) && (advert2 != tmp2))
226 			mii->mdio_write(dev, mii->phy_id, MII_CTRL1000, tmp2);
227 
228 		/* turn on autonegotiation, and force a renegotiate */
229 		bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
230 		bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
231 		mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr);
232 
233 		mii->force_media = 0;
234 	} else {
235 		u32 bmcr, tmp;
236 
237 		/* turn off auto negotiation, set speed and duplexity */
238 		bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR);
239 		tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 |
240 			       BMCR_SPEED1000 | BMCR_FULLDPLX);
241 		if (speed == SPEED_1000)
242 			tmp |= BMCR_SPEED1000;
243 		else if (speed == SPEED_100)
244 			tmp |= BMCR_SPEED100;
245 		if (ecmd->duplex == DUPLEX_FULL) {
246 			tmp |= BMCR_FULLDPLX;
247 			mii->full_duplex = 1;
248 		} else
249 			mii->full_duplex = 0;
250 		if (bmcr != tmp)
251 			mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp);
252 
253 		mii->force_media = 1;
254 	}
255 	return 0;
256 }
257 
258 /**
259  * mii_check_gmii_support - check if the MII supports Gb interfaces
260  * @mii: the MII interface
261  */
262 int mii_check_gmii_support(struct mii_if_info *mii)
263 {
264 	int reg;
265 
266 	reg = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
267 	if (reg & BMSR_ESTATEN) {
268 		reg = mii->mdio_read(mii->dev, mii->phy_id, MII_ESTATUS);
269 		if (reg & (ESTATUS_1000_TFULL | ESTATUS_1000_THALF))
270 			return 1;
271 	}
272 
273 	return 0;
274 }
275 
276 /**
277  * mii_link_ok - is link status up/ok
278  * @mii: the MII interface
279  *
280  * Returns 1 if the MII reports link status up/ok, 0 otherwise.
281  */
282 int mii_link_ok (struct mii_if_info *mii)
283 {
284 	/* first, a dummy read, needed to latch some MII phys */
285 	mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
286 	if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
287 		return 1;
288 	return 0;
289 }
290 
291 /**
292  * mii_nway_restart - restart NWay (autonegotiation) for this interface
293  * @mii: the MII interface
294  *
295  * Returns 0 on success, negative on error.
296  */
297 int mii_nway_restart (struct mii_if_info *mii)
298 {
299 	int bmcr;
300 	int r = -EINVAL;
301 
302 	/* if autoneg is off, it's an error */
303 	bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR);
304 
305 	if (bmcr & BMCR_ANENABLE) {
306 		bmcr |= BMCR_ANRESTART;
307 		mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr);
308 		r = 0;
309 	}
310 
311 	return r;
312 }
313 
314 /**
315  * mii_check_link - check MII link status
316  * @mii: MII interface
317  *
318  * If the link status changed (previous != current), call
319  * netif_carrier_on() if current link status is Up or call
320  * netif_carrier_off() if current link status is Down.
321  */
322 void mii_check_link (struct mii_if_info *mii)
323 {
324 	int cur_link = mii_link_ok(mii);
325 	int prev_link = netif_carrier_ok(mii->dev);
326 
327 	if (cur_link && !prev_link)
328 		netif_carrier_on(mii->dev);
329 	else if (prev_link && !cur_link)
330 		netif_carrier_off(mii->dev);
331 }
332 
333 /**
334  * mii_check_media - check the MII interface for a duplex change
335  * @mii: the MII interface
336  * @ok_to_print: OK to print link up/down messages
337  * @init_media: OK to save duplex mode in @mii
338  *
339  * Returns 1 if the duplex mode changed, 0 if not.
340  * If the media type is forced, always returns 0.
341  */
342 unsigned int mii_check_media (struct mii_if_info *mii,
343 			      unsigned int ok_to_print,
344 			      unsigned int init_media)
345 {
346 	unsigned int old_carrier, new_carrier;
347 	int advertise, lpa, media, duplex;
348 	int lpa2 = 0;
349 
350 	/* if forced media, go no further */
351 	if (mii->force_media)
352 		return 0; /* duplex did not change */
353 
354 	/* check current and old link status */
355 	old_carrier = netif_carrier_ok(mii->dev) ? 1 : 0;
356 	new_carrier = (unsigned int) mii_link_ok(mii);
357 
358 	/* if carrier state did not change, this is a "bounce",
359 	 * just exit as everything is already set correctly
360 	 */
361 	if ((!init_media) && (old_carrier == new_carrier))
362 		return 0; /* duplex did not change */
363 
364 	/* no carrier, nothing much to do */
365 	if (!new_carrier) {
366 		netif_carrier_off(mii->dev);
367 		if (ok_to_print)
368 			netdev_info(mii->dev, "link down\n");
369 		return 0; /* duplex did not change */
370 	}
371 
372 	/*
373 	 * we have carrier, see who's on the other end
374 	 */
375 	netif_carrier_on(mii->dev);
376 
377 	/* get MII advertise and LPA values */
378 	if ((!init_media) && (mii->advertising))
379 		advertise = mii->advertising;
380 	else {
381 		advertise = mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
382 		mii->advertising = advertise;
383 	}
384 	lpa = mii->mdio_read(mii->dev, mii->phy_id, MII_LPA);
385 	if (mii->supports_gmii)
386 		lpa2 = mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);
387 
388 	/* figure out media and duplex from advertise and LPA values */
389 	media = mii_nway_result(lpa & advertise);
390 	duplex = (media & ADVERTISE_FULL) ? 1 : 0;
391 	if (lpa2 & LPA_1000FULL)
392 		duplex = 1;
393 
394 	if (ok_to_print)
395 		netdev_info(mii->dev, "link up, %uMbps, %s-duplex, lpa 0x%04X\n",
396 			    lpa2 & (LPA_1000FULL | LPA_1000HALF) ? 1000 :
397 			    media & (ADVERTISE_100FULL | ADVERTISE_100HALF) ?
398 			    100 : 10,
399 			    duplex ? "full" : "half",
400 			    lpa);
401 
402 	if ((init_media) || (mii->full_duplex != duplex)) {
403 		mii->full_duplex = duplex;
404 		return 1; /* duplex changed */
405 	}
406 
407 	return 0; /* duplex did not change */
408 }
409 
410 /**
411  * generic_mii_ioctl - main MII ioctl interface
412  * @mii_if: the MII interface
413  * @mii_data: MII ioctl data structure
414  * @cmd: MII ioctl command
415  * @duplex_chg_out: pointer to @duplex_changed status if there was no
416  *	ioctl error
417  *
418  * Returns 0 on success, negative on error.
419  */
420 int generic_mii_ioctl(struct mii_if_info *mii_if,
421 		      struct mii_ioctl_data *mii_data, int cmd,
422 		      unsigned int *duplex_chg_out)
423 {
424 	int rc = 0;
425 	unsigned int duplex_changed = 0;
426 
427 	if (duplex_chg_out)
428 		*duplex_chg_out = 0;
429 
430 	mii_data->phy_id &= mii_if->phy_id_mask;
431 	mii_data->reg_num &= mii_if->reg_num_mask;
432 
433 	switch(cmd) {
434 	case SIOCGMIIPHY:
435 		mii_data->phy_id = mii_if->phy_id;
436 		/* fall through */
437 
438 	case SIOCGMIIREG:
439 		mii_data->val_out =
440 			mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
441 					  mii_data->reg_num);
442 		break;
443 
444 	case SIOCSMIIREG: {
445 		u16 val = mii_data->val_in;
446 
447 		if (mii_data->phy_id == mii_if->phy_id) {
448 			switch(mii_data->reg_num) {
449 			case MII_BMCR: {
450 				unsigned int new_duplex = 0;
451 				if (val & (BMCR_RESET|BMCR_ANENABLE))
452 					mii_if->force_media = 0;
453 				else
454 					mii_if->force_media = 1;
455 				if (mii_if->force_media &&
456 				    (val & BMCR_FULLDPLX))
457 					new_duplex = 1;
458 				if (mii_if->full_duplex != new_duplex) {
459 					duplex_changed = 1;
460 					mii_if->full_duplex = new_duplex;
461 				}
462 				break;
463 			}
464 			case MII_ADVERTISE:
465 				mii_if->advertising = val;
466 				break;
467 			default:
468 				/* do nothing */
469 				break;
470 			}
471 		}
472 
473 		mii_if->mdio_write(mii_if->dev, mii_data->phy_id,
474 				   mii_data->reg_num, val);
475 		break;
476 	}
477 
478 	default:
479 		rc = -EOPNOTSUPP;
480 		break;
481 	}
482 
483 	if ((rc == 0) && (duplex_chg_out) && (duplex_changed))
484 		*duplex_chg_out = 1;
485 
486 	return rc;
487 }
488 
489 MODULE_AUTHOR ("Jeff Garzik <jgarzik@pobox.com>");
490 MODULE_DESCRIPTION ("MII hardware support library");
491 MODULE_LICENSE("GPL");
492 
493 EXPORT_SYMBOL(mii_link_ok);
494 EXPORT_SYMBOL(mii_nway_restart);
495 EXPORT_SYMBOL(mii_ethtool_gset);
496 EXPORT_SYMBOL(mii_ethtool_sset);
497 EXPORT_SYMBOL(mii_check_link);
498 EXPORT_SYMBOL(mii_check_media);
499 EXPORT_SYMBOL(mii_check_gmii_support);
500 EXPORT_SYMBOL(generic_mii_ioctl);
501 
502