diff --git a/sys/arm/qualcomm/std.ipq4018 b/sys/arm/qualcomm/std.ipq4018 --- a/sys/arm/qualcomm/std.ipq4018 +++ b/sys/arm/qualcomm/std.ipq4018 @@ -8,3 +8,8 @@ arm/qualcomm/qcom_gcc_ipq4018_reset.c optional qcom_gcc_ipq4018 arm/qualcomm/qcom_gcc_ipq4018_clock.c optional qcom_gcc_ipq4018 +dev/qcom_tlmm/qcom_tlmm_debug.c optional qcom_tlmm_ipq4018 +dev/qcom_tlmm/qcom_tlmm_ipq4018.c optional qcom_tlmm_ipq4018 +dev/qcom_tlmm/qcom_tlmm_ipq4018_hw.c optional qcom_tlmm_ipq4018 +dev/qcom_tlmm/qcom_tlmm_pin.c optional qcom_tlmm_ipq4018 +dev/qcom_tlmm/qcom_tlmm_pinmux.c optional qcom_tlmm_ipq4018 diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_debug.h b/sys/dev/qcom_tlmm/qcom_tlmm_debug.h new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_debug.h @@ -0,0 +1,43 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef __QCOM_TLMM_DEBUG_H__ +#define __QCOM_TLMM_DEBUG_H__ + +#define QCOM_TLMM_DEBUG_PINMUX 0x00000001 + +#define QCOM_TLMM_DPRINTF(sc, flags, ...) \ + do { \ + if ((sc)->sc_debug & flags) \ + device_printf((sc)->dev, __VA_ARGS__); \ + } while (0) + +extern void qcom_tlmm_debug_sysctl_attach(struct qcom_tlmm_softc *sc); + +#endif /* __QCOM_TLMM_DEBUG_H__ */ diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_debug.c b/sys/dev/qcom_tlmm/qcom_tlmm_debug.c new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_debug.c @@ -0,0 +1,66 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "qcom_tlmm_var.h" +#include "qcom_tlmm_debug.h" + +void +qcom_tlmm_debug_sysctl_attach(struct qcom_tlmm_softc *sc) +{ + struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->dev); + struct sysctl_oid *tree = device_get_sysctl_tree(sc->dev); + + SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "debug", CTLFLAG_RW, &sc->sc_debug, 0, + "control debugging printfs"); +} diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018.c b/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018.c new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018.c @@ -0,0 +1,400 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This is a pinmux/gpio controller for the IPQ4018/IPQ4019. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "qcom_tlmm_var.h" +#include "qcom_tlmm_pin.h" +#include "qcom_tlmm_debug.h" + +#include "qcom_tlmm_ipq4018_reg.h" +#include "qcom_tlmm_ipq4018_hw.h" + +#include "gpio_if.h" + +#define DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ + GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN) + +/* 100 GPIO pins, 0..99 */ +#define QCOM_TLMM_IPQ4018_GPIO_PINS 100 + +static const struct qcom_tlmm_gpio_mux gpio_muxes[] = { + GDEF(0, "jtag_tdi", "smart0", "i2s_rx_bclk"), + GDEF(1, "jtag_tck", "smart0", "i2s_rx_fsync"), + GDEF(2, "jtag_tms", "smart0", "i2s_rxd"), + GDEF(3, "jtag_tdo"), + GDEF(4, "jtag_rst"), + GDEF(5, "jtag_trst"), + GDEF(6, "mdio0", NULL, "wcss0_dbg18", "wcss1_dbg18", NULL, + "qdss_tracedata_a"), + GDEF(7, "mdc", NULL, "wcss0_dbg19", "wcss1_dbg19", NULL, + "qdss_tracedata_a"), + GDEF(8, "blsp_uart1", "wifi0_uart", "wifi1_uart", "smart1", NULL, + "wcss0_dbg20", "wcss1_dbg20", NULL, "qdss_tracedata_a"), + GDEF(9, "blsp_uart1", "wifi0_uart0", "wifi1_uart0", "smart1", + "wifi0_uart", NULL, "wcss0_dbg21", "wcss1_dbg21", NULL, + "qdss_tracedata_a"), + + GDEF(10, "blsp_uart1", "wifi0_uart0", "wifi1_uart0", "blsp_i2c0", + NULL, "wcss0_dbg22", "wcss1_dbg22", NULL, "qdss_tracedata_a"), + GDEF(11, "blsp_uart1", "wifi0_uart", "wifi1_uart", "blsp_i2c0", + NULL, "wcss0_dbg23", "wcss1_dbg23", NULL, "qdss_tracedata_a"), + GDEF(12, "blsp_spi0", "blsp_i2c1", NULL, "wcss0_dbg24", + "wcss1_dbg24"), + GDEF(13, "blsp_spi0", "blsp_i2c1", NULL, "wcss0_dbg25", + "wcss1_dbg25"), + GDEF(14, "blsp_spi0", NULL, "wcss0_dbg26", "wcss1_dbg26"), + GDEF(15, "blsp_spi0", NULL, "wcss0_dbg", "wcss1_dbg"), + GDEF(16, "blsp_uart0", "led0", "smart1", NULL, "wcss0_dbg28", + "wcss1_dbg28", NULL, "qdss_tracedata_a"), + GDEF(17, "blsp_uart0", "led1", "smart1", NULL, "wcss0_dbg29", + "wcss1_dbg29", NULL, "qdss_tracedata_a"), + GDEF(18, "wifi0_uart1", "wifi1_uart1", NULL, "wcss0_dbg30", + "wcss1_dbg30"), + GDEF(19, "wifi0_uart", "wifi1_uart", NULL, "wcss0_dbg31", + "wcss1_dbg31"), + + GDEF(20, "blsp_i2c0", "i2s_rx_mclk", NULL, "wcss0_dbg16", + "wcss1_dbg16"), + GDEF(21, "blsp_i2c0", "i2s_rx_bclk", NULL, "wcss0_dbg17", + "wcss1_dbg17"), + GDEF(22, "rgmii0", "i2s_rx_fsync", NULL, "wcss0_dbg18", + "wcss1_dbg18"), + GDEF(23, "sdio0", "rgmii1", "i2s_rxd", NULL, "wcss0_dbg19", + "wcss1_dbg19"), + GDEF(24, "sdio1", "rgmii2", "i2s_tx_mclk", NULL, "wcss0_dbg20", + "wcss1_dbg20"), + GDEF(25, "sdio2", "rgmii3", "i2s_tx_bclk", NULL, "wcss0_dbg21", + "wcss1_dbg21"), + GDEF(26, "sdio3", "rgmii_rx", "i2s_tx_fsync", NULL, "wcss0_dbg22", + "wcss1_dbg22"), + GDEF(27, "sdio_clk", "rgmii_txc", "i2s_tdl", NULL, "wcss0_dbg23", + "wcss1_dbg23"), + GDEF(28, "sdio_cmd", "rgmii0", "i2s_td2", NULL, "wcss0_dbg24", + "wcss1_dbg24"), + GDEF(29, "sdio4", "rgmii1", "i2s_td3", NULL, "wcss0_dbg25", + "wcss1_dbg25"), + + GDEF(30, "sdio5", "rgmii2", "audio_pwm0", NULL, "wcss0_dbg26", + "wcss1_dbg26"), + GDEF(31, "sdio6", "rgmii3", "audio_pwm1", NULL, "wcss0_dbg27", + "wcss1_dbg27"), + GDEF(32, "sdio7", "rgmii_rxc", "audio_pwm2", NULL, "wcss0_dbg28", + "wcss1_dbg28"), + GDEF(33, "rgmii_tx", "audio_pwm3", NULL, "wcss0_dbg29", + "wcss1_dbg29", NULL, "boot2"), + GDEF(34, "blsp_i2c1", "i2s_spdif_in", NULL, "wcss0_dbg30", + "wcss1_dbg30"), + GDEF(35, "blsp_i2c1", "i2s_spdif_out", NULL, "wcss0_dbg31", + "wcss1_dbg31"), + GDEF(36, "rmii00", "led2", "led0"), + GDEF(37, "rmii01", "wifi0_wci", "wifi1_wci", "led1", NULL, NULL, + "wcss0_dbg16", "wcss1_dbg16", NULL, "qdss_tracedata_a", "boot4"), + GDEF(38, "rmii0_tx", "led2", NULL, NULL, "wcss0_dbg17", + "wcss1_dbg17", NULL, "qdss_tracedata_a", "boot5"), + GDEF(39, "rmii0_rx", "pcie_clk1", "led3", NULL, NULL, "wcss0_dbg18", + "wcss1_dbg18", NULL, NULL, "qdss_tracedata_a"), + + GDEF(40, "rmii0_refclk", "wifi0_rfsilent0", "wifi1_rfsilent0", + "smart2", "led4", NULL, NULL, "wcss0_dbg19", "wcss1_dbg19", NULL, + NULL, "qdss_tracedata_a"), + GDEF(41, "rmii00", "wifi0_cal", "wifi1_cal", "smart2", NULL, NULL, + "wcss0_dbg20", "wcss1_dbg20", NULL, NULL, "qdss_tracedata_a"), + GDEF(42, "rmii01", "wifi_wci0", NULL, NULL, "wcss0_dbg21", + "wcss1_dbg21", NULL, NULL, "qdss_tracedata_a"), + GDEF(43, "rmii0_dv", "wifi_wci1", NULL, NULL, "wcss0_dbg22", + "wcss1_dbg22", NULL, NULL, "qdss_tracedata_a"), + GDEF(44, "rmii1_refclk", "blsp_spi1", "smart0", "led5", NULL, NULL, + "wcss0_dbg23", "wcss1_dbg23"), + GDEF(45, "rmii10", "blsp_spi1", "smart0", "led6", NULL, NULL, + "wcss0_dbg24", "wcss1_dbg24"), + GDEF(46, "rmii11", "blsp_spi1", "smart0", "led7", NULL, NULL, + "wcss0_dbg25", "wcss1_dbg25"), + GDEF(47, "rmii1_dv", "blsp_spi1", "smart0", "led8", NULL, NULL, + "wcss0_dbg26", "wcss1_dbg26"), + GDEF(48, "rmii1_tx", "aud_pin", "smart2", "led9", NULL, NULL, + "wcss0_dbg27", "wcss1_dbg27"), + GDEF(49, "rmii1_rx", "aud_pin", "smart2", "led10", NULL, NULL, + "wcss0_dbg28", "wcss1_dbg28"), + + GDEF(50, "rmii10", "aud_pin", "wifi0_rfsilent1", "wifi1_rfsilent1", + "led11", NULL, NULL, "wcss0_dbg29", "wcss1_dbg29"), + GDEF(51, "rmii11", "aud_pin", "wifi0_cal", "wifi1_cal", NULL, NULL, + "wcss0_dbg30", "wcss1_dbg30", NULL, "boot7"), + GDEF(52, "qpic_pad", "mdc", "pcie_clk", "i2s_tx_mclk", NULL, NULL, + "wcss0_dbg31", "tm_clk0", "wifi00", "wifi10"), + GDEF(53, "qpic_pad", "mdio1", "i2s_tx_bclk", "prng_rsoc", "dbg_out", + "tm0", "wifi01", "wifi11"), + GDEF(54, "qpic_pad", "blsp_spi0", "i2s_tdl", "atest_char3", "pmu0", + NULL, NULL, "boot8", "tm1"), + GDEF(55, "qpic_pad", "blsp_spi0", "i2s_td2", "atest_char2", "pmu1", + NULL, NULL, "boot9", "tm2"), + GDEF(56, "qpic_pad", "blsp_spi0", "i2s_td3", "atest_char1", NULL, + "tm_ack", "wifi03", "wifi13"), + GDEF(57, "qpic_pad4", "blsp_spi0", "i2s_tx_fsync", "atest_char0", + NULL, "tm3", "wifi02", "wifi12"), + GDEF(58, "qpic_pad5", "led2", "blsp_i2c0", "smart3", "smart1", + "i2s_rx_mclk", NULL, "wcss0_dbg14", "tm4", "wifi04", "wifi14"), + GDEF(59, "qpic_pad6", "blsp_i2c0", "smart3", "smart1", "i2c_spdif_in", + NULL, NULL, "wcss0_dbg15", "qdss_tracectl_a", "boot18", "tm5" ), + + GDEF(60, "qpic_pad7", "blsp_uart0", "smart1", "smart3", "led0", + "i2s_tx_bclk", "i2s_rx_bclk", "atest_char", NULL, "wcss0_dbg4", + "qdss_traceclk_a", "boot19", "tm6" ), + GDEF(61, "qpic_pad", "blsp_uart0", "smart1", "smart3", "led1", + "i2s_tx_fsync", "i2s_rx_fsync", NULL, NULL, "wcss0_dbg5", + "qdss_cti_trig_out_a0", "boot14", "tm7"), + GDEF(62, "qpic_pad", "chip_rst", "wifi0_uart", "wifi1_uart", + "i2s_spdif_out", NULL, NULL, "wcss0_dbg6", "qdss_cti_trig_out_b0", + "boot11", "tm8"), + GDEF(63, "qpic_pad", "wifi0_uart1", "wifi1_uart1", "wifi1_uart", + "i2s_tdl", "i2s_rxd", "i2s_spdif_out", "i2s_spdif_in", NULL, + "wcss0_dbg7", "wcss1_dbg7", "boot20", "tm9"), + GDEF(64, "qpic_pad1", "audio_pwm0", NULL, "wcss0_dbg8", "wcss1_dbg8"), + GDEF(65, "qpic_pad2", "audio_pwm1", NULL, "wcss0_dbg9", + "wcss1_dbg9" ), + GDEF(66, "qpic_pad3", "audio_pwm2", NULL, "wcss0_dbg10", + "wcss1_dbg10"), + GDEF(67, "qpic_pad0", "audio_pwm3", NULL, "wcss0_dbg11", + "wcss1_dbg11"), + GDEF(68, "qpic_pad8", NULL, "wcss0_dbg12", "wcss1_dbg12"), + GDEF(69, "qpic_pad", NULL, "wcss0_dbg"), + + GDEF(70), + GDEF(71), + GDEF(72), + GDEF(73), + GDEF(74), + GDEF(75), + GDEF(76), + GDEF(77), + GDEF(78), + GDEF(79), + + GDEF(80), + GDEF(81), + GDEF(82), + GDEF(83), + GDEF(84), + GDEF(85), + GDEF(86), + GDEF(87), + GDEF(88), + GDEF(89), + + GDEF(90), + GDEF(91), + GDEF(92), + GDEF(93), + GDEF(94), + GDEF(95), + GDEF(96), + GDEF(97), + GDEF(98, "wifi034", "wifi134"), + GDEF(99), + + GDEF(-1), +}; + +static int +qcom_tlmm_ipq4018_probe(device_t dev) +{ + + if (! ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_is_compatible(dev, "qcom,ipq4019-pinctrl") == 0) + return (ENXIO); + + device_set_desc(dev, + "Qualcomm Atheross TLMM IPQ4018/IPQ4019 GPIO/Pinmux driver"); + return (0); +} + +static int +qcom_tlmm_ipq4018_detach(device_t dev) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + + KASSERT(mtx_initialized(&sc->gpio_mtx), ("gpio mutex not initialized")); + + gpiobus_detach_bus(dev); + if (sc->gpio_ih) + bus_teardown_intr(dev, sc->gpio_irq_res, sc->gpio_ih); + if (sc->gpio_irq_res) + bus_release_resource(dev, SYS_RES_IRQ, sc->gpio_irq_rid, + sc->gpio_irq_res); + if (sc->gpio_mem_res) + bus_release_resource(dev, SYS_RES_MEMORY, sc->gpio_mem_rid, + sc->gpio_mem_res); + if (sc->gpio_pins) + free(sc->gpio_pins, M_DEVBUF); + mtx_destroy(&sc->gpio_mtx); + + return(0); +} + + + +static int +qcom_tlmm_ipq4018_attach(device_t dev) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + int i; + + KASSERT((device_get_unit(dev) == 0), + ("qcom_tlmm_ipq4018: Only one gpio module supported")); + + mtx_init(&sc->gpio_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + /* Map control/status registers. */ + sc->gpio_mem_rid = 0; + sc->gpio_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &sc->gpio_mem_rid, RF_ACTIVE); + + if (sc->gpio_mem_res == NULL) { + device_printf(dev, "couldn't map memory\n"); + qcom_tlmm_ipq4018_detach(dev); + return (ENXIO); + } + + if ((sc->gpio_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &sc->gpio_irq_rid, RF_SHAREABLE | RF_ACTIVE)) == NULL) { + device_printf(dev, "unable to allocate IRQ resource\n"); + qcom_tlmm_ipq4018_detach(dev); + return (ENXIO); + } + + if ((bus_setup_intr(dev, sc->gpio_irq_res, INTR_TYPE_MISC, + qcom_tlmm_filter, qcom_tlmm_intr, sc, &sc->gpio_ih))) { + device_printf(dev, + "WARNING: unable to register interrupt handler\n"); + qcom_tlmm_ipq4018_detach(dev); + return (ENXIO); + } + + sc->dev = dev; + sc->gpio_npins = QCOM_TLMM_IPQ4018_GPIO_PINS; + sc->gpio_muxes = &gpio_muxes[0]; + sc->sc_debug = 0; + + qcom_tlmm_debug_sysctl_attach(sc); + + /* Allocate local pin state for all of our pins */ + sc->gpio_pins = malloc(sizeof(*sc->gpio_pins) * sc->gpio_npins, + M_DEVBUF, M_WAITOK | M_ZERO); + + /* Note: direct map between gpio pin and gpio_pin[] entry */ + for (i = 0; i < sc->gpio_npins; i++) { + snprintf(sc->gpio_pins[i].gp_name, GPIOMAXNAME, + "gpio%d", i); + sc->gpio_pins[i].gp_pin = i; + sc->gpio_pins[i].gp_caps = DEFAULT_CAPS; + (void) qcom_tlmm_pin_getflags(dev, i, + &sc->gpio_pins[i].gp_flags); + } + + fdt_pinctrl_register(dev, NULL); + fdt_pinctrl_configure_by_name(dev, "default"); + + sc->busdev = gpiobus_attach_bus(dev); + if (sc->busdev == NULL) { + device_printf(dev, "%s: failed to attach bus\n", __func__); + qcom_tlmm_ipq4018_detach(dev); + return (ENXIO); + } + + return (0); +} + +static device_method_t qcom_tlmm_ipq4018_methods[] = { + /* Driver */ + DEVMETHOD(device_probe, qcom_tlmm_ipq4018_probe), + DEVMETHOD(device_attach, qcom_tlmm_ipq4018_attach), + DEVMETHOD(device_detach, qcom_tlmm_ipq4018_detach), + + /* GPIO protocol */ + DEVMETHOD(gpio_get_bus, qcom_tlmm_get_bus), + DEVMETHOD(gpio_pin_max, qcom_tlmm_pin_max), + DEVMETHOD(gpio_pin_getname, qcom_tlmm_pin_getname), + DEVMETHOD(gpio_pin_getflags, qcom_tlmm_pin_getflags), + DEVMETHOD(gpio_pin_getcaps, qcom_tlmm_pin_getcaps), + DEVMETHOD(gpio_pin_setflags, qcom_tlmm_pin_setflags), + DEVMETHOD(gpio_pin_get, qcom_tlmm_pin_get), + DEVMETHOD(gpio_pin_set, qcom_tlmm_pin_set), + DEVMETHOD(gpio_pin_toggle, qcom_tlmm_pin_toggle), + + /* OFW */ + DEVMETHOD(ofw_bus_get_node, qcom_tlmm_pin_get_node), + + /* fdt_pinctrl interface */ + DEVMETHOD(fdt_pinctrl_configure, qcom_tlmm_pinctrl_configure), + + {0, 0}, +}; + +static driver_t qcom_tlmm_ipq4018_driver = { + "gpio", + qcom_tlmm_ipq4018_methods, + sizeof(struct qcom_tlmm_softc), +}; +static devclass_t qcom_tlmm_ipq4018_devclass; + + +EARLY_DRIVER_MODULE(qcom_tlmm_ipq4018, simplebus, qcom_tlmm_ipq4018_driver, + qcom_tlmm_ipq4018_devclass, NULL, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); +EARLY_DRIVER_MODULE(qcom_tlmm_ipq4018, ofwbus, qcom_tlmm_ipq4018_driver, + qcom_tlmm_ipq4018_devclass, NULL, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); +MODULE_VERSION(qcom_tlmm_ipq4018, 1); diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018_hw.h b/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018_hw.h new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018_hw.h @@ -0,0 +1,88 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#ifndef __QCOM_TLMM_IPQ4018_HW_H__ +#define __QCOM_TLMM_IPQ4018_HW_H__ + +extern int qcom_tlmm_ipq4018_hw_pin_set_function( + struct qcom_tlmm_softc *sc, int pin, int function); +extern int qcom_tlmm_ipq4018_hw_pin_get_function( + struct qcom_tlmm_softc *sc, int pin, int *function); + +extern int qcom_tlmm_ipq4018_hw_pin_set_oe_output( + struct qcom_tlmm_softc *sc, int pin); +extern int qcom_tlmm_ipq4018_hw_pin_set_oe_input( + struct qcom_tlmm_softc *sc, int pin); +extern int qcom_tlmm_ipq4018_hw_pin_get_oe_state( + struct qcom_tlmm_softc *sc, int pin, bool *is_output); + +extern int qcom_tlmm_ipq4018_hw_pin_set_output_value( + struct qcom_tlmm_softc *sc, + uint32_t pin, unsigned int value); +extern int qcom_tlmm_ipq4018_hw_pin_get_output_value( + struct qcom_tlmm_softc *sc, + uint32_t pin, unsigned int *val); +extern int qcom_tlmm_ipq4018_hw_pin_get_input_value( + struct qcom_tlmm_softc *sc, + uint32_t pin, unsigned int *val); +extern int qcom_tlmm_ipq4018_hw_pin_toggle_output_value( + struct qcom_tlmm_softc *sc, + uint32_t pin); + +extern int qcom_tlmm_ipq4018_hw_pin_set_pupd_config( + struct qcom_tlmm_softc *sc, uint32_t pin, + qcom_tlmm_pin_pupd_config_t pupd); +extern int qcom_tlmm_ipq4018_hw_pin_get_pupd_config( + struct qcom_tlmm_softc *sc, uint32_t pin, + qcom_tlmm_pin_pupd_config_t *pupd); + +extern int qcom_tlmm_ipq4018_hw_pin_set_drive_strength( + struct qcom_tlmm_softc *sc, uint32_t pin, + uint8_t drv); +extern int qcom_tlmm_ipq4018_hw_pin_get_drive_strength( + struct qcom_tlmm_softc *sc, uint32_t pin, + uint8_t *drv); + +extern int qcom_tlmm_ipq4018_hw_pin_set_vm( + struct qcom_tlmm_softc *sc, uint32_t pin, + bool enable); +extern int qcom_tlmm_ipq4018_hw_pin_get_vm( + struct qcom_tlmm_softc *sc, uint32_t pin, + bool *enable); + +extern int qcom_tlmm_ipq4018_hw_pin_set_open_drain( + struct qcom_tlmm_softc *sc, uint32_t pin, + bool enable); +extern int qcom_tlmm_ipq4018_hw_pin_get_open_drain( + struct qcom_tlmm_softc *sc, uint32_t pin, + bool *enable); + +#endif /* __QCOM_TLMM_IPQ4018_HW_H__ */ diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018_hw.c b/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018_hw.c new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018_hw.c @@ -0,0 +1,530 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This is a pinmux/gpio controller for the IPQ4018/IPQ4019. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "qcom_tlmm_var.h" + +#include "qcom_tlmm_ipq4018_reg.h" +#include "qcom_tlmm_ipq4018_hw.h" + +#include "gpio_if.h" + +/* + * Set the pin function. This is a hardware and pin specific mapping. + * + * Returns 0 if OK, an errno if an error was encountered. + */ +int +qcom_tlmm_ipq4018_hw_pin_set_function(struct qcom_tlmm_softc *sc, + int pin, int function) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + reg &= ~(QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_MASK + << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_SHIFT); + reg |= (function & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_MASK) + << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_SHIFT; + GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); + + return (0); +} + +/* + * Get the pin function. This is a hardware and pin specific mapping. + * + * Returns 0 if OK, an errno if a nerror was encountered. + */ +int +qcom_tlmm_ipq4018_hw_pin_get_function(struct qcom_tlmm_softc *sc, + int pin, int *function) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + reg = reg >> QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_SHIFT; + reg &= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_MASK; + *function = reg; + + return (0); +} + +/* + * Set the OE bit to be output. This assumes the port is configured + * as a GPIO port. + */ +int +qcom_tlmm_ipq4018_hw_pin_set_oe_output(struct qcom_tlmm_softc *sc, + int pin) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OE_ENABLE; + GPIO_WRITE(sc, + QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), + reg); + + return (0); +} + +/* + * Set the OE bit to be input. This assumes the port is configured + * as a GPIO port. + */ +int +qcom_tlmm_ipq4018_hw_pin_set_oe_input(struct qcom_tlmm_softc *sc, + int pin) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OE_ENABLE; + GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); + + return (0); +} + +/* + * Get the GPIO pin direction. is_output is set to true if the pin + * is an output pin, false if it's set to an input pin. + */ +int +qcom_tlmm_ipq4018_hw_pin_get_oe_state(struct qcom_tlmm_softc *sc, + int pin, bool *is_output) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + *is_output = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OE_ENABLE); + + return (0); +} + + +/* + * Set the given GPIO pin to the given value. + */ +int +qcom_tlmm_ipq4018_hw_pin_set_output_value(struct qcom_tlmm_softc *sc, + uint32_t pin, unsigned int value) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_IO)); + if (value) + reg |= QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN; + else + reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN; + GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_IO), reg); + + return (0); +} + +/* + * Get the input state of the current GPIO pin. + */ +int +qcom_tlmm_ipq4018_hw_pin_get_output_value(struct qcom_tlmm_softc *sc, + uint32_t pin, unsigned int *val) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_IO)); + + *val = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_IO_INPUT_STATUS); + + return (0); +} + + +/* + * Get the input state of the current GPIO pin. + */ +int +qcom_tlmm_ipq4018_hw_pin_get_input_value(struct qcom_tlmm_softc *sc, + uint32_t pin, unsigned int *val) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_IO)); + + *val = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_IO_INPUT_STATUS); + + return (0); +} + +/* + * Toggle the current output pin value. + */ +int +qcom_tlmm_ipq4018_hw_pin_toggle_output_value( + struct qcom_tlmm_softc *sc, uint32_t pin) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_IO)); + if ((reg & QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN) == 0) + reg |= QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN; + else + reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN; + GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_IO), reg); + + return (0); +} + +/* + * Configure the pull-up / pull-down top-level configuration. + * + * This doesn't configure the resistor values, just what's enabled/disabled. + */ +int +qcom_tlmm_ipq4018_hw_pin_set_pupd_config( + struct qcom_tlmm_softc *sc, uint32_t pin, + qcom_tlmm_pin_pupd_config_t pupd) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + + reg &= ~(QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_MASK + << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT); + + switch (pupd) { + case QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE: + reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_DISABLE + << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; + break; + case QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN: + reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLDOWN + << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; + break; + case QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP: + reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLUP + << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; + break; + case QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD: + reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_BUSHOLD + << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; + break; + } + + GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); + + return (0); +} + +/* + * Fetch the current pull-up / pull-down configuration. + */ +int +qcom_tlmm_ipq4018_hw_pin_get_pupd_config( + struct qcom_tlmm_softc *sc, uint32_t pin, + qcom_tlmm_pin_pupd_config_t *pupd) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + + reg >>= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; + reg &= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_MASK; + + switch (reg) { + case QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_DISABLE: + *pupd = QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE; + break; + case QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLDOWN: + *pupd = QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN; + break; + case QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLUP: + *pupd = QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP; + break; + default: + *pupd = QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE; + break; + } + + return (0); +} + +/* + * Set the drive strength in mA. + */ +int +qcom_tlmm_ipq4018_hw_pin_set_drive_strength( + struct qcom_tlmm_softc *sc, uint32_t pin, uint8_t drv) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + /* Convert mA to hardware */ + if (drv > 16 || drv < 2) + return (EINVAL); + drv = (drv / 2) - 1; + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + + reg &= ~(QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_SHIFT + << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_MASK); + reg |= (drv & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_MASK) + << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_SHIFT; + + GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); + + return (0); +} + +/* + * Get the drive strength in mA. + */ +int +qcom_tlmm_ipq4018_hw_pin_get_drive_strength( + struct qcom_tlmm_softc *sc, uint32_t pin, uint8_t *drv) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + + *drv = (reg >> QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_SHIFT) + & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_MASK; + + *drv = (*drv + 1) * 2; + + return (0); +} + + +/* + * Enable/disable whether this pin is passed through to a VM. + */ +int +qcom_tlmm_ipq4018_hw_pin_set_vm( + struct qcom_tlmm_softc *sc, uint32_t pin, bool enable) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + + reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_VM_ENABLE; + if (enable) + reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_VM_ENABLE; + + GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); + + return (0); +} + +/* + * Get the VM configuration bit. + */ +int +qcom_tlmm_ipq4018_hw_pin_get_vm( + struct qcom_tlmm_softc *sc, uint32_t pin, bool *enable) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + + *enable = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_VM_ENABLE); + + return (0); +} + +/* + * Enable/disable open drain. + */ +int +qcom_tlmm_ipq4018_hw_pin_set_open_drain( + struct qcom_tlmm_softc *sc, uint32_t pin, bool enable) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + + reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OD_ENABLE; + if (enable) + reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OD_ENABLE; + + GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); + + return (0); +} + +/* + * Get the open drain configuration bit. + */ +int +qcom_tlmm_ipq4018_hw_pin_get_open_drain( + struct qcom_tlmm_softc *sc, uint32_t pin, bool *enable) +{ + uint32_t reg; + + GPIO_LOCK_ASSERT(sc); + + if (pin >= sc->gpio_npins) + return (EINVAL); + + reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, + QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); + + *enable = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OD_ENABLE); + + return (0); +} diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018_reg.h b/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018_reg.h new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_ipq4018_reg.h @@ -0,0 +1,85 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef __QCOM_TLMM_IPQ4018_REG_H__ +#define __QCOM_TLMM_IPQ4018_REG_H__ + +/* + * Each GPIO pin configuration block exists in a 0x1000 sized window. + */ +#define QCOM_TLMM_IPQ4018_REG_CONFIG_PIN_BASE 0x0 +#define QCOM_TLMM_IPQ4018_REG_CONFIG_PIN_SIZE 0x1000 + +/* + * Inside each configuration block are the following registers for + * controlling the pin. + */ +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL 0x00 + /* 1 = output gpio pin, 0 = input gpio pin */ + +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_MASK 0x3 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT 0x0 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_DISABLE 0 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLDOWN 1 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLUP 2 + /* There's no BUSHOLD on IPQ4018 */ +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_BUSHOLD 0 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_MASK 0x7 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_SHIFT 2 + /* function/mux control */ +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_SHIFT 6 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_MASK 0x7 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OE_ENABLE (1U << 9) + /* output enable */ +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_VM_ENABLE (1U << 11) + /* VM passthrough enable */ +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OD_ENABLE (1U << 12) + /* open drain */ +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_RES_MASK 0x3 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_RES_SHIFT 13 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_10K 0x0 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_1K5 0x1 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_35K 0x2 +#define QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_20K 0x3 + +#define QCOM_TLMM_IPQ4018_REG_PIN_IO 0x04 +#define QCOM_TLMM_IPQ4018_REG_PIN_IO_INPUT_STATUS (1U << 0) + /* read gpio input status */ +#define QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN (1U << 1) + /* set gpio output high or low */ + + +#define QCOM_TLMM_IPQ4018_REG_PIN_INTR_CONFIG 0x08 +#define QCOM_TLMM_IPQ4018_REG_PIN_INTR_STATUS 0x0c + +#define QCOM_TLMM_IPQ4018_REG_PIN(p, reg) \ + (((p) * QCOM_TLMM_IPQ4018_REG_CONFIG_PIN_SIZE) + \ + QCOM_TLMM_IPQ4018_REG_CONFIG_PIN_BASE + (reg)) + +#endif /* __QCOM_TLMM_IPQ4018_REG_H__ */ diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_pin.h b/sys/dev/qcom_tlmm/qcom_tlmm_pin.h new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_pin.h @@ -0,0 +1,50 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#ifndef __QCOM_TLMM_PIN_H__ +#define __QCOM_TLMM_PIN_H__ + +extern device_t qcom_tlmm_get_bus(device_t dev); +extern int qcom_tlmm_pin_max(device_t dev, int *maxpin); +extern int qcom_tlmm_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps); +extern int qcom_tlmm_pin_getflags(device_t dev, uint32_t pin, + uint32_t *flags); +extern int qcom_tlmm_pin_getname(device_t dev, uint32_t pin, char *name); +extern int qcom_tlmm_pin_setflags(device_t dev, uint32_t pin, + uint32_t flags); +extern int qcom_tlmm_pin_set(device_t dev, uint32_t pin, unsigned int value); +extern int qcom_tlmm_pin_get(device_t dev, uint32_t pin, unsigned int *val); +extern int qcom_tlmm_pin_toggle(device_t dev, uint32_t pin); +extern int qcom_tlmm_filter(void *arg); +extern void qcom_tlmm_intr(void *arg); +extern phandle_t qcom_tlmm_pin_get_node(device_t dev, device_t bus); + +#endif /* __QCOM_TLMM_PIN_H__ */ diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_pin.c b/sys/dev/qcom_tlmm/qcom_tlmm_pin.c new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_pin.c @@ -0,0 +1,322 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "qcom_tlmm_var.h" +#include "qcom_tlmm_pin.h" + +#include "qcom_tlmm_ipq4018_reg.h" +#include "qcom_tlmm_ipq4018_hw.h" + +#include "gpio_if.h" + +static struct gpio_pin * +qcom_tlmm_pin_lookup(struct qcom_tlmm_softc *sc, int pin) +{ + if (pin >= sc->gpio_npins) + return (NULL); + + return &sc->gpio_pins[pin]; +} + +static void +qcom_tlmm_pin_configure(struct qcom_tlmm_softc *sc, + struct gpio_pin *pin, unsigned int flags) +{ + + GPIO_LOCK_ASSERT(sc); + + /* + * Manage input/output + */ + if (flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) { + pin->gp_flags &= ~(GPIO_PIN_INPUT|GPIO_PIN_OUTPUT); + if (flags & GPIO_PIN_OUTPUT) { + /* + * XXX TODO: read GPIO_PIN_PRESET_LOW / + * GPIO_PIN_PRESET_HIGH and if we're a GPIO + * function pin here, set the output + * pin value before we flip on oe_output. + */ + pin->gp_flags |= GPIO_PIN_OUTPUT; + qcom_tlmm_ipq4018_hw_pin_set_oe_output(sc, + pin->gp_pin); + } else { + pin->gp_flags |= GPIO_PIN_INPUT; + qcom_tlmm_ipq4018_hw_pin_set_oe_input(sc, + pin->gp_pin); + } + } + + /* + * Set pull-up / pull-down configuration + */ + if (flags & GPIO_PIN_PULLUP) { + pin->gp_flags |= GPIO_PIN_PULLUP; + qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, pin->gp_pin, + QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP); + } else if (flags & GPIO_PIN_PULLDOWN) { + pin->gp_flags |= GPIO_PIN_PULLDOWN; + qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, pin->gp_pin, + QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN); + } else if ((flags & (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)) == + (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)) { + pin->gp_flags |= GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN; + qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, pin->gp_pin, + QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD); + } else { + pin->gp_flags &= ~(GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN); + qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, pin->gp_pin, + QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE); + } +} + +device_t +qcom_tlmm_get_bus(device_t dev) +{ + struct qcom_tlmm_softc *sc; + + sc = device_get_softc(dev); + + return (sc->busdev); +} + +int +qcom_tlmm_pin_max(device_t dev, int *maxpin) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + + *maxpin = sc->gpio_npins - 1; + return (0); +} + +int +qcom_tlmm_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + struct gpio_pin *p; + + p = qcom_tlmm_pin_lookup(sc, pin); + if (p == NULL) + return (EINVAL); + + GPIO_LOCK(sc); + *caps = p->gp_caps; + GPIO_UNLOCK(sc); + + return (0); +} + +int +qcom_tlmm_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + uint32_t ret = 0, val; + bool is_output; + qcom_tlmm_pin_pupd_config_t pupd_config; + + if (pin >= sc->gpio_npins) + return (EINVAL); + + *flags = 0; + + GPIO_LOCK(sc); + + /* Lookup function - see what it is, whether we're a GPIO line */ + ret = qcom_tlmm_ipq4018_hw_pin_get_function(sc, pin, &val); + if (ret != 0) + goto done; + + /* Lookup input/output state */ + ret = qcom_tlmm_ipq4018_hw_pin_get_oe_state(sc, pin, &is_output); + if (ret != 0) + goto done; + if (is_output) + *flags |= GPIO_PIN_OUTPUT; + else + *flags |= GPIO_PIN_INPUT; + + /* Lookup pull-up / pull-down state */ + ret = qcom_tlmm_ipq4018_hw_pin_get_pupd_config(sc, pin, + &pupd_config); + if (ret != 0) + goto done; + + switch (pupd_config) { + case QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE: + break; + case QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN: + *flags |= GPIO_PIN_PULLDOWN; + break; + case QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP: + *flags |= GPIO_PIN_PULLUP; + break; + case QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD: + *flags |= (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN); + break; + } + +done: + GPIO_UNLOCK(sc); + return (ret); +} + +int +qcom_tlmm_pin_getname(device_t dev, uint32_t pin, char *name) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + struct gpio_pin *p; + + p = qcom_tlmm_pin_lookup(sc, pin); + if (p == NULL) + return (EINVAL); + + GPIO_LOCK(sc); + memcpy(name, p->gp_name, GPIOMAXNAME); + GPIO_UNLOCK(sc); + + return (0); +} + +int +qcom_tlmm_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + struct gpio_pin *p; + + p = qcom_tlmm_pin_lookup(sc, pin); + if (p == NULL) + return (EINVAL); + + GPIO_LOCK(sc); + qcom_tlmm_pin_configure(sc, p, flags); + GPIO_UNLOCK(sc); + + return (0); +} + +int +qcom_tlmm_pin_set(device_t dev, uint32_t pin, unsigned int value) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + int ret; + + if (pin >= sc->gpio_npins) + return (EINVAL); + + GPIO_LOCK(sc); + ret = qcom_tlmm_ipq4018_hw_pin_set_output_value(sc, pin, value); + GPIO_UNLOCK(sc); + + return (0); +} + +int +qcom_tlmm_pin_get(device_t dev, uint32_t pin, unsigned int *val) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + int ret; + + if (pin >= sc->gpio_npins) + return (EINVAL); + + GPIO_LOCK(sc); + ret = qcom_tlmm_ipq4018_hw_pin_get_input_value(sc, pin, val); + GPIO_UNLOCK(sc); + + return (0); +} + +int +qcom_tlmm_pin_toggle(device_t dev, uint32_t pin) +{ + struct qcom_tlmm_softc *sc = device_get_softc(dev); + int ret; + + if (pin >= sc->gpio_npins) + return (EINVAL); + + GPIO_LOCK(sc); + ret = qcom_tlmm_ipq4018_hw_pin_toggle_output_value(sc, pin); + GPIO_UNLOCK(sc); + + return (0); +} + +int +qcom_tlmm_filter(void *arg) +{ + + /* TODO: something useful */ + return (FILTER_STRAY); +} + +void +qcom_tlmm_intr(void *arg) +{ + struct qcom_tlmm_softc *sc = arg; + GPIO_LOCK(sc); + /* TODO: something useful */ + GPIO_UNLOCK(sc); +} + +/* + * ofw bus interface + */ +phandle_t +qcom_tlmm_pin_get_node(device_t dev, device_t bus) +{ + + /* We only have one child, the GPIO bus, which needs our own node. */ + return (ofw_bus_get_node(dev)); +} + diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_pinmux.c b/sys/dev/qcom_tlmm/qcom_tlmm_pinmux.c new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_pinmux.c @@ -0,0 +1,533 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This is the shared pinmux code that the qualcomm SoCs use for their + * specific way of configuring up pins. + * + * For now this does use the IPQ4018 TLMM related softc, but that + * may change as I extend the driver to support multiple kinds of + * qualcomm chipsets in the future. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "qcom_tlmm_var.h" +#include "qcom_tlmm_debug.h" + +/* + * For now we're hard-coded to doing IPQ4018 stuff here, but + * it's not going to be very hard to flip it to being generic. + */ +#include "qcom_tlmm_ipq4018_hw.h" + +#include "gpio_if.h" + +/* Parameters */ +static const struct qcom_tlmm_prop_name prop_names[] = { + { "bias-disable", PIN_ID_BIAS_DISABLE, 0 }, + { "bias-high-impedance", PIN_ID_BIAS_HIGH_IMPEDANCE, 0 }, + { "bias-bus-hold", PIN_ID_BIAS_BUS_HOLD, 0 }, + { "bias-pull-up", PIN_ID_BIAS_PULL_UP, 0 }, + { "bias-pull-down", PIN_ID_BIAS_PULL_DOWN, 0 }, + { "bias-pull-pin-default", PIN_ID_BIAS_PULL_PIN_DEFAULT, 0 }, + { "drive-push-pull", PIN_ID_DRIVE_PUSH_PULL, 0 }, + { "drive-open-drain", PIN_ID_DRIVE_OPEN_DRAIN, 0 }, + { "drive-open-source", PIN_ID_DRIVE_OPEN_SOURCE, 0 }, + { "drive-strength", PIN_ID_DRIVE_STRENGTH, 1 }, + { "input-enable", PIN_ID_INPUT_ENABLE, 0 }, + { "input-disable", PIN_ID_INPUT_DISABLE, 0 }, + { "input-schmitt-enable", PIN_ID_INPUT_SCHMITT_ENABLE, 0 }, + { "input-schmitt-disable", PIN_ID_INPUT_SCHMITT_DISABLE, 0 }, + { "input-debounce", PIN_ID_INPUT_DEBOUNCE, 0 }, + { "power-source", PIN_ID_POWER_SOURCE, 0 }, + { "slew-rate", PIN_ID_SLEW_RATE, 0}, + { "low-power-enable", PIN_ID_LOW_POWER_MODE_ENABLE, 0 }, + { "low-power-disable", PIN_ID_LOW_POWER_MODE_DISABLE, 0 }, + { "output-low", PIN_ID_OUTPUT_LOW, 0, }, + { "output-high", PIN_ID_OUTPUT_HIGH, 0, }, + { "vm-enable", PIN_ID_VM_ENABLE, 0, }, + { "vm-disable", PIN_ID_VM_DISABLE, 0, }, +}; + +static const struct qcom_tlmm_spec_pin * +qcom_tlmm_pinctrl_search_spin(struct qcom_tlmm_softc *sc, char *pin_name) +{ + int i; + + if (sc->spec_pins == NULL) + return (NULL); + + for (i = 0; sc->spec_pins[i].name != NULL; i++) { + if (strcmp(pin_name, sc->spec_pins[i].name) == 0) + return (&sc->spec_pins[i]); + } + + return (NULL); +} + +static int +qcom_tlmm_pinctrl_config_spin(struct qcom_tlmm_softc *sc, + char *pin_name, const struct qcom_tlmm_spec_pin *spin, + struct qcom_tlmm_pinctrl_cfg *cfg) +{ + /* XXX TODO */ + device_printf(sc->dev, "%s: TODO: called; pin_name=%s\n", + __func__, pin_name); + return (0); +} + +static const struct qcom_tlmm_gpio_mux * +qcom_tlmm_pinctrl_search_gmux(struct qcom_tlmm_softc *sc, char *pin_name) +{ + int i; + + if (sc->gpio_muxes == NULL) + return (NULL); + + for (i = 0; sc->gpio_muxes[i].id >= 0; i++) { + if (strcmp(pin_name, sc->gpio_muxes[i].name) == 0) + return (&sc->gpio_muxes[i]); + } + + return (NULL); +} + +static int +qcom_tlmm_pinctrl_gmux_function(const struct qcom_tlmm_gpio_mux *gmux, + char *fnc_name) +{ + int i; + + for (i = 0; i < 16; i++) { /* XXX size */ + if ((gmux->functions[i] != NULL) && + (strcmp(fnc_name, gmux->functions[i]) == 0)) + return (i); + } + + return (-1); +} + +static int +qcom_tlmm_pinctrl_read_node(struct qcom_tlmm_softc *sc, + phandle_t node, struct qcom_tlmm_pinctrl_cfg *cfg, char **pins, + int *lpins) +{ + int rv, i; + + *lpins = OF_getprop_alloc(node, "pins", (void **)pins); + if (*lpins <= 0) + return (ENOENT); + + /* Read function (mux) settings. */ + rv = OF_getprop_alloc(node, "function", (void **)&cfg->function); + if (rv <= 0) + cfg->function = NULL; + + /* + * Read the rest of the properties. + * + * Properties that are a flag are simply present with a value of 0. + * Properties that have arguments have have_value set to 1, and + * we will parse an argument out for it to use. + * + * Properties that were not found/parsed with have a value of -1 + * and thus we won't program them into the hardware. + */ + for (i = 0; i < PROP_ID_MAX_ID; i++) { + rv = OF_getencprop(node, prop_names[i].name, &cfg->params[i], + sizeof(cfg->params[i])); + if (prop_names[i].have_value) { + if (rv == 0) { + device_printf(sc->dev, + "WARNING: Missing value for propety" + " \"%s\"\n", + prop_names[i].name); + cfg->params[i] = 0; + } + } else { + /* No value, default to 0 */ + cfg->params[i] = 0; + } + if (rv < 0) + cfg->params[i] = -1; + } + return (0); +} + +static int +qcom_tlmm_pinctrl_config_gmux(struct qcom_tlmm_softc *sc, char *pin_name, + const struct qcom_tlmm_gpio_mux *gmux, struct qcom_tlmm_pinctrl_cfg *cfg) +{ + int err = 0, i; + + QCOM_TLMM_DPRINTF(sc, QCOM_TLMM_DEBUG_PINMUX, + "%s: called; pin=%s, function %s\n", + __func__, pin_name, cfg->function); + + GPIO_LOCK(sc); + + /* + * Lookup the function in the configuration table. Configure it + * if required. + */ + if (cfg->function != NULL) { + uint32_t tmp; + + tmp = qcom_tlmm_pinctrl_gmux_function(gmux, cfg->function); + if (tmp == -1) { + device_printf(sc->dev, + "%s: pin=%s, function=%s, unknown!\n", + __func__, + pin_name, + cfg->function); + err = EINVAL; + goto done; + } + + /* + * Program in the given function to the given pin. + */ + QCOM_TLMM_DPRINTF(sc, QCOM_TLMM_DEBUG_PINMUX, + "%s: pin id=%u, new function=%u\n", + __func__, + gmux->id, + tmp); + err = qcom_tlmm_ipq4018_hw_pin_set_function(sc, gmux->id, + tmp); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set function (%d)\n", + __func__, gmux->id, err); + goto done; + } + } + + /* + * Iterate the set of properties; call the relevant method + * if we need to change it. + */ + for (i = 0; i < PROP_ID_MAX_ID; i++) { + if (cfg->params[i] == -1) + continue; + QCOM_TLMM_DPRINTF(sc, QCOM_TLMM_DEBUG_PINMUX, + "%s: pin_id=%u, param=%d, val=%d\n", + __func__, + gmux->id, + i, + cfg->params[i]); + switch (i) { + case PIN_ID_BIAS_DISABLE: + err = qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, + gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set pupd(DISABLE):" + " %d\n", + __func__, gmux->id, err); + goto done; + } + break; + case PIN_ID_BIAS_PULL_DOWN: + err = qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, + gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set pupd(PD):" + " %d\n", + __func__, gmux->id, err); + goto done; + } + break; + case PIN_ID_BIAS_BUS_HOLD: + err = qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, + gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set pupd(HOLD):" + " %d\n", + __func__, gmux->id, err); + goto done; + } + break; + + case PIN_ID_BIAS_PULL_UP: + err = qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, + gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set pupd(PU):" + " %d\n", + __func__, gmux->id, err); + goto done; + } + break; + case PIN_ID_OUTPUT_LOW: + err = qcom_tlmm_ipq4018_hw_pin_set_oe_output(sc, + gmux->id); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set OE:" + " %d\n", + __func__, gmux->id, err); + goto done; + } + err = qcom_tlmm_ipq4018_hw_pin_set_output_value( + sc, gmux->id, 0); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set output value:" + " %d\n", + __func__, gmux->id, err); + goto done; + } + break; + case PIN_ID_OUTPUT_HIGH: + err = qcom_tlmm_ipq4018_hw_pin_set_oe_output(sc, + gmux->id); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set OE:" + " %d\n", + __func__, gmux->id, err); + goto done; + } + err = qcom_tlmm_ipq4018_hw_pin_set_output_value( + sc, gmux->id, 1); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set output value:" + " %d\n", + __func__, gmux->id, err); + goto done; + } + break; + case PIN_ID_DRIVE_STRENGTH: + err = qcom_tlmm_ipq4018_hw_pin_set_drive_strength(sc, + gmux->id, cfg->params[i]); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set drive" + " strength %d (%d)\n", + __func__, gmux->id, + cfg->params[i], err); + goto done; + } + break; + case PIN_ID_VM_ENABLE: + err = qcom_tlmm_ipq4018_hw_pin_set_vm(sc, + gmux->id, true); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set VM enable:" + " %d\n", + __func__, gmux->id, err); + goto done; + } + break; + case PIN_ID_VM_DISABLE: + err = qcom_tlmm_ipq4018_hw_pin_set_vm(sc, + gmux->id, false); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set VM disable:" + " %d\n", + __func__, gmux->id, err); + goto done; + } + break; + case PIN_ID_DRIVE_OPEN_DRAIN: + err = qcom_tlmm_ipq4018_hw_pin_set_open_drain(sc, + gmux->id, true); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set open drain" + " (%d)\n", + __func__, gmux->id, err); + goto done; + } + break; + case PIN_ID_INPUT_ENABLE: + /* Configure pin as an input */ + err = qcom_tlmm_ipq4018_hw_pin_set_oe_input(sc, + gmux->id); + if (err != 0) { + device_printf(sc->dev, + "%s: pin=%d: failed to set pin as input" + " (%d)\n", + __func__, gmux->id, err); + goto done; + } + break; + case PIN_ID_INPUT_DISABLE: + /* + * the linux-msm GPIO driver treats this as an error; + * a pin should be configured as an output instead. + */ + err = ENXIO; + goto done; + break; + case PIN_ID_BIAS_HIGH_IMPEDANCE: + case PIN_ID_INPUT_SCHMITT_ENABLE: + case PIN_ID_INPUT_SCHMITT_DISABLE: + case PIN_ID_INPUT_DEBOUNCE: + case PIN_ID_SLEW_RATE: + case PIN_ID_LOW_POWER_MODE_ENABLE: + case PIN_ID_LOW_POWER_MODE_DISABLE: + case PIN_ID_BIAS_PULL_PIN_DEFAULT: + case PIN_ID_DRIVE_PUSH_PULL: + case PIN_ID_DRIVE_OPEN_SOURCE: + case PIN_ID_POWER_SOURCE: + default: + device_printf(sc->dev, + "%s: ERROR: unknown/unsupported param: " + " pin_id=%u, param=%d, val=%d\n", + __func__, + gmux->id, + i, + cfg->params[i]); + err = ENXIO; + goto done; + + } + } +done: + GPIO_UNLOCK(sc); + return (0); +} + + +static int +qcom_tlmm_pinctrl_config_node(struct qcom_tlmm_softc *sc, + char *pin_name, struct qcom_tlmm_pinctrl_cfg *cfg) +{ + const struct qcom_tlmm_gpio_mux *gmux; + const struct qcom_tlmm_spec_pin *spin; + int rv; + + /* Handle GPIO pins */ + gmux = qcom_tlmm_pinctrl_search_gmux(sc, pin_name); + + if (gmux != NULL) { + rv = qcom_tlmm_pinctrl_config_gmux(sc, pin_name, gmux, cfg); + return (rv); + } + /* Handle special pin groups */ + spin = qcom_tlmm_pinctrl_search_spin(sc, pin_name); + if (spin != NULL) { + rv = qcom_tlmm_pinctrl_config_spin(sc, pin_name, spin, cfg); + return (rv); + } + device_printf(sc->dev, "Unknown pin: %s\n", pin_name); + return (ENXIO); +} + +static int +qcom_tlmm_pinctrl_process_node(struct qcom_tlmm_softc *sc, + phandle_t node) +{ + struct qcom_tlmm_pinctrl_cfg cfg; + char *pins, *pname; + int i, len, lpins, rv; + + /* + * Read the configuration and list of pins for the given node to + * configure. + */ + rv = qcom_tlmm_pinctrl_read_node(sc, node, &cfg, &pins, &lpins); + if (rv != 0) + return (rv); + + len = 0; + pname = pins; + do { + i = strlen(pname) + 1; + /* + * Configure the given node with the specific configuration. + */ + rv = qcom_tlmm_pinctrl_config_node(sc, pname, &cfg); + if (rv != 0) + device_printf(sc->dev, + "Cannot configure pin: %s: %d\n", pname, rv); + + len += i; + pname += i; + } while (len < lpins); + + if (pins != NULL) + free(pins, M_OFWPROP); + if (cfg.function != NULL) + free(cfg.function, M_OFWPROP); + + return (rv); +} + +int +qcom_tlmm_pinctrl_configure(device_t dev, phandle_t cfgxref) +{ + struct qcom_tlmm_softc *sc; + phandle_t node, cfgnode; + int rv; + + sc = device_get_softc(dev); + cfgnode = OF_node_from_xref(cfgxref); + + for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) { + if (!ofw_bus_node_status_okay(node)) + continue; + rv = qcom_tlmm_pinctrl_process_node(sc, node); + if (rv != 0) + device_printf(dev, "Pin config failed: %d\n", rv); + } + + return (0); +} + diff --git a/sys/dev/qcom_tlmm/qcom_tlmm_var.h b/sys/dev/qcom_tlmm/qcom_tlmm_var.h new file mode 100644 --- /dev/null +++ b/sys/dev/qcom_tlmm/qcom_tlmm_var.h @@ -0,0 +1,168 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Adrian Chadd . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#ifndef __QCOM_TLMM_VAR_H__ +#define __QCOM_TLMM_VAR_H__ + +#define GPIO_LOCK(_sc) mtx_lock(&(_sc)->gpio_mtx) +#define GPIO_UNLOCK(_sc) mtx_unlock(&(_sc)->gpio_mtx) +#define GPIO_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->gpio_mtx, MA_OWNED) + +/* + * register space access macros + */ +#define GPIO_WRITE(sc, reg, val) do { \ + bus_write_4(sc->gpio_mem_res, (reg), (val)); \ + } while (0) + +#define GPIO_READ(sc, reg) bus_read_4(sc->gpio_mem_res, (reg)) + +#define GPIO_SET_BITS(sc, reg, bits) \ + GPIO_WRITE(sc, reg, GPIO_READ(sc, (reg)) | (bits)) + +#define GPIO_CLEAR_BITS(sc, reg, bits) \ + GPIO_WRITE(sc, reg, GPIO_READ(sc, (reg)) & ~(bits)) + + +enum prop_id { + PIN_ID_BIAS_DISABLE = 0, + PIN_ID_BIAS_HIGH_IMPEDANCE, + PIN_ID_BIAS_BUS_HOLD, + PIN_ID_BIAS_PULL_UP, + PIN_ID_BIAS_PULL_DOWN, + PIN_ID_BIAS_PULL_PIN_DEFAULT, + PIN_ID_DRIVE_PUSH_PULL, + PIN_ID_DRIVE_OPEN_DRAIN, + PIN_ID_DRIVE_OPEN_SOURCE, + PIN_ID_DRIVE_STRENGTH, + PIN_ID_INPUT_ENABLE, + PIN_ID_INPUT_DISABLE, + PIN_ID_INPUT_SCHMITT_ENABLE, + PIN_ID_INPUT_SCHMITT_DISABLE, + PIN_ID_INPUT_DEBOUNCE, + PIN_ID_POWER_SOURCE, + PIN_ID_SLEW_RATE, + PIN_ID_LOW_POWER_MODE_ENABLE, + PIN_ID_LOW_POWER_MODE_DISABLE, + PIN_ID_OUTPUT_LOW, + PIN_ID_OUTPUT_HIGH, + PIN_ID_VM_ENABLE, + PIN_ID_VM_DISABLE, + PROP_ID_MAX_ID +}; + +struct qcom_tlmm_prop_name { + const char *name; + enum prop_id id; + int have_value; +}; + +/* + * Pull-up / pull-down configuration. + */ +typedef enum { + QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE = 0, + QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN = 1, + QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP = 2, + QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD = 3, +} qcom_tlmm_pin_pupd_config_t; + + +/* + * Pull-up / pull-down resistor configuration. + */ +typedef enum { + QCOM_TLMM_PIN_RESISTOR_PUPD_CONFIG_10K = 0, + QCOM_TLMM_PIN_RESISTOR_PUPD_CONFIG_1K5 = 1, + QCOM_TLMM_PIN_RESISTOR_PUPD_CONFIG_35K = 2, + QCOM_TLMM_PIN_RESISTOR_PUPD_CONFIG_20K = 3, +} qcom_tlmm_pin_resistor_pupd_config_t; + +/* + * configuration for one pin group. + */ +struct qcom_tlmm_pinctrl_cfg { + char *function; + int params[PROP_ID_MAX_ID]; +}; + +#define GDEF(_id, ...) \ +{ \ + .id = _id, \ + .name = "gpio" #_id, \ + .functions = {"gpio", __VA_ARGS__} \ +} + +struct qcom_tlmm_gpio_mux { + int id; + char *name; + char *functions[16]; /* XXX */ +}; + +#define SDEF(n, r, ps, hs...) \ +{ \ + .name = n, \ + .reg = r, \ + .pull_shift = ps, \ + .hdrv_shift = hs, \ +} + + +struct qcom_tlmm_spec_pin { + char *name; + uint32_t reg; + uint32_t pull_shift; + uint32_t hdrv_shift; +}; + +struct qcom_tlmm_softc { + device_t dev; + device_t busdev; + struct mtx gpio_mtx; + struct resource *gpio_mem_res; + int gpio_mem_rid; + struct resource *gpio_irq_res; + int gpio_irq_rid; + void *gpio_ih; + int gpio_npins; + struct gpio_pin *gpio_pins; + uint32_t sc_debug; + + const struct qcom_tlmm_gpio_mux *gpio_muxes; + const struct qcom_tlmm_spec_pin *spec_pins; +}; + +/* + * qcom_tlmm_pinmux.c + */ +extern int qcom_tlmm_pinctrl_configure(device_t dev, phandle_t cfgxref); + +#endif /* __QCOM_TLMM_PINMUX_VAR_H__ */