svn commit: r351186 - head/sys/arm64/rockchip
Michal Meloun
mmel at FreeBSD.org
Sun Aug 18 09:11:43 UTC 2019
Author: mmel
Date: Sun Aug 18 09:11:43 2019
New Revision: 351186
URL: https://svnweb.freebsd.org/changeset/base/351186
Log:
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
Modified:
head/sys/arm64/rockchip/rk_i2c.c
Modified: head/sys/arm64/rockchip/rk_i2c.c
==============================================================================
--- head/sys/arm64/rockchip/rk_i2c.c Sun Aug 18 08:54:10 2019 (r351185)
+++ head/sys/arm64/rockchip/rk_i2c.c Sun Aug 18 09:11:43 2019 (r351186)
@@ -46,9 +46,7 @@ __FBSDID("$FreeBSD$");
#include "iicbus_if.h"
-#include "opt_soc.h"
-
#define RK_I2C_CON 0x00
#define RK_I2C_CON_EN (1 << 0)
#define RK_I2C_CON_MODE_SHIFT 1
@@ -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,
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,29 +271,11 @@ 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)
{
uint32_t reg;
@@ -311,18 +290,29 @@ 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;
+ sc->ipd = RK_I2C_READ(sc, RK_I2C_IPD);
- RK_I2C_LOCK(sc);
+ /* Something to handle? */
+ if ((sc->ipd & RK_I2C_IPD_ALL) == 0)
+ return;
- sc->ipd = RK_I2C_READ(sc, RK_I2C_IPD);
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:
/* Disable start bit */
@@ -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;
- }
+ /* Disable the module and interrupts */
+ RK_I2C_WRITE(sc, RK_I2C_CON, 0);
+ RK_I2C_WRITE(sc, RK_I2C_IEN, 0);
- RK_I2C_LOCK(sc);
-
/* 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, ui
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,10 +598,23 @@ 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);
More information about the svn-src-all
mailing list