/* $NetBSD: dwiic.c,v 1.9 2022/10/19 22:34:10 riastradh 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.9 2022/10/19 22:34:10 riastradh 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;

	sc->sc_iba.iba_tag = &sc->sc_i2c_tag;

	/* config_found_ia 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;
}