i2c: testunit: add command to support versioning and test rep_start
authorWolfram Sang <wsa+renesas@sang-engineering.com>
Sun, 11 Aug 2024 21:23:15 +0000 (23:23 +0200)
committerWolfram Sang <wsa+renesas@sang-engineering.com>
Wed, 14 Aug 2024 17:55:40 +0000 (19:55 +0200)
For some devices, it is essential that controllers handle repeated start
correctly and do not replace it with a stop/start combination. This
addition helps to test that because it will only return a version string
if repeated start is done properly.

Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Documentation/i2c/slave-testunit-backend.rst
drivers/i2c/i2c-slave-testunit.c

index ee019db539389745b2fb5ef3530785d2c8efc72d..110c0055064f2467929e90a2e376cbb1e80c5731 100644 (file)
@@ -133,3 +133,41 @@ later)::
 
   # i2ctransfer -y 0 w3@0x30 3 1 0x10 r?
   0x10 0x0f 0x0e 0x0d 0x0c 0x0b 0x0a 0x09 0x08 0x07 0x06 0x05 0x04 0x03 0x02 0x01 0x00
+
+0x04 GET_VERSION_WITH_REP_START
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. list-table::
+  :header-rows: 1
+
+  * - CMD
+    - DATAL
+    - DATAH
+    - DELAY
+
+  * - 0x04
+    - currently unused
+    - currently unused
+    - leave out, partial command!
+
+Partial command. After sending this command, the testunit will reply to a read
+message with a NUL terminated version string based on UTS_RELEASE. The first
+character is always a 'v' and the length of the version string is at maximum
+128 bytes. However, it will only respond if the read message is connected to
+the write message via repeated start. If your controller driver handles
+repeated start correctly, this will work::
+
+  # i2ctransfer -y 0 w3@0x30 4 0 0 r128
+  0x76 0x36 0x2e 0x31 0x31 0x2e 0x30 0x2d 0x72 0x63 0x31 0x2d 0x30 0x30 0x30 0x30 ...
+
+If you have i2c-tools 4.4 or later, you can print out the data right away::
+
+  # i2ctransfer -y -b 0 w3@0x30 4 0 0 r128
+  v6.11.0-rc1-00009-gd37a1b4d3fd0
+
+STOP/START combinations between the two messages will *not* work because they
+are not equivalent to a REPEATED START. As an example, this returns just the
+default response::
+
+  # i2cset -y 0 0x30 4 0 0 i; i2cget -y 0 0x30
+  0x01
index be1d2e900aef3a1c203f5e76277cd787fff6feaa..51b399aa09a0213952b94ae2a1d396f074cf5fe6 100644 (file)
@@ -6,6 +6,7 @@
  * Copyright (C) 2020 by Renesas Electronics Corporation
  */
 
+#include <generated/utsrelease.h>
 #include <linux/bitops.h>
 #include <linux/i2c.h>
 #include <linux/init.h>
 #include <linux/workqueue.h> /* FIXME: is system_long_wq the best choice? */
 
 #define TU_CUR_VERSION 0x01
+#define TU_VERSION_MAX_LENGTH 128
 
 enum testunit_cmds {
        TU_CMD_READ_BYTES = 1,  /* save 0 for ABORT, RESET or similar */
        TU_CMD_SMBUS_HOST_NOTIFY,
        TU_CMD_SMBUS_BLOCK_PROC_CALL,
+       TU_CMD_GET_VERSION_WITH_REP_START,
        TU_NUM_CMDS
 };
 
@@ -39,10 +42,13 @@ struct testunit_data {
        unsigned long flags;
        u8 regs[TU_NUM_REGS];
        u8 reg_idx;
+       u8 read_idx;
        struct i2c_client *client;
        struct delayed_work worker;
 };
 
+static char tu_version_info[] = "v" UTS_RELEASE "\n\0";
+
 static void i2c_slave_testunit_work(struct work_struct *work)
 {
        struct testunit_data *tu = container_of(work, struct testunit_data, worker.work);
@@ -91,6 +97,8 @@ static int i2c_slave_testunit_slave_cb(struct i2c_client *client,
        struct testunit_data *tu = i2c_get_clientdata(client);
        bool is_proc_call = tu->reg_idx == 3 && tu->regs[TU_REG_DATAL] == 1 &&
                            tu->regs[TU_REG_CMD] == TU_CMD_SMBUS_BLOCK_PROC_CALL;
+       bool is_get_version = tu->reg_idx == 3 &&
+                             tu->regs[TU_REG_CMD] == TU_CMD_GET_VERSION_WITH_REP_START;
        int ret = 0;
 
        switch (event) {
@@ -100,6 +108,7 @@ static int i2c_slave_testunit_slave_cb(struct i2c_client *client,
 
                memset(tu->regs, 0, TU_NUM_REGS);
                tu->reg_idx = 0;
+               tu->read_idx = 0;
                break;
 
        case I2C_SLAVE_WRITE_RECEIVED:
@@ -136,12 +145,21 @@ static int i2c_slave_testunit_slave_cb(struct i2c_client *client,
                break;
 
        case I2C_SLAVE_READ_PROCESSED:
-               if (is_proc_call && tu->regs[TU_REG_DATAH])
+               /* Advance until we reach the NUL character */
+               if (is_get_version && tu_version_info[tu->read_idx] != 0)
+                       tu->read_idx++;
+               else if (is_proc_call && tu->regs[TU_REG_DATAH])
                        tu->regs[TU_REG_DATAH]--;
+
                fallthrough;
 
        case I2C_SLAVE_READ_REQUESTED:
-               *val = is_proc_call ? tu->regs[TU_REG_DATAH] : TU_CUR_VERSION;
+               if (is_get_version)
+                       *val = tu_version_info[tu->read_idx];
+               else if (is_proc_call)
+                       *val = tu->regs[TU_REG_DATAH];
+               else
+                       *val = TU_CUR_VERSION;
                break;
        }
 
@@ -160,6 +178,9 @@ static int i2c_slave_testunit_probe(struct i2c_client *client)
        i2c_set_clientdata(client, tu);
        INIT_DELAYED_WORK(&tu->worker, i2c_slave_testunit_work);
 
+       if (sizeof(tu_version_info) > TU_VERSION_MAX_LENGTH)
+               tu_version_info[TU_VERSION_MAX_LENGTH - 1] = 0;
+
        return i2c_slave_register(client, i2c_slave_testunit_slave_cb);
 };