diff options
author | Wolfram Sang <wsa+renesas@sang-engineering.com> | 2020-09-11 10:16:58 +0300 |
---|---|---|
committer | Wolfram Sang <wsa@kernel.org> | 2020-09-21 12:02:17 +0300 |
commit | a8335c64c5f0916c9ee8b88835ce114085c3642e (patch) | |
tree | a26de00eeea4a585165d311baa83bf71275323d6 /drivers/i2c | |
parent | b62590a92058f3edf8884b46312848e8aafe0922 (diff) | |
download | linux-a8335c64c5f0916c9ee8b88835ce114085c3642e.tar.xz |
i2c: add slave testunit driver
Here is an I2C slave backend driver which allows to test some uncommon
functionalities of the I2C and SMBus world. Usually, you need specific
devices to test e.g. SMBus Host Notify and such. With this driver you
just need the slave interface of another I2C controller.
This initial version has testcases for multi-master and SMBus Host
Notify. Already planned but not yet implemented are SMBus Alert and
messages with I2C_M_RECV_LEN.
Please read the documentation for further details.
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/Kconfig | 8 | ||||
-rw-r--r-- | drivers/i2c/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/i2c-slave-testunit.c | 175 |
3 files changed, 184 insertions, 0 deletions
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 5449729cdb87..438905e2a1d0 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -125,6 +125,14 @@ config I2C_SLAVE_EEPROM This backend makes Linux behave like an I2C EEPROM. Please read Documentation/i2c/slave-eeprom-backend.rst for further details. +config I2C_SLAVE_TESTUNIT + tristate "I2C eeprom testunit driver" + help + This backend can be used to trigger test cases for I2C bus masters + which require a remote device with certain capabilities, e.g. + multi-master, SMBus Host Notify, etc. Please read + Documentation/i2c/slave-testunit-backend.rst for further details. + endif config I2C_DEBUG_CORE diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index bed6ba63c983..c1d493dc9bac 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -16,5 +16,6 @@ obj-$(CONFIG_I2C_MUX) += i2c-mux.o obj-y += algos/ busses/ muxes/ obj-$(CONFIG_I2C_STUB) += i2c-stub.o obj-$(CONFIG_I2C_SLAVE_EEPROM) += i2c-slave-eeprom.o +obj-$(CONFIG_I2C_SLAVE_TESTUNIT) += i2c-slave-testunit.o ccflags-$(CONFIG_I2C_DEBUG_CORE) := -DDEBUG diff --git a/drivers/i2c/i2c-slave-testunit.c b/drivers/i2c/i2c-slave-testunit.c new file mode 100644 index 000000000000..c288102de324 --- /dev/null +++ b/drivers/i2c/i2c-slave-testunit.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C slave mode testunit + * + * Copyright (C) 2020 by Wolfram Sang, Sang Engineering <wsa@sang-engineering.com> + * Copyright (C) 2020 by Renesas Electronics Corporation + */ + +#include <linux/bitops.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/workqueue.h> /* FIXME: is system_long_wq the best choice? */ + +#define TU_CUR_VERSION 0x01 + +enum testunit_cmds { + TU_CMD_READ_BYTES = 1, /* save 0 for ABORT, RESET or similar */ + TU_CMD_HOST_NOTIFY, + TU_NUM_CMDS +}; + +enum testunit_regs { + TU_REG_CMD, + TU_REG_DATAL, + TU_REG_DATAH, + TU_REG_DELAY, + TU_NUM_REGS +}; + +enum testunit_flags { + TU_FLAG_IN_PROCESS, +}; + +struct testunit_data { + unsigned long flags; + u8 regs[TU_NUM_REGS]; + u8 reg_idx; + struct i2c_client *client; + struct delayed_work worker; +}; + +static void i2c_slave_testunit_work(struct work_struct *work) +{ + struct testunit_data *tu = container_of(work, struct testunit_data, worker.work); + struct i2c_msg msg; + u8 msgbuf[256]; + int ret = 0; + + msg.addr = I2C_CLIENT_END; + msg.buf = msgbuf; + + switch (tu->regs[TU_REG_CMD]) { + case TU_CMD_READ_BYTES: + msg.addr = tu->regs[TU_REG_DATAL]; + msg.flags = I2C_M_RD; + msg.len = tu->regs[TU_REG_DATAH]; + break; + + case TU_CMD_HOST_NOTIFY: + msg.addr = 0x08; + msg.flags = 0; + msg.len = 3; + msgbuf[0] = tu->client->addr; + msgbuf[1] = tu->regs[TU_REG_DATAL]; + msgbuf[2] = tu->regs[TU_REG_DATAH]; + break; + + default: + break; + } + + if (msg.addr != I2C_CLIENT_END) { + ret = i2c_transfer(tu->client->adapter, &msg, 1); + /* convert '0 msgs transferred' to errno */ + ret = (ret == 0) ? -EIO : ret; + } + + if (ret < 0) + dev_err(&tu->client->dev, "CMD%02X failed (%d)\n", tu->regs[TU_REG_CMD], ret); + + clear_bit(TU_FLAG_IN_PROCESS, &tu->flags); +} + +static int i2c_slave_testunit_slave_cb(struct i2c_client *client, + enum i2c_slave_event event, u8 *val) +{ + struct testunit_data *tu = i2c_get_clientdata(client); + int ret = 0; + + switch (event) { + case I2C_SLAVE_WRITE_RECEIVED: + if (test_bit(TU_FLAG_IN_PROCESS, &tu->flags)) + return -EBUSY; + + if (tu->reg_idx < TU_NUM_REGS) + tu->regs[tu->reg_idx] = *val; + else + ret = -EMSGSIZE; + + if (tu->reg_idx <= TU_NUM_REGS) + tu->reg_idx++; + + /* TU_REG_CMD always written at this point */ + if (tu->regs[TU_REG_CMD] >= TU_NUM_CMDS) + ret = -EINVAL; + + break; + + case I2C_SLAVE_STOP: + if (tu->reg_idx == TU_NUM_REGS) { + set_bit(TU_FLAG_IN_PROCESS, &tu->flags); + queue_delayed_work(system_long_wq, &tu->worker, + msecs_to_jiffies(10 * tu->regs[TU_REG_DELAY])); + } + fallthrough; + + case I2C_SLAVE_WRITE_REQUESTED: + tu->reg_idx = 0; + break; + + case I2C_SLAVE_READ_REQUESTED: + case I2C_SLAVE_READ_PROCESSED: + *val = TU_CUR_VERSION; + break; + } + + return ret; +} + +static int i2c_slave_testunit_probe(struct i2c_client *client) +{ + struct testunit_data *tu; + + tu = devm_kzalloc(&client->dev, sizeof(struct testunit_data), GFP_KERNEL); + if (!tu) + return -ENOMEM; + + tu->client = client; + i2c_set_clientdata(client, tu); + INIT_DELAYED_WORK(&tu->worker, i2c_slave_testunit_work); + + return i2c_slave_register(client, i2c_slave_testunit_slave_cb); +}; + +static int i2c_slave_testunit_remove(struct i2c_client *client) +{ + struct testunit_data *tu = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&tu->worker); + i2c_slave_unregister(client); + return 0; +} + +static const struct i2c_device_id i2c_slave_testunit_id[] = { + { "slave-testunit", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, i2c_slave_testunit_id); + +static struct i2c_driver i2c_slave_testunit_driver = { + .driver = { + .name = "i2c-slave-testunit", + }, + .probe_new = i2c_slave_testunit_probe, + .remove = i2c_slave_testunit_remove, + .id_table = i2c_slave_testunit_id, +}; +module_i2c_driver(i2c_slave_testunit_driver); + +MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>"); +MODULE_DESCRIPTION("I2C slave mode test unit"); +MODULE_LICENSE("GPL v2"); |