1/*
2
3	mii.c: MII interface library
4
5	Maintained by Jeff Garzik <jgarzik@mandrakesoft.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
36int 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
96int 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 (ADVERTISED_10baseT_Half)
128			tmp |= ADVERTISE_10HALF;
129		if (ADVERTISED_10baseT_Full)
130			tmp |= ADVERTISE_10FULL;
131		if (ADVERTISED_100baseT_Half)
132			tmp |= ADVERTISE_100HALF;
133		if (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
167int 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
176int 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
193void 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
204unsigned 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
267int 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
342MODULE_AUTHOR ("Jeff Garzik <jgarzik@mandrakesoft.com>");
343MODULE_DESCRIPTION ("MII hardware support library");
344MODULE_LICENSE("GPL");
345
346EXPORT_SYMBOL(mii_link_ok);
347EXPORT_SYMBOL(mii_nway_restart);
348EXPORT_SYMBOL(mii_ethtool_gset);
349EXPORT_SYMBOL(mii_ethtool_sset);
350EXPORT_SYMBOL(mii_check_link);
351EXPORT_SYMBOL(mii_check_media);
352EXPORT_SYMBOL(generic_mii_ioctl);
353
354