Skip to content

Commit

Permalink
i2c: rcar: implement STOP and REP_START according to docs
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Wolfram Sang <[email protected]>
Reviewed-by: Ulrich Hecht <[email protected]>
Signed-off-by: Wolfram Sang <[email protected]>
  • Loading branch information
Wolfram Sang authored and Wolfram Sang committed Aug 20, 2018
1 parent b07531a commit 19358d4
Showing 1 changed file with 20 additions and 14 deletions.
34 changes: 20 additions & 14 deletions drivers/i2c/busses/i2c-rcar.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit 19358d4

Please sign in to comment.