Commit 41f53812 authored by Troy Kisky's avatar Troy Kisky Committed by Kevin Hilman

I2C: DaVinci: fix signal handling bug

If wait_for_completion_interruptible_timeout exits due
to a signal, the i2c bus was locking up.
Signed-off-by: default avatarTroy Kisky <troy.kisky@boundarydevices.com>
Signed-off-by: default avatarKevin Hilman <khilman@mvista.com>
parent b686c5d3
...@@ -90,6 +90,7 @@ ...@@ -90,6 +90,7 @@
#define DAVINCI_I2C_MDR_MST (1 << 10) #define DAVINCI_I2C_MDR_MST (1 << 10)
#define DAVINCI_I2C_MDR_TRX (1 << 9) #define DAVINCI_I2C_MDR_TRX (1 << 9)
#define DAVINCI_I2C_MDR_XA (1 << 8) #define DAVINCI_I2C_MDR_XA (1 << 8)
#define DAVINCI_I2C_MDR_RM (1 << 7)
#define DAVINCI_I2C_MDR_IRS (1 << 5) #define DAVINCI_I2C_MDR_IRS (1 << 5)
#define DAVINCI_I2C_IMR_AAS (1 << 6) #define DAVINCI_I2C_IMR_AAS (1 << 6)
...@@ -117,6 +118,7 @@ struct davinci_i2c_dev { ...@@ -117,6 +118,7 @@ struct davinci_i2c_dev {
u8 *buf; u8 *buf;
size_t buf_len; size_t buf_len;
int irq; int irq;
u8 terminate;
struct i2c_adapter adapter; struct i2c_adapter adapter;
}; };
...@@ -303,20 +305,31 @@ i2c_davinci_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int stop) ...@@ -303,20 +305,31 @@ i2c_davinci_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int stop)
MOD_REG_BIT(w, DAVINCI_I2C_IMR_XRDY, 1); MOD_REG_BIT(w, DAVINCI_I2C_IMR_XRDY, 1);
davinci_i2c_write_reg(dev, DAVINCI_I2C_IMR_REG, w); davinci_i2c_write_reg(dev, DAVINCI_I2C_IMR_REG, w);
dev->terminate = 0;
/* write the data into mode register */ /* write the data into mode register */
davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, flag); davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, flag);
r = wait_for_completion_interruptible_timeout(&dev->cmd_complete, r = wait_for_completion_interruptible_timeout(&dev->cmd_complete,
DAVINCI_I2C_TIMEOUT); DAVINCI_I2C_TIMEOUT);
dev->buf_len = 0;
if (r < 0)
return r;
if (r == 0) { if (r == 0) {
dev_err(dev->dev, "controller timed out\n"); dev_err(dev->dev, "controller timed out\n");
i2c_davinci_init(dev); i2c_davinci_init(dev);
dev->buf = NULL;
return -ETIMEDOUT; return -ETIMEDOUT;
} }
if (dev->buf_len) {
/* signal may have aborted the transfer */
if (r >= 0) {
dev_err(dev->dev, "abnormal termination buf_len=%i\n",
dev->buf_len);
r = -EREMOTEIO;
}
dev->terminate = 1;
wmb();
dev->buf = NULL;
}
if (r < 0)
return r;
/* no error */ /* no error */
if (likely(!dev->cmd_err)) if (likely(!dev->cmd_err))
...@@ -377,6 +390,30 @@ static u32 i2c_davinci_func(struct i2c_adapter *adap) ...@@ -377,6 +390,30 @@ static u32 i2c_davinci_func(struct i2c_adapter *adap)
#endif #endif
} }
static inline void terminate_read(struct davinci_i2c_dev *dev)
{
if (dev->buf_len)
dev->buf_len--;
if (dev->buf_len) {
u16 w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG);
w |= DAVINCI_I2C_MDR_NACK;
davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w);
}
/* Throw away data */
davinci_i2c_read_reg(dev, DAVINCI_I2C_DRR_REG);
if (!dev->terminate)
dev_err(dev->dev, "RDR IRQ while no data requested\n");
}
static inline void terminate_write(struct davinci_i2c_dev *dev)
{
u16 w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG);
w |= DAVINCI_I2C_MDR_RM|DAVINCI_I2C_MDR_STP;
davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w);
if (!dev->terminate)
dev_err(dev->dev, "TDR IRQ while no data to send\n");
}
/* /*
* Interrupt service routine. This gets called whenever an I2C interrupt * Interrupt service routine. This gets called whenever an I2C interrupt
* occurs. * occurs.
...@@ -397,12 +434,15 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id) ...@@ -397,12 +434,15 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)
switch (stat) { switch (stat) {
case DAVINCI_I2C_IVR_AL: case DAVINCI_I2C_IVR_AL:
/* Arbitration lost, must retry */
dev->cmd_err |= DAVINCI_I2C_STR_AL; dev->cmd_err |= DAVINCI_I2C_STR_AL;
dev->buf_len = 0;
complete(&dev->cmd_complete); complete(&dev->cmd_complete);
break; break;
case DAVINCI_I2C_IVR_NACK: case DAVINCI_I2C_IVR_NACK:
dev->cmd_err |= DAVINCI_I2C_STR_NACK; dev->cmd_err |= DAVINCI_I2C_STR_NACK;
dev->buf_len = 0;
complete(&dev->cmd_complete); complete(&dev->cmd_complete);
break; break;
...@@ -413,7 +453,7 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id) ...@@ -413,7 +453,7 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)
break; break;
case DAVINCI_I2C_IVR_RDR: case DAVINCI_I2C_IVR_RDR:
if (dev->buf_len) { if (dev->buf && dev->buf_len) {
*dev->buf++ = *dev->buf++ =
davinci_i2c_read_reg(dev, davinci_i2c_read_reg(dev,
DAVINCI_I2C_DRR_REG); DAVINCI_I2C_DRR_REG);
...@@ -424,13 +464,14 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id) ...@@ -424,13 +464,14 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)
davinci_i2c_write_reg(dev, davinci_i2c_write_reg(dev,
DAVINCI_I2C_STR_REG, DAVINCI_I2C_STR_REG,
DAVINCI_I2C_IMR_RRDY); DAVINCI_I2C_IMR_RRDY);
} else } else {
dev_err(dev->dev, "RDR IRQ while no " /* signal can terminate transfer */
"data requested\n"); terminate_read(dev);
}
break; break;
case DAVINCI_I2C_IVR_XRDY: case DAVINCI_I2C_IVR_XRDY:
if (dev->buf_len) { if (dev->buf && dev->buf_len) {
davinci_i2c_write_reg(dev, DAVINCI_I2C_DXR_REG, davinci_i2c_write_reg(dev, DAVINCI_I2C_DXR_REG,
*dev->buf++); *dev->buf++);
dev->buf_len--; dev->buf_len--;
...@@ -443,9 +484,10 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id) ...@@ -443,9 +484,10 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)
davinci_i2c_write_reg(dev, davinci_i2c_write_reg(dev,
DAVINCI_I2C_IMR_REG, DAVINCI_I2C_IMR_REG,
w); w);
} else } else {
dev_err(dev->dev, "TDR IRQ while no data to " /* signal can terminate transfer */
"send\n"); terminate_write(dev);
}
break; break;
case DAVINCI_I2C_IVR_SCD: case DAVINCI_I2C_IVR_SCD:
......
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