1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2019 Rubicon Communications, LLC (Netgate)
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 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#include "opt_platform.h"
32
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/bus.h>
36#include <sys/gpio.h>
37#include <sys/kernel.h>
38#include <sys/module.h>
39#include <sys/socket.h>
40
41#include <net/if.h>
42#include <net/if_media.h>
43#include <net/if_types.h>
44
45#include <dev/fdt/fdt_common.h>
46#include <dev/gpio/gpiobusvar.h>
47#include <dev/mii/mii_bitbang.h>
48#include <dev/mii/miivar.h>
49#include <dev/ofw/ofw_bus.h>
50
51#include "gpiobus_if.h"
52#include "miibus_if.h"
53
54#define	GPIOMDIO_MDC_DFLT	0
55#define	GPIOMDIO_MDIO_DFLT	1
56#define	GPIOMDIO_MIN_PINS	2
57
58#define	MDO_BIT			0x01
59#define	MDI_BIT			0x02
60#define	MDC_BIT			0x04
61#define	MDIRPHY_BIT		0x08
62#define	MDIRHOST_BIT		0x10
63#define	MDO			sc->miibb_ops.mbo_bits[MII_BIT_MDO]
64#define	MDI			sc->miibb_ops.mbo_bits[MII_BIT_MDI]
65#define	MDC			sc->miibb_ops.mbo_bits[MII_BIT_MDC]
66#define	MDIRPHY			sc->miibb_ops.mbo_bits[MII_BIT_DIR_HOST_PHY]
67#define	MDIRHOST		sc->miibb_ops.mbo_bits[MII_BIT_DIR_PHY_HOST]
68
69static uint32_t gpiomdio_bb_read(device_t);
70static void gpiomdio_bb_write(device_t, uint32_t);
71
72struct gpiomdio_softc
73{
74	device_t		sc_dev;
75	device_t		sc_busdev;
76	int			mdc_pin;
77	int			mdio_pin;
78	struct mii_bitbang_ops	miibb_ops;
79};
80
81
82static int
83gpiomdio_probe(device_t dev)
84{
85	struct gpiobus_ivar *devi;
86
87	if (!ofw_bus_status_okay(dev))
88		return (ENXIO);
89	if (!ofw_bus_is_compatible(dev, "freebsd,gpiomdio"))
90		return (ENXIO);
91	devi = GPIOBUS_IVAR(dev);
92	if (devi->npins < GPIOMDIO_MIN_PINS) {
93		device_printf(dev,
94		    "gpiomdio needs at least %d GPIO pins (only %d given).\n",
95		    GPIOMDIO_MIN_PINS, devi->npins);
96		return (ENXIO);
97	}
98	device_set_desc(dev, "GPIO MDIO bit-banging Bus driver");
99
100	return (BUS_PROBE_DEFAULT);
101}
102
103static int
104gpiomdio_attach(device_t dev)
105{
106	phandle_t		node;
107	pcell_t			pin;
108	struct gpiobus_ivar	*devi;
109	struct gpiomdio_softc	*sc;
110
111	sc = device_get_softc(dev);
112	sc->sc_dev = dev;
113	sc->sc_busdev = device_get_parent(dev);
114
115	if ((node = ofw_bus_get_node(dev)) == -1)
116		return (ENXIO);
117	if (OF_getencprop(node, "mdc", &pin, sizeof(pin)) > 0)
118		sc->mdc_pin = (int)pin;
119	if (OF_getencprop(node, "mdio", &pin, sizeof(pin)) > 0)
120		sc->mdio_pin = (int)pin;
121
122	if (sc->mdc_pin < 0 || sc->mdc_pin > 1)
123		sc->mdc_pin = GPIOMDIO_MDC_DFLT;
124	if (sc->mdio_pin < 0 || sc->mdio_pin > 1)
125		sc->mdio_pin = GPIOMDIO_MDIO_DFLT;
126
127	devi = GPIOBUS_IVAR(dev);
128	device_printf(dev, "MDC pin: %d, MDIO pin: %d\n",
129	    devi->pins[sc->mdc_pin], devi->pins[sc->mdio_pin]);
130
131	/* Initialize mii_bitbang_ops. */
132	MDO = MDO_BIT;
133	MDI = MDI_BIT;
134	MDC = MDC_BIT;
135	MDIRPHY = MDIRPHY_BIT;
136	MDIRHOST = MDIRHOST_BIT;
137	sc->miibb_ops.mbo_read = gpiomdio_bb_read;
138	sc->miibb_ops.mbo_write = gpiomdio_bb_write;
139
140	/* Register our MDIO Bus device. */
141	OF_device_register_xref(OF_xref_from_node(node), dev);
142
143	return (0);
144}
145
146static uint32_t
147gpiomdio_bb_read(device_t dev)
148{
149	struct gpiomdio_softc *sc;
150	unsigned int val;
151
152	sc = device_get_softc(dev);
153	GPIOBUS_PIN_GET(sc->sc_busdev, sc->sc_dev, sc->mdio_pin, &val);
154
155	return (val != 0 ? MDI_BIT : 0);
156}
157
158static void
159gpiomdio_bb_write(device_t dev, uint32_t val)
160{
161	struct gpiomdio_softc *sc;
162
163	sc = device_get_softc(dev);
164
165	/* Set the data pin state. */
166	if ((val & (MDIRPHY_BIT | MDO_BIT)) == (MDIRPHY_BIT | MDO_BIT))
167		GPIOBUS_PIN_SET(sc->sc_busdev, sc->sc_dev, sc->mdio_pin, 1);
168	else if ((val & (MDIRPHY_BIT | MDO_BIT)) == MDIRPHY_BIT)
169		GPIOBUS_PIN_SET(sc->sc_busdev, sc->sc_dev, sc->mdio_pin, 0);
170	if (val & MDIRPHY_BIT)
171		GPIOBUS_PIN_SETFLAGS(sc->sc_busdev, sc->sc_dev, sc->mdio_pin,
172		    GPIO_PIN_OUTPUT);
173	else if (val & MDIRHOST_BIT)
174		GPIOBUS_PIN_SETFLAGS(sc->sc_busdev, sc->sc_dev, sc->mdio_pin,
175		    GPIO_PIN_INPUT);
176
177	/* And now the clock state. */
178	if (val & MDC_BIT)
179		GPIOBUS_PIN_SET(sc->sc_busdev, sc->sc_dev, sc->mdc_pin, 1);
180	else
181		GPIOBUS_PIN_SET(sc->sc_busdev, sc->sc_dev, sc->mdc_pin, 0);
182	GPIOBUS_PIN_SETFLAGS(sc->sc_busdev, sc->sc_dev, sc->mdc_pin,
183	    GPIO_PIN_OUTPUT);
184}
185
186static int
187gpiomdio_readreg(device_t dev, int phy, int reg)
188{
189	struct gpiomdio_softc	*sc;
190
191	sc = device_get_softc(dev);
192
193	return (mii_bitbang_readreg(dev, &sc->miibb_ops, phy, reg));
194}
195
196static int
197gpiomdio_writereg(device_t dev, int phy, int reg, int val)
198{
199	struct gpiomdio_softc	*sc;
200
201	sc = device_get_softc(dev);
202	mii_bitbang_writereg(dev, &sc->miibb_ops, phy, reg, val);
203
204	return (0);
205}
206
207static phandle_t
208gpiomdio_get_node(device_t bus, device_t dev)
209{
210
211	return (ofw_bus_get_node(bus));
212}
213
214static devclass_t gpiomdio_devclass;
215
216static device_method_t gpiomdio_methods[] = {
217	/* Device interface */
218	DEVMETHOD(device_probe,		gpiomdio_probe),
219	DEVMETHOD(device_attach,	gpiomdio_attach),
220	DEVMETHOD(device_detach,	bus_generic_detach),
221
222	/* MDIO interface */
223	DEVMETHOD(miibus_readreg,	gpiomdio_readreg),
224	DEVMETHOD(miibus_writereg,	gpiomdio_writereg),
225
226	/* OFW bus interface */
227	DEVMETHOD(ofw_bus_get_node,	gpiomdio_get_node),
228
229	DEVMETHOD_END
230};
231
232static driver_t gpiomdio_driver = {
233	"gpiomdio",
234	gpiomdio_methods,
235	sizeof(struct gpiomdio_softc),
236};
237
238EARLY_DRIVER_MODULE(gpiomdio, gpiobus, gpiomdio_driver, gpiomdio_devclass,
239    0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
240DRIVER_MODULE(miibus, gpiomdio, miibus_driver, miibus_devclass, 0, 0);
241MODULE_DEPEND(gpiomdio, gpiobus, 1, 1, 1);
242MODULE_DEPEND(gpiomdio, miibus, 1, 1, 1);
243MODULE_DEPEND(gpiomdio, mii_bitbang, 1, 1, 1);
244