1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (C) IBM Corporation 2023 */ |
3 | |
4 | #include <linux/device.h> |
5 | #include <linux/fsi.h> |
6 | #include <linux/i2c.h> |
7 | #include <linux/module.h> |
8 | #include <linux/mod_devicetable.h> |
9 | #include <linux/mutex.h> |
10 | |
11 | #include "fsi-master-i2cr.h" |
12 | |
13 | #define CREATE_TRACE_POINTS |
14 | #include <trace/events/fsi_master_i2cr.h> |
15 | |
16 | #define I2CR_ADDRESS_CFAM(a) ((a) >> 2) |
17 | #define I2CR_INITIAL_PARITY true |
18 | |
19 | #define I2CR_STATUS_CMD 0x60002 |
20 | #define I2CR_STATUS_ERR BIT_ULL(61) |
21 | #define I2CR_ERROR_CMD 0x60004 |
22 | #define I2CR_LOG_CMD 0x60008 |
23 | |
24 | static const u8 i2cr_cfam[] = { |
25 | 0xc0, 0x02, 0x0d, 0xa6, |
26 | 0x80, 0x01, 0x10, 0x02, |
27 | 0x80, 0x01, 0x10, 0x02, |
28 | 0x80, 0x01, 0x10, 0x02, |
29 | 0x80, 0x01, 0x80, 0x52, |
30 | 0x80, 0x01, 0x10, 0x02, |
31 | 0x80, 0x01, 0x10, 0x02, |
32 | 0x80, 0x01, 0x10, 0x02, |
33 | 0x80, 0x01, 0x10, 0x02, |
34 | 0x80, 0x01, 0x22, 0x2d, |
35 | 0x00, 0x00, 0x00, 0x00, |
36 | 0xde, 0xad, 0xc0, 0xde |
37 | }; |
38 | |
39 | static bool i2cr_check_parity32(u32 v, bool parity) |
40 | { |
41 | u32 i; |
42 | |
43 | for (i = 0; i < 32; ++i) { |
44 | if (v & (1u << i)) |
45 | parity = !parity; |
46 | } |
47 | |
48 | return parity; |
49 | } |
50 | |
51 | static bool i2cr_check_parity64(u64 v) |
52 | { |
53 | u32 i; |
54 | bool parity = I2CR_INITIAL_PARITY; |
55 | |
56 | for (i = 0; i < 64; ++i) { |
57 | if (v & (1llu << i)) |
58 | parity = !parity; |
59 | } |
60 | |
61 | return parity; |
62 | } |
63 | |
64 | static u32 i2cr_get_command(u32 address, bool parity) |
65 | { |
66 | address <<= 1; |
67 | |
68 | if (i2cr_check_parity32(v: address, parity)) |
69 | address |= 1; |
70 | |
71 | return address; |
72 | } |
73 | |
74 | static int i2cr_transfer(struct i2c_client *client, u32 command, u64 *data) |
75 | { |
76 | struct i2c_msg msgs[2]; |
77 | int ret; |
78 | |
79 | msgs[0].addr = client->addr; |
80 | msgs[0].flags = 0; |
81 | msgs[0].len = sizeof(command); |
82 | msgs[0].buf = (__u8 *)&command; |
83 | msgs[1].addr = client->addr; |
84 | msgs[1].flags = I2C_M_RD; |
85 | msgs[1].len = sizeof(*data); |
86 | msgs[1].buf = (__u8 *)data; |
87 | |
88 | ret = i2c_transfer(adap: client->adapter, msgs, num: 2); |
89 | if (ret == 2) |
90 | return 0; |
91 | |
92 | trace_i2cr_i2c_error(client, command, rc: ret); |
93 | |
94 | if (ret < 0) |
95 | return ret; |
96 | |
97 | return -EIO; |
98 | } |
99 | |
100 | static int i2cr_check_status(struct i2c_client *client) |
101 | { |
102 | u64 status; |
103 | int ret; |
104 | |
105 | ret = i2cr_transfer(client, I2CR_STATUS_CMD, data: &status); |
106 | if (ret) |
107 | return ret; |
108 | |
109 | if (status & I2CR_STATUS_ERR) { |
110 | u32 buf[3] = { 0, 0, 0 }; |
111 | u64 error; |
112 | u64 log; |
113 | |
114 | i2cr_transfer(client, I2CR_ERROR_CMD, data: &error); |
115 | i2cr_transfer(client, I2CR_LOG_CMD, data: &log); |
116 | |
117 | trace_i2cr_status_error(client, status, error, log); |
118 | |
119 | buf[0] = I2CR_STATUS_CMD; |
120 | i2c_master_send(client, buf: (const char *)buf, count: sizeof(buf)); |
121 | |
122 | buf[0] = I2CR_ERROR_CMD; |
123 | i2c_master_send(client, buf: (const char *)buf, count: sizeof(buf)); |
124 | |
125 | buf[0] = I2CR_LOG_CMD; |
126 | i2c_master_send(client, buf: (const char *)buf, count: sizeof(buf)); |
127 | |
128 | dev_err(&client->dev, "status:%016llx error:%016llx log:%016llx\n" , status, error, |
129 | log); |
130 | return -EREMOTEIO; |
131 | } |
132 | |
133 | trace_i2cr_status(client, status); |
134 | return 0; |
135 | } |
136 | |
137 | int fsi_master_i2cr_read(struct fsi_master_i2cr *i2cr, u32 addr, u64 *data) |
138 | { |
139 | u32 command = i2cr_get_command(address: addr, I2CR_INITIAL_PARITY); |
140 | int ret; |
141 | |
142 | mutex_lock(&i2cr->lock); |
143 | |
144 | ret = i2cr_transfer(client: i2cr->client, command, data); |
145 | if (ret) |
146 | goto unlock; |
147 | |
148 | ret = i2cr_check_status(client: i2cr->client); |
149 | if (ret) |
150 | goto unlock; |
151 | |
152 | trace_i2cr_read(client: i2cr->client, command, data); |
153 | |
154 | unlock: |
155 | mutex_unlock(lock: &i2cr->lock); |
156 | return ret; |
157 | } |
158 | EXPORT_SYMBOL_GPL(fsi_master_i2cr_read); |
159 | |
160 | int fsi_master_i2cr_write(struct fsi_master_i2cr *i2cr, u32 addr, u64 data) |
161 | { |
162 | u32 buf[3] = { 0 }; |
163 | int ret; |
164 | |
165 | buf[0] = i2cr_get_command(address: addr, parity: i2cr_check_parity64(v: data)); |
166 | memcpy(&buf[1], &data, sizeof(data)); |
167 | |
168 | mutex_lock(&i2cr->lock); |
169 | |
170 | ret = i2c_master_send(client: i2cr->client, buf: (const char *)buf, count: sizeof(buf)); |
171 | if (ret == sizeof(buf)) { |
172 | ret = i2cr_check_status(client: i2cr->client); |
173 | if (!ret) |
174 | trace_i2cr_write(client: i2cr->client, command: buf[0], data); |
175 | } else { |
176 | trace_i2cr_i2c_error(client: i2cr->client, command: buf[0], rc: ret); |
177 | |
178 | if (ret >= 0) |
179 | ret = -EIO; |
180 | } |
181 | |
182 | mutex_unlock(lock: &i2cr->lock); |
183 | return ret; |
184 | } |
185 | EXPORT_SYMBOL_GPL(fsi_master_i2cr_write); |
186 | |
187 | static int i2cr_read(struct fsi_master *master, int link, uint8_t id, uint32_t addr, void *val, |
188 | size_t size) |
189 | { |
190 | struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master); |
191 | u64 data; |
192 | size_t i; |
193 | int ret; |
194 | |
195 | if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4)) |
196 | return -EINVAL; |
197 | |
198 | /* |
199 | * The I2CR doesn't have CFAM or FSI slave address space - only the |
200 | * engines. In order for this to work with the FSI core, we need to |
201 | * emulate at minimum the CFAM config table so that the appropriate |
202 | * engines are discovered. |
203 | */ |
204 | if (addr < 0xc00) { |
205 | if (addr > sizeof(i2cr_cfam) - 4) |
206 | addr = (addr & 0x3) + (sizeof(i2cr_cfam) - 4); |
207 | |
208 | memcpy(val, &i2cr_cfam[addr], size); |
209 | return 0; |
210 | } |
211 | |
212 | ret = fsi_master_i2cr_read(i2cr, I2CR_ADDRESS_CFAM(addr), &data); |
213 | if (ret) |
214 | return ret; |
215 | |
216 | /* |
217 | * FSI core expects up to 4 bytes BE back, while I2CR replied with LE |
218 | * bytes on the wire. |
219 | */ |
220 | for (i = 0; i < size; ++i) |
221 | ((u8 *)val)[i] = ((u8 *)&data)[7 - i]; |
222 | |
223 | return 0; |
224 | } |
225 | |
226 | static int i2cr_write(struct fsi_master *master, int link, uint8_t id, uint32_t addr, |
227 | const void *val, size_t size) |
228 | { |
229 | struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master); |
230 | u64 data = 0; |
231 | size_t i; |
232 | |
233 | if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4)) |
234 | return -EINVAL; |
235 | |
236 | /* I2CR writes to CFAM or FSI slave address are a successful no-op. */ |
237 | if (addr < 0xc00) |
238 | return 0; |
239 | |
240 | /* |
241 | * FSI core passes up to 4 bytes BE, while the I2CR expects LE bytes on |
242 | * the wire. |
243 | */ |
244 | for (i = 0; i < size; ++i) |
245 | ((u8 *)&data)[7 - i] = ((u8 *)val)[i]; |
246 | |
247 | return fsi_master_i2cr_write(i2cr, I2CR_ADDRESS_CFAM(addr), data); |
248 | } |
249 | |
250 | static void i2cr_release(struct device *dev) |
251 | { |
252 | struct fsi_master_i2cr *i2cr = to_fsi_master_i2cr(to_fsi_master(dev)); |
253 | |
254 | of_node_put(node: dev->of_node); |
255 | |
256 | kfree(objp: i2cr); |
257 | } |
258 | |
259 | static int i2cr_probe(struct i2c_client *client) |
260 | { |
261 | struct fsi_master_i2cr *i2cr; |
262 | int ret; |
263 | |
264 | i2cr = kzalloc(size: sizeof(*i2cr), GFP_KERNEL); |
265 | if (!i2cr) |
266 | return -ENOMEM; |
267 | |
268 | /* Only one I2CR on any given I2C bus (fixed I2C device address) */ |
269 | i2cr->master.idx = client->adapter->nr; |
270 | dev_set_name(dev: &i2cr->master.dev, name: "i2cr%d" , i2cr->master.idx); |
271 | i2cr->master.dev.parent = &client->dev; |
272 | i2cr->master.dev.of_node = of_node_get(node: dev_of_node(dev: &client->dev)); |
273 | i2cr->master.dev.release = i2cr_release; |
274 | |
275 | i2cr->master.n_links = 1; |
276 | i2cr->master.read = i2cr_read; |
277 | i2cr->master.write = i2cr_write; |
278 | |
279 | mutex_init(&i2cr->lock); |
280 | i2cr->client = client; |
281 | |
282 | ret = fsi_master_register(master: &i2cr->master); |
283 | if (ret) |
284 | return ret; |
285 | |
286 | i2c_set_clientdata(client, data: i2cr); |
287 | return 0; |
288 | } |
289 | |
290 | static void i2cr_remove(struct i2c_client *client) |
291 | { |
292 | struct fsi_master_i2cr *i2cr = i2c_get_clientdata(client); |
293 | |
294 | fsi_master_unregister(master: &i2cr->master); |
295 | } |
296 | |
297 | static const struct of_device_id i2cr_ids[] = { |
298 | { .compatible = "ibm,i2cr-fsi-master" }, |
299 | { } |
300 | }; |
301 | MODULE_DEVICE_TABLE(of, i2cr_ids); |
302 | |
303 | static struct i2c_driver i2cr_driver = { |
304 | .probe = i2cr_probe, |
305 | .remove = i2cr_remove, |
306 | .driver = { |
307 | .name = "fsi-master-i2cr" , |
308 | .of_match_table = i2cr_ids, |
309 | }, |
310 | }; |
311 | |
312 | module_i2c_driver(i2cr_driver) |
313 | |
314 | MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>" ); |
315 | MODULE_DESCRIPTION("IBM I2C Responder virtual FSI master driver" ); |
316 | MODULE_LICENSE("GPL" ); |
317 | |