diff mbox series

[v3,1/3] i2c: imx: Fix reset of I2SR_IAL flag

Message ID 20201006105135.28985-2-ceggers@arri.de
State Superseded
Headers show
Series i2c: imx: Fix handling of arbitration loss | expand

Commit Message

Christian Eggers Oct. 6, 2020, 10:51 a.m. UTC
According to the "VFxxx Controller Reference Manual" (and the comment
block starting at line 97), Vybrid requires writing a one for clearing
an interrupt flag. Syncing the method for clearing I2SR_IIF in
i2c_imx_isr().

Signed-off-by: Christian Eggers <ceggers@arri.de>
Cc: stable@vger.kernel.org
---
 drivers/i2c/busses/i2c-imx.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

Comments

David Laight Oct. 6, 2020, 12:06 p.m. UTC | #1
From: Christian Eggers
> Sent: 06 October 2020 11:52
> 
> According to the "VFxxx Controller Reference Manual" (and the comment
> block starting at line 97), Vybrid requires writing a one for clearing
> an interrupt flag. Syncing the method for clearing I2SR_IIF in
> i2c_imx_isr().
> 
> Signed-off-by: Christian Eggers <ceggers@arri.de>
> Cc: stable@vger.kernel.org
> ---
>  drivers/i2c/busses/i2c-imx.c | 20 +++++++++++++++-----
>  1 file changed, 15 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
> index 0ab5381aa012..745f4071155a 100644
> --- a/drivers/i2c/busses/i2c-imx.c
> +++ b/drivers/i2c/busses/i2c-imx.c
> @@ -412,6 +412,19 @@ static void i2c_imx_dma_free(struct imx_i2c_struct *i2c_imx)
>  	dma->chan_using = NULL;
>  }
> 
> +static void i2c_imx_clear_irq(struct imx_i2c_struct *i2c_imx, unsigned int bits)
> +{
> +	unsigned int temp;
> +
> +	/*
> +	 * i2sr_clr_opcode is the value to clear all interrupts.
> +	 * Here we want to clear only <bits>, so we write
> +	 * ~i2sr_clr_opcode with just <bits> toggled.
> +	 */
> +	temp = ~i2c_imx->hwdata->i2sr_clr_opcode ^ bits;
> +	imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
> +}

That looks either wrong or maybe just overcomplicated.
Why isn't:
	imx_i2c_write_reg(bits, i2c_imx, IMX_I2C_I2SR);
enough?

More usually you just write back the read value of such
'write 1 to clear' status registers and then act on all
the set bits.
That ensures you clear all interrupts that were pending.

If you need to avoid writes of bits that aren't in the
'clear all interrupts' value then you just need:
	bits &= i2c_imx->hwdata->i2sr_clr_opcode;
prior to the write.

	David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
Christian Eggers Oct. 6, 2020, 12:30 p.m. UTC | #2
Hi David,

On Tuesday, 6 October 2020, 14:06:36 CEST, David Laight wrote:
> From: Christian Eggers
> > +static void i2c_imx_clear_irq(struct imx_i2c_struct *i2c_imx, unsigned
> > int bits) +{
> > +	unsigned int temp;
> > +
> > +	/*
> > +	 * i2sr_clr_opcode is the value to clear all interrupts.
> > +	 * Here we want to clear only <bits>, so we write
> > +	 * ~i2sr_clr_opcode with just <bits> toggled.
> > +	 */
> > +	temp = ~i2c_imx->hwdata->i2sr_clr_opcode ^ bits;
> > +	imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
> > +}
> 
> That looks either wrong or maybe just overcomplicated.
Yes, it looks so.

> Why isn't:
> 	imx_i2c_write_reg(bits, i2c_imx, IMX_I2C_I2SR);
> enough?
i.MX requires W1C and Vybrid requires W0C in order to clear status bits.

> More usually you just write back the read value of such
> 'write 1 to clear' status registers and then act on all
> the set bits.
This pattern has been suggested by Uwe Klein-Koenig. It works because write 
access to read-only register bits is ignored, independent whether 0 or 1 is 
written. W0C is quite unusual, but I didn't design the hardware... The pattern 
ensures that not accidentally more status bits are cleared than desired.

> That ensures you clear all interrupts that were pending.
I think that Uwe's intention was not clearing bits which are not handled at 
this place. Otherwise events may get lost.

> If you need to avoid writes of bits that aren't in the
> 'clear all interrupts' value then you just need:
> 	bits &= i2c_imx->hwdata->i2sr_clr_opcode;
> prior to the write.
I think this wouldn't fit the W0C case for Vybrid.

