diff mbox series

[v1,5/6] i2c: iproc: handle master read request

Message ID 20201011182254.17776-6-rayagonda.kokatanur@broadcom.com
State Superseded
Headers show
Series fix iproc driver to handle master read request | expand

Commit Message

Rayagonda Kokatanur Oct. 11, 2020, 6:22 p.m. UTC
Handle single or multi byte master read request with or without
repeated start.

Fixes: c245d94ed106 ("i2c: iproc: Add multi byte read-write support for slave mode")
Signed-off-by: Rayagonda Kokatanur <rayagonda.kokatanur@broadcom.com>
---
 drivers/i2c/busses/i2c-bcm-iproc.c | 215 +++++++++++++++++++++++------
 1 file changed, 170 insertions(+), 45 deletions(-)

Comments

Dhananjay Phadke Oct. 14, 2020, 3:20 a.m. UTC | #1
On Sun, 11 Oct 2020 23:52:53 +0530, Rayagonda Kokatanur wrote:
> --- a/drivers/i2c/busses/i2c-bcm-iproc.c
> +++ b/drivers/i2c/busses/i2c-bcm-iproc.c
> 
> -		} else if (status & BIT(IS_S_RD_EVENT_SHIFT)) {
> -			/* Start of SMBUS for Master Read */
> +					I2C_SLAVE_WRITE_REQUESTED, &rx_data);
> +			iproc_i2c->rx_start_rcvd = true;
> +			iproc_i2c->slave_read_complete = false;
> +		} else if (rx_status == I2C_SLAVE_RX_DATA &&
> +			   iproc_i2c->rx_start_rcvd) {
> +			/* Middle of SMBUS Master write */
>  			i2c_slave_event(iproc_i2c->slave,
> -					I2C_SLAVE_READ_REQUESTED, &value);
> -			iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, value);
> +					I2C_SLAVE_WRITE_RECEIVED, &rx_data);
> +		} else if (rx_status == I2C_SLAVE_RX_END &&
> +			   iproc_i2c->rx_start_rcvd) {
> +			/* End of SMBUS Master write */
> +			if (iproc_i2c->slave_rx_only)
> +				i2c_slave_event(iproc_i2c->slave,
> +						I2C_SLAVE_WRITE_RECEIVED,
> +						&rx_data);
> +
> +			i2c_slave_event(iproc_i2c->slave, I2C_SLAVE_STOP,
> +					&rx_data);
> +		} else if (rx_status == I2C_SLAVE_RX_FIFO_EMPTY) {
> +			iproc_i2c->rx_start_rcvd = false;
> +			iproc_i2c->slave_read_complete = true;
> +			break;
> +		}
>  
> -			val = BIT(S_CMD_START_BUSY_SHIFT);
> -			iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val);
> +		rx_bytes++;

rx_bytes should be incremented only along with I2C_SLAVE_WRITE_RECEIVED event?

