1 /* $OpenBSD: uslhcom.c,v 1.10 2024/05/23 03:21:09 jsg Exp $ */
2
3 /*
4 * Copyright (c) 2015 SASANO Takayoshi <uaa@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*
20 * Device driver for Silicon Labs CP2110 USB HID-UART bridge.
21 */
22
23 #include <sys/param.h>
24 #include <sys/systm.h>
25 #include <sys/malloc.h>
26 #include <sys/tty.h>
27 #include <sys/device.h>
28
29 #include <dev/usb/usb.h>
30 #include <dev/usb/usbdi.h>
31 #include <dev/usb/usbdevs.h>
32
33 #include <dev/usb/usbhid.h>
34 #include <dev/usb/uhidev.h>
35
36 #include <dev/usb/ucomvar.h>
37 #include <dev/usb/uslhcomreg.h>
38
39 #ifdef USLHCOM_DEBUG
40 #define DPRINTF(x) if (uslhcomdebug) printf x
41 #else
42 #define DPRINTF(x)
43 #endif
44
45 struct uslhcom_softc {
46 struct uhidev sc_hdev;
47 struct usbd_device *sc_udev;
48
49 u_char *sc_ibuf;
50 u_int sc_icnt;
51
52 u_char sc_lsr;
53 u_char sc_msr;
54
55 struct device *sc_subdev;
56 };
57
58 void uslhcom_get_status(void *, int, u_char *, u_char *);
59 void uslhcom_set(void *, int, int, int);
60 int uslhcom_param(void *, int, struct termios *);
61 int uslhcom_open(void *, int);
62 void uslhcom_close(void *, int);
63 void uslhcom_write(void *, int, u_char *, u_char *, u_int32_t *);
64 void uslhcom_read(void *, int, u_char **, u_int32_t *);
65 void uslhcom_intr(struct uhidev *, void *, u_int);
66
67 int uslhcom_match(struct device *, void *, void *);
68 void uslhcom_attach(struct device *, struct device *, void *);
69 int uslhcom_detach(struct device *, int);
70
71 int uslhcom_uart_endis(struct uslhcom_softc *, int);
72 int uslhcom_clear_fifo(struct uslhcom_softc *, int);
73 int uslhcom_get_version(struct uslhcom_softc *, struct uslhcom_version_info *);
74 int uslhcom_get_uart_status(struct uslhcom_softc *, struct uslhcom_uart_status *);
75 int uslhcom_set_break(struct uslhcom_softc *, int);
76 int uslhcom_set_config(struct uslhcom_softc *, struct uslhcom_uart_config *);
77 void uslhcom_set_baud_rate(struct uslhcom_uart_config *, u_int32_t);
78 int uslhcom_create_config(struct uslhcom_uart_config *, struct termios *);
79 int uslhcom_setup(struct uslhcom_softc *, struct uslhcom_uart_config *);
80
81 const struct ucom_methods uslhcom_methods = {
82 uslhcom_get_status,
83 uslhcom_set,
84 uslhcom_param,
85 NULL,
86 uslhcom_open,
87 uslhcom_close,
88 uslhcom_read,
89 uslhcom_write,
90 };
91
92 static const struct usb_devno uslhcom_devs[] = {
93 { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2110 },
94 };
95
96 struct cfdriver uslhcom_cd = {
97 NULL, "uslhcom", DV_DULL
98 };
99
100 const struct cfattach uslhcom_ca = {
101 sizeof(struct uslhcom_softc),
102 uslhcom_match, uslhcom_attach, uslhcom_detach
103 };
104
105 /* ----------------------------------------------------------------------
106 * driver entry points
107 */
108
109 int
uslhcom_match(struct device * parent,void * match,void * aux)110 uslhcom_match(struct device *parent, void *match, void *aux)
111 {
112 struct uhidev_attach_arg *uha = aux;
113
114 /* use all report IDs */
115 if (!UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
116 return UMATCH_NONE;
117
118 return (usb_lookup(uslhcom_devs,
119 uha->uaa->vendor, uha->uaa->product) != NULL ?
120 UMATCH_VENDOR_PRODUCT : UMATCH_NONE);
121 }
122
123 void
uslhcom_attach(struct device * parent,struct device * self,void * aux)124 uslhcom_attach(struct device *parent, struct device *self, void *aux)
125 {
126 struct uslhcom_softc *sc = (struct uslhcom_softc *)self;
127 struct uhidev_attach_arg *uha = aux;
128 struct usbd_device *dev = uha->parent->sc_udev;
129 struct ucom_attach_args uca;
130 struct uslhcom_version_info version;
131 int err, repid, size, rsize;
132 void *desc;
133
134 sc->sc_hdev.sc_intr = uslhcom_intr;
135 sc->sc_hdev.sc_parent = uha->parent;
136 sc->sc_hdev.sc_report_id = uha->reportid;
137
138 uhidev_get_report_desc(uha->parent, &desc, &size);
139 for (repid = 0; repid < uha->parent->sc_nrepid; repid++) {
140 rsize = hid_report_size(desc, size, hid_input, repid);
141 if (sc->sc_hdev.sc_isize < rsize) sc->sc_hdev.sc_isize = rsize;
142 rsize = hid_report_size(desc, size, hid_output, repid);
143 if (sc->sc_hdev.sc_osize < rsize) sc->sc_hdev.sc_osize = rsize;
144 rsize = hid_report_size(desc, size, hid_feature, repid);
145 if (sc->sc_hdev.sc_fsize < rsize) sc->sc_hdev.sc_fsize = rsize;
146 }
147
148 printf("\n");
149
150 sc->sc_udev = dev;
151
152 err = uhidev_open(&sc->sc_hdev);
153 if (err) {
154 DPRINTF(("uslhcom_attach: uhidev_open %d\n", err));
155 return;
156 }
157
158 DPRINTF(("uslhcom_attach: sc %p opipe %p ipipe %p report_id %d\n",
159 sc, sc->sc_hdev.sc_parent->sc_opipe,
160 sc->sc_hdev.sc_parent->sc_ipipe, uha->reportid));
161 DPRINTF(("uslhcom_attach: isize %d osize %d fsize %d\n",
162 sc->sc_hdev.sc_isize, sc->sc_hdev.sc_osize,
163 sc->sc_hdev.sc_fsize));
164
165 uslhcom_uart_endis(sc, UART_DISABLE);
166 uslhcom_get_version(sc, &version);
167 printf("%s: pid %#x rev %#x\n", sc->sc_hdev.sc_dev.dv_xname,
168 version.product_id, version.product_revision);
169
170 /* setup ucom layer */
171 bzero(&uca, sizeof uca);
172 uca.portno = UCOM_UNK_PORTNO;
173 uca.bulkin = uca.bulkout = -1;
174 uca.ibufsize = uca.ibufsizepad = 0;
175 uca.obufsize = sc->sc_hdev.sc_osize;
176 uca.opkthdrlen = USLHCOM_TX_HEADER_SIZE;
177 uca.uhidev = sc->sc_hdev.sc_parent;
178 uca.device = uha->uaa->device;
179 uca.iface = uha->uaa->iface;
180 uca.methods = &uslhcom_methods;
181 uca.arg = sc;
182 uca.info = NULL;
183
184 sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
185 }
186
187 int
uslhcom_detach(struct device * self,int flags)188 uslhcom_detach(struct device *self, int flags)
189 {
190 struct uslhcom_softc *sc = (struct uslhcom_softc *)self;
191
192 DPRINTF(("uslhcom_detach: sc=%p flags=%d\n", sc, flags));
193 if (sc->sc_subdev != NULL) {
194 config_detach(sc->sc_subdev, flags);
195 sc->sc_subdev = NULL;
196 }
197
198 if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
199 uhidev_close(&sc->sc_hdev);
200
201 return 0;
202 }
203
204 /* ----------------------------------------------------------------------
205 * low level I/O
206 */
207
208 int
uslhcom_uart_endis(struct uslhcom_softc * sc,int enable)209 uslhcom_uart_endis(struct uslhcom_softc *sc, int enable)
210 {
211 int len;
212 u_char val;
213
214 len = sizeof(val);
215 val = enable;
216
217 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
218 GET_SET_UART_ENABLE, &val, len) != len;
219 }
220
221 int
uslhcom_clear_fifo(struct uslhcom_softc * sc,int fifo)222 uslhcom_clear_fifo(struct uslhcom_softc *sc, int fifo)
223 {
224 int len;
225 u_char val;
226
227 len = sizeof(val);
228 val = fifo;
229
230 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
231 SET_CLEAR_FIFOS, &val, len) != len;
232 }
233
234 int
uslhcom_get_version(struct uslhcom_softc * sc,struct uslhcom_version_info * version)235 uslhcom_get_version(struct uslhcom_softc *sc, struct uslhcom_version_info *version)
236 {
237 int len;
238
239 len = sizeof(*version);
240
241 return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
242 GET_VERSION, version, len) < len;
243 }
244
245 int
uslhcom_get_uart_status(struct uslhcom_softc * sc,struct uslhcom_uart_status * status)246 uslhcom_get_uart_status(struct uslhcom_softc *sc, struct uslhcom_uart_status *status)
247 {
248 int len;
249
250 len = sizeof(*status);
251
252 return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
253 GET_UART_STATUS, status, len) < len;
254 }
255
256 int
uslhcom_set_break(struct uslhcom_softc * sc,int onoff)257 uslhcom_set_break(struct uslhcom_softc *sc, int onoff)
258 {
259 int len, reportid;
260 u_char val;
261
262 len = sizeof(val);
263
264 if (onoff) {
265 val = 0; /* send break until SET_STOP_LINE_BREAK */
266 reportid = SET_TRANSMIT_LINE_BREAK;
267 } else {
268 val = 0; /* any value can be accepted */
269 reportid = SET_STOP_LINE_BREAK;
270 }
271
272 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
273 reportid, &val, len) != len;
274 }
275
276 int
uslhcom_set_config(struct uslhcom_softc * sc,struct uslhcom_uart_config * config)277 uslhcom_set_config(struct uslhcom_softc *sc, struct uslhcom_uart_config *config)
278 {
279 int len;
280
281 len = sizeof(*config);
282
283 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
284 GET_SET_UART_CONFIG, config, len) != len;
285 }
286
287 void
uslhcom_set_baud_rate(struct uslhcom_uart_config * config,u_int32_t baud_rate)288 uslhcom_set_baud_rate(struct uslhcom_uart_config *config, u_int32_t baud_rate)
289 {
290 config->baud_rate[0] = baud_rate >> 24;
291 config->baud_rate[1] = baud_rate >> 16;
292 config->baud_rate[2] = baud_rate >> 8;
293 config->baud_rate[3] = baud_rate >> 0;
294 }
295
296 int
uslhcom_create_config(struct uslhcom_uart_config * config,struct termios * t)297 uslhcom_create_config(struct uslhcom_uart_config *config, struct termios *t)
298 {
299 if (t->c_ospeed < UART_CONFIG_BAUD_RATE_MIN ||
300 t->c_ospeed > UART_CONFIG_BAUD_RATE_MAX)
301 return EINVAL;
302
303 uslhcom_set_baud_rate(config, t->c_ospeed);
304
305 if (ISSET(t->c_cflag, PARENB)) {
306 if (ISSET(t->c_cflag, PARODD))
307 config->parity = UART_CONFIG_PARITY_ODD;
308 else
309 config->parity = UART_CONFIG_PARITY_EVEN;
310 } else
311 config->parity = UART_CONFIG_PARITY_NONE;
312
313 if (ISSET(t->c_cflag, CRTSCTS))
314 config->data_control = UART_CONFIG_DATA_CONTROL_HARD;
315 else
316 config->data_control = UART_CONFIG_DATA_CONTROL_NONE;
317
318 switch (ISSET(t->c_cflag, CSIZE)) {
319 case CS5:
320 config->data_bits = UART_CONFIG_DATA_BITS_5;
321 break;
322 case CS6:
323 config->data_bits = UART_CONFIG_DATA_BITS_6;
324 break;
325 case CS7:
326 config->data_bits = UART_CONFIG_DATA_BITS_7;
327 break;
328 case CS8:
329 config->data_bits = UART_CONFIG_DATA_BITS_8;
330 break;
331 default:
332 return EINVAL;
333 }
334
335 if (ISSET(t->c_cflag, CSTOPB))
336 config->stop_bits = UART_CONFIG_STOP_BITS_2;
337 else
338 config->stop_bits = UART_CONFIG_STOP_BITS_1;
339
340 return 0;
341 }
342
343 int
uslhcom_setup(struct uslhcom_softc * sc,struct uslhcom_uart_config * config)344 uslhcom_setup(struct uslhcom_softc *sc, struct uslhcom_uart_config *config)
345 {
346 struct uslhcom_uart_status status;
347
348 if (uslhcom_uart_endis(sc, UART_DISABLE))
349 return EIO;
350
351 if (uslhcom_set_config(sc, config))
352 return EIO;
353
354 if (uslhcom_clear_fifo(sc, CLEAR_TX_FIFO | CLEAR_RX_FIFO))
355 return EIO;
356
357 if (uslhcom_get_uart_status(sc, &status))
358 return EIO;
359
360 if (uslhcom_uart_endis(sc, UART_ENABLE))
361 return EIO;
362
363 return 0;
364 }
365
366 /* ----------------------------------------------------------------------
367 * methods for ucom
368 */
369
370 void
uslhcom_get_status(void * arg,int portno,u_char * rlsr,u_char * rmsr)371 uslhcom_get_status(void *arg, int portno, u_char *rlsr, u_char *rmsr)
372 {
373 struct uslhcom_softc *sc = arg;
374
375 if (usbd_is_dying(sc->sc_udev))
376 return;
377
378 *rlsr = sc->sc_lsr;
379 *rmsr = sc->sc_msr;
380 }
381
382 void
uslhcom_set(void * arg,int portno,int reg,int onoff)383 uslhcom_set(void *arg, int portno, int reg, int onoff)
384 {
385 struct uslhcom_softc *sc = arg;
386
387 if (usbd_is_dying(sc->sc_udev))
388 return;
389
390 switch (reg) {
391 case UCOM_SET_DTR:
392 case UCOM_SET_RTS:
393 /* no support, do nothing */
394 break;
395 case UCOM_SET_BREAK:
396 uslhcom_set_break(sc, onoff);
397 break;
398 }
399 }
400
401 int
uslhcom_param(void * arg,int portno,struct termios * t)402 uslhcom_param(void *arg, int portno, struct termios *t)
403 {
404 struct uslhcom_softc *sc = arg;
405 struct uslhcom_uart_config config;
406 int ret;
407
408 if (usbd_is_dying(sc->sc_udev))
409 return 0;
410
411 ret = uslhcom_create_config(&config, t);
412 if (ret)
413 return ret;
414
415 ret = uslhcom_setup(sc, &config);
416 if (ret)
417 return ret;
418
419 return 0;
420 }
421
422 int
uslhcom_open(void * arg,int portno)423 uslhcom_open(void *arg, int portno)
424 {
425 struct uslhcom_softc *sc = arg;
426 struct uslhcom_uart_config config;
427 int ret;
428
429 if (usbd_is_dying(sc->sc_udev))
430 return EIO;
431
432 sc->sc_ibuf = malloc(sc->sc_hdev.sc_isize, M_USBDEV, M_WAITOK);
433
434 uslhcom_set_baud_rate(&config, 9600);
435 config.parity = UART_CONFIG_PARITY_NONE;
436 config.data_control = UART_CONFIG_DATA_CONTROL_NONE;
437 config.data_bits = UART_CONFIG_DATA_BITS_8;
438 config.stop_bits = UART_CONFIG_STOP_BITS_1;
439
440 ret = uslhcom_set_config(sc, &config);
441 if (ret)
442 return ret;
443
444 return 0;
445 }
446
447 void
uslhcom_close(void * arg,int portno)448 uslhcom_close(void *arg, int portno)
449 {
450 struct uslhcom_softc *sc = arg;
451 int s;
452
453 if (usbd_is_dying(sc->sc_udev))
454 return;
455
456 uslhcom_uart_endis(sc, UART_DISABLE);
457
458 s = splusb();
459 if (sc->sc_ibuf != NULL) {
460 free(sc->sc_ibuf, M_USBDEV, sc->sc_hdev.sc_isize);
461 sc->sc_ibuf = NULL;
462 }
463 splx(s);
464 }
465
466 void
uslhcom_read(void * arg,int portno,u_char ** ptr,u_int32_t * cnt)467 uslhcom_read(void *arg, int portno, u_char **ptr, u_int32_t *cnt)
468 {
469 struct uslhcom_softc *sc = arg;
470
471 *ptr = sc->sc_ibuf;
472 *cnt = sc->sc_icnt;
473 }
474
475 void
uslhcom_write(void * arg,int portno,u_char * to,u_char * data,u_int32_t * cnt)476 uslhcom_write(void *arg, int portno, u_char *to, u_char *data, u_int32_t *cnt)
477 {
478 bcopy(data, &to[USLHCOM_TX_HEADER_SIZE], *cnt);
479 to[0] = *cnt; /* add Report ID (= transmit length) */
480 *cnt += USLHCOM_TX_HEADER_SIZE;
481 }
482
483 void
uslhcom_intr(struct uhidev * addr,void * ibuf,u_int len)484 uslhcom_intr(struct uhidev *addr, void *ibuf, u_int len)
485 {
486 extern void ucomreadcb(struct usbd_xfer *, void *, usbd_status);
487 struct uslhcom_softc *sc = (struct uslhcom_softc *)addr;
488 int s;
489
490 if (sc->sc_ibuf == NULL)
491 return;
492
493 s = spltty();
494 sc->sc_icnt = len; /* Report ID is already stripped */
495 bcopy(ibuf, sc->sc_ibuf, len);
496 ucomreadcb(addr->sc_parent->sc_ixfer, sc->sc_subdev,
497 USBD_NORMAL_COMPLETION);
498 splx(s);
499 }
500