git: 5349a46babc0 - main - gpio: add generic Intel GPIO pin controller framework
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 06 Aug 2026 06:42:45 UTC
The branch main has been updated by corvink:
URL: https://cgit.FreeBSD.org/src/commit/?id=5349a46babc03453b0e6679bf867272e7ef7ea22
commit 5349a46babc03453b0e6679bf867272e7ef7ea22
Author: YannickV <Y.Vossen@beckhoff.com>
AuthorDate: 2026-04-29 12:56:14 +0000
Commit: Corvin Köhne <corvink@FreeBSD.org>
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_ */