Skip to content

Commit 4a76954

Browse files
bence98wsakernel
authored andcommitted
i2c: cp2615: add i2c driver for Silicon Labs' CP2615 Digital Audio Bridge
Create an i2c_adapter for CP2615's I2C master interface Signed-off-by: Bence Csókás <[email protected]> [wsa: switched to '__packed', added some 'static' and an include] Signed-off-by: Wolfram Sang <[email protected]>
1 parent aca0141 commit 4a76954

File tree

4 files changed

+346
-0
lines changed

4 files changed

+346
-0
lines changed

MAINTAINERS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4585,6 +4585,11 @@ F: drivers/counter/
45854585
F: include/linux/counter.h
45864586
F: include/linux/counter_enum.h
45874587

4588+
CP2615 I2C DRIVER
4589+
M: Bence Csókás <[email protected]>
4590+
S: Maintained
4591+
F: drivers/i2c/busses/i2c-cp2615.c
4592+
45884593
CPMAC ETHERNET DRIVER
45894594
M: Florian Fainelli <[email protected]>
45904595

drivers/i2c/busses/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,16 @@ config I2C_DLN2
11991199
This driver can also be built as a module. If so, the module
12001200
will be called i2c-dln2.
12011201

1202+
config I2C_CP2615
1203+
tristate "Silicon Labs CP2615 USB sound card and I2C adapter"
1204+
depends on USB
1205+
help
1206+
If you say yes to this option, support will be included for Silicon
1207+
Labs CP2615's I2C interface.
1208+
1209+
This driver can also be built as a module. If so, the module
1210+
will be called i2c-cp2615.
1211+
12021212
config I2C_PARPORT
12031213
tristate "Parallel port adapter"
12041214
depends on PARPORT

drivers/i2c/busses/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o
123123
# External I2C/SMBus adapter drivers
124124
obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
125125
obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o
126+
obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
126127
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
127128
obj-$(CONFIG_I2C_ROBOTFUZZ_OSIF) += i2c-robotfuzz-osif.o
128129
obj-$(CONFIG_I2C_TAOS_EVM) += i2c-taos-evm.o

