git: 5349a46babc0 - main - gpio: add generic Intel GPIO pin controller framework

Corvin Köhne <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a742ce5.2777c.172065cd__618.046293833174$1785998628$gmane$org@gitrepo.freebsd.org>
The branch main has been updated by corvink:

URL: https://cgit.FreeBSD.org/src/commit/?id=5349a46babc03453b0e6679bf867272e7ef7ea22

commit 5349a46babc03453b0e6679bf867272e7ef7ea22
Author:     YannickV <[email protected]>
AuthorDate: 2026-04-29 12:56:14 +0000
Commit:     Corvin Köhne <[email protected]>
CommitDate: 2026-08-06 06:41:33 +0000

    gpio: add generic Intel GPIO pin controller framework
    
    Add a platform-independent driver framework for Intel GPIO pin controllers
    found on modern Intel SoCs. The driver accesses GPIO pad registers through
    ACPI-provided memory-mapped resources and implements the gpio interface [1]
    including pin enumeration, capability reporting, configuration, and
    read/write/toggle operations. A common data model of communities and pad groups
    allows individual SoC-specific drivers to supply their own pad tables and ACPI
    hardware IDs while sharing all register-level logic.
    
    [1] https://wiki.freebsd.org/GPIO
    
    Reviewed by:            vexeduxr
    MFC after:              1 week
    Sponsored by:           Beckhoff Automation GmbH & Co. KG
    Pull Request:           https://github.com/freebsd/freebsd-src/pull/2205
---
 sys/dev/gpio/intel/intelgpio.c | 415 +++++++++++++++++++++++++++++++++++++++++
 sys/dev/gpio/intel/intelgpio.h |  64 +++++++
 2 files changed, 479 insertions(+)