> 
> +static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
> +				    u32 status)
> +{
> +	u32 val;
> +	u8 value;
> +
> +	/*
> +	 * Slave events in case of master-write, master-write-read and,
> +	 * master-read
> +	 *
> +	 * Master-write     : only IS_S_RX_EVENT_SHIFT event
> +	 * Master-write-read: both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
> +	 *                    events
> +	 * Master-read      : both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
> +	 *                    events or only IS_S_RD_EVENT_SHIFT
> +	 */
> +	if (status & BIT(IS_S_RX_EVENT_SHIFT) ||
> +	    status & BIT(IS_S_RD_EVENT_SHIFT)) {
> +		/* disable slave interrupts */
> +		val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
> +		val &= ~iproc_i2c->slave_int_mask;
> +		iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
> +
> +		if (status & BIT(IS_S_RD_EVENT_SHIFT))
> +			/* Master-write-read request */
> +			iproc_i2c->slave_rx_only = false;
> +		else
> +			/* Master-write request only */
> +			iproc_i2c->slave_rx_only = true;
> +
> +		/* schedule tasklet to read data later */
> +		tasklet_schedule(&iproc_i2c->slave_rx_tasklet);
> +
> +		/* clear only IS_S_RX_EVENT_SHIFT interrupt */
> +		iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET,
> +				 BIT(IS_S_RX_EVENT_SHIFT));
> 

Both tasklet and isr are writing to status (IS_OFFSET) reg.

The tasklet seems to be batching up rx fifo reads because of time-sensitive
Master-write-read transaction? Linux I2C framework is byte interface anyway.
Can the need to batch reads be avoided by setting slave rx threshold for
interrupt (S_FIFO_RX_THLD) to 1-byte? 

Also, wouldn't tasklets be susceptible to other interrupts? If fifo reads
have to be batched up, can it be changed to threaded irq?
Rayagonda Kokatanur Oct. 14, 2020, 9:12 a.m. UTC | #2
On Wed, Oct 14, 2020 at 8:50 AM Dhananjay Phadke
<dphadke@linux.microsoft.com> wrote:
>
> On Sun, 11 Oct 2020 23:52:53 +0530, Rayagonda Kokatanur wrote:
> > --- a/drivers/i2c/busses/i2c-bcm-iproc.c
> > +++ b/drivers/i2c/busses/i2c-bcm-iproc.c
> >
> > -             } else if (status & BIT(IS_S_RD_EVENT_SHIFT)) {
> > -                     /* Start of SMBUS for Master Read */
> > +                                     I2C_SLAVE_WRITE_REQUESTED, &rx_data);
> > +                     iproc_i2c->rx_start_rcvd = true;
> > +                     iproc_i2c->slave_read_complete = false;
> > +             } else if (rx_status == I2C_SLAVE_RX_DATA &&
> > +                        iproc_i2c->rx_start_rcvd) {
> > +                     /* Middle of SMBUS Master write */
> >                       i2c_slave_event(iproc_i2c->slave,
> > -                                     I2C_SLAVE_READ_REQUESTED, &value);
> > -                     iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, value);
> > +                                     I2C_SLAVE_WRITE_RECEIVED, &rx_data);
> > +             } else if (rx_status == I2C_SLAVE_RX_END &&
> > +                        iproc_i2c->rx_start_rcvd) {
> > +                     /* End of SMBUS Master write */
> > +                     if (iproc_i2c->slave_rx_only)
> > +                             i2c_slave_event(iproc_i2c->slave,
> > +                                             I2C_SLAVE_WRITE_RECEIVED,
> > +                                             &rx_data);
> > +
> > +                     i2c_slave_event(iproc_i2c->slave, I2C_SLAVE_STOP,
> > +                                     &rx_data);
> > +             } else if (rx_status == I2C_SLAVE_RX_FIFO_EMPTY) {
> > +                     iproc_i2c->rx_start_rcvd = false;
> > +                     iproc_i2c->slave_read_complete = true;
> > +                     break;
> > +             }
> >
> > -                     val = BIT(S_CMD_START_BUSY_SHIFT);
> > -                     iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val);
> > +             rx_bytes++;
>
> rx_bytes should be incremented only along with I2C_SLAVE_WRITE_RECEIVED event?

It should be incremented in both I2C_SLAVE_WRITE_REQUESTED and
I2C_SLAVE_WRITE_RECEIVED cases because in both cases it is reading
valid bytes from rx fifo.

