root/sys/dev/ic/dwiic.c
/* $NetBSD: dwiic.c,v 1.11 2025/09/15 15:18:42 thorpej Exp $ */

/* $OpenBSD: dwiic.c,v 1.4 2018/05/23 22:08:00 kettenis Exp $ */

/*-
 * Copyright (c) 2017 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Manuel Bouyer.
 *
 * 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.
 */
/*
 * Synopsys DesignWare I2C controller
 *
 * Copyright (c) 2015, 2016 joshua stein <jcs@openbsd.org>
 *
 * Permission to use, copy, modify, and/or 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/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dwiic.c,v 1.11 2025/09/15 15:18:42 thorpej Exp $");

#include <sys/param.h>

#include <sys/atomic.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/systm.h>

#include <dev/ic/dwiic_var.h>

//#define DWIIC_DEBUG

#ifdef DWIIC_DEBUG
#define DPRINTF(x) printf x
#else
#define DPRINTF(x)
#endif

/* register offsets */
#define DW_IC_CON               0x0
#define DW_IC_TAR               0x4
#define DW_IC_DATA_CMD          0x10
#define DW_IC_SS_SCL_HCNT       0x14
#define DW_IC_SS_SCL_LCNT       0x18
#define DW_IC_FS_SCL_HCNT       0x1c
#define DW_IC_FS_SCL_LCNT       0x20
#define DW_IC_INTR_STAT         0x2c
#define DW_IC_INTR_MASK         0x30
#define DW_IC_RAW_INTR_STAT     0x34
#define DW_IC_RX_TL             0x38
#define DW_IC_TX_TL             0x3c
#define DW_IC_CLR_INTR          0x40
#define DW_IC_CLR_RX_UNDER      0x44
#define DW_IC_CLR_RX_OVER       0x48
#define DW_IC_CLR_TX_OVER       0x4c
#define DW_IC_CLR_RD_REQ        0x50
#define DW_IC_CLR_TX_ABRT       0x54
#define DW_IC_CLR_RX_DONE       0x58
#define DW_IC_CLR_ACTIVITY      0x5c
#define DW_IC_CLR_STOP_DET      0x60
#define DW_IC_CLR_START_DET     0x64
#define DW_IC_CLR_GEN_CALL      0x68
#define DW_IC_ENABLE            0x6c
#define DW_IC_STATUS            0x70
#define DW_IC_TXFLR             0x74
#define DW_IC_RXFLR             0x78
#define DW_IC_SDA_HOLD          0x7c
#define DW_IC_TX_ABRT_SOURCE    0x80
#define DW_IC_ENABLE_STATUS     0x9c
#define DW_IC_COMP_PARAM_1      0xf4
#define DW_IC_COMP_VERSION      0xf8
#define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A
#define DW_IC_COMP_TYPE         0xfc
#define DW_IC_COMP_TYPE_VALUE   0x44570140

#define DW_IC_CON_MASTER        0x1
#define DW_IC_CON_SPEED_STD     0x2
#define DW_IC_CON_SPEED_FAST    0x4
#define DW_IC_CON_10BITADDR_MASTER 0x10
#define DW_IC_CON_RESTART_EN    0x20
#define DW_IC_CON_SLAVE_DISABLE 0x40

#define DW_IC_DATA_CMD_READ     0x100
#define DW_IC_DATA_CMD_STOP     0x200
#define DW_IC_DATA_CMD_RESTART  0x400

#define DW_IC_INTR_RX_UNDER     0x001
#define DW_IC_INTR_RX_OVER      0x002
#define DW_IC_INTR_RX_FULL      0x004
#define DW_IC_INTR_TX_OVER      0x008
#define DW_IC_INTR_TX_EMPTY     0x010
#define DW_IC_INTR_RD_REQ       0x020
#define DW_IC_INTR_TX_ABRT      0x040
#define DW_IC_INTR_RX_DONE      0x080
#define DW_IC_INTR_ACTIVITY     0x100
#define DW_IC_INTR_STOP_DET     0x200
#define DW_IC_INTR_START_DET    0x400
#define DW_IC_INTR_GEN_CALL     0x800

