0d9b165aba62b272b6ec5ae2a103eb5d7277a398
[openwrt/staging/stintel.git] /
1 From 8feb8081c74d15ce368baa42981ca98e77800c03 Mon Sep 17 00:00:00 2001
2 From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= <noralf@tronnes.org>
3 Date: Fri, 23 Sep 2016 18:24:38 +0200
4 Subject: [PATCH] i2c: bcm2835: Protect against unexpected TXW/RXR interrupts
5 MIME-Version: 1.0
6 Content-Type: text/plain; charset=UTF-8
7 Content-Transfer-Encoding: 8bit
8
9 If an unexpected TXW or RXR interrupt occurs (msg_buf_remaining == 0),
10 the driver has no way to fill/drain the FIFO to stop the interrupts.
11 In this case the controller has to be disabled and the transfer
12 completed to avoid hang.
13
14 (CLKT | ERR) and DONE interrupts are completed in their own paths, and
15 the controller is disabled in the transfer function after completion.
16 Unite the code paths and do disabling inside the interrupt routine.
17
18 Clear interrupt status bits in the united completion path instead of
19 trying to do it on every interrupt which isn't necessary.
20 Only CLKT, ERR and DONE can be cleared that way.
21
22 Add the status value to the error value in case of TXW/RXR errors to
23 distinguish them from the other S_LEN error.
24
25 Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
26 Reviewed-by: Eric Anholt <eric@anholt.net>
27 ---
28 drivers/i2c/busses/i2c-bcm2835.c | 40 +++++++++++++++++++++++++++++++---------
29 1 file changed, 31 insertions(+), 9 deletions(-)
30
31 --- a/drivers/i2c/busses/i2c-bcm2835.c
32 +++ b/drivers/i2c/busses/i2c-bcm2835.c
33 @@ -50,8 +50,6 @@
34 #define BCM2835_I2C_S_CLKT BIT(9)
35 #define BCM2835_I2C_S_LEN BIT(10) /* Fake bit for SW error reporting */
36
37 -#define BCM2835_I2C_BITMSK_S 0x03FF
38 -
39 #define BCM2835_I2C_CDIV_MIN 0x0002
40 #define BCM2835_I2C_CDIV_MAX 0xFFFE
41
42 @@ -111,20 +109,26 @@ static void bcm2835_drain_rxfifo(struct
43 }
44 }
45
46 +/*
47 + * Note about I2C_C_CLEAR on error:
48 + * The I2C_C_CLEAR on errors will take some time to resolve -- if you were in
49 + * non-idle state and I2C_C_READ, it sets an abort_rx flag and runs through
50 + * the state machine to send a NACK and a STOP. Since we're setting CLEAR
51 + * without I2CEN, that NACK will be hanging around queued up for next time
52 + * we start the engine.
53 + */
54 +
55 static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data)
56 {
57 struct bcm2835_i2c_dev *i2c_dev = data;
58 u32 val, err;
59
60 val = bcm2835_i2c_readl(i2c_dev, BCM2835_I2C_S);
61 - val &= BCM2835_I2C_BITMSK_S;
62 - bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_S, val);
63
64 err = val & (BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR);
65 if (err) {
66 i2c_dev->msg_err = err;
67 - complete(&i2c_dev->completion);
68 - return IRQ_HANDLED;
69 + goto complete;
70 }
71
72 if (val & BCM2835_I2C_S_DONE) {
73 @@ -139,21 +143,38 @@ static irqreturn_t bcm2835_i2c_isr(int t
74 i2c_dev->msg_err = BCM2835_I2C_S_LEN;
75 else
76 i2c_dev->msg_err = 0;
77 - complete(&i2c_dev->completion);
78 - return IRQ_HANDLED;
79 + goto complete;
80 }
81
82 if (val & BCM2835_I2C_S_TXW) {
83 + if (!i2c_dev->msg_buf_remaining) {
84 + i2c_dev->msg_err = val | BCM2835_I2C_S_LEN;
85 + goto complete;
86 + }
87 +
88 bcm2835_fill_txfifo(i2c_dev);
89 return IRQ_HANDLED;
90 }
91
92 if (val & BCM2835_I2C_S_RXR) {
93 + if (!i2c_dev->msg_buf_remaining) {
94 + i2c_dev->msg_err = val | BCM2835_I2C_S_LEN;
95 + goto complete;
96 + }
97 +
98 bcm2835_drain_rxfifo(i2c_dev);
99 return IRQ_HANDLED;
100 }
101
102 return IRQ_NONE;
103 +
104 +complete:
105 + bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, BCM2835_I2C_C_CLEAR);
106 + bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_S, BCM2835_I2C_S_CLKT |
107 + BCM2835_I2C_S_ERR | BCM2835_I2C_S_DONE);
108 + complete(&i2c_dev->completion);
109 +
110 + return IRQ_HANDLED;
111 }
112
113 static int bcm2835_i2c_xfer_msg(struct bcm2835_i2c_dev *i2c_dev,
114 @@ -183,8 +204,9 @@ static int bcm2835_i2c_xfer_msg(struct b
115
116 time_left = wait_for_completion_timeout(&i2c_dev->completion,
117 BCM2835_I2C_TIMEOUT);
118 - bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, BCM2835_I2C_C_CLEAR);
119 if (!time_left) {
120 + bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C,
121 + BCM2835_I2C_C_CLEAR);
122 dev_err(i2c_dev->dev, "i2c transfer timed out\n");
123 return -ETIMEDOUT;
124 }