xref: /freebsd/sys/dev/qcom_mdio/qcom_mdio_ipq4018.c (revision bfed2417f472f87e720b37bdac9ffd75ca2abc54)
129332c0dSAdrian Chadd /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
329332c0dSAdrian Chadd  *
429332c0dSAdrian Chadd  * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>
529332c0dSAdrian Chadd  *
629332c0dSAdrian Chadd  * Redistribution and use in source and binary forms, with or without
729332c0dSAdrian Chadd  * modification, are permitted provided that the following conditions
829332c0dSAdrian Chadd  * are met:
929332c0dSAdrian Chadd  * 1. Redistributions of source code must retain the above copyright
1029332c0dSAdrian Chadd  *    notice unmodified, this list of conditions, and the following
1129332c0dSAdrian Chadd  *    disclaimer.
1229332c0dSAdrian Chadd  * 2. Redistributions in binary form must reproduce the above copyright
1329332c0dSAdrian Chadd  *    notice, this list of conditions and the following disclaimer in the
1429332c0dSAdrian Chadd  *    documentation and/or other materials provided with the distribution.
1529332c0dSAdrian Chadd  *
1629332c0dSAdrian Chadd  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1729332c0dSAdrian Chadd  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1829332c0dSAdrian Chadd  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1929332c0dSAdrian Chadd  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2029332c0dSAdrian Chadd  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2129332c0dSAdrian Chadd  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2229332c0dSAdrian Chadd  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2329332c0dSAdrian Chadd  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2429332c0dSAdrian Chadd  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2529332c0dSAdrian Chadd  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2629332c0dSAdrian Chadd  * SUCH DAMAGE.
2729332c0dSAdrian Chadd  */
2829332c0dSAdrian Chadd 
2929332c0dSAdrian Chadd /*
3029332c0dSAdrian Chadd  * This is the MDIO controller for the IPQ4018/IPQ4019.
3129332c0dSAdrian Chadd  */
3229332c0dSAdrian Chadd 
3329332c0dSAdrian Chadd #include <sys/cdefs.h>
3429332c0dSAdrian Chadd 
3529332c0dSAdrian Chadd #include <sys/param.h>
3629332c0dSAdrian Chadd #include <sys/systm.h>
3729332c0dSAdrian Chadd #include <sys/bus.h>
3829332c0dSAdrian Chadd 
3929332c0dSAdrian Chadd #include <sys/kernel.h>
4029332c0dSAdrian Chadd #include <sys/module.h>
4129332c0dSAdrian Chadd #include <sys/rman.h>
4229332c0dSAdrian Chadd #include <sys/lock.h>
4329332c0dSAdrian Chadd #include <sys/malloc.h>
4429332c0dSAdrian Chadd #include <sys/mutex.h>
4529332c0dSAdrian Chadd #include <sys/gpio.h>
4629332c0dSAdrian Chadd 
4729332c0dSAdrian Chadd #include <machine/bus.h>
4829332c0dSAdrian Chadd #include <machine/resource.h>
4929332c0dSAdrian Chadd #include <dev/gpio/gpiobusvar.h>
5029332c0dSAdrian Chadd 
5129332c0dSAdrian Chadd #include <dev/fdt/fdt_common.h>
5229332c0dSAdrian Chadd #include <dev/ofw/ofw_bus.h>
5329332c0dSAdrian Chadd #include <dev/ofw/ofw_bus_subr.h>
5429332c0dSAdrian Chadd 
5529332c0dSAdrian Chadd #include "mdio_if.h"
5629332c0dSAdrian Chadd 
5729332c0dSAdrian Chadd #include <dev/qcom_mdio/qcom_mdio_ipq4018_var.h>
5829332c0dSAdrian Chadd #include <dev/qcom_mdio/qcom_mdio_ipq4018_reg.h>
5929332c0dSAdrian Chadd 
6029332c0dSAdrian Chadd #include <dev/qcom_mdio/qcom_mdio_debug.h>
6129332c0dSAdrian Chadd 
6229332c0dSAdrian Chadd static int
qcom_mdio_ipq4018_probe(device_t dev)6329332c0dSAdrian Chadd qcom_mdio_ipq4018_probe(device_t dev)
6429332c0dSAdrian Chadd {
6529332c0dSAdrian Chadd 
6629332c0dSAdrian Chadd 	if (! ofw_bus_status_okay(dev))
6729332c0dSAdrian Chadd 		return (ENXIO);
6829332c0dSAdrian Chadd 
6929332c0dSAdrian Chadd 	if (ofw_bus_is_compatible(dev, "qcom,ipq4019-mdio") == 0)
7029332c0dSAdrian Chadd 		return (ENXIO);
7129332c0dSAdrian Chadd 
7229332c0dSAdrian Chadd 	device_set_desc(dev,
7329332c0dSAdrian Chadd 	    "Qualcomm Atheros IPQ4018/IPQ4019 MDIO driver");
7429332c0dSAdrian Chadd 	return (0);
7529332c0dSAdrian Chadd }
7629332c0dSAdrian Chadd 
7729332c0dSAdrian Chadd static int
qcom_mdio_ipq4018_detach(device_t dev)7829332c0dSAdrian Chadd qcom_mdio_ipq4018_detach(device_t dev)
7929332c0dSAdrian Chadd {
8029332c0dSAdrian Chadd 	struct qcom_mdio_ipq4018_softc *sc = device_get_softc(dev);
8129332c0dSAdrian Chadd 
8229332c0dSAdrian Chadd 	if (sc->sc_mem_res != NULL)
8329332c0dSAdrian Chadd 		bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid,
8429332c0dSAdrian Chadd 		    sc->sc_mem_res);
8529332c0dSAdrian Chadd 	mtx_destroy(&sc->sc_mtx);
8629332c0dSAdrian Chadd 
8729332c0dSAdrian Chadd 	return (0);
8829332c0dSAdrian Chadd }
8929332c0dSAdrian Chadd 
9029332c0dSAdrian Chadd static void
qcom_mdio_sysctl_attach(struct qcom_mdio_ipq4018_softc * sc)9129332c0dSAdrian Chadd qcom_mdio_sysctl_attach(struct qcom_mdio_ipq4018_softc *sc)
9229332c0dSAdrian Chadd {
9329332c0dSAdrian Chadd 	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev);
9429332c0dSAdrian Chadd 	struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev);
9529332c0dSAdrian Chadd 
9629332c0dSAdrian Chadd 	SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
9729332c0dSAdrian Chadd 	    "debug", CTLFLAG_RW, &sc->sc_debug, 0,
9829332c0dSAdrian Chadd 	    "control debugging printfs");
9929332c0dSAdrian Chadd }
10029332c0dSAdrian Chadd 
10129332c0dSAdrian Chadd static int
qcom_mdio_ipq4018_attach(device_t dev)10229332c0dSAdrian Chadd qcom_mdio_ipq4018_attach(device_t dev)
10329332c0dSAdrian Chadd {
10429332c0dSAdrian Chadd 	phandle_t node;
10529332c0dSAdrian Chadd 	struct qcom_mdio_ipq4018_softc *sc = device_get_softc(dev);
10629332c0dSAdrian Chadd 	int error = 0;
10729332c0dSAdrian Chadd 
10829332c0dSAdrian Chadd 	node = ofw_bus_get_node(dev);
10929332c0dSAdrian Chadd 
11029332c0dSAdrian Chadd 	sc->sc_dev = dev;
11129332c0dSAdrian Chadd 	sc->sc_debug = 0;
11229332c0dSAdrian Chadd 	mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
11329332c0dSAdrian Chadd 
11429332c0dSAdrian Chadd 	/*
11529332c0dSAdrian Chadd 	 * Map the MDIO memory region.
11629332c0dSAdrian Chadd 	 */
11729332c0dSAdrian Chadd 	sc->sc_mem_rid = 0;
11829332c0dSAdrian Chadd 	sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
11929332c0dSAdrian Chadd 	    &sc->sc_mem_rid, RF_ACTIVE);
12029332c0dSAdrian Chadd 	if (sc->sc_mem_res == NULL) {
12129332c0dSAdrian Chadd 		error = ENXIO;
12229332c0dSAdrian Chadd 		device_printf(dev, "%s: failed to map device memory\n",
12329332c0dSAdrian Chadd 		    __func__);
12429332c0dSAdrian Chadd 		goto error;
12529332c0dSAdrian Chadd 	}
12629332c0dSAdrian Chadd 	sc->sc_mem_res_size = (size_t) bus_get_resource_count(dev,
12729332c0dSAdrian Chadd 	    SYS_RES_MEMORY, sc->sc_mem_rid);
12829332c0dSAdrian Chadd 	if (sc->sc_mem_res_size == 0) {
12929332c0dSAdrian Chadd 		error = ENXIO;
13029332c0dSAdrian Chadd 		device_printf(dev, "%s: failed to get device memory size\n",
13129332c0dSAdrian Chadd 		    __func__);
13229332c0dSAdrian Chadd 		goto error;
13329332c0dSAdrian Chadd 
13429332c0dSAdrian Chadd 	}
13529332c0dSAdrian Chadd 
13629332c0dSAdrian Chadd 	qcom_mdio_sysctl_attach(sc);
13729332c0dSAdrian Chadd 
13829332c0dSAdrian Chadd 	OF_device_register_xref(OF_xref_from_node(node), dev);
13929332c0dSAdrian Chadd 
14029332c0dSAdrian Chadd 	return (0);
14129332c0dSAdrian Chadd error:
14229332c0dSAdrian Chadd 	if (sc->sc_mem_res != NULL)
14329332c0dSAdrian Chadd 		bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid,
14429332c0dSAdrian Chadd 		    sc->sc_mem_res);
14529332c0dSAdrian Chadd 
14629332c0dSAdrian Chadd 	mtx_destroy(&sc->sc_mtx);
14729332c0dSAdrian Chadd 	return (error);
14829332c0dSAdrian Chadd }
14929332c0dSAdrian Chadd 
15029332c0dSAdrian Chadd /*
15129332c0dSAdrian Chadd  * Wait for the BUSY flag to become zero.
15229332c0dSAdrian Chadd  *
15329332c0dSAdrian Chadd  * This has to happen before every MDIO transfer.
15429332c0dSAdrian Chadd  *
15529332c0dSAdrian Chadd  * Returns 0 if OK, error if error/timed out.
15629332c0dSAdrian Chadd  */
15729332c0dSAdrian Chadd static int
qcom_mdio_ipq4018_wait(struct qcom_mdio_ipq4018_softc * sc)15829332c0dSAdrian Chadd qcom_mdio_ipq4018_wait(struct qcom_mdio_ipq4018_softc *sc)
15929332c0dSAdrian Chadd {
16029332c0dSAdrian Chadd 	int i;
16129332c0dSAdrian Chadd 	uint32_t reg;
16229332c0dSAdrian Chadd 
16329332c0dSAdrian Chadd 	MDIO_LOCK_ASSERT(sc);
16429332c0dSAdrian Chadd 
16529332c0dSAdrian Chadd 	for (i = 0; i < QCOM_IPQ4018_MDIO_SLEEP_COUNT; i++) {
16629332c0dSAdrian Chadd 		MDIO_BARRIER_READ(sc);
16729332c0dSAdrian Chadd 
16829332c0dSAdrian Chadd 		reg = MDIO_READ(sc, QCOM_IPQ4018_MDIO_REG_CMD);
16929332c0dSAdrian Chadd 		if ((reg & QCOM_IPQ4018_MDIO_REG_CMD_ACCESS_BUSY) == 0)
17029332c0dSAdrian Chadd 			return (0);
17129332c0dSAdrian Chadd 		DELAY(QCOM_IPQ4018_MDIO_SLEEP);
17229332c0dSAdrian Chadd 	}
17329332c0dSAdrian Chadd 	device_printf(sc->sc_dev, "%s: warning: timeout waiting for bus\n",
17429332c0dSAdrian Chadd 	    __func__);
17529332c0dSAdrian Chadd 	return (ETIMEDOUT);
17629332c0dSAdrian Chadd }
17729332c0dSAdrian Chadd 
17829332c0dSAdrian Chadd static void
qcom_mdio_ipq4018_set_phy_reg_addr(struct qcom_mdio_ipq4018_softc * sc,int phy,int reg)17929332c0dSAdrian Chadd qcom_mdio_ipq4018_set_phy_reg_addr(struct qcom_mdio_ipq4018_softc *sc,
18029332c0dSAdrian Chadd   int phy, int reg)
18129332c0dSAdrian Chadd {
18229332c0dSAdrian Chadd 
18329332c0dSAdrian Chadd 	MDIO_LOCK_ASSERT(sc);
18429332c0dSAdrian Chadd 
18529332c0dSAdrian Chadd 	MDIO_WRITE(sc, QCOM_IPQ4018_MDIO_REG_ADDR,
18629332c0dSAdrian Chadd 	    ((phy & 0xff) << 8) | (reg & 0xff));
18729332c0dSAdrian Chadd 	MDIO_BARRIER_WRITE(sc);
18829332c0dSAdrian Chadd }
18929332c0dSAdrian Chadd 
19029332c0dSAdrian Chadd static int
qcom_mdio_ipq4018_readreg(device_t dev,int phy,int reg)19129332c0dSAdrian Chadd qcom_mdio_ipq4018_readreg(device_t dev, int phy, int reg)
19229332c0dSAdrian Chadd {
19329332c0dSAdrian Chadd 	struct qcom_mdio_ipq4018_softc *sc = device_get_softc(dev);
19429332c0dSAdrian Chadd 	uint32_t ret;
19529332c0dSAdrian Chadd 
19629332c0dSAdrian Chadd 	QCOM_MDIO_DPRINTF(sc, QCOM_MDIO_DEBUG_REG_READ,
19729332c0dSAdrian Chadd 	    "%s: called; phy=0x%x reg=0x%x\n",
19829332c0dSAdrian Chadd 	    __func__, phy, reg);
19929332c0dSAdrian Chadd 
20029332c0dSAdrian Chadd 	MDIO_LOCK(sc);
20129332c0dSAdrian Chadd 	if (qcom_mdio_ipq4018_wait(sc) != 0) {
20229332c0dSAdrian Chadd 		MDIO_UNLOCK(sc);
20329332c0dSAdrian Chadd 		return (-1);
20429332c0dSAdrian Chadd 	}
20529332c0dSAdrian Chadd 
20629332c0dSAdrian Chadd 	/* Set phy/reg values */
20729332c0dSAdrian Chadd 	qcom_mdio_ipq4018_set_phy_reg_addr(sc, phy, reg);
20829332c0dSAdrian Chadd 
20929332c0dSAdrian Chadd 	/* Issue read command */
21029332c0dSAdrian Chadd 	MDIO_WRITE(sc, QCOM_IPQ4018_MDIO_REG_CMD,
21129332c0dSAdrian Chadd 	    QCOM_IPQ4018_MDIO_REG_CMD_ACCESS_START |
21229332c0dSAdrian Chadd 	    QCOM_IPQ4018_MDIO_REG_CMD_ACCESS_CODE_READ);
21329332c0dSAdrian Chadd 	MDIO_BARRIER_WRITE(sc);
21429332c0dSAdrian Chadd 
21529332c0dSAdrian Chadd 	/* Wait for completion */
21629332c0dSAdrian Chadd 	if (qcom_mdio_ipq4018_wait(sc) != 0) {
21729332c0dSAdrian Chadd 		MDIO_UNLOCK(sc);
21829332c0dSAdrian Chadd 		return (-1);
21929332c0dSAdrian Chadd 	}
22029332c0dSAdrian Chadd 
22129332c0dSAdrian Chadd 	/* Fetch return register value */
22229332c0dSAdrian Chadd 	MDIO_BARRIER_READ(sc);
22329332c0dSAdrian Chadd 	ret = MDIO_READ(sc, QCOM_IPQ4018_MDIO_REG_READ);
22429332c0dSAdrian Chadd 	MDIO_UNLOCK(sc);
22529332c0dSAdrian Chadd 
22629332c0dSAdrian Chadd 	QCOM_MDIO_DPRINTF(sc, QCOM_MDIO_DEBUG_REG_READ,
22729332c0dSAdrian Chadd 	    "%s: -> 0x%x\n", __func__, ret);
22829332c0dSAdrian Chadd 
22929332c0dSAdrian Chadd 	return (ret);
23029332c0dSAdrian Chadd }
23129332c0dSAdrian Chadd 
23229332c0dSAdrian Chadd static int
qcom_mdio_ipq4018_writereg(device_t dev,int phy,int reg,int value)23329332c0dSAdrian Chadd qcom_mdio_ipq4018_writereg(device_t dev, int phy, int reg, int value)
23429332c0dSAdrian Chadd {
23529332c0dSAdrian Chadd 	struct qcom_mdio_ipq4018_softc *sc = device_get_softc(dev);
23629332c0dSAdrian Chadd 
23729332c0dSAdrian Chadd 	QCOM_MDIO_DPRINTF(sc, QCOM_MDIO_DEBUG_REG_WRITE,
23829332c0dSAdrian Chadd 	    "%s: called; phy=0x%x reg=0x%x val=0x%x\n",
23929332c0dSAdrian Chadd 	    __func__, phy, reg, value);
24029332c0dSAdrian Chadd 
24129332c0dSAdrian Chadd 	MDIO_LOCK(sc);
24229332c0dSAdrian Chadd 	if (qcom_mdio_ipq4018_wait(sc) != 0) {
24329332c0dSAdrian Chadd 		MDIO_UNLOCK(sc);
24429332c0dSAdrian Chadd 		return (-1);
24529332c0dSAdrian Chadd 	}
24629332c0dSAdrian Chadd 
24729332c0dSAdrian Chadd 	/* Set phy/reg values */
24829332c0dSAdrian Chadd 	qcom_mdio_ipq4018_set_phy_reg_addr(sc, phy, reg);
24929332c0dSAdrian Chadd 
25029332c0dSAdrian Chadd 	/* Write command */
25129332c0dSAdrian Chadd 	MDIO_WRITE(sc, QCOM_IPQ4018_MDIO_REG_WRITE, value);
25229332c0dSAdrian Chadd 	MDIO_BARRIER_WRITE(sc);
25329332c0dSAdrian Chadd 
25429332c0dSAdrian Chadd 	/* Issue write command */
25529332c0dSAdrian Chadd 	MDIO_WRITE(sc, QCOM_IPQ4018_MDIO_REG_CMD,
25629332c0dSAdrian Chadd 	    QCOM_IPQ4018_MDIO_REG_CMD_ACCESS_START |
25729332c0dSAdrian Chadd 	    QCOM_IPQ4018_MDIO_REG_CMD_ACCESS_CODE_WRITE);
25829332c0dSAdrian Chadd 	MDIO_BARRIER_WRITE(sc);
25929332c0dSAdrian Chadd 
26029332c0dSAdrian Chadd 	/* Wait for completion */
26129332c0dSAdrian Chadd 	if (qcom_mdio_ipq4018_wait(sc) != 0) {
26229332c0dSAdrian Chadd 		MDIO_UNLOCK(sc);
26329332c0dSAdrian Chadd 		return (-1);
26429332c0dSAdrian Chadd 	}
26529332c0dSAdrian Chadd 	MDIO_UNLOCK(sc);
26629332c0dSAdrian Chadd 
26729332c0dSAdrian Chadd 	return (0);
26829332c0dSAdrian Chadd }
26929332c0dSAdrian Chadd 
27029332c0dSAdrian Chadd static device_method_t qcom_mdio_ipq4018_methods[] = {
27129332c0dSAdrian Chadd 	/* Driver */
27229332c0dSAdrian Chadd 	DEVMETHOD(device_probe, qcom_mdio_ipq4018_probe),
27329332c0dSAdrian Chadd 	DEVMETHOD(device_attach, qcom_mdio_ipq4018_attach),
27429332c0dSAdrian Chadd 	DEVMETHOD(device_detach, qcom_mdio_ipq4018_detach),
27529332c0dSAdrian Chadd 
276*bfed2417SBjoern A. Zeeb 	/* Bus interface */
277*bfed2417SBjoern A. Zeeb 	DEVMETHOD(bus_add_child, bus_generic_add_child),
278*bfed2417SBjoern A. Zeeb 
27929332c0dSAdrian Chadd 	/* MDIO interface */
28029332c0dSAdrian Chadd 	DEVMETHOD(mdio_readreg, qcom_mdio_ipq4018_readreg),
28129332c0dSAdrian Chadd 	DEVMETHOD(mdio_writereg, qcom_mdio_ipq4018_writereg),
28229332c0dSAdrian Chadd 
28329332c0dSAdrian Chadd 	{0, 0},
28429332c0dSAdrian Chadd };
28529332c0dSAdrian Chadd 
28629332c0dSAdrian Chadd static driver_t qcom_mdio_ipq4018_driver = {
28729332c0dSAdrian Chadd 	"mdio",
28829332c0dSAdrian Chadd 	qcom_mdio_ipq4018_methods,
28929332c0dSAdrian Chadd 	sizeof(struct qcom_mdio_ipq4018_softc),
29029332c0dSAdrian Chadd };
29129332c0dSAdrian Chadd 
29229332c0dSAdrian Chadd EARLY_DRIVER_MODULE(qcom_mdio_ipq4018, simplebus, qcom_mdio_ipq4018_driver,
29353e1cbefSJohn Baldwin     NULL, NULL, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_LATE);
29429332c0dSAdrian Chadd 
29529332c0dSAdrian Chadd MODULE_DEPEND(qcom_mdio_ipq4018, ether, 1, 1, 1);
29629332c0dSAdrian Chadd MODULE_DEPEND(qcom_mdio_ipq4018, mdio, 1, 1, 1);
29729332c0dSAdrian Chadd MODULE_DEPEND(qcom_mdio_ipq4018, miibus, 1, 1, 1);
29829332c0dSAdrian Chadd 
29929332c0dSAdrian Chadd MODULE_VERSION(qcom_mdio_ipq4018, 1);
300