i2c-slave-testunit.c (4268B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * I2C slave mode testunit 4 * 5 * Copyright (C) 2020 by Wolfram Sang, Sang Engineering <wsa@sang-engineering.com> 6 * Copyright (C) 2020 by Renesas Electronics Corporation 7 */ 8 9#include <linux/bitops.h> 10#include <linux/i2c.h> 11#include <linux/init.h> 12#include <linux/module.h> 13#include <linux/of.h> 14#include <linux/slab.h> 15#include <linux/workqueue.h> /* FIXME: is system_long_wq the best choice? */ 16 17#define TU_CUR_VERSION 0x01 18 19enum testunit_cmds { 20 TU_CMD_READ_BYTES = 1, /* save 0 for ABORT, RESET or similar */ 21 TU_CMD_HOST_NOTIFY, 22 TU_CMD_SMBUS_BLOCK_PROC_CALL, 23 TU_NUM_CMDS 24}; 25 26enum testunit_regs { 27 TU_REG_CMD, 28 TU_REG_DATAL, 29 TU_REG_DATAH, 30 TU_REG_DELAY, 31 TU_NUM_REGS 32}; 33 34enum testunit_flags { 35 TU_FLAG_IN_PROCESS, 36}; 37 38struct testunit_data { 39 unsigned long flags; 40 u8 regs[TU_NUM_REGS]; 41 u8 reg_idx; 42 struct i2c_client *client; 43 struct delayed_work worker; 44}; 45 46static void i2c_slave_testunit_work(struct work_struct *work) 47{ 48 struct testunit_data *tu = container_of(work, struct testunit_data, worker.work); 49 struct i2c_msg msg; 50 u8 msgbuf[256]; 51 int ret = 0; 52 53 msg.addr = I2C_CLIENT_END; 54 msg.buf = msgbuf; 55 56 switch (tu->regs[TU_REG_CMD]) { 57 case TU_CMD_READ_BYTES: 58 msg.addr = tu->regs[TU_REG_DATAL]; 59 msg.flags = I2C_M_RD; 60 msg.len = tu->regs[TU_REG_DATAH]; 61 break; 62 63 case TU_CMD_HOST_NOTIFY: 64 msg.addr = 0x08; 65 msg.flags = 0; 66 msg.len = 3; 67 msgbuf[0] = tu->client->addr; 68 msgbuf[1] = tu->regs[TU_REG_DATAL]; 69 msgbuf[2] = tu->regs[TU_REG_DATAH]; 70 break; 71 72 default: 73 break; 74 } 75 76 if (msg.addr != I2C_CLIENT_END) { 77 ret = i2c_transfer(tu->client->adapter, &msg, 1); 78 /* convert '0 msgs transferred' to errno */ 79 ret = (ret == 0) ? -EIO : ret; 80 } 81 82 if (ret < 0) 83 dev_err(&tu->client->dev, "CMD%02X failed (%d)\n", tu->regs[TU_REG_CMD], ret); 84 85 clear_bit(TU_FLAG_IN_PROCESS, &tu->flags); 86} 87 88static int i2c_slave_testunit_slave_cb(struct i2c_client *client, 89 enum i2c_slave_event event, u8 *val) 90{ 91 struct testunit_data *tu = i2c_get_clientdata(client); 92 bool is_proc_call = tu->reg_idx == 3 && tu->regs[TU_REG_DATAL] == 1 && 93 tu->regs[TU_REG_CMD] == TU_CMD_SMBUS_BLOCK_PROC_CALL; 94 int ret = 0; 95 96 switch (event) { 97 case I2C_SLAVE_WRITE_RECEIVED: 98 if (test_bit(TU_FLAG_IN_PROCESS, &tu->flags)) 99 return -EBUSY; 100 101 if (tu->reg_idx < TU_NUM_REGS) 102 tu->regs[tu->reg_idx] = *val; 103 else 104 ret = -EMSGSIZE; 105 106 if (tu->reg_idx <= TU_NUM_REGS) 107 tu->reg_idx++; 108 109 /* TU_REG_CMD always written at this point */ 110 if (tu->regs[TU_REG_CMD] >= TU_NUM_CMDS) 111 ret = -EINVAL; 112 113 break; 114 115 case I2C_SLAVE_STOP: 116 if (tu->reg_idx == TU_NUM_REGS) { 117 set_bit(TU_FLAG_IN_PROCESS, &tu->flags); 118 queue_delayed_work(system_long_wq, &tu->worker, 119 msecs_to_jiffies(10 * tu->regs[TU_REG_DELAY])); 120 } 121 fallthrough; 122 123 case I2C_SLAVE_WRITE_REQUESTED: 124 memset(tu->regs, 0, TU_NUM_REGS); 125 tu->reg_idx = 0; 126 break; 127 128 case I2C_SLAVE_READ_PROCESSED: 129 if (is_proc_call && tu->regs[TU_REG_DATAH]) 130 tu->regs[TU_REG_DATAH]--; 131 fallthrough; 132 133 case I2C_SLAVE_READ_REQUESTED: 134 *val = is_proc_call ? tu->regs[TU_REG_DATAH] : TU_CUR_VERSION; 135 break; 136 } 137 138 return ret; 139} 140 141static int i2c_slave_testunit_probe(struct i2c_client *client) 142{ 143 struct testunit_data *tu; 144 145 tu = devm_kzalloc(&client->dev, sizeof(struct testunit_data), GFP_KERNEL); 146 if (!tu) 147 return -ENOMEM; 148 149 tu->client = client; 150 i2c_set_clientdata(client, tu); 151 INIT_DELAYED_WORK(&tu->worker, i2c_slave_testunit_work); 152 153 return i2c_slave_register(client, i2c_slave_testunit_slave_cb); 154}; 155 156static int i2c_slave_testunit_remove(struct i2c_client *client) 157{ 158 struct testunit_data *tu = i2c_get_clientdata(client); 159 160 cancel_delayed_work_sync(&tu->worker); 161 i2c_slave_unregister(client); 162 return 0; 163} 164 165static const struct i2c_device_id i2c_slave_testunit_id[] = { 166 { "slave-testunit", 0 }, 167 { } 168}; 169MODULE_DEVICE_TABLE(i2c, i2c_slave_testunit_id); 170 171static struct i2c_driver i2c_slave_testunit_driver = { 172 .driver = { 173 .name = "i2c-slave-testunit", 174 }, 175 .probe_new = i2c_slave_testunit_probe, 176 .remove = i2c_slave_testunit_remove, 177 .id_table = i2c_slave_testunit_id, 178}; 179module_i2c_driver(i2c_slave_testunit_driver); 180 181MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>"); 182MODULE_DESCRIPTION("I2C slave mode test unit"); 183MODULE_LICENSE("GPL v2");