>
> >
> > +static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
> > +                                 u32 status)
> > +{
> > +     u32 val;
> > +     u8 value;
> > +
> > +     /*
> > +      * Slave events in case of master-write, master-write-read and,
> > +      * master-read
> > +      *
> > +      * Master-write     : only IS_S_RX_EVENT_SHIFT event
> > +      * Master-write-read: both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
> > +      *                    events
> > +      * Master-read      : both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
> > +      *                    events or only IS_S_RD_EVENT_SHIFT
> > +      */
> > +     if (status & BIT(IS_S_RX_EVENT_SHIFT) ||
> > +         status & BIT(IS_S_RD_EVENT_SHIFT)) {
> > +             /* disable slave interrupts */
> > +             val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
> > +             val &= ~iproc_i2c->slave_int_mask;
> > +             iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
> > +
> > +             if (status & BIT(IS_S_RD_EVENT_SHIFT))
> > +                     /* Master-write-read request */
> > +                     iproc_i2c->slave_rx_only = false;
> > +             else
> > +                     /* Master-write request only */
> > +                     iproc_i2c->slave_rx_only = true;
> > +
> > +             /* schedule tasklet to read data later */
> > +             tasklet_schedule(&iproc_i2c->slave_rx_tasklet);
> > +
> > +             /* clear only IS_S_RX_EVENT_SHIFT interrupt */
> > +             iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET,
> > +                              BIT(IS_S_RX_EVENT_SHIFT));
> >
>
> Both tasklet and isr are writing to status (IS_OFFSET) reg.
>
> The tasklet seems to be batching up rx fifo reads because of time-sensitive
> Master-write-read transaction? Linux I2C framework is byte interface anyway.
> Can the need to batch reads be avoided by setting slave rx threshold for
> interrupt (S_FIFO_RX_THLD) to 1-byte?

To process more data with a single interrupt we are batching up rx fifo reads.
This will reduce the number of interrupts.

Also to avoid tasklet running more time (20us) we have a threshold of
10 bytes for batching read.
This is a better/optimised approach than reading single byte data per interrupt.

>
> Also, wouldn't tasklets be susceptible to other interrupts? If fifo reads
> have to be batched up, can it be changed to threaded irq?

tasklets have higher priority than threaded irq, since i2c is time
sensitive so using a tasklet is preferred over threaded irq.

Best regards,
Rayagonda

>
>
Ray Jui Oct. 23, 2020, 5:26 p.m. UTC | #3
On 10/13/2020 10:12 PM, Rayagonda Kokatanur wrote:
> 
> 
> On Wed, Oct 14, 2020 at 8:50 AM Dhananjay Phadke
> <dphadke@linux.microsoft.com <mailto:dphadke@linux.microsoft.com>> wrote:
> 
>     On Sun, 11 Oct 2020 23:52:53 +0530, Rayagonda Kokatanur wrote:
>     > --- a/drivers/i2c/busses/i2c-bcm-iproc.c
>     > +++ b/drivers/i2c/busses/i2c-bcm-iproc.c
>     >
>     > -             } else if (status & BIT(IS_S_RD_EVENT_SHIFT)) {
>     > -                     /* Start of SMBUS for Master Read */
>     > +                                     I2C_SLAVE_WRITE_REQUESTED,
>     &rx_data);
>     > +                     iproc_i2c->rx_start_rcvd = true;
>     > +                     iproc_i2c->slave_read_complete = false;
>     > +             } else if (rx_status == I2C_SLAVE_RX_DATA &&
>     > +                        iproc_i2c->rx_start_rcvd) {
>     > +                     /* Middle of SMBUS Master write */
>     >                       i2c_slave_event(iproc_i2c->slave,
>     > -                                     I2C_SLAVE_READ_REQUESTED,
>     &value);
>     > -                     iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, value);
>     > +                                     I2C_SLAVE_WRITE_RECEIVED,
>     &rx_data);
>     > +             } else if (rx_status == I2C_SLAVE_RX_END &&
>     > +                        iproc_i2c->rx_start_rcvd) {
>     > +                     /* End of SMBUS Master write */
>     > +                     if (iproc_i2c->slave_rx_only)
>     > +                             i2c_slave_event(iproc_i2c->slave,
>     > +                                           
>      I2C_SLAVE_WRITE_RECEIVED,
>     > +                                             &rx_data);
>     > +
>     > +                     i2c_slave_event(iproc_i2c->slave,
>     I2C_SLAVE_STOP,
>     > +                                     &rx_data);
>     > +             } else if (rx_status == I2C_SLAVE_RX_FIFO_EMPTY) {
>     > +                     iproc_i2c->rx_start_rcvd = false;
>     > +                     iproc_i2c->slave_read_complete = true;
>     > +                     break;
>     > +             }
>     > 
>     > -                     val = BIT(S_CMD_START_BUSY_SHIFT);
>     > -                     iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val);
>     > +             rx_bytes++;
> 
>     rx_bytes should be incremented only along with
>     I2C_SLAVE_WRITE_RECEIVED event?
> 
> 
> It should be incremented in both I2C_SLAVE_WRITE_REQUESTED and  
> I2C_SLAVE_WRITE_RECEIVED cases because in both case it is reading valid
> bytes from rx fifo.
> 
> 
>     >
>     > +static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev
>     *iproc_i2c,
>     > +                                 u32 status)
>     > +{
>     > +     u32 val;
>     > +     u8 value;
>     > +
>     > +     /*
>     > +      * Slave events in case of master-write, master-write-read and,
>     > +      * master-read
>     > +      *
>     > +      * Master-write     : only IS_S_RX_EVENT_SHIFT event
>     > +      * Master-write-read: both IS_S_RX_EVENT_SHIFT and
>     IS_S_RD_EVENT_SHIFT
>     > +      *                    events
>     > +      * Master-read      : both IS_S_RX_EVENT_SHIFT and
>     IS_S_RD_EVENT_SHIFT
>     > +      *                    events or only IS_S_RD_EVENT_SHIFT
>     > +      */
>     > +     if (status & BIT(IS_S_RX_EVENT_SHIFT) ||
>     > +         status & BIT(IS_S_RD_EVENT_SHIFT)) {
>     > +             /* disable slave interrupts */
>     > +             val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
>     > +             val &= ~iproc_i2c->slave_int_mask;
>     > +             iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
>     > +
>     > +             if (status & BIT(IS_S_RD_EVENT_SHIFT))
>     > +                     /* Master-write-read request */
>     > +                     iproc_i2c->slave_rx_only = false;
>     > +             else
>     > +                     /* Master-write request only */
>     > +                     iproc_i2c->slave_rx_only = true;
>     > +
>     > +             /* schedule tasklet to read data later */
>     > +             tasklet_schedule(&iproc_i2c->slave_rx_tasklet);
>     > +
>     > +             /* clear only IS_S_RX_EVENT_SHIFT interrupt */
>     > +             iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET,
>     > +                              BIT(IS_S_RX_EVENT_SHIFT));
>     >
> 
>     Both tasklet and isr are writing to status (IS_OFFSET) reg.
> 
> 
> Yes this is required.
> 
> For ex, If IS_S_RD_EVENT_SHIFT interrupt, this should be cleared once
> the driver completes reading all data from rx fifo.
> After this the driver can start sending data to master.
>  

If both tasklet and isr are accessing the IS_OFFSET register, don't you
need lock protection against race condition? That is, ISR can interrupt
tasklet.

> 
> 
>     The tasklet seems to be batching up rx fifo reads because of
>     time-sensitive
>     Master-write-read transaction? Linux I2C framework is byte interface
>     anyway.
>     Can the need to batch reads be avoided by setting slave rx threshold for
>     interrupt (S_FIFO_RX_THLD) to 1-byte?
> 
> 
> To process more data with a single interrupt we are batching up rx fifo
> reads.
> This will reduce the number of interrupts.
> 
> Also to avoid tasklet running more time (20us) we have a threshold of 10
> bytes for batching read.
> This is a better/optimised approach than reading single byte data per
> interrupt.
> 
> 
>     Also, wouldn't tasklets be susceptible to other interrupts? If fifo
>     reads
>     have to be batched up, can it be changed to threaded irq?
> 
> 
> tasklets have higher priority than threaded irq, since i2c is time
> sensitive so using a tasklet is preferred over threaded irq.
>
Rayagonda Kokatanur Oct. 26, 2020, 1:55 p.m. UTC | #4
On Fri, Oct 23, 2020 at 10:56 PM Ray Jui <ray.jui@broadcom.com> wrote:
>
>
>
> On 10/13/2020 10:12 PM, Rayagonda Kokatanur wrote:
> >
> >
> > On Wed, Oct 14, 2020 at 8:50 AM Dhananjay Phadke
> > <dphadke@linux.microsoft.com <mailto:dphadke@linux.microsoft.com>> wrote:
> >
> >     On Sun, 11 Oct 2020 23:52:53 +0530, Rayagonda Kokatanur wrote:
> >     > --- a/drivers/i2c/busses/i2c-bcm-iproc.c
> >     > +++ b/drivers/i2c/busses/i2c-bcm-iproc.c
> >     >
> >     > -             } else if (status & BIT(IS_S_RD_EVENT_SHIFT)) {
> >     > -                     /* Start of SMBUS for Master Read */
> >     > +                                     I2C_SLAVE_WRITE_REQUESTED,
> >     &rx_data);
> >     > +                     iproc_i2c->rx_start_rcvd = true;
> >     > +                     iproc_i2c->slave_read_complete = false;
> >     > +             } else if (rx_status == I2C_SLAVE_RX_DATA &&
> >     > +                        iproc_i2c->rx_start_rcvd) {
> >     > +                     /* Middle of SMBUS Master write */
> >     >                       i2c_slave_event(iproc_i2c->slave,
> >     > -                                     I2C_SLAVE_READ_REQUESTED,
> >     &value);
> >     > -                     iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, value);
> >     > +                                     I2C_SLAVE_WRITE_RECEIVED,
> >     &rx_data);
> >     > +             } else if (rx_status == I2C_SLAVE_RX_END &&
> >     > +                        iproc_i2c->rx_start_rcvd) {
> >     > +                     /* End of SMBUS Master write */
> >     > +                     if (iproc_i2c->slave_rx_only)
> >     > +                             i2c_slave_event(iproc_i2c->slave,
> >     > +
> >      I2C_SLAVE_WRITE_RECEIVED,
> >     > +                                             &rx_data);
> >     > +
> >     > +                     i2c_slave_event(iproc_i2c->slave,
> >     I2C_SLAVE_STOP,
> >     > +                                     &rx_data);
> >     > +             } else if (rx_status == I2C_SLAVE_RX_FIFO_EMPTY) {
> >     > +                     iproc_i2c->rx_start_rcvd = false;
> >     > +                     iproc_i2c->slave_read_complete = true;
> >     > +                     break;
> >     > +             }
> >     >
> >     > -                     val = BIT(S_CMD_START_BUSY_SHIFT);
> >     > -                     iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val);
> >     > +             rx_bytes++;
> >
> >     rx_bytes should be incremented only along with
> >     I2C_SLAVE_WRITE_RECEIVED event?
> >
> >
> > It should be incremented in both I2C_SLAVE_WRITE_REQUESTED and
> > I2C_SLAVE_WRITE_RECEIVED cases because in both case it is reading valid
> > bytes from rx fifo.
> >
> >
> >     >
> >     > +static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev
> >     *iproc_i2c,
> >     > +                                 u32 status)
> >     > +{
> >     > +     u32 val;
> >     > +     u8 value;
> >     > +
> >     > +     /*
> >     > +      * Slave events in case of master-write, master-write-read and,
> >     > +      * master-read
> >     > +      *
> >     > +      * Master-write     : only IS_S_RX_EVENT_SHIFT event
> >     > +      * Master-write-read: both IS_S_RX_EVENT_SHIFT and
> >     IS_S_RD_EVENT_SHIFT
> >     > +      *                    events
> >     > +      * Master-read      : both IS_S_RX_EVENT_SHIFT and
> >     IS_S_RD_EVENT_SHIFT
> >     > +      *                    events or only IS_S_RD_EVENT_SHIFT
> >     > +      */
> >     > +     if (status & BIT(IS_S_RX_EVENT_SHIFT) ||
> >     > +         status & BIT(IS_S_RD_EVENT_SHIFT)) {
> >     > +             /* disable slave interrupts */
> >     > +             val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
> >     > +             val &= ~iproc_i2c->slave_int_mask;
> >     > +             iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
> >     > +
> >     > +             if (status & BIT(IS_S_RD_EVENT_SHIFT))
> >     > +                     /* Master-write-read request */
> >     > +                     iproc_i2c->slave_rx_only = false;
> >     > +             else
> >     > +                     /* Master-write request only */
> >     > +                     iproc_i2c->slave_rx_only = true;
> >     > +
> >     > +             /* schedule tasklet to read data later */
> >     > +             tasklet_schedule(&iproc_i2c->slave_rx_tasklet);
> >     > +
> >     > +             /* clear only IS_S_RX_EVENT_SHIFT interrupt */
> >     > +             iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET,
> >     > +                              BIT(IS_S_RX_EVENT_SHIFT));
> >     >
> >
> >     Both tasklet and isr are writing to status (IS_OFFSET) reg.
> >
> >
> > Yes this is required.
> >
> > For ex, If IS_S_RD_EVENT_SHIFT interrupt, this should be cleared once
> > the driver completes reading all data from rx fifo.
> > After this the driver can start sending data to master.
> >
>
> If both tasklet and isr are accessing the IS_OFFSET register, don't you
> need lock protection against race condition? That is, ISR can interrupt
> tasklet.

All interrupts are disbaled when the tasklet is running.
Interrupts are re-enabled at the end of the tasklet.
So no race condition between tasklet and isr.

Best regards,
Rayagonda

>
> >
> >
> >     The tasklet seems to be batching up rx fifo reads because of
> >     time-sensitive
> >     Master-write-read transaction? Linux I2C framework is byte interface
> >     anyway.
> >     Can the need to batch reads be avoided by setting slave rx threshold for
> >     interrupt (S_FIFO_RX_THLD) to 1-byte?
> >
> >
> > To process more data with a single interrupt we are batching up rx fifo
> > reads.
> > This will reduce the number of interrupts.
> >
> > Also to avoid tasklet running more time (20us) we have a threshold of 10
> > bytes for batching read.
> > This is a better/optimised approach than reading single byte data per
> > interrupt.
> >
> >
> >     Also, wouldn't tasklets be susceptible to other interrupts? If fifo
> >     reads
> >     have to be batched up, can it be changed to threaded irq?
> >
> >
> > tasklets have higher priority than threaded irq, since i2c is time
> > sensitive so using a tasklet is preferred over threaded irq.
> >
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-bcm-iproc.c b/drivers/i2c/busses/i2c-bcm-iproc.c
index 7a235f9f5884..22e04055b447 100644
--- a/drivers/i2c/busses/i2c-bcm-iproc.c
+++ b/drivers/i2c/busses/i2c-bcm-iproc.c
@@ -160,6 +160,11 @@ 
 
 #define IE_S_ALL_INTERRUPT_SHIFT     21
 #define IE_S_ALL_INTERRUPT_MASK      0x3f
+/*
+ * It takes ~18us to reading 10bytes of data, hence to keep tasklet
+ * running for less time, max slave read per tasklet is set to 10 bytes.
+ */
+#define MAX_SLAVE_RX_PER_INT         10
 
 enum i2c_slave_read_status {
 	I2C_SLAVE_RX_FIFO_EMPTY = 0,
@@ -206,8 +211,18 @@  struct bcm_iproc_i2c_dev {
 	/* bytes that have been read */
 	unsigned int rx_bytes;
 	unsigned int thld_bytes;
+
+	bool slave_rx_only;
+	bool rx_start_rcvd;
+	bool slave_read_complete;
+	u32 tx_underrun;
+	u32 slave_int_mask;
+	struct tasklet_struct slave_rx_tasklet;
 };
 
+/* tasklet to process slave rx data */
+static void slave_rx_tasklet_fn(unsigned long);
+
 /*
  * Can be expanded in the future if more interrupt status bits are utilized
  */
@@ -261,6 +276,7 @@  static void bcm_iproc_i2c_slave_init(
 {
 	u32 val;
 
+	iproc_i2c->tx_underrun = 0;
 	if (need_reset) {
 		/* put controller in reset */
 		val = iproc_i2c_rd_reg(iproc_i2c, CFG_OFFSET);
@@ -297,8 +313,11 @@  static void bcm_iproc_i2c_slave_init(
 
 	/* Enable interrupt register to indicate a valid byte in receive fifo */
 	val = BIT(IE_S_RX_EVENT_SHIFT);
+	/* Enable interrupt register to indicate a Master read transaction */
+	val |= BIT(IE_S_RD_EVENT_SHIFT);
 	/* Enable interrupt register for the Slave BUSY command */
 	val |= BIT(IE_S_START_BUSY_SHIFT);
+	iproc_i2c->slave_int_mask = val;
 	iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
 }
 
@@ -324,76 +343,176 @@  static void bcm_iproc_i2c_check_slave_status(
 	}
 }
 
-static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
-				    u32 status)
+static void bcm_iproc_i2c_slave_read(struct bcm_iproc_i2c_dev *iproc_i2c)
 {
+	u8 rx_data, rx_status;
+	u32 rx_bytes = 0;
 	u32 val;
-	u8 value, rx_status;
 
-	/* Slave RX byte receive */
-	if (status & BIT(IS_S_RX_EVENT_SHIFT)) {
+	while (rx_bytes < MAX_SLAVE_RX_PER_INT) {
 		val = iproc_i2c_rd_reg(iproc_i2c, S_RX_OFFSET);
 		rx_status = (val >> S_RX_STATUS_SHIFT) & S_RX_STATUS_MASK;
-		if (rx_status == I2C_SLAVE_RX_START) {
-			/* Start of SMBUS for Master write */
-			i2c_slave_event(iproc_i2c->slave,
-					I2C_SLAVE_WRITE_REQUESTED, &value);
+		rx_data = ((val >> S_RX_DATA_SHIFT) & S_RX_DATA_MASK);
 
-			val = iproc_i2c_rd_reg(iproc_i2c, S_RX_OFFSET);
-			value = (u8)((val >> S_RX_DATA_SHIFT) & S_RX_DATA_MASK);
+		if (rx_status == I2C_SLAVE_RX_START) {
+			/* Start of SMBUS Master write */
 			i2c_slave_event(iproc_i2c->slave,
-					I2C_SLAVE_WRITE_RECEIVED, &value);
-		} else if (status & BIT(IS_S_RD_EVENT_SHIFT)) {
-			/* Start of SMBUS for Master Read */
+					I2C_SLAVE_WRITE_REQUESTED, &rx_data);
+			iproc_i2c->rx_start_rcvd = true;
+			iproc_i2c->slave_read_complete = false;
+		} else if (rx_status == I2C_SLAVE_RX_DATA &&
+			   iproc_i2c->rx_start_rcvd) {
+			/* Middle of SMBUS Master write */
 			i2c_slave_event(iproc_i2c->slave,
-					I2C_SLAVE_READ_REQUESTED, &value);
-			iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, value);
+					I2C_SLAVE_WRITE_RECEIVED, &rx_data);
+		} else if (rx_status == I2C_SLAVE_RX_END &&
+			   iproc_i2c->rx_start_rcvd) {
+			/* End of SMBUS Master write */
+			if (iproc_i2c->slave_rx_only)
+				i2c_slave_event(iproc_i2c->slave,
+						I2C_SLAVE_WRITE_RECEIVED,
+						&rx_data);
+
+			i2c_slave_event(iproc_i2c->slave, I2C_SLAVE_STOP,
+					&rx_data);
+		} else if (rx_status == I2C_SLAVE_RX_FIFO_EMPTY) {
+			iproc_i2c->rx_start_rcvd = false;
+			iproc_i2c->slave_read_complete = true;
+			break;
+		}
 
-			val = BIT(S_CMD_START_BUSY_SHIFT);
-			iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val);
+		rx_bytes++;
+	}
+}
 
-			/*
-			 * Enable interrupt for TX FIFO becomes empty and
-			 * less than PKT_LENGTH bytes were output on the SMBUS
-			 */
-			val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
-			val |= BIT(IE_S_TX_UNDERRUN_SHIFT);
-			iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
-		} else {
-			/* Master write other than start */
-			value = (u8)((val >> S_RX_DATA_SHIFT) & S_RX_DATA_MASK);
+static void slave_rx_tasklet_fn(unsigned long data)
+{
+	struct bcm_iproc_i2c_dev *iproc_i2c = (struct bcm_iproc_i2c_dev *)data;
+	u32 int_clr;
+
+	bcm_iproc_i2c_slave_read(iproc_i2c);
+
+	/* clear pending IS_S_RX_EVENT_SHIFT interrupt */
+	int_clr = BIT(IS_S_RX_EVENT_SHIFT);
+
+	if (!iproc_i2c->slave_rx_only && iproc_i2c->slave_read_complete) {
+		/*
+		 * In case of single byte master-read request,
+		 * IS_S_TX_UNDERRUN_SHIFT event is generated before
+		 * IS_S_START_BUSY_SHIFT event. Hence start slave data send
+		 * from first IS_S_TX_UNDERRUN_SHIFT event.
+		 *
+		 * This means don't send any data from slave when
+		 * IS_S_RD_EVENT_SHIFT event is generated else it will increment
+		 * eeprom or other backend slave driver read pointer twice.
+		 */
+		iproc_i2c->tx_underrun = 0;
+		iproc_i2c->slave_int_mask |= BIT(IE_S_TX_UNDERRUN_SHIFT);
+
+		/* clear IS_S_RD_EVENT_SHIFT interrupt */
+		int_clr |= BIT(IS_S_RD_EVENT_SHIFT);
+	}
+
+	/* clear slave interrupt */
+	iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, int_clr);
+	/* enable slave interrupts */
+	iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, iproc_i2c->slave_int_mask);
+}
+
+static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
+				    u32 status)
+{
+	u32 val;
+	u8 value;
+
+	/*
+	 * Slave events in case of master-write, master-write-read and,
+	 * master-read
+	 *
+	 * Master-write     : only IS_S_RX_EVENT_SHIFT event
+	 * Master-write-read: both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
+	 *                    events
+	 * Master-read      : both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
+	 *                    events or only IS_S_RD_EVENT_SHIFT
+	 */
+	if (status & BIT(IS_S_RX_EVENT_SHIFT) ||
+	    status & BIT(IS_S_RD_EVENT_SHIFT)) {
+		/* disable slave interrupts */
+		val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
+		val &= ~iproc_i2c->slave_int_mask;
+		iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
+
+		if (status & BIT(IS_S_RD_EVENT_SHIFT))
+			/* Master-write-read request */
+			iproc_i2c->slave_rx_only = false;
+		else
+			/* Master-write request only */
+			iproc_i2c->slave_rx_only = true;
+
+		/* schedule tasklet to read data later */
+		tasklet_schedule(&iproc_i2c->slave_rx_tasklet);
+
+		/* clear only IS_S_RX_EVENT_SHIFT interrupt */
+		iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET,
+				 BIT(IS_S_RX_EVENT_SHIFT));
+	}
+
+	if (status & BIT(IS_S_TX_UNDERRUN_SHIFT)) {
+		iproc_i2c->tx_underrun++;
+		if (iproc_i2c->tx_underrun == 1)
+			/* Start of SMBUS for Master Read */
 			i2c_slave_event(iproc_i2c->slave,
-					I2C_SLAVE_WRITE_RECEIVED, &value);
-			if (rx_status == I2C_SLAVE_RX_END)
-				i2c_slave_event(iproc_i2c->slave,
-						I2C_SLAVE_STOP, &value);
-		}
-	} else if (status & BIT(IS_S_TX_UNDERRUN_SHIFT)) {
-		/* Master read other than start */
-		i2c_slave_event(iproc_i2c->slave,
-				I2C_SLAVE_READ_PROCESSED, &value);
+					I2C_SLAVE_READ_REQUESTED,
+					&value);
+		else
+			/* Master read other than start */
+			i2c_slave_event(iproc_i2c->slave,
+					I2C_SLAVE_READ_PROCESSED,
+					&value);
 
 		iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, value);
