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 + * + * 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 +__FBSDID("$FreeBSD$"); + +#include "opt_platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#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/