]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/usb/ucycom.c
Use usbd_clear_endpoint_stall_async() when clearing endpoint stalls in
[FreeBSD/FreeBSD.git] / sys / dev / usb / ucycom.c
1 /*-
2  * Copyright (c) 2004 Dag-Erling Coïdan Smørgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
16  *
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.
27  *
28  * $FreeBSD$
29  */
30
31 /*
32  * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to
33  * RS232 bridges.
34  *
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
38  * reports.
39  */
40
41 #include <sys/cdefs.h>
42 __FBSDID("$FreeBSD$");
43
44 #include <sys/param.h>
45 #include <sys/systm.h>
46 #include <sys/conf.h>
47 #include <sys/kernel.h>
48 #include <sys/module.h>
49 #include <sys/sysctl.h>
50 #include <sys/bus.h>
51 #include <sys/tty.h>
52
53 #include "usbdevs.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>
60
61 #define UCYCOM_EP_INPUT          0
62 #define UCYCOM_EP_OUTPUT         1
63
64 #define UCYCOM_MAX_IOLEN         32U
65
66 struct ucycom_softc {
67         device_t                 sc_dev;
68         struct tty              *sc_tty;
69         int                      sc_error;
70         unsigned long            sc_cintr;
71         unsigned long            sc_cin;
72         unsigned long            sc_clost;
73         unsigned long            sc_cout;
74
75         /* usb parameters */
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];
87
88         /* model and settings */
89         uint32_t                 sc_model;
90 #define MODEL_CY7C63743          0x63743
91 #define MODEL_CY7C64013          0x64013
92         uint32_t                 sc_baud;
93         uint8_t                  sc_cfg;
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 */
101
102         /* flags */
103         char                     sc_dying;
104 };
105
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);
116
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),
121         { 0, 0 }
122 };
123
124 static driver_t ucycom_driver = {
125         "ucycom",
126         ucycom_methods,
127         sizeof(struct ucycom_softc),
128 };
129
130 static devclass_t ucycom_devclass;
131
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);
135
136 /*
137  * Supported devices
138  */
139
140 static struct ucycom_device {
141         uint16_t                 vendor;
142         uint16_t                 product;
143         uint32_t                 model;
144 } ucycom_devices[] = {
145         { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013 },
146         { 0, 0, 0 },
147 };
148
149 #define UCYCOM_DEFAULT_RATE      4800
150 #define UCYCOM_DEFAULT_CFG       0x03 /* N-8-1 */
151
152 /*****************************************************************************
153  *
154  * Driver interface
155  *
156  */
157
158 static int
159 ucycom_probe(device_t dev)
160 {
161         struct usb_attach_arg *uaa;
162         struct ucycom_device *ud;
163
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);
171 }
172
173 static int
174 ucycom_attach(device_t dev)
175 {
176         struct usb_attach_arg *uaa;
177         struct ucycom_softc *sc;
178         struct ucycom_device *ud;
179         usb_endpoint_descriptor_t *ued;
180         char *devinfo;
181         void *urd;
182         int error, urdlen;
183
184         /* get arguments and softc */
185         uaa = device_get_ivars(dev);
186         sc = device_get_softc(dev);
187         bzero(sc, sizeof *sc);
188         sc->sc_dev = dev;
189         sc->sc_usbdev = uaa->device;
190
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);
198
199         /* get chip model */
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");
205                 return (ENXIO);
206         }
207         device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model);
208
209         /* select configuration */
210         error = usbd_set_config_index(sc->sc_usbdev, 0, 1 /* verbose */);
211         if (error != 0) {
212                 device_printf(dev, "failed to select configuration: %s\n",
213                     usbd_errstr(error));
214                 return (ENXIO);
215         }
216
217         /* get first interface handle */
218         error = usbd_device2interface_handle(sc->sc_usbdev, 0, &sc->sc_iface);
219         if (error != 0) {
220                 device_printf(dev, "failed to get interface handle: %s\n",
221                     usbd_errstr(error));
222                 return (ENXIO);
223         }
224
225         /* get report descriptor */
226         error = usbd_read_report_desc(sc->sc_iface, &urd, &urdlen, M_USBDEV);
227         if (error != 0) {
228                 device_printf(dev, "failed to get report descriptor: %s\n",
229                     usbd_errstr(error));
230                 return (ENXIO);
231         }
232
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);
237
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);
241                 return (ENXIO);
242         }
243
244         /* get and verify input endpoint descriptor */
245         ued = usbd_interface2endpoint_descriptor(sc->sc_iface, UCYCOM_EP_INPUT);
246         if (ued == NULL) {
247                 device_printf(dev, "failed to get input endpoint descriptor\n");
248                 return (ENXIO);
249         }
250         if (UE_GET_DIR(ued->bEndpointAddress) != UE_DIR_IN) {
251                 device_printf(dev, "not an input endpoint\n");
252                 return (ENXIO);
253         }
254         if ((ued->bmAttributes & UE_XFERTYPE) != UE_INTERRUPT) {
255                 device_printf(dev, "not an interrupt endpoint\n");
256                 return (ENXIO);
257         }
258         sc->sc_iep = ued->bEndpointAddress;
259
260         /* set up tty */
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;
268
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,
272             "interrupt count");
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,
276             "input bytes read");
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,
280             "input bytes lost");
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,
284             "output bytes");
285
286         /* create character device node */
287         ttycreate(sc->sc_tty, 0, "y%r", device_get_unit(sc->sc_dev));
288
289         return (0);
290 }
291
292 static int
293 ucycom_detach(device_t dev)
294 {
295         struct ucycom_softc *sc;
296
297         sc = device_get_softc(dev);
298
299         ttyfree(sc->sc_tty);
300
301         return (0);
302 }
303
304 /*****************************************************************************
305  *
306  * Device interface
307  *
308  */
309
310 static int
311 ucycom_open(struct tty *tp, struct cdev *cdev)
312 {
313         struct ucycom_softc *sc = tp->t_sc;
314         int error;
315
316         /* set default configuration */
317         ucycom_configure(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG);
318
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);
323         if (error != 0) {
324                 device_printf(sc->sc_dev, "failed to open interrupt pipe: %s\n",
325                     usbd_errstr(error));
326                 return (ENXIO);
327         }
328
329         if (bootverbose)
330                 device_printf(sc->sc_dev, "%s bypass l_rint()\n",
331                     (sc->sc_tty->t_state & TS_CAN_BYPASS_L_RINT) ?
332                     "can" : "can't");
333
334         /* done! */
335         return (0);
336 }
337
338 static void
339 ucycom_close(struct tty *tp)
340 {
341         struct ucycom_softc *sc = tp->t_sc;
342
343         /* stop interrupts and close the interrupt pipe */
344         usbd_abort_pipe(sc->sc_pipe);
345         usbd_close_pipe(sc->sc_pipe);
346         sc->sc_pipe = 0;
347
348         return;
349 }
350
351 /*****************************************************************************
352  *
353  * TTY interface
354  *
355  */
356
357 static void
358 ucycom_start(struct tty *tty)
359 {
360         struct ucycom_softc *sc = tty->t_sc;
361         uint8_t report[sc->sc_olen];
362         int error, len;
363
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);
369                         sc->sc_cout += len;
370                         report[0] = len;
371                         len += 1;
372                         break;
373                 case MODEL_CY7C64013:
374                         len = q_to_b(&sc->sc_tty->t_outq,
375                             report + 2, sc->sc_olen - 2);
376                         sc->sc_cout += len;
377                         report[0] = 0;
378                         report[1] = len;
379                         len += 2;
380                         break;
381                 default:
382                         panic("unsupported model (driver error)");
383                 }
384
385                 while (len < sc->sc_olen)
386                         report[len++] = 0;
387                 error = usbd_set_report(sc->sc_iface, UHID_OUTPUT_REPORT,
388                     sc->sc_oid, report, sc->sc_olen);
389 #if 0
390                 if (error != 0) {
391                         device_printf(sc->sc_dev,
392                             "failed to set output report: %s\n",
393                             usbd_errstr(error));
394                         sc->sc_error = error;
395                 }
396 #endif
397         }
398 }
399
400 static void
401 ucycom_stop(struct tty *tty, int flags)
402 {
403         struct ucycom_softc *sc;
404
405         sc = tty->t_sc;
406         if (bootverbose)
407                 device_printf(sc->sc_dev, "%s()\n", __func__);
408 }
409
410 static int
411 ucycom_param(struct tty *tty, struct termios *t)
412 {
413         struct ucycom_softc *sc;
414         uint32_t baud;
415         uint8_t cfg;
416         int error;
417
418         sc = tty->t_sc;
419
420         if (t->c_ispeed != t->c_ospeed)
421                 return (EINVAL);
422         baud = t->c_ispeed;
423
424         if (t->c_cflag & CIGNORE) {
425                 cfg = sc->sc_cfg;
426         } else {
427                 cfg = 0;
428                 switch (t->c_cflag & CSIZE) {
429                 case CS8:
430                         ++cfg;
431                 case CS7:
432                         ++cfg;
433                 case CS6:
434                         ++cfg;
435                 case CS5:
436                         break;
437                 default:
438                         return (EINVAL);
439                 }
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;
446         }
447
448         error = ucycom_configure(sc, baud, cfg);
449         return (error);
450 }
451
452 /*****************************************************************************
453  *
454  * Hardware interface
455  *
456  */
457
458 static int
459 ucycom_configure(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg)
460 {
461         uint8_t report[sc->sc_flen];
462         int error;
463
464         switch (baud) {
465         case 600:
466         case 1200:
467         case 2400:
468         case 4800:
469         case 9600:
470         case 19200:
471         case 38400:
472         case 57600:
473 #if 0
474         /*
475          * Stock chips only support standard baud rates in the 600 - 57600
476          * range, but higher rates can be achieved using custom firmware.
477          */
478         case 115200:
479         case 153600:
480         case 192000:
481 #endif
482                 break;
483         default:
484                 return (EINVAL);
485         }
486
487         if (bootverbose)
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;
497         report[4] = cfg;
498         error = usbd_set_report(sc->sc_iface, UHID_FEATURE_REPORT,
499             sc->sc_fid, report, sc->sc_flen);
500         if (error != 0) {
501                 device_printf(sc->sc_dev, "%s\n", usbd_errstr(error));
502                 return (EIO);
503         }
504         sc->sc_baud = baud;
505         sc->sc_cfg = cfg;
506         return (0);
507 }
508
509 static void
510 ucycom_intr(usbd_xfer_handle xfer, usbd_private_handle scp, usbd_status status)
511 {
512         struct ucycom_softc *sc = scp;
513         uint8_t *data;
514         int i, len, lost;
515
516         sc->sc_cintr++;
517
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;
523                 break;
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;
528                 break;
529         default:
530                 panic("unsupported model (driver error)");
531         }
532
533         switch (status) {
534         case USBD_NORMAL_COMPLETION:
535                 break;
536         default:
537                 /* XXX */
538                 return;
539         }
540
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);
546         } else {
547                 for (i = 0, lost = len; i < len; ++i, --lost)
548                         if (ttyld_rint(sc->sc_tty, data[i]) != 0)
549                                 break;
550         }
551         sc->sc_cin += len - lost;
552         sc->sc_clost += lost;
553 }