2 * Copyright (c) 2004 Dag-Erling Coïdan Smørgrav
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer
10 * in this position and unchanged.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to
35 * Normally, a driver for a USB-to-serial chip would hang off the ucom(4)
36 * driver, but ucom(4) was written under the assumption that all USB-to-
37 * serial chips use bulk pipes for I/O, while the Cypress parts use HID
41 #include <sys/cdefs.h>
42 __FBSDID("$FreeBSD$");
44 #include <sys/param.h>
45 #include <sys/systm.h>
47 #include <sys/kernel.h>
48 #include <sys/module.h>
49 #include <sys/sysctl.h>
54 #include <dev/usb/usb.h>
55 #include <dev/usb/usb_port.h>
56 #include <dev/usb/usbdi.h>
57 #include <dev/usb/usbdi_util.h>
58 #include <dev/usb/usbhid.h>
59 #include <dev/usb/hid.h>
61 #define UCYCOM_EP_INPUT 0
62 #define UCYCOM_EP_OUTPUT 1
64 #define UCYCOM_MAX_IOLEN 32U
70 unsigned long sc_cintr;
72 unsigned long sc_clost;
73 unsigned long sc_cout;
76 usbd_device_handle sc_usbdev;
77 usbd_interface_handle sc_iface;
78 usbd_pipe_handle sc_pipe;
79 uint8_t sc_iep; /* input endpoint */
80 uint8_t sc_fid; /* feature report id*/
81 uint8_t sc_iid; /* input report id */
82 uint8_t sc_oid; /* output report id */
83 size_t sc_flen; /* feature report length */
84 size_t sc_ilen; /* input report length */
85 size_t sc_olen; /* output report length */
86 uint8_t sc_ibuf[UCYCOM_MAX_IOLEN];
88 /* model and settings */
90 #define MODEL_CY7C63743 0x63743
91 #define MODEL_CY7C64013 0x64013
94 #define UCYCOM_CFG_RESET 0x80
95 #define UCYCOM_CFG_PARODD 0x20
96 #define UCYCOM_CFG_PAREN 0x10
97 #define UCYCOM_CFG_STOPB 0x08
98 #define UCYCOM_CFG_DATAB 0x03
99 uint8_t sc_ist; /* status flags from last input */
100 uint8_t sc_ost; /* status flags for next output */
106 static int ucycom_probe(device_t);
107 static int ucycom_attach(device_t);
108 static int ucycom_detach(device_t);
109 static t_open_t ucycom_open;
110 static t_close_t ucycom_close;
111 static void ucycom_start(struct tty *);
112 static void ucycom_stop(struct tty *, int);
113 static int ucycom_param(struct tty *, struct termios *);
114 static int ucycom_configure(struct ucycom_softc *, uint32_t, uint8_t);
115 static void ucycom_intr(usbd_xfer_handle, usbd_private_handle, usbd_status);
117 static device_method_t ucycom_methods[] = {
118 DEVMETHOD(device_probe, ucycom_probe),
119 DEVMETHOD(device_attach, ucycom_attach),
120 DEVMETHOD(device_detach, ucycom_detach),
124 static driver_t ucycom_driver = {
127 sizeof(struct ucycom_softc),
130 static devclass_t ucycom_devclass;
132 DRIVER_MODULE(ucycom, uhub, ucycom_driver, ucycom_devclass, usbd_driver_load, 0);
133 MODULE_VERSION(ucycom, 1);
134 MODULE_DEPEND(ucycom, usb, 1, 1, 1);
140 static struct ucycom_device {
144 } ucycom_devices[] = {
145 { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013 },
149 #define UCYCOM_DEFAULT_RATE 4800
150 #define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */
152 /*****************************************************************************
159 ucycom_probe(device_t dev)
161 struct usb_attach_arg *uaa;
162 struct ucycom_device *ud;
164 uaa = device_get_ivars(dev);
165 if (uaa->iface != NULL)
166 return (UMATCH_NONE);
167 for (ud = ucycom_devices; ud->model != 0; ++ud)
168 if (ud->vendor == uaa->vendor && ud->product == uaa->product)
169 return (UMATCH_VENDOR_PRODUCT);
170 return (UMATCH_NONE);
174 ucycom_attach(device_t dev)
176 struct usb_attach_arg *uaa;
177 struct ucycom_softc *sc;
178 struct ucycom_device *ud;
179 usb_endpoint_descriptor_t *ued;
184 /* get arguments and softc */
185 uaa = device_get_ivars(dev);
186 sc = device_get_softc(dev);
187 bzero(sc, sizeof *sc);
189 sc->sc_usbdev = uaa->device;
191 /* get device description */
192 /* XXX usb_devinfo() has little or no overflow protection */
193 devinfo = malloc(1024, M_USBDEV, M_WAITOK);
194 usbd_devinfo(sc->sc_usbdev, 0, devinfo);
195 device_set_desc_copy(dev, devinfo);
196 device_printf(dev, "%s\n", devinfo);
197 free(devinfo, M_USBDEV);
200 for (ud = ucycom_devices; ud->model != 0; ++ud)
201 if (ud->vendor == uaa->vendor && ud->product == uaa->product)
202 sc->sc_model = ud->model;
203 if (sc->sc_model == 0) {
204 device_printf(dev, "unsupported device\n");
207 device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model);
209 /* select configuration */
210 error = usbd_set_config_index(sc->sc_usbdev, 0, 1 /* verbose */);
212 device_printf(dev, "failed to select configuration: %s\n",
217 /* get first interface handle */
218 error = usbd_device2interface_handle(sc->sc_usbdev, 0, &sc->sc_iface);
220 device_printf(dev, "failed to get interface handle: %s\n",
225 /* get report descriptor */
226 error = usbd_read_report_desc(sc->sc_iface, &urd, &urdlen, M_USBDEV);
228 device_printf(dev, "failed to get report descriptor: %s\n",
233 /* get report sizes */
234 sc->sc_flen = hid_report_size(urd, urdlen, hid_feature, &sc->sc_fid);
235 sc->sc_ilen = hid_report_size(urd, urdlen, hid_input, &sc->sc_iid);
236 sc->sc_olen = hid_report_size(urd, urdlen, hid_output, &sc->sc_oid);
238 if (sc->sc_ilen > UCYCOM_MAX_IOLEN || sc->sc_olen > UCYCOM_MAX_IOLEN) {
239 device_printf(dev, "I/O report size too big (%zu, %zu, %u)\n",
240 sc->sc_ilen, sc->sc_olen, UCYCOM_MAX_IOLEN);
244 /* get and verify input endpoint descriptor */
245 ued = usbd_interface2endpoint_descriptor(sc->sc_iface, UCYCOM_EP_INPUT);
247 device_printf(dev, "failed to get input endpoint descriptor\n");
250 if (UE_GET_DIR(ued->bEndpointAddress) != UE_DIR_IN) {
251 device_printf(dev, "not an input endpoint\n");
254 if ((ued->bmAttributes & UE_XFERTYPE) != UE_INTERRUPT) {
255 device_printf(dev, "not an interrupt endpoint\n");
258 sc->sc_iep = ued->bEndpointAddress;
261 sc->sc_tty = ttyalloc();
262 sc->sc_tty->t_sc = sc;
263 sc->sc_tty->t_oproc = ucycom_start;
264 sc->sc_tty->t_stop = ucycom_stop;
265 sc->sc_tty->t_param = ucycom_param;
266 sc->sc_tty->t_open = ucycom_open;
267 sc->sc_tty->t_close = ucycom_close;
269 SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
270 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
271 OID_AUTO, "intr", CTLFLAG_RD, &sc->sc_cintr, 0,
273 SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
274 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
275 OID_AUTO, "in", CTLFLAG_RD, &sc->sc_cin, 0,
277 SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
278 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
279 OID_AUTO, "lost", CTLFLAG_RD, &sc->sc_clost, 0,
281 SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
282 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
283 OID_AUTO, "out", CTLFLAG_RD, &sc->sc_cout, 0,
286 /* create character device node */
287 ttycreate(sc->sc_tty, 0, "y%r", device_get_unit(sc->sc_dev));
293 ucycom_detach(device_t dev)
295 struct ucycom_softc *sc;
297 sc = device_get_softc(dev);
304 /*****************************************************************************
311 ucycom_open(struct tty *tp, struct cdev *cdev)
313 struct ucycom_softc *sc = tp->t_sc;
316 /* set default configuration */
317 ucycom_configure(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG);
319 /* open interrupt pipe */
320 error = usbd_open_pipe_intr(sc->sc_iface, sc->sc_iep, 0,
321 &sc->sc_pipe, sc, sc->sc_ibuf, sc->sc_ilen,
322 ucycom_intr, USBD_DEFAULT_INTERVAL);
324 device_printf(sc->sc_dev, "failed to open interrupt pipe: %s\n",
330 device_printf(sc->sc_dev, "%s bypass l_rint()\n",
331 (sc->sc_tty->t_state & TS_CAN_BYPASS_L_RINT) ?
339 ucycom_close(struct tty *tp)
341 struct ucycom_softc *sc = tp->t_sc;
343 /* stop interrupts and close the interrupt pipe */
344 usbd_abort_pipe(sc->sc_pipe);
345 usbd_close_pipe(sc->sc_pipe);
351 /*****************************************************************************
358 ucycom_start(struct tty *tty)
360 struct ucycom_softc *sc = tty->t_sc;
361 uint8_t report[sc->sc_olen];
364 while (sc->sc_error == 0 && sc->sc_tty->t_outq.c_cc > 0) {
365 switch (sc->sc_model) {
366 case MODEL_CY7C63743:
367 len = q_to_b(&sc->sc_tty->t_outq,
368 report + 1, sc->sc_olen - 1);
373 case MODEL_CY7C64013:
374 len = q_to_b(&sc->sc_tty->t_outq,
375 report + 2, sc->sc_olen - 2);
382 panic("unsupported model (driver error)");
385 while (len < sc->sc_olen)
387 error = usbd_set_report(sc->sc_iface, UHID_OUTPUT_REPORT,
388 sc->sc_oid, report, sc->sc_olen);
391 device_printf(sc->sc_dev,
392 "failed to set output report: %s\n",
394 sc->sc_error = error;
401 ucycom_stop(struct tty *tty, int flags)
403 struct ucycom_softc *sc;
407 device_printf(sc->sc_dev, "%s()\n", __func__);
411 ucycom_param(struct tty *tty, struct termios *t)
413 struct ucycom_softc *sc;
420 if (t->c_ispeed != t->c_ospeed)
424 if (t->c_cflag & CIGNORE) {
428 switch (t->c_cflag & CSIZE) {
440 if (t->c_cflag & CSTOPB)
441 cfg |= UCYCOM_CFG_STOPB;
442 if (t->c_cflag & PARENB)
443 cfg |= UCYCOM_CFG_PAREN;
444 if (t->c_cflag & PARODD)
445 cfg |= UCYCOM_CFG_PARODD;
448 error = ucycom_configure(sc, baud, cfg);
452 /*****************************************************************************
459 ucycom_configure(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg)
461 uint8_t report[sc->sc_flen];
475 * Stock chips only support standard baud rates in the 600 - 57600
476 * range, but higher rates can be achieved using custom firmware.
488 device_printf(sc->sc_dev, "%d baud, %c-%d-%d\n", baud,
489 (cfg & UCYCOM_CFG_PAREN) ?
490 ((cfg & UCYCOM_CFG_PARODD) ? 'O' : 'E') : 'N',
491 5 + (cfg & UCYCOM_CFG_DATAB),
492 (cfg & UCYCOM_CFG_STOPB) ? 2 : 1);
493 report[0] = baud & 0xff;
494 report[1] = (baud >> 8) & 0xff;
495 report[2] = (baud >> 16) & 0xff;
496 report[3] = (baud >> 24) & 0xff;
498 error = usbd_set_report(sc->sc_iface, UHID_FEATURE_REPORT,
499 sc->sc_fid, report, sc->sc_flen);
501 device_printf(sc->sc_dev, "%s\n", usbd_errstr(error));
510 ucycom_intr(usbd_xfer_handle xfer, usbd_private_handle scp, usbd_status status)
512 struct ucycom_softc *sc = scp;
518 switch (sc->sc_model) {
519 case MODEL_CY7C63743:
520 sc->sc_ist = sc->sc_ibuf[0] & ~0x07;
521 len = sc->sc_ibuf[0] & 0x07;
522 data = sc->sc_ibuf + 1;
524 case MODEL_CY7C64013:
525 sc->sc_ist = sc->sc_ibuf[0] & ~0x07;
526 len = sc->sc_ibuf[1];
527 data = sc->sc_ibuf + 2;
530 panic("unsupported model (driver error)");
534 case USBD_NORMAL_COMPLETION:
541 if (sc->sc_tty->t_state & TS_CAN_BYPASS_L_RINT) {
542 /* XXX flow control! */
543 lost = b_to_q(data, len, &sc->sc_tty->t_rawq);
544 sc->sc_tty->t_rawcc += len - lost;
545 ttwakeup(sc->sc_tty);
547 for (i = 0, lost = len; i < len; ++i, --lost)
548 if (ttyld_rint(sc->sc_tty, data[i]) != 0)
551 sc->sc_cin += len - lost;
552 sc->sc_clost += lost;