ucycom.c revision 189275
1#include <sys/cdefs.h>
2__FBSDID("$FreeBSD: head/sys/dev/usb/serial/ucycom.c 189275 2009-03-02 05:37:05Z thompsa $");
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
62enum {
63	UCYCOM_CTRL_RD,
64	UCYCOM_INTR_RD,
65	UCYCOM_N_TRANSFER,
66};
67
68struct 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	struct mtx sc_mtx;
75
76	uint32_t sc_model;
77#define	MODEL_CY7C63743		0x63743
78#define	MODEL_CY7C64013		0x64013
79
80	uint16_t sc_flen;		/* feature report length */
81	uint16_t sc_ilen;		/* input report length */
82	uint16_t sc_olen;		/* output report length */
83
84	uint8_t	sc_fid;			/* feature report id */
85	uint8_t	sc_iid;			/* input report id */
86	uint8_t	sc_oid;			/* output report id */
87	uint8_t	sc_cfg;
88#define	UCYCOM_CFG_RESET	0x80
89#define	UCYCOM_CFG_PARODD	0x20
90#define	UCYCOM_CFG_PAREN	0x10
91#define	UCYCOM_CFG_STOPB	0x08
92#define	UCYCOM_CFG_DATAB	0x03
93	uint8_t	sc_ist;			/* status flags from last input */
94	uint8_t	sc_name[16];
95	uint8_t	sc_iface_no;
96	uint8_t	sc_temp_cfg[32];
97};
98
99/* prototypes */
100
101static device_probe_t ucycom_probe;
102static device_attach_t ucycom_attach;
103static device_detach_t ucycom_detach;
104
105static usb2_callback_t ucycom_ctrl_write_callback;
106static usb2_callback_t ucycom_intr_read_callback;
107
108static void	ucycom_cfg_open(struct usb2_com_softc *);
109static void	ucycom_start_read(struct usb2_com_softc *);
110static void	ucycom_stop_read(struct usb2_com_softc *);
111static void	ucycom_start_write(struct usb2_com_softc *);
112static void	ucycom_stop_write(struct usb2_com_softc *);
113static void	ucycom_cfg_write(struct ucycom_softc *, uint32_t, uint8_t);
114static int	ucycom_pre_param(struct usb2_com_softc *, struct termios *);
115static void	ucycom_cfg_param(struct usb2_com_softc *, struct termios *);
116
117static const struct usb2_config ucycom_config[UCYCOM_N_TRANSFER] = {
118
119	[UCYCOM_CTRL_RD] = {
120		.type = UE_CONTROL,
121		.endpoint = 0x00,	/* Control pipe */
122		.direction = UE_DIR_ANY,
123		.mh.bufsize = (sizeof(struct usb2_device_request) + UCYCOM_MAX_IOLEN),
124		.mh.flags = {},
125		.mh.callback = &ucycom_ctrl_write_callback,
126		.mh.timeout = 1000,	/* 1 second */
127	},
128
129	[UCYCOM_INTR_RD] = {
130		.type = UE_INTERRUPT,
131		.endpoint = UE_ADDR_ANY,
132		.direction = UE_DIR_IN,
133		.mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
134		.mh.bufsize = UCYCOM_MAX_IOLEN,
135		.mh.callback = &ucycom_intr_read_callback,
136	},
137};
138
139static const struct usb2_com_callback ucycom_callback = {
140	.usb2_com_cfg_param = &ucycom_cfg_param,
141	.usb2_com_cfg_open = &ucycom_cfg_open,
142	.usb2_com_pre_param = &ucycom_pre_param,
143	.usb2_com_start_read = &ucycom_start_read,
144	.usb2_com_stop_read = &ucycom_stop_read,
145	.usb2_com_start_write = &ucycom_start_write,
146	.usb2_com_stop_write = &ucycom_stop_write,
147};
148
149static device_method_t ucycom_methods[] = {
150	DEVMETHOD(device_probe, ucycom_probe),
151	DEVMETHOD(device_attach, ucycom_attach),
152	DEVMETHOD(device_detach, ucycom_detach),
153	{0, 0}
154};
155
156static devclass_t ucycom_devclass;
157
158static driver_t ucycom_driver = {
159	.name = "ucycom",
160	.methods = ucycom_methods,
161	.size = sizeof(struct ucycom_softc),
162};
163
164DRIVER_MODULE(ucycom, uhub, ucycom_driver, ucycom_devclass, NULL, 0);
165MODULE_DEPEND(ucycom, ucom, 1, 1, 1);
166MODULE_DEPEND(ucycom, usb, 1, 1, 1);
167
168/*
169 * Supported devices
170 */
171static const struct usb2_device_id ucycom_devs[] = {
172	{USB_VPI(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013)},
173};
174
175#define	UCYCOM_DEFAULT_RATE	 4800
176#define	UCYCOM_DEFAULT_CFG	 0x03	/* N-8-1 */
177
178static int
179ucycom_probe(device_t dev)
180{
181	struct usb2_attach_arg *uaa = device_get_ivars(dev);
182
183	if (uaa->usb2_mode != USB_MODE_HOST) {
184		return (ENXIO);
185	}
186	if (uaa->info.bConfigIndex != 0) {
187		return (ENXIO);
188	}
189	if (uaa->info.bIfaceIndex != UCYCOM_IFACE_INDEX) {
190		return (ENXIO);
191	}
192	return (usb2_lookup_id_by_uaa(ucycom_devs, sizeof(ucycom_devs), uaa));
193}
194
195static int
196ucycom_attach(device_t dev)
197{
198	struct usb2_attach_arg *uaa = device_get_ivars(dev);
199	struct ucycom_softc *sc = device_get_softc(dev);
200	void *urd_ptr = NULL;
201	int32_t error;
202	uint16_t urd_len;
203	uint8_t iface_index;
204
205	sc->sc_udev = uaa->device;
206
207	device_set_usb2_desc(dev);
208	mtx_init(&sc->sc_mtx, "ucycom", NULL, MTX_DEF);
209
210	snprintf(sc->sc_name, sizeof(sc->sc_name),
211	    "%s", device_get_nameunit(dev));
212
213	DPRINTF("\n");
214
215	/* get chip model */
216	sc->sc_model = USB_GET_DRIVER_INFO(uaa);
217	if (sc->sc_model == 0) {
218		device_printf(dev, "unsupported device\n");
219		goto detach;
220	}
221	device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model);
222
223	/* get report descriptor */
224
225	error = usb2_req_get_hid_desc
226	    (uaa->device, &Giant,
227	    &urd_ptr, &urd_len, M_USBDEV,
228	    UCYCOM_IFACE_INDEX);
229
230	if (error) {
231		device_printf(dev, "failed to get report "
232		    "descriptor: %s\n",
233		    usb2_errstr(error));
234		goto detach;
235	}
236	/* get report sizes */
237
238	sc->sc_flen = hid_report_size(urd_ptr, urd_len, hid_feature, &sc->sc_fid);
239	sc->sc_ilen = hid_report_size(urd_ptr, urd_len, hid_input, &sc->sc_iid);
240	sc->sc_olen = hid_report_size(urd_ptr, urd_len, hid_output, &sc->sc_oid);
241
242	if ((sc->sc_ilen > UCYCOM_MAX_IOLEN) || (sc->sc_ilen < 1) ||
243	    (sc->sc_olen > UCYCOM_MAX_IOLEN) || (sc->sc_olen < 2) ||
244	    (sc->sc_flen > UCYCOM_MAX_IOLEN) || (sc->sc_flen < 5)) {
245		device_printf(dev, "invalid report size i=%d, o=%d, f=%d, max=%d\n",
246		    sc->sc_ilen, sc->sc_olen, sc->sc_flen,
247		    UCYCOM_MAX_IOLEN);
248		goto detach;
249	}
250	sc->sc_iface_no = uaa->info.bIfaceNum;
251
252	iface_index = UCYCOM_IFACE_INDEX;
253	error = usb2_transfer_setup(uaa->device, &iface_index,
254	    sc->sc_xfer, ucycom_config, UCYCOM_N_TRANSFER,
255	    sc, &sc->sc_mtx);
256	if (error) {
257		device_printf(dev, "allocating USB "
258		    "transfers failed!\n");
259		goto detach;
260	}
261	error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
262	    &ucycom_callback, &sc->sc_mtx);
263
264	if (error) {
265		goto detach;
266	}
267	if (urd_ptr) {
268		free(urd_ptr, M_USBDEV);
269	}
270	return (0);			/* success */
271
272detach:
273	if (urd_ptr) {
274		free(urd_ptr, M_USBDEV);
275	}
276	ucycom_detach(dev);
277	return (ENXIO);
278}
279
280static int
281ucycom_detach(device_t dev)
282{
283	struct ucycom_softc *sc = device_get_softc(dev);
284
285	usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1);
286	usb2_transfer_unsetup(sc->sc_xfer, UCYCOM_N_TRANSFER);
287	mtx_destroy(&sc->sc_mtx);
288
289	return (0);
290}
291
292static void
293ucycom_cfg_open(struct usb2_com_softc *ucom)
294{
295	struct ucycom_softc *sc = ucom->sc_parent;
296
297	/* set default configuration */
298	ucycom_cfg_write(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG);
299}
300
301static void
302ucycom_start_read(struct usb2_com_softc *ucom)
303{
304	struct ucycom_softc *sc = ucom->sc_parent;
305
306	usb2_transfer_start(sc->sc_xfer[UCYCOM_INTR_RD]);
307}
308
309static void
310ucycom_stop_read(struct usb2_com_softc *ucom)
311{
312	struct ucycom_softc *sc = ucom->sc_parent;
313
314	usb2_transfer_stop(sc->sc_xfer[UCYCOM_INTR_RD]);
315}
316
317static void
318ucycom_start_write(struct usb2_com_softc *ucom)
319{
320	struct ucycom_softc *sc = ucom->sc_parent;
321
322	usb2_transfer_start(sc->sc_xfer[UCYCOM_CTRL_RD]);
323}
324
325static void
326ucycom_stop_write(struct usb2_com_softc *ucom)
327{
328	struct ucycom_softc *sc = ucom->sc_parent;
329
330	usb2_transfer_stop(sc->sc_xfer[UCYCOM_CTRL_RD]);
331}
332
333static void
334ucycom_ctrl_write_callback(struct usb2_xfer *xfer)
335{
336	struct ucycom_softc *sc = xfer->priv_sc;
337	struct usb2_device_request req;
338	uint8_t data[2];
339	uint8_t offset;
340	uint32_t actlen;
341
342	switch (USB_GET_STATE(xfer)) {
343	case USB_ST_TRANSFERRED:
344tr_transferred:
345	case USB_ST_SETUP:
346
347		switch (sc->sc_model) {
348		case MODEL_CY7C63743:
349			offset = 1;
350			break;
351		case MODEL_CY7C64013:
352			offset = 2;
353			break;
354		default:
355			offset = 0;
356			break;
357		}
358
359		if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers + 1, offset,
360		    sc->sc_olen - offset, &actlen)) {
361
362			req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
363			req.bRequest = UR_SET_REPORT;
364			USETW2(req.wValue, UHID_OUTPUT_REPORT, sc->sc_oid);
365			req.wIndex[0] = sc->sc_iface_no;
366			req.wIndex[1] = 0;
367			USETW(req.wLength, sc->sc_olen);
368
369			switch (sc->sc_model) {
370			case MODEL_CY7C63743:
371				data[0] = actlen;
372				break;
373			case MODEL_CY7C64013:
374				data[0] = 0;
375				data[1] = actlen;
376				break;
377			default:
378				break;
379			}
380
381			usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req));
382			usb2_copy_in(xfer->frbuffers + 1, 0, data, offset);
383
384			xfer->frlengths[0] = sizeof(req);
385			xfer->frlengths[1] = sc->sc_olen;
386			xfer->nframes = xfer->frlengths[1] ? 2 : 1;
387			usb2_start_hardware(xfer);
388		}
389		return;
390
391	default:			/* Error */
392		if (xfer->error == USB_ERR_CANCELLED) {
393			return;
394		}
395		DPRINTF("error=%s\n",
396		    usb2_errstr(xfer->error));
397		goto tr_transferred;
398	}
399}
400
401static void
402ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg)
403{
404	struct usb2_device_request req;
405	uint16_t len;
406	usb2_error_t err;
407
408	len = sc->sc_flen;
409	if (len > sizeof(sc->sc_temp_cfg)) {
410		len = sizeof(sc->sc_temp_cfg);
411	}
412	sc->sc_cfg = cfg;
413
414	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
415	req.bRequest = UR_SET_REPORT;
416	USETW2(req.wValue, UHID_FEATURE_REPORT, sc->sc_fid);
417	req.wIndex[0] = sc->sc_iface_no;
418	req.wIndex[1] = 0;
419	USETW(req.wLength, len);
420
421	sc->sc_temp_cfg[0] = (baud & 0xff);
422	sc->sc_temp_cfg[1] = (baud >> 8) & 0xff;
423	sc->sc_temp_cfg[2] = (baud >> 16) & 0xff;
424	sc->sc_temp_cfg[3] = (baud >> 24) & 0xff;
425	sc->sc_temp_cfg[4] = cfg;
426
427	err = usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
428	    &req, sc->sc_temp_cfg, 0, 1000);
429	if (err) {
430		DPRINTFN(0, "device request failed, err=%s "
431		    "(ignored)\n", usb2_errstr(err));
432	}
433}
434
435static int
436ucycom_pre_param(struct usb2_com_softc *ucom, struct termios *t)
437{
438	switch (t->c_ospeed) {
439		case 600:
440		case 1200:
441		case 2400:
442		case 4800:
443		case 9600:
444		case 19200:
445		case 38400:
446		case 57600:
447#if 0
448		/*
449		 * Stock chips only support standard baud rates in the 600 - 57600
450		 * range, but higher rates can be achieved using custom firmware.
451		 */
452		case 115200:
453		case 153600:
454		case 192000:
455#endif
456		break;
457	default:
458		return (EINVAL);
459	}
460	return (0);
461}
462
463static void
464ucycom_cfg_param(struct usb2_com_softc *ucom, struct termios *t)
465{
466	struct ucycom_softc *sc = ucom->sc_parent;
467	uint8_t cfg;
468
469	DPRINTF("\n");
470
471	if (t->c_cflag & CIGNORE) {
472		cfg = sc->sc_cfg;
473	} else {
474		cfg = 0;
475		switch (t->c_cflag & CSIZE) {
476		default:
477		case CS8:
478			++cfg;
479		case CS7:
480			++cfg;
481		case CS6:
482			++cfg;
483		case CS5:
484			break;
485		}
486
487		if (t->c_cflag & CSTOPB)
488			cfg |= UCYCOM_CFG_STOPB;
489		if (t->c_cflag & PARENB)
490			cfg |= UCYCOM_CFG_PAREN;
491		if (t->c_cflag & PARODD)
492			cfg |= UCYCOM_CFG_PARODD;
493	}
494
495	ucycom_cfg_write(sc, t->c_ospeed, cfg);
496}
497
498static void
499ucycom_intr_read_callback(struct usb2_xfer *xfer)
500{
501	struct ucycom_softc *sc = xfer->priv_sc;
502	uint8_t buf[2];
503	uint32_t offset;
504	uint32_t len;
505
506	switch (USB_GET_STATE(xfer)) {
507	case USB_ST_TRANSFERRED:
508		switch (sc->sc_model) {
509		case MODEL_CY7C63743:
510			if (xfer->actlen < 1) {
511				goto tr_setup;
512			}
513			usb2_copy_out(xfer->frbuffers, 0, buf, 1);
514
515			sc->sc_ist = buf[0] & ~0x07;
516			len = buf[0] & 0x07;
517
518			(xfer->actlen)--;
519
520			offset = 1;
521
522			break;
523
524		case MODEL_CY7C64013:
525			if (xfer->actlen < 2) {
526				goto tr_setup;
527			}
528			usb2_copy_out(xfer->frbuffers, 0, buf, 2);
529
530			sc->sc_ist = buf[0] & ~0x07;
531			len = buf[1];
532
533			(xfer->actlen) -= 2;
534
535			offset = 2;
536
537			break;
538
539		default:
540			DPRINTFN(0, "unsupported model number!\n");
541			goto tr_setup;
542		}
543
544		if (len > xfer->actlen) {
545			len = xfer->actlen;
546		}
547		if (len) {
548			usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers,
549			    offset, len);
550		}
551	case USB_ST_SETUP:
552tr_setup:
553		xfer->frlengths[0] = sc->sc_ilen;
554		usb2_start_hardware(xfer);
555		return;
556
557	default:			/* Error */
558		if (xfer->error != USB_ERR_CANCELLED) {
559			/* try to clear stall first */
560			xfer->flags.stall_pipe = 1;
561			goto tr_setup;
562		}
563		return;
564
565	}
566}
567