Improve rk_i2c driver:

- Properly handle IIC_M_NOSTOP and IIC_M_NOSTART flags.
- add polling mode, so driver can be used even if interrupts are not
  enabled (this is necessary for proper support of PMICs).
- add support for RK3288

MFC after:	2 weeks
This commit is contained in:
Michal Meloun 2019-08-18 09:11:43 +00:00
parent e088e853c3
commit c6ed46a518
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=351186

View File

@ -46,8 +46,6 @@ __FBSDID("$FreeBSD$");
#include "iicbus_if.h"
#include "opt_soc.h"
#define RK_I2C_CON 0x00
#define RK_I2C_CON_EN (1 << 0)
@ -61,6 +59,7 @@ __FBSDID("$FreeBSD$");
#define RK_I2C_CON_STOP (1 << 4)
#define RK_I2C_CON_LASTACK (1 << 5)
#define RK_I2C_CON_NAKSTOP (1 << 6)
#define RK_I2C_CON_CTRL_MASK 0xFF
#define RK_I2C_CLKDIV 0x04
#define RK_I2C_CLKDIVL_MASK 0xFFFF
@ -91,8 +90,7 @@ __FBSDID("$FreeBSD$");
#define RK_I2C_IEN_STARTIEN (1 << 4)
#define RK_I2C_IEN_STOPIEN (1 << 5)
#define RK_I2C_IEN_NAKRCVIEN (1 << 6)
#define RK_I2C_IEN_ALL (RK_I2C_IEN_BTFIEN | \
RK_I2C_IEN_BRFIEN | RK_I2C_IEN_MBTFIEN | RK_I2C_IEN_MBRFIEN | \
#define RK_I2C_IEN_ALL (RK_I2C_IEN_MBTFIEN | RK_I2C_IEN_MBRFIEN | \
RK_I2C_IEN_STARTIEN | RK_I2C_IEN_STOPIEN | RK_I2C_IEN_NAKRCVIEN)
#define RK_I2C_IPD 0x1C
@ -103,8 +101,7 @@ __FBSDID("$FreeBSD$");
#define RK_I2C_IPD_STARTIPD (1 << 4)
#define RK_I2C_IPD_STOPIPD (1 << 5)
#define RK_I2C_IPD_NAKRCVIPD (1 << 6)
#define RK_I2C_IPD_ALL (RK_I2C_IPD_BTFIPD | \
RK_I2C_IPD_BRFIPD | RK_I2C_IPD_MBTFIPD | RK_I2C_IPD_MBRFIPD | \
#define RK_I2C_IPD_ALL (RK_I2C_IPD_MBTFIPD | RK_I2C_IPD_MBRFIPD | \
RK_I2C_IPD_STARTIPD | RK_I2C_IPD_STOPIPD | RK_I2C_IPD_NAKRCVIPD)
#define RK_I2C_FNCT 0x20
@ -134,8 +131,10 @@ struct rk_i2c_softc {
uint32_t ipd;
struct iic_msg *msg;
size_t cnt;
int transfer_done;
int nak_recv;
int msg_len;
bool transfer_done;
bool nak_recv;
bool tx_slave_addr;
uint8_t mode;
uint8_t state;
@ -143,12 +142,9 @@ struct rk_i2c_softc {
};
static struct ofw_compat_data compat_data[] = {
#ifdef SOC_ROCKCHIP_RK3328
{"rockchip,rk3288-i2c", 1},
{"rockchip,rk3328-i2c", 1},
#endif
#ifdef SOC_ROCKCHIP_RK3399
{"rockchip,rk3399-i2c", 1},
#endif
{NULL, 0}
};
@ -169,7 +165,7 @@ static int rk_i2c_detach(device_t dev);
#define RK_I2C_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val))
static uint32_t
rk_i2c_get_clkdiv(struct rk_i2c_softc *sc, uint64_t speed)
rk_i2c_get_clkdiv(struct rk_i2c_softc *sc, uint32_t speed)
{
uint64_t sclk_freq;
uint32_t clkdiv;
@ -213,7 +209,7 @@ rk_i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
return (0);
}
static void
static uint8_t
rk_i2c_fill_tx(struct rk_i2c_softc *sc)
{
uint32_t buf32;
@ -221,7 +217,7 @@ rk_i2c_fill_tx(struct rk_i2c_softc *sc)
int i, j, len;
if (sc->msg == NULL || sc->msg->len == sc->cnt)
return;
return (0);
len = sc->msg->len - sc->cnt;
if (len > 8)
@ -234,22 +230,23 @@ rk_i2c_fill_tx(struct rk_i2c_softc *sc)
break;
/* Fill the addr if needed */
if (sc->cnt == 0) {
if (sc->cnt == 0 && sc->tx_slave_addr) {
buf = sc->msg->slave;
sc->tx_slave_addr = false;
} else {
buf = sc->msg->buf[sc->cnt];
sc->cnt++;
}
else
buf = sc->msg->buf[sc->cnt - 1];
buf32 |= buf << (j * 8);
sc->cnt++;
}
RK_I2C_WRITE(sc, RK_I2C_TXDATA_BASE + 4 * i, buf32);
if (sc->cnt == sc->msg->len)
break;
}
return (uint8_t)len;
}
static void
@ -274,28 +271,10 @@ rk_i2c_drain_rx(struct rk_i2c_softc *sc)
buf32 = RK_I2C_READ(sc, RK_I2C_RXDATA_BASE + (i / 4) * 4);
buf8 = (buf32 >> ((i % 4) * 8)) & 0xFF;
sc->msg->buf[sc->cnt++] = buf8;
}
}
static void
rk_i2c_send_start(struct rk_i2c_softc *sc)
{
uint32_t reg;
RK_I2C_WRITE(sc, RK_I2C_IEN, RK_I2C_IEN_STARTIEN);
sc->state = STATE_START;
reg = RK_I2C_READ(sc, RK_I2C_CON);
reg |= RK_I2C_CON_START;
reg |= RK_I2C_CON_EN;
reg &= ~RK_I2C_CON_MODE_MASK;
reg |= sc->mode << RK_I2C_CON_MODE_SHIFT;
RK_I2C_WRITE(sc, RK_I2C_CON, reg);
}
static void
rk_i2c_send_stop(struct rk_i2c_softc *sc)
{
@ -311,17 +290,28 @@ rk_i2c_send_stop(struct rk_i2c_softc *sc)
}
static void
rk_i2c_intr(void *arg)
rk_i2c_intr_locked(struct rk_i2c_softc *sc)
{
struct rk_i2c_softc *sc;
uint32_t reg;
sc = (struct rk_i2c_softc *)arg;
RK_I2C_LOCK(sc);
sc->ipd = RK_I2C_READ(sc, RK_I2C_IPD);
/* Something to handle? */
if ((sc->ipd & RK_I2C_IPD_ALL) == 0)
return;
RK_I2C_WRITE(sc, RK_I2C_IPD, sc->ipd);
sc->ipd &= RK_I2C_IPD_ALL;
if (sc->ipd & RK_I2C_IPD_NAKRCVIPD) {
/* NACK received */
sc->ipd &= ~RK_I2C_IPD_NAKRCVIPD;
sc->nak_recv = 1;
/* XXXX last byte !!!, signal error !!! */
sc->transfer_done = 1;
sc->state = STATE_IDLE;
goto err;
}
switch (sc->state) {
case STATE_START:
@ -359,10 +349,12 @@ rk_i2c_intr(void *arg)
break;
case STATE_WRITE:
if (sc->cnt == sc->msg->len)
if (sc->cnt == sc->msg->len &&
!(sc->msg->flags & IIC_M_NOSTOP)) {
rk_i2c_send_stop(sc);
break;
break;
}
/* passthru */
case STATE_STOP:
/* Disable stop bit */
reg = RK_I2C_READ(sc, RK_I2C_CON);
@ -376,77 +368,177 @@ rk_i2c_intr(void *arg)
break;
}
err:
wakeup(sc);
}
static void
rk_i2c_intr(void *arg)
{
struct rk_i2c_softc *sc;
sc = (struct rk_i2c_softc *)arg;
RK_I2C_LOCK(sc);
rk_i2c_intr_locked(sc);
RK_I2C_UNLOCK(sc);
}
static void
rk_i2c_start_xfer(struct rk_i2c_softc *sc, struct iic_msg *msg, boolean_t last)
{
uint32_t reg;
uint8_t len;
sc->transfer_done = false;
sc->nak_recv = false;
sc->tx_slave_addr = false;
sc->cnt = 0;
sc->state = STATE_IDLE;
sc->msg = msg;
sc->msg_len = sc->msg->len;
reg = RK_I2C_READ(sc, RK_I2C_CON) & ~RK_I2C_CON_CTRL_MASK;
if (!(sc->msg->flags & IIC_M_NOSTART)) {
/* Stadard message */
if (sc->mode == RK_I2C_CON_MODE_TX) {
sc->msg_len++; /* Take slave address in account. */
sc->tx_slave_addr = true;
}
sc->state = STATE_START;
reg |= RK_I2C_CON_START;
RK_I2C_WRITE(sc, RK_I2C_IEN, RK_I2C_IEN_STARTIEN);
} else {
/* Continuation message */
if (sc->mode == RK_I2C_CON_MODE_RX) {
sc->state = STATE_READ;
if (last)
reg |= RK_I2C_CON_LASTACK;
RK_I2C_WRITE(sc, RK_I2C_MRXCNT, sc->msg->len);
RK_I2C_WRITE(sc, RK_I2C_IEN, RK_I2C_IEN_MBRFIEN |
RK_I2C_IEN_NAKRCVIEN);
} else {
sc->state = STATE_WRITE;
len = rk_i2c_fill_tx(sc);
RK_I2C_WRITE(sc, RK_I2C_MTXCNT, len);
RK_I2C_WRITE(sc, RK_I2C_IEN, RK_I2C_IEN_MBTFIEN |
RK_I2C_IEN_NAKRCVIEN);
}
}
reg |= sc->mode << RK_I2C_CON_MODE_SHIFT;
reg |= RK_I2C_CON_EN;
RK_I2C_WRITE(sc, RK_I2C_CON, reg);
}
static int
rk_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
{
struct rk_i2c_softc *sc;
uint32_t reg;
int i, j, msgskip, err = 0;
bool last_msg;
int i, j, timeout, err;
sc = device_get_softc(dev);
RK_I2C_LOCK(sc);
while (sc->busy)
mtx_sleep(sc, &sc->mtx, 0, "i2cbuswait", 0);
sc->busy = 1;
err = clk_enable(sc->pclk);
if (err != 0) {
device_printf(dev, "cannot enable pclk clock\n");
goto out;
}
err = clk_enable(sc->sclk);
if (err != 0) {
device_printf(dev, "cannot enable i2c clock\n");
goto out;
}
RK_I2C_LOCK(sc);
/* Disable the module and interrupts */
RK_I2C_WRITE(sc, RK_I2C_CON, 0);
RK_I2C_WRITE(sc, RK_I2C_IEN, 0);
/* Clean stale interrupts */
RK_I2C_WRITE(sc, RK_I2C_IPD, RK_I2C_IPD_ALL);
for (i = 0; i < nmsgs; i += msgskip) {
if (nmsgs - i >= 2 && !(msgs[i].flags & IIC_M_RD) &&
msgs[i + 1].flags & IIC_M_RD && msgs[i].len <= 4) {
err = 0;
for (i = 0; i < nmsgs; i++) {
/* Validate parameters. */
if (msgs == NULL || msgs[i].buf == NULL ||
msgs[i].len == 0) {
err = EINVAL;
break;
}
/*
* If next message have NOSTART flag, then they both
* should be same type (read/write) and same address.
*/
if (i < nmsgs - 1) {
if ((msgs[i + 1].flags & IIC_M_NOSTART) &&
((msgs[i].flags & IIC_M_RD) !=
(msgs[i + 1].flags & IIC_M_RD) ||
(msgs[i].slave != msgs[i + 1].slave))) {
err = EINVAL;
break;
}
}
/*
* Detect simple register read case.
* The first message should be IIC_M_WR | IIC_M_NOSTOP,
* next pure IIC_M_RD (no other flags allowed). Both
* messages should have same slave address.
*/
if (nmsgs - i >= 2 && msgs[i].len < 4 &&
msgs[i].flags == (IIC_M_WR | IIC_M_NOSTOP) &&
msgs[i + 1].flags == IIC_M_RD &&
(msgs[i].slave & ~LSB) == (msgs[i + 1].slave & ~LSB)) {
sc->mode = RK_I2C_CON_MODE_RRX;
msgskip = 2;
sc->msg = &msgs[i + 1];
/* Write slave address */
reg = msgs[i].slave | RK_I2C_MRXADDR_VALID(0);
reg = msgs[i].slave & ~LSB;
reg |= RK_I2C_MRXADDR_VALID(0);
RK_I2C_WRITE(sc, RK_I2C_MRXADDR, reg);
/* Write slave register address */
for (j = 0, reg = 0; j < msgs[i].len; j++) {
reg = 0;
for (j = 0; j < msgs[i].len ; j++) {
reg |= (msgs[i].buf[j] & 0xff) << (j * 8);
reg |= RK_I2C_MRXADDR_VALID(j);
}
RK_I2C_WRITE(sc, RK_I2C_MRXRADDR, reg);
i++;
} else {
if (msgs[i].flags & IIC_M_RD) {
sc->mode = RK_I2C_CON_MODE_RX;
msgs[i].slave |= LSB;
}
else {
if (msgs[i].flags & IIC_M_NOSTART) {
sc->mode = RK_I2C_CON_MODE_RX;
} else {
sc->mode = RK_I2C_CON_MODE_RRX;
reg = msgs[i].slave & LSB;
reg |= RK_I2C_MRXADDR_VALID(0);
RK_I2C_WRITE(sc, RK_I2C_MRXADDR, reg);
RK_I2C_WRITE(sc, RK_I2C_MRXRADDR, 0);
}
} else {
sc->mode = RK_I2C_CON_MODE_TX;
msgs[i].slave &= ~LSB;
}
msgskip = 1;
sc->msg = &msgs[i];
}
/* last message ? */
last_msg = (i > nmsgs - 1) ||
!(msgs[i + 1].flags & IIC_M_NOSTART);
rk_i2c_start_xfer(sc, msgs + i, last_msg);
sc->transfer_done = 0;
sc->cnt = 0;
sc->state = STATE_IDLE;
rk_i2c_send_start(sc);
while (err == 0 && sc->transfer_done != 1) {
err = msleep(sc, &sc->mtx, 0, "rk_i2c", 10 * hz);
if (cold) {
for(timeout = 10000; timeout > 0; timeout--) {
rk_i2c_intr_locked(sc);
if (sc->transfer_done != 0)
break;
DELAY(100);
}
if (timeout <= 0)
err = ETIMEDOUT;
} else {
while (err == 0 && sc->transfer_done != 1) {
err = msleep(sc, &sc->mtx, PZERO, "rk_i2c",
10 * hz);
}
}
}
@ -457,19 +549,6 @@ rk_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
sc->busy = 0;
RK_I2C_UNLOCK(sc);
err = clk_disable(sc->pclk);
if (err != 0) {
device_printf(dev, "cannot enable pclk clock\n");
goto out;
}
err = clk_disable(sc->sclk);
if (err != 0) {
device_printf(dev, "cannot enable i2c clock\n");
goto out;
}
out:
return (err);
}
@ -519,11 +598,24 @@ rk_i2c_attach(device_t dev)
device_printf(dev, "cannot get i2c clock\n");
goto fail;
}
error = clk_get_by_ofw_name(dev, 0, "pclk", &sc->pclk);
error = clk_enable(sc->sclk);
if (error != 0) {
device_printf(dev, "cannot enable i2c clock\n");
goto fail;
}
/* pclk clock is optional. */
error = clk_get_by_ofw_name(dev, 0, "pclk", &sc->pclk);
if (error != 0 && error != ENOENT) {
device_printf(dev, "cannot get pclk clock\n");
goto fail;
}
if (sc->sclk != NULL) {
error = clk_enable(sc->sclk);
if (error != 0) {
device_printf(dev, "cannot enable pclk clock\n");
goto fail;
}
}
sc->iicbus = device_add_child(dev, "iicbus", -1);
if (sc->iicbus == NULL) {