Re: [PATCH] i2c: cadence: Clear HOLD bit before xfer_size register rolls over

Michal Simek
2021-06-23T08:49:17Z

út 24. 11. 2020 v 8:48 odesílatel Raviteja Narayanam
<raviteja.narayanam@xilinx.com> napsal:
>
> On Xilinx zynq SOC if the delay between address register write and
> control register write in cdns_mrecv function is more, the xfer size
> register rolls over and controller is stuck. This is an IP bug and

and the controller

> is resolved in later versions of IP.
>
> To avoid this scenario, disable the interrupts on the current processor
> core between the two register writes and enable them later. This can
> help achieve the timing constraint.
>
> Signed-off-by: Raviteja Narayanam <raviteja.narayanam@xilinx.com>
> ---
> drivers/i2c/busses/i2c-cadence.c | 48 ++++++++++++++++++++++++++++++++++------
> 1 file changed, 41 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c
> index e4b7f2a..81b7c45 100644
> --- a/drivers/i2c/busses/i2c-cadence.c
> +++ b/drivers/i2c/busses/i2c-cadence.c
> @@ -578,6 +578,11 @@ static void cdns_i2c_mrecv(struct cdns_i2c *id)
> {
> unsigned int ctrl_reg;
> unsigned int isr_status;
> + unsigned long flags;
> + bool hold_clear = false;
> + bool irq_save = false;
> +
> + u32 addr;
>
> id->p_recv_buf = id->p_msg->buf;
> id->recv_count = id->p_msg->len;
> @@ -618,14 +623,43 @@ static void cdns_i2c_mrecv(struct cdns_i2c *id)
> cdns_i2c_writereg(id->recv_count, CDNS_I2C_XFER_SIZE_OFFSET);
> }
>
> - /* Set the slave address in address register - triggers operation */
> - cdns_i2c_writereg(id->p_msg->addr & CDNS_I2C_ADDR_MASK,
> - CDNS_I2C_ADDR_OFFSET);
> - /* Clear the bus hold flag if bytes to receive is less than FIFO size */
> + /* Determine hold_clear based on number of bytes to receive and hold flag */
> if (!id->bus_hold_flag &&
> - ((id->p_msg->flags & I2C_M_RECV_LEN) != I2C_M_RECV_LEN) &&
> - (id->recv_count <= CDNS_I2C_FIFO_DEPTH))
> - cdns_i2c_clear_bus_hold(id);
> + ((id->p_msg->flags & I2C_M_RECV_LEN) != I2C_M_RECV_LEN) &&
> + (id->recv_count <= CDNS_I2C_FIFO_DEPTH)) {
> + if (cdns_i2c_readreg(CDNS_I2C_CR_OFFSET) & CDNS_I2C_CR_HOLD) {
> + hold_clear = true;
> + if (id->quirks & CDNS_I2C_BROKEN_HOLD_BIT)
> + irq_save = true;
> + }
> + }
> +
> + addr = id->p_msg->addr;
> + addr &= CDNS_I2C_ADDR_MASK;
> +
> + if (hold_clear) {
> + ctrl_reg = cdns_i2c_readreg(CDNS_I2C_CR_OFFSET) & ~CDNS_I2C_CR_HOLD;
> + /*
> + * In case of Xilinx Zynq SOC, clear the HOLD bit before transfer size
> + * register reaches '0'. This is an IP bug which causes transfer size
> + * register overflow to 0xFF. To satisfy this timing requirement,
> + * disable the interrupts on current processor core between register
> + * writes to slave address register and control register.
> + */
> + if (irq_save)
> + local_irq_save(flags);
> +
> + cdns_i2c_writereg(addr, CDNS_I2C_ADDR_OFFSET);
> + cdns_i2c_writereg(ctrl_reg, CDNS_I2C_CR_OFFSET);
> + /* Read it back to avoid bufferring and make sure write happens */
> + cdns_i2c_readreg(CDNS_I2C_CR_OFFSET);
> +
> + if (irq_save)
> + local_irq_restore(flags);
> + } else {
> + cdns_i2c_writereg(addr, CDNS_I2C_ADDR_OFFSET);
> + }
> +
> cdns_i2c_writereg(CDNS_I2C_ENABLED_INTR_MASK, CDNS_I2C_IER_OFFSET);
> }

Unfortunately we can't do anything with this on the Zynq platform. It
is better than nothing.
ZynqMP is not affected.

Acked-by: Michal Simek <michal.simek@xilinx.com>

Thanks,
Michal

--
Michal Simek, Ing. (M.Eng), OpenPGP -> KeyID: FE3D1F91
w: www.monstr.eu p: +42-0-721842854
Maintainer of Linux kernel - Xilinx Microblaze
Maintainer of Linux kernel - Xilinx Zynq ARM and ZynqMP ARM64 SoCs
U-Boot custodian - Xilinx Microblaze/Zynq/ZynqMP/Versal SoCs