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