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_CMD_SMBUS_BLOCK_PROC_CALL, |
23 | TU_NUM_CMDS |
24 | }; |
25 | |
26 | enum testunit_regs { |
27 | TU_REG_CMD, |
28 | TU_REG_DATAL, |
29 | TU_REG_DATAH, |
30 | TU_REG_DELAY, |
31 | TU_NUM_REGS |
32 | }; |
33 | |
34 | enum testunit_flags { |
35 | TU_FLAG_IN_PROCESS, |
36 | }; |
37 | |
38 | struct 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 | |
46 | static 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(adap: tu->client->adapter, msgs: &msg, num: 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(nr: TU_FLAG_IN_PROCESS, addr: &tu->flags); |
86 | } |
87 | |
88 | static 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(nr: TU_FLAG_IN_PROCESS, addr: &tu->flags); |
118 | queue_delayed_work(wq: system_long_wq, dwork: &tu->worker, |
119 | delay: msecs_to_jiffies(m: 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 | |
141 | static int i2c_slave_testunit_probe(struct i2c_client *client) |
142 | { |
143 | struct testunit_data *tu; |
144 | |
145 | tu = devm_kzalloc(dev: &client->dev, size: sizeof(struct testunit_data), GFP_KERNEL); |
146 | if (!tu) |
147 | return -ENOMEM; |
148 | |
149 | tu->client = client; |
150 | i2c_set_clientdata(client, data: tu); |
151 | INIT_DELAYED_WORK(&tu->worker, i2c_slave_testunit_work); |
152 | |
153 | return i2c_slave_register(client, slave_cb: i2c_slave_testunit_slave_cb); |
154 | }; |
155 | |
156 | static void i2c_slave_testunit_remove(struct i2c_client *client) |
157 | { |
158 | struct testunit_data *tu = i2c_get_clientdata(client); |
159 | |
160 | cancel_delayed_work_sync(dwork: &tu->worker); |
161 | i2c_slave_unregister(client); |
162 | } |
163 | |
164 | static const struct i2c_device_id i2c_slave_testunit_id[] = { |
165 | { "slave-testunit" , 0 }, |
166 | { } |
167 | }; |
168 | MODULE_DEVICE_TABLE(i2c, i2c_slave_testunit_id); |
169 | |
170 | static struct i2c_driver i2c_slave_testunit_driver = { |
171 | .driver = { |
172 | .name = "i2c-slave-testunit" , |
173 | }, |
174 | .probe = i2c_slave_testunit_probe, |
175 | .remove = i2c_slave_testunit_remove, |
176 | .id_table = i2c_slave_testunit_id, |
177 | }; |
178 | module_i2c_driver(i2c_slave_testunit_driver); |
179 | |
180 | MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>" ); |
181 | MODULE_DESCRIPTION("I2C slave mode test unit" ); |
182 | MODULE_LICENSE("GPL v2" ); |
183 | |