Module Name: src Committed By: jmcneill Date: Sun Dec 8 20:49:15 UTC 2024
Modified Files: src/sys/dev/acpi: acpivar.h files.acpi Added Files: src/sys/dev/acpi: acpi_gpio.c acpi_gpio.h qcomgpio.c qcomgpioreg.h qcomiic.c Log Message: Add support for Snapdragon X Elite GPIO and I2C controllers. To generate a diff of this commit: cvs rdiff -u -r0 -r1.1 src/sys/dev/acpi/acpi_gpio.c \ src/sys/dev/acpi/acpi_gpio.h src/sys/dev/acpi/qcomgpio.c \ src/sys/dev/acpi/qcomgpioreg.h src/sys/dev/acpi/qcomiic.c cvs rdiff -u -r1.90 -r1.91 src/sys/dev/acpi/acpivar.h cvs rdiff -u -r1.133 -r1.134 src/sys/dev/acpi/files.acpi Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/dev/acpi/acpivar.h diff -u src/sys/dev/acpi/acpivar.h:1.90 src/sys/dev/acpi/acpivar.h:1.91 --- src/sys/dev/acpi/acpivar.h:1.90 Wed Mar 20 03:14:45 2024 +++ src/sys/dev/acpi/acpivar.h Sun Dec 8 20:49:14 2024 @@ -1,4 +1,4 @@ -/* $NetBSD: acpivar.h,v 1.90 2024/03/20 03:14:45 riastradh Exp $ */ +/* $NetBSD: acpivar.h,v 1.91 2024/12/08 20:49:14 jmcneill Exp $ */ /* * Copyright 2001 Wasabi Systems, Inc. @@ -135,6 +135,10 @@ struct acpi_devnode { bus_dma_tag_t ad_dmat; /* Bus DMA tag for device */ bus_dma_tag_t ad_dmat64; /* Bus DMA tag for device (64-bit) */ + device_t ad_gpiodev; /* GPIO controller device */ + int (*ad_gpio_translate)(void *, ACPI_INTEGER, void **); + void *ad_gpio_priv; /* private data for translate */ + SIMPLEQ_ENTRY(acpi_devnode) ad_list; SIMPLEQ_ENTRY(acpi_devnode) ad_child_list; SIMPLEQ_HEAD(, acpi_devnode) ad_child_head; Index: src/sys/dev/acpi/files.acpi diff -u src/sys/dev/acpi/files.acpi:1.133 src/sys/dev/acpi/files.acpi:1.134 --- src/sys/dev/acpi/files.acpi:1.133 Mon Aug 26 13:38:28 2024 +++ src/sys/dev/acpi/files.acpi Sun Dec 8 20:49:14 2024 @@ -1,4 +1,4 @@ -# $NetBSD: files.acpi,v 1.133 2024/08/26 13:38:28 riastradh Exp $ +# $NetBSD: files.acpi,v 1.134 2024/12/08 20:49:14 jmcneill Exp $ defflag opt_acpi.h ACPIVERBOSE ACPI_DEBUG ACPI_ACTIVATE_DEV ACPI_DSDT_OVERRIDE ACPI_SCANPCI ACPI_BREAKPOINT @@ -22,6 +22,7 @@ file dev/acpi/acpi.c acpi file dev/acpi/acpi_debug.c acpi file dev/acpi/acpi_dev.c acpi file dev/acpi/acpi_event.c acpi +file dev/acpi/acpi_gpio.c acpi file dev/acpi/acpi_i2c.c acpi file dev/acpi/acpi_mcfg.c acpi & pci file dev/acpi/acpi_pci.c acpi & pci @@ -336,4 +337,14 @@ file dev/acpi/dwcmmc_acpi.c dwcmmc_acpi attach dwcwdt at acpinodebus with dwcwdt_acpi file dev/acpi/dwcwdt_acpi.c dwcwdt_acpi +# Qualcomm GPIO +device qcomgpio: gpiobus +attach qcomgpio at acpinodebus +file dev/acpi/qcomgpio.c qcomgpio + +# Qualcomm I2C controller +device qcomiic: i2cbus +attach qcomiic at acpinodebus +file dev/acpi/qcomiic.c qcomiic + include "dev/acpi/wmi/files.wmi" Added files: Index: src/sys/dev/acpi/acpi_gpio.c diff -u /dev/null src/sys/dev/acpi/acpi_gpio.c:1.1 --- /dev/null Sun Dec 8 20:49:15 2024 +++ src/sys/dev/acpi/acpi_gpio.c Sun Dec 8 20:49:14 2024 @@ -0,0 +1,193 @@ +/* $NetBSD: acpi_gpio.c,v 1.1 2024/12/08 20:49:14 jmcneill 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 <jmcne...@invisible.ca>. + * + * 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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: acpi_gpio.c,v 1.1 2024/12/08 20:49:14 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/gpio.h> + +#include <dev/acpi/acpireg.h> +#include <dev/acpi/acpivar.h> +#include <dev/acpi/acpi_gpio.h> + +int +acpi_gpio_register(struct acpi_devnode *ad, device_t dev, + int (*translate)(void *, ACPI_INTEGER, void **), void *priv) +{ + if (ad->ad_gpiodev != NULL) { + device_printf(dev, "%s already registered\n", + device_xname(ad->ad_gpiodev)); + return EBUSY; + } + + ad->ad_gpiodev = dev; + ad->ad_gpio_translate = translate; + ad->ad_gpio_priv = priv; + + return 0; +} + +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->PinTable[0], gpiop); + if (xpin == -1) { + /* Pin could not be translated. */ + return AE_NOT_IMPLEMENTED; + } + + *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; + } + + rv = acpi_gpio_translate(ctx.res, gpiop, pin); + if (ACPI_FAILURE(rv)) { + printf("%s: translate failed: %s\n", __func__, + AcpiFormatException(rv)); + return rv; + } + + gpio = ctx.res; + 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 **gpio, 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, gpio, pin); +} Index: src/sys/dev/acpi/acpi_gpio.h diff -u /dev/null src/sys/dev/acpi/acpi_gpio.h:1.1 --- /dev/null Sun Dec 8 20:49:15 2024 +++ src/sys/dev/acpi/acpi_gpio.h Sun Dec 8 20:49:14 2024 @@ -0,0 +1,40 @@ +/* $NetBSD: acpi_gpio.h,v 1.1 2024/12/08 20:49:14 jmcneill 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 <jmcne...@invisible.ca>. + * + * 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. + */ + +#ifndef _DEV_ACPI_ACPI_GPIO_H +#define _DEV_ACPI_ACPI_GPIO_H + +int acpi_gpio_register(struct acpi_devnode *, device_t, + int (*)(void *, ACPI_INTEGER, void **), void *); +ACPI_STATUS acpi_gpio_get_int(ACPI_HANDLE, u_int, void **, int *, int *); +ACPI_STATUS acpi_gpio_get_io(ACPI_HANDLE, u_int, void **, int *); + +#endif /* !_DEV_ACPI_ACPI_GPIO_H */ Index: src/sys/dev/acpi/qcomgpio.c diff -u /dev/null src/sys/dev/acpi/qcomgpio.c:1.1 --- /dev/null Sun Dec 8 20:49:15 2024 +++ src/sys/dev/acpi/qcomgpio.c Sun Dec 8 20:49:14 2024 @@ -0,0 +1,506 @@ +/* $NetBSD: qcomgpio.c,v 1.1 2024/12/08 20:49:14 jmcneill 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 <jmcne...@invisible.ca>. + * + * 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. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: qcomgpio.c,v 1.1 2024/12/08 20:49:14 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/device.h> +#include <sys/gpio.h> +#include <sys/queue.h> +#include <sys/kmem.h> +#include <sys/mutex.h> + +#include <dev/acpi/acpireg.h> +#include <dev/acpi/acpivar.h> +#include <dev/acpi/acpi_intr.h> +#include <dev/acpi/acpi_event.h> +#include <dev/acpi/acpi_gpio.h> +#include <dev/acpi/qcomgpioreg.h> + +#include <dev/gpio/gpiovar.h> + +typedef enum { + QCOMGPIO_X1E, +} qcomgpio_type; + +struct qcomgpio_config { + u_int num_pins; + int (*translate)(ACPI_INTEGER); +}; + +struct qcomgpio_intr_handler { + int (*ih_func)(void *); + void *ih_arg; + int ih_pin; + LIST_ENTRY(qcomgpio_intr_handler) ih_list; +}; + +struct qcomgpio_softc { + device_t sc_dev; + device_t sc_gpiodev; + bus_space_handle_t sc_bsh; + bus_space_tag_t sc_bst; + const struct qcomgpio_config *sc_config; + struct gpio_chipset_tag sc_gc; + gpio_pin_t *sc_pins; + LIST_HEAD(, qcomgpio_intr_handler) sc_intrs; + kmutex_t sc_lock; +}; + +#define RD4(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define WR4(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) + +static int qcomgpio_match(device_t, cfdata_t, void *); +static void qcomgpio_attach(device_t, device_t, void *); + +static int qcomgpio_pin_read(void *, int); +static void qcomgpio_pin_write(void *, int, int); +static void qcomgpio_pin_ctl(void *, int, int); +static void * qcomgpio_intr_establish(void *, int, int, int, + int (*)(void *), void *); +static void qcomgpio_intr_disestablish(void *, void *); +static bool qcomgpio_intr_str(void *, int, int, char *, size_t); +static void qcomgpio_intr_mask(void *, void *); +static void qcomgpio_intr_unmask(void *, void *); + +static int qcomgpio_acpi_translate(void *, ACPI_INTEGER, void **); +static void qcomgpio_register_event(void *, struct acpi_event *, + ACPI_RESOURCE_GPIO *); +static int qcomgpio_intr(void *); + +CFATTACH_DECL_NEW(qcomgpio, sizeof(struct qcomgpio_softc), + qcomgpio_match, qcomgpio_attach, NULL, NULL); + +static int +qcomgpio_x1e_translate(ACPI_INTEGER val) +{ + switch (val) { + case 0x33: + return 51; + case 0x180: + return 67; + case 0x380: + return 3; + default: + return -1; + } +} + +static struct qcomgpio_config qcomgpio_x1e_config = { + .num_pins = 239, + .translate = qcomgpio_x1e_translate, +}; + +static const struct device_compatible_entry compat_data[] = { + { .compat = "QCOM0C0C", .data = &qcomgpio_x1e_config }, + DEVICE_COMPAT_EOL +}; + +static int +qcomgpio_match(device_t parent, cfdata_t cf, void *aux) +{ + struct acpi_attach_args *aa = aux; + + return acpi_compatible_match(aa, compat_data); +} + +static void +qcomgpio_attach(device_t parent, device_t self, void *aux) +{ + struct qcomgpio_softc * const sc = device_private(self); + struct acpi_attach_args *aa = aux; + struct gpiobus_attach_args gba; + ACPI_HANDLE hdl = aa->aa_node->ad_handle; + struct acpi_resources res; + struct acpi_mem *mem; + struct acpi_irq *irq; + ACPI_STATUS rv; + int error, pin; + void *ih; + + sc->sc_dev = self; + sc->sc_config = acpi_compatible_lookup(aa, compat_data)->data; + sc->sc_bst = aa->aa_memt; + KASSERT(sc->sc_config != NULL); + LIST_INIT(&sc->sc_intrs); + mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM); + + rv = acpi_resource_parse(sc->sc_dev, hdl, "_CRS", + &res, &acpi_resource_parse_ops_default); + if (ACPI_FAILURE(rv)) { + return; + } + + mem = acpi_res_mem(&res, 0); + if (mem == NULL) { + aprint_error_dev(self, "couldn't find mem resource\n"); + goto done; + } + + irq = acpi_res_irq(&res, 0); + if (irq == NULL) { + aprint_error_dev(self, "couldn't find irq resource\n"); + goto done; + } + + error = bus_space_map(sc->sc_bst, mem->ar_base, mem->ar_length, 0, + &sc->sc_bsh); + if (error) { + aprint_error_dev(self, "couldn't map registers\n"); + goto done; + } + + sc->sc_pins = kmem_zalloc(sizeof(*sc->sc_pins) * + sc->sc_config->num_pins, KM_SLEEP); + for (pin = 0; pin < sc->sc_config->num_pins; pin++) { + sc->sc_pins[pin].pin_num = pin; + sc->sc_pins[pin].pin_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; + sc->sc_pins[pin].pin_intrcaps = + GPIO_INTR_POS_EDGE | GPIO_INTR_NEG_EDGE | + GPIO_INTR_DOUBLE_EDGE | GPIO_INTR_HIGH_LEVEL | + GPIO_INTR_LOW_LEVEL | GPIO_INTR_MPSAFE; + /* It's not safe to read all pins, so leave pin state unknown */ + sc->sc_pins[pin].pin_state = 0; + } + + sc->sc_gc.gp_cookie = sc; + sc->sc_gc.gp_pin_read = qcomgpio_pin_read; + sc->sc_gc.gp_pin_write = qcomgpio_pin_write; + sc->sc_gc.gp_pin_ctl = qcomgpio_pin_ctl; + sc->sc_gc.gp_intr_establish = qcomgpio_intr_establish; + sc->sc_gc.gp_intr_disestablish = qcomgpio_intr_disestablish; + sc->sc_gc.gp_intr_str = qcomgpio_intr_str; + sc->sc_gc.gp_intr_mask = qcomgpio_intr_mask; + sc->sc_gc.gp_intr_unmask = qcomgpio_intr_unmask; + + rv = acpi_event_create_gpio(self, hdl, qcomgpio_register_event, sc); + if (ACPI_FAILURE(rv)) { + if (rv != AE_NOT_FOUND) { + aprint_error_dev(self, "failed to create events: %s\n", + AcpiFormatException(rv)); + } + goto done; + } + + ih = acpi_intr_establish(self, (uint64_t)(uintptr_t)hdl, + IPL_VM, false, qcomgpio_intr, sc, device_xname(self)); + if (ih == NULL) { + aprint_error_dev(self, "couldn't establish interrupt\n"); + goto done; + } + + memset(&gba, 0, sizeof(gba)); + gba.gba_gc = &sc->sc_gc; + gba.gba_pins = sc->sc_pins; + gba.gba_npins = sc->sc_config->num_pins; + sc->sc_gpiodev = config_found(self, &gba, gpiobus_print, + CFARGS(.iattr = "gpiobus")); + if (sc->sc_gpiodev != NULL) { + acpi_gpio_register(aa->aa_node, self, + qcomgpio_acpi_translate, sc); + } + +done: + acpi_resource_cleanup(&res); +} + +static int +qcomgpio_acpi_translate(void *priv, ACPI_INTEGER pin, void **gpiop) +{ + struct qcomgpio_softc * const sc = priv; + int xpin; + + xpin = sc->sc_config->translate(pin); + + aprint_debug_dev(sc->sc_dev, "translate %#lx -> %u\n", pin, xpin); + + if (gpiop != NULL) { + if (sc->sc_gpiodev != NULL) { + *gpiop = device_private(sc->sc_gpiodev); + } else { + device_printf(sc->sc_dev, + "no gpiodev for pin %#lx -> %u\n", pin, xpin); + xpin = -1; + } + } + + return xpin; +} + +static int +qcomgpio_acpi_event(void *priv) +{ + struct acpi_event * const ev = priv; + + acpi_event_notify(ev); + + return 1; +} + +static void +qcomgpio_register_event(void *priv, struct acpi_event *ev, + ACPI_RESOURCE_GPIO *gpio) +{ + struct qcomgpio_softc * const sc = priv; + int irqmode; + void *ih; + + const int pin = qcomgpio_acpi_translate(sc, gpio->PinTable[0], NULL); + + if (pin < 0) { + aprint_error_dev(sc->sc_dev, + "ignoring event for pin %#x (out of range)\n", + gpio->PinTable[0]); + return; + } + + 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; + } + } + + ih = qcomgpio_intr_establish(sc, pin, IPL_VM, irqmode, + qcomgpio_acpi_event, ev); + if (ih == NULL) { + aprint_error_dev(sc->sc_dev, + "couldn't register event for pin %#x\n", + gpio->PinTable[0]); + } +} + +static int +qcomgpio_pin_read(void *priv, int pin) +{ + struct qcomgpio_softc * const sc = priv; + uint32_t val; + + if (pin < 0 || pin >= sc->sc_config->num_pins) { + return 0; + } + + val = RD4(sc, TLMM_GPIO_IN_OUT(pin)); + return (val & TLMM_GPIO_IN_OUT_GPIO_IN) != 0; +} + +static void +qcomgpio_pin_write(void *priv, int pin, int pinval) +{ + struct qcomgpio_softc * const sc = priv; + uint32_t val; + + if (pin < 0 || pin >= sc->sc_config->num_pins) { + return; + } + + val = RD4(sc, TLMM_GPIO_IN_OUT(pin)); + if (pinval) { + val |= TLMM_GPIO_IN_OUT_GPIO_OUT; + } else { + val &= ~TLMM_GPIO_IN_OUT_GPIO_OUT; + } + WR4(sc, TLMM_GPIO_IN_OUT(pin), val); +} + +static void +qcomgpio_pin_ctl(void *priv, int pin, int flags) +{ + /* Nothing to do here, as firmware has already configured pins. */ +} + +static void * +qcomgpio_intr_establish(void *priv, int pin, int ipl, int irqmode, + int (*func)(void *), void *arg) +{ + struct qcomgpio_softc * const sc = priv; + struct qcomgpio_intr_handler *qih, *qihp; + uint32_t dect, pol; + uint32_t val; + + if (pin < 0 || pin >= sc->sc_config->num_pins) { + return NULL; + } + if (ipl != IPL_VM) { + device_printf(sc->sc_dev, "%s: only IPL_VM supported\n", + __func__); + return NULL; + } + + qih = kmem_alloc(sizeof(*qih), KM_SLEEP); + qih->ih_func = func; + qih->ih_arg = arg; + qih->ih_pin = pin; + + mutex_enter(&sc->sc_lock); + + LIST_FOREACH(qihp, &sc->sc_intrs, ih_list) { + if (qihp->ih_pin == qih->ih_pin) { + mutex_exit(&sc->sc_lock); + kmem_free(qih, sizeof(*qih)); + device_printf(sc->sc_dev, + "%s: pin %d already establish\n", __func__, pin); + return NULL; + } + } + + LIST_INSERT_HEAD(&sc->sc_intrs, qih, ih_list); + + if ((irqmode & GPIO_INTR_LEVEL_MASK) != 0) { + dect = TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_LEVEL; + pol = (irqmode & GPIO_INTR_HIGH_LEVEL) != 0 ? + TLMM_GPIO_INTR_CFG_INTR_POL_CTL : 0; + } else { + KASSERT((irqmode & GPIO_INTR_EDGE_MASK) != 0); + if ((irqmode & GPIO_INTR_NEG_EDGE) != 0) { + dect = TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_NEG; + pol = TLMM_GPIO_INTR_CFG_INTR_POL_CTL; + } else if ((irqmode & GPIO_INTR_POS_EDGE) != 0) { + dect = TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_POS; + pol = TLMM_GPIO_INTR_CFG_INTR_POL_CTL; + } else { + KASSERT((irqmode & GPIO_INTR_DOUBLE_EDGE) != 0); + dect = TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_BOTH; + pol = 0; + } + } + + val = RD4(sc, TLMM_GPIO_INTR_CFG(pin)); + val &= ~TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_MASK; + val |= __SHIFTIN(dect, TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_MASK); + val &= ~TLMM_GPIO_INTR_CFG_INTR_POL_CTL; + val |= pol; + val &= ~TLMM_GPIO_INTR_CFG_TARGET_PROC_MASK; + val |= __SHIFTIN(TLMM_GPIO_INTR_CFG_TARGET_PROC_RPM, + TLMM_GPIO_INTR_CFG_TARGET_PROC_MASK); + val |= TLMM_GPIO_INTR_CFG_INTR_RAW_STATUS_EN; + val |= TLMM_GPIO_INTR_CFG_INTR_ENABLE; + WR4(sc, TLMM_GPIO_INTR_CFG(pin), val); + + mutex_exit(&sc->sc_lock); + + return qih; +} + +static void +qcomgpio_intr_disestablish(void *priv, void *ih) +{ + struct qcomgpio_softc * const sc = priv; + struct qcomgpio_intr_handler *qih = ih; + uint32_t val; + + mutex_enter(&sc->sc_lock); + + LIST_REMOVE(qih, ih_list); + + val = RD4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin)); + val &= ~TLMM_GPIO_INTR_CFG_INTR_ENABLE; + WR4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin), val); + + mutex_exit(&sc->sc_lock); + + kmem_free(qih, sizeof(*qih)); +} + +static bool +qcomgpio_intr_str(void *priv, int pin, int irqmode, char *buf, size_t buflen) +{ + struct qcomgpio_softc * const sc = priv; + int rv; + + rv = snprintf(buf, buflen, "%s pin %d", device_xname(sc->sc_dev), pin); + + return rv < buflen; +} + +static void +qcomgpio_intr_mask(void *priv, void *ih) +{ + struct qcomgpio_softc * const sc = priv; + struct qcomgpio_intr_handler *qih = ih; + uint32_t val; + + val = RD4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin)); + val &= ~TLMM_GPIO_INTR_CFG_INTR_ENABLE; + WR4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin), val); +} + +static void +qcomgpio_intr_unmask(void *priv, void *ih) +{ + struct qcomgpio_softc * const sc = priv; + struct qcomgpio_intr_handler *qih = ih; + uint32_t val; + + val = RD4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin)); + val |= TLMM_GPIO_INTR_CFG_INTR_ENABLE; + WR4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin), val); +} + +static int +qcomgpio_intr(void *priv) +{ + struct qcomgpio_softc * const sc = priv; + struct qcomgpio_intr_handler *qih; + int rv = 0; + + mutex_enter(&sc->sc_lock); + + LIST_FOREACH(qih, &sc->sc_intrs, ih_list) { + const int pin = qih->ih_pin; + uint32_t val; + + val = RD4(sc, TLMM_GPIO_INTR_STATUS(pin)); + if ((val & TLMM_GPIO_INTR_STATUS_INTR_STATUS) != 0) { + rv |= qih->ih_func(qih->ih_arg); + + val &= ~TLMM_GPIO_INTR_STATUS_INTR_STATUS; + WR4(sc, TLMM_GPIO_INTR_STATUS(pin), val); + } + } + + mutex_exit(&sc->sc_lock); + + return rv; +} Index: src/sys/dev/acpi/qcomgpioreg.h diff -u /dev/null src/sys/dev/acpi/qcomgpioreg.h:1.1 --- /dev/null Sun Dec 8 20:49:15 2024 +++ src/sys/dev/acpi/qcomgpioreg.h Sun Dec 8 20:49:14 2024 @@ -0,0 +1,42 @@ +/* $NetBSD: qcomgpioreg.h,v 1.1 2024/12/08 20:49:14 jmcneill Exp $ */ +/* + * Copyright (c) 2022 Mark Kettenis <kette...@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef QCOMGPIOREG_H +#define QCOMGPIOREG_H + +#define _TLMM_GPIO_PIN_OFFSET(pin, reg) ((pin) * 0x1000 + (reg)) + +#define TLMM_GPIO_IN_OUT(pin) _TLMM_GPIO_PIN_OFFSET(pin, 0x4) +#define TLMM_GPIO_IN_OUT_GPIO_IN __BIT(0) +#define TLMM_GPIO_IN_OUT_GPIO_OUT __BIT(1) + +#define TLMM_GPIO_INTR_CFG(pin) _TLMM_GPIO_PIN_OFFSET(pin, 0x8) +#define TLMM_GPIO_INTR_CFG_TARGET_PROC_MASK __BITS(7,5) +#define TLMM_GPIO_INTR_CFG_TARGET_PROC_RPM 3 +#define TLMM_GPIO_INTR_CFG_INTR_RAW_STATUS_EN __BIT(4) +#define TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_MASK __BITS(3,2) +#define TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_LEVEL 0 +#define TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_POS 1 +#define TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_NEG 2 +#define TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_BOTH 3 +#define TLMM_GPIO_INTR_CFG_INTR_POL_CTL __BIT(1) +#define TLMM_GPIO_INTR_CFG_INTR_ENABLE __BIT(0) + +#define TLMM_GPIO_INTR_STATUS(pin) _TLMM_GPIO_PIN_OFFSET(pin, 0xc) +#define TLMM_GPIO_INTR_STATUS_INTR_STATUS __BIT(0) + +#endif /* !QCOMGPIOREG_H */ Index: src/sys/dev/acpi/qcomiic.c diff -u /dev/null src/sys/dev/acpi/qcomiic.c:1.1 --- /dev/null Sun Dec 8 20:49:15 2024 +++ src/sys/dev/acpi/qcomiic.c Sun Dec 8 20:49:14 2024 @@ -0,0 +1,293 @@ +/* $NetBSD: qcomiic.c,v 1.1 2024/12/08 20:49:14 jmcneill Exp $ */ + +/* $OpenBSD: qciic.c,v 1.7 2024/10/02 21:21:32 kettenis Exp $ */ +/* + * Copyright (c) 2022 Mark Kettenis <kette...@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/device.h> + +#include <dev/acpi/acpireg.h> +#include <dev/acpi/acpivar.h> +#include <dev/acpi/acpi_intr.h> +#include <dev/acpi/acpi_i2c.h> + +#include <dev/i2c/i2cvar.h> + +/* Registers */ +#define GENI_I2C_TX_TRANS_LEN 0x26c +#define GENI_I2C_RX_TRANS_LEN 0x270 +#define GENI_M_CMD0 0x600 +#define GENI_M_CMD0_OPCODE_I2C_WRITE (0x1 << 27) +#define GENI_M_CMD0_OPCODE_I2C_READ (0x2 << 27) +#define GENI_M_CMD0_SLV_ADDR_SHIFT 9 +#define GENI_M_CMD0_STOP_STRETCH (1 << 2) +#define GENI_M_IRQ_STATUS 0x610 +#define GENI_M_IRQ_CLEAR 0x618 +#define GENI_M_IRQ_CMD_DONE (1 << 0) +#define GENI_TX_FIFO 0x700 +#define GENI_RX_FIFO 0x780 +#define GENI_TX_FIFO_STATUS 0x800 +#define GENI_RX_FIFO_STATUS 0x804 +#define GENI_RX_FIFO_STATUS_WC(val) ((val) & 0xffffff) + +#define HREAD4(sc, reg) \ + bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)) +#define HWRITE4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) + +struct qciic_softc { + device_t sc_dev; + struct acpi_devnode *sc_acpi; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + + device_t sc_iic; + + struct i2c_controller sc_ic; +}; + +static int qciic_acpi_match(device_t, cfdata_t, void *); +static void qciic_acpi_attach(device_t, device_t, void *); +static void qciic_acpi_attach_late(device_t); +static int qciic_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t, + void *, size_t, int); + +CFATTACH_DECL_NEW(qcomiic, sizeof(struct qciic_softc), + qciic_acpi_match, qciic_acpi_attach, NULL, NULL); + +static const struct device_compatible_entry compat_data[] = { + { .compat = "QCOM0610" }, + { .compat = "QCOM0811" }, + { .compat = "QCOM0C10" }, + DEVICE_COMPAT_EOL +}; + +static int +qciic_acpi_match(device_t parent, cfdata_t cf, void *aux) +{ + struct acpi_attach_args *aa = aux; + + return acpi_compatible_match(aa, compat_data); +} + +static void +qciic_acpi_attach(device_t parent, device_t self, void *aux) +{ + struct qciic_softc * const sc = device_private(self); + struct acpi_attach_args *aa = aux; + struct acpi_resources res; + struct acpi_mem *mem; + struct acpi_irq *irq; + ACPI_STATUS rv; + int error; + + sc->sc_dev = self; + sc->sc_acpi = aa->aa_node; + sc->sc_iot = aa->aa_memt; + + rv = acpi_resource_parse(sc->sc_dev, aa->aa_node->ad_handle, "_CRS", + &res, &acpi_resource_parse_ops_default); + if (ACPI_FAILURE(rv)) { + return; + } + + mem = acpi_res_mem(&res, 0); + if (mem == NULL) { + aprint_error_dev(self, "couldn't find mem resource\n"); + goto done; + } + + irq = acpi_res_irq(&res, 0); + if (irq == NULL) { + aprint_error_dev(self, "couldn't find irq resource\n"); + goto done; + } + + error = bus_space_map(sc->sc_iot, mem->ar_base, mem->ar_length, 0, + &sc->sc_ioh); + if (error != 0) { + aprint_error_dev(self, "couldn't map registers\n"); + return; + } + + iic_tag_init(&sc->sc_ic); + sc->sc_ic.ic_cookie = sc; + sc->sc_ic.ic_exec = qciic_exec; + + /* + * Defer the attachment of I2C bus until all ACPI devices have been + * enumerated, as other devices may provide resources for devices + * attached to the I2C bus. + */ + config_defer(self, qciic_acpi_attach_late); + +done: + acpi_resource_cleanup(&res); +} + +static void +qciic_acpi_attach_late(device_t self) +{ + struct i2cbus_attach_args iba; + struct qciic_softc * const sc = device_private(self); + + memset(&iba, 0, sizeof(iba)); + iba.iba_tag = &sc->sc_ic; + iba.iba_child_devices = acpi_enter_i2c_devs(self, sc->sc_acpi); + + config_found(self, &iba, iicbus_print, CFARGS_NONE); +} + +static int +qciic_wait(struct qciic_softc *sc, uint32_t bits) +{ + uint32_t stat; + int timo; + + for (timo = 50000; timo > 0; timo--) { + stat = HREAD4(sc, GENI_M_IRQ_STATUS); + if (stat & bits) + break; + delay(10); + } + if (timo == 0) + return ETIMEDOUT; + + return 0; +} + +static int +qciic_read(struct qciic_softc *sc, uint8_t *buf, size_t len) +{ + uint32_t stat, word; + int timo, i; + + word = 0; + for (i = 0; i < len; i++) { + if ((i % 4) == 0) { + for (timo = 50000; timo > 0; timo--) { + stat = HREAD4(sc, GENI_RX_FIFO_STATUS); + if (GENI_RX_FIFO_STATUS_WC(stat) > 0) + break; + delay(10); + } + if (timo == 0) + return ETIMEDOUT; + word = HREAD4(sc, GENI_RX_FIFO); + } + buf[i] = word >> ((i % 4) * 8); + } + + return 0; +} + +static int +qciic_write(struct qciic_softc *sc, const uint8_t *buf, size_t len) +{ + uint32_t stat, word; + int timo, i; + + word = 0; + for (i = 0; i < len; i++) { + word |= buf[i] << ((i % 4) * 8); + if ((i % 4) == 3 || i == (len - 1)) { + for (timo = 50000; timo > 0; timo--) { + stat = HREAD4(sc, GENI_TX_FIFO_STATUS); + if (stat < 16) + break; + delay(10); + } + if (timo == 0) + return ETIMEDOUT; + HWRITE4(sc, GENI_TX_FIFO, word); + word = 0; + } + } + + return 0; +} + +static int +qciic_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmd, + size_t cmdlen, void *buf, size_t buflen, int flags) +{ + struct qciic_softc *sc = cookie; + uint32_t m_cmd, m_param, stat; + int error; + + m_param = addr << GENI_M_CMD0_SLV_ADDR_SHIFT; + m_param |= GENI_M_CMD0_STOP_STRETCH; + + if (buflen == 0 && I2C_OP_STOP_P(op)) + m_param &= ~GENI_M_CMD0_STOP_STRETCH; + + if (cmdlen > 0) { + stat = HREAD4(sc, GENI_M_IRQ_STATUS); + HWRITE4(sc, GENI_M_IRQ_CLEAR, stat); + HWRITE4(sc, GENI_I2C_TX_TRANS_LEN, cmdlen); + m_cmd = GENI_M_CMD0_OPCODE_I2C_WRITE | m_param; + HWRITE4(sc, GENI_M_CMD0, m_cmd); + + error = qciic_write(sc, cmd, cmdlen); + if (error) + return error; + + error = qciic_wait(sc, GENI_M_IRQ_CMD_DONE); + if (error) + return error; + } + + if (buflen == 0) + return 0; + + if (I2C_OP_STOP_P(op)) + m_param &= ~GENI_M_CMD0_STOP_STRETCH; + + if (I2C_OP_READ_P(op)) { + stat = HREAD4(sc, GENI_M_IRQ_STATUS); + HWRITE4(sc, GENI_M_IRQ_CLEAR, stat); + HWRITE4(sc, GENI_I2C_RX_TRANS_LEN, buflen); + m_cmd = GENI_M_CMD0_OPCODE_I2C_READ | m_param; + HWRITE4(sc, GENI_M_CMD0, m_cmd); + + error = qciic_read(sc, buf, buflen); + if (error) + return error; + + error = qciic_wait(sc, GENI_M_IRQ_CMD_DONE); + if (error) + return error; + } else { + stat = HREAD4(sc, GENI_M_IRQ_STATUS); + HWRITE4(sc, GENI_M_IRQ_CLEAR, stat); + HWRITE4(sc, GENI_I2C_TX_TRANS_LEN, buflen); + m_cmd = GENI_M_CMD0_OPCODE_I2C_WRITE | m_param; + HWRITE4(sc, GENI_M_CMD0, m_cmd); + + error = qciic_write(sc, buf, buflen); + if (error) + return error; + + error = qciic_wait(sc, GENI_M_IRQ_CMD_DONE); + if (error) + return error; + } + + return 0; +}