gpiobus(4): Add an acpi variant of gpiobus

This currently only implements the address space handler and attempts to
configure pins with flags obtained from ACPI.

Reviewed by:	wulf
Approved by:	re (kib)
MFC after:	1 month
Pull Request:	https://github.com/freebsd/freebsd-src/pull/1359

(cherry picked from commit 92adaa5862d5ea94318a011e0618622d0fb72521)
(cherry picked from commit 14887d2c86)
This commit is contained in:
Ahmad Khalifa 2024-07-08 15:22:17 +03:00 committed by Colin Percival
parent 603608e520
commit ce99ca6967
5 changed files with 319 additions and 4 deletions

View file

@ -1741,6 +1741,7 @@ dev/gve/gve_sysctl.c optional gve
dev/gve/gve_tx.c optional gve
dev/gve/gve_utils.c optional gve
dev/goldfish/goldfish_rtc.c optional goldfish_rtc fdt
dev/gpio/acpi_gpiobus.c optional acpi gpio
dev/gpio/dwgpio/dwgpio.c optional gpio dwgpio fdt
dev/gpio/dwgpio/dwgpio_bus.c optional gpio dwgpio fdt
dev/gpio/dwgpio/dwgpio_if.m optional gpio dwgpio fdt

311
sys/dev/gpio/acpi_gpiobus.c Normal file
View file

@ -0,0 +1,311 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2024 Ahmad Khalifa <ahmadkhalifa570@gmail.com>
*
* 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 <sys/types.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/gpio.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <dev/acpica/acpivar.h>
#include <dev/gpio/gpiobusvar.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;
};
static uint32_t
acpi_gpiobus_convflags(ACPI_RESOURCE_GPIO *gpio_res)
{
uint32_t flags = 0;
/* Figure out pin flags */
#ifdef NOT_YET
/* These are currently unused. */
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;
}
if (gpio_res->Shareable == ACPI_SHARED)
flags |= GPIO_INTR_SHAREABLE;
}
#endif
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 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");
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));
}
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),
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);

View file

@ -53,8 +53,6 @@
static void gpiobus_print_pins(struct gpiobus_ivar *, struct sbuf *);
static int gpiobus_parse_pins(struct gpiobus_softc *, device_t, int);
static int gpiobus_probe(device_t);
static int gpiobus_attach(device_t);
static int gpiobus_detach(device_t);
static int gpiobus_suspend(device_t);
static int gpiobus_resume(device_t);
static void gpiobus_probe_nomatch(device_t, device_t);
@ -551,7 +549,7 @@ gpiobus_probe(device_t dev)
return (BUS_PROBE_GENERIC);
}
static int
int
gpiobus_attach(device_t dev)
{
int err;
@ -573,7 +571,7 @@ gpiobus_attach(device_t dev)
* Since this is not a self-enumerating bus, and since we always add
* children in attach, we have to always delete children here.
*/
static int
int
gpiobus_detach(device_t dev)
{
struct gpiobus_softc *sc;

View file

@ -174,6 +174,8 @@ struct resource *gpio_alloc_intr_resource(device_t consumer_dev, int *rid,
int gpio_check_flags(uint32_t, uint32_t);
device_t gpiobus_attach_bus(device_t);
int gpiobus_detach_bus(device_t);
int gpiobus_attach(device_t);
int gpiobus_detach(device_t);
int gpiobus_init_softc(device_t);
int gpiobus_alloc_ivars(struct gpiobus_ivar *);
void gpiobus_free_ivars(struct gpiobus_ivar *);

View file

@ -38,6 +38,9 @@ SRCS+= device_if.h bus_if.h opt_platform.h
.if !empty(OPT_FDT)
SRCS+= ofw_gpiobus.c
.endif
.if ${MACHINE_CPUARCH} == "amd64" || ${MACHINE_CPUARCH} == "aarch64"
SRCS+= acpi_gpiobus.c opt_acpi.h acpi_if.h
.endif
CFLAGS+= -I. -I${SRCTOP}/sys/dev/gpio/