#define DW_IC_STATUS_ACTIVITY   0x1

/* hardware abort codes from the DW_IC_TX_ABRT_SOURCE register */
#define ABRT_7B_ADDR_NOACK      0
#define ABRT_10ADDR1_NOACK      1
#define ABRT_10ADDR2_NOACK      2
#define ABRT_TXDATA_NOACK       3
#define ABRT_GCALL_NOACK        4
#define ABRT_GCALL_READ         5
#define ABRT_SBYTE_ACKDET       7
#define ABRT_SBYTE_NORSTRT      9
#define ABRT_10B_RD_NORSTRT     10
#define ABRT_MASTER_DIS         11
#define ARB_LOST                12

static int      dwiic_init(struct dwiic_softc *);
static void     dwiic_enable(struct dwiic_softc *, bool);
static uint32_t dwiic_read(struct dwiic_softc *, int);
static void     dwiic_write(struct dwiic_softc *, int, uint32_t);
static int      dwiic_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
                    size_t, void *, size_t, int);

bool
dwiic_attach(struct dwiic_softc *sc)
{
        if (sc->sc_power != NULL) {
                if (!sc->sc_power(sc, 1)) {
                        aprint_error_dev(sc->sc_dev, "failed to power up\n");
                        return 0;
                }
        }

        /* fetch timing parameters */
        if (sc->ss_hcnt == 0)
                sc->ss_hcnt = dwiic_read(sc, DW_IC_SS_SCL_HCNT);
        if (sc->ss_lcnt == 0)
                sc->ss_lcnt = dwiic_read(sc, DW_IC_SS_SCL_LCNT);
        if (sc->fs_hcnt == 0)
                sc->fs_hcnt = dwiic_read(sc, DW_IC_FS_SCL_HCNT);
        if (sc->fs_lcnt == 0)
                sc->fs_lcnt = dwiic_read(sc, DW_IC_FS_SCL_LCNT);
        if (sc->sda_hold_time == 0)
                sc->sda_hold_time = dwiic_read(sc, DW_IC_SDA_HOLD);

        if (dwiic_init(sc)) {
                aprint_error_dev(sc->sc_dev, "failed initializing\n");
                return 0;
        }

        /* leave the controller disabled */
        dwiic_write(sc, DW_IC_INTR_MASK, 0);
        dwiic_enable(sc, 0);
        dwiic_read(sc, DW_IC_CLR_INTR);

        mutex_init(&sc->sc_int_lock, MUTEX_DEFAULT, IPL_VM);
        cv_init(&sc->sc_int_readwait, "dwiicr");
        cv_init(&sc->sc_int_writewait, "dwiicw");
        cv_init(&sc->sc_int_stopwait, "dwiics");

        /* setup and attach iic bus */
        iic_tag_init(&sc->sc_i2c_tag);
        sc->sc_i2c_tag.ic_cookie = sc;
        sc->sc_i2c_tag.ic_exec = dwiic_i2c_exec;

        /* config_found for "i2cbus" is done in the bus-attachment glue */

        atomic_store_release(&sc->sc_attached, true);

        return 1;
}

int
dwiic_detach(device_t self, int flags)
{
        struct dwiic_softc *sc = device_private(self);

        dwiic_enable(sc, 0);
        if (sc->sc_ih != NULL) {
                intr_disestablish(sc->sc_ih);
                sc->sc_ih = NULL;
        }

        return 0;
}

bool
dwiic_suspend(device_t self, const pmf_qual_t *qual)
{
        struct dwiic_softc *sc = device_private(self);

        /* disable controller */
        dwiic_enable(sc, 0);

        /* disable interrupts */
        dwiic_write(sc, DW_IC_INTR_MASK, 0);
        dwiic_read(sc, DW_IC_CLR_INTR);
        if (sc->sc_power != NULL) {
                if (!sc->sc_power(sc, 0)) {
                        device_printf(sc->sc_dev, "failed to power off\n");
                }
        }
        return true;
}

