xref: /freebsd/sys/dev/usb/serial/ucycom.c (revision 39beb93c3f8bdbf72a61fda42300b5ebed7390c8)
1 #include <sys/cdefs.h>
2 __FBSDID("$FreeBSD$");
3 
4 /*-
5  * Copyright (c) 2004 Dag-Erling Co�dan Sm�rgrav
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer
13  *    in this position and unchanged.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. The name of the author may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to
34  * RS232 bridges.
35  */
36 
37 #include "usbdevs.h"
38 #include <dev/usb/usb.h>
39 #include <dev/usb/usb_mfunc.h>
40 #include <dev/usb/usb_error.h>
41 #include <dev/usb/usb_cdc.h>
42 #include <dev/usb/usb_ioctl.h>
43 #include <dev/usb/usbhid.h>
44 
45 #define	USB_DEBUG_VAR usb2_debug
46 
47 #include <dev/usb/usb_core.h>
48 #include <dev/usb/usb_debug.h>
49 #include <dev/usb/usb_process.h>
50 #include <dev/usb/usb_request.h>
51 #include <dev/usb/usb_lookup.h>
52 #include <dev/usb/usb_util.h>
53 #include <dev/usb/usb_busdma.h>
54 #include <dev/usb/usb_hid.h>
55 
56 #include <dev/usb/serial/usb_serial.h>
57 
58 #define	UCYCOM_MAX_IOLEN	(1024 + 2)	/* bytes */
59 
60 #define	UCYCOM_IFACE_INDEX	0
61 
62 enum {
63 	UCYCOM_CTRL_RD,
64 	UCYCOM_INTR_RD,
65 	UCYCOM_N_TRANSFER,
66 };
67 
68 struct ucycom_softc {
69 	struct usb2_com_super_softc sc_super_ucom;
70 	struct usb2_com_softc sc_ucom;
71 
72 	struct usb2_device *sc_udev;
73 	struct usb2_xfer *sc_xfer[UCYCOM_N_TRANSFER];
74 
75 	uint32_t sc_model;
76 #define	MODEL_CY7C63743		0x63743
77 #define	MODEL_CY7C64013		0x64013
78 
79 	uint16_t sc_flen;		/* feature report length */
80 	uint16_t sc_ilen;		/* input report length */
81 	uint16_t sc_olen;		/* output report length */
82 
83 	uint8_t	sc_fid;			/* feature report id */
84 	uint8_t	sc_iid;			/* input report id */
85 	uint8_t	sc_oid;			/* output report id */
86 	uint8_t	sc_cfg;
87 #define	UCYCOM_CFG_RESET	0x80
88 #define	UCYCOM_CFG_PARODD	0x20
89 #define	UCYCOM_CFG_PAREN	0x10
90 #define	UCYCOM_CFG_STOPB	0x08
91 #define	UCYCOM_CFG_DATAB	0x03
92 	uint8_t	sc_ist;			/* status flags from last input */
93 	uint8_t	sc_name[16];
94 	uint8_t	sc_iface_no;
95 	uint8_t	sc_temp_cfg[32];
96 };
97 
98 /* prototypes */
99 
100 static device_probe_t ucycom_probe;
101 static device_attach_t ucycom_attach;
102 static device_detach_t ucycom_detach;
103 
104 static usb2_callback_t ucycom_ctrl_write_callback;
105 static usb2_callback_t ucycom_intr_read_callback;
106 
107 static void	ucycom_cfg_open(struct usb2_com_softc *);
108 static void	ucycom_start_read(struct usb2_com_softc *);
109 static void	ucycom_stop_read(struct usb2_com_softc *);
110 static void	ucycom_start_write(struct usb2_com_softc *);
111 static void	ucycom_stop_write(struct usb2_com_softc *);
112 static void	ucycom_cfg_write(struct ucycom_softc *, uint32_t, uint8_t);
113 static int	ucycom_pre_param(struct usb2_com_softc *, struct termios *);
114 static void	ucycom_cfg_param(struct usb2_com_softc *, struct termios *);
115 
116 static const struct usb2_config ucycom_config[UCYCOM_N_TRANSFER] = {
117 
118 	[UCYCOM_CTRL_RD] = {
119 		.type = UE_CONTROL,
120 		.endpoint = 0x00,	/* Control pipe */
121 		.direction = UE_DIR_ANY,
122 		.mh.bufsize = (sizeof(struct usb2_device_request) + UCYCOM_MAX_IOLEN),
123 		.mh.flags = {},
124 		.mh.callback = &ucycom_ctrl_write_callback,
125 		.mh.timeout = 1000,	/* 1 second */
126 	},
127 
128 	[UCYCOM_INTR_RD] = {
129 		.type = UE_INTERRUPT,
130 		.endpoint = UE_ADDR_ANY,
131 		.direction = UE_DIR_IN,
132 		.mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
133 		.mh.bufsize = UCYCOM_MAX_IOLEN,
134 		.mh.callback = &ucycom_intr_read_callback,
135 	},
136 };
137 
138 static const struct usb2_com_callback ucycom_callback = {
139 	.usb2_com_cfg_param = &ucycom_cfg_param,
140 	.usb2_com_cfg_open = &ucycom_cfg_open,
141 	.usb2_com_pre_param = &ucycom_pre_param,
142 	.usb2_com_start_read = &ucycom_start_read,
143 	.usb2_com_stop_read = &ucycom_stop_read,
144 	.usb2_com_start_write = &ucycom_start_write,
145 	.usb2_com_stop_write = &ucycom_stop_write,
146 };
147 
148 static device_method_t ucycom_methods[] = {
149 	DEVMETHOD(device_probe, ucycom_probe),
150 	DEVMETHOD(device_attach, ucycom_attach),
151 	DEVMETHOD(device_detach, ucycom_detach),
152 	{0, 0}
153 };
154 
155 static devclass_t ucycom_devclass;
156 
157 static driver_t ucycom_driver = {
158 	.name = "ucycom",
159 	.methods = ucycom_methods,
160 	.size = sizeof(struct ucycom_softc),
161 };
162 
163 DRIVER_MODULE(ucycom, ushub, ucycom_driver, ucycom_devclass, NULL, 0);
164 MODULE_DEPEND(ucycom, ucom, 1, 1, 1);
165 MODULE_DEPEND(ucycom, usb, 1, 1, 1);
166 
167 /*
168  * Supported devices
169  */
170 static const struct usb2_device_id ucycom_devs[] = {
171 	{USB_VPI(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013)},
172 };
173 
174 #define	UCYCOM_DEFAULT_RATE	 4800
175 #define	UCYCOM_DEFAULT_CFG	 0x03	/* N-8-1 */
176 
177 static int
178 ucycom_probe(device_t dev)
179 {
180 	struct usb2_attach_arg *uaa = device_get_ivars(dev);
181 
182 	if (uaa->usb2_mode != USB_MODE_HOST) {
183 		return (ENXIO);
184 	}
185 	if (uaa->info.bConfigIndex != 0) {
186 		return (ENXIO);
187 	}
188 	if (uaa->info.bIfaceIndex != UCYCOM_IFACE_INDEX) {
189 		return (ENXIO);
190 	}
191 	return (usb2_lookup_id_by_uaa(ucycom_devs, sizeof(ucycom_devs), uaa));
192 }
193 
194 static int
195 ucycom_attach(device_t dev)
196 {
197 	struct usb2_attach_arg *uaa = device_get_ivars(dev);
198 	struct ucycom_softc *sc = device_get_softc(dev);
199 	void *urd_ptr = NULL;
200 	int32_t error;
201 	uint16_t urd_len;
202 	uint8_t iface_index;
203 
204 	sc->sc_udev = uaa->device;
205 
206 	device_set_usb2_desc(dev);
207 
208 	snprintf(sc->sc_name, sizeof(sc->sc_name),
209 	    "%s", device_get_nameunit(dev));
210 
211 	DPRINTF("\n");
212 
213 	/* get chip model */
214 	sc->sc_model = USB_GET_DRIVER_INFO(uaa);
215 	if (sc->sc_model == 0) {
216 		device_printf(dev, "unsupported device\n");
217 		goto detach;
218 	}
219 	device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model);
220 
221 	/* get report descriptor */
222 
223 	error = usb2_req_get_hid_desc
224 	    (uaa->device, &Giant,
225 	    &urd_ptr, &urd_len, M_USBDEV,
226 	    UCYCOM_IFACE_INDEX);
227 
228 	if (error) {
229 		device_printf(dev, "failed to get report "
230 		    "descriptor: %s\n",
231 		    usb2_errstr(error));
232 		goto detach;
233 	}
234 	/* get report sizes */
235 
236 	sc->sc_flen = hid_report_size(urd_ptr, urd_len, hid_feature, &sc->sc_fid);
237 	sc->sc_ilen = hid_report_size(urd_ptr, urd_len, hid_input, &sc->sc_iid);
238 	sc->sc_olen = hid_report_size(urd_ptr, urd_len, hid_output, &sc->sc_oid);
239 
240 	if ((sc->sc_ilen > UCYCOM_MAX_IOLEN) || (sc->sc_ilen < 1) ||
241 	    (sc->sc_olen > UCYCOM_MAX_IOLEN) || (sc->sc_olen < 2) ||
242 	    (sc->sc_flen > UCYCOM_MAX_IOLEN) || (sc->sc_flen < 5)) {
243 		device_printf(dev, "invalid report size i=%d, o=%d, f=%d, max=%d\n",
244 		    sc->sc_ilen, sc->sc_olen, sc->sc_flen,
245 		    UCYCOM_MAX_IOLEN);
246 		goto detach;
247 	}
248 	sc->sc_iface_no = uaa->info.bIfaceNum;
249 
250 	iface_index = UCYCOM_IFACE_INDEX;
251 	error = usb2_transfer_setup(uaa->device, &iface_index,
252 	    sc->sc_xfer, ucycom_config, UCYCOM_N_TRANSFER,
253 	    sc, &Giant);
254 	if (error) {
255 		device_printf(dev, "allocating USB "
256 		    "transfers failed!\n");
257 		goto detach;
258 	}
259 	error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
260 	    &ucycom_callback, &Giant);
261 
262 	if (error) {
263 		goto detach;
264 	}
265 	if (urd_ptr) {
266 		free(urd_ptr, M_USBDEV);
267 	}
268 	return (0);			/* success */
269 
270 detach:
271 	if (urd_ptr) {
272 		free(urd_ptr, M_USBDEV);
273 	}
274 	ucycom_detach(dev);
275 	return (ENXIO);
276 }
277 
278 static int
279 ucycom_detach(device_t dev)
280 {
281 	struct ucycom_softc *sc = device_get_softc(dev);
282 
283 	usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1);
284 
285 	usb2_transfer_unsetup(sc->sc_xfer, UCYCOM_N_TRANSFER);
286 
287 	return (0);
288 }
289 
290 static void
291 ucycom_cfg_open(struct usb2_com_softc *ucom)
292 {
293 	struct ucycom_softc *sc = ucom->sc_parent;
294 
295 	/* set default configuration */
296 	ucycom_cfg_write(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG);
297 }
298 
299 static void
300 ucycom_start_read(struct usb2_com_softc *ucom)
301 {
302 	struct ucycom_softc *sc = ucom->sc_parent;
303 
304 	usb2_transfer_start(sc->sc_xfer[UCYCOM_INTR_RD]);
305 }
306 
307 static void
308 ucycom_stop_read(struct usb2_com_softc *ucom)
309 {
310 	struct ucycom_softc *sc = ucom->sc_parent;
311 
312 	usb2_transfer_stop(sc->sc_xfer[UCYCOM_INTR_RD]);
313 }
314 
315 static void
316 ucycom_start_write(struct usb2_com_softc *ucom)
317 {
318 	struct ucycom_softc *sc = ucom->sc_parent;
319 
320 	usb2_transfer_start(sc->sc_xfer[UCYCOM_CTRL_RD]);
321 }
322 
323 static void
324 ucycom_stop_write(struct usb2_com_softc *ucom)
325 {
326 	struct ucycom_softc *sc = ucom->sc_parent;
327 
328 	usb2_transfer_stop(sc->sc_xfer[UCYCOM_CTRL_RD]);
329 }
330 
331 static void
332 ucycom_ctrl_write_callback(struct usb2_xfer *xfer)
333 {
334 	struct ucycom_softc *sc = xfer->priv_sc;
335 	struct usb2_device_request req;
336 	uint8_t data[2];
337 	uint8_t offset;
338 	uint32_t actlen;
339 
340 	switch (USB_GET_STATE(xfer)) {
341 	case USB_ST_TRANSFERRED:
342 tr_transferred:
343 	case USB_ST_SETUP:
344 
345 		switch (sc->sc_model) {
346 		case MODEL_CY7C63743:
347 			offset = 1;
348 			break;
349 		case MODEL_CY7C64013:
350 			offset = 2;
351 			break;
352 		default:
353 			offset = 0;
354 			break;
355 		}
356 
357 		if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers + 1, offset,
358 		    sc->sc_olen - offset, &actlen)) {
359 
360 			req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
361 			req.bRequest = UR_SET_REPORT;
362 			USETW2(req.wValue, UHID_OUTPUT_REPORT, sc->sc_oid);
363 			req.wIndex[0] = sc->sc_iface_no;
364 			req.wIndex[1] = 0;
365 			USETW(req.wLength, sc->sc_olen);
366 
367 			switch (sc->sc_model) {
368 			case MODEL_CY7C63743:
369 				data[0] = actlen;
370 				break;
371 			case MODEL_CY7C64013:
372 				data[0] = 0;
373 				data[1] = actlen;
374 				break;
375 			default:
376 				break;
377 			}
378 
379 			usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req));
380 			usb2_copy_in(xfer->frbuffers + 1, 0, data, offset);
381 
382 			xfer->frlengths[0] = sizeof(req);
383 			xfer->frlengths[1] = sc->sc_olen;
384 			xfer->nframes = xfer->frlengths[1] ? 2 : 1;
385 			usb2_start_hardware(xfer);
386 		}
387 		return;
388 
389 	default:			/* Error */
390 		if (xfer->error == USB_ERR_CANCELLED) {
391 			return;
392 		}
393 		DPRINTF("error=%s\n",
394 		    usb2_errstr(xfer->error));
395 		goto tr_transferred;
396 	}
397 }
398 
399 static void
400 ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg)
401 {
402 	struct usb2_device_request req;
403 	uint16_t len;
404 	usb2_error_t err;
405 
406 	len = sc->sc_flen;
407 	if (len > sizeof(sc->sc_temp_cfg)) {
408 		len = sizeof(sc->sc_temp_cfg);
409 	}
410 	sc->sc_cfg = cfg;
411 
412 	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
413 	req.bRequest = UR_SET_REPORT;
414 	USETW2(req.wValue, UHID_FEATURE_REPORT, sc->sc_fid);
415 	req.wIndex[0] = sc->sc_iface_no;
416 	req.wIndex[1] = 0;
417 	USETW(req.wLength, len);
418 
419 	sc->sc_temp_cfg[0] = (baud & 0xff);
420 	sc->sc_temp_cfg[1] = (baud >> 8) & 0xff;
421 	sc->sc_temp_cfg[2] = (baud >> 16) & 0xff;
422 	sc->sc_temp_cfg[3] = (baud >> 24) & 0xff;
423 	sc->sc_temp_cfg[4] = cfg;
424 
425 	err = usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
426 	    &req, sc->sc_temp_cfg, 0, 1000);
427 	if (err) {
428 		DPRINTFN(0, "device request failed, err=%s "
429 		    "(ignored)\n", usb2_errstr(err));
430 	}
431 }
432 
433 static int
434 ucycom_pre_param(struct usb2_com_softc *ucom, struct termios *t)
435 {
436 	switch (t->c_ospeed) {
437 		case 600:
438 		case 1200:
439 		case 2400:
440 		case 4800:
441 		case 9600:
442 		case 19200:
443 		case 38400:
444 		case 57600:
445 #if 0
446 		/*
447 		 * Stock chips only support standard baud rates in the 600 - 57600
448 		 * range, but higher rates can be achieved using custom firmware.
449 		 */
450 		case 115200:
451 		case 153600:
452 		case 192000:
453 #endif
454 		break;
455 	default:
456 		return (EINVAL);
457 	}
458 	return (0);
459 }
460 
461 static void
462 ucycom_cfg_param(struct usb2_com_softc *ucom, struct termios *t)
463 {
464 	struct ucycom_softc *sc = ucom->sc_parent;
465 	uint8_t cfg;
466 
467 	DPRINTF("\n");
468 
469 	if (t->c_cflag & CIGNORE) {
470 		cfg = sc->sc_cfg;
471 	} else {
472 		cfg = 0;
473 		switch (t->c_cflag & CSIZE) {
474 		default:
475 		case CS8:
476 			++cfg;
477 		case CS7:
478 			++cfg;
479 		case CS6:
480 			++cfg;
481 		case CS5:
482 			break;
483 		}
484 
485 		if (t->c_cflag & CSTOPB)
486 			cfg |= UCYCOM_CFG_STOPB;
487 		if (t->c_cflag & PARENB)
488 			cfg |= UCYCOM_CFG_PAREN;
489 		if (t->c_cflag & PARODD)
490 			cfg |= UCYCOM_CFG_PARODD;
491 	}
492 
493 	ucycom_cfg_write(sc, t->c_ospeed, cfg);
494 }
495 
496 static void
497 ucycom_intr_read_callback(struct usb2_xfer *xfer)
498 {
499 	struct ucycom_softc *sc = xfer->priv_sc;
500 	uint8_t buf[2];
501 	uint32_t offset;
502 	uint32_t len;
503 
504 	switch (USB_GET_STATE(xfer)) {
505 	case USB_ST_TRANSFERRED:
506 		switch (sc->sc_model) {
507 		case MODEL_CY7C63743:
508 			if (xfer->actlen < 1) {
509 				goto tr_setup;
510 			}
511 			usb2_copy_out(xfer->frbuffers, 0, buf, 1);
512 
513 			sc->sc_ist = buf[0] & ~0x07;
514 			len = buf[0] & 0x07;
515 
516 			(xfer->actlen)--;
517 
518 			offset = 1;
519 
520 			break;
521 
522 		case MODEL_CY7C64013:
523 			if (xfer->actlen < 2) {
524 				goto tr_setup;
525 			}
526 			usb2_copy_out(xfer->frbuffers, 0, buf, 2);
527 
528 			sc->sc_ist = buf[0] & ~0x07;
529 			len = buf[1];
530 
531 			(xfer->actlen) -= 2;
532 
533 			offset = 2;
534 
535 			break;
536 
537 		default:
538 			DPRINTFN(0, "unsupported model number!\n");
539 			goto tr_setup;
540 		}
541 
542 		if (len > xfer->actlen) {
543 			len = xfer->actlen;
544 		}
545 		if (len) {
546 			usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers,
547 			    offset, len);
548 		}
549 	case USB_ST_SETUP:
550 tr_setup:
551 		xfer->frlengths[0] = sc->sc_ilen;
552 		usb2_start_hardware(xfer);
553 		return;
554 
555 	default:			/* Error */
556 		if (xfer->error != USB_ERR_CANCELLED) {
557 			/* try to clear stall first */
558 			xfer->flags.stall_pipe = 1;
559 			goto tr_setup;
560 		}
561 		return;
562 
563 	}
564 }
565