[2/2] I2c receive buffer overrun bugfix - clear hold bit before end.

Message ID 5ac37ef7.5466620a.2f985.b195@mx.google.com
State New
Headers show
Series
  • Untitled series #37153
Related show

Commit Message

Andrew Worsley April 3, 2018, 12:40 p.m.
The i2c-cadence driver was not clearing the hold bit before the i2c
transfer finished. This caused the Zynq hardware to perform another
transfer even even though the transfer register was zero.  The transfer
register then wraps over to something like 255 and keeps transferring
more data. Before the received data checking code it would continue to
copy the extra bytes past the end of the receive buffer.

The fix removes the hold bit just before reading the byte that allows
the last part of the transfer to completely fit into the FIFO.  As soon
as the last data is read the hardware will correctly release the clock and data
lines signaling an i2c stop and the transfer size register will be zero.
---
 drivers/i2c/busses/i2c-cadence.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

Patch

diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c
index 81f924d4adcb..925f77dfded8 100644
--- a/drivers/i2c/busses/i2c-cadence.c
+++ b/drivers/i2c/busses/i2c-cadence.c
@@ -430,6 +430,10 @@  static irqreturn_t cdns_i2c_master_isr(void *ptr)
 	    ((isr_status & CDNS_I2C_IXR_COMP) ||
 	     (isr_status & CDNS_I2C_IXR_DATA))) {
 		unsigned char *p = id->p_recv_buf;
+                int check_hold = 0;
+
+                if ((id->recv_count <= 2*CDNS_I2C_FIFO_DEPTH) && !id->bus_hold_flag)
+                    check_hold = cdns_i2c_readreg(CDNS_I2C_CR_OFFSET) & CDNS_I2C_CR_HOLD;
 		/* Read data if receive data valid is set */
 		while (cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) &
 		       CDNS_I2C_SR_RXDV) {
@@ -438,9 +442,6 @@  static irqreturn_t cdns_i2c_master_isr(void *ptr)
 			 * RX data left is less than FIFO depth, unless
 			 * repeated start is selected.
 			 */
-			if ((id->recv_count < CDNS_I2C_FIFO_DEPTH) &&
-			    !id->bus_hold_flag)
-				cdns_i2c_clear_bus_hold(id);
 
 			if (id->recv_count == 0) {
 				pr_notice("%s: i2c receive buffer filled : %u aborting transfer %p - %p\n",
@@ -450,6 +451,8 @@  static irqreturn_t cdns_i2c_master_isr(void *ptr)
 
 			*(id->p_recv_buf)++ =
 				cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);
+			if (check_hold && id->recv_count == CDNS_I2C_FIFO_DEPTH + 1)
+				cdns_i2c_clear_bus_hold(id);
 			id->recv_count--;
 			id->curr_recv_count--;