1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2006 Benno Rice.  All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following 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 ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30/*
31 * Driver for the SMSC LAN8710A
32 */
33
34#include <sys/param.h>
35#include <sys/systm.h>
36#include <sys/kernel.h>
37#include <sys/socket.h>
38#include <sys/errno.h>
39#include <sys/module.h>
40#include <sys/bus.h>
41#include <sys/malloc.h>
42
43#include <machine/bus.h>
44
45#include <net/if.h>
46#include <net/if_media.h>
47
48#include <dev/mii/mii.h>
49#include <dev/mii/miivar.h>
50#include "miidevs.h"
51
52#include "miibus_if.h"
53
54static int	smscphy_probe(device_t);
55static int	smscphy_attach(device_t);
56
57static int	smscphy_service(struct mii_softc *, struct mii_data *, int);
58static void	smscphy_auto(struct mii_softc *, int);
59static void	smscphy_status(struct mii_softc *);
60
61static device_method_t smscphy_methods[] = {
62	/* device interface */
63	DEVMETHOD(device_probe,		smscphy_probe),
64	DEVMETHOD(device_attach,	smscphy_attach),
65	DEVMETHOD(device_detach,	mii_phy_detach),
66	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
67	DEVMETHOD_END
68};
69
70static devclass_t smscphy_devclass;
71
72static driver_t smscphy_driver = {
73	"smscphy",
74	smscphy_methods,
75	sizeof(struct mii_softc)
76};
77
78DRIVER_MODULE(smscphy, miibus, smscphy_driver, smscphy_devclass, 0, 0);
79
80static const struct mii_phydesc smscphys[] = {
81	MII_PHY_DESC(SMC, LAN8710A),
82	MII_PHY_DESC(SMC, LAN8700),
83	MII_PHY_END
84};
85
86static const struct mii_phy_funcs smscphy_funcs = {
87	smscphy_service,
88	smscphy_status,
89	mii_phy_reset
90};
91
92static int
93smscphy_probe(device_t dev)
94{
95
96	return (mii_phy_dev_probe(dev, smscphys, BUS_PROBE_DEFAULT));
97}
98
99static int
100smscphy_attach(device_t dev)
101{
102	struct mii_softc *sc;
103	const struct mii_phy_funcs *mpf;
104
105	sc = device_get_softc(dev);
106	mpf = &smscphy_funcs;
107	mii_phy_dev_attach(dev, MIIF_NOISOLATE | MIIF_NOMANPAUSE, mpf, 1);
108	mii_phy_setmedia(sc);
109
110	return (0);
111}
112
113static int
114smscphy_service(struct mii_softc *sc, struct mii_data *mii, int cmd)
115{
116        struct	ifmedia_entry *ife;
117        int	reg;
118
119	ife = mii->mii_media.ifm_cur;
120
121        switch (cmd) {
122        case MII_POLLSTAT:
123                break;
124
125        case MII_MEDIACHG:
126		switch (IFM_SUBTYPE(ife->ifm_media)) {
127		case IFM_AUTO:
128			smscphy_auto(sc, ife->ifm_media);
129			break;
130
131		default:
132                	mii_phy_setmedia(sc);
133			break;
134		}
135
136                break;
137
138        case MII_TICK:
139		if (IFM_SUBTYPE(ife->ifm_media) != IFM_AUTO) {
140			break;
141		}
142
143		/* I have no idea why BMCR_ISO gets set. */
144		reg = PHY_READ(sc, MII_BMCR);
145		if (reg & BMCR_ISO) {
146			PHY_WRITE(sc, MII_BMCR, reg & ~BMCR_ISO);
147		}
148
149		reg = PHY_READ(sc, MII_BMSR) | PHY_READ(sc, MII_BMSR);
150		if (reg & BMSR_LINK) {
151			sc->mii_ticks = 0;
152			break;
153		}
154
155		if (++sc->mii_ticks <= MII_ANEGTICKS) {
156			break;
157		}
158
159		sc->mii_ticks = 0;
160		PHY_RESET(sc);
161		smscphy_auto(sc, ife->ifm_media);
162                break;
163        }
164
165        /* Update the media status. */
166        PHY_STATUS(sc);
167
168        /* Callback if something changed. */
169        mii_phy_update(sc, cmd);
170        return (0);
171}
172
173static void
174smscphy_auto(struct mii_softc *sc, int media)
175{
176	uint16_t	anar;
177
178	anar = BMSR_MEDIA_TO_ANAR(sc->mii_capabilities) | ANAR_CSMA;
179	if ((media & IFM_FLOW) != 0 || (sc->mii_flags & MIIF_FORCEPAUSE) != 0)
180		anar |= ANAR_FC;
181	PHY_WRITE(sc, MII_ANAR, anar);
182	/* Apparently this helps. */
183	anar = PHY_READ(sc, MII_ANAR);
184	PHY_WRITE(sc, MII_BMCR, BMCR_AUTOEN | BMCR_STARTNEG);
185}
186
187static void
188smscphy_status(struct mii_softc *sc)
189{
190	struct mii_data *mii;
191	uint32_t bmcr, bmsr, status;
192
193	mii = sc->mii_pdata;
194	mii->mii_media_status = IFM_AVALID;
195	mii->mii_media_active = IFM_ETHER;
196
197	bmsr = PHY_READ(sc, MII_BMSR) | PHY_READ(sc, MII_BMSR);
198	if ((bmsr & BMSR_LINK) != 0)
199		mii->mii_media_status |= IFM_ACTIVE;
200
201	bmcr = PHY_READ(sc, MII_BMCR);
202	if ((bmcr & BMCR_ISO) != 0) {
203		mii->mii_media_active |= IFM_NONE;
204		mii->mii_media_status = 0;
205		return;
206	}
207
208	if ((bmcr & BMCR_LOOP) != 0)
209		mii->mii_media_active |= IFM_LOOP;
210
211	if ((bmcr & BMCR_AUTOEN) != 0) {
212		if ((bmsr & BMSR_ACOMP) == 0) {
213			/* Erg, still trying, I guess... */
214			mii->mii_media_active |= IFM_NONE;
215			return;
216		}
217	}
218
219	status = PHY_READ(sc, 0x1F);
220	if (status & 0x0008)
221		mii->mii_media_active |= IFM_100_TX;
222	else
223		mii->mii_media_active |= IFM_10_T;
224	if (status & 0x0010)
225		mii->mii_media_active |= IFM_FDX | mii_phy_flowstatus(sc);
226	else
227		mii->mii_media_active |= IFM_HDX;
228}
229