aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/iicbus/controller/qcom/geni_iic.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/iicbus/controller/qcom/geni_iic.c')
-rw-r--r--sys/dev/iicbus/controller/qcom/geni_iic.c608
1 files changed, 608 insertions, 0 deletions
diff --git a/sys/dev/iicbus/controller/qcom/geni_iic.c b/sys/dev/iicbus/controller/qcom/geni_iic.c
new file mode 100644
index 000000000000..f53fc1d3f1cd
--- /dev/null
+++ b/sys/dev/iicbus/controller/qcom/geni_iic.c
@@ -0,0 +1,608 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Poul-Henning Kamp <phk@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 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.
+ *
+ * QualComm GENI I2C controller
+ *
+ * The GENI is actually a multi-protocol serial controller, so a lot of
+ * this can probably be shared if we ever get to those protocols.
+ *
+ * The best open "documentation" of the hardware is the Linux device driver
+ * from which much was learned, and we tip our hat to the authors of it.
+ */
+
+#include <sys/cdefs.h>
+
+#include "opt_acpi.h"
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/endian.h>
+#include <sys/time.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/bus.h>
+
+#include <machine/bus.h>
+#include <sys/rman.h>
+
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+
+#include <dev/iicbus/controller/qcom/geni_iic_var.h>
+
+#define GENI_ALL_REGISTERS(THIS_MACRO) \
+ THIS_MACRO(GENI_FORCE_DEFAULT_REG, 0x020) \
+ THIS_MACRO(GENI_OUTPUT_CTRL, 0x024) \
+ THIS_MACRO(GENI_STATUS, 0x040) \
+ THIS_MACRO(GENI_SER_M_CLK_CFG, 0x048) \
+ THIS_MACRO(GENI_SER_S_CLK_CFG, 0x04c) \
+ THIS_MACRO(GENI_IF_DISABLE_RO, 0x064) \
+ THIS_MACRO(GENI_FW_REVISION_RO, 0x068) \
+ THIS_MACRO(GENI_CLK_SEL, 0x07c) \
+ THIS_MACRO(GENI_CFG_SEQ_START, 0x084) \
+ THIS_MACRO(GENI_BYTE_GRANULARITY, 0x254) \
+ THIS_MACRO(GENI_DMA_MODE_EN, 0x258) \
+ THIS_MACRO(GENI_TX_PACKING_CFG0, 0x260) \
+ THIS_MACRO(GENI_TX_PACKING_CFG1, 0x264) \
+ THIS_MACRO(GENI_I2C_TX_TRANS_LEN, 0x26c) \
+ THIS_MACRO(GENI_I2C_RX_TRANS_LEN, 0x270) \
+ THIS_MACRO(GENI_I2C_SCL_COUNTERS, 0x278) \
+ THIS_MACRO(GENI_RX_PACKING_CFG0, 0x284) \
+ THIS_MACRO(GENI_RX_PACKING_CFG1, 0x288) \
+ THIS_MACRO(GENI_M_CMD0, 0x600) \
+ THIS_MACRO(GENI_M_CMD_CTRL_REG, 0x604) \
+ THIS_MACRO(GENI_M_IRQ_STATUS, 0x610) \
+ THIS_MACRO(GENI_M_IRQ_EN, 0x614) \
+ THIS_MACRO(GENI_M_IRQ_CLEAR, 0x618) \
+ THIS_MACRO(GENI_M_IRQ_EN_SET, 0x61c) \
+ THIS_MACRO(GENI_M_IRQ_EN_CLEAR, 0x620) \
+ THIS_MACRO(GENI_S_CMD0, 0x630) \
+ THIS_MACRO(GENI_S_CMD_CTRL_REG, 0x634) \
+ THIS_MACRO(GENI_S_IRQ_STATUS, 0x640) \
+ THIS_MACRO(GENI_S_IRQ_EN, 0x644) \
+ THIS_MACRO(GENI_S_IRQ_CLEAR, 0x648) \
+ THIS_MACRO(GENI_S_IRQ_EN_SET, 0x64c) \
+ THIS_MACRO(GENI_S_IRQ_EN_CLEAR, 0x650) \
+ THIS_MACRO(GENI_TX_FIFOn, 0x700) \
+ THIS_MACRO(GENI_RX_FIFOn, 0x780) \
+ THIS_MACRO(GENI_TX_FIFO_STATUS, 0x800) \
+ THIS_MACRO(GENI_RX_FIFO_STATUS, 0x804) \
+ THIS_MACRO(GENI_TX_WATERMARK_REG, 0x80c) \
+ THIS_MACRO(GENI_RX_WATERMARK_REG, 0x810) \
+ THIS_MACRO(GENI_RX_RFR_WATERMARK_REG, 0x814) \
+ THIS_MACRO(GENI_IOS, 0x908) \
+ THIS_MACRO(GENI_M_GP_LENGTH, 0x910) \
+ THIS_MACRO(GENI_S_GP_LENGTH, 0x914) \
+ THIS_MACRO(GENI_DMA_TX_IRQ_STAT, 0xc40) \
+ THIS_MACRO(GENI_DMA_TX_IRQ_CLR, 0xc44) \
+ THIS_MACRO(GENI_DMA_TX_IRQ_EN, 0xc48) \
+ THIS_MACRO(GENI_DMA_TX_IRQ_EN_CLR, 0xc4c) \
+ THIS_MACRO(GENI_DMA_TX_IRQ_EN_SET, 0xc50) \
+ THIS_MACRO(GENI_DMA_TX_FSM_RST, 0xc58) \
+ THIS_MACRO(GENI_DMA_RX_IRQ_STAT, 0xd40) \
+ THIS_MACRO(GENI_DMA_RX_IRQ_CLR, 0xd44) \
+ THIS_MACRO(GENI_DMA_RX_IRQ_EN, 0xd48) \
+ THIS_MACRO(GENI_DMA_RX_IRQ_EN_CLR, 0xd4c) \
+ THIS_MACRO(GENI_DMA_RX_IRQ_EN_SET, 0xd50) \
+ THIS_MACRO(GENI_DMA_RX_LEN_IN, 0xd54) \
+ THIS_MACRO(GENI_DMA_RX_FSM_RST, 0xd58) \
+ THIS_MACRO(GENI_IRQ_EN, 0xe1c) \
+ THIS_MACRO(GENI_HW_PARAM_0, 0xe24) \
+ THIS_MACRO(GENI_HW_PARAM_1, 0xe28)
+
+enum geni_registers {
+#define ITER_MACRO(name, offset) name = offset,
+ GENI_ALL_REGISTERS(ITER_MACRO)
+#undef ITER_MACRO
+};
+
+#define RD(sc, reg) bus_read_4((sc)->regs_res, reg)
+#define WR(sc, reg, val) bus_write_4((sc)->regs_res, reg, val)
+
+static void
+geni_dump_regs(geniiic_softc_t *sc)
+{
+ device_printf(sc->dev, "Register Dump\n");
+#define DUMP_MACRO(name, offset) \
+ device_printf(sc->dev, \
+ " %08x %04x " #name "\n", \
+ RD(sc, offset), offset);
+ GENI_ALL_REGISTERS(DUMP_MACRO)
+#undef DUMP_MACRO
+}
+
+static unsigned geniiic_debug_units = 0;
+
+static SYSCTL_NODE(_hw, OID_AUTO, geniiic, CTLFLAG_RW, 0, "GENI I2C");
+SYSCTL_INT(_hw_geniiic, OID_AUTO, debug_units, CTLFLAG_RWTUN,
+ &geniiic_debug_units, 1, "Bitmask of units to debug");
+
+
+static driver_filter_t geniiic_intr;
+
+static int
+geniiic_intr(void *cookie)
+{
+ uint32_t m_status, rx_fifo_status;
+ int retval = FILTER_STRAY;
+ geniiic_softc_t *sc = cookie;
+
+ mtx_lock_spin(&sc->intr_lock);
+ m_status = RD(sc, GENI_M_IRQ_STATUS);
+
+ rx_fifo_status = RD(sc, GENI_RX_FIFO_STATUS);
+ if (sc->rx_buf != NULL && rx_fifo_status & 0x3f) {
+
+ // Number of whole FIFO words, each 4 bytes
+ unsigned gotlen = (((rx_fifo_status & 0x3f) << 2)-1) * 4;
+
+ // Valid bytes in the last FIFO word
+ // (Field is 3 bits, we'll only ever see 0…3)
+ gotlen += (rx_fifo_status >> 28) & 0x7;
+
+ unsigned cnt;
+ for (cnt = 0; cnt < (rx_fifo_status & 0x3f); cnt++) {
+ uint32_t data = RD(sc, GENI_RX_FIFOn);
+ unsigned u;
+ for (u = 0; u < 4 && sc->rx_len && gotlen; u++) {
+ *sc->rx_buf++ = data & 0xff;
+ data >>= 8;
+ sc->rx_len--;
+ gotlen--;
+ }
+ }
+ }
+ if (m_status & (1<<26)) {
+ WR(sc, GENI_M_IRQ_CLEAR, (1<<26));
+ retval = FILTER_HANDLED;
+ }
+
+ if (m_status & (1<<0)) {
+ sc->rx_complete = true;
+ WR(sc, GENI_M_IRQ_EN_CLEAR, (1<<0));
+ WR(sc, GENI_M_IRQ_EN_CLEAR, (1<<26));
+ WR(sc, GENI_M_IRQ_CLEAR, (1<<0));
+ wakeup(sc);
+ retval = FILTER_HANDLED;
+ }
+ sc->cmd_status = m_status;
+
+ if (sc->rx_buf == NULL) {
+ device_printf(sc->dev,
+ "Interrupt m_stat %x rx_fifo_status %x retval %d\n",
+ m_status, rx_fifo_status, retval);
+ WR(sc, GENI_M_IRQ_EN, 0);
+ WR(sc, GENI_M_IRQ_CLEAR, m_status);
+ device_printf(sc->dev,
+ "Interrupt M_IRQ_STATUS 0x%x M_IRQ_EN 0x%x\n",
+ RD(sc, GENI_M_IRQ_STATUS), RD(sc, GENI_M_IRQ_EN));
+ device_printf(sc->dev,
+ "Interrupt S_IRQ_STATUS 0x%x S_IRQ_EN 0x%x\n",
+ RD(sc, GENI_S_IRQ_STATUS), RD(sc, GENI_S_IRQ_EN));
+ device_printf(sc->dev,
+ "Interrupt DMA_TX_IRQ_STAT 0x%x DMA_RX_IRQ_STAT 0x%x\n",
+ RD(sc, GENI_DMA_TX_IRQ_STAT), RD(sc, GENI_DMA_RX_IRQ_STAT));
+ device_printf(sc->dev,
+ "Interrupt DMA_TX_IRQ_EN 0x%x DMA_RX_IRQ_EN 0x%x\n",
+ RD(sc, GENI_DMA_TX_IRQ_EN), RD(sc, GENI_DMA_RX_IRQ_EN));
+ WR(sc, GENI_DMA_TX_IRQ_EN_CLR, RD(sc, GENI_DMA_TX_IRQ_STAT));
+ WR(sc, GENI_DMA_TX_IRQ_CLR, RD(sc, GENI_DMA_TX_IRQ_STAT));
+ WR(sc, GENI_DMA_RX_IRQ_EN_CLR, RD(sc, GENI_DMA_RX_IRQ_STAT));
+ WR(sc, GENI_DMA_RX_IRQ_CLR, RD(sc, GENI_DMA_RX_IRQ_STAT));
+ }
+ mtx_unlock_spin(&sc->intr_lock);
+ return(retval);
+}
+
+static int
+geniiic_wait_m_ireq(geniiic_softc_t *sc, uint32_t bits)
+{
+ uint32_t status;
+ int timeout;
+
+ for (timeout = 0; timeout < 10000; timeout++) {
+ status = RD(sc, GENI_M_IRQ_STATUS);
+ if (status & bits) {
+ return (0);
+ }
+ DELAY(10);
+ }
+ return (IIC_ETIMEOUT);
+}
+
+static int
+geniiic_read(geniiic_softc_t *sc,
+ uint8_t slave, uint8_t *buf, uint16_t len, bool nonfinal)
+{
+ uint32_t cmd, istatus;
+
+ istatus = RD(sc, GENI_M_IRQ_STATUS);
+ WR(sc, GENI_M_IRQ_CLEAR, istatus);
+
+ sc->rx_complete = false;
+ sc->rx_fifo = false;
+ sc->rx_buf = buf;
+ sc->rx_len = len;
+ WR(sc, GENI_I2C_RX_TRANS_LEN, len);
+
+ // GENI_M_CMD0_OPCODE_I2C_READ << M_OPCODE_SHFT
+ cmd = (0x2 << 27);
+
+ // GENI_M_CMD0_SLV_ADDR_SHIFT
+ cmd |= slave << 9;
+
+ if (nonfinal) {
+ // GENI_M_CMD0_STOP_STRETCH
+ cmd |= (1<<2);
+ }
+ WR(sc, GENI_RX_WATERMARK_REG, sc->rx_fifo_size - 4);
+
+ // CMD_DONE, RX_FIFO_WATERMARK
+ WR(sc, GENI_M_IRQ_EN, (1<<0) | (1<<26));
+
+ // M_IRQ
+ WR(sc, GENI_IRQ_EN, (1<<2));
+
+ WR(sc, GENI_M_CMD0, cmd);
+
+ mtx_lock_spin(&sc->intr_lock);
+ sc->rx_fifo = false;
+ unsigned msec;
+ for (msec = 0; msec < 100; msec++) {
+ msleep_spin_sbt(sc, &sc->intr_lock,
+ "geniwait", SBT_1MS, SBT_1MS / 10, 0);
+ if (sc->rx_complete)
+ break;
+ }
+ if (msec > sc->worst) {
+ device_printf(sc->dev,
+ "Tworst from %u to %u\n", sc->worst, msec);
+ if (msec != 100)
+ sc->worst = msec;
+ }
+
+ if (!sc->rx_complete) {
+ // S_GENI_CMD_CANCEL
+ WR(sc, GENI_M_CMD_CTRL_REG, (1<<2));
+
+ WR(sc, GENI_IRQ_EN, 0);
+ device_printf(sc->dev,
+ "Incomplete read (residual %x)\n", sc->rx_len);
+ }
+
+ sc->rx_buf = NULL;
+ len = sc->rx_len;
+ sc->rx_len = 0;
+
+ mtx_unlock_spin(&sc->intr_lock);
+
+#define COMPLAIN(about) \
+ device_printf(sc->dev, \
+ "read " about " slave=0x%x len=0x%x, cmd=0x%x cmd_status=0x%x\n", \
+ slave, len, cmd, sc->cmd_status \
+ )
+
+ if (geniiic_debug_units) {
+ unsigned unit = device_get_unit(sc->dev);
+ if (unit < 32 && geniiic_debug_units & (1<<unit) && len == 0) {
+ COMPLAIN("OK");
+ return(IIC_NOERR);
+ }
+ }
+ if (len == 0)
+ return(IIC_NOERR);
+
+ if (sc->cmd_status & (1<<10)) {
+ COMPLAIN("ESTATUS");
+ return(IIC_ESTATUS);
+ }
+ if (len) {
+ COMPLAIN("EUNDERFLOW");
+ return(IIC_EUNDERFLOW);
+ }
+ COMPLAIN("EBUSERR");
+ return (IIC_EBUSERR);
+#undef COMPLAIN
+}
+
+static int
+geniiic_write(geniiic_softc_t *sc,
+ uint8_t slave, uint8_t *buf, uint16_t len, bool nonfinal)
+{
+ uint32_t status, data, cmd;
+ int timeout, error;
+
+ status = RD(sc, GENI_M_IRQ_STATUS);
+ WR(sc, GENI_M_IRQ_CLEAR, status);
+
+ WR(sc, GENI_I2C_TX_TRANS_LEN, len);
+
+ // GENI_M_CMD0_OPCODE_I2C_WRITE << M_OPCODE_SHFT
+ cmd = (0x1 << 27);
+
+ // GENI_M_CMD0_SLV_ADDR_SHIFT
+ cmd |= slave << 9;
+
+ if (nonfinal) {
+ // GENI_M_CMD0_STOP_STRETCH
+ cmd |= (1<<2);
+ }
+ WR(sc, GENI_M_CMD0, cmd);
+ for(timeout = 0; len > 0 && timeout < 100; timeout++) {
+ status = RD(sc, GENI_TX_FIFO_STATUS);
+ if (status < 16) {
+ data = 0;
+ if (len) { data |= *buf << 0; buf++; len--; }
+ if (len) { data |= *buf << 8; buf++; len--; }
+ if (len) { data |= *buf << 16; buf++; len--; }
+ if (len) { data |= *buf << 24; buf++; len--; }
+ WR(sc, GENI_TX_FIFOn, data);
+ } else {
+ DELAY(10);
+ }
+ }
+
+ // GENI_M_IRQ_CMD_DONE
+ error = geniiic_wait_m_ireq(sc, 1);
+
+ if (len == 0 && error == 0)
+ return(IIC_NOERR);
+ device_printf(sc->dev,
+ "write ERR len=%d, error=%d cmd=0x%x\n", len, error, cmd);
+ return (IIC_EBUSERR);
+}
+
+static void
+geniiic_dumpmsg(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
+{
+ unsigned u;
+
+ device_printf(dev, "transfer:\n");
+ for (u = 0; u < nmsgs; u++) {
+ device_printf(dev,
+ " [%d] slave=0x%x, flags=0x%x len=0x%x buf=%p\n",
+ u, msgs[u].slave, msgs[u].flags, msgs[u].len, msgs[u].buf
+ );
+ }
+}
+
+int
+geniiic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
+{
+ geniiic_softc_t *sc = device_get_softc(dev);
+ unsigned u;
+ int error;
+
+ if (sc->nfail > 4) {
+ pause_sbt("geniic_fail", SBT_1S * 5, SBT_1S, 0);
+ return (IIC_ERESOURCE);
+ }
+
+ sx_xlock(&sc->real_bus_lock);
+
+ if (geniiic_debug_units) {
+ unsigned unit = device_get_unit(dev);
+ if (unit < 32 && geniiic_debug_units & (1<<unit)) {
+ geniiic_dumpmsg(dev, msgs, nmsgs);
+ }
+ }
+
+ error = 0;
+ for (u = 0; u < nmsgs; u++) {
+ bool nonfinal =
+ (u < nmsgs - 1) && (msgs[u].flags & IIC_M_NOSTOP);
+ unsigned slave = msgs[u].slave >> 1;
+ if (msgs[u].flags & IIC_M_RD) {
+ error = geniiic_read(sc,
+ slave, msgs[u].buf, msgs[u].len, nonfinal);
+ } else {
+ error = geniiic_write(sc,
+ slave, msgs[u].buf, msgs[u].len, nonfinal);
+ }
+ }
+ if (error) {
+ device_printf(dev, "transfer error %d\n", error);
+ geniiic_dumpmsg(dev, msgs, nmsgs);
+ }
+ if (error) {
+ geniiic_reset(dev, 0, 0, NULL);
+ }
+ if (error)
+ sc->nfail++;
+ else
+ sc->nfail = 0;
+ sx_xunlock(&sc->real_bus_lock);
+ return (error);
+}
+
+int
+geniiic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
+{
+ geniiic_softc_t *sc = device_get_softc(dev);
+ unsigned u;
+
+ device_printf(dev, "reset\n");
+ WR(sc, GENI_M_IRQ_EN, 0);
+ WR(sc, GENI_M_IRQ_CLEAR, ~0);
+ WR(sc, GENI_DMA_TX_IRQ_EN_CLR, ~0);
+ WR(sc, GENI_DMA_TX_IRQ_CLR, ~0);
+ WR(sc, GENI_DMA_RX_IRQ_EN_CLR, ~0);
+ WR(sc, GENI_DMA_RX_IRQ_CLR, ~0);
+
+ // S_GENI_CMD_ABORT
+ WR(sc, GENI_M_CMD_CTRL_REG, (1<<1));
+
+ WR(sc, GENI_DMA_RX_FSM_RST, 1);
+ for (u = 0; u < 1000; u++) {
+ if (RD(sc, GENI_DMA_RX_IRQ_STAT) & 0x8)
+ break;
+ DELAY(10);
+ }
+ if (u > 0)
+ device_printf(dev, "RXRESET time %u\n", u);
+ WR(sc, GENI_DMA_TX_FSM_RST, 1);
+ for (u = 0; u < 1000; u++) {
+ if (RD(sc, GENI_DMA_TX_IRQ_STAT) & 0x8)
+ break;
+ DELAY(10);
+ }
+ if (u > 0)
+ device_printf(dev, "TXRESET time %u\n", u);
+ return (0);
+}
+
+int
+geniiic_callback(device_t dev, int index, caddr_t data)
+{
+ geniiic_softc_t *sc = device_get_softc(dev);
+ int error = 0;
+
+ return(0);
+ switch (index) {
+ case IIC_REQUEST_BUS:
+ if (sx_try_xlock(&sc->bus_lock) == 0)
+ error = IIC_EBUSBSY;
+ else
+ sc->bus_locked = true;
+ break;
+
+ case IIC_RELEASE_BUS:
+ if (!sc->bus_locked) {
+ device_printf(dev, "Unlocking unlocked bus\n");
+ }
+ sc->bus_locked = false;
+ sx_xunlock(&sc->bus_lock);
+ break;
+
+ default:
+ device_printf(dev, "callback unknown %d\n", index);
+ error = errno2iic(EINVAL);
+ }
+
+ return (error);
+}
+
+int
+geniiic_attach(geniiic_softc_t *sc)
+{
+ int error = 0;
+
+ if (bootverbose)
+ geni_dump_regs(sc);
+ mtx_init(&sc->intr_lock, "geniiic intr lock", NULL, MTX_SPIN);
+ sx_init(&sc->real_bus_lock, "geniiic real bus lock");
+ sx_init(&sc->bus_lock, "geniiic bus lock");
+
+ sc->rx_fifo_size = (RD(sc, GENI_HW_PARAM_1) >> 16) & 0x3f;
+ device_printf(sc->dev, " RX fifo size= 0x%x\n", sc->rx_fifo_size);
+
+ // We might want to set/check the following registers:
+ // GENI_BYTE_GRANULARITY (0x00000000)
+ // GENI_TX_PACKING_CFG0 (0x0007f8fe)
+ // GENI_TX_PACKING_CFG1 (000ffefe)
+ // GENI_RX_PACKING_CFG0 (0x0007f8fe)
+ // GENI_RX_PACKING_CFG1 (000ffefe)
+
+ sc->iicbus = device_add_child(sc->dev, "iicbus", DEVICE_UNIT_ANY);
+ if (sc->iicbus == NULL) {
+ device_printf(sc->dev, "iicbus driver not found\n");
+ return(ENXIO);
+ }
+
+ error = bus_setup_intr(sc->dev,
+ sc->intr_res, INTR_TYPE_MISC | INTR_MPSAFE,
+ geniiic_intr, NULL, sc, &sc->intr_handle);
+ if (error) {
+ device_printf(sc->dev,
+ "Unable to setup irq: error %d\n", error);
+ }
+
+ bus_attach_children(sc->dev);
+ return (error);
+}
+
+int
+geniiic_detach(geniiic_softc_t *sc)
+{
+ int error = 0;
+
+ error = bus_generic_detach(sc->dev);
+ if (error)
+ return (error);
+
+ WR(sc, GENI_M_IRQ_EN, 0);
+
+ if (sc->intr_handle) {
+ bus_teardown_intr(sc->dev, sc->intr_res, sc->intr_handle);
+ }
+
+ sx_xlock(&sc->bus_lock);
+ sx_xlock(&sc->real_bus_lock);
+
+ geniiic_reset(sc->dev, 0, 0, NULL);
+ sc->iicbus = NULL;
+ sc->intr_handle = NULL;
+
+ sx_xunlock(&sc->real_bus_lock);
+ sx_xunlock(&sc->bus_lock);
+
+ sx_destroy(&sc->real_bus_lock);
+ sx_destroy(&sc->bus_lock);
+
+ mtx_destroy(&sc->intr_lock);
+ return (error);
+}
+
+int
+geniiic_suspend(geniiic_softc_t *sc)
+{
+ int error;
+
+ device_printf(sc->dev, "suspend method is NO-OP (good luck!)\n");
+
+ error = bus_generic_suspend(sc->dev);
+
+ return (error);
+}
+
+int geniiic_resume(geniiic_softc_t *sc)
+{
+ int error;
+
+ device_printf(sc->dev, "resume method is NO-OP (good luck!)\n");
+
+ error = bus_generic_resume(sc->dev);
+
+ return (error);
+}
+
+DRIVER_MODULE(iicbus, geniiic, iicbus_driver, NULL, NULL);
+DRIVER_MODULE(acpi_iicbus, geniiic, acpi_iicbus_driver, NULL, NULL);
+MODULE_DEPEND(geniiic, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+MODULE_VERSION(geniiic, 1);