aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/brcm2708/patches-4.9/950-0107-i2c-bcm2835-Protect-against-unexpected-TXW-RXR-inter.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/brcm2708/patches-4.9/950-0107-i2c-bcm2835-Protect-against-unexpected-TXW-RXR-inter.patch')
-rw-r--r--target/linux/brcm2708/patches-4.9/950-0107-i2c-bcm2835-Protect-against-unexpected-TXW-RXR-inter.patch124
1 files changed, 124 insertions, 0 deletions
diff --git a/target/linux/brcm2708/patches-4.9/950-0107-i2c-bcm2835-Protect-against-unexpected-TXW-RXR-inter.patch b/target/linux/brcm2708/patches-4.9/950-0107-i2c-bcm2835-Protect-against-unexpected-TXW-RXR-inter.patch
new file mode 100644
index 0000000000..6646d77944
--- /dev/null
+++ b/target/linux/brcm2708/patches-4.9/950-0107-i2c-bcm2835-Protect-against-unexpected-TXW-RXR-inter.patch
@@ -0,0 +1,124 @@
+From 8feb8081c74d15ce368baa42981ca98e77800c03 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;
+ }