NetBSD/sys/dev/ic/dwiic.c

647 lines
18 KiB
C

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