[PATCH] i2c: imx: add slave support. v2

From: Dmitriy Baranov
Date: Tue Jan 26 2016 - 11:15:49 EST


Add I2C slave provider using the generic slave interface.
It also supports master transactions when the slave in the idle mode.

Issues:
Changes work only in PIO mode (when driver doesn`t use DMA)
It weren`t tested with DMA is enabled (in PIO mode it works well)
There are might be race conditions.

We hope that these changes will be helpfull.
Thank you.

Signed-off-by: Maxim Syrchin <syrchin@xxxxxxxxxxxxx>
Signed-off-by: Dmitriy Baranov <dbaranov@xxxxxxxxxxxxx>
---
drivers/i2c/busses/Kconfig | 1 +
drivers/i2c/busses/i2c-imx.c | 311 ++++++++++++++++++++++++++++++++++++++++---
2 files changed, 291 insertions(+), 21 deletions(-)

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 0299dfa..bc7cbfd 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -587,6 +587,7 @@ config I2C_IMG
config I2C_IMX
tristate "IMX I2C interface"
depends on ARCH_MXC || ARCH_LAYERSCAPE
+ select I2C_SLAVE
help
Say Y here if you want to use the IIC bus controller on
the Freescale i.MX/MXC or Layerscape processors.
diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
index a2b132c..3c286f1 100644
--- a/drivers/i2c/busses/i2c-imx.c
+++ b/drivers/i2c/busses/i2c-imx.c
@@ -53,6 +53,7 @@
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/slab.h>
+#include <linux/kthread.h>

/* This will be the driver name the kernel reports */
#define DRIVER_NAME "imx-i2c"
@@ -171,6 +172,18 @@ enum imx_i2c_type {
VF610_I2C,
};

+enum imx_i2c_mode {
+ I2C_IMX_SLAVE,
+ I2C_IMX_MASTER,
+ I2C_IMX_UNDEFINED
+};
+
+enum imx_i2c_slave_state {
+ I2C_IMX_SLAVE_IDLE,
+ I2C_IMX_SLAVE_IRQ,
+ I2C_IMX_SLAVE_POLLING
+};
+
struct imx_i2c_hwdata {
enum imx_i2c_type devtype;
unsigned regshift;
@@ -193,10 +206,12 @@ struct imx_i2c_dma {

struct imx_i2c_struct {
struct i2c_adapter adapter;
+ struct i2c_client *slave;
struct clk *clk;
void __iomem *base;
wait_queue_head_t queue;
unsigned long i2csr;
+ unsigned long i2csr_slave;
unsigned int disable_delay;
int stopped;
unsigned int ifdr; /* IMX_I2C_IFDR */
@@ -210,6 +225,11 @@ struct imx_i2c_struct {
struct pinctrl_state *pinctrl_pins_gpio;

struct imx_i2c_dma *dma;
+
+ enum imx_i2c_mode dev_mode;
+ atomic_t slave_state;
+ struct task_struct *slave_task;
+ wait_queue_head_t slave_queue;
};

static const struct imx_i2c_hwdata imx1_i2c_hwdata = {
@@ -510,39 +530,97 @@ static void i2c_imx_set_clk(struct imx_i2c_struct *i2c_imx)
#endif
}

-static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
+static int i2c_imx_configure_clock(struct imx_i2c_struct *i2c_imx)
{
- unsigned int temp = 0;
int result;

- dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
-
i2c_imx_set_clk(i2c_imx);

- imx_i2c_write_reg(i2c_imx->ifdr, i2c_imx, IMX_I2C_IFDR);
- /* Enable I2C controller */
- imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);
- imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode, i2c_imx, IMX_I2C_I2CR);
+ result = clk_prepare_enable(i2c_imx->clk);
+ if (result == 0)
+ imx_i2c_write_reg(i2c_imx->ifdr, i2c_imx, IMX_I2C_IFDR);
+
+ return result;
+}
+
+static void i2c_imx_enable_i2c_controller(struct imx_i2c_struct *i2c_imx)
+{
+ imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx,
+ IMX_I2C_I2SR);
+ imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode, i2c_imx,
+ IMX_I2C_I2CR);

/* Wait controller to be stable */
udelay(50);
+}
+
+static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
+{
+ unsigned int temp = 0;
+ int result;
+
+ i2c_imx->dev_mode = I2C_IMX_UNDEFINED;
+
+ dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
+
+ result = i2c_imx_configure_clock(i2c_imx);
+ if (result != 0)
+ return result;
+
+ i2c_imx_enable_i2c_controller(i2c_imx);

/* Start I2C transaction */
temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
temp |= I2CR_MSTA;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
+
result = i2c_imx_bus_busy(i2c_imx, 1);
if (result)
return result;
i2c_imx->stopped = 0;

+ i2c_imx->dev_mode = I2C_IMX_MASTER;
+
temp |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK;
temp &= ~I2CR_DMAEN;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
return result;
}

