mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 11:26:02 +09:00
i2c: rk3x: Add auto support for rockchip
If the transfer finished, auto stop to end this transfer. Signed-off-by: David Wu <david.wu@rock-chips.com> Change-Id: I23cd39e63b8ce292a63c9530edde2c9b72c289cb
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user