[PATCH] i2c: imx: add slave support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux