diff mbox series

[2/2] i2c: rcar: implement STOP and REP_START according to docs

Message ID 20180808075928.31107-3-wsa+renesas@sang-engineering.com
State Accepted
Headers show
Series i2c: rcar: implement STOP and REP_START according to docs | expand

Commit Message

Wolfram Sang Aug. 8, 2018, 7:59 a.m. UTC
When doing a REP_START after a read message, the driver used to trigger
a STOP first which would then be overwritten by REP_START. This was the
only stable method found when doing the last refactoring. However, this
was not in accordance with the documentation.

After research from our BSP team and myself, we now can implement a
version which works and is according to the documentation. The new
approach ensures the ICMCR register is only changed when really needed.

Tested on a R-Car Gen2 (H2) and Gen3 with DMA (M3N).

Signed-off-by: Hiromitsu Yamasaki <hiromitsu.yamasaki.ym@renesas.com>
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
---
 drivers/i2c/busses/i2c-rcar.c | 34 ++++++++++++++++++++--------------
 1 file changed, 20 insertions(+), 14 deletions(-)

Comments

Ulrich Hecht Aug. 16, 2018, 5:15 p.m. UTC | #1
Thank you for your patch.

> On August 8, 2018 at 9:59 AM Wolfram Sang <wsa+renesas@sang-engineering.com> wrote:
> 
> 
> When doing a REP_START after a read message, the driver used to trigger
> a STOP first which would then be overwritten by REP_START. This was the
> only stable method found when doing the last refactoring. However, this
> was not in accordance with the documentation.
> 
> After research from our BSP team and myself, we now can implement a
> version which works and is according to the documentation. The new
> approach ensures the ICMCR register is only changed when really needed.
> 
> Tested on a R-Car Gen2 (H2) and Gen3 with DMA (M3N).
> 
> Signed-off-by: Hiromitsu Yamasaki <hiromitsu.yamasaki.ym@renesas.com>
> Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
> ---
>  drivers/i2c/busses/i2c-rcar.c | 34 ++++++++++++++++++++--------------
>  1 file changed, 20 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/i2c/busses/i2c-rcar.c b/drivers/i2c/busses/i2c-rcar.c
> index a9f1880e2eae..43ad933df0f0 100644
> --- a/drivers/i2c/busses/i2c-rcar.c
> +++ b/drivers/i2c/busses/i2c-rcar.c
> @@ -113,9 +113,10 @@
>  #define ID_ARBLOST	(1 << 3)
>  #define ID_NACK		(1 << 4)
>  /* persistent flags */
> +#define ID_P_REP_AFTER_RD	BIT(29)
>  #define ID_P_NO_RXDMA		BIT(30) /* HW forbids RXDMA sometimes */
>  #define ID_P_PM_BLOCKED		BIT(31)
> -#define ID_P_MASK		GENMASK(31, 30)
> +#define ID_P_MASK		GENMASK(31, 29)
>  
>  enum rcar_i2c_type {
>  	I2C_RCAR_GEN1,
> @@ -345,7 +346,10 @@ static void rcar_i2c_prepare_msg(struct rcar_i2c_priv *priv)
>  		rcar_i2c_write(priv, ICMSR, 0);
>  		rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
>  	} else {
> -		rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
> +		if (priv->flags & ID_P_REP_AFTER_RD)
> +			priv->flags &= ~ID_P_REP_AFTER_RD;
> +		else
> +			rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
>  		rcar_i2c_write(priv, ICMSR, 0);
>  	}
>  	rcar_i2c_write(priv, ICMIER, read ? RCAR_IRQ_RECV : RCAR_IRQ_SEND);
> @@ -550,15 +554,15 @@ static void rcar_i2c_irq_recv(struct rcar_i2c_priv *priv, u32 msr)
>  		priv->pos++;
>  	}
>  
> -	/*
> -	 * If next received data is the _LAST_, go to STOP phase. Might be
> -	 * overwritten by REP START when setting up a new msg. Not elegant
> -	 * but the only stable sequence for REP START I have found so far.
> -	 * If you want to change this code, make sure sending one transfer with
> -	 * four messages (WR-RD-WR-RD) works!
> -	 */
> -	if (priv->pos + 1 >= msg->len)
> -		rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP);
> +	/* If next received data is the _LAST_, go to new phase. */
> +	if (priv->pos + 1 == msg->len) {
> +		if (priv->flags & ID_LAST_MSG) {
> +			rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP);
> +		} else {
> +			rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
> +			priv->flags |= ID_P_REP_AFTER_RD;
> +		}
> +	}

