Date: Thu, 06 Aug 2026 06:42:45 +0000 From: Corvi=?utf-8?Q?n K=C3=B6h?=ne <corvink@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: YannickV <Y.Vossen@beckhoff.com> Subject: git: 5349a46babc0 - main - gpio: add generic Intel GPIO pin controller framework Message-ID: <6a742ce5.2777c.172065cd@gitrepo.freebsd.org>
index | next in thread | raw e-mail
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_ */home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a742ce5.2777c.172065cd>