drivers/i2c/busses/i2c-cp2615.c

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
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 <[email protected]>
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 = 0x2A2A;
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, iop_DoI2cTransfer, data, 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(sizeof(*msg), GFP_KERNEL);
128+
struct usb_device *usbdev = interface_to_usbdev(usbif);
129+
int res = cp2615_init_i2c_msg(msg, i2c_w);
130+
131+
if (!res)
132+
res = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, IOP_EP_OUT),
133+
msg, ntohs(msg->length), NULL, 0);
134+
kfree(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 cp2615_iop_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
142+
struct cp2615_i2c_transfer_result *i2c_r = (struct cp2615_i2c_transfer_result *)&msg->data;
143+
struct usb_device *usbdev = interface_to_usbdev(usbif);
144+
int res = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, IOP_EP_IN),
145+
msg, sizeof(struct cp2615_iop_msg), NULL, 0);
146+
147+
if (res < 0) {
148+
kfree(msg);
149+
return res;
150+
}
151+
152+
if (msg->msg != htons(iop_I2cTransferResult) || i2c_r->tag != tag) {
153+
kfree(msg);
154+
return -EIO;
155+
}
156+
157+
res = cp2615_check_status(i2c_r->status);
158+
if (!res)
159+
memcpy(buf, &i2c_r->data, i2c_r->read_len);
160+
161+
kfree(msg);
162+
return res;
163+
}
164+
165+
/* Checks if the IOP is functional by querying the part's ID */
166+
static int cp2615_check_iop(struct usb_interface *usbif)
167+
{
168+
struct cp2615_iop_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
169+
struct cp2615_iop_accessory_info *info = (struct cp2615_iop_accessory_info *)&msg->data;
170+
struct usb_device *usbdev = interface_to_usbdev(usbif);
171+
int res = cp2615_init_iop_msg(msg, iop_GetAccessoryInfo, NULL, 0);
172+
173+
if (res)
174+
goto out;
175+
176+
res = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, IOP_EP_OUT),
177+
msg, ntohs(msg->length), NULL, 0);
178+
if (res)
179+
goto out;
180+
181+
res = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, IOP_EP_IN),
182+
msg, sizeof(struct cp2615_iop_msg), NULL, 0);
183+
if (res)
184+
goto out;
185+
186+
if (msg->msg != htons(iop_AccessoryInfo)) {
187+
res = -EIO;
188+
goto out;
189+
}
190+
191+
switch (ntohs(info->part_id)) {
192+
case PART_ID_A01:
193+
dev_dbg(&usbif->dev, "Found A01 part. (WARNING: errata exists!)\n");
194+
break;
195+
case PART_ID_A02:
196+
dev_dbg(&usbif->dev, "Found good A02 part.\n");
197+
break;
198+
default:
199+
dev_warn(&usbif->dev, "Unknown part ID %04X\n", ntohs(info->part_id));
200+
}
201+
202+
out:
203+
kfree(msg);
204+
return res;
205+
}
206+
207+
static int
208+
cp2615_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
209+
{
210+
struct usb_interface *usbif = adap->algo_data;
211+
int i = 0, ret = 0;
212+
struct i2c_msg *msg;
213+
struct cp2615_i2c_transfer i2c_w = {0};
214+
215+
dev_dbg(&usbif->dev, "Doing %d I2C transactions\n", num);
216+
217+
for (; !ret && i < num; i++) {
218+
msg = &msgs[i];
219+
220+
i2c_w.tag = 0xdd;
221+
i2c_w.i2caddr = i2c_8bit_addr_from_msg(msg);
222+
if (msg->flags & I2C_M_RD) {
223+
i2c_w.read_len = msg->len;
224+
i2c_w.write_len = 0;
225+
} else {
226+
i2c_w.read_len = 0;
227+
i2c_w.write_len = msg->len;
228+
memcpy(&i2c_w.data, msg->buf, i2c_w.write_len);
229+
}
230+
ret = cp2615_i2c_send(usbif, &i2c_w);
231+
if (ret)
232+
break;
233+
ret = cp2615_i2c_recv(usbif, i2c_w.tag, msg->buf);
234+
}
235+
if (ret < 0)
236+
return ret;
237+
return i;
238+
}
239+
240+
static u32
241+
cp2615_i2c_func(struct i2c_adapter *adap)
242+
{
243+
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
244+
}
245+
246+
static const struct i2c_algorithm cp2615_i2c_algo = {
247+
.master_xfer = cp2615_i2c_master_xfer,
248+
.functionality = cp2615_i2c_func,
249+
};
250+
251+
/*
252+
* This chip has some limitations: one is that the USB endpoint
253+
* can only receive 64 bytes/transfer, that leaves 54 bytes for
254+
* the I2C transfer. On top of that, EITHER read_len OR write_len
255+
* may be zero, but not both. If both are non-zero, the adapter
256+
* issues a write followed by a read. And the chip does not
257+
* support repeated START between the write and read phases.
258+
*/
259+
static struct i2c_adapter_quirks cp2615_i2c_quirks = {
260+
.max_write_len = MAX_I2C_SIZE,
261+
.max_read_len = MAX_I2C_SIZE,
262+
.flags = I2C_AQ_COMB_WRITE_THEN_READ | I2C_AQ_NO_ZERO_LEN | I2C_AQ_NO_REP_START,
263+
.max_comb_1st_msg_len = MAX_I2C_SIZE,
264+
.max_comb_2nd_msg_len = MAX_I2C_SIZE
265+
};
266+
267+
static void
268+
cp2615_i2c_remove(struct usb_interface *usbif)
269+
{
270+
struct i2c_adapter *adap = usb_get_intfdata(usbif);
271+
272+
usb_set_intfdata(usbif, NULL);
273+
i2c_del_adapter(adap);
274+
}
275+
276+
static int
277+
cp2615_i2c_probe(struct usb_interface *usbif, const struct usb_device_id *id)
278+
{
279+
int ret = 0;
280+
struct i2c_adapter *adap;
281+
struct usb_device *usbdev = interface_to_usbdev(usbif);
282+
283+
ret = usb_set_interface(usbdev, IOP_IFN, IOP_ALTSETTING);
284+
if (ret)
285+
return ret;
286+
287+
ret = cp2615_check_iop(usbif);
288+
if (ret)
289+
return ret;
290+
291+
adap = devm_kzalloc(&usbif->dev, sizeof(struct i2c_adapter), GFP_KERNEL);
292+
if (!adap)
293+
return -ENOMEM;
294+
295+
strncpy(adap->name, usbdev->serial, sizeof(adap->name) - 1);
296+
adap->owner = THIS_MODULE;
297+
adap->dev.parent = &usbif->dev;
298+
adap->dev.of_node = usbif->dev.of_node;
299+
adap->timeout = HZ;
300+
adap->algo = &cp2615_i2c_algo;
301+
adap->quirks = &cp2615_i2c_quirks;
302+
adap->algo_data = usbif;
303+
304+
ret = i2c_add_adapter(adap);
305+
if (ret)
306+
return ret;
307+
308+
usb_set_intfdata(usbif, adap);
309+
return 0;
310+
}
311+
312+
static const struct usb_device_id id_table[] = {
313+
{ USB_DEVICE_INTERFACE_NUMBER(CP2615_VID, CP2615_PID, IOP_IFN) },
314+
{ }
315+
};
316+
317+
MODULE_DEVICE_TABLE(usb, id_table);
318+
319+
static struct usb_driver cp2615_i2c_driver = {
320+
.name = "i2c-cp2615",
321+
.probe = cp2615_i2c_probe,
322+
.disconnect = cp2615_i2c_remove,
323+
.id_table = id_table,
324+
};
325+
326+
module_usb_driver(cp2615_i2c_driver);
327+
328+
MODULE_AUTHOR("Bence Csókás <[email protected]>");
329+
MODULE_DESCRIPTION("CP2615 I2C bus driver");
330+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)