/* $NetBSD: acpi_gpio.c,v 1.5 2024/12/15 10:15:55 hannken Exp $ */ /*- * Copyright (c) 2024 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jared McNeill . * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * ACPI GPIO resource support. */ #include "gpio.h" #include __KERNEL_RCSID(0, "$NetBSD: acpi_gpio.c,v 1.5 2024/12/15 10:15:55 hannken Exp $"); #include #include #include #include #include #include #include #if NGPIO > 0 #define _COMPONENT ACPI_RESOURCE_COMPONENT ACPI_MODULE_NAME ("acpi_gpio") struct acpi_gpio_address_space_context { ACPI_CONNECTION_INFO conn_info; /* must be first */ struct acpi_devnode *ad; }; static ACPI_STATUS acpi_gpio_address_space_init(ACPI_HANDLE region_hdl, UINT32 function, void *handler_ctx, void **region_ctx) { if (function == ACPI_REGION_DEACTIVATE) { *region_ctx = NULL; } else { *region_ctx = region_hdl; } return AE_OK; } static ACPI_STATUS acpi_gpio_address_space_handler(UINT32 function, ACPI_PHYSICAL_ADDRESS address, UINT32 bit_width, UINT64 *value, void *handler_ctx, void *region_ctx) { ACPI_OPERAND_OBJECT *region_obj = region_ctx; struct acpi_gpio_address_space_context *context = handler_ctx; ACPI_CONNECTION_INFO *conn_info = &context->conn_info; struct acpi_devnode *ad = context->ad; ACPI_RESOURCE *res; ACPI_STATUS rv; struct gpio_pinmap pinmap; int pins[1]; void *gpiop; int pin; if (region_obj->Region.Type != ACPI_TYPE_REGION) { return AE_OK; } if (ad->ad_gpiodev == NULL) { return AE_NO_HANDLER; } rv = AcpiBufferToResource(conn_info->Connection, conn_info->Length, &res); if (ACPI_FAILURE(rv)) { return rv; } if (res->Data.Gpio.PinTableLength != 1) { /* TODO */ aprint_debug_dev(ad->ad_gpiodev, "Pin table length %u not implemented\n", res->Data.Gpio.PinTableLength); rv = AE_NOT_IMPLEMENTED; goto done; } pin = ad->ad_gpio_translate(ad->ad_gpio_priv, &res->Data.Gpio, &gpiop); if (pin == -1) { /* Pin could not be translated. */ rv = AE_SUPPORT; goto done; } pinmap.pm_map = pins; if (gpio_pin_map(gpiop, pin, 1, &pinmap) != 0) { rv = AE_NOT_ACQUIRED; goto done; } if (function & ACPI_IO_MASK) { gpio_pin_write(gpiop, &pinmap, 0, *value & 1); } else { *value = gpio_pin_read(gpiop, &pinmap, 0); } gpio_pin_unmap(gpiop, &pinmap); done: ACPI_FREE(res); return rv; } #endif ACPI_STATUS acpi_gpio_register(struct acpi_devnode *ad, device_t dev, int (*translate)(void *, ACPI_RESOURCE_GPIO *, void **), void *priv) { #if NGPIO > 0 struct acpi_gpio_address_space_context *context; ACPI_STATUS rv; if (ad->ad_gpiodev != NULL) { device_printf(dev, "%s already registered\n", device_xname(ad->ad_gpiodev)); return AE_ALREADY_EXISTS; } context = kmem_zalloc(sizeof(*context), KM_SLEEP); context->ad = ad; rv = AcpiInstallAddressSpaceHandler(ad->ad_handle, ACPI_ADR_SPACE_GPIO, acpi_gpio_address_space_handler, acpi_gpio_address_space_init, context); if (ACPI_FAILURE(rv)) { aprint_error_dev(dev, "couldn't install address space handler: %s", AcpiFormatException(rv)); return rv; } ad->ad_gpiodev = dev; ad->ad_gpio_translate = translate; ad->ad_gpio_priv = priv; return AE_OK; #else return AE_NOT_CONFIGURED; #endif } static ACPI_STATUS acpi_gpio_translate(ACPI_RESOURCE_GPIO *res, void **gpiop, int *pin) { struct acpi_devnode *ad, *gpioad = NULL; ACPI_HANDLE hdl; ACPI_RESOURCE_SOURCE *rs; ACPI_STATUS rv; int xpin; /* Find the device node providing the GPIO resource. */ rs = &res->ResourceSource; if (rs->StringPtr == NULL) { return AE_NOT_FOUND; } rv = AcpiGetHandle(NULL, rs->StringPtr, &hdl); if (ACPI_FAILURE(rv)) { return rv; } SIMPLEQ_FOREACH(ad, &acpi_softc->sc_head, ad_list) { if (ad->ad_handle == hdl) { gpioad = ad; break; } } if (gpioad == NULL) { /* No device node found. */ return AE_NOT_FOUND; } if (gpioad->ad_gpiodev == NULL) { /* No resource provider is registered. */ return AE_NO_HANDLER; } xpin = gpioad->ad_gpio_translate(gpioad->ad_gpio_priv, res, gpiop); if (xpin == -1) { /* Pin could not be translated. */ return AE_SUPPORT; } *pin = xpin; return AE_OK; } struct acpi_gpio_resource_context { u_int index; u_int conntype; u_int curindex; ACPI_RESOURCE_GPIO *res; }; static ACPI_STATUS acpi_gpio_parse(ACPI_RESOURCE *res, void *context) { struct acpi_gpio_resource_context *ctx = context; if (res->Type != ACPI_RESOURCE_TYPE_GPIO) { return AE_OK; } if (res->Data.Gpio.ConnectionType != ctx->conntype) { return AE_OK; } if (ctx->curindex == ctx->index) { ctx->res = &res->Data.Gpio; return AE_CTRL_TERMINATE; } ctx->curindex++; return AE_OK; } ACPI_STATUS acpi_gpio_get_int(ACPI_HANDLE hdl, u_int index, void **gpiop, int *pin, int *irqmode) { struct acpi_gpio_resource_context ctx = { .index = index, .conntype = ACPI_RESOURCE_GPIO_TYPE_INT, }; ACPI_RESOURCE_GPIO *gpio; ACPI_STATUS rv; rv = AcpiWalkResources(hdl, "_CRS", acpi_gpio_parse, &ctx); if (ACPI_FAILURE(rv)) { return rv; } gpio = ctx.res; rv = acpi_gpio_translate(gpio, gpiop, pin); if (ACPI_FAILURE(rv)) { printf("%s: translate failed: %s\n", __func__, AcpiFormatException(rv)); return rv; } if (gpio->Triggering == ACPI_LEVEL_SENSITIVE) { *irqmode = gpio->Polarity == ACPI_ACTIVE_HIGH ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL; } else { KASSERT(gpio->Triggering == ACPI_EDGE_SENSITIVE); if (gpio->Polarity == ACPI_ACTIVE_LOW) { *irqmode = GPIO_INTR_NEG_EDGE; } else if (gpio->Polarity == ACPI_ACTIVE_HIGH) { *irqmode = GPIO_INTR_POS_EDGE; } else { KASSERT(gpio->Polarity == ACPI_ACTIVE_BOTH); *irqmode = GPIO_INTR_DOUBLE_EDGE; } } return AE_OK; } ACPI_STATUS acpi_gpio_get_io(ACPI_HANDLE hdl, u_int index, void **gpiop, int *pin) { struct acpi_gpio_resource_context ctx = { .index = index, .conntype = ACPI_RESOURCE_GPIO_TYPE_INT, }; ACPI_STATUS rv; rv = AcpiWalkResources(hdl, "_CRS", acpi_gpio_parse, &ctx); if (ACPI_FAILURE(rv)) { return rv; } return acpi_gpio_translate(ctx.res, gpiop, pin); }