+		/* start transfer */
 		val = BIT(S_CMD_START_BUSY_SHIFT);
 		iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val);
+
+		/* clear interrupt */
+		iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET,
+				 BIT(IS_S_TX_UNDERRUN_SHIFT));
 	}
 
-	/* Stop */
+	/* Stop received from master in case of master read transaction */
 	if (status & BIT(IS_S_START_BUSY_SHIFT)) {
-		i2c_slave_event(iproc_i2c->slave, I2C_SLAVE_STOP, &value);
 		/*
 		 * Disable interrupt for TX FIFO becomes empty and
 		 * less than PKT_LENGTH bytes were output on the SMBUS
 		 */
-		val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
-		val &= ~BIT(IE_S_TX_UNDERRUN_SHIFT);
-		iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
+		iproc_i2c->slave_int_mask &= ~BIT(IE_S_TX_UNDERRUN_SHIFT);
+		iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET,
+				 iproc_i2c->slave_int_mask);
+
+		/* End of SMBUS for Master Read */
+		val = BIT(S_TX_WR_STATUS_SHIFT);
+		iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, val);
+
+		val = BIT(S_CMD_START_BUSY_SHIFT);
+		iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val);
+
+		/* flush TX FIFOs */
+		val = iproc_i2c_rd_reg(iproc_i2c, S_FIFO_CTRL_OFFSET);
+		val |= (BIT(S_FIFO_TX_FLUSH_SHIFT));
+		iproc_i2c_wr_reg(iproc_i2c, S_FIFO_CTRL_OFFSET, val);
+
+		i2c_slave_event(iproc_i2c->slave, I2C_SLAVE_STOP, &value);
+
+		/* clear interrupt */
+		iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET,
+				 BIT(IS_S_START_BUSY_SHIFT));
 	}
 
