mirror of https://github.com/hak5/openwrt.git
125 lines
3.8 KiB
Diff
125 lines
3.8 KiB
Diff
From 95729fe299cca5ef749f4b6a2c3e0f40ec7b43b9 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= <noralf@tronnes.org>
|
|
Date: Fri, 23 Sep 2016 18:24:38 +0200
|
|
Subject: [PATCH] i2c: bcm2835: Protect against unexpected TXW/RXR interrupts
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
If an unexpected TXW or RXR interrupt occurs (msg_buf_remaining == 0),
|
|
the driver has no way to fill/drain the FIFO to stop the interrupts.
|
|
In this case the controller has to be disabled and the transfer
|
|
completed to avoid hang.
|
|
|
|
(CLKT | ERR) and DONE interrupts are completed in their own paths, and
|
|
the controller is disabled in the transfer function after completion.
|
|
Unite the code paths and do disabling inside the interrupt routine.
|
|
|
|
Clear interrupt status bits in the united completion path instead of
|
|
trying to do it on every interrupt which isn't necessary.
|
|
Only CLKT, ERR and DONE can be cleared that way.
|
|
|
|
Add the status value to the error value in case of TXW/RXR errors to
|
|
distinguish them from the other S_LEN error.
|
|
|
|
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
|
|
Reviewed-by: Eric Anholt <eric@anholt.net>
|
|
---
|
|
drivers/i2c/busses/i2c-bcm2835.c | 40 +++++++++++++++++++++++++++++++---------
|
|
1 file changed, 31 insertions(+), 9 deletions(-)
|
|
|
|
--- a/drivers/i2c/busses/i2c-bcm2835.c
|
|
+++ b/drivers/i2c/busses/i2c-bcm2835.c
|
|
@@ -50,8 +50,6 @@
|
|
#define BCM2835_I2C_S_CLKT BIT(9)
|
|
#define BCM2835_I2C_S_LEN BIT(10) /* Fake bit for SW error reporting */
|
|
|
|
-#define BCM2835_I2C_BITMSK_S 0x03FF
|
|
-
|
|
#define BCM2835_I2C_CDIV_MIN 0x0002
|
|
#define BCM2835_I2C_CDIV_MAX 0xFFFE
|
|
|
|
@@ -111,20 +109,26 @@ static void bcm2835_drain_rxfifo(struct
|
|
}
|
|
}
|
|
|
|
+/*
|
|
+ * Note about I2C_C_CLEAR on error:
|
|
+ * The I2C_C_CLEAR on errors will take some time to resolve -- if you were in
|
|
+ * non-idle state and I2C_C_READ, it sets an abort_rx flag and runs through
|
|
+ * the state machine to send a NACK and a STOP. Since we're setting CLEAR
|
|
+ * without I2CEN, that NACK will be hanging around queued up for next time
|
|
+ * we start the engine.
|
|
+ */
|
|
+
|
|
static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data)
|
|
{
|
|
struct bcm2835_i2c_dev *i2c_dev = data;
|
|
u32 val, err;
|
|
|
|
val = bcm2835_i2c_readl(i2c_dev, BCM2835_I2C_S);
|
|
- val &= BCM2835_I2C_BITMSK_S;
|
|
- bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_S, val);
|
|
|
|
err = val & (BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR);
|
|
if (err) {
|
|
i2c_dev->msg_err = err;
|
|
- complete(&i2c_dev->completion);
|
|
- return IRQ_HANDLED;
|
|
+ goto complete;
|
|
}
|
|
|
|
if (val & BCM2835_I2C_S_DONE) {
|
|
@@ -137,21 +141,38 @@ static irqreturn_t bcm2835_i2c_isr(int t
|
|
i2c_dev->msg_err = BCM2835_I2C_S_LEN;
|
|
else
|
|
i2c_dev->msg_err = 0;
|
|
- complete(&i2c_dev->completion);
|
|
- return IRQ_HANDLED;
|
|
+ goto complete;
|
|
}
|
|
|
|
if (val & BCM2835_I2C_S_TXW) {
|
|
+ if (!i2c_dev->msg_buf_remaining) {
|
|
+ i2c_dev->msg_err = val | BCM2835_I2C_S_LEN;
|
|
+ goto complete;
|
|
+ }
|
|
+
|
|
bcm2835_fill_txfifo(i2c_dev);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (val & BCM2835_I2C_S_RXR) {
|
|
+ if (!i2c_dev->msg_buf_remaining) {
|
|
+ i2c_dev->msg_err = val | BCM2835_I2C_S_LEN;
|
|
+ goto complete;
|
|
+ }
|
|
+
|
|
bcm2835_drain_rxfifo(i2c_dev);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
return IRQ_NONE;
|
|
+
|
|
+complete:
|
|
+ bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, BCM2835_I2C_C_CLEAR);
|
|
+ bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_S, BCM2835_I2C_S_CLKT |
|
|
+ BCM2835_I2C_S_ERR | BCM2835_I2C_S_DONE);
|
|
+ complete(&i2c_dev->completion);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
}
|
|
|
|
static int bcm2835_i2c_xfer_msg(struct bcm2835_i2c_dev *i2c_dev,
|
|
@@ -181,8 +202,9 @@ static int bcm2835_i2c_xfer_msg(struct b
|
|
|
|
time_left = wait_for_completion_timeout(&i2c_dev->completion,
|
|
BCM2835_I2C_TIMEOUT);
|
|
- bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, BCM2835_I2C_C_CLEAR);
|
|
if (!time_left) {
|
|
+ bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C,
|
|
+ BCM2835_I2C_C_CLEAR);
|
|
dev_err(i2c_dev->dev, "i2c transfer timed out\n");
|
|
return -ETIMEDOUT;
|
|
}
|