]>
Commit | Line | Data |
---|---|---|
e4204b17 UH |
1 | /* |
2 | * This file is part of the libsigrok project. | |
3 | * | |
4 | * Copyright (C) 2017 Carl-Fredrik Sundström <audio.cf@gmail.com> | |
5 | * Copyright (C) 2017-2019 Gerhard Sittig <gerhard.sittig@gmx.net> | |
6 | * | |
7 | * This program is free software: you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation, either version 3 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | */ | |
20 | ||
21 | #include <config.h> | |
22 | #include <glib.h> | |
23 | #include <libsigrok/libsigrok.h> | |
24 | #include "libsigrok-internal.h" | |
25 | #include "serial_hid.h" | |
26 | #include <string.h> | |
27 | ||
28 | /** @cond PRIVATE */ | |
29 | #define LOG_PREFIX "serial-cp2110" | |
30 | /** @endcond */ | |
31 | ||
32 | #ifdef HAVE_SERIAL_COMM | |
33 | #ifdef HAVE_LIBHIDAPI | |
34 | ||
35 | /** | |
36 | * @file | |
37 | * | |
38 | * Support serial-over-HID, specifically the SiLabs CP2110 chip. | |
39 | */ | |
40 | ||
41 | #define CP2110_MAX_BYTES_PER_REQUEST 63 | |
42 | ||
43 | static const struct vid_pid_item vid_pid_items_cp2110[] = { | |
44 | { 0x10c4, 0xea80, }, | |
45 | ALL_ZERO | |
46 | }; | |
47 | ||
48 | enum cp2110_report_id { | |
49 | CP2110_UART_ENDIS = 0x41, | |
50 | CP2110_UART_STATUS = 0x42, | |
51 | CP2110_FIFO_PURGE = 0x43, | |
52 | CP2110_UART_CONFIG = 0x50, | |
53 | }; | |
54 | ||
55 | enum cp2110_uart_enable_param { | |
56 | CP2110_UART_DISABLE = 0, | |
57 | CP2110_UART_ENABLE = 1, | |
58 | }; | |
59 | ||
60 | enum cp2110_fifo_purge_flag { | |
61 | CP2110_FIFO_PURGE_TX = (1 << 0), | |
62 | CP2110_FIFO_PURGE_RX = (1 << 1), | |
63 | }; | |
64 | ||
65 | enum cp2110_uart_config_bitrate { | |
66 | CP2110_BAUDRATE_MIN = 300, | |
67 | CP2110_BAUDRATE_MAX = 1000000, | |
68 | }; | |
69 | ||
70 | enum cp2110_uart_config_databits { | |
71 | CP2110_DATABITS_MIN = 5, | |
72 | CP2110_DATABITS_MAX = 8, | |
73 | }; | |
74 | ||
75 | enum cp2110_uart_config_parity { | |
76 | CP2110_PARITY_NONE = 0, | |
77 | CP2110_PARITY_EVEN = 1, | |
78 | CP2110_PARITY_ODD = 2, | |
79 | CP2110_PARITY_MARK = 3, | |
80 | CP2110_PARITY_SPACE = 4, | |
81 | }; | |
82 | ||
83 | enum cp2110_uart_config_stopbits { | |
84 | CP2110_STOPBITS_SHORT = 0, | |
85 | CP2110_STOPBITS_LONG = 1, | |
86 | }; | |
87 | ||
88 | /* Hardware flow control on CP2110 is RTS/CTS only. */ | |
89 | enum cp2110_uart_config_flowctrl { | |
90 | CP2110_FLOWCTRL_NONE = 0, | |
91 | CP2110_FLOWCTRL_HARD = 1, | |
92 | }; | |
93 | ||
94 | static int cp2110_set_params(struct sr_serial_dev_inst *serial, | |
95 | int baudrate, int bits, int parity, int stopbits, | |
96 | int flowcontrol, int rts, int dtr) | |
97 | { | |
98 | uint8_t report[9]; | |
99 | int replen; | |
100 | int rc; | |
101 | ||
102 | /* Map serial API specs to CP2110 register values. Check ranges. */ | |
103 | if (baudrate < CP2110_BAUDRATE_MIN || baudrate > CP2110_BAUDRATE_MAX) { | |
104 | sr_err("CP2110: baudrate %d out of range", baudrate); | |
105 | return SR_ERR_ARG; | |
106 | } | |
107 | if (bits < CP2110_DATABITS_MIN || bits > CP2110_DATABITS_MAX) { | |
108 | sr_err("CP2110: %d databits out of range", bits); | |
109 | return SR_ERR_ARG; | |
110 | } | |
111 | bits -= CP2110_DATABITS_MIN; | |
112 | switch (parity) { | |
113 | case SP_PARITY_NONE: | |
114 | parity = CP2110_PARITY_NONE; | |
115 | break; | |
116 | case SP_PARITY_ODD: | |
117 | parity = CP2110_PARITY_ODD; | |
118 | break; | |
119 | case SP_PARITY_EVEN: | |
120 | parity = CP2110_PARITY_EVEN; | |
121 | break; | |
122 | case SP_PARITY_MARK: | |
123 | parity = CP2110_PARITY_MARK; | |
124 | break; | |
125 | case SP_PARITY_SPACE: | |
126 | parity = CP2110_PARITY_SPACE; | |
127 | break; | |
128 | default: | |
129 | sr_err("CP2110: unknown parity spec %d", parity); | |
130 | return SR_ERR_ARG; | |
131 | } | |
132 | switch (stopbits) { | |
133 | case 1: | |
134 | stopbits = CP2110_STOPBITS_SHORT; | |
135 | break; | |
136 | case 2: | |
137 | stopbits = CP2110_STOPBITS_LONG; | |
138 | break; | |
139 | default: | |
140 | sr_err("CP2110: unknown stop bits spec %d", stopbits); | |
141 | return SR_ERR_ARG; | |
142 | } | |
143 | switch (flowcontrol) { | |
144 | case SP_FLOWCONTROL_NONE: | |
145 | flowcontrol = CP2110_FLOWCTRL_NONE; | |
146 | break; | |
147 | case SP_FLOWCONTROL_XONXOFF: | |
148 | sr_err("CP2110: unsupported XON/XOFF flow control spec"); | |
149 | return SR_ERR_ARG; | |
150 | case SP_FLOWCONTROL_RTSCTS: | |
151 | flowcontrol = CP2110_FLOWCTRL_HARD; | |
152 | break; | |
153 | default: | |
154 | sr_err("CP2110: unknown flow control spec %d", flowcontrol); | |
155 | return SR_ERR_ARG; | |
156 | } | |
157 | ||
158 | /* | |
159 | * Enable the UART. Report layout: | |
160 | * @0, length 1, enabled state (0: disable, 1: enable) | |
161 | */ | |
162 | replen = 0; | |
163 | report[replen++] = CP2110_UART_ENDIS; | |
164 | report[replen++] = CP2110_UART_ENABLE; | |
165 | rc = ser_hid_hidapi_set_report(serial, report, replen); | |
166 | if (rc < 0) | |
167 | return SR_ERR; | |
168 | if (rc != replen) | |
169 | return SR_ERR; | |
170 | ||
171 | /* | |
172 | * Setup bitrate and frame format. Report layout: | |
173 | * (@-1, length 1, report number) | |
174 | * @0, length 4, bitrate (big endian format) | |
175 | * @4, length 1, parity | |
176 | * @5, length 1, flow control | |
177 | * @6, length 1, data bits (0: 5, 1: 6, 2: 7, 3: 8) | |
178 | * @7, length 1, stop bits | |
179 | */ | |
180 | replen = 0; | |
181 | report[replen++] = CP2110_UART_CONFIG; | |
182 | WB32(&report[replen], baudrate); | |
183 | replen += sizeof(uint32_t); | |
184 | report[replen++] = parity; | |
185 | report[replen++] = flowcontrol; | |
186 | report[replen++] = bits; | |
187 | report[replen++] = stopbits; | |
188 | rc = ser_hid_hidapi_set_report(serial, report, replen); | |
189 | if (rc < 0) | |
190 | return SR_ERR; | |
191 | if (rc != replen) | |
192 | return SR_ERR; | |
193 | ||
194 | /* | |
195 | * Currently not implemented: Control RTS and DTR state. | |
196 | * TODO are these controlled via GPIO requests? | |
197 | * GPIO.1 == RTS, can't find DTR in AN433 table 4.3 | |
198 | */ | |
199 | (void)rts; | |
200 | (void)dtr; | |
201 | ||
202 | return SR_OK; | |
203 | } | |
204 | ||
205 | static int cp2110_read_bytes(struct sr_serial_dev_inst *serial, | |
206 | uint8_t *data, int space, unsigned int timeout) | |
207 | { | |
208 | uint8_t buffer[1 + CP2110_MAX_BYTES_PER_REQUEST]; | |
209 | int rc; | |
210 | int count; | |
211 | ||
212 | (void)timeout; | |
213 | ||
214 | /* | |
215 | * Check for available input data from the serial port. | |
216 | * Packet layout: | |
217 | * @0, length 1, number of bytes, range 0-63 | |
218 | * @1, length N, data bytes | |
219 | */ | |
220 | memset(buffer, 0, sizeof(buffer)); | |
221 | rc = ser_hid_hidapi_get_data(serial, 0, buffer, sizeof(buffer), timeout); | |
222 | if (rc == SR_ERR_TIMEOUT) | |
223 | return 0; | |
224 | if (rc < 0) | |
225 | return SR_ERR; | |
226 | if (rc == 0) | |
227 | return 0; | |
228 | sr_dbg("DBG: %s() got report len %d, 0x%02x.", __func__, rc, buffer[0]); | |
229 | ||
230 | /* Check the length spec, get the byte count. */ | |
231 | count = buffer[0]; | |
232 | if (!count) | |
233 | return 0; | |
234 | if (count > CP2110_MAX_BYTES_PER_REQUEST) | |
235 | return SR_ERR; | |
236 | sr_dbg("DBG: %s(), got %d UART RX bytes.", __func__, count); | |
237 | if (count > space) | |
238 | return SR_ERR; | |
239 | ||
240 | /* Pass received data bytes and their count to the caller. */ | |
241 | memcpy(data, &buffer[1], count); | |
242 | return count; | |
243 | } | |
244 | ||
245 | static int cp2110_write_bytes(struct sr_serial_dev_inst *serial, | |
246 | const uint8_t *data, int size) | |
247 | { | |
248 | uint8_t buffer[1 + CP2110_MAX_BYTES_PER_REQUEST]; | |
249 | int rc; | |
250 | ||
251 | sr_dbg("DBG: %s() shall send UART TX data, len %d.", __func__, size); | |
252 | ||
253 | if (size < 1) | |
254 | return 0; | |
255 | if (size > CP2110_MAX_BYTES_PER_REQUEST) { | |
256 | size = CP2110_MAX_BYTES_PER_REQUEST; | |
257 | sr_dbg("DBG: %s() capping size to %d.", __func__, size); | |
258 | } | |
259 | ||
260 | /* | |
261 | * Packet layout to send serial data to the USB HID chip: | |
262 | * @0, length 1, number of bytes, range 0-63 | |
263 | * @1, length N, data bytes | |
264 | */ | |
265 | buffer[0] = size; | |
266 | memcpy(&buffer[1], data, size); | |
267 | rc = ser_hid_hidapi_set_data(serial, 0, buffer, sizeof(buffer), 0); | |
268 | if (rc < 0) | |
269 | return rc; | |
270 | if (rc == 0) | |
271 | return 0; | |
272 | return size; | |
273 | } | |
274 | ||
275 | static int cp2110_flush(struct sr_serial_dev_inst *serial) | |
276 | { | |
277 | uint8_t buffer[2]; | |
278 | int rc; | |
279 | ||
280 | sr_dbg("DBG: %s() discarding RX and TX FIFO data.", __func__); | |
281 | ||
282 | buffer[0] = CP2110_FIFO_PURGE; | |
283 | buffer[1] = CP2110_FIFO_PURGE_TX | CP2110_FIFO_PURGE_RX; | |
284 | rc = ser_hid_hidapi_set_data(serial, 0, buffer, sizeof(buffer), 0); | |
285 | if (rc != 0) | |
286 | return SR_ERR; | |
287 | return SR_OK; | |
288 | } | |
289 | ||
290 | static int cp2110_drain(struct sr_serial_dev_inst *serial) | |
291 | { | |
292 | uint8_t buffer[7]; | |
293 | int rc; | |
294 | uint16_t tx_fill, rx_fill; | |
295 | ||
296 | sr_dbg("DBG: %s() waiting for TX data to drain.", __func__); | |
297 | ||
298 | /* | |
299 | * Keep retrieving the UART status until the FIFO is found empty, | |
300 | * or an error occured. | |
301 | * Packet layout: | |
302 | * @0, length 1, report ID | |
303 | * @1, length 2, number of bytes in the TX FIFO (up to 480) | |
304 | * @3, length 2, number of bytes in the RX FIFO (up to 480) | |
305 | * @5, length 1, error status (parity and overrun error flags) | |
306 | * @6, length 1, line break status | |
307 | */ | |
308 | rx_fill = ~0; | |
309 | do { | |
310 | memset(buffer, 0, sizeof(buffer)); | |
311 | buffer[0] = CP2110_UART_STATUS; | |
312 | rc = ser_hid_hidapi_get_data(serial, 0, buffer, sizeof(buffer), 0); | |
313 | if (rc != sizeof(buffer)) { | |
314 | rc = SR_ERR_DATA; | |
315 | break; | |
316 | } | |
317 | if (buffer[0] != CP2110_UART_STATUS) { | |
318 | rc = SR_ERR_DATA; | |
319 | break; | |
320 | } | |
321 | rx_fill = RB16(&buffer[1]); | |
322 | tx_fill = RB16(&buffer[3]); | |
323 | if (!tx_fill) { | |
324 | rc = SR_OK; | |
325 | break; | |
326 | } | |
327 | g_usleep(2000); | |
328 | } while (1); | |
329 | ||
330 | sr_dbg("DBG: %s() TX drained, rc %d, RX fill %u, returning.", | |
331 | __func__, rc, (unsigned int)rx_fill); | |
332 | return rc; | |
333 | } | |
334 | ||
335 | static struct ser_hid_chip_functions chip_cp2110 = { | |
336 | .chipname = "cp2110", | |
337 | .chipdesc = "SiLabs CP2110", | |
338 | .vid_pid_items = vid_pid_items_cp2110, | |
339 | .max_bytes_per_request = CP2110_MAX_BYTES_PER_REQUEST, | |
340 | .set_params = cp2110_set_params, | |
341 | .read_bytes = cp2110_read_bytes, | |
342 | .write_bytes = cp2110_write_bytes, | |
343 | .flush = cp2110_flush, | |
344 | .drain = cp2110_drain, | |
345 | }; | |
346 | SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_cp2110 = &chip_cp2110; | |
347 | ||
348 | #else | |
349 | ||
350 | SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_cp2110 = NULL; | |
351 | ||
352 | #endif | |
353 | #endif |