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