qcom_tlmm: add initial gpio/pinmux controller (TLMM)

The qualcomm TLMM (top level mode manager) is their gpio/pinmux hardware
controller.

Although the pinmux is generic enough to use for the IPQ/APQ series
chips, I'm directly calling the IPQ4018 routines to expedite bring-up.

Notably, I'm not yet implementing the interrupt support - it's not
required at this stage of bring-up.

Differential Revision: https://reviews.freebsd.org/D33554
This commit is contained in:
Adrian Chadd 2021-12-18 20:03:40 -08:00
parent 0727f7b520
commit 4abe6533e9
11 changed files with 2290 additions and 0 deletions

View File

@ -8,3 +8,8 @@ arm/qualcomm/qcom_gcc_ipq4018.c optional qcom_gcc_ipq4018
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

View File

@ -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");
}

View File

@ -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__ */

View File

@ -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);

View File

@ -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);
}

View File

@ -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__ */

View File

@ -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__ */

View File

@ -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));
}

View File

@ -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__ */

View File

@ -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);
}

View File

@ -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__ */