bool
dwiic_resume(device_t self, const pmf_qual_t *qual)
{
        struct dwiic_softc *sc = device_private(self);
        if (sc->sc_power != NULL) {
                if (!sc->sc_power(sc, 1)) {
                        device_printf(sc->sc_dev, "failed to power up\n");
                        return false;
                }
        }
        dwiic_init(sc);
        return true;
}

static uint32_t
dwiic_read(struct dwiic_softc *sc, int offset)
{
        u_int32_t b = bus_space_read_4(sc->sc_iot, sc->sc_ioh, offset);

        DPRINTF(("%s: read at 0x%x = 0x%x\n", device_xname(sc->sc_dev), offset, b));

        return b;
}

static void
dwiic_write(struct dwiic_softc *sc, int offset, uint32_t val)
{
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, offset, val);

        DPRINTF(("%s: write at 0x%x: 0x%x\n", device_xname(sc->sc_dev), offset,
            val));
}

static int
dwiic_init(struct dwiic_softc *sc)
{
        uint32_t reg;

        /* make sure we're talking to a device we know */
        reg = dwiic_read(sc, DW_IC_COMP_TYPE);
        if (reg != DW_IC_COMP_TYPE_VALUE) {
                DPRINTF(("%s: invalid component type 0x%x\n",
                    device_xname(sc->sc_dev), reg));
                return 1;
        }

        /* disable the adapter */
        dwiic_enable(sc, 0);

        /* write standard-mode SCL timing parameters */
        dwiic_write(sc, DW_IC_SS_SCL_HCNT, sc->ss_hcnt);
        dwiic_write(sc, DW_IC_SS_SCL_LCNT, sc->ss_lcnt);

        /* and fast-mode SCL timing parameters */
        dwiic_write(sc, DW_IC_FS_SCL_HCNT, sc->fs_hcnt);
        dwiic_write(sc, DW_IC_FS_SCL_LCNT, sc->fs_lcnt);

        /* SDA hold time */
        reg = dwiic_read(sc, DW_IC_COMP_VERSION);
        if (reg >= DW_IC_SDA_HOLD_MIN_VERS)
                dwiic_write(sc, DW_IC_SDA_HOLD, sc->sda_hold_time);

        /* FIFO threshold levels */
        sc->tx_fifo_depth = 32;
        sc->rx_fifo_depth = 32;
        dwiic_write(sc, DW_IC_TX_TL, sc->tx_fifo_depth / 2);
        dwiic_write(sc, DW_IC_RX_TL, 0);

        /* configure as i2c master with fast speed */
        sc->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE |
            DW_IC_CON_RESTART_EN | DW_IC_CON_SPEED_FAST;
        dwiic_write(sc, DW_IC_CON, sc->master_cfg);

        return 0;
}

static void
dwiic_enable(struct dwiic_softc *sc, bool enable)
{
        int retries;

        for (retries = 100; retries > 0; retries--) {
                dwiic_write(sc, DW_IC_ENABLE, enable);
                if ((dwiic_read(sc, DW_IC_ENABLE_STATUS) & 1) == enable)
                        return;

                DELAY(25);
        }

        device_printf(sc->sc_dev, "failed to %sable\n",
            (enable ? "en" : "dis"));
}

