Line data Source code
1 : /* $OpenBSD: ucycom.c,v 1.37 2017/12/30 20:46:59 guenther Exp $ */
2 : /* $NetBSD: ucycom.c,v 1.3 2005/08/05 07:27:47 skrll Exp $ */
3 :
4 : /*
5 : * Copyright (c) 2005 The NetBSD Foundation, Inc.
6 : * All rights reserved.
7 : *
8 : * This code is derived from software contributed to The NetBSD Foundation
9 : * by Nick Hudson
10 : *
11 : * Redistribution and use in source and binary forms, with or without
12 : * modification, are permitted provided that the following conditions
13 : * are met:
14 : * 1. Redistributions of source code must retain the above copyright
15 : * notice, this list of conditions and the following disclaimer.
16 : * 2. Redistributions in binary form must reproduce the above copyright
17 : * notice, this list of conditions and the following disclaimer in the
18 : * documentation and/or other materials provided with the distribution.
19 : *
20 : * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 : * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 : * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 : * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 : * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 : * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 : * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 : * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 : * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 : * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 : * POSSIBILITY OF SUCH DAMAGE.
31 : */
32 : /*
33 : * This code is based on the ucom driver.
34 : */
35 :
36 : /*
37 : * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to
38 : * RS232 bridges.
39 : */
40 :
41 : #include <sys/param.h>
42 : #include <sys/systm.h>
43 : #include <sys/conf.h>
44 : #include <sys/kernel.h>
45 : #include <sys/malloc.h>
46 : #include <sys/device.h>
47 : #include <sys/tty.h>
48 :
49 : #include <dev/usb/usb.h>
50 : #include <dev/usb/usbhid.h>
51 :
52 : #include <dev/usb/usbdi.h>
53 : #include <dev/usb/usbdi_util.h>
54 : #include <dev/usb/usbdevs.h>
55 : #include <dev/usb/uhidev.h>
56 :
57 : #include <dev/usb/ucomvar.h>
58 :
59 : #ifdef UCYCOM_DEBUG
60 : #define DPRINTF(x) if (ucycomdebug) printf x
61 : #define DPRINTFN(n, x) if (ucycomdebug > (n)) printf x
62 : int ucycomdebug = 200;
63 : #else
64 : #define DPRINTF(x)
65 : #define DPRINTFN(n,x)
66 : #endif
67 :
68 : /* Configuration Byte */
69 : #define UCYCOM_RESET 0x80
70 : #define UCYCOM_PARITY_TYPE_MASK 0x20
71 : #define UCYCOM_PARITY_ODD 0x20
72 : #define UCYCOM_PARITY_EVEN 0x00
73 : #define UCYCOM_PARITY_MASK 0x10
74 : #define UCYCOM_PARITY_ON 0x10
75 : #define UCYCOM_PARITY_OFF 0x00
76 : #define UCYCOM_STOP_MASK 0x08
77 : #define UCYCOM_STOP_BITS_2 0x08
78 : #define UCYCOM_STOP_BITS_1 0x00
79 : #define UCYCOM_DATA_MASK 0x03
80 : #define UCYCOM_DATA_BITS_8 0x03
81 : #define UCYCOM_DATA_BITS_7 0x02
82 : #define UCYCOM_DATA_BITS_6 0x01
83 : #define UCYCOM_DATA_BITS_5 0x00
84 :
85 : /* Modem (Input) status byte */
86 : #define UCYCOM_RI 0x80
87 : #define UCYCOM_DCD 0x40
88 : #define UCYCOM_DSR 0x20
89 : #define UCYCOM_CTS 0x10
90 : #define UCYCOM_ERROR 0x08
91 : #define UCYCOM_LMASK 0x07
92 :
93 : /* Modem (Output) control byte */
94 : #define UCYCOM_DTR 0x20
95 : #define UCYCOM_RTS 0x10
96 : #define UCYCOM_ORESET 0x08
97 :
98 : struct ucycom_softc {
99 : struct uhidev sc_hdev;
100 : struct usbd_device *sc_udev;
101 :
102 : /* uhidev parameters */
103 : size_t sc_flen; /* feature report length */
104 : size_t sc_ilen; /* input report length */
105 : size_t sc_olen; /* output report length */
106 :
107 : uint8_t *sc_obuf;
108 :
109 : uint8_t *sc_ibuf;
110 : uint32_t sc_icnt;
111 :
112 : /* settings */
113 : uint32_t sc_baud;
114 : uint8_t sc_cfg; /* Data format */
115 : uint8_t sc_mcr; /* Modem control */
116 : uint8_t sc_msr; /* Modem status */
117 : uint8_t sc_newmsr; /* from HID intr */
118 : int sc_swflags;
119 :
120 : struct device *sc_subdev;
121 : };
122 :
123 : /* Callback routines */
124 : void ucycom_set(void *, int, int, int);
125 : int ucycom_param(void *, int, struct termios *);
126 : void ucycom_get_status(void *, int, u_char *, u_char *);
127 : int ucycom_open(void *, int);
128 : void ucycom_close(void *, int);
129 : void ucycom_write(void *, int, u_char *, u_char *, u_int32_t *);
130 : void ucycom_read(void *, int, u_char **, u_int32_t *);
131 :
132 : struct ucom_methods ucycom_methods = {
133 : NULL, /* ucycom_get_status, */
134 : ucycom_set,
135 : ucycom_param,
136 : NULL,
137 : ucycom_open,
138 : ucycom_close,
139 : ucycom_read,
140 : ucycom_write,
141 : };
142 :
143 : void ucycom_intr(struct uhidev *, void *, u_int);
144 :
145 : const struct usb_devno ucycom_devs[] = {
146 : { USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_USBRS232 },
147 : { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EMUSB },
148 : { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EMLT20 },
149 : };
150 :
151 : int ucycom_match(struct device *, void *, void *);
152 : void ucycom_attach(struct device *, struct device *, void *);
153 : int ucycom_detach(struct device *, int);
154 :
155 : struct cfdriver ucycom_cd = {
156 : NULL, "ucycom", DV_DULL
157 : };
158 :
159 : const struct cfattach ucycom_ca = {
160 : sizeof(struct ucycom_softc), ucycom_match, ucycom_attach, ucycom_detach
161 : };
162 :
163 : int
164 0 : ucycom_match(struct device *parent, void *match, void *aux)
165 : {
166 0 : struct uhidev_attach_arg *uha = aux;
167 :
168 0 : if (uha->reportid == UHIDEV_CLAIM_ALLREPORTID)
169 0 : return (UMATCH_NONE);
170 :
171 0 : return (usb_lookup(ucycom_devs, uha->uaa->vendor, uha->uaa->product) != NULL ?
172 : UMATCH_VENDOR_PRODUCT : UMATCH_NONE);
173 0 : }
174 :
175 : void
176 0 : ucycom_attach(struct device *parent, struct device *self, void *aux)
177 : {
178 0 : struct ucycom_softc *sc = (struct ucycom_softc *)self;
179 0 : struct usb_attach_arg *uaa = aux;
180 0 : struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)uaa;
181 0 : struct usbd_device *dev = uha->parent->sc_udev;
182 0 : struct ucom_attach_args uca;
183 0 : int size, repid, err;
184 0 : void *desc;
185 :
186 0 : sc->sc_hdev.sc_intr = ucycom_intr;
187 0 : sc->sc_hdev.sc_parent = uha->parent;
188 0 : sc->sc_hdev.sc_report_id = uha->reportid;
189 :
190 0 : uhidev_get_report_desc(uha->parent, &desc, &size);
191 0 : repid = uha->reportid;
192 0 : sc->sc_ilen = hid_report_size(desc, size, hid_input, repid);
193 0 : sc->sc_olen = hid_report_size(desc, size, hid_output, repid);
194 0 : sc->sc_flen = hid_report_size(desc, size, hid_feature, repid);
195 :
196 : DPRINTF(("ucycom_open: olen %d ilen %d flen %d\n", sc->sc_ilen,
197 : sc->sc_olen, sc->sc_flen));
198 :
199 0 : printf("\n");
200 :
201 0 : sc->sc_udev = dev;
202 :
203 0 : err = uhidev_open(&sc->sc_hdev);
204 0 : if (err) {
205 : DPRINTF(("ucycom_open: uhidev_open %d\n", err));
206 0 : return;
207 : }
208 :
209 : DPRINTF(("ucycom attach: sc %p opipe %p ipipe %p report_id %d\n",
210 : sc, sc->sc_hdev.sc_parent->sc_opipe, sc->sc_hdev.sc_parent->sc_ipipe,
211 : uha->reportid));
212 :
213 : /* bulkin, bulkout set above */
214 0 : bzero(&uca, sizeof uca);
215 0 : uca.bulkin = uca.bulkout = -1;
216 0 : uca.ibufsize = sc->sc_ilen - 1;
217 0 : uca.obufsize = sc->sc_olen - 1;
218 0 : uca.ibufsizepad = 1;
219 0 : uca.opkthdrlen = 0;
220 0 : uca.uhidev = sc->sc_hdev.sc_parent;
221 0 : uca.device = uaa->device;
222 0 : uca.iface = uaa->iface;
223 0 : uca.methods = &ucycom_methods;
224 0 : uca.arg = sc;
225 0 : uca.info = NULL;
226 :
227 0 : sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
228 : DPRINTF(("ucycom_attach: complete %p\n", sc->sc_subdev));
229 0 : }
230 :
231 : void
232 0 : ucycom_get_status(void *addr, int portno, u_char *lsr, u_char *msr)
233 : {
234 0 : struct ucycom_softc *sc = addr;
235 :
236 : DPRINTF(("ucycom_get_status:\n"));
237 :
238 : #if 0
239 : if (lsr != NULL)
240 : *lsr = sc->sc_lsr;
241 : #endif
242 0 : if (msr != NULL)
243 0 : *msr = sc->sc_msr;
244 0 : }
245 :
246 : int
247 0 : ucycom_open(void *addr, int portno)
248 : {
249 0 : struct ucycom_softc *sc = addr;
250 0 : struct termios t;
251 : int err;
252 :
253 : DPRINTF(("ucycom_open: complete\n"));
254 :
255 0 : if (usbd_is_dying(sc->sc_udev))
256 0 : return (EIO);
257 :
258 : /* Allocate an output report buffer */
259 0 : sc->sc_obuf = malloc(sc->sc_olen, M_USBDEV, M_WAITOK | M_ZERO);
260 :
261 : /* Allocate an input report buffer */
262 0 : sc->sc_ibuf = malloc(sc->sc_ilen, M_USBDEV, M_WAITOK);
263 :
264 : DPRINTF(("ucycom_open: sc->sc_ibuf=%p sc->sc_obuf=%p \n",
265 : sc->sc_ibuf, sc->sc_obuf));
266 :
267 0 : t.c_ospeed = 9600;
268 0 : t.c_cflag = CSTOPB | CS8;
269 0 : (void)ucycom_param(sc, portno, &t);
270 :
271 0 : sc->sc_mcr = UCYCOM_DTR | UCYCOM_RTS;
272 0 : sc->sc_obuf[0] = sc->sc_mcr;
273 0 : err = uhidev_write(sc->sc_hdev.sc_parent, sc->sc_obuf, sc->sc_olen);
274 0 : if (err) {
275 : DPRINTF(("ucycom_open: set RTS err=%d\n", err));
276 0 : return (EIO);
277 : }
278 :
279 0 : return (0);
280 0 : }
281 :
282 : void
283 0 : ucycom_close(void *addr, int portno)
284 : {
285 0 : struct ucycom_softc *sc = addr;
286 : int s;
287 :
288 0 : if (usbd_is_dying(sc->sc_udev))
289 0 : return;
290 :
291 0 : s = splusb();
292 0 : if (sc->sc_obuf != NULL) {
293 0 : free(sc->sc_obuf, M_USBDEV, sc->sc_olen);
294 0 : sc->sc_obuf = NULL;
295 0 : }
296 0 : if (sc->sc_ibuf != NULL) {
297 0 : free(sc->sc_ibuf, M_USBDEV, sc->sc_ilen);
298 0 : sc->sc_ibuf = NULL;
299 0 : }
300 0 : splx(s);
301 0 : }
302 :
303 : void
304 0 : ucycom_read(void *addr, int portno, u_char **ptr, u_int32_t *count)
305 : {
306 0 : struct ucycom_softc *sc = addr;
307 :
308 0 : if (sc->sc_newmsr ^ sc->sc_msr) {
309 : DPRINTF(("ucycom_read: msr %d new %d\n",
310 : sc->sc_msr, sc->sc_newmsr));
311 0 : sc->sc_msr = sc->sc_newmsr;
312 0 : ucom_status_change((struct ucom_softc *)sc->sc_subdev);
313 0 : }
314 :
315 : DPRINTF(("ucycom_read: buf %p chars %d\n", sc->sc_ibuf, sc->sc_icnt));
316 0 : *ptr = sc->sc_ibuf;
317 0 : *count = sc->sc_icnt;
318 0 : }
319 :
320 : void
321 0 : ucycom_write(void *addr, int portno, u_char *to, u_char *data, u_int32_t *cnt)
322 : {
323 0 : struct ucycom_softc *sc = addr;
324 : u_int32_t len;
325 : #ifdef UCYCOM_DEBUG
326 : u_int32_t want = *cnt;
327 : #endif
328 :
329 : /*
330 : * The 8 byte output report uses byte 0 for control and byte
331 : * count.
332 : *
333 : * The 32 byte output report uses byte 0 for control. Byte 1
334 : * is used for byte count.
335 : */
336 0 : len = sc->sc_olen;
337 0 : memset(to, 0, len);
338 0 : switch (sc->sc_olen) {
339 : case 8:
340 0 : to[0] = *cnt | sc->sc_mcr;
341 0 : memcpy(&to[1], data, *cnt);
342 : DPRINTF(("ucycomstart(8): to[0] = %d | %d = %d\n",
343 : *cnt, sc->sc_mcr, to[0]));
344 0 : break;
345 :
346 : case 32:
347 0 : to[0] = sc->sc_mcr;
348 0 : to[1] = *cnt;
349 0 : memcpy(&to[2], data, *cnt);
350 : DPRINTF(("ucycomstart(32): to[0] = %d\nto[1] = %d\n",
351 : to[0], to[1]));
352 0 : break;
353 : }
354 :
355 : #ifdef UCYCOM_DEBUG
356 : if (ucycomdebug > 5) {
357 : int i;
358 :
359 : if (len != 0) {
360 : DPRINTF(("ucycomstart: to[0..%d) =", len-1));
361 : for (i = 0; i < len; i++)
362 : DPRINTF((" %02x", to[i]));
363 : DPRINTF(("\n"));
364 : }
365 : }
366 : #endif
367 0 : *cnt = len;
368 :
369 : DPRINTFN(4,("ucycomstart: req %d chars did %d chars\n", want, len));
370 0 : }
371 :
372 : int
373 0 : ucycom_param(void *addr, int portno, struct termios *t)
374 : {
375 0 : struct ucycom_softc *sc = addr;
376 0 : uint8_t report[5];
377 : uint32_t baud = 0;
378 : uint8_t cfg;
379 :
380 0 : if (usbd_is_dying(sc->sc_udev))
381 0 : return (EIO);
382 :
383 0 : switch (t->c_ospeed) {
384 : case 600:
385 : case 1200:
386 : case 2400:
387 : case 4800:
388 : case 9600:
389 : case 19200:
390 : case 38400:
391 : case 57600:
392 : #if 0
393 : /*
394 : * Stock chips only support standard baud rates in the 600 - 57600
395 : * range, but higher rates can be achieved using custom firmware.
396 : */
397 : case 115200:
398 : case 153600:
399 : case 192000:
400 : #endif
401 0 : baud = t->c_ospeed;
402 : break;
403 : default:
404 0 : return (EINVAL);
405 : }
406 :
407 0 : if (t->c_cflag & CIGNORE) {
408 0 : cfg = sc->sc_cfg;
409 0 : } else {
410 : cfg = 0;
411 0 : switch (t->c_cflag & CSIZE) {
412 : case CS8:
413 : cfg |= UCYCOM_DATA_BITS_8;
414 0 : break;
415 : case CS7:
416 : cfg |= UCYCOM_DATA_BITS_7;
417 0 : break;
418 : case CS6:
419 : cfg |= UCYCOM_DATA_BITS_6;
420 0 : break;
421 : case CS5:
422 : cfg |= UCYCOM_DATA_BITS_5;
423 0 : break;
424 : default:
425 : return (EINVAL);
426 : }
427 0 : cfg |= ISSET(t->c_cflag, CSTOPB) ?
428 : UCYCOM_STOP_BITS_2 : UCYCOM_STOP_BITS_1;
429 0 : cfg |= ISSET(t->c_cflag, PARENB) ?
430 : UCYCOM_PARITY_ON : UCYCOM_PARITY_OFF;
431 0 : cfg |= ISSET(t->c_cflag, PARODD) ?
432 : UCYCOM_PARITY_ODD : UCYCOM_PARITY_EVEN;
433 : }
434 :
435 : DPRINTF(("ucycom_param: setting %d baud, %d-%c-%d (%d)\n", baud,
436 : 5 + (cfg & UCYCOM_DATA_MASK),
437 : (cfg & UCYCOM_PARITY_MASK) ?
438 : ((cfg & UCYCOM_PARITY_TYPE_MASK) ? 'O' : 'E') : 'N',
439 : (cfg & UCYCOM_STOP_MASK) ? 2 : 1, cfg));
440 :
441 0 : report[0] = baud & 0xff;
442 0 : report[1] = (baud >> 8) & 0xff;
443 0 : report[2] = (baud >> 16) & 0xff;
444 0 : report[3] = (baud >> 24) & 0xff;
445 0 : report[4] = cfg;
446 0 : if (uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
447 0 : sc->sc_hdev.sc_report_id, report, sc->sc_flen) != sc->sc_flen)
448 0 : return EIO;
449 0 : sc->sc_baud = baud;
450 0 : return (0);
451 0 : }
452 :
453 : void
454 0 : ucycom_intr(struct uhidev *addr, void *ibuf, u_int len)
455 : {
456 : extern void ucomreadcb(struct usbd_xfer *, void *, usbd_status);
457 0 : struct ucycom_softc *sc = (struct ucycom_softc *)addr;
458 : uint8_t *cp = ibuf;
459 : int n, st, s;
460 :
461 : /* not accepting data anymore.. */
462 0 : if (sc->sc_ibuf == NULL)
463 0 : return;
464 :
465 : /* We understand 8 byte and 32 byte input records */
466 0 : switch (len) {
467 : case 8:
468 0 : n = cp[0] & UCYCOM_LMASK;
469 0 : st = cp[0] & ~UCYCOM_LMASK;
470 0 : cp++;
471 0 : break;
472 :
473 : case 32:
474 0 : st = cp[0];
475 0 : n = cp[1];
476 0 : cp += 2;
477 0 : break;
478 :
479 : default:
480 : DPRINTFN(3,("ucycom_intr: Unknown input report length\n"));
481 0 : return;
482 : }
483 :
484 : #ifdef UCYCOM_DEBUG
485 : if (ucycomdebug > 5) {
486 : u_int32_t i;
487 :
488 : if (n != 0) {
489 : DPRINTF(("ucycom_intr: ibuf[0..%d) =", n));
490 : for (i = 0; i < n; i++)
491 : DPRINTF((" %02x", cp[i]));
492 : DPRINTF(("\n"));
493 : }
494 : }
495 : #endif
496 :
497 0 : if (n > 0 || st != sc->sc_msr) {
498 0 : s = spltty();
499 0 : sc->sc_newmsr = st;
500 0 : bcopy(cp, sc->sc_ibuf, n);
501 0 : sc->sc_icnt = n;
502 0 : ucomreadcb(addr->sc_parent->sc_ixfer, sc->sc_subdev,
503 : USBD_NORMAL_COMPLETION);
504 0 : splx(s);
505 0 : }
506 0 : }
507 :
508 : void
509 0 : ucycom_set(void *addr, int portno, int reg, int onoff)
510 : {
511 0 : struct ucycom_softc *sc = addr;
512 : int err;
513 :
514 0 : switch (reg) {
515 : case UCOM_SET_DTR:
516 0 : if (onoff)
517 0 : SET(sc->sc_mcr, UCYCOM_DTR);
518 : else
519 0 : CLR(sc->sc_mcr, UCYCOM_DTR);
520 : break;
521 : case UCOM_SET_RTS:
522 0 : if (onoff)
523 0 : SET(sc->sc_mcr, UCYCOM_RTS);
524 : else
525 0 : CLR(sc->sc_mcr, UCYCOM_RTS);
526 : break;
527 : case UCOM_SET_BREAK:
528 : break;
529 : }
530 :
531 0 : memset(sc->sc_obuf, 0, sc->sc_olen);
532 0 : sc->sc_obuf[0] = sc->sc_mcr;
533 :
534 0 : err = uhidev_write(sc->sc_hdev.sc_parent, sc->sc_obuf, sc->sc_olen);
535 : if (err)
536 : DPRINTF(("ucycom_set_status: err=%d\n", err));
537 0 : }
538 :
539 : int
540 0 : ucycom_detach(struct device *self, int flags)
541 : {
542 0 : struct ucycom_softc *sc = (struct ucycom_softc *)self;
543 :
544 : DPRINTF(("ucycom_detach: sc=%p flags=%d\n", sc, flags));
545 0 : if (sc->sc_subdev != NULL) {
546 0 : config_detach(sc->sc_subdev, flags);
547 0 : sc->sc_subdev = NULL;
548 0 : }
549 :
550 0 : if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
551 0 : uhidev_close(&sc->sc_hdev);
552 :
553 0 : return (0);
554 : }
|