git: 0a91888870e2 - main - arm64: add a GPIO driver for Apple Silicon

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Thu, 03 Apr 2025 01:11:40 UTC
The branch main has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=0a91888870e2227d3b3576aaa949764929550b94

commit 0a91888870e2227d3b3576aaa949764929550b94
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2025-04-03 01:11:26 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2025-04-03 01:11:33 +0000

    arm64: add a GPIO driver for Apple Silicon
    
    This is a ported version of OpenBSD's work, modulo interrupt
    functionality.  We won't need GPIO interrupts until we start to get
    closer to audio support, and the existing version is sufficient for,
    e.g., pcie.
    
    Reviewed by:    andrew
    Differential Revision:  https://reviews.freebsd.org/D49630
---
 sys/arm64/apple/apple_pinctrl.c | 469 ++++++++++++++++++++++++++++++++++++++++
 sys/conf/files.arm64            |   1 +
 2 files changed, 470 insertions(+)

diff --git a/sys/arm64/apple/apple_pinctrl.c b/sys/arm64/apple/apple_pinctrl.c
new file mode 100644
index 000000000000..ec2dd5907024
--- /dev/null
+++ b/sys/arm64/apple/apple_pinctrl.c
@@ -0,0 +1,469 @@
+/*	$OpenBSD: aplpinctrl.c,v 1.4 2022/04/06 18:59:26 naddy Exp $	*/
+/*
+ * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org>
+ * Copyright (c) 2022 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+
+#include <machine/bus.h>
+#include <machine/intr.h>
+#include <machine/resource.h>
+
+#include <dev/gpio/gpiobusvar.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/fdt/fdt_pinctrl.h>
+
+#include "pic_if.h"
+#include "gpio_if.h"
+
+#define APPLE_PIN(pinmux) ((pinmux) & 0xffff)
+#define APPLE_FUNC(pinmux) ((pinmux) >> 16)
+
+#define GPIO_PIN(pin)		((pin) * 4)
+#define  GPIO_PIN_GROUP_MASK	(7 << 16)
+#define  GPIO_PIN_INPUT_ENABLE	(1 << 9)
+#define  GPIO_PIN_FUNC_MASK	(3 << 5)
+#define  GPIO_PIN_FUNC_SHIFT	5
+#define  GPIO_PIN_MODE_MASK	(7 << 1)
+#define  GPIO_PIN_MODE_INPUT	(0 << 1)
+#define  GPIO_PIN_MODE_OUTPUT	(1 << 1)
+#define  GPIO_PIN_MODE_IRQ_HI	(2 << 1)
+#define  GPIO_PIN_MODE_IRQ_LO	(3 << 1)
+#define  GPIO_PIN_MODE_IRQ_UP	(4 << 1)
+#define  GPIO_PIN_MODE_IRQ_DN	(5 << 1)
+#define  GPIO_PIN_MODE_IRQ_ANY	(6 << 1)
+#define  GPIO_PIN_MODE_IRQ_OFF	(7 << 1)
+#define  GPIO_PIN_DATA		(1 << 0)
+#define GPIO_IRQ(grp, pin)	(0x800 + (grp) * 64 + ((pin) >> 5) * 4)
+
+#define	APPLE_PINCTRL_DEFAULT_CAPS	\
+	(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)
+
+#define HREAD4(sc, reg)							\
+	bus_read_4((sc)->sc_res[APPLE_PINCTRL_MEMRES], reg)
+#define HWRITE4(sc, reg, val)						\
+	bus_write_4((sc)->sc_res[APPLE_PINCTRL_MEMRES], reg, val)
+#define HSET4(sc, reg, bits)						\
+	HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
+#define HCLR4(sc, reg, bits)						\
+	HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
+
+struct apple_pinctrl_irqsrc {
+	struct intr_irqsrc	isrc;
+	int			irq;
+	int			type;
+};
+
+enum {
+	APPLE_PINCTRL_MEMRES = 0,
+	APPLE_PINCTRL_IRQRES,
+	APPLE_PINCTRL_NRES,
+};
+
+struct apple_pinctrl_softc {
+	device_t		sc_dev;
+	device_t		sc_busdev;
+	struct mtx		sc_mtx;
+	int			sc_ngpios;
+
+	void			*sc_intrhand;
+	struct resource		*sc_res[APPLE_PINCTRL_NRES];
+	struct apple_pinctrl_irqsrc	*sc_irqs;
+};
+
+#define	APPLE_PINCTRL_LOCK(sc)		mtx_lock_spin(&(sc)->sc_mtx)
+#define	APPLE_PINCTRL_UNLOCK(sc)	mtx_unlock_spin(&(sc)->sc_mtx)
+#define	APPLE_PINCTRL_LOCK_ASSERT(sc)	mtx_assert(&(sc)->sc_mtx, MA_OWNED)
+
+static struct ofw_compat_data compat_data[] = {
+	{"apple,pinctrl",	1},
+	{NULL,			0},
+};
+
+static struct resource_spec apple_pinctrl_res_spec[] = {
+	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
+	{ SYS_RES_IRQ,		0,	RF_ACTIVE },
+	{ -1,			0,	0 },
+};
+
+static int	apple_pinctrl_probe(device_t dev);
+static int	apple_pinctrl_attach(device_t dev);
+static int	apple_pinctrl_detach(device_t dev);
+
+static int	apple_pinctrl_configure(device_t, phandle_t);
+static phandle_t	apple_pinctrl_get_node(device_t, device_t);
+
+static int
+apple_pinctrl_probe(device_t dev)
+{
+
+	if (!ofw_bus_status_okay(dev))
+		return (ENXIO);
+
+	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+		return (ENXIO);
+
+	device_set_desc(dev, "Apple Pinmux Controller");
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+apple_pinctrl_attach(device_t dev)
+{
+	pcell_t gpio_ranges[4];
+	phandle_t node;
+	struct apple_pinctrl_softc *sc;
+	int error;
+
+	sc = device_get_softc(dev);
+	sc->sc_dev = dev;
+
+	node = ofw_bus_get_node(dev);
+
+	if (bus_alloc_resources(dev, apple_pinctrl_res_spec, sc->sc_res) != 0) {
+		device_printf(dev, "cannot allocate device resources\n");
+		return (ENXIO);
+	}
+
+	mtx_init(&sc->sc_mtx, "aapl gpio", "gpio", MTX_SPIN);
+
+	error = OF_getencprop(node, "gpio-ranges", gpio_ranges,
+	    sizeof(gpio_ranges));
+	if (error == -1) {
+		device_printf(dev, "failed to get gpio-ranges\n");
+		goto error;
+	}
+
+	sc->sc_ngpios = gpio_ranges[3];
+	if (sc->sc_ngpios == 0) {
+		device_printf(dev, "no GPIOs\n");
+		goto error;
+	}
+
+	sc->sc_busdev = gpiobus_attach_bus(dev);
+	if (sc->sc_busdev == NULL) {
+		device_printf(dev, "failed to attach gpiobus\n");
+		goto error;
+	}
+
+	fdt_pinctrl_register(dev, "pinmux");
+	fdt_pinctrl_configure_tree(dev);
+
+	if (!OF_hasprop(node, "interrupt-controller"))
+		return (0);
+
+	sc->sc_irqs = mallocarray(sc->sc_ngpios,
+	    sizeof(*sc->sc_irqs), M_DEVBUF, M_ZERO | M_WAITOK);
+	intr_pic_register(dev, OF_xref_from_node(ofw_bus_get_node(dev)));
+
+	return (0);
+error:
+	mtx_destroy(&sc->sc_mtx);
+	bus_release_resources(dev, apple_pinctrl_res_spec, sc->sc_res);
+	return (ENXIO);
+}
+
+static int
+apple_pinctrl_detach(device_t dev)
+{
+
+	return (EBUSY);
+}
+
+static void
+apple_pinctrl_pin_configure(struct apple_pinctrl_softc *sc, uint32_t pin,
+    uint32_t flags)
+{
+	uint32_t reg;
+
+	APPLE_PINCTRL_LOCK_ASSERT(sc);
+
+	MPASS(pin < sc->sc_ngpios);
+
+	reg = HREAD4(sc, GPIO_PIN(pin));
+	reg &= ~GPIO_PIN_FUNC_MASK;
+	reg &= ~GPIO_PIN_MODE_MASK;
+
+	if ((flags & GPIO_PIN_PRESET_LOW) != 0)
+		reg &= ~GPIO_PIN_DATA;
+	else if ((flags & GPIO_PIN_PRESET_HIGH) != 0)
+		reg |= GPIO_PIN_DATA;
+
+	if ((flags & GPIO_PIN_INPUT) != 0)
+		reg |= GPIO_PIN_MODE_INPUT;
+	else if ((flags & GPIO_PIN_OUTPUT) != 0)
+		reg |= GPIO_PIN_MODE_OUTPUT;
+
+	HWRITE4(sc, GPIO_PIN(pin), reg);
+}
+
+static device_t
+apple_pinctrl_get_bus(device_t dev)
+{
+	struct apple_pinctrl_softc *sc;
+
+	sc = device_get_softc(dev);
+	return (sc->sc_busdev);
+}
+
+static int
+apple_pinctrl_pin_max(device_t dev, int *maxpin)
+{
+	struct apple_pinctrl_softc *sc;
+
+	sc = device_get_softc(dev);
+	*maxpin = sc->sc_ngpios - 1;
+	return (0);
+}
+
+static int
+apple_pinctrl_pin_getname(device_t dev, uint32_t pin, char *name)
+{
+	struct apple_pinctrl_softc *sc;
+
+	sc = device_get_softc(dev);
+	if (pin >= sc->sc_ngpios)
+		return (EINVAL);
+
+	snprintf(name, GPIOMAXNAME - 1, "gpio%c%d",
+	    device_get_unit(dev) + 'a', pin);
+
+	return (0);
+}
+
+static int
+apple_pinctrl_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)
+{
+	struct apple_pinctrl_softc *sc;
+	uint32_t reg;
+
+	sc = device_get_softc(dev);
+	if (pin >= sc->sc_ngpios)
+		return (EINVAL);
+
+	*flags = 0;
+
+	APPLE_PINCTRL_LOCK(sc);
+
+	reg = HREAD4(sc, GPIO_PIN(pin));
+	if ((reg & GPIO_PIN_MODE_INPUT) != 0)
+		*flags |= GPIO_PIN_INPUT;
+	else if ((reg & GPIO_PIN_MODE_OUTPUT) != 0)
+		*flags |= GPIO_PIN_OUTPUT;
+
+	APPLE_PINCTRL_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+apple_pinctrl_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
+{
+
+	*caps = APPLE_PINCTRL_DEFAULT_CAPS;
+	return (0);
+}
+
+static int
+apple_pinctrl_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
+{
+	struct apple_pinctrl_softc *sc;
+
+	sc = device_get_softc(dev);
+	if (pin >= sc->sc_ngpios)
+		return (EINVAL);
+
+	APPLE_PINCTRL_LOCK(sc);
+	apple_pinctrl_pin_configure(sc, pin, flags);
+	APPLE_PINCTRL_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+apple_pinctrl_pin_get(device_t dev, uint32_t pin, unsigned int *val)
+{
+	struct apple_pinctrl_softc *sc;
+	uint32_t reg;
+
+	sc = device_get_softc(dev);
+	if (pin >= sc->sc_ngpios)
+		return (EINVAL);
+
+	APPLE_PINCTRL_LOCK(sc);
+	reg = HREAD4(sc, GPIO_PIN(pin));
+	*val = !!(reg & GPIO_PIN_DATA);
+	APPLE_PINCTRL_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+apple_pinctrl_pin_set(device_t dev, uint32_t pin, unsigned int value)
+{
+	struct apple_pinctrl_softc *sc;
+
+	sc = device_get_softc(dev);
+	if (pin >= sc->sc_ngpios)
+		return (EINVAL);
+
+	APPLE_PINCTRL_LOCK(sc);
+	if (value)
+		HSET4(sc, GPIO_PIN(pin), GPIO_PIN_DATA);
+	else
+		HCLR4(sc, GPIO_PIN(pin), GPIO_PIN_DATA);
+	device_printf(sc->sc_dev, "set pin %d to %x\n",
+	    pin, HREAD4(sc, GPIO_PIN(pin)));
+	APPLE_PINCTRL_UNLOCK(sc);
+	return (0);
+}
+
+
+static int
+apple_pinctrl_pin_toggle(device_t dev, uint32_t pin)
+{
+	struct apple_pinctrl_softc *sc;
+	uint32_t reg;
+
+	sc = device_get_softc(dev);
+	if (pin >= sc->sc_ngpios)
+		return (EINVAL);
+
+	APPLE_PINCTRL_LOCK(sc);
+	reg = HREAD4(sc, GPIO_PIN(pin));
+	if ((reg & GPIO_PIN_DATA) == 0)
+		reg |= GPIO_PIN_DATA;
+	else
+		reg &= ~GPIO_PIN_DATA;
+	HWRITE4(sc, GPIO_PIN(pin), reg);
+	APPLE_PINCTRL_UNLOCK(sc);
+	return (0);
+}
+
+
+static int
+apple_pinctrl_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins,
+    uint32_t *pin_flags)
+{
+	struct apple_pinctrl_softc *sc;
+	uint32_t pin;
+
+	sc = device_get_softc(dev);
+	if (first_pin >= sc->sc_ngpios)
+		return (EINVAL);
+
+	/*
+	 * The configuration for a bank of pins is scattered among several
+	 * registers; we cannot g'tee to simultaneously change the state of all
+	 * the pins in the flags array.  So just loop through the array
+	 * configuring each pin for now.  If there was a strong need, it might
+	 * be possible to support some limited simultaneous config, such as
+	 * adjacent groups of 8 pins that line up the same as the config regs.
+	 */
+	APPLE_PINCTRL_LOCK(sc);
+	for (pin = first_pin; pin < num_pins; ++pin) {
+		if (pin_flags[pin] & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT))
+			apple_pinctrl_pin_configure(sc, pin, pin_flags[pin]);
+	}
+	APPLE_PINCTRL_UNLOCK(sc);
+
+	return (0);
+}
+
+static phandle_t
+apple_pinctrl_get_node(device_t dev, device_t bus)
+{
+
+	/* GPIO bus */
+	return (ofw_bus_get_node(dev));
+}
+
+static int
+apple_pinctrl_configure(device_t dev, phandle_t cfgxref)
+{
+	struct apple_pinctrl_softc *sc;
+	pcell_t *pinmux;
+	phandle_t node;
+	ssize_t len;
+	uint32_t reg;
+	uint16_t pin, func;
+	int i;
+
+	sc = device_get_softc(dev);
+	node = OF_node_from_xref(cfgxref);
+
+	len = OF_getencprop_alloc(node, "pinmux", (void **)&pinmux);
+	if (len <= 0)
+		return (-1);
+
+	APPLE_PINCTRL_LOCK(sc);
+	for (i = 0; i < len / sizeof(pcell_t); i++) {
+		pin = APPLE_PIN(pinmux[i]);
+		func = APPLE_FUNC(pinmux[i]);
+		reg = HREAD4(sc, GPIO_PIN(pin));
+		reg &= ~GPIO_PIN_FUNC_MASK;
+		reg |= (func << GPIO_PIN_FUNC_SHIFT) & GPIO_PIN_FUNC_MASK;
+		HWRITE4(sc, GPIO_PIN(pin), reg);
+	}
+	APPLE_PINCTRL_UNLOCK(sc);
+
+	OF_prop_free(pinmux);
+	return 0;
+}
+
+static device_method_t apple_pinctrl_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,		apple_pinctrl_probe),
+	DEVMETHOD(device_attach,	apple_pinctrl_attach),
+	DEVMETHOD(device_detach,	apple_pinctrl_detach),
+
+	/* GPIO protocol */
+	DEVMETHOD(gpio_get_bus,		apple_pinctrl_get_bus),
+	DEVMETHOD(gpio_pin_max,		apple_pinctrl_pin_max),
+	DEVMETHOD(gpio_pin_getname,	apple_pinctrl_pin_getname),
+	DEVMETHOD(gpio_pin_getflags,	apple_pinctrl_pin_getflags),
+	DEVMETHOD(gpio_pin_getcaps,	apple_pinctrl_pin_getcaps),
+	DEVMETHOD(gpio_pin_setflags,	apple_pinctrl_pin_setflags),
+	DEVMETHOD(gpio_pin_get,		apple_pinctrl_pin_get),
+	DEVMETHOD(gpio_pin_set,		apple_pinctrl_pin_set),
+	DEVMETHOD(gpio_pin_toggle,	apple_pinctrl_pin_toggle),
+	DEVMETHOD(gpio_pin_config_32,	apple_pinctrl_pin_config_32),
+
+	/* ofw_bus interface */
+	DEVMETHOD(ofw_bus_get_node,		apple_pinctrl_get_node),
+
+        /* fdt_pinctrl interface */
+	DEVMETHOD(fdt_pinctrl_configure,	apple_pinctrl_configure),
+
+	DEVMETHOD_END
+};
+
+static driver_t apple_pinctrl_driver = {
+	"gpio",
+	apple_pinctrl_methods,
+	sizeof(struct apple_pinctrl_softc),
+};
+
+EARLY_DRIVER_MODULE(apple_pinctrl, simplebus, apple_pinctrl_driver,
+    0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);
diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64
index 74387914043e..901da27e63f2 100644
--- a/sys/conf/files.arm64
+++ b/sys/conf/files.arm64
@@ -547,6 +547,7 @@ arm/annapurna/alpine/alpine_serdes.c		optional al_serdes fdt		\
 
 # Apple
 arm64/apple/apple_aic.c				optional soc_apple_t8103 fdt
+arm64/apple/apple_pinctrl.c			optional soc_apple_t8103 fdt
 arm64/apple/apple_wdog.c			optional soc_apple_t8103 fdt
 arm64/apple/exynos_uart.c			optional soc_apple_t8103 fdt