OSDN Git Service

i2c: rcar: add support for I2C_M_RECV_LEN
authorWolfram Sang <wsa+renesas@sang-engineering.com>
Tue, 5 Apr 2022 10:07:56 +0000 (12:07 +0200)
committerWolfram Sang <wsa@kernel.org>
Fri, 15 Apr 2022 21:36:18 +0000 (23:36 +0200)
With this feature added, SMBus Block reads and Proc calls are now
supported. This patch is the best of two independent developments by
Wolfram and Bhuvanesh + Andrew, refactored again by Wolfram.

Signed-off-by: Bhuvanesh Surachari <bhuvanesh_surachari@mentor.com>
Signed-off-by: Andrew Gabbasov <andrew_gabbasov@mentor.com>
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Reviewed-by: Eugeniu Rosca <erosca@de.adit-jv.com>
Tested-by: Eugeniu Rosca <erosca@de.adit-jv.com>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
drivers/i2c/busses/i2c-rcar.c

index 0db3d75..ca61dbe 100644 (file)
 #define ID_DONE                (1 << 2)
 #define ID_ARBLOST     (1 << 3)
 #define ID_NACK                (1 << 4)
+#define ID_EPROTO      (1 << 5)
 /* persistent flags */
 #define ID_P_HOST_NOTIFY       BIT(28)
 #define ID_P_REP_AFTER_RD      BIT(29)
@@ -522,6 +523,7 @@ static void rcar_i2c_irq_send(struct rcar_i2c_priv *priv, u32 msr)
 static void rcar_i2c_irq_recv(struct rcar_i2c_priv *priv, u32 msr)
 {
        struct i2c_msg *msg = priv->msg;
+       bool recv_len_init = priv->pos == 0 && msg->flags & I2C_M_RECV_LEN;
 
        /* FIXME: sometimes, unknown interrupt happened. Do nothing */
        if (!(msr & MDR))
@@ -535,12 +537,29 @@ static void rcar_i2c_irq_recv(struct rcar_i2c_priv *priv, u32 msr)
                rcar_i2c_dma(priv);
        } else if (priv->pos < msg->len) {
                /* get received data */
-               msg->buf[priv->pos] = rcar_i2c_read(priv, ICRXTX);
+               u8 data = rcar_i2c_read(priv, ICRXTX);
+
+               msg->buf[priv->pos] = data;
+               if (recv_len_init) {
+                       if (data == 0 || data > I2C_SMBUS_BLOCK_MAX) {
+                               priv->flags |= ID_DONE | ID_EPROTO;
+                               return;
+                       }
+                       msg->len += msg->buf[0];
+                       /* Enough data for DMA? */
+                       if (rcar_i2c_dma(priv))
+                               return;
+                       /* new length after RECV_LEN now properly initialized */
+                       recv_len_init = false;
+               }
                priv->pos++;
        }
 
-       /* If next received data is the _LAST_, go to new phase. */
-       if (priv->pos + 1 == msg->len) {
+       /*
+        * If next received data is the _LAST_ and we are not waiting for a new
+        * length because of RECV_LEN, then go to a new phase.
+        */
+       if (priv->pos + 1 == msg->len && !recv_len_init) {
                if (priv->flags & ID_LAST_MSG) {
                        rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP);
                } else {
@@ -847,6 +866,8 @@ static int rcar_i2c_master_xfer(struct i2c_adapter *adap,
                ret = -ENXIO;
        } else if (priv->flags & ID_ARBLOST) {
                ret = -EAGAIN;
+       } else if (priv->flags & ID_EPROTO) {
+               ret = -EPROTO;
        } else {
                ret = num - priv->msgs_left; /* The number of transfer */
        }
@@ -909,6 +930,8 @@ static int rcar_i2c_master_xfer_atomic(struct i2c_adapter *adap,
                ret = -ENXIO;
        } else if (priv->flags & ID_ARBLOST) {
                ret = -EAGAIN;
+       } else if (priv->flags & ID_EPROTO) {
+               ret = -EPROTO;
        } else {
                ret = num - priv->msgs_left; /* The number of transfer */
        }
@@ -975,7 +998,7 @@ static u32 rcar_i2c_func(struct i2c_adapter *adap)
         * I2C_M_IGNORE_NAK (automatically sends STOP after NAK)
         */
        u32 func = I2C_FUNC_I2C | I2C_FUNC_SLAVE |
-                  (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+                  (I2C_FUNC_SMBUS_EMUL_ALL & ~I2C_FUNC_SMBUS_QUICK);
 
        if (priv->flags & ID_P_HOST_NOTIFY)
                func |= I2C_FUNC_SMBUS_HOST_NOTIFY;