Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F145048703
D26311.1777276517.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
15 KB
Referenced Files
None
Subscribers
None
D26311.1777276517.diff
View Options
Index: sys/dev/gpio/gpioled_acpi.c
===================================================================
--- /dev/null
+++ sys/dev/gpio/gpioled_acpi.c
@@ -0,0 +1,596 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) Andriy Gapon <avg@FreeBSD.org>
+ *
+ * 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 AUTHOR 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 AUTHOR 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_platform.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+
+#include <dev/acpica/acpivar.h>
+#include <dev/gpio/gpiobusvar.h>
+#include <dev/led/led.h>
+
+#include "gpiobus_if.h"
+
+struct gpioled
+{
+ gpio_pin_t pin;
+ struct cdev *leddev;
+};
+
+struct gpioleds_softc
+{
+ device_t sc_dev;
+ struct gpioled *sc_leds;
+ int sc_led_count;
+ int sc_led_size;
+};
+
+/* UUID_DEVICE_PROPERTIES, daffd814-6eba-4d8c-8a91-bc9bbf4aa301. */
+static const uint8_t dev_prop_uuid[] = {
+ 0x14, 0xd8, 0xff, 0xda,
+ 0xba, 0x6e,
+ 0x8c, 0x4d,
+ 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01
+};
+
+static const char * gpio_leds_compat[] = {
+ "gpio-leds",
+ NULL
+};
+
+static void gpioled_control(void *priv, int onoff);
+
+typedef int (*dsd_property_cb)(const char *name, const ACPI_OBJECT *obj,
+ void *arg);
+
+static int
+acpi_dsd_walk(device_t dev, const uint8_t *uuid, dsd_property_cb cb, void *arg)
+{
+ ACPI_HANDLE h;
+ ACPI_BUFFER buf;
+ ACPI_OBJECT *dsd_data, *dsd_uuid;
+ ACPI_OBJECT *bp;
+ ACPI_STATUS status;
+ int error;
+
+ h = acpi_get_handle(dev);
+ if (h == NULL)
+ return (ENXIO);
+
+ buf.Pointer = NULL;
+ buf.Length = ACPI_ALLOCATE_BUFFER;
+ status = AcpiEvaluateObjectTyped(h, METHOD_NAME__DSD, NULL, &buf,
+ ACPI_TYPE_PACKAGE);
+ if (status == AE_NOT_FOUND)
+ return (ENOENT);
+ if (ACPI_FAILURE(status))
+ return (ENXIO);
+
+ error = 0;
+ bp = buf.Pointer;
+ if (bp->Package.Count % 2 != 0) {
+ error = EINVAL;
+ goto out;
+ }
+
+ for (int i = 0; i < bp->Package.Count; i += 2) {
+ dsd_uuid = &bp->Package.Elements[i];
+ dsd_data = &bp->Package.Elements[i + 1];
+
+ if (dsd_uuid->Type != ACPI_TYPE_BUFFER ||
+ dsd_uuid->Buffer.Length != UUID_BUFFER_LENGTH ||
+ dsd_data->Type != ACPI_TYPE_PACKAGE) {
+ error = EINVAL;
+ goto out;
+ }
+
+ if (memcmp(uuid, dsd_uuid->Buffer.Pointer,
+ UUID_BUFFER_LENGTH) != 0)
+ continue;
+
+ for (int p = 0; p < dsd_data->Package.Count; p++) {
+ ACPI_OBJECT *prop;
+ ACPI_OBJECT *name;
+ ACPI_OBJECT *val;
+
+ prop = &dsd_data->Package.Elements[p];
+ if (prop->Type != ACPI_TYPE_PACKAGE ||
+ prop->Package.Count != 2) {
+ error = EINVAL;
+ goto out;
+ }
+
+ name = &prop->Package.Elements[0];
+ val = &prop->Package.Elements[1];
+ if (name->Type != ACPI_TYPE_STRING) {
+ error = EINVAL;
+ goto out;
+ }
+
+ switch (val->Type) {
+ case ACPI_TYPE_PACKAGE:
+ /* Validate types used in the package. */
+ for (int e = 0; e < val->Package.Count; e++) {
+ ACPI_OBJECT *element;
+
+ element = &val->Package.Elements[e];
+ switch (element->Type) {
+ case ACPI_TYPE_STRING:
+ case ACPI_TYPE_INTEGER:
+ case ACPI_TYPE_LOCAL_REFERENCE:
+ break;
+ default:
+ error = EINVAL;
+ goto out;
+ }
+ }
+ /* FALLTHROUGH */
+ case ACPI_TYPE_STRING:
+ case ACPI_TYPE_INTEGER:
+ case ACPI_TYPE_LOCAL_REFERENCE:
+ /* Allow the callback to abort the walk. */
+ error = cb(name->String.Pointer, val, arg);
+ if (error != 0) {
+ if (error == EJUSTRETURN)
+ error = 0;
+ goto out;
+ }
+ break;
+ default:
+ error = EINVAL;
+ goto out;
+ }
+ }
+ }
+
+out:
+ AcpiOsFree(buf.Pointer);
+ return (error);
+}
+
+struct compat_check_arg
+{
+ const char * const * const strings;
+ int index;
+ bool found;
+};
+
+static int
+compat_check_cb(const char *name, const ACPI_OBJECT *obj, void *arg)
+{
+ struct compat_check_arg *cca = arg;
+
+ if (strcmp(name, "compatible") != 0)
+ return (0); /* keep going */
+ if (obj->Type != ACPI_TYPE_STRING)
+ return (EINVAL);
+ for (int i = 0; cca->strings[i] != NULL; i++) {
+ if (strcmp(cca->strings[i], obj->String.Pointer) == 0) {
+ cca->index = i;
+ cca->found = true;
+ break;
+ }
+ }
+ return (EJUSTRETURN); /* a single instance is expected */
+}
+
+static int
+acpi_dsd_check_compat(device_t dev, const char *strings[])
+{
+ struct compat_check_arg cca = {
+ .strings = strings,
+ .index = -1,
+ .found = false,
+ };
+ int error;
+
+ error = acpi_dsd_walk(dev, dev_prop_uuid, compat_check_cb, &cca);
+ if (error != 0)
+ return (error);
+ if (!cca.found)
+ return (ENXIO);
+ return (0);
+}
+
+struct acpi_gpio_iopin
+{
+ ACPI_HANDLE handle;
+ int pin_num;
+ int config;
+ int io_restriction;
+};
+
+struct crs_iopin_walk
+{
+ struct acpi_gpio_iopin *iopin;
+ ACPI_HANDLE handle;
+ int crs_index;
+ int pos;
+ int pin_index;
+};
+
+static ACPI_STATUS
+acpi_gpio_io_cb(ACPI_RESOURCE *res, void *arg)
+{
+ struct crs_iopin_walk *ctx = arg;
+ ACPI_RESOURCE_GPIO *gpio_res;
+ ACPI_HANDLE controller;
+ ACPI_STATUS status;
+
+ if (res->Type != ACPI_RESOURCE_TYPE_GPIO)
+ return (AE_OK);
+
+ if (ctx->pos++ != ctx->crs_index)
+ return (AE_OK);
+
+ gpio_res = &res->Data.Gpio;
+ if (gpio_res->ConnectionType == ACPI_RESOURCE_GPIO_TYPE_IO &&
+ gpio_res->ProducerConsumer == ACPI_CONSUMER) {
+ if (ctx->pin_index >= gpio_res->PinTableLength)
+ return (AE_ERROR);
+ ctx->iopin->pin_num = gpio_res->PinTable[ctx->pin_index];
+
+ /* Allow both relative and absolute paths. */
+ status = AcpiGetHandle(ctx->handle,
+ gpio_res->ResourceSource.StringPtr, &controller);
+ if (ACPI_FAILURE(status)) {
+ printf("failed to resolve GPIO resource source %s\n",
+ gpio_res->ResourceSource.StringPtr);
+ return (status);
+ }
+ ctx->iopin->handle = controller;
+
+ ctx->iopin->config = gpio_res->PinConfig;
+ ctx->iopin->io_restriction = gpio_res->IoRestriction;
+ return (AE_CTRL_TERMINATE);
+ }
+ return (AE_ERROR);
+}
+
+static int
+acpi_gpio_get_iopin(ACPI_HANDLE handle, int crs_index, int pin_index,
+ struct acpi_gpio_iopin *iopin)
+{
+ struct crs_iopin_walk ctx;
+ ACPI_STATUS status;
+
+ ctx.handle = handle;
+ ctx.iopin = iopin;
+ ctx.crs_index = crs_index;
+ ctx.pin_index = pin_index;
+ ctx.pos = 0;
+
+ status = AcpiWalkResources(handle, "_CRS", acpi_gpio_io_cb, &ctx);
+ if (ACPI_FAILURE(status))
+ return (ENXIO);
+ return (0);
+}
+
+static ACPI_STATUS
+gpioled_count_leds_cb(ACPI_HANDLE handle, UINT32 level, void *context,
+ void **status)
+{
+ struct gpioleds_softc *sc = context;
+
+ sc->sc_led_size++;
+ return (AE_OK);
+}
+
+struct led_props_arg
+{
+ struct gpioleds_softc *leds_sc;
+ gpio_pin_t pin;
+ char *name;
+ int state;
+};
+
+static void
+gpios_prop_cb(device_t dev, const ACPI_OBJECT *obj, struct led_props_arg *lpa)
+{
+ static char path[256];
+ ACPI_BUFFER buf;
+ struct acpi_gpio_iopin iopin;
+ device_t gpio_dev;
+ device_t bus_dev;
+ ACPI_HANDLE ref;
+ int crs_index, pin_index;
+ int active_low;
+ int err;
+
+ if (obj->Type != ACPI_TYPE_PACKAGE) {
+ device_printf(dev, "gpios property is not a package, "
+ "type = %d\n", obj->Type);
+ return;
+ }
+
+ if (obj->Package.Count != 4 ||
+ obj->Package.Elements[0].Type != ACPI_TYPE_LOCAL_REFERENCE ||
+ obj->Package.Elements[1].Type != ACPI_TYPE_INTEGER ||
+ obj->Package.Elements[2].Type != ACPI_TYPE_INTEGER ||
+ obj->Package.Elements[3].Type != ACPI_TYPE_INTEGER) {
+ device_printf(dev, "gpios package has incorrect structure\n");
+ return;
+ }
+
+ crs_index = obj->Package.Elements[1].Integer.Value;
+ pin_index = obj->Package.Elements[2].Integer.Value;
+ active_low = obj->Package.Elements[3].Integer.Value;
+
+ ref = acpi_GetReference(NULL, &obj->Package.Elements[0]);
+ if (ref == NULL) {
+ device_printf(dev, "failed to resolve _CRS device reference\n");
+ return;
+ }
+
+ err = acpi_gpio_get_iopin(ref, crs_index, pin_index, &iopin);
+ if (err != 0) {
+ buf.Length = sizeof(path);
+ buf.Pointer = path;
+ AcpiGetName(ref, ACPI_FULL_PATHNAME, &buf);
+ device_printf(dev, "failed to get GpioIo resource, "
+ "device = %s, CRS crs_index = %d, err = %d\n",
+ path, crs_index, err);
+ return;
+ }
+
+ /* Stash controller's path, we may need it later. */
+ buf.Length = sizeof(path);
+ buf.Pointer = path;
+ AcpiGetName(iopin.handle, ACPI_FULL_PATHNAME, &buf);
+
+ gpio_dev = acpi_get_device(iopin.handle);
+ if (gpio_dev == NULL || !device_is_attached(gpio_dev)) {
+ device_printf(dev, "GPIO controller device %s is not "
+ "attached\n", path);
+ return;
+ }
+
+ bus_dev = GPIO_GET_BUS(gpio_dev);
+ err = gpio_pin_get_by_bus_pinnum(bus_dev, iopin.pin_num, &lpa->pin);
+ if (err != 0) {
+ device_printf(dev, "failed to acquire pin %d from %s: %d\n",
+ iopin.pin_num, device_get_nameunit(bus_dev), err);
+ return;
+ }
+
+ /* TODO expose GPIO_ACTIVE_LOW. */
+ lpa->pin->flags = active_low ? 1 : 0;
+
+ if (bootverbose) {
+ device_printf(dev, "using pin %d of %s [%s] (active %s)\n",
+ iopin.pin_num, device_get_nameunit(gpio_dev), path,
+ active_low ? "low" : "high");
+
+ }
+}
+
+static int
+led_props_cb(const char *name, const ACPI_OBJECT *obj, void *arg)
+{
+ struct led_props_arg *lpa = arg;
+ struct gpioleds_softc *sc;
+ device_t dev;
+
+ sc = lpa->leds_sc;
+ dev = sc->sc_dev;
+
+ if (strcmp(name, "label") == 0) {
+ if (obj->Type == ACPI_TYPE_STRING)
+ lpa->name = strdup(obj->String.Pointer, M_DEVBUF);
+ else
+ device_printf(dev, "label is not a string, type = %d\n",
+ obj->Type);
+ } else if (strcmp(name, "default-state") == 0) {
+ if (obj->Type != ACPI_TYPE_STRING) {
+ device_printf(dev, "default-state is not a string, "
+ "type = %d\n", obj->Type);
+ } else if (strcasecmp(obj->String.Pointer, "on") == 0) {
+ lpa->state = 1;
+ } else if (strcasecmp(obj->String.Pointer, "off") == 0) {
+ lpa->state = 0;
+ } else if (strcasecmp(obj->String.Pointer, "keep") == 0) {
+ lpa->state = -1;
+ } else {
+ lpa->state = -1;
+ device_printf(dev, "unknown default-state value %s\n",
+ obj->String.Pointer);
+ }
+ } else if (strcmp(name, "gpios") == 0) {
+ gpios_prop_cb(dev, obj, lpa);
+ } else if (bootverbose) {
+ device_printf(dev, "ignored unknown property %s\n", name);
+ }
+ return (0);
+}
+
+static ACPI_STATUS
+gpioleds_attach_led_cb(ACPI_HANDLE handle, UINT32 level, void *context,
+ void **status)
+{
+ struct led_props_arg lpa;
+ struct gpioleds_softc *sc = context;
+ struct gpioled *led;
+ int error;
+
+ lpa.leds_sc = sc;
+ lpa.pin = NULL;
+ lpa.name = NULL;
+ lpa.state = 0;
+ error = acpi_dsd_walk(acpi_get_device(handle), dev_prop_uuid,
+ led_props_cb, &lpa);
+
+ if (error != 0)
+ goto out;
+ if (lpa.pin == NULL)
+ goto out;
+ if (lpa.name == NULL) {
+ gpio_pin_release(lpa.pin);
+ goto out;
+ }
+
+ led = &sc->sc_leds[sc->sc_led_count];
+ led->pin = lpa.pin;
+ gpio_pin_setflags(led->pin, GPIO_PIN_OUTPUT);
+ led->leddev = led_create_state(gpioled_control, led, lpa.name,
+ lpa.state);
+ sc->sc_led_count++;
+
+ if (bootverbose)
+ device_printf(sc->sc_dev, "added LED %s\n", lpa.name);
+
+out:
+ free(lpa.name, M_DEVBUF);
+ return (AE_OK);
+}
+
+static void
+gpioled_control(void *priv, int onoff)
+{
+ struct gpioled *led;
+
+ led = (struct gpioled *)priv;
+ gpio_pin_set_active(led->pin, onoff);
+}
+
+static void
+gpioleds_detach_led(struct gpioled *led)
+{
+
+ if (led->leddev != NULL)
+ led_destroy(led->leddev);
+
+ if (led->pin)
+ gpio_pin_release(led->pin);
+}
+
+static int
+gpioleds_probe(device_t dev)
+{
+ if (acpi_dsd_check_compat(dev, gpio_leds_compat) != 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "ACPI GPIO LEDs");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+gpioleds_attach(device_t dev)
+{
+ ACPI_HANDLE handle;
+ struct gpioleds_softc *sc;
+
+ handle = acpi_get_handle(dev);
+ sc = device_get_softc(dev);
+ sc->sc_dev = dev;
+
+ AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 1,
+ gpioled_count_leds_cb, NULL, sc, NULL);
+ if (sc->sc_led_size == 0)
+ return (ENXIO);
+
+ sc->sc_leds = malloc(sizeof(struct gpioled) * sc->sc_led_size,
+ M_DEVBUF, M_WAITOK | M_ZERO);
+
+ AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 1,
+ gpioleds_attach_led_cb, NULL, sc, NULL);
+
+ return (0);
+}
+
+static int
+gpioleds_detach(device_t dev)
+{
+ struct gpioleds_softc *sc;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ for (i = 0; i < sc->sc_led_count; i++)
+ gpioleds_detach_led(&sc->sc_leds[i]);
+
+ free(sc->sc_leds, M_DEVBUF);
+
+ return (0);
+}
+
+/*
+ * Turn off all LEDs.
+ * TODO support for retain-state-shutdown, retain-state-suspended.
+ * TODO support for restoring LED state after resume.
+ */
+static int
+gpioleds_shutdown(device_t dev)
+{
+ struct gpioleds_softc *sc;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ for (i = 0; i < sc->sc_led_count; i++)
+ gpioled_control(&sc->sc_leds[i], 0);
+
+ return (0);
+}
+
+static devclass_t acpi_gpioleds_devclass;
+
+static device_method_t acpi_gpioleds_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, gpioleds_probe),
+ DEVMETHOD(device_attach, gpioleds_attach),
+ DEVMETHOD(device_detach, gpioleds_detach),
+ DEVMETHOD(device_shutdown, gpioleds_shutdown),
+
+ DEVMETHOD_END
+};
+
+static driver_t acpi_gpioleds_driver = {
+ "gpioleds",
+ acpi_gpioleds_methods,
+ sizeof(struct gpioleds_softc),
+};
+
+DRIVER_MODULE(gpioleds, acpi, acpi_gpioleds_driver, acpi_gpioleds_devclass,
+ NULL, NULL);
+MODULE_DEPEND(gpioleds, acpi, 1, 1, 1);
+MODULE_DEPEND(gpioleds, gpiobus, 1, 1, 1);
+MODULE_VERSION(gpioleds, 1);
Index: sys/modules/gpio/gpioled/Makefile
===================================================================
--- sys/modules/gpio/gpioled/Makefile
+++ sys/modules/gpio/gpioled/Makefile
@@ -32,12 +32,20 @@
.PATH: ${SRCTOP}/sys/dev/gpio/
KMOD= gpioled
+
.if !empty(OPT_FDT)
SRCS= gpioled_fdt.c
+SRCS+= ofw_bus_if.h
.else
SRCS= gpioled.c
.endif
-SRCS+= device_if.h bus_if.h gpio_if.h gpiobus_if.h opt_platform.h ofw_bus_if.h
+
+.if ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \
+ ${MACHINE_CPUARCH} == "i386"
+SRCS+= gpioled_acpi.c opt_acpi.h acpi_if.h
+.endif
+
+SRCS+= device_if.h bus_if.h gpio_if.h gpiobus_if.h opt_platform.h
CFLAGS+= -I. -I${SRCTOP}/sys/dev/gpio/
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Apr 27, 7:55 AM (8 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
28437464
Default Alt Text
D26311.1777276517.diff (15 KB)
Attached To
Mode
D26311: gpioled_acpi: driver for GPIO controlled LEDs declared using ACPI _DSD
Attached
Detach File
Event Timeline
Log In to Comment