static int
dwiic_i2c_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmdbuf,
    size_t cmdlen, void *buf, size_t len, int flags)
{
        struct dwiic_softc *sc = cookie;
        u_int32_t ic_con, st, cmd, resp;
        int retries, tx_limit, rx_avail, x, readpos;
        const uint8_t *bcmd;
        uint8_t *bdata;

        if (sc->sc_poll)
                flags |= I2C_F_POLL;

        DPRINTF(("%s: %s: op %d, addr 0x%02x, cmdlen %zu, len %zu, "
            "flags 0x%02x\n", device_xname(sc->sc_dev), __func__, op, addr, cmdlen,
            len, flags));

        /* setup transfer */
        sc->sc_i2c_xfer.op = op;
        sc->sc_i2c_xfer.buf = buf;
        sc->sc_i2c_xfer.len = len;
        sc->sc_i2c_xfer.flags = flags;
        sc->sc_i2c_xfer.error = 0;

        /* wait for bus to be idle */
        for (retries = 100; retries > 0; retries--) {
                st = dwiic_read(sc, DW_IC_STATUS);
                if (!(st & DW_IC_STATUS_ACTIVITY))
                        break;
                DELAY(1000);
        }
        DPRINTF(("%s: %s: status 0x%x\n", device_xname(sc->sc_dev), __func__, st));
        if (st & DW_IC_STATUS_ACTIVITY) {
                return (1);
        }

        /* disable controller */
        dwiic_enable(sc, 0);

        /* set slave address */
        ic_con = dwiic_read(sc, DW_IC_CON);
        ic_con &= ~DW_IC_CON_10BITADDR_MASTER;
        dwiic_write(sc, DW_IC_CON, ic_con);
        dwiic_write(sc, DW_IC_TAR, addr);

        /* disable interrupts */
        dwiic_write(sc, DW_IC_INTR_MASK, 0);
        dwiic_read(sc, DW_IC_CLR_INTR);

        /* enable controller */
        dwiic_enable(sc, 1);

        /* wait until the controller is ready for commands */
        if (flags & I2C_F_POLL)
                DELAY(200);
        else {
                mutex_enter(&sc->sc_int_lock);
                dwiic_read(sc, DW_IC_CLR_INTR);
                dwiic_write(sc, DW_IC_INTR_MASK, DW_IC_INTR_TX_EMPTY);

                if (cv_timedwait(&sc->sc_int_writewait,
                    &sc->sc_int_lock, hz / 2) != 0)
                        device_printf(sc->sc_dev,
                            "timed out waiting for tx_empty intr\n");
                dwiic_write(sc, DW_IC_INTR_MASK, 0);
                dwiic_read(sc, DW_IC_CLR_INTR);
                mutex_exit(&sc->sc_int_lock);
        }

        /* send our command, one byte at a time */
        if (cmdlen > 0) {
                bcmd = (const void *)cmdbuf;

                DPRINTF(("%s: %s: sending cmd (len %zu):", device_xname(sc->sc_dev),
                    __func__, cmdlen));
                for (x = 0; x < cmdlen; x++)
                        DPRINTF((" %02x", bcmd[x]));
                DPRINTF(("\n"));

                tx_limit = sc->tx_fifo_depth - dwiic_read(sc, DW_IC_TXFLR);
                if (cmdlen > tx_limit) {
                        /* TODO */
                        device_printf(sc->sc_dev, "can't write %zu (> %d)\n",
                            cmdlen, tx_limit);
                        sc->sc_i2c_xfer.error = 1;
                        return (1);
                }

                for (x = 0; x < cmdlen; x++) {
                        cmd = bcmd[x];
                        /*
                         * Generate STOP condition if this is the last
                         * byte of the transfer.
                         */
                        if (x == (cmdlen - 1) && len == 0 && I2C_OP_STOP_P(op))
                                cmd |= DW_IC_DATA_CMD_STOP;
                        dwiic_write(sc, DW_IC_DATA_CMD, cmd);
                }
        }

        bdata = (void *)buf;
        x = readpos = 0;
        tx_limit = sc->tx_fifo_depth - dwiic_read(sc, DW_IC_TXFLR);

        DPRINTF(("%s: %s: need to read %zu bytes, can send %d read reqs\n",
                device_xname(sc->sc_dev), __func__, len, tx_limit));

        while (x < len) {
                if (I2C_OP_WRITE_P(op))
                        cmd = bdata[x];
                else
                        cmd = DW_IC_DATA_CMD_READ;

                /*
                 * Generate RESTART condition if we're reversing
                 * direction.
                 */
                if (x == 0 && cmdlen > 0 && I2C_OP_READ_P(op))
                        cmd |= DW_IC_DATA_CMD_RESTART;
                /*
                 * Generate STOP condition on the last byte of the
                 * transfer.
                 */
                if (x == (len - 1) && I2C_OP_STOP_P(op))
                        cmd |= DW_IC_DATA_CMD_STOP;

                dwiic_write(sc, DW_IC_DATA_CMD, cmd);

                tx_limit--;
                x++;

                /*
                 * As TXFLR fills up, we need to clear it out by reading all
                 * available data.
                 */
                while (I2C_OP_READ_P(op) && (tx_limit == 0 || x == len)) {
                        DPRINTF(("%s: %s: tx_limit %d, sent %d read reqs\n",
                            device_xname(sc->sc_dev), __func__, tx_limit, x));

                        if (flags & I2C_F_POLL) {
                                for (retries = 100; retries > 0; retries--) {
                                        rx_avail = dwiic_read(sc, DW_IC_RXFLR);
                                        if (rx_avail > 0)
                                                break;
                                        DELAY(50);
                                }
                        } else {
                                mutex_enter(&sc->sc_int_lock);
                                dwiic_read(sc, DW_IC_CLR_INTR);
                                dwiic_write(sc, DW_IC_INTR_MASK,
                                    DW_IC_INTR_RX_FULL);

                                if (cv_timedwait(&sc->sc_int_readwait,
                                    &sc->sc_int_lock, hz / 2) != 0)
                                        device_printf(sc->sc_dev,
                                            "timed out waiting for "
                                            "rx_full intr\n");

                                dwiic_write(sc, DW_IC_INTR_MASK, 0);
                                dwiic_read(sc, DW_IC_CLR_INTR);
                                mutex_exit(&sc->sc_int_lock);
                                rx_avail = dwiic_read(sc, DW_IC_RXFLR);
                        }

                        if (rx_avail == 0) {
                                device_printf(sc->sc_dev,
                                    "timed out reading remaining %d\n",
                                    (int)(len - 1 - readpos));
                                sc->sc_i2c_xfer.error = 1;
                                return (1);
                        }

                        DPRINTF(("%s: %s: %d avail to read (%zu remaining)\n",
                            device_xname(sc->sc_dev), __func__, rx_avail,
                            len - readpos));

                        while (rx_avail > 0) {
                                resp = dwiic_read(sc, DW_IC_DATA_CMD);
                                if (readpos < len) {
                                        bdata[readpos] = resp;
                                        readpos++;
                                }
                                rx_avail--;
                        }

                        if (readpos >= len)
                                break;

                        DPRINTF(("%s: still need to read %d bytes\n",
                            device_xname(sc->sc_dev), (int)(len - readpos)));
                        tx_limit = sc->tx_fifo_depth -
                            dwiic_read(sc, DW_IC_TXFLR);
                }
        }

        if (I2C_OP_STOP_P(op) && I2C_OP_WRITE_P(op)) {
                if (flags & I2C_F_POLL) {
                        /* wait for bus to be idle */
                        for (retries = 100; retries > 0; retries--) {
                                st = dwiic_read(sc, DW_IC_STATUS);
                                if (!(st & DW_IC_STATUS_ACTIVITY))
                                        break;
                                DELAY(1000);
                        }
                        if (st & DW_IC_STATUS_ACTIVITY)
                                device_printf(sc->sc_dev, "timed out waiting "
                                    "for bus idle\n");
                } else {
                        mutex_enter(&sc->sc_int_lock);
                        dwiic_read(sc, DW_IC_CLR_INTR);
                        dwiic_write(sc, DW_IC_INTR_MASK,
                            DW_IC_INTR_STOP_DET);
                        if (cv_timedwait(&sc->sc_int_stopwait,
                            &sc->sc_int_lock, hz / 2) != 0)
                                device_printf(sc->sc_dev, "timed out waiting "
                                    "for stop intr\n");
                        dwiic_write(sc, DW_IC_INTR_MASK, 0);
                        dwiic_read(sc, DW_IC_CLR_INTR);
                        mutex_exit(&sc->sc_int_lock);
                }
        }

        return 0;
}

