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