src/sys/dev/ic/dwiic.c

565 lines
14 KiB
C

/* $OpenBSD: dwiic.c,v 1.15 2023/08/29 12:09:40 kettenis Exp $ */
/*
* Synopsys DesignWare I2C controller
*
* Copyright (c) 2015-2017 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/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#ifdef __HAVE_ACPI
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpidev.h>
#include <dev/acpi/amltypes.h>
#include <dev/acpi/dsdt.h>
#endif
#include <dev/i2c/i2cvar.h>
#include <dev/ic/dwiicvar.h>
struct cfdriver dwiic_cd = {
NULL, "dwiic", DV_DULL
};
int
dwiic_activate(struct device *self, int act)
{
struct dwiic_softc *sc = (struct dwiic_softc *)self;
switch (act) {
case DVACT_SUSPEND:
/* disable controller */
dwiic_enable(sc, 0);
/* disable interrupts */
dwiic_write(sc, DW_IC_INTR_MASK, 0);
dwiic_read(sc, DW_IC_CLR_INTR);
#if notyet
/* power down the controller */
dwiic_acpi_power(sc, 0);
#endif
break;
case DVACT_WAKEUP:
#if notyet
/* power up the controller */
dwiic_acpi_power(sc, 1);
#endif
dwiic_init(sc);
break;
}
config_activate_children(self, act);
return 0;
}
int
dwiic_i2c_print(void *aux, const char *pnp)
{
struct i2c_attach_args *ia = aux;
if (pnp != NULL)
printf("\"%s\" at %s", ia->ia_name, pnp);
printf(" addr 0x%x", ia->ia_addr);
return UNCONF;
}
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", sc->sc_dev.dv_xname, offset, b));
return b;
}
void
dwiic_write(struct dwiic_softc *sc, int offset, uint32_t val)
{
DPRINTF(("%s: write at 0x%x: 0x%x\n", sc->sc_dev.dv_xname, offset,
val));
bus_space_write_4(sc->sc_iot, sc->sc_ioh, offset, val);
}
int
dwiic_i2c_acquire_bus(void *cookie, int flags)
{
struct dwiic_softc *sc = cookie;
if (cold || sc->sc_poll || (flags & I2C_F_POLL))
return (0);
return rw_enter(&sc->sc_i2c_lock, RW_WRITE | RW_INTR);
}
void
dwiic_i2c_release_bus(void *cookie, int flags)
{
struct dwiic_softc *sc = cookie;
if (cold || sc->sc_poll || (flags & I2C_F_POLL))
return;
rw_exit(&sc->sc_i2c_lock);
}
int
dwiic_init(struct dwiic_softc *sc)
{
uint32_t reg;
uint8_t tx_fifo_depth;
uint8_t rx_fifo_depth;
/* 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",
sc->sc_dev.dv_xname, reg));
return 1;
}
/* fetch default timing parameters if not already specified */
if (!sc->ss_hcnt)
sc->ss_hcnt = dwiic_read(sc, DW_IC_SS_SCL_HCNT);
if (!sc->ss_lcnt)
sc->ss_lcnt = dwiic_read(sc, DW_IC_SS_SCL_LCNT);
if (!sc->fs_hcnt)
sc->fs_hcnt = dwiic_read(sc, DW_IC_FS_SCL_HCNT);
if (!sc->fs_lcnt)
sc->fs_lcnt = dwiic_read(sc, DW_IC_FS_SCL_LCNT);
if (!sc->sda_hold_time)
sc->sda_hold_time = dwiic_read(sc, DW_IC_SDA_HOLD);
/* 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;
reg = dwiic_read(sc, DW_IC_COMP_PARAM_1);
tx_fifo_depth = DW_IC_TX_FIFO_DEPTH(reg);
rx_fifo_depth = DW_IC_RX_FIFO_DEPTH(reg);
if (tx_fifo_depth > 1 && tx_fifo_depth < sc->tx_fifo_depth)
sc->tx_fifo_depth = tx_fifo_depth;
if (rx_fifo_depth > 1 && rx_fifo_depth < sc->rx_fifo_depth)
sc->rx_fifo_depth = rx_fifo_depth;
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;
}
void
dwiic_enable(struct dwiic_softc *sc, int 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);
}
printf("%s: failed to %sable\n", sc->sc_dev.dv_xname,
(enable ? "en" : "dis"));
}
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;
uint8_t *b;
int s;
if (sc->sc_busy)
return 1;
sc->sc_busy++;
DPRINTF(("%s: %s: op %d, addr 0x%02x, cmdlen %zu, len %zu, "
"flags 0x%02x\n", sc->sc_dev.dv_xname, __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", sc->sc_dev.dv_xname, __func__, st));
if (st & DW_IC_STATUS_ACTIVITY) {
sc->sc_busy = 0;
return (1);
}
if (cold || sc->sc_poll)
flags |= I2C_F_POLL;
/* 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 {
s = splbio();
dwiic_read(sc, DW_IC_CLR_INTR);
dwiic_write(sc, DW_IC_INTR_MASK, DW_IC_INTR_TX_EMPTY);
if (tsleep_nsec(&sc->sc_writewait, PRIBIO, "dwiic",
MSEC_TO_NSEC(500)) != 0)
printf("%s: timed out waiting for tx_empty intr\n",
sc->sc_dev.dv_xname);
splx(s);
}
/* send our command, one byte at a time */
if (cmdlen > 0) {
b = (void *)cmdbuf;
DPRINTF(("%s: %s: sending cmd (len %zu):", sc->sc_dev.dv_xname,
__func__, cmdlen));
for (x = 0; x < cmdlen; x++)
DPRINTF((" %02x", b[x]));
DPRINTF(("\n"));
tx_limit = sc->tx_fifo_depth - dwiic_read(sc, DW_IC_TXFLR);
if (cmdlen > tx_limit) {
/* TODO */
printf("%s: can't write %zu (> %d)\n",
sc->sc_dev.dv_xname, cmdlen, tx_limit);
sc->sc_i2c_xfer.error = 1;
sc->sc_busy = 0;
return (1);
}
for (x = 0; x < cmdlen; x++) {
cmd = b[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);
}
}
b = (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",
sc->sc_dev.dv_xname, __func__, len, tx_limit));
while (x < len) {
if (I2C_OP_WRITE_P(op))
cmd = b[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);
/*
* For a block read, get the byte count before
* continuing to read the data bytes.
*/
if (I2C_OP_READ_P(op) && I2C_OP_BLKMODE_P(op) && readpos == 0)
tx_limit = 1;
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",
sc->sc_dev.dv_xname, __func__, tx_limit, x));
if (flags & I2C_F_POLL) {
for (retries = 1000; retries > 0; retries--) {
rx_avail = dwiic_read(sc, DW_IC_RXFLR);
if (rx_avail > 0)
break;
DELAY(50);
}
} else {
s = splbio();
dwiic_read(sc, DW_IC_CLR_INTR);
dwiic_write(sc, DW_IC_INTR_MASK,
DW_IC_INTR_RX_FULL);
if (tsleep_nsec(&sc->sc_readwait, PRIBIO,
"dwiic", MSEC_TO_NSEC(500)) != 0)
printf("%s: timed out waiting for "
"rx_full intr\n",
sc->sc_dev.dv_xname);
splx(s);
rx_avail = dwiic_read(sc, DW_IC_RXFLR);
}
if (rx_avail == 0) {
printf("%s: timed out reading remaining %d\n",
sc->sc_dev.dv_xname, (int)(len - readpos));
sc->sc_i2c_xfer.error = 1;
sc->sc_busy = 0;
return (1);
}
DPRINTF(("%s: %s: %d avail to read (%zu remaining)\n",
sc->sc_dev.dv_xname, __func__, rx_avail,
len - readpos));
while (rx_avail > 0) {
resp = dwiic_read(sc, DW_IC_DATA_CMD);
if (readpos < len) {
b[readpos] = resp;
readpos++;
}
rx_avail--;
}
/*
* Update the transfer length when doing a
* block read.
*/
if (I2C_OP_BLKMODE_P(op) && readpos > 0 && len > b[0])
len = b[0] + 1;
if (readpos >= len)
break;
DPRINTF(("%s: still need to read %d bytes\n",
sc->sc_dev.dv_xname, (int)(len - readpos)));
tx_limit = sc->tx_fifo_depth -
dwiic_read(sc, DW_IC_TXFLR);
}
if (I2C_OP_WRITE_P(op) && tx_limit == 0 && x < len) {
if (flags & I2C_F_POLL) {
for (retries = 1000; retries > 0; retries--) {
tx_limit = sc->tx_fifo_depth -
dwiic_read(sc, DW_IC_TXFLR);
if (tx_limit > 0)
break;
DELAY(50);
}
} else {
s = splbio();
dwiic_read(sc, DW_IC_CLR_INTR);
dwiic_write(sc, DW_IC_INTR_MASK,
DW_IC_INTR_TX_EMPTY);
if (tsleep_nsec(&sc->sc_writewait, PRIBIO,
"dwiic", MSEC_TO_NSEC(500)) != 0)
printf("%s: timed out waiting for "
"tx_empty intr\n",
sc->sc_dev.dv_xname);
splx(s);
tx_limit = sc->tx_fifo_depth -
dwiic_read(sc, DW_IC_TXFLR);
}
if (tx_limit == 0) {
printf("%s: timed out writing remaining %d\n",
sc->sc_dev.dv_xname, (int)(len - x));
sc->sc_i2c_xfer.error = 1;
sc->sc_busy = 0;
return (1);
}
}
}
if (I2C_OP_STOP_P(op) && I2C_OP_WRITE_P(op)) {
if (flags & I2C_F_POLL) {
for (retries = 100; retries > 0; retries--) {
st = dwiic_read(sc, DW_IC_RAW_INTR_STAT);
if (st & DW_IC_INTR_STOP_DET)
break;
DELAY(1000);
}
if (!(st & DW_IC_INTR_STOP_DET))
printf("%s: timed out waiting for bus idle\n",
sc->sc_dev.dv_xname);
} else {
s = splbio();
while (sc->sc_busy) {
dwiic_write(sc, DW_IC_INTR_MASK,
DW_IC_INTR_STOP_DET);
if (tsleep_nsec(&sc->sc_busy, PRIBIO, "dwiic",
MSEC_TO_NSEC(500)) != 0)
printf("%s: timed out waiting for "
"stop intr\n",
sc->sc_dev.dv_xname);
}
splx(s);
}
}
sc->sc_busy = 0;
return 0;
}
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", sc->sc_dev.dv_xname,
__func__, en, stat));
if (!(stat & ~DW_IC_INTR_ACTIVITY))
return 0;
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", sc->sc_dev.dv_xname,
__func__));
else {
if (stat & DW_IC_INTR_RX_FULL) {
dwiic_write(sc, DW_IC_INTR_MASK, 0);
DPRINTF(("%s: %s: waking up reader\n",
sc->sc_dev.dv_xname, __func__));
wakeup(&sc->sc_readwait);
}
if (stat & DW_IC_INTR_TX_EMPTY) {
dwiic_write(sc, DW_IC_INTR_MASK, 0);
DPRINTF(("%s: %s: waking up writer\n",
sc->sc_dev.dv_xname, __func__));
wakeup(&sc->sc_writewait);
}
if (stat & DW_IC_INTR_STOP_DET) {
dwiic_write(sc, DW_IC_INTR_MASK, 0);
sc->sc_busy = 0;
wakeup(&sc->sc_busy);
}
}
return 1;
}