static uint32_t
dwiic_read_clear_intrbits(struct dwiic_softc *sc)
{
       uint32_t stat;

       stat = dwiic_read(sc, DW_IC_INTR_STAT);

       if (stat & DW_IC_INTR_RX_UNDER)
               dwiic_read(sc, DW_IC_CLR_RX_UNDER);
       if (stat & DW_IC_INTR_RX_OVER)
               dwiic_read(sc, DW_IC_CLR_RX_OVER);
       if (stat & DW_IC_INTR_TX_OVER)
               dwiic_read(sc, DW_IC_CLR_TX_OVER);
       if (stat & DW_IC_INTR_RD_REQ)
               dwiic_read(sc, DW_IC_CLR_RD_REQ);
       if (stat & DW_IC_INTR_TX_ABRT)
               dwiic_read(sc, DW_IC_CLR_TX_ABRT);
       if (stat & DW_IC_INTR_RX_DONE)
               dwiic_read(sc, DW_IC_CLR_RX_DONE);
       if (stat & DW_IC_INTR_ACTIVITY)
               dwiic_read(sc, DW_IC_CLR_ACTIVITY);
       if (stat & DW_IC_INTR_STOP_DET)
               dwiic_read(sc, DW_IC_CLR_STOP_DET);
       if (stat & DW_IC_INTR_START_DET)
               dwiic_read(sc, DW_IC_CLR_START_DET);
       if (stat & DW_IC_INTR_GEN_CALL)
               dwiic_read(sc, DW_IC_CLR_GEN_CALL);

       return stat;
}

