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 <[email protected]>.
+ *
+ * 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 <[email protected]>.
+ *
+ * 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 <[email protected]>.
+ *
+ * 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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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;
+}