632 lines
17 KiB
C
632 lines
17 KiB
C
/* $NetBSD: dwiic.c,v 1.7 2019/12/23 15:28:08 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.7 2019/12/23 15:28:08 thorpej Exp $");
|
|
|
|
#include <sys/param.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 */
|
|
|
|
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 conditon 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;
|
|
|
|
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;
|
|
}
|