xref: /openbsd/sys/dev/usb/uslhcom.c (revision 81508fe3)
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