/* $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 <
[email protected]>.
*
* 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: acpi_gpio.c,v 1.5 2024/12/15 10:15:55 hannken Exp $");
#include <sys/param.h>
#include <sys/kmem.h>
#include <sys/gpio.h>
#include <dev/gpio/gpiovar.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpi_gpio.h>
#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);
}