mii_physubr.c revision 95722
1/*	$NetBSD: mii_physubr.c,v 1.5 1999/08/03 19:41:49 drochner Exp $	*/
2
3/*-
4 * Copyright (c) 1998, 1999, 2000, 2001 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
9 * NASA Ames Research Center.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 *    must display the following acknowledgement:
21 *	This product includes software developed by the NetBSD
22 *	Foundation, Inc. and its contributors.
23 * 4. Neither the name of The NetBSD Foundation nor the names of its
24 *    contributors may be used to endorse or promote products derived
25 *    from this software without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40/*
41 * Subroutines common to all PHYs.
42 */
43
44#include <sys/param.h>
45#include <sys/systm.h>
46#include <sys/kernel.h>
47#include <sys/socket.h>
48#include <sys/errno.h>
49#include <sys/module.h>
50#include <sys/bus.h>
51
52
53#include <net/if.h>
54#include <net/if_media.h>
55
56#include <dev/mii/mii.h>
57#include <dev/mii/miivar.h>
58
59#include "miibus_if.h"
60
61#if !defined(lint)
62static const char rcsid[] =
63  "$FreeBSD: head/sys/dev/mii/mii_physubr.c 95722 2002-04-29 13:07:38Z phk $";
64#endif
65
66/*
67 * Media to register setting conversion table.  Order matters.
68 */
69const struct mii_media mii_media_table[MII_NMEDIA] = {
70	/* None */
71	{ BMCR_ISO,		ANAR_CSMA,
72	  0, },
73
74	/* 10baseT */
75	{ BMCR_S10,		ANAR_CSMA|ANAR_10,
76	  0, },
77
78	/* 10baseT-FDX */
79	{ BMCR_S10|BMCR_FDX,	ANAR_CSMA|ANAR_10_FD,
80	  0, },
81
82	/* 100baseT4 */
83	{ BMCR_S100,		ANAR_CSMA|ANAR_T4,
84	  0, },
85
86	/* 100baseTX */
87	{ BMCR_S100,		ANAR_CSMA|ANAR_TX,
88	  0, },
89
90	/* 100baseTX-FDX */
91	{ BMCR_S100|BMCR_FDX,	ANAR_CSMA|ANAR_TX_FD,
92	  0, },
93
94	/* 1000baseX */
95	{ BMCR_S1000,		ANAR_CSMA,
96	  0, },
97
98	/* 1000baseX-FDX */
99	{ BMCR_S1000|BMCR_FDX,	ANAR_CSMA,
100	  0, },
101
102	/* 1000baseT */
103	{ BMCR_S1000,		ANAR_CSMA,
104	  GTCR_ADV_1000THDX },
105
106	/* 1000baseT-FDX */
107	{ BMCR_S1000,		ANAR_CSMA,
108	  GTCR_ADV_1000TFDX },
109};
110
111void	mii_phy_auto_timeout(void *);
112
113void
114mii_phy_setmedia(struct mii_softc *sc)
115{
116	struct mii_data *mii = sc->mii_pdata;
117	struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
118	int bmcr, anar, gtcr;
119
120	if (IFM_SUBTYPE(ife->ifm_media) == IFM_AUTO) {
121		if ((PHY_READ(sc, MII_BMCR) & BMCR_AUTOEN) == 0)
122			(void) mii_phy_auto(sc, 1);
123		return;
124	}
125
126	/*
127	 * Table index is stored in the media entry.
128	 */
129
130	KASSERT(ife->ifm_data >=0 && ife->ifm_data < MII_NMEDIA,
131	    ("invalid ife->ifm_data (0x%x) in mii_phy_setmedia",
132	    ife->ifm_data));
133
134	anar = mii_media_table[ife->ifm_data].mm_anar;
135	bmcr = mii_media_table[ife->ifm_data].mm_bmcr;
136	gtcr = mii_media_table[ife->ifm_data].mm_gtcr;
137
138	if (mii->mii_media.ifm_media & IFM_ETH_MASTER) {
139		switch (IFM_SUBTYPE(ife->ifm_media)) {
140		case IFM_1000_T:
141			gtcr |= GTCR_MAN_MS|GTCR_ADV_MS;
142			break;
143
144		default:
145			panic("mii_phy_setmedia: MASTER on wrong media");
146		}
147	}
148
149	if (ife->ifm_media & IFM_LOOP)
150		bmcr |= BMCR_LOOP;
151
152	PHY_WRITE(sc, MII_ANAR, anar);
153	PHY_WRITE(sc, MII_BMCR, bmcr);
154	if (sc->mii_flags & MIIF_HAVE_GTCR)
155		PHY_WRITE(sc, MII_100T2CR, gtcr);
156}
157
158int
159mii_phy_auto(struct mii_softc *sc, int waitfor)
160{
161	int bmsr, i;
162
163	if ((sc->mii_flags & MIIF_DOINGAUTO) == 0) {
164		/*
165		 * Check for 1000BASE-X.  Autonegotiation is a bit
166		 * different on such devices.
167		 */
168		if (sc->mii_flags & MIIF_IS_1000X) {
169			uint16_t anar = 0;
170
171			if (sc->mii_extcapabilities & EXTSR_1000XFDX)
172				anar |= ANAR_X_FD;
173			if (sc->mii_extcapabilities & EXTSR_1000XHDX)
174				anar |= ANAR_X_HD;
175
176			if (sc->mii_flags & MIIF_DOPAUSE) {
177				/* XXX Asymmetric vs. symmetric? */
178				anar |= ANLPAR_X_PAUSE_TOWARDS;
179			}
180
181			PHY_WRITE(sc, MII_ANAR, anar);
182		} else {
183			uint16_t anar;
184
185			anar = BMSR_MEDIA_TO_ANAR(sc->mii_capabilities) |
186			    ANAR_CSMA;
187			if (sc->mii_flags & MIIF_DOPAUSE)
188				anar |= ANAR_FC;
189			PHY_WRITE(sc, MII_ANAR, anar);
190			if (sc->mii_flags & MIIF_HAVE_GTCR) {
191				uint16_t gtcr = 0;
192
193				if (sc->mii_extcapabilities & EXTSR_1000TFDX)
194					gtcr |= GTCR_ADV_1000TFDX;
195				if (sc->mii_extcapabilities & EXTSR_1000THDX)
196					gtcr |= GTCR_ADV_1000THDX;
197
198				PHY_WRITE(sc, MII_100T2CR, gtcr);
199			}
200		}
201		PHY_WRITE(sc, MII_BMCR, BMCR_AUTOEN | BMCR_STARTNEG);
202	}
203
204	if (waitfor) {
205		/* Wait 500ms for it to complete. */
206		for (i = 0; i < 500; i++) {
207			if ((bmsr = PHY_READ(sc, MII_BMSR)) & BMSR_ACOMP)
208				return (0);
209			DELAY(1000);
210		}
211
212		/*
213		 * Don't need to worry about clearing MIIF_DOINGAUTO.
214		 * If that's set, a timeout is pending, and it will
215		 * clear the flag.
216		 */
217		return (EIO);
218	}
219
220	/*
221	 * Just let it finish asynchronously.  This is for the benefit of
222	 * the tick handler driving autonegotiation.  Don't want 500ms
223	 * delays all the time while the system is running!
224	 */
225	if ((sc->mii_flags & MIIF_DOINGAUTO) == 0) {
226		sc->mii_flags |= MIIF_DOINGAUTO;
227		sc->mii_auto_ch = timeout(mii_phy_auto_timeout, sc, hz >> 1);
228	}
229	return (EJUSTRETURN);
230}
231
232void
233mii_phy_auto_timeout(void *arg)
234{
235	struct mii_softc *sc = arg;
236	int s, bmsr;
237
238#if 0
239	if ((sc->mii_dev.dv_flags & DVF_ACTIVE) == 0)
240		return;
241#endif
242
243	s = splnet();
244	sc->mii_flags &= ~MIIF_DOINGAUTO;
245	bmsr = PHY_READ(sc, MII_BMSR);
246
247	/* Update the media status. */
248	(void) (*sc->mii_service)(sc, sc->mii_pdata, MII_POLLSTAT);
249	splx(s);
250}
251
252int
253mii_phy_tick(struct mii_softc *sc)
254{
255	struct ifmedia_entry *ife = sc->mii_pdata->mii_media.ifm_cur;
256	struct ifnet *ifp = sc->mii_pdata->mii_ifp;
257	int reg;
258
259	/* Just bail now if the interface is down. */
260	if ((ifp->if_flags & IFF_UP) == 0)
261		return (EJUSTRETURN);
262
263	/*
264	 * If we're not doing autonegotiation, we don't need to do
265	 * any extra work here.  However, we need to check the link
266	 * status so we can generate an announcement if the status
267	 * changes.
268	 */
269	if (IFM_SUBTYPE(ife->ifm_media) != IFM_AUTO)
270		return (0);
271
272	/* Read the status register twice; BMSR_LINK is latch-low. */
273	reg = PHY_READ(sc, MII_BMSR) | PHY_READ(sc, MII_BMSR);
274	if (reg & BMSR_LINK) {
275		/*
276		 * See above.
277		 */
278		return (0);
279	}
280
281	/*
282	 * Only retry autonegotiation every N seconds.
283	 */
284	if (sc->mii_anegticks == 0)
285		sc->mii_anegticks = 5;
286	if (++sc->mii_ticks != sc->mii_anegticks)
287		return (EJUSTRETURN);
288
289	sc->mii_ticks = 0;
290	mii_phy_reset(sc);
291	if (mii_phy_auto(sc, 0) == EJUSTRETURN)
292		return (EJUSTRETURN);
293
294	/*
295	 * Might need to generate a status message if autonegotiation
296	 * failed.
297	 */
298	return (0);
299}
300
301void
302mii_phy_reset(struct mii_softc *sc)
303{
304	int reg, i;
305
306	if (sc->mii_flags & MIIF_NOISOLATE)
307		reg = BMCR_RESET;
308	else
309		reg = BMCR_RESET | BMCR_ISO;
310	PHY_WRITE(sc, MII_BMCR, reg);
311
312	/* Wait 100ms for it to complete. */
313	for (i = 0; i < 100; i++) {
314		reg = PHY_READ(sc, MII_BMCR);
315		if ((reg & BMCR_RESET) == 0)
316			break;
317		DELAY(1000);
318	}
319
320	if (sc->mii_inst != 0 && ((sc->mii_flags & MIIF_NOISOLATE) == 0))
321		PHY_WRITE(sc, MII_BMCR, reg | BMCR_ISO);
322}
323
324void
325mii_phy_down(struct mii_softc *sc)
326{
327
328	if (sc->mii_flags & MIIF_DOINGAUTO) {
329		sc->mii_flags &= ~MIIF_DOINGAUTO;
330		untimeout(mii_phy_auto_timeout, sc, sc->mii_auto_ch);
331	}
332}
333
334void
335mii_phy_update(struct mii_softc *sc, int cmd)
336{
337	struct mii_data *mii = sc->mii_pdata;
338
339	if (sc->mii_media_active != mii->mii_media_active ||
340	    cmd == MII_MEDIACHG) {
341		MIIBUS_STATCHG(sc->mii_dev);
342		sc->mii_media_active = mii->mii_media_active;
343	}
344	if (sc->mii_media_status != mii->mii_media_status) {
345		MIIBUS_LINKCHG(sc->mii_dev);
346		sc->mii_media_status = mii->mii_media_status;
347	}
348}
349
350/*
351 * Given an ifmedia word, return the corresponding ANAR value.
352 */
353int
354mii_anar(media)
355	int media;
356{
357	int rv;
358
359	switch (media & (IFM_TMASK|IFM_NMASK|IFM_FDX)) {
360	case IFM_ETHER|IFM_10_T:
361		rv = ANAR_10|ANAR_CSMA;
362		break;
363	case IFM_ETHER|IFM_10_T|IFM_FDX:
364		rv = ANAR_10_FD|ANAR_CSMA;
365		break;
366	case IFM_ETHER|IFM_100_TX:
367		rv = ANAR_TX|ANAR_CSMA;
368		break;
369	case IFM_ETHER|IFM_100_TX|IFM_FDX:
370		rv = ANAR_TX_FD|ANAR_CSMA;
371		break;
372	case IFM_ETHER|IFM_100_T4:
373		rv = ANAR_T4|ANAR_CSMA;
374		break;
375	default:
376		rv = 0;
377		break;
378	}
379
380	return (rv);
381}
382
383/*
384 * Given a BMCR value, return the corresponding ifmedia word.
385 */
386int
387mii_media_from_bmcr(bmcr)
388	int bmcr;
389{
390	int rv = IFM_ETHER;
391
392	if (bmcr & BMCR_S100)
393		rv |= IFM_100_TX;
394	else
395		rv |= IFM_10_T;
396	if (bmcr & BMCR_FDX)
397		rv |= IFM_FDX;
398
399	return (rv);
400}
401
402/*
403 * Initialize generic PHY media based on BMSR, called when a PHY is
404 * attached.  We expect to be set up to print a comma-separated list
405 * of media names.  Does not print a newline.
406 */
407void
408mii_add_media(struct mii_softc *sc)
409{
410	const char *sep = "";
411	struct mii_data *mii;
412
413	mii = device_get_softc(sc->mii_dev);
414	if ((sc->mii_capabilities & BMSR_MEDIAMASK) == 0) {
415		printf("no media present");
416		return;
417	}
418
419#define	ADD(m, c)	ifmedia_add(&mii->mii_media, (m), (c), NULL)
420#define	PRINT(s)	printf("%s%s", sep, s); sep = ", "
421
422	if (sc->mii_capabilities & BMSR_10THDX) {
423		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_10_T, 0, sc->mii_inst), 0);
424		PRINT("10baseT");
425	}
426	if (sc->mii_capabilities & BMSR_10TFDX) {
427		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_10_T, IFM_FDX, sc->mii_inst),
428		    BMCR_FDX);
429		PRINT("10baseT-FDX");
430	}
431	if (sc->mii_capabilities & BMSR_100TXHDX) {
432		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_100_TX, 0, sc->mii_inst),
433		    BMCR_S100);
434		PRINT("100baseTX");
435	}
436	if (sc->mii_capabilities & BMSR_100TXFDX) {
437		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_100_TX, IFM_FDX, sc->mii_inst),
438		    BMCR_S100|BMCR_FDX);
439		PRINT("100baseTX-FDX");
440	}
441	if (sc->mii_capabilities & BMSR_100T4) {
442		/*
443		 * XXX How do you enable 100baseT4?  I assume we set
444		 * XXX BMCR_S100 and then assume the PHYs will take
445		 * XXX watever action is necessary to switch themselves
446		 * XXX into T4 mode.
447		 */
448		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_100_T4, 0, sc->mii_inst),
449		    BMCR_S100);
450		PRINT("100baseT4");
451	}
452	if (sc->mii_capabilities & BMSR_ANEG) {
453		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, sc->mii_inst),
454		    BMCR_AUTOEN);
455		PRINT("auto");
456	}
457
458
459
460#undef ADD
461#undef PRINT
462}
463
464/*
465 * Initialize generic PHY media based on BMSR, called when a PHY is
466 * attached.  We expect to be set up to print a comma-separated list
467 * of media names.  Does not print a newline.
468 */
469void
470mii_phy_add_media(struct mii_softc *sc)
471{
472	struct mii_data *mii = sc->mii_pdata;
473	const char *sep = "";
474
475#define	ADD(m, c)	ifmedia_add(&mii->mii_media, (m), (c), NULL)
476#define	PRINT(s)	printf("%s%s", sep, s); sep = ", "
477
478	if ((sc->mii_flags & MIIF_NOISOLATE) == 0)
479		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_NONE, 0, sc->mii_inst),
480		    MII_MEDIA_NONE);
481
482	/*
483	 * There are different interpretations for the bits in
484	 * HomePNA PHYs.  And there is really only one media type
485	 * that is supported.
486	 */
487	if (sc->mii_flags & MIIF_IS_HPNA) {
488		if (sc->mii_capabilities & BMSR_10THDX) {
489			ADD(IFM_MAKEWORD(IFM_ETHER, IFM_HPNA_1, 0,
490					 sc->mii_inst),
491			    MII_MEDIA_10_T);
492			PRINT("HomePNA1");
493		}
494		return;
495	}
496
497	if (sc->mii_capabilities & BMSR_10THDX) {
498		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_10_T, 0, sc->mii_inst),
499		    MII_MEDIA_10_T);
500		PRINT("10baseT");
501	}
502	if (sc->mii_capabilities & BMSR_10TFDX) {
503		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_10_T, IFM_FDX, sc->mii_inst),
504		    MII_MEDIA_10_T_FDX);
505		PRINT("10baseT-FDX");
506	}
507	if (sc->mii_capabilities & BMSR_100TXHDX) {
508		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_100_TX, 0, sc->mii_inst),
509		    MII_MEDIA_100_TX);
510		PRINT("100baseTX");
511	}
512	if (sc->mii_capabilities & BMSR_100TXFDX) {
513		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_100_TX, IFM_FDX, sc->mii_inst),
514		    MII_MEDIA_100_TX_FDX);
515		PRINT("100baseTX-FDX");
516	}
517	if (sc->mii_capabilities & BMSR_100T4) {
518		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_100_T4, 0, sc->mii_inst),
519		    MII_MEDIA_100_T4);
520		PRINT("100baseT4");
521	}
522
523	if (sc->mii_extcapabilities & EXTSR_MEDIAMASK) {
524		/*
525		 * XXX Right now only handle 1000SX and 1000TX.  Need
526		 * XXX to handle 1000LX and 1000CX some how.
527		 *
528		 * Note since it can take 5 seconds to auto-negotiate
529		 * a gigabit link, we make anegticks 10 seconds for
530		 * all the gigabit media types.
531		 */
532		if (sc->mii_extcapabilities & EXTSR_1000XHDX) {
533			sc->mii_anegticks = 10;
534			sc->mii_flags |= MIIF_IS_1000X;
535			ADD(IFM_MAKEWORD(IFM_ETHER, IFM_1000_SX, 0,
536			    sc->mii_inst), MII_MEDIA_1000_X);
537			PRINT("1000baseSX");
538		}
539		if (sc->mii_extcapabilities & EXTSR_1000XFDX) {
540			sc->mii_anegticks = 10;
541			sc->mii_flags |= MIIF_IS_1000X;
542			ADD(IFM_MAKEWORD(IFM_ETHER, IFM_1000_SX, IFM_FDX,
543			    sc->mii_inst), MII_MEDIA_1000_X_FDX);
544			PRINT("1000baseSX-FDX");
545		}
546
547		/*
548		 * 1000baseT media needs to be able to manipulate
549		 * master/slave mode.  We set IFM_ETH_MASTER in
550		 * the "don't care mask" and filter it out when
551		 * the media is set.
552		 *
553		 * All 1000baseT PHYs have a 1000baseT control register.
554		 */
555		if (sc->mii_extcapabilities & EXTSR_1000THDX) {
556			sc->mii_anegticks = 10;
557			sc->mii_flags |= MIIF_HAVE_GTCR;
558			mii->mii_media.ifm_mask |= IFM_ETH_MASTER;
559			ADD(IFM_MAKEWORD(IFM_ETHER, IFM_1000_T, 0,
560			    sc->mii_inst), MII_MEDIA_1000_T);
561			PRINT("1000baseT");
562		}
563		if (sc->mii_extcapabilities & EXTSR_1000TFDX) {
564			sc->mii_anegticks = 10;
565			sc->mii_flags |= MIIF_HAVE_GTCR;
566			mii->mii_media.ifm_mask |= IFM_ETH_MASTER;
567			ADD(IFM_MAKEWORD(IFM_ETHER, IFM_1000_T, IFM_FDX,
568			    sc->mii_inst), MII_MEDIA_1000_T_FDX);
569			PRINT("1000baseT-FDX");
570		}
571	}
572
573	if (sc->mii_capabilities & BMSR_ANEG) {
574		ADD(IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, sc->mii_inst),
575		    MII_NMEDIA);	/* intentionally invalid index */
576		PRINT("auto");
577	}
578#undef ADD
579#undef PRINT
580}
581
582int
583mii_phy_detach(device_t dev)
584{
585	struct mii_softc *sc;
586	struct mii_data *mii;
587
588	sc = device_get_softc(dev);
589	mii = device_get_softc(device_get_parent(dev));
590	mii_phy_down(sc);
591	sc->mii_dev = NULL;
592	LIST_REMOVE(sc, mii_list);
593
594	return(0);
595}
596