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;
+}

Reply via email to