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