git: 14887d2c869a - stable/14 - gpiobus(4): Add an acpi variant of gpiobus

From: Colin Percival <cperciva_at_FreeBSD.org>
Date: Sun, 03 Nov 2024 16:02:19 UTC
The branch stable/14 has been updated by cperciva:

URL: https://cgit.FreeBSD.org/src/commit/?id=14887d2c869ad47d5921fc9aa07e891a38950121

commit 14887d2c869ad47d5921fc9aa07e891a38950121
Author:     Ahmad Khalifa <ahmadkhalifa570@gmail.com>
AuthorDate: 2024-07-08 12:22:17 +0000
Commit:     Colin Percival <cperciva@FreeBSD.org>
CommitDate: 2024-11-03 16:01:27 +0000

    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
    MFC after:      1 month
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/1359
    
    (cherry picked from commit 92adaa5862d5ea94318a011e0618622d0fb72521)
---
 sys/conf/files                    |   1 +
 sys/dev/gpio/acpi_gpiobus.c       | 311 ++++++++++++++++++++++++++++++++++++++
 sys/dev/gpio/gpiobus.c            |   6 +-
 sys/dev/gpio/gpiobusvar.h         |   2 +
 sys/modules/gpio/gpiobus/Makefile |   3 +
 5 files changed, 319 insertions(+), 4 deletions(-)

diff --git a/sys/conf/files b/sys/conf/files
index 5325e010d970..5239f38d5154 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -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
diff --git a/sys/dev/gpio/acpi_gpiobus.c b/sys/dev/gpio/acpi_gpiobus.c
new file mode 100644
index 000000000000..eafa1c07fab1
--- /dev/null
+++ b/sys/dev/gpio/acpi_gpiobus.c
@@ -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);
diff --git a/sys/dev/gpio/gpiobus.c b/sys/dev/gpio/gpiobus.c
index 65e8d1a775fe..7bdb080eb4f1 100644
--- a/sys/dev/gpio/gpiobus.c
+++ b/sys/dev/gpio/gpiobus.c
@@ -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;
diff --git a/sys/dev/gpio/gpiobusvar.h b/sys/dev/gpio/gpiobusvar.h
index 521132fbac9d..e3669e82e594 100644
--- a/sys/dev/gpio/gpiobusvar.h
+++ b/sys/dev/gpio/gpiobusvar.h
@@ -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 *);
diff --git a/sys/modules/gpio/gpiobus/Makefile b/sys/modules/gpio/gpiobus/Makefile
index d9345e00e2be..baaf7faf69e8 100644
--- a/sys/modules/gpio/gpiobus/Makefile
+++ b/sys/modules/gpio/gpiobus/Makefile
@@ -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/