1139749Simp/*-
267314Sjon * Copyright (c) 2000,2001 Jonathan Chen.
367314Sjon * All rights reserved.
467314Sjon *
567314Sjon * Redistribution and use in source and binary forms, with or without
667314Sjon * modification, are permitted provided that the following conditions
767314Sjon * are met:
867314Sjon * 1. Redistributions of source code must retain the above copyright
967314Sjon *    notice, this list of conditions, and the following disclaimer,
1067314Sjon *    without modification, immediately at the beginning of the file.
1167314Sjon * 2. Redistributions in binary form must reproduce the above copyright
1267314Sjon *    notice, this list of conditions and the following disclaimer in
1367314Sjon *    the documentation and/or other materials provided with the
1467314Sjon *    distribution.
1567314Sjon *
1667314Sjon * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1767314Sjon * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1867314Sjon * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1967314Sjon * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
2067314Sjon * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2167314Sjon * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2267314Sjon * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2367314Sjon * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2467314Sjon * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2567314Sjon * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2667314Sjon * SUCH DAMAGE.
2767314Sjon */
2867314Sjon
29119418Sobrien#include <sys/cdefs.h>
30119418Sobrien__FBSDID("$FreeBSD$");
31119418Sobrien
3267314Sjon/*
3367314Sjon * Driver for the TDK 78Q2120 MII
3467314Sjon *
3567314Sjon * References:
3667314Sjon *   Datasheet for the 78Q2120 - http://www.tsc.tdk.com/lan/78q2120.pdf
3767314Sjon *   Most of this code stolen from ukphy.c
3867314Sjon */
3967314Sjon
4067314Sjon/*
41201453Simp * The TDK 78Q2120 is found on some Xircom X3201 based CardBus cards,
42150762Simp * also spotted on some 3C575 cards.  It's just like any other normal
43150762Simp * phy, except it does auto negotiation in a different way.
4467314Sjon */
4567314Sjon
4667314Sjon#include <sys/param.h>
4767314Sjon#include <sys/systm.h>
4867314Sjon#include <sys/kernel.h>
4967314Sjon#include <sys/socket.h>
5067314Sjon#include <sys/errno.h>
5167314Sjon#include <sys/module.h>
5267314Sjon#include <sys/bus.h>
5367314Sjon
5467314Sjon#include <net/if.h>
5567314Sjon#include <net/if_media.h>
5667314Sjon
5767314Sjon#include <dev/mii/mii.h>
5867314Sjon#include <dev/mii/miivar.h>
59109514Sobrien#include "miidevs.h"
6067314Sjon
6167314Sjon#include <dev/mii/tdkphyreg.h>
6267314Sjon
6367314Sjon#include "miibus_if.h"
6467314Sjon
65105135Salfredstatic int tdkphy_probe(device_t);
66105135Salfredstatic int tdkphy_attach(device_t);
6767314Sjon
6867314Sjonstatic device_method_t tdkphy_methods[] = {
6967314Sjon	/* device interface */
7067314Sjon	DEVMETHOD(device_probe,		tdkphy_probe),
7167314Sjon	DEVMETHOD(device_attach,	tdkphy_attach),
7295722Sphk	DEVMETHOD(device_detach,	mii_phy_detach),
7367314Sjon	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
74227908Smarius	DEVMETHOD_END
7567314Sjon};
7667314Sjon
7767314Sjonstatic devclass_t tdkphy_devclass;
7867314Sjon
7967314Sjonstatic driver_t tdkphy_driver = {
8067314Sjon	"tdkphy",
8167314Sjon	tdkphy_methods,
8267314Sjon	sizeof(struct mii_softc)
8367314Sjon};
8467314Sjon
8567314SjonDRIVER_MODULE(tdkphy, miibus, tdkphy_driver, tdkphy_devclass, 0, 0);
8667314Sjon
8792739Salfredstatic int tdkphy_service(struct mii_softc *, struct mii_data *, int);
8892739Salfredstatic void tdkphy_status(struct mii_softc *);
8967314Sjon
90164827Smariusstatic const struct mii_phydesc tdkphys[] = {
91221407Smarius	MII_PHY_DESC(xxTSC, 78Q2120),
92164827Smarius	MII_PHY_END
93164827Smarius};
94164827Smarius
95221407Smariusstatic const struct mii_phy_funcs tdkphy_funcs = {
96221407Smarius	tdkphy_service,
97221407Smarius	tdkphy_status,
98221407Smarius	mii_phy_reset
99221407Smarius};
100221407Smarius
10167314Sjonstatic int
10267314Sjontdkphy_probe(device_t dev)
10367314Sjon{
10467314Sjon
105164827Smarius	return (mii_phy_dev_probe(dev, tdkphys, BUS_PROBE_DEFAULT));
10667314Sjon}
10767314Sjon
10867314Sjonstatic int
10967314Sjontdkphy_attach(device_t dev)
11067314Sjon{
11167314Sjon
112221407Smarius	mii_phy_dev_attach(dev, MIIF_NOMANPAUSE, &tdkphy_funcs, 1);
113164830Smarius	return (0);
11467314Sjon}
11567314Sjon
11684145Sjlemonstatic int
11767314Sjontdkphy_service(struct mii_softc *sc, struct mii_data *mii, int cmd)
11867314Sjon{
11967314Sjon
12067314Sjon	switch (cmd) {
12167314Sjon	case MII_POLLSTAT:
12267314Sjon		break;
12367314Sjon
12467314Sjon	case MII_MEDIACHG:
12567314Sjon		/*
12667314Sjon		 * If the interface is not up, don't do anything.
12767314Sjon		 */
12867314Sjon		if ((mii->mii_ifp->if_flags & IFF_UP) == 0)
12967314Sjon			break;
13067314Sjon
131164832Smarius		mii_phy_setmedia(sc);
13267314Sjon		break;
13367314Sjon
13467314Sjon	case MII_TICK:
13584145Sjlemon		if (mii_phy_tick(sc) == EJUSTRETURN)
13667314Sjon			return (0);
13767314Sjon		break;
13867314Sjon	}
13967314Sjon
14067314Sjon	/* Update the media status. */
141221407Smarius	PHY_STATUS(sc);
14267314Sjon	if (sc->mii_pdata->mii_media_active & IFM_FDX)
14367314Sjon		PHY_WRITE(sc, MII_BMCR, PHY_READ(sc, MII_BMCR) | BMCR_FDX);
14467314Sjon	else
14567314Sjon		PHY_WRITE(sc, MII_BMCR, PHY_READ(sc, MII_BMCR) & ~BMCR_FDX);
14684145Sjlemon
14767314Sjon	/* Callback if something changed. */
14884145Sjlemon	mii_phy_update(sc, cmd);
14967314Sjon	return (0);
15067314Sjon}
15167314Sjon
15284145Sjlemonstatic void
15367314Sjontdkphy_status(struct mii_softc *phy)
15467314Sjon{
15567314Sjon	struct mii_data *mii = phy->mii_pdata;
156164832Smarius	struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
15767314Sjon	int bmsr, bmcr, anlpar, diag;
15867314Sjon
15967314Sjon	mii->mii_media_status = IFM_AVALID;
16067314Sjon	mii->mii_media_active = IFM_ETHER;
16167314Sjon
16267314Sjon	bmsr = PHY_READ(phy, MII_BMSR) | PHY_READ(phy, MII_BMSR);
16367314Sjon	if (bmsr & BMSR_LINK)
16467314Sjon		mii->mii_media_status |= IFM_ACTIVE;
16567314Sjon
16667314Sjon	bmcr = PHY_READ(phy, MII_BMCR);
16767314Sjon	if (bmcr & BMCR_ISO) {
16867314Sjon		mii->mii_media_active |= IFM_NONE;
16967314Sjon		mii->mii_media_status = 0;
17067314Sjon		return;
17167314Sjon	}
17267314Sjon
17367314Sjon	if (bmcr & BMCR_LOOP)
17467314Sjon		mii->mii_media_active |= IFM_LOOP;
17567314Sjon
17667314Sjon	if (bmcr & BMCR_AUTOEN) {
17767314Sjon		/*
17867314Sjon		 * NWay autonegotiation takes the highest-order common
17967314Sjon		 * bit of the ANAR and ANLPAR (i.e. best media advertised
18067314Sjon		 * both by us and our link partner).
18167314Sjon		 */
18267314Sjon		if ((bmsr & BMSR_ACOMP) == 0) {
18367314Sjon			/* Erg, still trying, I guess... */
18467314Sjon			mii->mii_media_active |= IFM_NONE;
18567314Sjon			return;
18667314Sjon		}
18767314Sjon
18867314Sjon		anlpar = PHY_READ(phy, MII_ANAR) & PHY_READ(phy, MII_ANLPAR);
18967314Sjon		/*
19067314Sjon		 * ANLPAR doesn't get set on my card, but we check it anyway,
19167314Sjon		 * since it is mentioned in the 78Q2120 specs.
19267314Sjon		 */
193173665Syongari		if (anlpar & ANLPAR_TX_FD)
194173665Syongari			mii->mii_media_active |= IFM_100_TX|IFM_FDX;
195173665Syongari		else if (anlpar & ANLPAR_T4)
196213384Smarius			mii->mii_media_active |= IFM_100_T4|IFM_HDX;
19767314Sjon		else if (anlpar & ANLPAR_TX)
198213384Smarius			mii->mii_media_active |= IFM_100_TX|IFM_HDX;
19967314Sjon		else if (anlpar & ANLPAR_10_FD)
20067314Sjon			mii->mii_media_active |= IFM_10_T|IFM_FDX;
20167314Sjon		else if (anlpar & ANLPAR_10)
202213384Smarius			mii->mii_media_active |= IFM_10_T|IFM_HDX;
20367314Sjon		else {
20467314Sjon			/*
20567314Sjon			 * ANLPAR isn't set, which leaves two possibilities:
20667314Sjon			 * 1) Auto negotiation failed
20767314Sjon			 * 2) Auto negotiation completed, but the card forgot
20867314Sjon			 *    to set ANLPAR.
20967314Sjon			 * So we check the MII_DIAG(18) register...
21067314Sjon			 */
21167314Sjon			diag = PHY_READ(phy, MII_DIAG);
21267314Sjon			if (diag & DIAG_NEGFAIL) /* assume 10baseT if no neg */
213213384Smarius				mii->mii_media_active |= IFM_10_T|IFM_HDX;
21467314Sjon			else {
21567314Sjon				if (diag & DIAG_DUPLEX)
21667314Sjon					mii->mii_media_active |= IFM_FDX;
217213384Smarius				else
218213384Smarius					mii->mii_media_active |= IFM_HDX;
21967314Sjon				if (diag & DIAG_RATE_100)
22067314Sjon					mii->mii_media_active |= IFM_100_TX;
22167314Sjon				else
22267314Sjon					mii->mii_media_active |= IFM_10_T;
22367314Sjon			}
22467314Sjon		}
225221407Smarius		if ((mii->mii_media_active & IFM_FDX) != 0)
226221407Smarius			mii->mii_media_active |= mii_phy_flowstatus(phy);
227164832Smarius	} else
228164832Smarius		mii->mii_media_active = ife->ifm_media;
22967314Sjon}
230