-	/* clear interrupt status */
-	iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, status);
+	/* check slave transmit status only if slave is transmitting */
+	if (!iproc_i2c->slave_rx_only)
+		bcm_iproc_i2c_check_slave_status(iproc_i2c);
 
-	bcm_iproc_i2c_check_slave_status(iproc_i2c);
 	return true;
 }
 
@@ -1074,6 +1193,10 @@  static int bcm_iproc_i2c_reg_slave(struct i2c_client *slave)
 		return -EAFNOSUPPORT;
 
 	iproc_i2c->slave = slave;
+
+	tasklet_init(&iproc_i2c->slave_rx_tasklet, slave_rx_tasklet_fn,
+		     (unsigned long)iproc_i2c);
+
 	bcm_iproc_i2c_slave_init(iproc_i2c, false);
 	return 0;
 }
@@ -1094,6 +1217,8 @@  static int bcm_iproc_i2c_unreg_slave(struct i2c_client *slave)
 			IE_S_ALL_INTERRUPT_SHIFT);
 	iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, tmp);
 
+	tasklet_kill(&iproc_i2c->slave_rx_tasklet);
+
 	/* Erase the slave address programmed */
 	tmp = iproc_i2c_rd_reg(iproc_i2c, S_CFG_SMBUS_ADDR_OFFSET);
 	tmp &= ~BIT(S_CFG_EN_NIC_SMB_ADDR3_SHIFT);