Commit e7663ef5 authored by Federico Vaga's avatar Federico Vaga Committed by Wolfram Sang

i2c: ocores: stop transfer on timeout

Detecting a timeout is ok, but we also need to assert a STOP command on
the bus in order to prevent it from generating interrupts when there are
no on going transfers.

Example: very long transmission.

1. ocores_xfer: START a transfer
2. ocores_isr : handle byte by byte the transfer
3. ocores_xfer: goes in timeout [[bugfix here]]
4. ocores_xfer: return to I2C subsystem and to the I2C driver
5. I2C driver : it may clean up the i2c_msg memory
6. ocores_isr : receives another interrupt (pending bytes to be
                transferred) but the i2c_msg memory is invalid now

So, since the transfer was too long, we have to detect the timeout and
STOP the transfer.

Another point is that we have a critical region here. When handling the
timeout condition we may have a running IRQ handler. For this reason I
introduce a spinlock.

In order to make easier to understan locking I have:
- added a new function to handle timeout
- modified the current ocores_process() function in order to be protected
  by the new spinlock
Like this it is obvious at first sight that this locking serializes
the execution of ocores_process() and ocores_process_timeout()
Signed-off-by: default avatarFederico Vaga <federico.vaga@cern.ch>
Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarWolfram Sang <wsa@the-dreams.de>
parent 0940d249
...@@ -25,7 +25,12 @@ ...@@ -25,7 +25,12 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/log2.h> #include <linux/log2.h>
#include <linux/spinlock.h>
/**
* @process_lock: protect I2C transfer process.
* ocores_process() and ocores_process_timeout() can't run in parallel.
*/
struct ocores_i2c { struct ocores_i2c {
void __iomem *base; void __iomem *base;
u32 reg_shift; u32 reg_shift;
...@@ -36,6 +41,7 @@ struct ocores_i2c { ...@@ -36,6 +41,7 @@ struct ocores_i2c {
int pos; int pos;
int nmsgs; int nmsgs;
int state; /* see STATE_ */ int state; /* see STATE_ */
spinlock_t process_lock;
struct clk *clk; struct clk *clk;
int ip_clock_khz; int ip_clock_khz;
int bus_clock_khz; int bus_clock_khz;
...@@ -141,19 +147,26 @@ static void ocores_process(struct ocores_i2c *i2c) ...@@ -141,19 +147,26 @@ static void ocores_process(struct ocores_i2c *i2c)
{ {
struct i2c_msg *msg = i2c->msg; struct i2c_msg *msg = i2c->msg;
u8 stat = oc_getreg(i2c, OCI2C_STATUS); u8 stat = oc_getreg(i2c, OCI2C_STATUS);
unsigned long flags;
/*
* If we spin here is because we are in timeout, so we are going
* to be in STATE_ERROR. See ocores_process_timeout()
*/
spin_lock_irqsave(&i2c->process_lock, flags);
if ((i2c->state == STATE_DONE) || (i2c->state == STATE_ERROR)) { if ((i2c->state == STATE_DONE) || (i2c->state == STATE_ERROR)) {
/* stop has been sent */ /* stop has been sent */
oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK); oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK);
wake_up(&i2c->wait); wake_up(&i2c->wait);
return; goto out;
} }
/* error? */ /* error? */
if (stat & OCI2C_STAT_ARBLOST) { if (stat & OCI2C_STAT_ARBLOST) {
i2c->state = STATE_ERROR; i2c->state = STATE_ERROR;
oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP);
return; goto out;
} }
if ((i2c->state == STATE_START) || (i2c->state == STATE_WRITE)) { if ((i2c->state == STATE_START) || (i2c->state == STATE_WRITE)) {
...@@ -163,7 +176,7 @@ static void ocores_process(struct ocores_i2c *i2c) ...@@ -163,7 +176,7 @@ static void ocores_process(struct ocores_i2c *i2c)
if (stat & OCI2C_STAT_NACK) { if (stat & OCI2C_STAT_NACK) {
i2c->state = STATE_ERROR; i2c->state = STATE_ERROR;
oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP);
return; goto out;
} }
} else } else
msg->buf[i2c->pos++] = oc_getreg(i2c, OCI2C_DATA); msg->buf[i2c->pos++] = oc_getreg(i2c, OCI2C_DATA);
...@@ -184,14 +197,14 @@ static void ocores_process(struct ocores_i2c *i2c) ...@@ -184,14 +197,14 @@ static void ocores_process(struct ocores_i2c *i2c)
oc_setreg(i2c, OCI2C_DATA, addr); oc_setreg(i2c, OCI2C_DATA, addr);
oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START);
return; goto out;
} else } else
i2c->state = (msg->flags & I2C_M_RD) i2c->state = (msg->flags & I2C_M_RD)
? STATE_READ : STATE_WRITE; ? STATE_READ : STATE_WRITE;
} else { } else {
i2c->state = STATE_DONE; i2c->state = STATE_DONE;
oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP);
return; goto out;
} }
} }
...@@ -202,6 +215,9 @@ static void ocores_process(struct ocores_i2c *i2c) ...@@ -202,6 +215,9 @@ static void ocores_process(struct ocores_i2c *i2c)
oc_setreg(i2c, OCI2C_DATA, msg->buf[i2c->pos++]); oc_setreg(i2c, OCI2C_DATA, msg->buf[i2c->pos++]);
oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_WRITE); oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_WRITE);
} }
out:
spin_unlock_irqrestore(&i2c->process_lock, flags);
} }
static irqreturn_t ocores_isr(int irq, void *dev_id) static irqreturn_t ocores_isr(int irq, void *dev_id)
...@@ -213,9 +229,24 @@ static irqreturn_t ocores_isr(int irq, void *dev_id) ...@@ -213,9 +229,24 @@ static irqreturn_t ocores_isr(int irq, void *dev_id)
return IRQ_HANDLED; return IRQ_HANDLED;
} }
/**
* Process timeout event
* @i2c: ocores I2C device instance
*/
static void ocores_process_timeout(struct ocores_i2c *i2c)
{
unsigned long flags;
spin_lock_irqsave(&i2c->process_lock, flags);
i2c->state = STATE_ERROR;
oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP);
spin_unlock_irqrestore(&i2c->process_lock, flags);
}
static int ocores_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) static int ocores_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{ {
struct ocores_i2c *i2c = i2c_get_adapdata(adap); struct ocores_i2c *i2c = i2c_get_adapdata(adap);
int ret;
i2c->msg = msgs; i2c->msg = msgs;
i2c->pos = 0; i2c->pos = 0;
...@@ -225,11 +256,14 @@ static int ocores_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) ...@@ -225,11 +256,14 @@ static int ocores_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
oc_setreg(i2c, OCI2C_DATA, i2c_8bit_addr_from_msg(i2c->msg)); oc_setreg(i2c, OCI2C_DATA, i2c_8bit_addr_from_msg(i2c->msg));
oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START);
if (wait_event_timeout(i2c->wait, (i2c->state == STATE_ERROR) || ret = wait_event_timeout(i2c->wait, (i2c->state == STATE_ERROR) ||
(i2c->state == STATE_DONE), HZ)) (i2c->state == STATE_DONE), HZ);
return (i2c->state == STATE_DONE) ? num : -EIO; if (ret == 0) {
else ocores_process_timeout(i2c);
return -ETIMEDOUT; return -ETIMEDOUT;
}
return (i2c->state == STATE_DONE) ? num : -EIO;
} }
static int ocores_init(struct device *dev, struct ocores_i2c *i2c) static int ocores_init(struct device *dev, struct ocores_i2c *i2c)
...@@ -422,6 +456,8 @@ static int ocores_i2c_probe(struct platform_device *pdev) ...@@ -422,6 +456,8 @@ static int ocores_i2c_probe(struct platform_device *pdev)
if (!i2c) if (!i2c)
return -ENOMEM; return -ENOMEM;
spin_lock_init(&i2c->process_lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->base = devm_ioremap_resource(&pdev->dev, res); i2c->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(i2c->base)) if (IS_ERR(i2c->base))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment