i2c: testunit: add SMBusAlert trigger

To test SMBusAlert handlers, let the testunit be able to trigger
SMBusAlert interrupts. This new command needs a GPIO connected to the
SMBAlert# line.

Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
This commit is contained in:
Wolfram Sang 2024-08-14 20:22:09 +02:00
parent 06e12ae5f0
commit 3d16973f77
2 changed files with 107 additions and 0 deletions

View file

@ -185,3 +185,51 @@ default response::
# i2cset -y 0 0x30 4 0 0 i; i2cget -y 0 0x30
0x00
0x05 SMBUS_ALERT_REQUEST
~~~~~~~~~~~~~~~~~~~~~~~~
.. list-table::
:header-rows: 1
* - CMD
- DATAL
- DATAH
- DELAY
* - 0x05
- response value (7 MSBs interpreted as I2C address)
- currently unused
- n * 10ms
This test raises an interrupt via the SMBAlert pin which the host controller
must handle. The pin must be connected to the testunit as a GPIO. GPIO access
is not allowed to sleep. Currently, this can only be described using firmware
nodes. So, for devicetree, you would add something like this to the testunit
node::
gpios = <&gpio1 24 GPIO_ACTIVE_LOW>;
The following command will trigger the alert with a response of 0xc9 after 1
second of delay::
# i2cset -y 0 0x30 5 0xc9 0x00 100 i
If the host controller supports SMBusAlert, this message with debug level
should appear::
smbus_alert 0-000c: SMBALERT# from dev 0x64, flag 1
This message may appear more than once because the testunit is software not
hardware and, thus, may not be able to react to the response of the host fast
enough. The interrupt count should increase only by one, though::
# cat /proc/interrupts | grep smbus_alert
93: 1 gpio-rcar 26 Edge smbus_alert
If the host does not respond to the alert within 1 second, the test will be
aborted and the testunit will report an error.
For this test, the testunit will shortly drop its assigned address and listen
on the SMBus Alert Response Address (0x0c). It will reassign its original
address afterwards.

View file

@ -8,6 +8,8 @@
#include <generated/utsrelease.h>
#include <linux/bitops.h>
#include <linux/completion.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
@ -22,6 +24,7 @@ enum testunit_cmds {
TU_CMD_SMBUS_HOST_NOTIFY,
TU_CMD_SMBUS_BLOCK_PROC_CALL,
TU_CMD_GET_VERSION_WITH_REP_START,
TU_CMD_SMBUS_ALERT_REQUEST,
TU_NUM_CMDS
};
@ -44,10 +47,37 @@ struct testunit_data {
u8 read_idx;
struct i2c_client *client;
struct delayed_work worker;
struct gpio_desc *gpio;
struct completion alert_done;
};
static char tu_version_info[] = "v" UTS_RELEASE "\n\0";
static int i2c_slave_testunit_smbalert_cb(struct i2c_client *client,
enum i2c_slave_event event, u8 *val)
{
struct testunit_data *tu = i2c_get_clientdata(client);
switch (event) {
case I2C_SLAVE_READ_PROCESSED:
gpiod_set_value(tu->gpio, 0);
fallthrough;
case I2C_SLAVE_READ_REQUESTED:
*val = tu->regs[TU_REG_DATAL];
break;
case I2C_SLAVE_STOP:
complete(&tu->alert_done);
break;
case I2C_SLAVE_WRITE_REQUESTED:
case I2C_SLAVE_WRITE_RECEIVED:
return -EOPNOTSUPP;
}
return 0;
}
static int i2c_slave_testunit_slave_cb(struct i2c_client *client,
enum i2c_slave_event event, u8 *val)
{
@ -127,8 +157,10 @@ static int i2c_slave_testunit_slave_cb(struct i2c_client *client,
static void i2c_slave_testunit_work(struct work_struct *work)
{
struct testunit_data *tu = container_of(work, struct testunit_data, worker.work);
unsigned long time_left;
struct i2c_msg msg;
u8 msgbuf[256];
u16 orig_addr;
int ret = 0;
msg.addr = I2C_CLIENT_END;
@ -150,6 +182,26 @@ static void i2c_slave_testunit_work(struct work_struct *work)
msgbuf[2] = tu->regs[TU_REG_DATAH];
break;
case TU_CMD_SMBUS_ALERT_REQUEST:
i2c_slave_unregister(tu->client);
orig_addr = tu->client->addr;
tu->client->addr = 0x0c;
ret = i2c_slave_register(tu->client, i2c_slave_testunit_smbalert_cb);
if (ret)
goto out_smbalert;
reinit_completion(&tu->alert_done);
gpiod_set_value(tu->gpio, 1);
time_left = wait_for_completion_timeout(&tu->alert_done, HZ);
if (!time_left)
ret = -ETIMEDOUT;
i2c_slave_unregister(tu->client);
out_smbalert:
tu->client->addr = orig_addr;
i2c_slave_register(tu->client, i2c_slave_testunit_slave_cb);
break;
default:
break;
}
@ -176,8 +228,15 @@ static int i2c_slave_testunit_probe(struct i2c_client *client)
tu->client = client;
i2c_set_clientdata(client, tu);
init_completion(&tu->alert_done);
INIT_DELAYED_WORK(&tu->worker, i2c_slave_testunit_work);
tu->gpio = devm_gpiod_get_index_optional(&client->dev, NULL, 0, GPIOD_OUT_LOW);
if (gpiod_cansleep(tu->gpio)) {
dev_err(&client->dev, "GPIO access which may sleep is not allowed\n");
return -EDEADLK;
}
if (sizeof(tu_version_info) > TU_VERSION_MAX_LENGTH)
tu_version_info[TU_VERSION_MAX_LENGTH - 1] = 0;