Best regards
Christian
Uwe Kleine-König Oct. 6, 2020, 12:46 p.m. UTC | #3
On Tue, Oct 06, 2020 at 12:06:36PM +0000, David Laight wrote:
> From: Christian Eggers
> > Sent: 06 October 2020 11:52
> > 
> > According to the "VFxxx Controller Reference Manual" (and the comment
> > block starting at line 97), Vybrid requires writing a one for clearing
> > an interrupt flag. Syncing the method for clearing I2SR_IIF in
> > i2c_imx_isr().
> > 
> > Signed-off-by: Christian Eggers <ceggers@arri.de>
> > Cc: stable@vger.kernel.org
> > ---
> >  drivers/i2c/busses/i2c-imx.c | 20 +++++++++++++++-----
> >  1 file changed, 15 insertions(+), 5 deletions(-)
> > 
> > diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
> > index 0ab5381aa012..745f4071155a 100644
> > --- a/drivers/i2c/busses/i2c-imx.c
> > +++ b/drivers/i2c/busses/i2c-imx.c
> > @@ -412,6 +412,19 @@ static void i2c_imx_dma_free(struct imx_i2c_struct *i2c_imx)
> >  	dma->chan_using = NULL;
> >  }
> > 
> > +static void i2c_imx_clear_irq(struct imx_i2c_struct *i2c_imx, unsigned int bits)
> > +{
> > +	unsigned int temp;
> > +
> > +	/*
> > +	 * i2sr_clr_opcode is the value to clear all interrupts.
> > +	 * Here we want to clear only <bits>, so we write
> > +	 * ~i2sr_clr_opcode with just <bits> toggled.
> > +	 */
> > +	temp = ~i2c_imx->hwdata->i2sr_clr_opcode ^ bits;
> > +	imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
> > +}
> 
> That looks either wrong or maybe just overcomplicated.
> Why isn't:
> 	imx_i2c_write_reg(bits, i2c_imx, IMX_I2C_I2SR);
> enough?

Your question suggests you either didn't read the comment or the comment
is not good enough. Maybe once you understood the complication (see
Christian's mail) you could suggest a better wording? Maybe we have to
mention that this handles both W1C and W0C.

Best regards
Uwe
David Laight Oct. 6, 2020, 12:52 p.m. UTC | #4
From: Uwe Kleine-König
> Sent: 06 October 2020 13:46
...
> > > +static void i2c_imx_clear_irq(struct imx_i2c_struct *i2c_imx, unsigned int bits)
> > > +{
> > > +	unsigned int temp;
> > > +
> > > +	/*
> > > +	 * i2sr_clr_opcode is the value to clear all interrupts.
> > > +	 * Here we want to clear only <bits>, so we write
> > > +	 * ~i2sr_clr_opcode with just <bits> toggled.
> > > +	 */
> > > +	temp = ~i2c_imx->hwdata->i2sr_clr_opcode ^ bits;
> > > +	imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
> > > +}
> >
> > That looks either wrong or maybe just overcomplicated.
> > Why isn't:
> > 	imx_i2c_write_reg(bits, i2c_imx, IMX_I2C_I2SR);
> > enough?
> 
> Your question suggests you either didn't read the comment or the comment
> is not good enough. Maybe once you understood the complication (see
> Christian's mail) you could suggest a better wording? Maybe we have to
> mention that this handles both W1C and W0C.

Yes, the comment should just say that some devices are W1C and
other W0C.
It isn't obvious from that code fragment at all.

Now for the 3rd variant which zeros the bits when they are read :-)

	David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
index 0ab5381aa012..745f4071155a 100644
--- a/drivers/i2c/busses/i2c-imx.c
+++ b/drivers/i2c/busses/i2c-imx.c
@@ -412,6 +412,19 @@  static void i2c_imx_dma_free(struct imx_i2c_struct *i2c_imx)
 	dma->chan_using = NULL;
 }
 
+static void i2c_imx_clear_irq(struct imx_i2c_struct *i2c_imx, unsigned int bits)
+{
+	unsigned int temp;
+
+	/*
+	 * i2sr_clr_opcode is the value to clear all interrupts.
+	 * Here we want to clear only <bits>, so we write
+	 * ~i2sr_clr_opcode with just <bits> toggled.
+	 */
+	temp = ~i2c_imx->hwdata->i2sr_clr_opcode ^ bits;
+	imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
+}
+
 static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy, bool atomic)
 {
 	unsigned long orig_jiffies = jiffies;
@@ -424,8 +437,7 @@  static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy, bool a
 
 		/* check for arbitration lost */
 		if (temp & I2SR_IAL) {
-			temp &= ~I2SR_IAL;
-			imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
+			i2c_imx_clear_irq(i2c_imx, I2SR_IAL);
 			return -EAGAIN;
 		}
 
@@ -623,9 +635,7 @@  static irqreturn_t i2c_imx_isr(int irq, void *dev_id)
 	if (temp & I2SR_IIF) {
 		/* save status register */
 		i2c_imx->i2csr = temp;
-		temp &= ~I2SR_IIF;
-		temp |= (i2c_imx->hwdata->i2sr_clr_opcode & I2SR_IIF);
-		imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
+		i2c_imx_clear_irq(i2c_imx, I2SR_IIF);
 		wake_up(&i2c_imx->queue);
 		return IRQ_HANDLED;
 	}