/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2024 Ahmad Khalifa * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "gpiobus_if.h" struct acpi_gpiobus_softc { struct gpiobus_softc super_sc; ACPI_CONNECTION_INFO handler_info; }; struct acpi_gpiobus_ctx { struct gpiobus_softc *sc; ACPI_HANDLE dev_handle; }; struct acpi_gpiobus_ivar { struct gpiobus_ivar gpiobus; /* Must come first */ ACPI_HANDLE dev_handle; /* ACPI handle for bus */ uint32_t flags; }; static uint32_t acpi_gpiobus_convflags(ACPI_RESOURCE_GPIO *gpio_res) { uint32_t flags = 0; /* Figure out pin flags */ if (gpio_res->ConnectionType == ACPI_RESOURCE_GPIO_TYPE_INT) { switch (gpio_res->Polarity) { case ACPI_ACTIVE_HIGH: flags = gpio_res->Triggering == ACPI_LEVEL_SENSITIVE ? GPIO_INTR_LEVEL_HIGH : GPIO_INTR_EDGE_RISING; break; case ACPI_ACTIVE_LOW: flags = gpio_res->Triggering == ACPI_LEVEL_SENSITIVE ? GPIO_INTR_LEVEL_LOW : GPIO_INTR_EDGE_FALLING; break; case ACPI_ACTIVE_BOTH: flags = GPIO_INTR_EDGE_BOTH; break; } #ifdef NOT_YET /* This is not currently implemented. */ if (gpio_res->Shareable == ACPI_SHARED) flags |= GPIO_INTR_SHAREABLE; #endif } if (gpio_res->ConnectionType == ACPI_RESOURCE_GPIO_TYPE_IO) { switch (gpio_res->IoRestriction) { case ACPI_IO_RESTRICT_INPUT: flags |= GPIO_PIN_INPUT; break; case ACPI_IO_RESTRICT_OUTPUT: flags |= GPIO_PIN_OUTPUT; break; } } switch (gpio_res->PinConfig) { case ACPI_PIN_CONFIG_PULLUP: flags |= GPIO_PIN_PULLUP; break; case ACPI_PIN_CONFIG_PULLDOWN: flags |= GPIO_PIN_PULLDOWN; break; } return (flags); } static ACPI_STATUS acpi_gpiobus_enumerate_res(ACPI_RESOURCE *res, void *context) { ACPI_RESOURCE_GPIO *gpio_res = &res->Data.Gpio; struct acpi_gpiobus_ctx *ctx = context; struct gpiobus_softc *super_sc = ctx->sc; ACPI_HANDLE handle; uint32_t flags, i; if (res->Type != ACPI_RESOURCE_TYPE_GPIO) return (AE_OK); if (ACPI_FAILURE(AcpiGetHandle(ACPI_ROOT_OBJECT, gpio_res->ResourceSource.StringPtr, &handle)) || handle != ctx->dev_handle) return (AE_OK); if (__predict_false(gpio_res->PinTableLength > super_sc->sc_npins)) { device_printf(super_sc->sc_busdev, "invalid pin table length %hu, max: %d (bad ACPI tables?)\n", gpio_res->PinTableLength, super_sc->sc_npins); return (AE_LIMIT); } flags = acpi_gpiobus_convflags(gpio_res); for (i = 0; i < gpio_res->PinTableLength; i++) { UINT16 pin = gpio_res->PinTable[i]; if (__predict_false(pin >= super_sc->sc_npins)) { device_printf(super_sc->sc_busdev, "invalid pin 0x%x, max: 0x%x (bad ACPI tables?)\n", pin, super_sc->sc_npins - 1); return (AE_LIMIT); } GPIO_PIN_SETFLAGS(super_sc->sc_dev, pin, flags & ~GPIO_INTR_MASK); } return (AE_OK); } static struct acpi_gpiobus_ivar * acpi_gpiobus_setup_devinfo(device_t bus, device_t child, ACPI_RESOURCE_GPIO *gpio_res) { struct acpi_gpiobus_ivar *devi; devi = malloc(sizeof(*devi), M_DEVBUF, M_NOWAIT | M_ZERO); if (devi == NULL) return (NULL); resource_list_init(&devi->gpiobus.rl); devi->flags = acpi_gpiobus_convflags(gpio_res); if (acpi_quirks & ACPI_Q_AEI_NOPULL) devi->flags &= ~GPIO_PIN_PULLUP; devi->gpiobus.npins = 1; if (gpiobus_alloc_ivars(&devi->gpiobus) != 0) { free(devi, M_DEVBUF); return (NULL); } for (int i = 0; i < devi->gpiobus.npins; i++) devi->gpiobus.pins[i] = gpio_res->PinTable[i]; return (devi); } static ACPI_STATUS acpi_gpiobus_enumerate_aei(ACPI_RESOURCE *res, void *context) { ACPI_RESOURCE_GPIO *gpio_res = &res->Data.Gpio; struct acpi_gpiobus_ctx *ctx = context; device_t bus = ctx->sc->sc_busdev; device_t child; struct acpi_gpiobus_ivar *devi; /* Check that we have a GpioInt object. */ if (res->Type != ACPI_RESOURCE_TYPE_GPIO) return (AE_OK); if (gpio_res->ConnectionType != ACPI_RESOURCE_GPIO_TYPE_INT) return (AE_OK); /* Add a child. */ child = device_add_child_ordered(bus, 0, "gpio_aei", DEVICE_UNIT_ANY); if (child == NULL) return (AE_OK); devi = acpi_gpiobus_setup_devinfo(bus, child, gpio_res); if (devi == NULL) { device_delete_child(bus, child); return (AE_OK); } device_set_ivars(child, devi); for (int i = 0; i < devi->gpiobus.npins; i++) { if (GPIOBUS_PIN_SETFLAGS(bus, child, 0, devi->flags)) { device_delete_child(bus, child); return (AE_OK); } } /* Pass ACPI information to children. */ devi->dev_handle = ctx->dev_handle; return (AE_OK); } static ACPI_STATUS acpi_gpiobus_enumerate(ACPI_HANDLE handle, UINT32 depth, void *context, void **result) { UINT32 sta; /* * If no _STA method or if it failed, then assume that * the device is present. */ if (!ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) && !ACPI_DEVICE_PRESENT(sta)) return (AE_OK); if (!acpi_has_hid(handle)) return (AE_OK); /* Look for GPIO resources */ AcpiWalkResources(handle, "_CRS", acpi_gpiobus_enumerate_res, context); return (AE_OK); } static ACPI_STATUS acpi_gpiobus_space_handler(UINT32 function, ACPI_PHYSICAL_ADDRESS address, UINT32 length, UINT64 *value, void *context, void *region_context) { ACPI_CONNECTION_INFO *info = context; ACPI_RESOURCE_GPIO *gpio_res; device_t controller; ACPI_RESOURCE *res; ACPI_STATUS status; status = AcpiBufferToResource(info->Connection, info->Length, &res); if (ACPI_FAILURE(status) || res->Type != ACPI_RESOURCE_TYPE_GPIO) goto err; gpio_res = &res->Data.Gpio; controller = __containerof(info, struct acpi_gpiobus_softc, handler_info)->super_sc.sc_dev; switch (function) { case ACPI_WRITE: if (__predict_false( gpio_res->IoRestriction == ACPI_IO_RESTRICT_INPUT)) goto err; for (int i = 0; i < length; i++) if (GPIO_PIN_SET(controller, gpio_res->PinTable[address + i], (*value & 1 << i) ? GPIO_PIN_HIGH : GPIO_PIN_LOW) != 0) goto err; break; case ACPI_READ: if (__predict_false( gpio_res->IoRestriction == ACPI_IO_RESTRICT_OUTPUT)) goto err; for (int i = 0; i < length; i++) { uint32_t v; if (GPIO_PIN_GET(controller, gpio_res->PinTable[address + i], &v) != 0) goto err; *value |= v << i; } break; default: goto err; } ACPI_FREE(res); return (AE_OK); err: ACPI_FREE(res); return (AE_BAD_PARAMETER); } static int acpi_gpiobus_probe(device_t dev) { device_t controller; if (acpi_disabled("gpiobus")) return (ENXIO); controller = device_get_parent(dev); if (controller == NULL) return (ENXIO); if (acpi_get_handle(controller) == NULL) return (ENXIO); device_set_desc(dev, "GPIO bus (ACPI-hinted)"); return (BUS_PROBE_DEFAULT); } static int acpi_gpiobus_attach(device_t dev) { struct acpi_gpiobus_softc *sc; struct acpi_gpiobus_ctx ctx; ACPI_HANDLE handle; ACPI_STATUS status; int err; if ((err = gpiobus_attach(dev)) != 0) return (err); sc = device_get_softc(dev); handle = acpi_get_handle(sc->super_sc.sc_dev); if (handle == NULL) { gpiobus_detach(dev); return (ENXIO); } status = AcpiInstallAddressSpaceHandler(handle, ACPI_ADR_SPACE_GPIO, acpi_gpiobus_space_handler, NULL, &sc->handler_info); if (ACPI_FAILURE(status)) { device_printf(dev, "Failed to install GPIO address space handler\n"); gpiobus_detach(dev); return (ENXIO); } ctx.dev_handle = handle; ctx.sc = &sc->super_sc; status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX, acpi_gpiobus_enumerate, NULL, &ctx, NULL); if (ACPI_FAILURE(status)) device_printf(dev, "Failed to enumerate GPIO resources\n"); /* Look for AEI children */ status = AcpiWalkResources(handle, "_AEI", acpi_gpiobus_enumerate_aei, &ctx); if (ACPI_FAILURE(status)) device_printf(dev, "Failed to enumerate GPIO resources\n"); return (0); } static int acpi_gpiobus_detach(device_t dev) { struct gpiobus_softc *super_sc; ACPI_STATUS status; super_sc = device_get_softc(dev); status = AcpiRemoveAddressSpaceHandler( acpi_get_handle(super_sc->sc_dev), ACPI_ADR_SPACE_GPIO, acpi_gpiobus_space_handler ); if (ACPI_FAILURE(status)) device_printf(dev, "Failed to remove GPIO address space handler\n"); return (gpiobus_detach(dev)); } int gpio_pin_get_by_acpi_index(device_t consumer, uint32_t idx, gpio_pin_t *out_pin) { struct acpi_gpiobus_ivar *devi; int rv; rv = gpio_pin_get_by_child_index(consumer, idx, out_pin); if (rv != 0) return (rv); devi = device_get_ivars(consumer); (*out_pin)->flags = devi->flags; return (0); } static int acpi_gpiobus_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct acpi_gpiobus_ivar *devi = device_get_ivars(child); switch (which) { case ACPI_GPIOBUS_IVAR_HANDLE: *result = (uintptr_t)devi->dev_handle; break; default: return (gpiobus_read_ivar(dev, child, which, result)); } return (0); } static device_method_t acpi_gpiobus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_gpiobus_probe), DEVMETHOD(device_attach, acpi_gpiobus_attach), DEVMETHOD(device_detach, acpi_gpiobus_detach), /* Bus interface */ DEVMETHOD(bus_read_ivar, acpi_gpiobus_read_ivar), DEVMETHOD_END }; DEFINE_CLASS_1(gpiobus, acpi_gpiobus_driver, acpi_gpiobus_methods, sizeof(struct acpi_gpiobus_softc), gpiobus_driver); EARLY_DRIVER_MODULE(acpi_gpiobus, gpio, acpi_gpiobus_driver, NULL, NULL, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(acpi_gpiobus, 1); MODULE_DEPEND(acpi_gpiobus, acpi, 1, 1, 1);