summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorjeanleflambeur <catalin.vasile@gmail.com>2015-02-01 12:35:38 +0100
committerpopcornmix <popcornmix@gmail.com>2015-05-18 14:11:49 +0100
commit0e2bf64dfa73e3b03f9600989024d7313d06912b (patch)
tree860a617eaae1785b30cfd5260b742de9723eee43 /drivers
parent2e4da5fe9cdf8094eb1c7ffa8ae6cc7b66ea3558 (diff)
downloadlinux-0e2bf64dfa73e3b03f9600989024d7313d06912b.tar.gz
Fix grabbing lock from atomic context in i2c driver
2 main changes: - check for timeouts in the bcm2708_bsc_setup function as indicated by this comment: /* poll for transfer start bit (should only take 1-20 polls) */ This implies that the setup function can now fail so account for this everywhere it's called - Removed the clk_get_rate call from inside the setup function as it locks a mutex and that's not ok since we call it from under a spin lock. removed dead code and update comment fixed typo in comment
Diffstat (limited to 'drivers')
-rw-r--r--drivers/i2c/busses/i2c-bcm2708.c90
1 files changed, 65 insertions, 25 deletions
diff --git a/drivers/i2c/busses/i2c-bcm2708.c b/drivers/i2c/busses/i2c-bcm2708.c
index fda59ba7ae30..81e9374ea120 100644
--- a/drivers/i2c/busses/i2c-bcm2708.c
+++ b/drivers/i2c/busses/i2c-bcm2708.c
@@ -68,6 +68,7 @@
#define BSC_S_TA 0x00000001
#define I2C_TIMEOUT_MS 150
+#define I2C_WAIT_LOOP_COUNT 40
#define DRV_NAME "bcm2708_i2c"
@@ -86,6 +87,7 @@ struct bcm2708_i2c {
void __iomem *base;
int irq;
struct clk *clk;
+ u32 cdiv;
struct completion done;
@@ -109,10 +111,10 @@ static void bcm2708_i2c_init_pinmode(int id)
int pin;
u32 *gpio = ioremap(GPIO_BASE, SZ_16K);
- BUG_ON(id != 0 && id != 1);
+ BUG_ON(id != 0 && id != 1);
/* BSC0 is on GPIO 0 & 1, BSC1 is on GPIO 2 & 3 */
for (pin = id*2+0; pin <= id*2+1; pin++) {
-printk("bcm2708_i2c_init_pinmode(%d,%d)\n", id, pin);
+ printk("bcm2708_i2c_init_pinmode(%d,%d)\n", id, pin);
INP_GPIO(pin); /* set mode to GPIO input first */
SET_GPIO_ALT(pin, 0); /* set mode to ALT 0 */
}
@@ -151,16 +153,16 @@ static inline void bcm2708_bsc_fifo_fill(struct bcm2708_i2c *bi)
bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
}
-static inline void bcm2708_bsc_setup(struct bcm2708_i2c *bi)
+static inline int bcm2708_bsc_setup(struct bcm2708_i2c *bi)
{
- unsigned long bus_hz;
u32 cdiv, s;
u32 c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_ST | BSC_C_CLEAR_1;
+ int wait_loops = I2C_WAIT_LOOP_COUNT;
- bus_hz = clk_get_rate(bi->clk);
- cdiv = bus_hz / baudrate;
- if (cdiv > 0xffff)
- cdiv = 0xffff;
+ /* Can't call clk_get_rate as it locks a mutex and here we are spinlocked.
+ * Use the value that we cached in the probe.
+ */
+ cdiv = bi->cdiv;
if (bi->msg->flags & I2C_M_RD)
c |= BSC_C_INTR | BSC_C_READ;
@@ -177,17 +179,25 @@ static inline void bcm2708_bsc_setup(struct bcm2708_i2c *bi)
- Both messages to same slave address
- Write message can fit inside FIFO (16 bytes or less) */
if ( (bi->nmsgs > 1) &&
- !(bi->msg[0].flags & I2C_M_RD) && (bi->msg[1].flags & I2C_M_RD) &&
- (bi->msg[0].addr == bi->msg[1].addr) && (bi->msg[0].len <= 16)) {
+ !(bi->msg[0].flags & I2C_M_RD) && (bi->msg[1].flags & I2C_M_RD) &&
+ (bi->msg[0].addr == bi->msg[1].addr) && (bi->msg[0].len <= 16)) {
/* Fill FIFO with entire write message (16 byte FIFO) */
- while (bi->pos < bi->msg->len)
+ while (bi->pos < bi->msg->len) {
bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
+ }
/* Start write transfer (no interrupts, don't clear FIFO) */
bcm2708_wr(bi, BSC_C, BSC_C_I2CEN | BSC_C_ST);
+
/* poll for transfer start bit (should only take 1-20 polls) */
do {
s = bcm2708_rd(bi, BSC_S);
- } while (!(s & (BSC_S_TA | BSC_S_ERR | BSC_S_CLKT | BSC_S_DONE)));
+ } while (!(s & (BSC_S_TA | BSC_S_ERR | BSC_S_CLKT | BSC_S_DONE)) && --wait_loops >= 0);
+
+ /* did we time out or some error occured? */
+ if (wait_loops < 0 || (s & (BSC_S_ERR | BSC_S_CLKT))) {
+ return -1;
+ }
+
/* Send next read message before the write transfer finishes. */
bi->nmsgs--;
bi->msg++;
@@ -197,6 +207,8 @@ static inline void bcm2708_bsc_setup(struct bcm2708_i2c *bi)
}
}
bcm2708_wr(bi, BSC_C, c);
+
+ return 0;
}
static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id)
@@ -204,13 +216,15 @@ static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id)
struct bcm2708_i2c *bi = dev_id;
bool handled = true;
u32 s;
+ int ret;
spin_lock(&bi->lock);
/* we may see camera interrupts on the "other" I2C channel
- Just return if we've not sent anything */
- if (!bi->nmsgs || !bi->msg )
+ Just return if we've not sent anything */
+ if (!bi->nmsgs || !bi->msg) {
goto early_exit;
+ }
s = bcm2708_rd(bi, BSC_S);
@@ -218,13 +232,16 @@ static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id)
bcm2708_bsc_reset(bi);
bi->error = true;
+ bi->msg = 0; /* to inform the that all work is done */
+ bi->nmsgs = 0;
/* wake up our bh */
complete(&bi->done);
} else if (s & BSC_S_DONE) {
bi->nmsgs--;
- if (bi->msg->flags & I2C_M_RD)
+ if (bi->msg->flags & I2C_M_RD) {
bcm2708_bsc_fifo_drain(bi);
+ }
bcm2708_bsc_reset(bi);
@@ -232,8 +249,19 @@ static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id)
/* advance to next message */
bi->msg++;
bi->pos = 0;
- bcm2708_bsc_setup(bi);
+ ret = bcm2708_bsc_setup(bi);
+ if (ret < 0) {
+ bcm2708_bsc_reset(bi);
+ bi->error = true;
+ bi->msg = 0; /* to inform the that all work is done */
+ bi->nmsgs = 0;
+ /* wake up our bh */
+ complete(&bi->done);
+ goto early_exit;
+ }
} else {
+ bi->msg = 0; /* to inform the that all work is done */
+ bi->nmsgs = 0;
/* wake up our bh */
complete(&bi->done);
}
@@ -266,22 +294,33 @@ static int bcm2708_i2c_master_xfer(struct i2c_adapter *adap,
bi->nmsgs = num;
bi->error = false;
- bcm2708_bsc_setup(bi);
+ ret = bcm2708_bsc_setup(bi);
- /* unlockig _after_ the setup to avoid races with the interrupt routine */
spin_unlock_irqrestore(&bi->lock, flags);
- ret = wait_for_completion_timeout(&bi->done,
- msecs_to_jiffies(I2C_TIMEOUT_MS));
+ /* check the result of the setup */
+ if (ret < 0)
+ {
+ dev_err(&adap->dev, "transfer setup timed out\n");
+ goto error_timeout;
+ }
+
+ ret = wait_for_completion_timeout(&bi->done, msecs_to_jiffies(I2C_TIMEOUT_MS));
if (ret == 0) {
dev_err(&adap->dev, "transfer timed out\n");
- spin_lock_irqsave(&bi->lock, flags);
- bcm2708_bsc_reset(bi);
- spin_unlock_irqrestore(&bi->lock, flags);
- return -ETIMEDOUT;
+ goto error_timeout;
}
- return bi->error ? -EIO : num;
+ ret = bi->error ? -EIO : num;
+ return ret;
+
+error_timeout:
+ spin_lock_irqsave(&bi->lock, flags);
+ bcm2708_bsc_reset(bi);
+ bi->msg = 0; /* to inform the interrupt handler that there's nothing else to be done */
+ bi->nmsgs = 0;
+ spin_unlock_irqrestore(&bi->lock, flags);
+ return -ETIMEDOUT;
}
static u32 bcm2708_i2c_functionality(struct i2c_adapter *adap)
@@ -406,6 +445,7 @@ static int bcm2708_i2c_probe(struct platform_device *pdev)
cdiv = 0xffff;
baudrate = bus_hz / cdiv;
}
+ bi->cdiv = cdiv;
dev_info(&pdev->dev, "BSC%d Controller at 0x%08lx (irq %d) (baudrate %d)\n",
pdev->id, (unsigned long)regs->start, irq, baudrate);