1 | /* $NetBSD: uchcom.c,v 1.15 2016/07/07 06:55:42 msaitoh Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2007 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Takuya SHIOZAKI (tshiozak@netbsd.org). |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * 1. Redistributions of source code must retain the above copyright |
14 | * notice, this list of conditions and the following disclaimer. |
15 | * 2. Redistributions in binary form must reproduce the above copyright |
16 | * notice, this list of conditions and the following disclaimer in the |
17 | * documentation and/or other materials provided with the distribution. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 | * POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | |
32 | #include <sys/cdefs.h> |
33 | __KERNEL_RCSID(0, "$NetBSD: uchcom.c,v 1.15 2016/07/07 06:55:42 msaitoh Exp $" ); |
34 | |
35 | /* |
36 | * driver for WinChipHead CH341/340, the worst USB-serial chip in the world. |
37 | */ |
38 | |
39 | #include <sys/param.h> |
40 | #include <sys/systm.h> |
41 | #include <sys/kernel.h> |
42 | #include <sys/kmem.h> |
43 | #include <sys/ioctl.h> |
44 | #include <sys/conf.h> |
45 | #include <sys/tty.h> |
46 | #include <sys/file.h> |
47 | #include <sys/select.h> |
48 | #include <sys/proc.h> |
49 | #include <sys/device.h> |
50 | #include <sys/poll.h> |
51 | |
52 | #include <dev/usb/usb.h> |
53 | #include <dev/usb/usbcdc.h> |
54 | |
55 | #include <dev/usb/usbdi.h> |
56 | #include <dev/usb/usbdi_util.h> |
57 | #include <dev/usb/usbdevs.h> |
58 | #include <dev/usb/usb_quirks.h> |
59 | |
60 | #include <dev/usb/ucomvar.h> |
61 | |
62 | #ifdef UCHCOM_DEBUG |
63 | #define DPRINTFN(n, x) if (uchcomdebug > (n)) printf x |
64 | int uchcomdebug = 0; |
65 | #else |
66 | #define DPRINTFN(n, x) |
67 | #endif |
68 | #define DPRINTF(x) DPRINTFN(0, x) |
69 | |
70 | #define UCHCOM_IFACE_INDEX 0 |
71 | #define UCHCOM_CONFIG_INDEX 0 |
72 | |
73 | #define UCHCOM_REV_CH340 0x0250 |
74 | #define UCHCOM_INPUT_BUF_SIZE 8 |
75 | |
76 | #define UCHCOM_REQ_GET_VERSION 0x5F |
77 | #define UCHCOM_REQ_READ_REG 0x95 |
78 | #define UCHCOM_REQ_WRITE_REG 0x9A |
79 | #define UCHCOM_REQ_RESET 0xA1 |
80 | #define UCHCOM_REQ_SET_DTRRTS 0xA4 |
81 | |
82 | #define UCHCOM_REG_STAT1 0x06 |
83 | #define UCHCOM_REG_STAT2 0x07 |
84 | #define UCHCOM_REG_BPS_PRE 0x12 |
85 | #define UCHCOM_REG_BPS_DIV 0x13 |
86 | #define UCHCOM_REG_BPS_MOD 0x14 |
87 | #define UCHCOM_REG_BPS_PAD 0x0F |
88 | #define UCHCOM_REG_BREAK1 0x05 |
89 | #define UCHCOM_REG_BREAK2 0x18 |
90 | #define UCHCOM_REG_LCR1 0x18 |
91 | #define UCHCOM_REG_LCR2 0x25 |
92 | |
93 | #define UCHCOM_VER_20 0x20 |
94 | |
95 | #define UCHCOM_BASE_UNKNOWN 0 |
96 | #define UCHCOM_BPS_MOD_BASE 20000000 |
97 | #define UCHCOM_BPS_MOD_BASE_OFS 1100 |
98 | |
99 | #define UCHCOM_DTR_MASK 0x20 |
100 | #define UCHCOM_RTS_MASK 0x40 |
101 | |
102 | #define UCHCOM_BRK1_MASK 0x01 |
103 | #define UCHCOM_BRK2_MASK 0x40 |
104 | |
105 | #define UCHCOM_LCR1_MASK 0xAF |
106 | #define UCHCOM_LCR2_MASK 0x07 |
107 | #define UCHCOM_LCR1_PARENB 0x80 |
108 | #define UCHCOM_LCR2_PAREVEN 0x07 |
109 | #define UCHCOM_LCR2_PARODD 0x06 |
110 | #define UCHCOM_LCR2_PARMARK 0x05 |
111 | #define UCHCOM_LCR2_PARSPACE 0x04 |
112 | |
113 | #define UCHCOM_INTR_STAT1 0x02 |
114 | #define UCHCOM_INTR_STAT2 0x03 |
115 | #define UCHCOM_INTR_LEAST 4 |
116 | |
117 | #define UCHCOMIBUFSIZE 256 |
118 | #define UCHCOMOBUFSIZE 256 |
119 | |
120 | struct uchcom_softc |
121 | { |
122 | device_t sc_dev; |
123 | struct usbd_device * sc_udev; |
124 | device_t sc_subdev; |
125 | struct usbd_interface * sc_iface; |
126 | int sc_dying; |
127 | /* */ |
128 | int sc_intr_endpoint; |
129 | int sc_intr_size; |
130 | struct usbd_pipe * sc_intr_pipe; |
131 | u_char *sc_intr_buf; |
132 | /* */ |
133 | uint8_t sc_version; |
134 | int sc_dtr; |
135 | int sc_rts; |
136 | u_char sc_lsr; |
137 | u_char sc_msr; |
138 | int sc_lcr1; |
139 | int sc_lcr2; |
140 | }; |
141 | |
142 | struct uchcom_endpoints |
143 | { |
144 | int ep_bulkin; |
145 | int ep_bulkout; |
146 | int ep_intr; |
147 | int ep_intr_size; |
148 | }; |
149 | |
150 | struct uchcom_divider |
151 | { |
152 | uint8_t dv_prescaler; |
153 | uint8_t dv_div; |
154 | uint8_t dv_mod; |
155 | }; |
156 | |
157 | struct uchcom_divider_record |
158 | { |
159 | uint32_t dvr_high; |
160 | uint32_t dvr_low; |
161 | uint32_t dvr_base_clock; |
162 | struct uchcom_divider dvr_divider; |
163 | }; |
164 | |
165 | static const struct uchcom_divider_record dividers[] = |
166 | { |
167 | { 307200, 307200, UCHCOM_BASE_UNKNOWN, { 7, 0xD9, 0 } }, |
168 | { 921600, 921600, UCHCOM_BASE_UNKNOWN, { 7, 0xF3, 0 } }, |
169 | { 2999999, 23530, 6000000, { 3, 0, 0 } }, |
170 | { 23529, 2942, 750000, { 2, 0, 0 } }, |
171 | { 2941, 368, 93750, { 1, 0, 0 } }, |
172 | { 367, 1, 11719, { 0, 0, 0 } }, |
173 | }; |
174 | #define NUM_DIVIDERS (sizeof (dividers) / sizeof (dividers[0])) |
175 | |
176 | static const struct usb_devno uchcom_devs[] = { |
177 | { USB_VENDOR_WINCHIPHEAD, USB_PRODUCT_WINCHIPHEAD_CH341SER }, |
178 | { USB_VENDOR_WINCHIPHEAD2, USB_PRODUCT_WINCHIPHEAD2_CH341 }, |
179 | }; |
180 | #define uchcom_lookup(v, p) usb_lookup(uchcom_devs, v, p) |
181 | |
182 | Static void uchcom_get_status(void *, int, u_char *, u_char *); |
183 | Static void uchcom_set(void *, int, int, int); |
184 | Static int uchcom_param(void *, int, struct termios *); |
185 | Static int uchcom_open(void *, int); |
186 | Static void uchcom_close(void *, int); |
187 | Static void uchcom_intr(struct usbd_xfer *, void *, |
188 | usbd_status); |
189 | |
190 | static int set_config(struct uchcom_softc *); |
191 | static int find_ifaces(struct uchcom_softc *, struct usbd_interface **); |
192 | static int find_endpoints(struct uchcom_softc *, |
193 | struct uchcom_endpoints *); |
194 | static void close_intr_pipe(struct uchcom_softc *); |
195 | |
196 | |
197 | struct ucom_methods uchcom_methods = { |
198 | .ucom_get_status = uchcom_get_status, |
199 | .ucom_set = uchcom_set, |
200 | .ucom_param = uchcom_param, |
201 | .ucom_ioctl = NULL, |
202 | .ucom_open = uchcom_open, |
203 | .ucom_close = uchcom_close, |
204 | .ucom_read = NULL, |
205 | .ucom_write = NULL, |
206 | }; |
207 | |
208 | int uchcom_match(device_t, cfdata_t, void *); |
209 | void uchcom_attach(device_t, device_t, void *); |
210 | void uchcom_childdet(device_t, device_t); |
211 | int uchcom_detach(device_t, int); |
212 | int uchcom_activate(device_t, enum devact); |
213 | |
214 | extern struct cfdriver uchcom_cd; |
215 | |
216 | CFATTACH_DECL2_NEW(uchcom, |
217 | sizeof(struct uchcom_softc), |
218 | uchcom_match, |
219 | uchcom_attach, |
220 | uchcom_detach, |
221 | uchcom_activate, |
222 | NULL, |
223 | uchcom_childdet); |
224 | |
225 | /* ---------------------------------------------------------------------- |
226 | * driver entry points |
227 | */ |
228 | |
229 | int |
230 | uchcom_match(device_t parent, cfdata_t match, void *aux) |
231 | { |
232 | struct usb_attach_arg *uaa = aux; |
233 | |
234 | return (uchcom_lookup(uaa->uaa_vendor, uaa->uaa_product) != NULL ? |
235 | UMATCH_VENDOR_PRODUCT : UMATCH_NONE); |
236 | } |
237 | |
238 | void |
239 | uchcom_attach(device_t parent, device_t self, void *aux) |
240 | { |
241 | struct uchcom_softc *sc = device_private(self); |
242 | struct usb_attach_arg *uaa = aux; |
243 | struct usbd_device *dev = uaa->uaa_device; |
244 | char *devinfop; |
245 | struct uchcom_endpoints endpoints; |
246 | struct ucom_attach_args ucaa; |
247 | |
248 | aprint_naive("\n" ); |
249 | aprint_normal("\n" ); |
250 | |
251 | devinfop = usbd_devinfo_alloc(dev, 0); |
252 | aprint_normal_dev(self, "%s\n" , devinfop); |
253 | usbd_devinfo_free(devinfop); |
254 | |
255 | sc->sc_dev = self; |
256 | sc->sc_udev = dev; |
257 | sc->sc_dying = 0; |
258 | sc->sc_dtr = sc->sc_rts = -1; |
259 | sc->sc_lsr = sc->sc_msr = 0; |
260 | |
261 | DPRINTF(("\n\nuchcom attach: sc=%p\n" , sc)); |
262 | |
263 | if (set_config(sc)) |
264 | goto failed; |
265 | |
266 | switch (uaa->uaa_release) { |
267 | case UCHCOM_REV_CH340: |
268 | aprint_normal_dev(self, "CH340 detected\n" ); |
269 | break; |
270 | default: |
271 | aprint_normal_dev(self, "CH341 detected\n" ); |
272 | break; |
273 | } |
274 | |
275 | if (find_ifaces(sc, &sc->sc_iface)) |
276 | goto failed; |
277 | |
278 | if (find_endpoints(sc, &endpoints)) |
279 | goto failed; |
280 | |
281 | sc->sc_intr_endpoint = endpoints.ep_intr; |
282 | sc->sc_intr_size = endpoints.ep_intr_size; |
283 | |
284 | /* setup ucom layer */ |
285 | ucaa.ucaa_portno = UCOM_UNK_PORTNO; |
286 | ucaa.ucaa_bulkin = endpoints.ep_bulkin; |
287 | ucaa.ucaa_bulkout = endpoints.ep_bulkout; |
288 | ucaa.ucaa_ibufsize = UCHCOMIBUFSIZE; |
289 | ucaa.ucaa_obufsize = UCHCOMOBUFSIZE; |
290 | ucaa.ucaa_ibufsizepad = UCHCOMIBUFSIZE; |
291 | ucaa.ucaa_opkthdrlen = 0; |
292 | ucaa.ucaa_device = dev; |
293 | ucaa.ucaa_iface = sc->sc_iface; |
294 | ucaa.ucaa_methods = &uchcom_methods; |
295 | ucaa.ucaa_arg = sc; |
296 | ucaa.ucaa_info = NULL; |
297 | |
298 | usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); |
299 | |
300 | sc->sc_subdev = config_found_sm_loc(self, "ucombus" , NULL, &ucaa, |
301 | ucomprint, ucomsubmatch); |
302 | |
303 | return; |
304 | |
305 | failed: |
306 | sc->sc_dying = 1; |
307 | return; |
308 | } |
309 | |
310 | void |
311 | uchcom_childdet(device_t self, device_t child) |
312 | { |
313 | struct uchcom_softc *sc = device_private(self); |
314 | |
315 | KASSERT(sc->sc_subdev == child); |
316 | sc->sc_subdev = NULL; |
317 | } |
318 | |
319 | int |
320 | uchcom_detach(device_t self, int flags) |
321 | { |
322 | struct uchcom_softc *sc = device_private(self); |
323 | int rv = 0; |
324 | |
325 | DPRINTF(("uchcom_detach: sc=%p flags=%d\n" , sc, flags)); |
326 | |
327 | close_intr_pipe(sc); |
328 | |
329 | sc->sc_dying = 1; |
330 | |
331 | if (sc->sc_subdev != NULL) |
332 | rv = config_detach(sc->sc_subdev, flags); |
333 | |
334 | usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); |
335 | |
336 | return rv; |
337 | } |
338 | |
339 | int |
340 | uchcom_activate(device_t self, enum devact act) |
341 | { |
342 | struct uchcom_softc *sc = device_private(self); |
343 | |
344 | switch (act) { |
345 | case DVACT_DEACTIVATE: |
346 | close_intr_pipe(sc); |
347 | sc->sc_dying = 1; |
348 | return 0; |
349 | default: |
350 | return EOPNOTSUPP; |
351 | } |
352 | } |
353 | |
354 | static int |
355 | set_config(struct uchcom_softc *sc) |
356 | { |
357 | usbd_status err; |
358 | |
359 | err = usbd_set_config_index(sc->sc_udev, UCHCOM_CONFIG_INDEX, 1); |
360 | if (err) { |
361 | aprint_error_dev(sc->sc_dev, |
362 | "failed to set configuration: %s\n" , usbd_errstr(err)); |
363 | return -1; |
364 | } |
365 | |
366 | return 0; |
367 | } |
368 | |
369 | static int |
370 | find_ifaces(struct uchcom_softc *sc, struct usbd_interface **riface) |
371 | { |
372 | usbd_status err; |
373 | |
374 | err = usbd_device2interface_handle(sc->sc_udev, UCHCOM_IFACE_INDEX, |
375 | riface); |
376 | if (err) { |
377 | aprint_error("\n%s: failed to get interface: %s\n" , |
378 | device_xname(sc->sc_dev), usbd_errstr(err)); |
379 | return -1; |
380 | } |
381 | |
382 | return 0; |
383 | } |
384 | |
385 | static int |
386 | find_endpoints(struct uchcom_softc *sc, struct uchcom_endpoints *endpoints) |
387 | { |
388 | int i, bin=-1, bout=-1, intr=-1, isize=0; |
389 | usb_interface_descriptor_t *id; |
390 | usb_endpoint_descriptor_t *ed; |
391 | |
392 | id = usbd_get_interface_descriptor(sc->sc_iface); |
393 | |
394 | for (i = 0; i < id->bNumEndpoints; i++) { |
395 | ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i); |
396 | if (ed == NULL) { |
397 | aprint_error_dev(sc->sc_dev, |
398 | "no endpoint descriptor for %d\n" , i); |
399 | return -1; |
400 | } |
401 | |
402 | if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && |
403 | UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { |
404 | intr = ed->bEndpointAddress; |
405 | isize = UGETW(ed->wMaxPacketSize); |
406 | } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && |
407 | UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { |
408 | bin = ed->bEndpointAddress; |
409 | } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && |
410 | UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { |
411 | bout = ed->bEndpointAddress; |
412 | } |
413 | } |
414 | |
415 | if (intr == -1 || bin == -1 || bout == -1) { |
416 | if (intr == -1) { |
417 | aprint_error_dev(sc->sc_dev, |
418 | "no interrupt end point\n" ); |
419 | } |
420 | if (bin == -1) { |
421 | aprint_error_dev(sc->sc_dev, |
422 | "no data bulk in end point\n" ); |
423 | } |
424 | if (bout == -1) { |
425 | aprint_error_dev(sc->sc_dev, |
426 | "no data bulk out end point\n" ); |
427 | } |
428 | return -1; |
429 | } |
430 | if (isize < UCHCOM_INTR_LEAST) { |
431 | aprint_error_dev(sc->sc_dev, "intr pipe is too short\n" ); |
432 | return -1; |
433 | } |
434 | |
435 | DPRINTF(("%s: bulkin=%d, bulkout=%d, intr=%d, isize=%d\n" , |
436 | device_xname(sc->sc_dev), bin, bout, intr, isize)); |
437 | |
438 | endpoints->ep_intr = intr; |
439 | endpoints->ep_intr_size = isize; |
440 | endpoints->ep_bulkin = bin; |
441 | endpoints->ep_bulkout = bout; |
442 | |
443 | return 0; |
444 | } |
445 | |
446 | |
447 | /* ---------------------------------------------------------------------- |
448 | * low level i/o |
449 | */ |
450 | |
451 | static __inline usbd_status |
452 | generic_control_out(struct uchcom_softc *sc, uint8_t reqno, |
453 | uint16_t value, uint16_t index) |
454 | { |
455 | usb_device_request_t req; |
456 | |
457 | req.bmRequestType = UT_WRITE_VENDOR_DEVICE; |
458 | req.bRequest = reqno; |
459 | USETW(req.wValue, value); |
460 | USETW(req.wIndex, index); |
461 | USETW(req.wLength, 0); |
462 | |
463 | return usbd_do_request(sc->sc_udev, &req, 0); |
464 | } |
465 | |
466 | static __inline usbd_status |
467 | generic_control_in(struct uchcom_softc *sc, uint8_t reqno, |
468 | uint16_t value, uint16_t index, void *buf, int buflen, |
469 | int *actlen) |
470 | { |
471 | usb_device_request_t req; |
472 | |
473 | req.bmRequestType = UT_READ_VENDOR_DEVICE; |
474 | req.bRequest = reqno; |
475 | USETW(req.wValue, value); |
476 | USETW(req.wIndex, index); |
477 | USETW(req.wLength, (uint16_t)buflen); |
478 | |
479 | return usbd_do_request_flags(sc->sc_udev, &req, buf, |
480 | USBD_SHORT_XFER_OK, actlen, |
481 | USBD_DEFAULT_TIMEOUT); |
482 | } |
483 | |
484 | static __inline usbd_status |
485 | write_reg(struct uchcom_softc *sc, |
486 | uint8_t reg1, uint8_t val1, uint8_t reg2, uint8_t val2) |
487 | { |
488 | DPRINTF(("uchcom: write reg 0x%02X<-0x%02X, 0x%02X<-0x%02X\n" , |
489 | (unsigned)reg1, (unsigned)val1, |
490 | (unsigned)reg2, (unsigned)val2)); |
491 | return generic_control_out( |
492 | sc, UCHCOM_REQ_WRITE_REG, |
493 | reg1|((uint16_t)reg2<<8), val1|((uint16_t)val2<<8)); |
494 | } |
495 | |
496 | static __inline usbd_status |
497 | read_reg(struct uchcom_softc *sc, |
498 | uint8_t reg1, uint8_t *rval1, uint8_t reg2, uint8_t *rval2) |
499 | { |
500 | uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; |
501 | usbd_status err; |
502 | int actin; |
503 | |
504 | err = generic_control_in( |
505 | sc, UCHCOM_REQ_READ_REG, |
506 | reg1|((uint16_t)reg2<<8), 0, buf, sizeof(buf), &actin); |
507 | if (err) |
508 | return err; |
509 | |
510 | DPRINTF(("uchcom: read reg 0x%02X->0x%02X, 0x%02X->0x%02X\n" , |
511 | (unsigned)reg1, (unsigned)buf[0], |
512 | (unsigned)reg2, (unsigned)buf[1])); |
513 | |
514 | if (rval1) *rval1 = buf[0]; |
515 | if (rval2) *rval2 = buf[1]; |
516 | |
517 | return USBD_NORMAL_COMPLETION; |
518 | } |
519 | |
520 | static __inline usbd_status |
521 | get_version(struct uchcom_softc *sc, uint8_t *rver) |
522 | { |
523 | uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; |
524 | usbd_status err; |
525 | int actin; |
526 | |
527 | err = generic_control_in( |
528 | sc, UCHCOM_REQ_GET_VERSION, 0, 0, buf, sizeof(buf), &actin); |
529 | if (err) |
530 | return err; |
531 | |
532 | if (rver) *rver = buf[0]; |
533 | |
534 | return USBD_NORMAL_COMPLETION; |
535 | } |
536 | |
537 | static __inline usbd_status |
538 | get_status(struct uchcom_softc *sc, uint8_t *rval) |
539 | { |
540 | return read_reg(sc, UCHCOM_REG_STAT1, rval, UCHCOM_REG_STAT2, NULL); |
541 | } |
542 | |
543 | static __inline usbd_status |
544 | set_dtrrts_10(struct uchcom_softc *sc, uint8_t val) |
545 | { |
546 | return write_reg(sc, UCHCOM_REG_STAT1, val, UCHCOM_REG_STAT1, val); |
547 | } |
548 | |
549 | static __inline usbd_status |
550 | set_dtrrts_20(struct uchcom_softc *sc, uint8_t val) |
551 | { |
552 | return generic_control_out(sc, UCHCOM_REQ_SET_DTRRTS, val, 0); |
553 | } |
554 | |
555 | |
556 | /* ---------------------------------------------------------------------- |
557 | * middle layer |
558 | */ |
559 | |
560 | static int |
561 | update_version(struct uchcom_softc *sc) |
562 | { |
563 | usbd_status err; |
564 | |
565 | err = get_version(sc, &sc->sc_version); |
566 | if (err) { |
567 | aprint_error_dev(sc->sc_dev, "cannot get version: %s\n" , |
568 | usbd_errstr(err)); |
569 | return EIO; |
570 | } |
571 | |
572 | return 0; |
573 | } |
574 | |
575 | static void |
576 | convert_status(struct uchcom_softc *sc, uint8_t cur) |
577 | { |
578 | sc->sc_dtr = !(cur & UCHCOM_DTR_MASK); |
579 | sc->sc_rts = !(cur & UCHCOM_RTS_MASK); |
580 | |
581 | cur = ~cur & 0x0F; |
582 | sc->sc_msr = (cur << 4) | ((sc->sc_msr >> 4) ^ cur); |
583 | } |
584 | |
585 | static int |
586 | update_status(struct uchcom_softc *sc) |
587 | { |
588 | usbd_status err; |
589 | uint8_t cur; |
590 | |
591 | err = get_status(sc, &cur); |
592 | if (err) { |
593 | aprint_error_dev(sc->sc_dev, |
594 | "cannot update status: %s\n" , usbd_errstr(err)); |
595 | return EIO; |
596 | } |
597 | convert_status(sc, cur); |
598 | |
599 | return 0; |
600 | } |
601 | |
602 | |
603 | static int |
604 | set_dtrrts(struct uchcom_softc *sc, int dtr, int rts) |
605 | { |
606 | usbd_status err; |
607 | uint8_t val = 0; |
608 | |
609 | if (dtr) val |= UCHCOM_DTR_MASK; |
610 | if (rts) val |= UCHCOM_RTS_MASK; |
611 | |
612 | if (sc->sc_version < UCHCOM_VER_20) |
613 | err = set_dtrrts_10(sc, ~val); |
614 | else |
615 | err = set_dtrrts_20(sc, ~val); |
616 | |
617 | if (err) { |
618 | aprint_error_dev(sc->sc_dev, "cannot set DTR/RTS: %s\n" , |
619 | usbd_errstr(err)); |
620 | return EIO; |
621 | } |
622 | |
623 | return 0; |
624 | } |
625 | |
626 | static int |
627 | set_break(struct uchcom_softc *sc, int onoff) |
628 | { |
629 | usbd_status err; |
630 | uint8_t brk1, brk2; |
631 | |
632 | err = read_reg(sc, UCHCOM_REG_BREAK1, &brk1, UCHCOM_REG_BREAK2, &brk2); |
633 | if (err) |
634 | return EIO; |
635 | if (onoff) { |
636 | /* on - clear bits */ |
637 | brk1 &= ~UCHCOM_BRK1_MASK; |
638 | brk2 &= ~UCHCOM_BRK2_MASK; |
639 | } else { |
640 | /* off - set bits */ |
641 | brk1 |= UCHCOM_BRK1_MASK; |
642 | brk2 |= UCHCOM_BRK2_MASK; |
643 | } |
644 | err = write_reg(sc, UCHCOM_REG_BREAK1, brk1, UCHCOM_REG_BREAK2, brk2); |
645 | if (err) |
646 | return EIO; |
647 | |
648 | return 0; |
649 | } |
650 | |
651 | static int |
652 | calc_divider_settings(struct uchcom_divider *dp, uint32_t rate) |
653 | { |
654 | int i; |
655 | const struct uchcom_divider_record *rp; |
656 | uint32_t div, rem, mod; |
657 | |
658 | /* find record */ |
659 | for (i=0; i<NUM_DIVIDERS; i++) { |
660 | if (dividers[i].dvr_high >= rate && |
661 | dividers[i].dvr_low <= rate) { |
662 | rp = ÷rs[i]; |
663 | goto found; |
664 | } |
665 | } |
666 | return -1; |
667 | |
668 | found: |
669 | dp->dv_prescaler = rp->dvr_divider.dv_prescaler; |
670 | if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN) |
671 | dp->dv_div = rp->dvr_divider.dv_div; |
672 | else { |
673 | div = rp->dvr_base_clock / rate; |
674 | rem = rp->dvr_base_clock % rate; |
675 | if (div==0 || div>=0xFF) |
676 | return -1; |
677 | if ((rem<<1) >= rate) |
678 | div += 1; |
679 | dp->dv_div = (uint8_t)-div; |
680 | } |
681 | |
682 | mod = UCHCOM_BPS_MOD_BASE/rate + UCHCOM_BPS_MOD_BASE_OFS; |
683 | mod = mod + mod/2; |
684 | |
685 | dp->dv_mod = mod / 0x100; |
686 | |
687 | return 0; |
688 | } |
689 | |
690 | static int |
691 | set_dte_rate(struct uchcom_softc *sc, uint32_t rate) |
692 | { |
693 | usbd_status err; |
694 | struct uchcom_divider dv; |
695 | |
696 | if (calc_divider_settings(&dv, rate)) |
697 | return EINVAL; |
698 | |
699 | if ((err = write_reg(sc, |
700 | UCHCOM_REG_BPS_PRE, dv.dv_prescaler, |
701 | UCHCOM_REG_BPS_DIV, dv.dv_div)) || |
702 | (err = write_reg(sc, |
703 | UCHCOM_REG_BPS_MOD, dv.dv_mod, |
704 | UCHCOM_REG_BPS_PAD, 0))) { |
705 | aprint_error_dev(sc->sc_dev, "cannot set DTE rate: %s\n" , |
706 | usbd_errstr(err)); |
707 | return EIO; |
708 | } |
709 | |
710 | return 0; |
711 | } |
712 | |
713 | static int |
714 | set_line_control(struct uchcom_softc *sc, tcflag_t cflag) |
715 | { |
716 | usbd_status err; |
717 | uint8_t lcr1val = 0, lcr2val = 0; |
718 | |
719 | err = read_reg(sc, UCHCOM_REG_LCR1, &lcr1val, UCHCOM_REG_LCR2, &lcr2val); |
720 | if (err) { |
721 | aprint_error_dev(sc->sc_dev, "cannot get LCR: %s\n" , |
722 | usbd_errstr(err)); |
723 | return EIO; |
724 | } |
725 | |
726 | lcr1val &= ~UCHCOM_LCR1_MASK; |
727 | lcr2val &= ~UCHCOM_LCR2_MASK; |
728 | |
729 | /* |
730 | * XXX: it is difficult to handle the line control appropriately: |
731 | * - CS8, !CSTOPB and any parity mode seems ok, but |
732 | * - the chip doesn't have the function to calculate parity |
733 | * in !CS8 mode. |
734 | * - it is unclear that the chip supports CS5,6 mode. |
735 | * - it is unclear how to handle stop bits. |
736 | */ |
737 | |
738 | switch (ISSET(cflag, CSIZE)) { |
739 | case CS5: |
740 | case CS6: |
741 | case CS7: |
742 | return EINVAL; |
743 | case CS8: |
744 | break; |
745 | } |
746 | |
747 | if (ISSET(cflag, PARENB)) { |
748 | lcr1val |= UCHCOM_LCR1_PARENB; |
749 | if (ISSET(cflag, PARODD)) |
750 | lcr2val |= UCHCOM_LCR2_PARODD; |
751 | else |
752 | lcr2val |= UCHCOM_LCR2_PAREVEN; |
753 | } |
754 | |
755 | err = write_reg(sc, UCHCOM_REG_LCR1, lcr1val, UCHCOM_REG_LCR2, lcr2val); |
756 | if (err) { |
757 | aprint_error_dev(sc->sc_dev, "cannot set LCR: %s\n" , |
758 | usbd_errstr(err)); |
759 | return EIO; |
760 | } |
761 | |
762 | return 0; |
763 | } |
764 | |
765 | static int |
766 | clear_chip(struct uchcom_softc *sc) |
767 | { |
768 | usbd_status err; |
769 | |
770 | DPRINTF(("%s: clear\n" , device_xname(sc->sc_dev))); |
771 | err = generic_control_out(sc, UCHCOM_REQ_RESET, 0, 0); |
772 | if (err) { |
773 | aprint_error_dev(sc->sc_dev, "cannot clear: %s\n" , |
774 | usbd_errstr(err)); |
775 | return EIO; |
776 | } |
777 | |
778 | return 0; |
779 | } |
780 | |
781 | static int |
782 | reset_chip(struct uchcom_softc *sc) |
783 | { |
784 | usbd_status err; |
785 | uint8_t lcr1val, lcr2val, pre, div, mod; |
786 | uint16_t val=0, idx=0; |
787 | |
788 | err = read_reg(sc, UCHCOM_REG_LCR1, &lcr1val, UCHCOM_REG_LCR2, &lcr2val); |
789 | if (err) |
790 | goto failed; |
791 | |
792 | err = read_reg(sc, UCHCOM_REG_BPS_PRE, &pre, UCHCOM_REG_BPS_DIV, &div); |
793 | if (err) |
794 | goto failed; |
795 | |
796 | err = read_reg(sc, UCHCOM_REG_BPS_MOD, &mod, UCHCOM_REG_BPS_PAD, NULL); |
797 | if (err) |
798 | goto failed; |
799 | |
800 | val |= (uint16_t)(lcr1val&0xF0) << 8; |
801 | val |= 0x01; |
802 | val |= (uint16_t)(lcr2val&0x0F) << 8; |
803 | val |= 0x02; |
804 | idx |= pre & 0x07; |
805 | val |= 0x04; |
806 | idx |= (uint16_t)div << 8; |
807 | val |= 0x08; |
808 | idx |= mod & 0xF8; |
809 | val |= 0x10; |
810 | |
811 | DPRINTF(("%s: reset v=0x%04X, i=0x%04X\n" , |
812 | device_xname(sc->sc_dev), val, idx)); |
813 | |
814 | err = generic_control_out(sc, UCHCOM_REQ_RESET, val, idx); |
815 | if (err) |
816 | goto failed; |
817 | |
818 | return 0; |
819 | |
820 | failed: |
821 | printf("%s: cannot reset: %s\n" , |
822 | device_xname(sc->sc_dev), usbd_errstr(err)); |
823 | return EIO; |
824 | } |
825 | |
826 | static int |
827 | setup_comm(struct uchcom_softc *sc) |
828 | { |
829 | int ret; |
830 | |
831 | ret = update_version(sc); |
832 | if (ret) |
833 | return ret; |
834 | |
835 | ret = clear_chip(sc); |
836 | if (ret) |
837 | return ret; |
838 | |
839 | ret = set_dte_rate(sc, TTYDEF_SPEED); |
840 | if (ret) |
841 | return ret; |
842 | |
843 | ret = set_line_control(sc, CS8); |
844 | if (ret) |
845 | return ret; |
846 | |
847 | ret = update_status(sc); |
848 | if (ret) |
849 | return ret; |
850 | |
851 | ret = reset_chip(sc); |
852 | if (ret) |
853 | return ret; |
854 | |
855 | ret = set_dte_rate(sc, TTYDEF_SPEED); /* XXX */ |
856 | if (ret) |
857 | return ret; |
858 | |
859 | sc->sc_dtr = sc->sc_rts = 1; |
860 | ret = set_dtrrts(sc, sc->sc_dtr, sc->sc_rts); |
861 | if (ret) |
862 | return ret; |
863 | |
864 | return 0; |
865 | } |
866 | |
867 | static int |
868 | setup_intr_pipe(struct uchcom_softc *sc) |
869 | { |
870 | usbd_status err; |
871 | |
872 | if (sc->sc_intr_endpoint != -1 && sc->sc_intr_pipe == NULL) { |
873 | sc->sc_intr_buf = kmem_alloc(sc->sc_intr_size, KM_SLEEP); |
874 | err = usbd_open_pipe_intr(sc->sc_iface, |
875 | sc->sc_intr_endpoint, |
876 | USBD_SHORT_XFER_OK, |
877 | &sc->sc_intr_pipe, sc, |
878 | sc->sc_intr_buf, |
879 | sc->sc_intr_size, |
880 | uchcom_intr, USBD_DEFAULT_INTERVAL); |
881 | if (err) { |
882 | aprint_error_dev(sc->sc_dev, |
883 | "cannot open interrupt pipe: %s\n" , |
884 | usbd_errstr(err)); |
885 | return EIO; |
886 | } |
887 | } |
888 | return 0; |
889 | } |
890 | |
891 | static void |
892 | close_intr_pipe(struct uchcom_softc *sc) |
893 | { |
894 | usbd_status err; |
895 | |
896 | if (sc->sc_dying) |
897 | return; |
898 | |
899 | if (sc->sc_intr_pipe != NULL) { |
900 | err = usbd_abort_pipe(sc->sc_intr_pipe); |
901 | if (err) |
902 | aprint_error_dev(sc->sc_dev, |
903 | "abort interrupt pipe failed: %s\n" , |
904 | usbd_errstr(err)); |
905 | err = usbd_close_pipe(sc->sc_intr_pipe); |
906 | if (err) |
907 | aprint_error_dev(sc->sc_dev, |
908 | "close interrupt pipe failed: %s\n" , |
909 | usbd_errstr(err)); |
910 | kmem_free(sc->sc_intr_buf, sc->sc_intr_size); |
911 | sc->sc_intr_pipe = NULL; |
912 | } |
913 | } |
914 | |
915 | |
916 | /* ---------------------------------------------------------------------- |
917 | * methods for ucom |
918 | */ |
919 | void |
920 | uchcom_get_status(void *arg, int portno, u_char *rlsr, u_char *rmsr) |
921 | { |
922 | struct uchcom_softc *sc = arg; |
923 | |
924 | if (sc->sc_dying) |
925 | return; |
926 | |
927 | *rlsr = sc->sc_lsr; |
928 | *rmsr = sc->sc_msr; |
929 | } |
930 | |
931 | void |
932 | uchcom_set(void *arg, int portno, int reg, int onoff) |
933 | { |
934 | struct uchcom_softc *sc = arg; |
935 | |
936 | if (sc->sc_dying) |
937 | return; |
938 | |
939 | switch (reg) { |
940 | case UCOM_SET_DTR: |
941 | sc->sc_dtr = !!onoff; |
942 | set_dtrrts(sc, sc->sc_dtr, sc->sc_rts); |
943 | break; |
944 | case UCOM_SET_RTS: |
945 | sc->sc_rts = !!onoff; |
946 | set_dtrrts(sc, sc->sc_dtr, sc->sc_rts); |
947 | break; |
948 | case UCOM_SET_BREAK: |
949 | set_break(sc, onoff); |
950 | break; |
951 | } |
952 | } |
953 | |
954 | int |
955 | uchcom_param(void *arg, int portno, struct termios *t) |
956 | { |
957 | struct uchcom_softc *sc = arg; |
958 | int ret; |
959 | |
960 | if (sc->sc_dying) |
961 | return 0; |
962 | |
963 | ret = set_line_control(sc, t->c_cflag); |
964 | if (ret) |
965 | return ret; |
966 | |
967 | ret = set_dte_rate(sc, t->c_ospeed); |
968 | if (ret) |
969 | return ret; |
970 | |
971 | return 0; |
972 | } |
973 | |
974 | int |
975 | uchcom_open(void *arg, int portno) |
976 | { |
977 | int ret; |
978 | struct uchcom_softc *sc = arg; |
979 | |
980 | if (sc->sc_dying) |
981 | return EIO; |
982 | |
983 | ret = setup_intr_pipe(sc); |
984 | if (ret) |
985 | return ret; |
986 | |
987 | ret = setup_comm(sc); |
988 | if (ret) |
989 | return ret; |
990 | |
991 | return 0; |
992 | } |
993 | |
994 | void |
995 | uchcom_close(void *arg, int portno) |
996 | { |
997 | struct uchcom_softc *sc = arg; |
998 | |
999 | if (sc->sc_dying) |
1000 | return; |
1001 | |
1002 | close_intr_pipe(sc); |
1003 | } |
1004 | |
1005 | |
1006 | /* ---------------------------------------------------------------------- |
1007 | * callback when the modem status is changed. |
1008 | */ |
1009 | void |
1010 | uchcom_intr(struct usbd_xfer *xfer, void * priv, |
1011 | usbd_status status) |
1012 | { |
1013 | struct uchcom_softc *sc = priv; |
1014 | u_char *buf = sc->sc_intr_buf; |
1015 | |
1016 | if (sc->sc_dying) |
1017 | return; |
1018 | |
1019 | if (status != USBD_NORMAL_COMPLETION) { |
1020 | if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) |
1021 | return; |
1022 | |
1023 | DPRINTF(("%s: abnormal status: %s\n" , |
1024 | device_xname(sc->sc_dev), usbd_errstr(status))); |
1025 | usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); |
1026 | return; |
1027 | } |
1028 | DPRINTF(("%s: intr: 0x%02X 0x%02X 0x%02X 0x%02X " |
1029 | "0x%02X 0x%02X 0x%02X 0x%02X\n" , |
1030 | device_xname(sc->sc_dev), |
1031 | (unsigned)buf[0], (unsigned)buf[1], |
1032 | (unsigned)buf[2], (unsigned)buf[3], |
1033 | (unsigned)buf[4], (unsigned)buf[5], |
1034 | (unsigned)buf[6], (unsigned)buf[7])); |
1035 | |
1036 | convert_status(sc, buf[UCHCOM_INTR_STAT1]); |
1037 | ucom_status_change(device_private(sc->sc_subdev)); |
1038 | } |
1039 | |