From 5cfc724687684fa8cb24963e9ddea99108d8d5da Mon Sep 17 00:00:00 2001 From: David Wu Date: Fri, 3 Jul 2020 11:43:52 +0800 Subject: [PATCH] i2c: rk3x: Add auto support for rockchip If the transfer finished, auto stop to end this transfer. Signed-off-by: David Wu Change-Id: I23cd39e63b8ce292a63c9530edde2c9b72c289cb --- drivers/i2c/busses/i2c-rk3x.c | 107 ++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 12 deletions(-) diff --git a/drivers/i2c/busses/i2c-rk3x.c b/drivers/i2c/busses/i2c-rk3x.c index c7c8d2f0abe7..d3a1171b06c4 100644 --- a/drivers/i2c/busses/i2c-rk3x.c +++ b/drivers/i2c/busses/i2c-rk3x.c @@ -37,6 +37,7 @@ #define REG_IEN 0x18 /* interrupt enable */ #define REG_IPD 0x1c /* interrupt pending */ #define REG_FCNT 0x20 /* finished count */ +#define REG_CON1 0x228 /* control register1 */ /* Data buffer offsets */ #define TXBUFFER_BASE 0x100 @@ -64,6 +65,15 @@ enum { #define REG_CON_STA_CFG(cfg) ((cfg) << 12) #define REG_CON_STO_CFG(cfg) ((cfg) << 14) +enum { + RK_I2C_VERSION0 = 0, + RK_I2C_VERSION1, + RK_I2C_VERSION5 = 5, +}; + +#define REG_CON_VERSION GENMASK_ULL(24, 16) +#define REG_CON_VERSION_SHIFT 16 + /* REG_MRXADDR bits */ #define REG_MRXADDR_VALID(x) BIT(24 + (x)) /* [x*8+7:x*8] of MRX[R]ADDR valid */ @@ -80,6 +90,9 @@ enum { /* Disable i2c all irqs */ #define IEN_ALL_DISABLE 0 +#define REG_CON1_AUTO_STOP BIT(0) +#define REG_CON1_TRANSFER_AUTO_STOP BIT(1) + /* Constants */ #define WAIT_TIMEOUT 1000 /* ms */ #define DEFAULT_SCL_RATE (100 * 1000) /* Hz */ @@ -206,6 +219,7 @@ struct rk3x_i2c { struct clk *clk; struct clk *pclk; struct notifier_block clk_rate_nb; + bool autostop_supported; /* Settings */ struct i2c_timings t; @@ -269,23 +283,55 @@ static inline void rk3x_i2c_disable(struct rk3x_i2c *i2c) i2c_writel(i2c, val, REG_CON); } +static bool rk3x_i2c_auto_stop(struct rk3x_i2c *i2c) +{ + unsigned int len, con1 = 0; + + if (!i2c->autostop_supported) + return false; + + if (!i2c->is_last_msg) + goto out; + + len = i2c->msg->len - i2c->processed; + + if (len > 32) + goto out; + + i2c->state = STATE_STOP; + + con1 |= REG_CON1_TRANSFER_AUTO_STOP | REG_CON1_AUTO_STOP; + i2c_writel(i2c, con1, REG_CON1); + i2c_writel(i2c, REG_INT_STOP | REG_INT_NAKRCV, REG_IEN); + + return true; + +out: + return false; +} + /** * Generate a START condition, which triggers a REG_INT_START interrupt. */ static void rk3x_i2c_start(struct rk3x_i2c *i2c) { u32 val = i2c_readl(i2c, REG_CON) & REG_CON_TUNING_MASK; + bool auto_stop = rk3x_i2c_auto_stop(i2c); int length = 0; /* enable appropriate interrupts */ if (i2c->mode == REG_CON_MOD_TX) { - i2c_writel(i2c, REG_INT_MBTF | REG_INT_NAKRCV, REG_IEN); - i2c->state = STATE_WRITE; + if (!auto_stop) { + i2c_writel(i2c, REG_INT_MBTF | REG_INT_NAKRCV, REG_IEN); + i2c->state = STATE_WRITE; + } length = rk3x_i2c_fill_transmit_buf(i2c, false); } else { /* in any other case, we are going to be reading. */ - i2c_writel(i2c, REG_INT_MBRF | REG_INT_NAKRCV, REG_IEN); - i2c->state = STATE_READ; + if (!auto_stop) { + i2c_writel(i2c, REG_INT_MBRF | REG_INT_NAKRCV, REG_IEN); + i2c->state = STATE_READ; + } } /* enable adapter with correct mode, send START condition */ @@ -430,6 +476,7 @@ static void rk3x_i2c_handle_write(struct rk3x_i2c *i2c, unsigned int ipd) /* ack interrupt */ i2c_writel(i2c, REG_INT_MBTF, REG_IPD); + rk3x_i2c_auto_stop(i2c); /* are we finished? */ if (i2c->processed == i2c->msg->len) rk3x_i2c_stop(i2c, i2c->error); @@ -437,20 +484,13 @@ static void rk3x_i2c_handle_write(struct rk3x_i2c *i2c, unsigned int ipd) rk3x_i2c_fill_transmit_buf(i2c, true); } -static void rk3x_i2c_handle_read(struct rk3x_i2c *i2c, unsigned int ipd) +static void rk3x_i2c_read(struct rk3x_i2c *i2c) { unsigned int i; unsigned int len = i2c->msg->len - i2c->processed; u32 val; u8 byte; - /* we only care for MBRF here. */ - if (!(ipd & REG_INT_MBRF)) - return; - - /* ack interrupt */ - i2c_writel(i2c, REG_INT_MBRF, REG_IPD); - /* Can only handle a maximum of 32 bytes at a time */ if (len > 32) len = 32; @@ -463,7 +503,21 @@ static void rk3x_i2c_handle_read(struct rk3x_i2c *i2c, unsigned int ipd) byte = (val >> ((i % 4) * 8)) & 0xff; i2c->msg->buf[i2c->processed++] = byte; } +} +static void rk3x_i2c_handle_read(struct rk3x_i2c *i2c, unsigned int ipd) +{ + /* we only care for MBRF here. */ + if (!(ipd & REG_INT_MBRF)) + return; + + /* ack interrupt */ + i2c_writel(i2c, REG_INT_MBRF, REG_IPD); + + /* read the data from receive buffer */ + rk3x_i2c_read(i2c); + + rk3x_i2c_auto_stop(i2c); /* are we finished? */ if (i2c->processed == i2c->msg->len) rk3x_i2c_stop(i2c, i2c->error); @@ -482,12 +536,24 @@ static void rk3x_i2c_handle_stop(struct rk3x_i2c *i2c, unsigned int ipd) return; } + if (i2c->autostop_supported && !i2c->error) { + if (i2c->mode != REG_CON_MOD_TX && i2c->msg) { + if ((i2c->msg->len - i2c->processed) > 0) + rk3x_i2c_read(i2c); + } + + i2c->processed = 0; + i2c->msg = NULL; + } + /* ack interrupt */ i2c_writel(i2c, REG_INT_STOP, REG_IPD); /* disable STOP bit */ con = i2c_readl(i2c, REG_CON); con &= ~REG_CON_STOP; + if (i2c->autostop_supported) + con &= ~REG_CON_START; i2c_writel(i2c, con, REG_CON); i2c->busy = false; @@ -1062,6 +1128,8 @@ static int rk3x_i2c_setup(struct rk3x_i2c *i2c, struct i2c_msg *msgs, int num) i2c->error = 0; rk3x_i2c_clean_ipd(i2c); + if (i2c->autostop_supported) + i2c_writel(i2c, 0, REG_CON1); return ret; } @@ -1205,6 +1273,18 @@ static int rk3x_i2c_restart_notify(struct notifier_block *this, return NOTIFY_DONE; } +static unsigned int rk3x_i2c_get_version(struct rk3x_i2c *i2c) +{ + unsigned int version; + + clk_enable(i2c->pclk); + version = i2c_readl(i2c, REG_CON) & REG_CON_VERSION; + clk_disable(i2c->pclk); + version >>= REG_CON_VERSION_SHIFT; + + return version; +} + static __maybe_unused int rk3x_i2c_suspend_noirq(struct device *dev) { struct rk3x_i2c *i2c = dev_get_drvdata(dev); @@ -1452,6 +1532,9 @@ static int rk3x_i2c_probe(struct platform_device *pdev) clk_rate = clk_get_rate(i2c->clk); rk3x_i2c_adapt_div(i2c, clk_rate); + if (rk3x_i2c_get_version(i2c) >= RK_I2C_VERSION5) + i2c->autostop_supported = true; + ret = i2c_add_adapter(&i2c->adap); if (ret < 0) goto err_clk_notifier;