1216828Syongari/*-
2216828Syongari * Copyright (c) 2010, Pyun YongHyeon <yongari@FreeBSD.org>
3216828Syongari * All rights reserved.
4216828Syongari *
5216828Syongari * Redistribution and use in source and binary forms, with or without
6216828Syongari * modification, are permitted provided that the following conditions
7216828Syongari * are met:
8216828Syongari * 1. Redistributions of source code must retain the above copyright
9216828Syongari *    notice unmodified, this list of conditions, and the following
10216828Syongari *    disclaimer.
11216828Syongari * 2. Redistributions in binary form must reproduce the above copyright
12216828Syongari *    notice, this list of conditions and the following disclaimer in the
13216828Syongari *    documentation and/or other materials provided with the distribution.
14216828Syongari *
15216828Syongari * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16216828Syongari * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17216828Syongari * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18216828Syongari * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19216828Syongari * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20216828Syongari * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21216828Syongari * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22216828Syongari * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23216828Syongari * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24216828Syongari * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25216828Syongari * SUCH DAMAGE.
26216828Syongari */
27216828Syongari
28216828Syongari#include <sys/cdefs.h>
29216828Syongari__FBSDID("$FreeBSD$");
30216828Syongari
31216828Syongari/*
32216828Syongari * Driver for the RDC Semiconductor R6040 10/100 PHY.
33216828Syongari */
34216828Syongari
35216828Syongari#include <sys/param.h>
36216828Syongari#include <sys/systm.h>
37216828Syongari#include <sys/kernel.h>
38216828Syongari#include <sys/module.h>
39216828Syongari#include <sys/socket.h>
40216828Syongari#include <sys/bus.h>
41216828Syongari
42216828Syongari#include <net/if.h>
43216828Syongari#include <net/if_media.h>
44216828Syongari
45216828Syongari#include <dev/mii/mii.h>
46216828Syongari#include <dev/mii/miivar.h>
47216828Syongari#include "miidevs.h"
48216828Syongari
49216828Syongari#include <dev/mii/rdcphyreg.h>
50216828Syongari
51216828Syongari#include "miibus_if.h"
52216828Syongari
53216828Syongaristatic device_probe_t	rdcphy_probe;
54216828Syongaristatic device_attach_t	rdcphy_attach;
55216828Syongari
56216828Syongaristruct rdcphy_softc {
57216828Syongari	struct mii_softc mii_sc;
58216828Syongari	int mii_link_tick;
59216828Syongari#define	RDCPHY_MANNEG_TICK	3
60216828Syongari};
61216828Syongari
62216828Syongaristatic device_method_t rdcphy_methods[] = {
63216828Syongari	/* device interface */
64216828Syongari	DEVMETHOD(device_probe,		rdcphy_probe),
65216828Syongari	DEVMETHOD(device_attach,	rdcphy_attach),
66216828Syongari	DEVMETHOD(device_detach,	mii_phy_detach),
67216828Syongari	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
68227848Smarius	DEVMETHOD_END
69216828Syongari};
70216828Syongari
71216828Syongaristatic devclass_t rdcphy_devclass;
72216828Syongari
73216828Syongaristatic driver_t rdcphy_driver = {
74216828Syongari	"rdcphy",
75216828Syongari	rdcphy_methods,
76216828Syongari	sizeof(struct rdcphy_softc)
77216828Syongari};
78216828Syongari
79216828SyongariDRIVER_MODULE(rdcphy, miibus, rdcphy_driver, rdcphy_devclass, 0, 0);
80216828Syongari
81216828Syongaristatic int	rdcphy_service(struct mii_softc *, struct mii_data *, int);
82216828Syongaristatic void	rdcphy_status(struct mii_softc *);
83216828Syongari
84216828Syongaristatic const struct mii_phydesc rdcphys[] = {
85216828Syongari	MII_PHY_DESC(RDC, R6040),
86216828Syongari	MII_PHY_END
87216828Syongari};
88216828Syongari
89221407Smariusstatic const struct mii_phy_funcs rdcphy_funcs = {
90221407Smarius	rdcphy_service,
91221407Smarius	rdcphy_status,
92221407Smarius	mii_phy_reset
93221407Smarius};
94221407Smarius
95216828Syongaristatic int
96216828Syongarirdcphy_probe(device_t dev)
97216828Syongari{
98216828Syongari
99216828Syongari	return (mii_phy_dev_probe(dev, rdcphys, BUS_PROBE_DEFAULT));
100216828Syongari}
101216828Syongari
102216828Syongaristatic int
103216828Syongarirdcphy_attach(device_t dev)
104216828Syongari{
105216828Syongari
106221407Smarius	mii_phy_dev_attach(dev, MIIF_NOMANPAUSE, &rdcphy_funcs, 1);
107216828Syongari	return (0);
108216828Syongari}
109216828Syongari
110216828Syongaristatic int
111216828Syongarirdcphy_service(struct mii_softc *sc, struct mii_data *mii, int cmd)
112216828Syongari{
113216828Syongari	struct rdcphy_softc *rsc;
114216828Syongari	struct ifmedia_entry *ife;
115216828Syongari
116216828Syongari	rsc = (struct rdcphy_softc *)sc;
117216828Syongari	ife = mii->mii_media.ifm_cur;
118216828Syongari
119216828Syongari	switch (cmd) {
120216828Syongari	case MII_POLLSTAT:
121216828Syongari		break;
122216828Syongari
123216828Syongari	case MII_MEDIACHG:
124216828Syongari		/*
125216828Syongari		 * If the interface is not up, don't do anything.
126216828Syongari		 */
127216828Syongari		if ((mii->mii_ifp->if_flags & IFF_UP) == 0)
128216828Syongari			break;
129216828Syongari
130216828Syongari		mii_phy_setmedia(sc);
131216828Syongari		switch (IFM_SUBTYPE(ife->ifm_media)) {
132216828Syongari		case IFM_100_TX:
133216828Syongari		case IFM_10_T:
134216828Syongari			/*
135216828Syongari			 * Report fake lost link event to parent
136216828Syongari			 * driver.  This will stop MAC of parent
137216828Syongari			 * driver and make it possible to reconfigure
138216828Syongari			 * MAC after completion of link establishment.
139216828Syongari			 * Note, the parent MAC seems to require
140216828Syongari			 * restarting MAC when underlying any PHY
141216828Syongari			 * configuration was changed even if the
142216828Syongari			 * resolved speed/duplex was not changed at
143216828Syongari			 * all.
144216828Syongari			 */
145216828Syongari			mii->mii_media_status = 0;
146216828Syongari			mii->mii_media_active = IFM_ETHER | IFM_NONE;
147216828Syongari			rsc->mii_link_tick = RDCPHY_MANNEG_TICK;
148216828Syongari			/* Immediately report link down. */
149216828Syongari			mii_phy_update(sc, MII_MEDIACHG);
150216828Syongari			return (0);
151216828Syongari		default:
152216828Syongari			break;
153216828Syongari		}
154216828Syongari		break;
155216828Syongari
156216828Syongari	case MII_TICK:
157216828Syongari		if (mii_phy_tick(sc) == EJUSTRETURN)
158216828Syongari			return (0);
159216828Syongari		if (IFM_SUBTYPE(ife->ifm_media) != IFM_AUTO) {
160216828Syongari			/*
161216828Syongari			 * It seems the PHY hardware does not correctly
162216828Syongari			 * report link status changes when manual link
163216828Syongari			 * configuration is in progress.  It is also
164216828Syongari			 * possible for the PHY to complete establishing
165216828Syongari			 * a link within one second such that mii(4)
166216828Syongari			 * did not notice the link change.  To workaround
167216828Syongari			 * the issue, emulate lost link event and wait
168216828Syongari			 * for 3 seconds when manual link configuration
169216828Syongari			 * is in progress.  3 seconds would be long
170216828Syongari			 * enough to absorb transient link flips.
171216828Syongari			 */
172216828Syongari			if (rsc->mii_link_tick > 0) {
173216828Syongari				rsc->mii_link_tick--;
174216828Syongari				return (0);
175216828Syongari			}
176216828Syongari		}
177216828Syongari		break;
178216828Syongari	}
179216828Syongari
180216828Syongari	/* Update the media status. */
181221407Smarius	PHY_STATUS(sc);
182216828Syongari
183216828Syongari	/* Callback if something changed. */
184216828Syongari	mii_phy_update(sc, cmd);
185216828Syongari	return (0);
186216828Syongari}
187216828Syongari
188216828Syongaristatic void
189216828Syongarirdcphy_status(struct mii_softc *sc)
190216828Syongari{
191216828Syongari	struct mii_data *mii;
192216828Syongari	struct ifmedia_entry *ife;
193216828Syongari	int bmsr, bmcr, physts;
194216828Syongari
195216828Syongari	mii = sc->mii_pdata;
196216828Syongari	ife = mii->mii_media.ifm_cur;
197216828Syongari
198216828Syongari	mii->mii_media_status = IFM_AVALID;
199216828Syongari	mii->mii_media_active = IFM_ETHER;
200216828Syongari
201216828Syongari	bmsr = PHY_READ(sc, MII_BMSR) | PHY_READ(sc, MII_BMSR);
202216828Syongari	physts = PHY_READ(sc, MII_RDCPHY_STATUS);
203216828Syongari
204216828Syongari	if ((physts & STATUS_LINK_UP) != 0)
205216828Syongari		mii->mii_media_status |= IFM_ACTIVE;
206216828Syongari
207216828Syongari	bmcr = PHY_READ(sc, MII_BMCR);
208216828Syongari	if ((bmcr & BMCR_ISO) != 0) {
209216828Syongari		mii->mii_media_active |= IFM_NONE;
210216828Syongari		mii->mii_media_status = 0;
211216828Syongari		return;
212216828Syongari	}
213216828Syongari
214216828Syongari	if ((bmcr & BMCR_LOOP) != 0)
215216828Syongari		mii->mii_media_active |= IFM_LOOP;
216216828Syongari
217216828Syongari	if ((bmcr & BMCR_AUTOEN) != 0) {
218216828Syongari		if ((bmsr & BMSR_ACOMP) == 0) {
219216828Syongari			/* Erg, still trying, I guess... */
220216828Syongari			mii->mii_media_active |= IFM_NONE;
221216828Syongari			return;
222216828Syongari		}
223216828Syongari	}
224216828Syongari
225216828Syongari	switch (physts & STATUS_SPEED_MASK) {
226216828Syongari	case STATUS_SPEED_100:
227216828Syongari		mii->mii_media_active |= IFM_100_TX;
228216828Syongari		break;
229216828Syongari	case STATUS_SPEED_10:
230216828Syongari		mii->mii_media_active |= IFM_10_T;
231216828Syongari		break;
232216828Syongari	default:
233216828Syongari		mii->mii_media_active |= IFM_NONE;
234216828Syongari		return;
235216828Syongari	}
236216828Syongari	if ((physts & STATUS_FULL_DUPLEX) != 0)
237216828Syongari		mii->mii_media_active |= IFM_FDX | mii_phy_flowstatus(sc);
238216828Syongari	else
239216828Syongari		mii->mii_media_active |= IFM_HDX;
240216828Syongari}
241