]> CyberLeo.Net >> Repos - FreeBSD/releng/7.2.git/blob - sys/dev/usb/ucycom.c
Create releng/7.2 from stable/7 in preparation for 7.2-RELEASE.
[FreeBSD/releng/7.2.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/usbdi.h>
56 #include <dev/usb/usbdi_util.h>
57 #include <dev/usb/usbhid.h>
58 #include <dev/usb/hid.h>
59
60 #define UCYCOM_EP_INPUT          0
61 #define UCYCOM_EP_OUTPUT         1
62
63 #define UCYCOM_MAX_IOLEN         32U
64
65 struct ucycom_softc {
66         device_t                 sc_dev;
67         struct tty              *sc_tty;
68         int                      sc_error;
69         unsigned long            sc_cintr;
70         unsigned long            sc_cin;
71         unsigned long            sc_clost;
72         unsigned long            sc_cout;
73
74         /* usb parameters */
75         usbd_device_handle       sc_usbdev;
76         usbd_interface_handle    sc_iface;
77         usbd_pipe_handle         sc_pipe;
78         uint8_t                  sc_iep; /* input endpoint */
79         uint8_t                  sc_fid; /* feature report id*/
80         uint8_t                  sc_iid; /* input report id */
81         uint8_t                  sc_oid; /* output report id */
82         size_t                   sc_flen; /* feature report length */
83         size_t                   sc_ilen; /* input report length */
84         size_t                   sc_olen; /* output report length */
85         uint8_t                  sc_ibuf[UCYCOM_MAX_IOLEN];
86
87         /* model and settings */
88         uint32_t                 sc_model;
89 #define MODEL_CY7C63743          0x63743
90 #define MODEL_CY7C64013          0x64013
91         uint32_t                 sc_baud;
92         uint8_t                  sc_cfg;
93 #define UCYCOM_CFG_RESET         0x80
94 #define UCYCOM_CFG_PARODD        0x20
95 #define UCYCOM_CFG_PAREN         0x10
96 #define UCYCOM_CFG_STOPB         0x08
97 #define UCYCOM_CFG_DATAB         0x03
98         uint8_t                  sc_ist; /* status flags from last input */
99         uint8_t                  sc_ost; /* status flags for next output */
100
101         /* flags */
102         char                     sc_dying;
103 };
104
105 static device_probe_t ucycom_probe;
106 static device_attach_t ucycom_attach;
107 static device_detach_t ucycom_detach;
108 static t_open_t ucycom_open;
109 static t_close_t ucycom_close;
110 static void ucycom_start(struct tty *);
111 static void ucycom_stop(struct tty *, int);
112 static int ucycom_param(struct tty *, struct termios *);
113 static int ucycom_configure(struct ucycom_softc *, uint32_t, uint8_t);
114 static void ucycom_intr(usbd_xfer_handle, usbd_private_handle, usbd_status);
115
116 static device_method_t ucycom_methods[] = {
117         DEVMETHOD(device_probe, ucycom_probe),
118         DEVMETHOD(device_attach, ucycom_attach),
119         DEVMETHOD(device_detach, ucycom_detach),
120         { 0, 0 }
121 };
122
123 static driver_t ucycom_driver = {
124         "ucycom",
125         ucycom_methods,
126         sizeof(struct ucycom_softc),
127 };
128
129 static devclass_t ucycom_devclass;
130
131 DRIVER_MODULE(ucycom, uhub, ucycom_driver, ucycom_devclass, usbd_driver_load, 0);
132 MODULE_VERSION(ucycom, 1);
133 MODULE_DEPEND(ucycom, usb, 1, 1, 1);
134
135 /*
136  * Supported devices
137  */
138
139 static struct ucycom_device {
140         uint16_t                 vendor;
141         uint16_t                 product;
142         uint32_t                 model;
143 } ucycom_devices[] = {
144         { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013 },
145         { 0, 0, 0 },
146 };
147
148 #define UCYCOM_DEFAULT_RATE      4800
149 #define UCYCOM_DEFAULT_CFG       0x03 /* N-8-1 */
150
151 /*****************************************************************************
152  *
153  * Driver interface
154  *
155  */
156
157 static int
158 ucycom_probe(device_t dev)
159 {
160         struct usb_attach_arg *uaa;
161         struct ucycom_device *ud;
162
163         uaa = device_get_ivars(dev);
164         if (uaa->iface != NULL)
165                 return (UMATCH_NONE);
166         for (ud = ucycom_devices; ud->model != 0; ++ud)
167                 if (ud->vendor == uaa->vendor && ud->product == uaa->product)
168                         return (UMATCH_VENDOR_PRODUCT);
169         return (UMATCH_NONE);
170 }
171
172 static int
173 ucycom_attach(device_t dev)
174 {
175         struct usb_attach_arg *uaa;
176         struct ucycom_softc *sc;
177         struct ucycom_device *ud;
178         usb_endpoint_descriptor_t *ued;
179         void *urd;
180         int error, urdlen;
181
182         /* get arguments and softc */
183         uaa = device_get_ivars(dev);
184         sc = device_get_softc(dev);
185         bzero(sc, sizeof *sc);
186         sc->sc_dev = dev;
187         sc->sc_usbdev = uaa->device;
188
189         /* get chip model */
190         for (ud = ucycom_devices; ud->model != 0; ++ud)
191                 if (ud->vendor == uaa->vendor && ud->product == uaa->product)
192                         sc->sc_model = ud->model;
193         if (sc->sc_model == 0) {
194                 device_printf(dev, "unsupported device\n");
195                 return (ENXIO);
196         }
197         device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model);
198
199         /* select configuration */
200         error = usbd_set_config_index(sc->sc_usbdev, 0, 1 /* verbose */);
201         if (error != 0) {
202                 device_printf(dev, "failed to select configuration: %s\n",
203                     usbd_errstr(error));
204                 return (ENXIO);
205         }
206
207         /* get first interface handle */
208         error = usbd_device2interface_handle(sc->sc_usbdev, 0, &sc->sc_iface);
209         if (error != 0) {
210                 device_printf(dev, "failed to get interface handle: %s\n",
211                     usbd_errstr(error));
212                 return (ENXIO);
213         }
214
215         /* get report descriptor */
216         error = usbd_read_report_desc(sc->sc_iface, &urd, &urdlen, M_USBDEV);
217         if (error != 0) {
218                 device_printf(dev, "failed to get report descriptor: %s\n",
219                     usbd_errstr(error));
220                 return (ENXIO);
221         }
222
223         /* get report sizes */
224         sc->sc_flen = hid_report_size(urd, urdlen, hid_feature, &sc->sc_fid);
225         sc->sc_ilen = hid_report_size(urd, urdlen, hid_input, &sc->sc_iid);
226         sc->sc_olen = hid_report_size(urd, urdlen, hid_output, &sc->sc_oid);
227
228         if (sc->sc_ilen > UCYCOM_MAX_IOLEN || sc->sc_olen > UCYCOM_MAX_IOLEN) {
229                 device_printf(dev, "I/O report size too big (%zu, %zu, %u)\n",
230                     sc->sc_ilen, sc->sc_olen, UCYCOM_MAX_IOLEN);
231                 return (ENXIO);
232         }
233
234         /* get and verify input endpoint descriptor */
235         ued = usbd_interface2endpoint_descriptor(sc->sc_iface, UCYCOM_EP_INPUT);
236         if (ued == NULL) {
237                 device_printf(dev, "failed to get input endpoint descriptor\n");
238                 return (ENXIO);
239         }
240         if (UE_GET_DIR(ued->bEndpointAddress) != UE_DIR_IN) {
241                 device_printf(dev, "not an input endpoint\n");
242                 return (ENXIO);
243         }
244         if ((ued->bmAttributes & UE_XFERTYPE) != UE_INTERRUPT) {
245                 device_printf(dev, "not an interrupt endpoint\n");
246                 return (ENXIO);
247         }
248         sc->sc_iep = ued->bEndpointAddress;
249
250         /* set up tty */
251         sc->sc_tty = ttyalloc();
252         sc->sc_tty->t_sc = sc;
253         sc->sc_tty->t_oproc = ucycom_start;
254         sc->sc_tty->t_stop = ucycom_stop;
255         sc->sc_tty->t_param = ucycom_param;
256         sc->sc_tty->t_open = ucycom_open;
257         sc->sc_tty->t_close = ucycom_close;
258
259         SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
260             SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
261             OID_AUTO, "intr", CTLFLAG_RD, &sc->sc_cintr, 0,
262             "interrupt count");
263         SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
264             SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
265             OID_AUTO, "in", CTLFLAG_RD, &sc->sc_cin, 0,
266             "input bytes read");
267         SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
268             SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
269             OID_AUTO, "lost", CTLFLAG_RD, &sc->sc_clost, 0,
270             "input bytes lost");
271         SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
272             SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
273             OID_AUTO, "out", CTLFLAG_RD, &sc->sc_cout, 0,
274             "output bytes");
275
276         /* create character device node */
277         ttycreate(sc->sc_tty, 0, "y%r", device_get_unit(sc->sc_dev));
278
279         return (0);
280 }
281
282 static int
283 ucycom_detach(device_t dev)
284 {
285         struct ucycom_softc *sc;
286
287         sc = device_get_softc(dev);
288
289         ttyfree(sc->sc_tty);
290
291         return (0);
292 }
293
294 /*****************************************************************************
295  *
296  * Device interface
297  *
298  */
299
300 static int
301 ucycom_open(struct tty *tp, struct cdev *cdev)
302 {
303         struct ucycom_softc *sc = tp->t_sc;
304         int error;
305
306         /* set default configuration */
307         ucycom_configure(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG);
308
309         /* open interrupt pipe */
310         error = usbd_open_pipe_intr(sc->sc_iface, sc->sc_iep, 0,
311             &sc->sc_pipe, sc, sc->sc_ibuf, sc->sc_ilen,
312             ucycom_intr, USBD_DEFAULT_INTERVAL);
313         if (error != 0) {
314                 device_printf(sc->sc_dev, "failed to open interrupt pipe: %s\n",
315                     usbd_errstr(error));
316                 return (ENXIO);
317         }
318
319         if (bootverbose)
320                 device_printf(sc->sc_dev, "%s bypass l_rint()\n",
321                     (sc->sc_tty->t_state & TS_CAN_BYPASS_L_RINT) ?
322                     "can" : "can't");
323
324         /* done! */
325         return (0);
326 }
327
328 static void
329 ucycom_close(struct tty *tp)
330 {
331         struct ucycom_softc *sc = tp->t_sc;
332
333         /* stop interrupts and close the interrupt pipe */
334         usbd_abort_pipe(sc->sc_pipe);
335         usbd_close_pipe(sc->sc_pipe);
336         sc->sc_pipe = 0;
337
338         return;
339 }
340
341 /*****************************************************************************
342  *
343  * TTY interface
344  *
345  */
346
347 static void
348 ucycom_start(struct tty *tty)
349 {
350         struct ucycom_softc *sc = tty->t_sc;
351         uint8_t report[sc->sc_olen];
352         int error, len;
353
354         while (sc->sc_error == 0 && sc->sc_tty->t_outq.c_cc > 0) {
355                 switch (sc->sc_model) {
356                 case MODEL_CY7C63743:
357                         len = q_to_b(&sc->sc_tty->t_outq,
358                             report + 1, sc->sc_olen - 1);
359                         sc->sc_cout += len;
360                         report[0] = len;
361                         len += 1;
362                         break;
363                 case MODEL_CY7C64013:
364                         len = q_to_b(&sc->sc_tty->t_outq,
365                             report + 2, sc->sc_olen - 2);
366                         sc->sc_cout += len;
367                         report[0] = 0;
368                         report[1] = len;
369                         len += 2;
370                         break;
371                 default:
372                         panic("unsupported model (driver error)");
373                 }
374
375                 while (len < sc->sc_olen)
376                         report[len++] = 0;
377                 error = usbd_set_report(sc->sc_iface, UHID_OUTPUT_REPORT,
378                     sc->sc_oid, report, sc->sc_olen);
379 #if 0
380                 if (error != 0) {
381                         device_printf(sc->sc_dev,
382                             "failed to set output report: %s\n",
383                             usbd_errstr(error));
384                         sc->sc_error = error;
385                 }
386 #endif
387         }
388 }
389
390 static void
391 ucycom_stop(struct tty *tty, int flags)
392 {
393         struct ucycom_softc *sc;
394
395         sc = tty->t_sc;
396         if (bootverbose)
397                 device_printf(sc->sc_dev, "%s()\n", __func__);
398 }
399
400 static int
401 ucycom_param(struct tty *tty, struct termios *t)
402 {
403         struct ucycom_softc *sc;
404         uint32_t baud;
405         uint8_t cfg;
406         int error;
407
408         sc = tty->t_sc;
409
410         if (t->c_ispeed != t->c_ospeed)
411                 return (EINVAL);
412         baud = t->c_ispeed;
413
414         if (t->c_cflag & CIGNORE) {
415                 cfg = sc->sc_cfg;
416         } else {
417                 cfg = 0;
418                 switch (t->c_cflag & CSIZE) {
419                 case CS8:
420                         ++cfg;
421                 case CS7:
422                         ++cfg;
423                 case CS6:
424                         ++cfg;
425                 case CS5:
426                         break;
427                 default:
428                         return (EINVAL);
429                 }
430                 if (t->c_cflag & CSTOPB)
431                         cfg |= UCYCOM_CFG_STOPB;
432                 if (t->c_cflag & PARENB)
433                         cfg |= UCYCOM_CFG_PAREN;
434                 if (t->c_cflag & PARODD)
435                         cfg |= UCYCOM_CFG_PARODD;
436         }
437
438         error = ucycom_configure(sc, baud, cfg);
439         return (error);
440 }
441
442 /*****************************************************************************
443  *
444  * Hardware interface
445  *
446  */
447
448 static int
449 ucycom_configure(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg)
450 {
451         uint8_t report[sc->sc_flen];
452         int error;
453
454         switch (baud) {
455         case 600:
456         case 1200:
457         case 2400:
458         case 4800:
459         case 9600:
460         case 19200:
461         case 38400:
462         case 57600:
463 #if 0
464         /*
465          * Stock chips only support standard baud rates in the 600 - 57600
466          * range, but higher rates can be achieved using custom firmware.
467          */
468         case 115200:
469         case 153600:
470         case 192000:
471 #endif
472                 break;
473         default:
474                 return (EINVAL);
475         }
476
477         if (bootverbose)
478                 device_printf(sc->sc_dev, "%d baud, %c-%d-%d\n", baud,
479                     (cfg & UCYCOM_CFG_PAREN) ?
480                     ((cfg & UCYCOM_CFG_PARODD) ? 'O' : 'E') : 'N',
481                     5 + (cfg & UCYCOM_CFG_DATAB),
482                     (cfg & UCYCOM_CFG_STOPB) ? 2 : 1);
483         report[0] = baud & 0xff;
484         report[1] = (baud >> 8) & 0xff;
485         report[2] = (baud >> 16) & 0xff;
486         report[3] = (baud >> 24) & 0xff;
487         report[4] = cfg;
488         error = usbd_set_report(sc->sc_iface, UHID_FEATURE_REPORT,
489             sc->sc_fid, report, sc->sc_flen);
490         if (error != 0) {
491                 device_printf(sc->sc_dev, "%s\n", usbd_errstr(error));
492                 return (EIO);
493         }
494         sc->sc_baud = baud;
495         sc->sc_cfg = cfg;
496         return (0);
497 }
498
499 static void
500 ucycom_intr(usbd_xfer_handle xfer, usbd_private_handle scp, usbd_status status)
501 {
502         struct ucycom_softc *sc = scp;
503         uint8_t *data;
504         int i, len, lost;
505
506         sc->sc_cintr++;
507
508         switch (sc->sc_model) {
509         case MODEL_CY7C63743:
510                 sc->sc_ist = sc->sc_ibuf[0] & ~0x07;
511                 len = sc->sc_ibuf[0] & 0x07;
512                 data = sc->sc_ibuf + 1;
513                 break;
514         case MODEL_CY7C64013:
515                 sc->sc_ist = sc->sc_ibuf[0] & ~0x07;
516                 len = sc->sc_ibuf[1];
517                 data = sc->sc_ibuf + 2;
518                 break;
519         default:
520                 panic("unsupported model (driver error)");
521         }
522
523         switch (status) {
524         case USBD_NORMAL_COMPLETION:
525                 break;
526         default:
527                 /* XXX */
528                 return;
529         }
530
531         if (sc->sc_tty->t_state & TS_CAN_BYPASS_L_RINT) {
532                 /* XXX flow control! */
533                 lost = b_to_q(data, len, &sc->sc_tty->t_rawq);
534                 sc->sc_tty->t_rawcc += len - lost;
535                 ttwakeup(sc->sc_tty);
536         } else {
537                 for (i = 0, lost = len; i < len; ++i, --lost)
538                         if (ttyld_rint(sc->sc_tty, data[i]) != 0)
539                                 break;
540         }
541         sc->sc_cin += len - lost;
542         sc->sc_clost += lost;
543 }