Commit | Line | Data |
---|---|---|
a8335c64 WS |
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, | |
b39ab96a | 22 | TU_CMD_SMBUS_BLOCK_PROC_CALL, |
a8335c64 WS |
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(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 | ||
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); | |
b39ab96a WS |
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; | |
a8335c64 WS |
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: | |
b39ab96a | 124 | memset(tu->regs, 0, TU_NUM_REGS); |
a8335c64 WS |
125 | tu->reg_idx = 0; |
126 | break; | |
127 | ||
a8335c64 | 128 | case I2C_SLAVE_READ_PROCESSED: |
b39ab96a WS |
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; | |
a8335c64 WS |
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(&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 | ||
ed5c2f5f | 156 | static void i2c_slave_testunit_remove(struct i2c_client *client) |
a8335c64 WS |
157 | { |
158 | struct testunit_data *tu = i2c_get_clientdata(client); | |
159 | ||
160 | cancel_delayed_work_sync(&tu->worker); | |
161 | i2c_slave_unregister(client); | |
a8335c64 WS |
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 | }, | |
834a9dc4 | 174 | .probe = i2c_slave_testunit_probe, |
a8335c64 WS |
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"); |