| From 12bc93ab89d8ad575da4e01e1cab2708c9e08076 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= <noralf@tronnes.org> |
| Date: Mon, 3 Oct 2016 22:06:08 +0200 |
| Subject: [PATCH] i2c: bcm2835: Fix hang for writing messages larger than 16 |
| bytes |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| commit e2474541032db65d02bf88b6a8c2f954654b443f upstream. |
| |
| Writing messages larger than the FIFO size results in a hang, rendering |
| the machine unusable. This is because the RXD status flag is set on the |
| first interrupt which results in bcm2835_drain_rxfifo() stealing bytes |
| from the buffer. The controller continues to trigger interrupts waiting |
| for the missing bytes, but bcm2835_fill_txfifo() has none to give. |
| In this situation wait_for_completion_timeout() apparently is unable to |
| stop the madness. |
| |
| The BCM2835 ARM Peripherals datasheet has this to say about the flags: |
| TXD: is set when the FIFO has space for at least one byte of data. |
| RXD: is set when the FIFO contains at least one byte of data. |
| TXW: is set during a write transfer and the FIFO is less than full. |
| RXR: is set during a read transfer and the FIFO is or more full. |
| |
| Implementing the logic from the downstream i2c-bcm2708 driver solved |
| the hang problem. |
| |
| Signed-off-by: Noralf TrΓΈnnes <noralf@tronnes.org> |
| Reviewed-by: Eric Anholt <eric@anholt.net> |
| Reviewed-by: Martin Sperl <kernel@martin.sperl.org> |
| Signed-off-by: Wolfram Sang <wsa@the-dreams.de> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/i2c/busses/i2c-bcm2835.c b/drivers/i2c/busses/i2c-bcm2835.c |
| index d4f3239b5686..f283b714aa79 100644 |
| --- a/drivers/i2c/busses/i2c-bcm2835.c |
| +++ b/drivers/i2c/busses/i2c-bcm2835.c |
| @@ -64,6 +64,7 @@ struct bcm2835_i2c_dev { |
| int irq; |
| struct i2c_adapter adapter; |
| struct completion completion; |
| + struct i2c_msg *curr_msg; |
| u32 msg_err; |
| u8 *msg_buf; |
| size_t msg_buf_remaining; |
| @@ -126,14 +127,13 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data) |
| return IRQ_HANDLED; |
| } |
| |
| - if (val & BCM2835_I2C_S_RXD) { |
| - bcm2835_drain_rxfifo(i2c_dev); |
| - if (!(val & BCM2835_I2C_S_DONE)) |
| - return IRQ_HANDLED; |
| - } |
| - |
| if (val & BCM2835_I2C_S_DONE) { |
| - if (i2c_dev->msg_buf_remaining) |
| + if (i2c_dev->curr_msg->flags & I2C_M_RD) { |
| + bcm2835_drain_rxfifo(i2c_dev); |
| + val = bcm2835_i2c_readl(i2c_dev, BCM2835_I2C_S); |
| + } |
| + |
| + if ((val & BCM2835_I2C_S_RXD) || i2c_dev->msg_buf_remaining) |
| i2c_dev->msg_err = BCM2835_I2C_S_LEN; |
| else |
| i2c_dev->msg_err = 0; |
| @@ -141,11 +141,16 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data) |
| return IRQ_HANDLED; |
| } |
| |
| - if (val & BCM2835_I2C_S_TXD) { |
| + if (val & BCM2835_I2C_S_TXW) { |
| bcm2835_fill_txfifo(i2c_dev); |
| return IRQ_HANDLED; |
| } |
| |
| + if (val & BCM2835_I2C_S_RXR) { |
| + bcm2835_drain_rxfifo(i2c_dev); |
| + return IRQ_HANDLED; |
| + } |
| + |
| return IRQ_NONE; |
| } |
| |
| @@ -155,6 +160,7 @@ static int bcm2835_i2c_xfer_msg(struct bcm2835_i2c_dev *i2c_dev, |
| u32 c; |
| unsigned long time_left; |
| |
| + i2c_dev->curr_msg = msg; |
| i2c_dev->msg_buf = msg->buf; |
| i2c_dev->msg_buf_remaining = msg->len; |
| reinit_completion(&i2c_dev->completion); |
| -- |
| 2.12.0 |
| |