diff --git a/sys/dev/gpio/intel/intelgpio.c b/sys/dev/gpio/intel/intelgpio.c
new file mode 100644
index 000000000000..aba4c12da828
--- /dev/null
+++ b/sys/dev/gpio/intel/intelgpio.c
@@ -0,0 +1,415 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Beckhoff Automation GmbH & Co. KG
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+
+#include <dev/acpica/acpivar.h>
+#include <dev/gpio/gpiobusvar.h>
+
+#include "intelgpio.h"
+
+#include "gpio_if.h"
+#include "opt_acpi.h"
+
+#define INTELGPIO_LOCK(_sc)   mtx_lock_spin(&(_sc)->sc_mtx)
+#define INTELGPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx)
+#define INTELGPIO_LOCK_INIT(_sc)                                     \
+	mtx_init(&(_sc)->sc_mtx, device_get_nameunit((_sc)->sc_dev), \
+	    "intelgpio", MTX_SPIN)
+#define INTELGPIO_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx)
+
+static int
+intelgpio_gpio_to_pad(struct intelgpio_softc *sc, uint32_t pin,
+    struct resource **res, bus_size_t *pad_off,
+    const struct intelgpio_padgroup **pgp)
+{
+	const struct intelgpio_platform *plat = sc->sc_plat;
+	const struct intelgpio_community *com;
+	const struct intelgpio_padgroup *pg;
+	int i, j, offset, local_pad;
+
+	for (i = 0; i < plat->ncommunities; i++) {
+		com = &plat->communities[i];
+		for (j = 0; j < com->ngroups; j++) {
+			pg = &com->groups[j];
+			if (pg->gpio_base == INTELGPIO_GPIO_NOMAP)
+				continue;
+			if (pin >= pg->gpio_base &&
+			    pin < pg->gpio_base + pg->npads) {
+				if (res != NULL)
+					*res = sc->sc_mem_res[i];
+				if (pad_off != NULL) {
+					offset = pin - pg->gpio_base;
+					local_pad = (pg->first_pad + offset) -
+					    com->groups[0].first_pad;
+					*pad_off = sc->sc_padbar[i] +
+					    (local_pad * INTELGPIO_PAD_SIZE);
+				}
+				if (pgp != NULL)
+					*pgp = pg;
+				return (0);
+			}
+		}
+	}
+	return (-1);
+}
+
+static device_t
+intelgpio_get_bus(device_t dev)
+{
+	struct intelgpio_softc *sc;
+
+	sc = device_get_softc(dev);
+	return (sc->sc_busdev);
+}
+
+static int
+intelgpio_pin_max(device_t dev, int *maxpin)
+{
+	struct intelgpio_softc *sc;
+	const struct intelgpio_platform *plat;
+	const struct intelgpio_community *com;
+	const struct intelgpio_padgroup *pg;
+	int i, j;
+
+	if (maxpin == NULL)
+		return (EINVAL);
+
+	sc = device_get_softc(dev);
+	plat = sc->sc_plat;
+
+	/* Find the last group with a valid gpio_base, skipping NOMAP */
+	*maxpin = 0;
+	for (i = 0; i < plat->ncommunities; i++) {
+		com = &plat->communities[i];
+		for (j = 0; j < com->ngroups; j++) {
+			pg = &com->groups[j];
+			if (pg->gpio_base == INTELGPIO_GPIO_NOMAP)
+				continue;
+			*maxpin = pg->gpio_base + (pg->npads - 1);
+		}
+	}
+	return (0);
+}
+
+static int
+intelgpio_pin_getname(device_t dev, uint32_t pin, char *name)
+{
+	struct intelgpio_softc *sc;
+	const struct intelgpio_padgroup *pg;
+	int offset;
+
+	sc = device_get_softc(dev);
+	if (intelgpio_gpio_to_pad(sc, pin, NULL, NULL, &pg) != 0)
+		return (EINVAL);
+
+	offset = pin - pg->gpio_base;
+	snprintf(name, GPIOMAXNAME, "%s%d", pg->name, offset);
+	return (0);
+}
+
+static int
+intelgpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
+{
+	struct intelgpio_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	/*
+	 * The getcaps function is expected to return the capabilities
+	 * of the pin. Validate that the pin number maps to an existing
+	 * pad first.
+	 */
+	if (intelgpio_gpio_to_pad(sc, pin, NULL, NULL, NULL) != 0)
+		return (EINVAL);
+
+	*caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT;
+	return (0);
+}
+
+static int
+intelgpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)
+{
+	struct intelgpio_softc *sc;
+	struct resource *res;
+	bus_size_t pad_off;
+	uint32_t val;
+
+	sc = device_get_softc(dev);
+	if (intelgpio_gpio_to_pad(sc, pin, &res, &pad_off, NULL) != 0)
+		return (EINVAL);
+	if (res == NULL)
+		return (EINVAL);
+
+	*flags = 0;
+
+	INTELGPIO_LOCK(sc);
+	val = bus_read_4(res, pad_off);
+	INTELGPIO_UNLOCK(sc);
+
+	/*
+	 * If the pad is not in GPIO mode (i.e. configured for a native
+	 * function like PCIe, UART, I2C, etc.), report no flags. The pin
+	 * cannot be used as GPIO without first switching it via setflags.
+	 */
+	if ((val & INTELGPIO_PADCFG0_PMODE_MASK) !=
+	    INTELGPIO_PADCFG0_PMODE_GPIO)
+		return (0);
+
+	/*
+	 * Report OUTPUT only if TX is explicitly enabled. Default to INPUT
+	 * otherwise: a non-driving pin is considered an input from the user's
+	 * perspective.
+	 */
+	if (!(val & INTELGPIO_PADCFG0_GPIOTXDIS))
+		*flags = GPIO_PIN_OUTPUT;
+	else
+		*flags = GPIO_PIN_INPUT;
+
+	return (0);
+}
+
+static int
+intelgpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
+{
+	struct intelgpio_softc *sc;
+	struct resource *res;
+	bus_size_t pad_off;
+	uint32_t val;
+
+	sc = device_get_softc(dev);
+	if (intelgpio_gpio_to_pad(sc, pin, &res, &pad_off, NULL) != 0)
+		return (EINVAL);
+	if (res == NULL)
+		return (EINVAL);
+
+	/*
+	 * We currently only support input and output, not both simultaneously
+	 */
+	if (flags & ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT))
+		return (EINVAL);
+	if ((flags & GPIO_PIN_INPUT) && (flags & GPIO_PIN_OUTPUT))
+		return (EINVAL);
+
+	INTELGPIO_LOCK(sc);
+	val = bus_read_4(res, pad_off);
+
+	/*
+	 * Switch the pad to GPIO mode. Intel pads can operate in different
+	 * modes (native function 1-15 or GPIO). Since setflags is only called
+	 * when userspace or a consumer explicitly wants to use the pin as a
+	 * GPIO, we force the pad into GPIO mode here so the direction bits
+	 * (TXDIS/RXDIS) below take effect.
+	 */
+	val &= ~INTELGPIO_PADCFG0_PMODE_MASK;
+	val |= INTELGPIO_PADCFG0_PMODE_GPIO;
+
+	if (flags & GPIO_PIN_INPUT)
+		val &= ~INTELGPIO_PADCFG0_GPIORXDIS;
+	else
+		val |= INTELGPIO_PADCFG0_GPIORXDIS;
+
+	if (flags & GPIO_PIN_OUTPUT)
+		val &= ~INTELGPIO_PADCFG0_GPIOTXDIS;
+	else
+		val |= INTELGPIO_PADCFG0_GPIOTXDIS;
+
+	bus_write_4(res, pad_off, val);
+	INTELGPIO_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+intelgpio_pin_get(device_t dev, uint32_t pin, unsigned int *value)
+{
+	struct intelgpio_softc *sc;
+	struct resource *res;
+	bus_size_t pad_off;
+	uint32_t val;
+
+	sc = device_get_softc(dev);
+	if (intelgpio_gpio_to_pad(sc, pin, &res, &pad_off, NULL) != 0)
+		return (EINVAL);
+	if (res == NULL)
+		return (EINVAL);
+
+	INTELGPIO_LOCK(sc);
+	val = bus_read_4(res, pad_off);
+	INTELGPIO_UNLOCK(sc);
+
+	*value = (val & INTELGPIO_PADCFG0_GPIORXSTATE) ?
+	    GPIO_PIN_HIGH : GPIO_PIN_LOW;
+
+	return (0);
+}
+
+static int
+intelgpio_pin_set(device_t dev, uint32_t pin, unsigned int value)
+{
+	struct intelgpio_softc *sc;
+	struct resource *res;
+	bus_size_t pad_off;
+	uint32_t val;
+
+	sc = device_get_softc(dev);
+	if (intelgpio_gpio_to_pad(sc, pin, &res, &pad_off, NULL) != 0)
+		return (EINVAL);
+	if (res == NULL)
+		return (EINVAL);
+
+	INTELGPIO_LOCK(sc);
+	val = bus_read_4(res, pad_off);
+	if (value == GPIO_PIN_LOW)
+		val &= ~INTELGPIO_PADCFG0_GPIOTXSTATE;
+	else
+		val |= INTELGPIO_PADCFG0_GPIOTXSTATE;
+	bus_write_4(res, pad_off, val);
+	INTELGPIO_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+intelgpio_pin_toggle(device_t dev, uint32_t pin)
+{
+	struct intelgpio_softc *sc;
+	struct resource *res;
+	bus_size_t pad_off;
+	uint32_t val;
+
+	sc = device_get_softc(dev);
+	if (intelgpio_gpio_to_pad(sc, pin, &res, &pad_off, NULL) != 0)
+		return (EINVAL);
+	if (res == NULL)
+		return (EINVAL);
+
+	INTELGPIO_LOCK(sc);
+	val = bus_read_4(res, pad_off);
+	val ^= INTELGPIO_PADCFG0_GPIOTXSTATE;
+	bus_write_4(res, pad_off, val);
+	INTELGPIO_UNLOCK(sc);
+
+	return (0);
+}
+
+int
+intelgpio_probe(device_t dev, const struct intelgpio_platform *plat)
+{
+	int rv;
+
+	if (acpi_disabled("intelgpio"))
+		return (ENXIO);
+
+	rv = ACPI_ID_PROBE(device_get_parent(dev), dev, plat->hids, NULL);
+	if (rv <= 0)
+		device_set_desc(dev, plat->desc);
+	return (rv);
+}
+
+static int
+intelgpio_detach(device_t dev)
+{
+	struct intelgpio_softc *sc;
+	int i;
+
+	sc = device_get_softc(dev);
+
+	if (sc->sc_busdev != NULL)
+		gpiobus_detach_bus(dev);
+
+	for (i = 0; i < sc->sc_plat->ncommunities; i++) {
+		if (sc->sc_mem_res[i] != NULL)
+			bus_release_resource(dev, SYS_RES_MEMORY, i,
+			    sc->sc_mem_res[i]);
+	}
+
+	INTELGPIO_LOCK_DESTROY(sc);
+	return (0);
+}
+
+int
+intelgpio_attach(device_t dev, const struct intelgpio_platform *plat)
+{
+	struct intelgpio_softc *sc;
+	bool have_res;
+	int i;
+
+	sc = device_get_softc(dev);
+	sc->sc_dev = dev;
+	sc->sc_plat = plat;
+
+	KASSERT(plat->ncommunities <= INTELGPIO_MAX_COMMUNITIES,
+	    ("too many communities: %d > %d", plat->ncommunities,
+		INTELGPIO_MAX_COMMUNITIES));
+
+	INTELGPIO_LOCK_INIT(sc);
+
+	have_res = false;
+	for (i = 0; i < plat->ncommunities; i++) {
+		sc->sc_mem_res[i] = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+		    &i, RF_ACTIVE);
+		if (sc->sc_mem_res[i] == NULL) {
+			device_printf(dev,
+			    "failed to allocate memory resource "
+			    "for community %d\n",
+			    i);
+			continue;
+		}
+		sc->sc_padbar[i] = bus_read_4(sc->sc_mem_res[i],
+		    INTELGPIO_PADBAR_REG);
+		have_res = true;
+	}
+
+	if (!have_res) {
+		device_printf(dev, "no memory resources found\n");
+		INTELGPIO_LOCK_DESTROY(sc);
+		return (ENXIO);
+	}
+
+	sc->sc_busdev = gpiobus_add_bus(dev);
+	if (sc->sc_busdev == NULL) {
+		device_printf(dev, "failed to add gpiobus\n");
+		intelgpio_detach(dev);
+		return (ENXIO);
+	}
+
+	bus_attach_children(dev);
+
+	return (0);
+}
+
+
+static device_method_t intelgpio_methods[] = {
+	DEVMETHOD(device_detach, intelgpio_detach),
+
+	DEVMETHOD(gpio_get_bus, intelgpio_get_bus),
+	DEVMETHOD(gpio_pin_max, intelgpio_pin_max),
+	DEVMETHOD(gpio_pin_getname, intelgpio_pin_getname),
+	DEVMETHOD(gpio_pin_getcaps, intelgpio_pin_getcaps),
+	DEVMETHOD(gpio_pin_getflags, intelgpio_pin_getflags),
+	DEVMETHOD(gpio_pin_setflags, intelgpio_pin_setflags),
+	DEVMETHOD(gpio_pin_get, intelgpio_pin_get),
+	DEVMETHOD(gpio_pin_set, intelgpio_pin_set),
+	DEVMETHOD(gpio_pin_toggle, intelgpio_pin_toggle),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(intelgpio, intelgpio_driver, intelgpio_methods,
+    sizeof(struct intelgpio_softc));
diff --git a/sys/dev/gpio/intel/intelgpio.h b/sys/dev/gpio/intel/intelgpio.h
new file mode 100644
index 000000000000..54aecc9b76d8
--- /dev/null
+++ b/sys/dev/gpio/intel/intelgpio.h
@@ -0,0 +1,64 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Beckhoff Automation GmbH & Co. KG
+ */
+
+#ifndef _DEV_GPIO_INTEL_INTELGPIO_H_
+#define _DEV_GPIO_INTEL_INTELGPIO_H_
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+
+#define INTELGPIO_PADBAR_REG	      0x00c
+#define INTELGPIO_PAD_SIZE	      16
+
+#define INTELGPIO_PADCFG0_GPIOTXSTATE (1 << 0)
+#define INTELGPIO_PADCFG0_GPIORXSTATE (1 << 1)
+#define INTELGPIO_PADCFG0_GPIOTXDIS   (1 << 8)
+#define INTELGPIO_PADCFG0_GPIORXDIS   (1 << 9)
+#define INTELGPIO_PADCFG0_PMODE_MASK  (0x7 << 10)
+#define INTELGPIO_PADCFG0_PMODE_GPIO  0
+
+#define INTELGPIO_GPIO_NOMAP	      (-1)
+#define INTELGPIO_MAX_COMMUNITIES     8
+
+struct intelgpio_padgroup {
+	int first_pad;
+	int npads;
+	int gpio_base;
+	const char *name;
+};
+
+struct intelgpio_community {
+	int ngroups;
+	const struct intelgpio_padgroup *groups;
+};
+
+struct intelgpio_platform {
+	const struct intelgpio_community *communities;
+	int ncommunities;
+	char **hids;
+	const char *desc;
+};
+
+struct intelgpio_softc {
+	device_t sc_dev;
+	device_t sc_busdev;
+	struct mtx sc_mtx;
+	const struct intelgpio_platform *sc_plat;
+
+	struct resource *sc_mem_res[INTELGPIO_MAX_COMMUNITIES];
+	uint32_t sc_padbar[INTELGPIO_MAX_COMMUNITIES];
+};
+
+DECLARE_CLASS(intelgpio_driver);
+
+int intelgpio_probe(device_t dev, const struct intelgpio_platform *plat);
+int intelgpio_attach(device_t dev, const struct intelgpio_platform *plat);
+
+#endif /* _DEV_GPIO_INTEL_INTELGPIO_H_ */
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.