int
dwiic_intr(void *arg)
{
        struct dwiic_softc *sc = arg;
        uint32_t en, stat;

        /*
         * Give up if attach hasn't succeeded.  If it failed, nothing
         * to do here.  If it is still ongoing and simply hasn't yet
         * succeeded, interrupts from the device are masked -- so this
         * interrupt must be shared with another driver -- and any
         * interrupts applicable to us will be delivered once
         * interrupts from the device are unmasked in dwiic_i2c_exec.
         */
        if (!atomic_load_acquire(&sc->sc_attached))
                return 0;

        en = dwiic_read(sc, DW_IC_ENABLE);
        /* probably for the other controller */
        if (!en)
                return 0;

        stat = dwiic_read_clear_intrbits(sc);
        DPRINTF(("%s: %s: enabled=0x%x stat=0x%x\n", device_xname(sc->sc_dev),
            __func__, en, stat));
        if (!(stat & ~DW_IC_INTR_ACTIVITY))
                return 1;

        if (stat & DW_IC_INTR_TX_ABRT)
                sc->sc_i2c_xfer.error = 1;

        if (sc->sc_i2c_xfer.flags & I2C_F_POLL)
                DPRINTF(("%s: %s: intr in poll mode?\n", device_xname(sc->sc_dev),
                    __func__));
        else {
                mutex_enter(&sc->sc_int_lock);
                if (stat & DW_IC_INTR_RX_FULL) {
                        dwiic_write(sc, DW_IC_INTR_MASK, 0);
                        DPRINTF(("%s: %s: waking up reader\n",
                            device_xname(sc->sc_dev), __func__));
                        cv_signal(&sc->sc_int_readwait);
                }
                if (stat & DW_IC_INTR_TX_EMPTY) {
                        dwiic_write(sc, DW_IC_INTR_MASK, 0);
                        DPRINTF(("%s: %s: waking up writer\n",
                            device_xname(sc->sc_dev), __func__));
                        cv_signal(&sc->sc_int_writewait);
                }
                if (stat & DW_IC_INTR_STOP_DET) {
                        dwiic_write(sc, DW_IC_INTR_MASK, 0);
                        DPRINTF(("%s: %s: waking up stopper\n",
                            device_xname(sc->sc_dev), __func__));
                        cv_signal(&sc->sc_int_stopwait);
                }
                mutex_exit(&sc->sc_int_lock);
        }

        return 1;
}