So "priv->pos + 1 <= msg->len" is an invariant? (The current code seems to imply that it isn't.)

If it is,
Reviewed-by: Ulrich Hecht <uli+renesas@fpond.eu>

CU
Uli
Wolfram Sang Aug. 20, 2018, 8:58 a.m. UTC | #2
> > -	if (priv->pos + 1 >= msg->len)
> > -		rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP);
> > +	/* If next received data is the _LAST_, go to new phase. */
> > +	if (priv->pos + 1 == msg->len) {
> > +		if (priv->flags & ID_LAST_MSG) {
> > +			rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP);
> > +		} else {
> > +			rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
> > +			priv->flags |= ID_P_REP_AFTER_RD;
> > +		}
> > +	}
> 
> So "priv->pos + 1 <= msg->len" is an invariant? (The current code seems to imply that it isn't.)

I think it is, we increment pos by 1 right before this 'if'. Do you
agree? IIRC the old '>=' came from a 'well, won't hurt' attitude. It was
not precise, sadly.

> If it is,
> Reviewed-by: Ulrich Hecht <uli+renesas@fpond.eu>

Thanks.
Wolfram Sang Aug. 20, 2018, 12:50 p.m. UTC | #3
On Wed, Aug 08, 2018 at 09:59:28AM +0200, Wolfram Sang wrote:
> When doing a REP_START after a read message, the driver used to trigger
> a STOP first which would then be overwritten by REP_START. This was the
> only stable method found when doing the last refactoring. However, this
> was not in accordance with the documentation.
> 
> After research from our BSP team and myself, we now can implement a
> version which works and is according to the documentation. The new
> approach ensures the ICMCR register is only changed when really needed.
> 
> Tested on a R-Car Gen2 (H2) and Gen3 with DMA (M3N).
> 
> Signed-off-by: Hiromitsu Yamasaki <hiromitsu.yamasaki.ym@renesas.com>
> Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>

Applied to for-next, thanks!
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-rcar.c b/drivers/i2c/busses/i2c-rcar.c
index a9f1880e2eae..43ad933df0f0 100644
--- a/drivers/i2c/busses/i2c-rcar.c
+++ b/drivers/i2c/busses/i2c-rcar.c
@@ -113,9 +113,10 @@ 
 #define ID_ARBLOST	(1 << 3)
 #define ID_NACK		(1 << 4)
 /* persistent flags */
+#define ID_P_REP_AFTER_RD	BIT(29)
 #define ID_P_NO_RXDMA		BIT(30) /* HW forbids RXDMA sometimes */
 #define ID_P_PM_BLOCKED		BIT(31)
-#define ID_P_MASK		GENMASK(31, 30)
+#define ID_P_MASK		GENMASK(31, 29)
 
 enum rcar_i2c_type {
 	I2C_RCAR_GEN1,
@@ -345,7 +346,10 @@  static void rcar_i2c_prepare_msg(struct rcar_i2c_priv *priv)
 		rcar_i2c_write(priv, ICMSR, 0);
 		rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
 	} else {
-		rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
+		if (priv->flags & ID_P_REP_AFTER_RD)
+			priv->flags &= ~ID_P_REP_AFTER_RD;
+		else
+			rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
 		rcar_i2c_write(priv, ICMSR, 0);
 	}
 	rcar_i2c_write(priv, ICMIER, read ? RCAR_IRQ_RECV : RCAR_IRQ_SEND);
@@ -550,15 +554,15 @@  static void rcar_i2c_irq_recv(struct rcar_i2c_priv *priv, u32 msr)
 		priv->pos++;
 	}
 
-	/*
-	 * If next received data is the _LAST_, go to STOP phase. Might be
-	 * overwritten by REP START when setting up a new msg. Not elegant
-	 * but the only stable sequence for REP START I have found so far.
-	 * If you want to change this code, make sure sending one transfer with
-	 * four messages (WR-RD-WR-RD) works!
-	 */
-	if (priv->pos + 1 >= msg->len)
-		rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP);
+	/* If next received data is the _LAST_, go to new phase. */
+	if (priv->pos + 1 == msg->len) {
+		if (priv->flags & ID_LAST_MSG) {
+			rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP);
+		} else {
+			rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
+			priv->flags |= ID_P_REP_AFTER_RD;
+		}
+	}
 
 	if (priv->pos == msg->len && !(priv->flags & ID_LAST_MSG))
 		rcar_i2c_next_msg(priv);
@@ -626,9 +630,11 @@  static irqreturn_t rcar_i2c_irq(int irq, void *ptr)
 	struct rcar_i2c_priv *priv = ptr;
 	u32 msr, val;
 
-	/* Clear START or STOP as soon as we can */
-	val = rcar_i2c_read(priv, ICMCR);
-	rcar_i2c_write(priv, ICMCR, val & RCAR_BUS_MASK_DATA);
+	/* Clear START or STOP immediately, except for REPSTART after read */
+	if (likely(!(priv->flags & ID_P_REP_AFTER_RD))) {
+		val = rcar_i2c_read(priv, ICMCR);
+		rcar_i2c_write(priv, ICMCR, val & RCAR_BUS_MASK_DATA);
+	}
 
 	msr = rcar_i2c_read(priv, ICMSR);