1/*-
2 * Copyright (c) 2008, Pyun YongHyeon <yongari@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice unmodified, this list of conditions, and the following
10 *    disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD$");
30
31/*
32 * Driver for the JMicron JMP211 10/100/1000, JMP202 10/100 PHY.
33 */
34
35#include <sys/param.h>
36#include <sys/systm.h>
37#include <sys/kernel.h>
38#include <sys/module.h>
39#include <sys/socket.h>
40#include <sys/bus.h>
41
42#include <net/if.h>
43#include <net/if_var.h>
44#include <net/if_media.h>
45
46#include <dev/mii/mii.h>
47#include <dev/mii/miivar.h>
48#include "miidevs.h"
49
50#include <dev/mii/jmphyreg.h>
51
52#include "miibus_if.h"
53
54static int	jmphy_probe(device_t);
55static int	jmphy_attach(device_t);
56static void	jmphy_reset(struct mii_softc *);
57static uint16_t	jmphy_anar(struct ifmedia_entry *);
58static int	jmphy_setmedia(struct mii_softc *, struct ifmedia_entry *);
59
60static device_method_t jmphy_methods[] = {
61	/* Device interface. */
62	DEVMETHOD(device_probe,		jmphy_probe),
63	DEVMETHOD(device_attach,	jmphy_attach),
64	DEVMETHOD(device_detach,	mii_phy_detach),
65	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
66	DEVMETHOD_END
67};
68
69static devclass_t jmphy_devclass;
70static driver_t jmphy_driver = {
71	"jmphy",
72	jmphy_methods,
73	sizeof(struct mii_softc)
74};
75
76DRIVER_MODULE(jmphy, miibus, jmphy_driver, jmphy_devclass, 0, 0);
77
78static int	jmphy_service(struct mii_softc *, struct mii_data *, int);
79static void	jmphy_status(struct mii_softc *);
80
81static const struct mii_phydesc jmphys[] = {
82	MII_PHY_DESC(JMICRON, JMP202),
83	MII_PHY_DESC(JMICRON, JMP211),
84	MII_PHY_END
85};
86
87static const struct mii_phy_funcs jmphy_funcs = {
88	jmphy_service,
89	jmphy_status,
90	jmphy_reset
91};
92
93static int
94jmphy_probe(device_t dev)
95{
96
97	return (mii_phy_dev_probe(dev, jmphys, BUS_PROBE_DEFAULT));
98}
99
100static int
101jmphy_attach(device_t dev)
102{
103	u_int flags;
104
105	flags = 0;
106	if (mii_dev_mac_match(dev, "jme") &&
107	    (miibus_get_flags(dev) & MIIF_MACPRIV0) != 0)
108		flags |= MIIF_PHYPRIV0;
109	mii_phy_dev_attach(dev, flags, &jmphy_funcs, 1);
110	return (0);
111}
112
113static int
114jmphy_service(struct mii_softc *sc, struct mii_data *mii, int cmd)
115{
116	struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
117
118	switch (cmd) {
119	case MII_POLLSTAT:
120		break;
121
122	case MII_MEDIACHG:
123		if (jmphy_setmedia(sc, ife) != EJUSTRETURN)
124			return (EINVAL);
125		break;
126
127	case MII_TICK:
128		/*
129		 * Only used for autonegotiation.
130		 */
131		if (IFM_SUBTYPE(ife->ifm_media) != IFM_AUTO) {
132			sc->mii_ticks = 0;
133			break;
134		}
135
136		/* Check for link. */
137		if ((PHY_READ(sc, JMPHY_SSR) & JMPHY_SSR_LINK_UP) != 0) {
138			sc->mii_ticks = 0;
139			break;
140		}
141
142		/* Announce link loss right after it happens. */
143		if (sc->mii_ticks++ == 0)
144			break;
145		if (sc->mii_ticks <= sc->mii_anegticks)
146			return (0);
147
148		sc->mii_ticks = 0;
149		(void)jmphy_setmedia(sc, ife);
150		break;
151	}
152
153	/* Update the media status. */
154	PHY_STATUS(sc);
155
156	/* Callback if something changed. */
157	mii_phy_update(sc, cmd);
158	return (0);
159}
160
161static void
162jmphy_status(struct mii_softc *sc)
163{
164	struct mii_data *mii = sc->mii_pdata;
165	int bmcr, ssr;
166
167	mii->mii_media_status = IFM_AVALID;
168	mii->mii_media_active = IFM_ETHER;
169
170	ssr = PHY_READ(sc, JMPHY_SSR);
171	if ((ssr & JMPHY_SSR_LINK_UP) != 0)
172		mii->mii_media_status |= IFM_ACTIVE;
173
174	bmcr = PHY_READ(sc, MII_BMCR);
175	if ((bmcr & BMCR_ISO) != 0) {
176		mii->mii_media_active |= IFM_NONE;
177		mii->mii_media_status = 0;
178		return;
179	}
180
181	if ((bmcr & BMCR_LOOP) != 0)
182		mii->mii_media_active |= IFM_LOOP;
183
184	if ((ssr & JMPHY_SSR_SPD_DPLX_RESOLVED) == 0) {
185		/* Erg, still trying, I guess... */
186		mii->mii_media_active |= IFM_NONE;
187		return;
188	}
189
190	switch ((ssr & JMPHY_SSR_SPEED_MASK)) {
191	case JMPHY_SSR_SPEED_1000:
192		mii->mii_media_active |= IFM_1000_T;
193		/*
194		 * jmphy(4) got a valid link so reset mii_ticks.
195		 * Resetting mii_ticks is needed in order to
196		 * detect link loss after auto-negotiation.
197		 */
198		sc->mii_ticks = 0;
199		break;
200	case JMPHY_SSR_SPEED_100:
201		mii->mii_media_active |= IFM_100_TX;
202		sc->mii_ticks = 0;
203		break;
204	case JMPHY_SSR_SPEED_10:
205		mii->mii_media_active |= IFM_10_T;
206		sc->mii_ticks = 0;
207		break;
208	default:
209		mii->mii_media_active |= IFM_NONE;
210		return;
211	}
212
213	if ((ssr & JMPHY_SSR_DUPLEX) != 0)
214		mii->mii_media_active |= IFM_FDX | mii_phy_flowstatus(sc);
215	else
216		mii->mii_media_active |= IFM_HDX;
217
218	if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T) {
219		if ((PHY_READ(sc, MII_100T2SR) & GTSR_MS_RES) != 0)
220			mii->mii_media_active |= IFM_ETH_MASTER;
221	}
222}
223
224static void
225jmphy_reset(struct mii_softc *sc)
226{
227	uint16_t t2cr, val;
228	int i;
229
230	/* Disable sleep mode. */
231	PHY_WRITE(sc, JMPHY_TMCTL,
232	    PHY_READ(sc, JMPHY_TMCTL) & ~JMPHY_TMCTL_SLEEP_ENB);
233	PHY_WRITE(sc, MII_BMCR, BMCR_RESET | BMCR_AUTOEN);
234
235	for (i = 0; i < 1000; i++) {
236		DELAY(1);
237		if ((PHY_READ(sc, MII_BMCR) & BMCR_RESET) == 0)
238			break;
239	}
240	/* Perform vendor recommended PHY calibration. */
241	if ((sc->mii_flags & MIIF_PHYPRIV0) != 0) {
242		/* Select PHY test mode 1. */
243		t2cr = PHY_READ(sc, MII_100T2CR);
244		t2cr &= ~GTCR_TEST_MASK;
245		t2cr |= 0x2000;
246		PHY_WRITE(sc, MII_100T2CR, t2cr);
247		/* Apply calibration patch. */
248		PHY_WRITE(sc, JMPHY_SPEC_ADDR, JMPHY_SPEC_ADDR_READ |
249		    JMPHY_EXT_COMM_2);
250		val = PHY_READ(sc, JMPHY_SPEC_DATA);
251		val &= ~0x0002;
252		val |= 0x0010 | 0x0001;
253		PHY_WRITE(sc, JMPHY_SPEC_DATA, val);
254		PHY_WRITE(sc, JMPHY_SPEC_ADDR, JMPHY_SPEC_ADDR_WRITE |
255		    JMPHY_EXT_COMM_2);
256
257		/* XXX 20ms to complete recalibration. */
258		DELAY(20 * 1000);
259
260		PHY_READ(sc, MII_100T2CR);
261		PHY_WRITE(sc, JMPHY_SPEC_ADDR, JMPHY_SPEC_ADDR_READ |
262		    JMPHY_EXT_COMM_2);
263		val = PHY_READ(sc, JMPHY_SPEC_DATA);
264		val &= ~(0x0001 | 0x0002 | 0x0010);
265		PHY_WRITE(sc, JMPHY_SPEC_DATA, val);
266		PHY_WRITE(sc, JMPHY_SPEC_ADDR, JMPHY_SPEC_ADDR_WRITE |
267		    JMPHY_EXT_COMM_2);
268		/* Disable PHY test mode. */
269		PHY_READ(sc, MII_100T2CR);
270		t2cr &= ~GTCR_TEST_MASK;
271		PHY_WRITE(sc, MII_100T2CR, t2cr);
272	}
273}
274
275static uint16_t
276jmphy_anar(struct ifmedia_entry *ife)
277{
278	uint16_t anar;
279
280	anar = 0;
281	switch (IFM_SUBTYPE(ife->ifm_media)) {
282	case IFM_AUTO:
283		anar |= ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10;
284		break;
285	case IFM_1000_T:
286		break;
287	case IFM_100_TX:
288		anar |= ANAR_TX | ANAR_TX_FD;
289		break;
290	case IFM_10_T:
291		anar |= ANAR_10 | ANAR_10_FD;
292		break;
293	default:
294		break;
295	}
296
297	return (anar);
298}
299
300static int
301jmphy_setmedia(struct mii_softc *sc, struct ifmedia_entry *ife)
302{
303	uint16_t anar, bmcr, gig;
304
305	gig = 0;
306	bmcr = PHY_READ(sc, MII_BMCR);
307	switch (IFM_SUBTYPE(ife->ifm_media)) {
308	case IFM_AUTO:
309		gig |= GTCR_ADV_1000TFDX | GTCR_ADV_1000THDX;
310		break;
311	case IFM_1000_T:
312		gig |= GTCR_ADV_1000TFDX | GTCR_ADV_1000THDX;
313		break;
314	case IFM_100_TX:
315	case IFM_10_T:
316		break;
317	case IFM_NONE:
318		PHY_WRITE(sc, MII_BMCR, bmcr | BMCR_ISO | BMCR_PDOWN);
319		return (EJUSTRETURN);
320	default:
321		return (EINVAL);
322	}
323
324	anar = jmphy_anar(ife);
325	if ((IFM_SUBTYPE(ife->ifm_media) == IFM_AUTO ||
326	    (ife->ifm_media & IFM_FDX) != 0) &&
327	    ((ife->ifm_media & IFM_FLOW) != 0 ||
328	    (sc->mii_flags & MIIF_FORCEPAUSE) != 0))
329		anar |= ANAR_PAUSE_TOWARDS;
330
331	if ((sc->mii_flags & MIIF_HAVE_GTCR) != 0) {
332		if (IFM_SUBTYPE(ife->ifm_media) == IFM_1000_T) {
333			gig |= GTCR_MAN_MS;
334			if ((ife->ifm_media & IFM_ETH_MASTER) != 0)
335				gig |= GTCR_ADV_MS;
336		}
337		PHY_WRITE(sc, MII_100T2CR, gig);
338	}
339	PHY_WRITE(sc, MII_ANAR, anar | ANAR_CSMA);
340	PHY_WRITE(sc, MII_BMCR, bmcr | BMCR_AUTOEN | BMCR_STARTNEG);
341
342	return (EJUSTRETURN);
343}
344