]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
i2c: rcar: fix NACK handling when being a target
authorWolfram Sang <wsa+renesas@sang-engineering.com>
Wed, 15 Jan 2025 12:36:23 +0000 (13:36 +0100)
committerWolfram Sang <wsa+renesas@sang-engineering.com>
Wed, 15 Jan 2025 18:37:33 +0000 (19:37 +0100)
When this controller is a target, the NACK handling had two issues.
First, the return value from the backend was not checked on the initial
WRITE_REQUESTED. So, the driver missed to send a NACK in this case.
Also, the NACK always arrives one byte late on the bus, even in the
WRITE_RECEIVED case. This seems to be a HW issue. We should then not
rely on the backend to correctly NACK the superfluous byte as well. Fix
both issues by introducing a flag which gets set whenever the backend
requests a NACK and keep sending it until we get a STOP condition.

Fixes: de20d1857dd6 ("i2c: rcar: add slave support")
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
drivers/i2c/busses/i2c-rcar.c

index a7b77d14ee8671eb2b0b2a9caf606fd04d95ba0a..5693a38da7b5af259465396e087a011a1ff4a1f0 100644 (file)
 #define ID_P_PM_BLOCKED                BIT(31)
 #define ID_P_MASK              GENMASK(31, 27)
 
+#define ID_SLAVE_NACK          BIT(0)
+
 enum rcar_i2c_type {
        I2C_RCAR_GEN1,
        I2C_RCAR_GEN2,
@@ -166,6 +168,7 @@ struct rcar_i2c_priv {
        int irq;
 
        struct i2c_client *host_notify_client;
+       u8 slave_flags;
 };
 
 #define rcar_i2c_priv_to_dev(p)                ((p)->adap.dev.parent)
@@ -655,6 +658,7 @@ static bool rcar_i2c_slave_irq(struct rcar_i2c_priv *priv)
 {
        u32 ssr_raw, ssr_filtered;
        u8 value;
+       int ret;
 
        ssr_raw = rcar_i2c_read(priv, ICSSR) & 0xff;
        ssr_filtered = ssr_raw & rcar_i2c_read(priv, ICSIER);
@@ -670,7 +674,10 @@ static bool rcar_i2c_slave_irq(struct rcar_i2c_priv *priv)
                        rcar_i2c_write(priv, ICRXTX, value);
                        rcar_i2c_write(priv, ICSIER, SDE | SSR | SAR);
                } else {
-                       i2c_slave_event(priv->slave, I2C_SLAVE_WRITE_REQUESTED, &value);
+                       ret = i2c_slave_event(priv->slave, I2C_SLAVE_WRITE_REQUESTED, &value);
+                       if (ret)
+                               priv->slave_flags |= ID_SLAVE_NACK;
+
                        rcar_i2c_read(priv, ICRXTX);    /* dummy read */
                        rcar_i2c_write(priv, ICSIER, SDR | SSR | SAR);
                }
@@ -683,18 +690,21 @@ static bool rcar_i2c_slave_irq(struct rcar_i2c_priv *priv)
        if (ssr_filtered & SSR) {
                i2c_slave_event(priv->slave, I2C_SLAVE_STOP, &value);
                rcar_i2c_write(priv, ICSCR, SIE | SDBS); /* clear our NACK */
+               priv->slave_flags &= ~ID_SLAVE_NACK;
                rcar_i2c_write(priv, ICSIER, SAR);
                rcar_i2c_write(priv, ICSSR, ~SSR & 0xff);
        }
 
        /* master wants to write to us */
        if (ssr_filtered & SDR) {
-               int ret;
-
                value = rcar_i2c_read(priv, ICRXTX);
                ret = i2c_slave_event(priv->slave, I2C_SLAVE_WRITE_RECEIVED, &value);
-               /* Send NACK in case of error */
-               rcar_i2c_write(priv, ICSCR, SIE | SDBS | (ret < 0 ? FNA : 0));
+               if (ret)
+                       priv->slave_flags |= ID_SLAVE_NACK;
+
+               /* Send NACK in case of error, but it will come 1 byte late :( */
+               rcar_i2c_write(priv, ICSCR, SIE | SDBS |
+                              (priv->slave_flags & ID_SLAVE_NACK ? FNA : 0));
                rcar_i2c_write(priv, ICSSR, ~SDR & 0xff);
        }