[PATCH] i2c: imx: add slave support

From: Dmitriy Baranov
Date: Mon Jan 25 2016 - 09:54:10 EST


Add I2C slave provider using the generic slave interface.
It also supports master transactions when the slave in the idle mode.
Changes work only in PIO mode (when driver doesn`t use DMA)
These changes weren`t tested with DMA is enabled.

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

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