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