1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * i2c support for Silicon Labs' CP2615 Digital Audio Bridge |
4 | * |
5 | * (c) 2021, Bence Csókás <bence98@sch.bme.hu> |
6 | */ |
7 | |
8 | #include <linux/errno.h> |
9 | #include <linux/i2c.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/string.h> |
13 | #include <linux/usb.h> |
14 | |
15 | /** CP2615 I/O Protocol implementation */ |
16 | |
17 | #define CP2615_VID 0x10c4 |
18 | #define CP2615_PID 0xeac1 |
19 | |
20 | #define IOP_EP_IN 0x82 |
21 | #define IOP_EP_OUT 0x02 |
22 | #define IOP_IFN 1 |
23 | #define IOP_ALTSETTING 2 |
24 | |
25 | #define MAX_IOP_SIZE 64 |
26 | #define MAX_IOP_PAYLOAD_SIZE (MAX_IOP_SIZE - 6) |
27 | #define MAX_I2C_SIZE (MAX_IOP_PAYLOAD_SIZE - 4) |
28 | |
29 | enum cp2615_iop_msg_type { |
30 | iop_GetAccessoryInfo = 0xD100, |
31 | iop_AccessoryInfo = 0xA100, |
32 | iop_GetPortConfiguration = 0xD203, |
33 | iop_PortConfiguration = 0xA203, |
34 | iop_DoI2cTransfer = 0xD400, |
35 | iop_I2cTransferResult = 0xA400, |
36 | iop_GetSerialState = 0xD501, |
37 | iop_SerialState = 0xA501 |
38 | }; |
39 | |
40 | struct __packed cp2615_iop_msg { |
41 | __be16 preamble, length, msg; |
42 | u8 data[MAX_IOP_PAYLOAD_SIZE]; |
43 | }; |
44 | |
45 | #define PART_ID_A01 0x1400 |
46 | #define PART_ID_A02 0x1500 |
47 | |
48 | struct __packed cp2615_iop_accessory_info { |
49 | __be16 part_id, option_id, proto_ver; |
50 | }; |
51 | |
52 | struct __packed cp2615_i2c_transfer { |
53 | u8 tag, i2caddr, read_len, write_len; |
54 | u8 data[MAX_I2C_SIZE]; |
55 | }; |
56 | |
57 | /* Possible values for struct cp2615_i2c_transfer_result.status */ |
58 | enum cp2615_i2c_status { |
59 | /* Writing to the internal EEPROM failed, because it is locked */ |
60 | CP2615_CFG_LOCKED = -6, |
61 | /* read_len or write_len out of range */ |
62 | CP2615_INVALID_PARAM = -4, |
63 | /* I2C slave did not ACK in time */ |
64 | CP2615_TIMEOUT, |
65 | /* I2C bus busy */ |
66 | CP2615_BUS_BUSY, |
67 | /* I2C bus error (ie. device NAK'd the request) */ |
68 | CP2615_BUS_ERROR, |
69 | CP2615_SUCCESS |
70 | }; |
71 | |
72 | struct __packed cp2615_i2c_transfer_result { |
73 | u8 tag, i2caddr; |
74 | s8 status; |
75 | u8 read_len; |
76 | u8 data[MAX_I2C_SIZE]; |
77 | }; |
78 | |
79 | static int cp2615_init_iop_msg(struct cp2615_iop_msg *ret, enum cp2615_iop_msg_type msg, |
80 | const void *data, size_t data_len) |
81 | { |
82 | if (data_len > MAX_IOP_PAYLOAD_SIZE) |
83 | return -EFBIG; |
84 | |
85 | if (!ret) |
86 | return -EINVAL; |
87 | |
88 | ret->preamble = htons(0x2A2AU); |
89 | ret->length = htons(data_len + 6); |
90 | ret->msg = htons(msg); |
91 | if (data && data_len) |
92 | memcpy(&ret->data, data, data_len); |
93 | return 0; |
94 | } |
95 | |
96 | static int cp2615_init_i2c_msg(struct cp2615_iop_msg *ret, const struct cp2615_i2c_transfer *data) |
97 | { |
98 | return cp2615_init_iop_msg(ret, msg: iop_DoI2cTransfer, data, data_len: 4 + data->write_len); |
99 | } |
100 | |
101 | /* Translates status codes to Linux errno's */ |
102 | static int cp2615_check_status(enum cp2615_i2c_status status) |
103 | { |
104 | switch (status) { |
105 | case CP2615_SUCCESS: |
106 | return 0; |
107 | case CP2615_BUS_ERROR: |
108 | return -ENXIO; |
109 | case CP2615_BUS_BUSY: |
110 | return -EAGAIN; |
111 | case CP2615_TIMEOUT: |
112 | return -ETIMEDOUT; |
113 | case CP2615_INVALID_PARAM: |
114 | return -EINVAL; |
115 | case CP2615_CFG_LOCKED: |
116 | return -EPERM; |
117 | } |
118 | /* Unknown error code */ |
119 | return -EPROTO; |
120 | } |
121 | |
122 | /** Driver code */ |
123 | |
124 | static int |
125 | cp2615_i2c_send(struct usb_interface *usbif, struct cp2615_i2c_transfer *i2c_w) |
126 | { |
127 | struct cp2615_iop_msg *msg = kzalloc(size: sizeof(*msg), GFP_KERNEL); |
128 | struct usb_device *usbdev = interface_to_usbdev(usbif); |
129 | int res = cp2615_init_i2c_msg(ret: msg, data: i2c_w); |
130 | |
131 | if (!res) |
132 | res = usb_bulk_msg(usb_dev: usbdev, usb_sndbulkpipe(usbdev, IOP_EP_OUT), |
133 | data: msg, ntohs(msg->length), NULL, timeout: 0); |
134 | kfree(objp: msg); |
135 | return res; |
136 | } |
137 | |
138 | static int |
139 | cp2615_i2c_recv(struct usb_interface *usbif, unsigned char tag, void *buf) |
140 | { |
141 | struct usb_device *usbdev = interface_to_usbdev(usbif); |
142 | struct cp2615_iop_msg *msg; |
143 | struct cp2615_i2c_transfer_result *i2c_r; |
144 | int res; |
145 | |
146 | msg = kzalloc(size: sizeof(*msg), GFP_KERNEL); |
147 | if (!msg) |
148 | return -ENOMEM; |
149 | |
150 | res = usb_bulk_msg(usb_dev: usbdev, usb_rcvbulkpipe(usbdev, IOP_EP_IN), data: msg, |
151 | len: sizeof(struct cp2615_iop_msg), NULL, timeout: 0); |
152 | if (res < 0) { |
153 | kfree(objp: msg); |
154 | return res; |
155 | } |
156 | |
157 | i2c_r = (struct cp2615_i2c_transfer_result *)&msg->data; |
158 | if (msg->msg != htons(iop_I2cTransferResult) || i2c_r->tag != tag) { |
159 | kfree(objp: msg); |
160 | return -EIO; |
161 | } |
162 | |
163 | res = cp2615_check_status(status: i2c_r->status); |
164 | if (!res) |
165 | memcpy(buf, &i2c_r->data, i2c_r->read_len); |
166 | |
167 | kfree(objp: msg); |
168 | return res; |
169 | } |
170 | |
171 | /* Checks if the IOP is functional by querying the part's ID */ |
172 | static int cp2615_check_iop(struct usb_interface *usbif) |
173 | { |
174 | struct cp2615_iop_msg *msg = kzalloc(size: sizeof(*msg), GFP_KERNEL); |
175 | struct cp2615_iop_accessory_info *info = (struct cp2615_iop_accessory_info *)&msg->data; |
176 | struct usb_device *usbdev = interface_to_usbdev(usbif); |
177 | int res = cp2615_init_iop_msg(ret: msg, msg: iop_GetAccessoryInfo, NULL, data_len: 0); |
178 | |
179 | if (res) |
180 | goto out; |
181 | |
182 | res = usb_bulk_msg(usb_dev: usbdev, usb_sndbulkpipe(usbdev, IOP_EP_OUT), |
183 | data: msg, ntohs(msg->length), NULL, timeout: 0); |
184 | if (res) |
185 | goto out; |
186 | |
187 | res = usb_bulk_msg(usb_dev: usbdev, usb_rcvbulkpipe(usbdev, IOP_EP_IN), |
188 | data: msg, len: sizeof(struct cp2615_iop_msg), NULL, timeout: 0); |
189 | if (res) |
190 | goto out; |
191 | |
192 | if (msg->msg != htons(iop_AccessoryInfo)) { |
193 | res = -EIO; |
194 | goto out; |
195 | } |
196 | |
197 | switch (ntohs(info->part_id)) { |
198 | case PART_ID_A01: |
199 | dev_dbg(&usbif->dev, "Found A01 part. (WARNING: errata exists!)\n" ); |
200 | break; |
201 | case PART_ID_A02: |
202 | dev_dbg(&usbif->dev, "Found good A02 part.\n" ); |
203 | break; |
204 | default: |
205 | dev_warn(&usbif->dev, "Unknown part ID %04X\n" , ntohs(info->part_id)); |
206 | } |
207 | |
208 | out: |
209 | kfree(objp: msg); |
210 | return res; |
211 | } |
212 | |
213 | static int |
214 | cp2615_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) |
215 | { |
216 | struct usb_interface *usbif = adap->algo_data; |
217 | int i = 0, ret = 0; |
218 | struct i2c_msg *msg; |
219 | struct cp2615_i2c_transfer i2c_w = {0}; |
220 | |
221 | dev_dbg(&usbif->dev, "Doing %d I2C transactions\n" , num); |
222 | |
223 | for (; !ret && i < num; i++) { |
224 | msg = &msgs[i]; |
225 | |
226 | i2c_w.tag = 0xdd; |
227 | i2c_w.i2caddr = i2c_8bit_addr_from_msg(msg); |
228 | if (msg->flags & I2C_M_RD) { |
229 | i2c_w.read_len = msg->len; |
230 | i2c_w.write_len = 0; |
231 | } else { |
232 | i2c_w.read_len = 0; |
233 | i2c_w.write_len = msg->len; |
234 | memcpy(&i2c_w.data, msg->buf, i2c_w.write_len); |
235 | } |
236 | ret = cp2615_i2c_send(usbif, i2c_w: &i2c_w); |
237 | if (ret) |
238 | break; |
239 | ret = cp2615_i2c_recv(usbif, tag: i2c_w.tag, buf: msg->buf); |
240 | } |
241 | if (ret < 0) |
242 | return ret; |
243 | return i; |
244 | } |
245 | |
246 | static u32 |
247 | cp2615_i2c_func(struct i2c_adapter *adap) |
248 | { |
249 | return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; |
250 | } |
251 | |
252 | static const struct i2c_algorithm cp2615_i2c_algo = { |
253 | .master_xfer = cp2615_i2c_master_xfer, |
254 | .functionality = cp2615_i2c_func, |
255 | }; |
256 | |
257 | /* |
258 | * This chip has some limitations: one is that the USB endpoint |
259 | * can only receive 64 bytes/transfer, that leaves 54 bytes for |
260 | * the I2C transfer. On top of that, EITHER read_len OR write_len |
261 | * may be zero, but not both. If both are non-zero, the adapter |
262 | * issues a write followed by a read. And the chip does not |
263 | * support repeated START between the write and read phases. |
264 | */ |
265 | static struct i2c_adapter_quirks cp2615_i2c_quirks = { |
266 | .max_write_len = MAX_I2C_SIZE, |
267 | .max_read_len = MAX_I2C_SIZE, |
268 | .flags = I2C_AQ_COMB_WRITE_THEN_READ | I2C_AQ_NO_ZERO_LEN | I2C_AQ_NO_REP_START, |
269 | .max_comb_1st_msg_len = MAX_I2C_SIZE, |
270 | .max_comb_2nd_msg_len = MAX_I2C_SIZE |
271 | }; |
272 | |
273 | static void |
274 | cp2615_i2c_remove(struct usb_interface *usbif) |
275 | { |
276 | struct i2c_adapter *adap = usb_get_intfdata(intf: usbif); |
277 | |
278 | usb_set_intfdata(intf: usbif, NULL); |
279 | i2c_del_adapter(adap); |
280 | } |
281 | |
282 | static int |
283 | cp2615_i2c_probe(struct usb_interface *usbif, const struct usb_device_id *id) |
284 | { |
285 | int ret = 0; |
286 | struct i2c_adapter *adap; |
287 | struct usb_device *usbdev = interface_to_usbdev(usbif); |
288 | |
289 | ret = usb_set_interface(dev: usbdev, IOP_IFN, IOP_ALTSETTING); |
290 | if (ret) |
291 | return ret; |
292 | |
293 | ret = cp2615_check_iop(usbif); |
294 | if (ret) |
295 | return ret; |
296 | |
297 | adap = devm_kzalloc(dev: &usbif->dev, size: sizeof(struct i2c_adapter), GFP_KERNEL); |
298 | if (!adap) |
299 | return -ENOMEM; |
300 | |
301 | strscpy(adap->name, usbdev->serial, sizeof(adap->name)); |
302 | adap->owner = THIS_MODULE; |
303 | adap->dev.parent = &usbif->dev; |
304 | adap->dev.of_node = usbif->dev.of_node; |
305 | adap->timeout = HZ; |
306 | adap->algo = &cp2615_i2c_algo; |
307 | adap->quirks = &cp2615_i2c_quirks; |
308 | adap->algo_data = usbif; |
309 | |
310 | ret = i2c_add_adapter(adap); |
311 | if (ret) |
312 | return ret; |
313 | |
314 | usb_set_intfdata(intf: usbif, data: adap); |
315 | return 0; |
316 | } |
317 | |
318 | static const struct usb_device_id id_table[] = { |
319 | { USB_DEVICE_INTERFACE_NUMBER(CP2615_VID, CP2615_PID, IOP_IFN) }, |
320 | { } |
321 | }; |
322 | |
323 | MODULE_DEVICE_TABLE(usb, id_table); |
324 | |
325 | static struct usb_driver cp2615_i2c_driver = { |
326 | .name = "i2c-cp2615" , |
327 | .probe = cp2615_i2c_probe, |
328 | .disconnect = cp2615_i2c_remove, |
329 | .id_table = id_table, |
330 | }; |
331 | |
332 | module_usb_driver(cp2615_i2c_driver); |
333 | |
334 | MODULE_AUTHOR("Bence Csókás <bence98@sch.bme.hu>" ); |
335 | MODULE_DESCRIPTION("CP2615 I2C bus driver" ); |
336 | MODULE_LICENSE("GPL" ); |
337 | |