Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F107524054
D33554.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
69 KB
Referenced Files
None
Subscribers
None
D33554.diff
View Options
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 <adrian@FreeBSD.org>
+ *
+ * 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 <adrian@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/gpio.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <dev/gpio/gpiobusvar.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/fdt/fdt_pinctrl.h>
+
+#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 <adrian@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/gpio.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <dev/gpio/gpiobusvar.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/fdt/fdt_pinctrl.h>
+
+#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 <adrian@FreeBSD.org>.
+ *
+ * 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 <adrian@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/gpio.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <dev/gpio/gpiobusvar.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/fdt/fdt_pinctrl.h>
+
+#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 <adrian@FreeBSD.org>
+ *
+ * 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 <adrian@FreeBSD.org>.
+ *
+ * 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 <adrian@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/gpio.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <dev/gpio/gpiobusvar.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/fdt/fdt_pinctrl.h>
+
+#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 <adrian@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/gpio.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <dev/gpio/gpiobusvar.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/fdt/fdt_pinctrl.h>
+
+#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 <adrian@FreeBSD.org>.
+ *
+ * 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__ */
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Jan 16, 10:21 AM (20 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
15823941
Default Alt Text
D33554.diff (69 KB)
Attached To
Mode
D33554: qcom_tlmm: add initial gpio/pinmux controller (TLMM)
Attached
Detach File
Event Timeline
Log In to Comment