-static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
+static int i2c_imx_start_slave_mode(struct imx_i2c_struct *i2c_imx, bool enable)
+{
+ unsigned int temp;
+ int result;
+
+ dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
+
+ i2c_imx->dev_mode = I2C_IMX_UNDEFINED;
+
+ if (enable) {
+ result = i2c_imx_configure_clock(i2c_imx);
+ if (result != 0)
+ return result;
+ }
+
+ /* Set the Slave bit */
+ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
+ temp &= ~I2CR_MSTA;
+ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
+
+ /* Set the Slave address */
+ imx_i2c_write_reg((i2c_imx->slave->addr << 1), i2c_imx, IMX_I2C_IADR);
+
+ i2c_imx->dev_mode = I2C_IMX_SLAVE;
+
+ i2c_imx_enable_i2c_controller(i2c_imx);
+
+ imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode | I2CR_IIEN, i2c_imx,
+ IMX_I2C_I2CR);
+
+ return 0;
+}
+
+static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx, bool disable)
{
unsigned int temp = 0;

@@ -568,24 +646,152 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
i2c_imx->stopped = 1;
}

- /* Disable I2C controller */
- temp = i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
+ temp = i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
+
+ if (disable)
+ clk_disable_unprepare(i2c_imx->clk);
+
+ i2c_imx->dev_mode = I2C_IMX_UNDEFINED;
+}
+
+static void i2c_imx_clear_isr_bit(struct imx_i2c_struct *i2c_imx,
+ unsigned int status)
+{
+ status &= ~I2SR_IIF;
+ status |= (i2c_imx->hwdata->i2sr_clr_opcode & I2SR_IIF);
+ imx_i2c_write_reg(status, i2c_imx, IMX_I2C_I2SR);
+}
+
+static void i2c_imx_master_isr_handler(struct imx_i2c_struct *i2c_imx,
+ unsigned int status)
+{
+ /* save status register */
+ i2c_imx->i2csr = status;
+ wake_up(&i2c_imx->queue);
+}
+
+static int i2c_imx_slave_threadfn(void *pdata)
+{
+ unsigned int ctl, status, timeout = HZ;
+ u8 data;
+ struct imx_i2c_struct *i2c_imx = (struct imx_i2c_struct *) pdata;
+
+ do {
+ wait_event_timeout(i2c_imx->slave_queue,
+ atomic_read(&i2c_imx->slave_state) == I2C_IMX_SLAVE_IRQ,
+ timeout);
+
+ if (atomic_read(&i2c_imx->slave_state) == I2C_IMX_SLAVE_IRQ) {
+ atomic_set(&i2c_imx->slave_state,
+ I2C_IMX_SLAVE_POLLING);
+
+ timeout = HZ/10;
+ status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ ctl = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
+
+ if (status & I2SR_IAAS) {
+ if (status & I2SR_SRW) {
+ /* master wants to read from us */
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_READ_REQUESTED, &data);
+ ctl |= I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+
+ /*send data */
+ imx_i2c_write_reg(data, i2c_imx, IMX_I2C_I2DR);
+ } else {
+ dev_dbg(&i2c_imx->adapter.dev, "write requested");
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_WRITE_REQUESTED, &data);
+ /*slave receive */
+ ctl &= ~I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+
+ /*dummy read */
+ data = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
+ }
+ } else {
+ /* slave send */
+ if (ctl & I2CR_MTX) {
+ if (!(status & I2SR_RXAK)) { /*ACK received */
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_READ_PROCESSED, &data);
+ ctl |= I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+ /*send data */
+ imx_i2c_write_reg(data, i2c_imx, IMX_I2C_I2DR);
+ } else {
+ /*no ACK. */
+ /*dummy read */
+ dev_dbg(&i2c_imx->adapter.dev, "read requested");
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_READ_REQUESTED, &data);
+
+ ctl &= ~I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+ imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
+ }
+ } else { /*read */
+ ctl &= ~I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+
+ /*read */
+ data = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
+ dev_dbg(&i2c_imx->adapter.dev, "received %x",
+ (unsigned int) data);
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_WRITE_RECEIVED, &data);
+ }
+ }
+ }
+
+ if (atomic_read(&i2c_imx->slave_state) == I2C_IMX_SLAVE_POLLING) {
+ udelay(50);
+ status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+
+ if ((status & I2SR_IBB) == 0) {
+ pr_debug("end of package");
+ i2c_slave_event(i2c_imx->slave, I2C_SLAVE_STOP, &data);
+ atomic_set(&i2c_imx->slave_state, I2C_IMX_SLAVE_IDLE);
+ timeout = HZ;
+ }
+ }
+ } while (kthread_should_stop() == 0);
+
+ return 0;
+}
+
+static void i2c_imx_slave_isr_handler(struct imx_i2c_struct *i2c_imx,
+ unsigned int status)
+{
+ atomic_set(&i2c_imx->slave_state, I2C_IMX_SLAVE_IRQ);
+ wake_up(&i2c_imx->slave_queue);
}

