/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2026 Beckhoff Automation GmbH & Co. KG */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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));