static irqreturn_t i2c_imx_isr(int irq, void *dev_id)
{
struct imx_i2c_struct *i2c_imx = dev_id;
- unsigned int temp;
+ unsigned int current_status;
+
+ current_status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ if (current_status & I2SR_IIF) {
+ i2c_imx_clear_isr_bit(i2c_imx, current_status);
+
+ switch (i2c_imx->dev_mode) {
+ case I2C_IMX_SLAVE:
+ dev_dbg(&i2c_imx->adapter.dev, "slave interrupt");
+ i2c_imx_slave_isr_handler(i2c_imx, current_status);
+ break;
+ case I2C_IMX_MASTER:
+ dev_dbg(&i2c_imx->adapter.dev, "master interrupt");
+ i2c_imx_master_isr_handler(i2c_imx, current_status);
+ break;
+ case I2C_IMX_UNDEFINED:
+ dev_dbg(&i2c_imx->adapter.dev, "undefined interrupt");
+ break;
+ }

- temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
- if (temp & I2SR_IIF) {
- /* save status register */
- i2c_imx->i2csr = temp;
- temp &= ~I2SR_IIF;
- temp |= (i2c_imx->hwdata->i2sr_clr_opcode & I2SR_IIF);
- imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
- wake_up(&i2c_imx->queue);
return IRQ_HANDLED;
}

@@ -889,6 +1095,11 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,

dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);

+ if (atomic_read(&i2c_imx->slave_state) != I2C_IMX_SLAVE_IDLE) {
+ dev_dbg(&i2c_imx->adapter.dev, "slave is working now\n");
+ return -EBUSY;
+ }
+
result = pm_runtime_get_sync(i2c_imx->adapter.dev.parent);
if (result < 0)
goto out;
@@ -954,7 +1165,14 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,

fail0:
/* Stop I2C transfer */
- i2c_imx_stop(i2c_imx);
+ if (i2c_imx->slave != NULL) {
+ /* Stop I2C transfer */
+ i2c_imx_stop(i2c_imx, false);
+
+ i2c_imx_start_slave_mode(i2c_imx, false);
+ } else {
+ i2c_imx_stop(i2c_imx, true);
+ }

pm_runtime_mark_last_busy(i2c_imx->adapter.dev.parent);
pm_runtime_put_autosuspend(i2c_imx->adapter.dev.parent);
@@ -1013,6 +1231,46 @@ static void i2c_imx_init_recovery_info(struct imx_i2c_struct *i2c_imx,
i2c_imx->adapter.bus_recovery_info = rinfo;
}

+static int i2c_imx_reg_slave(struct i2c_client *slave)
+{
+ struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(slave->adapter);
+
+ dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
+
+ if (i2c_imx->slave)
+ return -EBUSY;
+
+ if (slave->flags & I2C_CLIENT_TEN)
+ return -EAFNOSUPPORT;
+
+ i2c_imx->slave = slave;
+
+ i2c_imx->slave_task = kthread_run(i2c_imx_slave_threadfn, (void *) i2c_imx, "i2c-slave-%s", i2c_imx->adapter.name);
+
+ return i2c_imx_start_slave_mode(i2c_imx, true);
+}
+
+static int i2c_imx_unreg_slave(struct i2c_client *slave)
+{
+ struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(slave->adapter);
+
+ dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
+
+ if (i2c_imx->slave_task != NULL)
+ kthread_stop(i2c_imx->slave_task);
+ i2c_imx->slave_task = NULL;
+
+ /* slave_state is still tested by xfer() code */
+ atomic_set(&i2c_imx->slave_state, I2C_IMX_SLAVE_IDLE);
+ i2c_imx->dev_mode = I2C_IMX_UNDEFINED;
+
+ i2c_imx->slave = NULL;
+
+ i2c_imx_stop(i2c_imx, true);
+
+ return 0;
+}
+
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
@@ -1022,6 +1280,8 @@ static u32 i2c_imx_func(struct i2c_adapter *adapter)
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
+ .reg_slave = i2c_imx_reg_slave,
+ .unreg_slave = i2c_imx_unreg_slave,
};

static int i2c_imx_probe(struct platform_device *pdev)
@@ -1097,6 +1357,7 @@ static int i2c_imx_probe(struct platform_device *pdev)

/* Init queue */
init_waitqueue_head(&i2c_imx->queue);
+ init_waitqueue_head(&i2c_imx->slave_queue);

/* Set up adapter data */
i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
@@ -1146,6 +1407,11 @@ static int i2c_imx_probe(struct platform_device *pdev)
/* Init DMA config if supported */
i2c_imx_dma_request(i2c_imx, phy_addr);

+ /* init slave_state to IDLE */
+ atomic_set(&i2c_imx->slave_state, I2C_IMX_SLAVE_IDLE);
+
+ i2c_imx->dev_mode = I2C_IMX_UNDEFINED;
+
return 0; /* Return OK */

rpm_disable:
@@ -1172,6 +1438,9 @@ static int i2c_imx_remove(struct platform_device *pdev)
dev_dbg(&i2c_imx->adapter.dev, "adapter removed\n");
i2c_del_adapter(&i2c_imx->adapter);

+ if (i2c_imx->slave_task != NULL)
+ kthread_stop(i2c_imx->slave_task);
+
if (i2c_imx->dma)
i2c_imx_dma_free(i2c_imx);

--
2.5.0