1 #include <sys/cdefs.h> 2 __FBSDID("$FreeBSD$"); 3 4 /*- 5 * Copyright (c) 2004 Dag-Erling Coïdan Smørgrav 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer 13 * in this position and unchanged. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. The name of the author may not be used to endorse or promote products 18 * derived from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to 34 * RS232 bridges. 35 */ 36 37 #include <sys/stdint.h> 38 #include <sys/stddef.h> 39 #include <sys/param.h> 40 #include <sys/queue.h> 41 #include <sys/types.h> 42 #include <sys/systm.h> 43 #include <sys/kernel.h> 44 #include <sys/bus.h> 45 #include <sys/module.h> 46 #include <sys/lock.h> 47 #include <sys/mutex.h> 48 #include <sys/condvar.h> 49 #include <sys/sysctl.h> 50 #include <sys/sx.h> 51 #include <sys/unistd.h> 52 #include <sys/callout.h> 53 #include <sys/malloc.h> 54 #include <sys/priv.h> 55 56 #include <dev/usb/usb.h> 57 #include <dev/usb/usbdi.h> 58 #include <dev/usb/usbdi_util.h> 59 #include <dev/usb/usbhid.h> 60 #include "usbdevs.h" 61 62 #define USB_DEBUG_VAR usb_debug 63 #include <dev/usb/usb_debug.h> 64 #include <dev/usb/usb_process.h> 65 66 #include <dev/usb/serial/usb_serial.h> 67 68 #define UCYCOM_MAX_IOLEN (1024 + 2) /* bytes */ 69 70 #define UCYCOM_IFACE_INDEX 0 71 72 enum { 73 UCYCOM_CTRL_RD, 74 UCYCOM_INTR_RD, 75 UCYCOM_N_TRANSFER, 76 }; 77 78 struct ucycom_softc { 79 struct ucom_super_softc sc_super_ucom; 80 struct ucom_softc sc_ucom; 81 82 struct usb_device *sc_udev; 83 struct usb_xfer *sc_xfer[UCYCOM_N_TRANSFER]; 84 struct mtx sc_mtx; 85 86 uint32_t sc_model; 87 #define MODEL_CY7C63743 0x63743 88 #define MODEL_CY7C64013 0x64013 89 90 uint16_t sc_flen; /* feature report length */ 91 uint16_t sc_ilen; /* input report length */ 92 uint16_t sc_olen; /* output report length */ 93 94 uint8_t sc_fid; /* feature report id */ 95 uint8_t sc_iid; /* input report id */ 96 uint8_t sc_oid; /* output report id */ 97 uint8_t sc_cfg; 98 #define UCYCOM_CFG_RESET 0x80 99 #define UCYCOM_CFG_PARODD 0x20 100 #define UCYCOM_CFG_PAREN 0x10 101 #define UCYCOM_CFG_STOPB 0x08 102 #define UCYCOM_CFG_DATAB 0x03 103 uint8_t sc_ist; /* status flags from last input */ 104 uint8_t sc_iface_no; 105 uint8_t sc_temp_cfg[32]; 106 }; 107 108 /* prototypes */ 109 110 static device_probe_t ucycom_probe; 111 static device_attach_t ucycom_attach; 112 static device_detach_t ucycom_detach; 113 static void ucycom_free_softc(struct ucycom_softc *); 114 115 static usb_callback_t ucycom_ctrl_write_callback; 116 static usb_callback_t ucycom_intr_read_callback; 117 118 static void ucycom_free(struct ucom_softc *); 119 static void ucycom_cfg_open(struct ucom_softc *); 120 static void ucycom_start_read(struct ucom_softc *); 121 static void ucycom_stop_read(struct ucom_softc *); 122 static void ucycom_start_write(struct ucom_softc *); 123 static void ucycom_stop_write(struct ucom_softc *); 124 static void ucycom_cfg_write(struct ucycom_softc *, uint32_t, uint8_t); 125 static int ucycom_pre_param(struct ucom_softc *, struct termios *); 126 static void ucycom_cfg_param(struct ucom_softc *, struct termios *); 127 static void ucycom_poll(struct ucom_softc *ucom); 128 129 static const struct usb_config ucycom_config[UCYCOM_N_TRANSFER] = { 130 131 [UCYCOM_CTRL_RD] = { 132 .type = UE_CONTROL, 133 .endpoint = 0x00, /* Control pipe */ 134 .direction = UE_DIR_ANY, 135 .bufsize = (sizeof(struct usb_device_request) + UCYCOM_MAX_IOLEN), 136 .callback = &ucycom_ctrl_write_callback, 137 .timeout = 1000, /* 1 second */ 138 }, 139 140 [UCYCOM_INTR_RD] = { 141 .type = UE_INTERRUPT, 142 .endpoint = UE_ADDR_ANY, 143 .direction = UE_DIR_IN, 144 .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, 145 .bufsize = UCYCOM_MAX_IOLEN, 146 .callback = &ucycom_intr_read_callback, 147 }, 148 }; 149 150 static const struct ucom_callback ucycom_callback = { 151 .ucom_cfg_param = &ucycom_cfg_param, 152 .ucom_cfg_open = &ucycom_cfg_open, 153 .ucom_pre_param = &ucycom_pre_param, 154 .ucom_start_read = &ucycom_start_read, 155 .ucom_stop_read = &ucycom_stop_read, 156 .ucom_start_write = &ucycom_start_write, 157 .ucom_stop_write = &ucycom_stop_write, 158 .ucom_poll = &ucycom_poll, 159 .ucom_free = &ucycom_free, 160 }; 161 162 static device_method_t ucycom_methods[] = { 163 DEVMETHOD(device_probe, ucycom_probe), 164 DEVMETHOD(device_attach, ucycom_attach), 165 DEVMETHOD(device_detach, ucycom_detach), 166 DEVMETHOD_END 167 }; 168 169 static devclass_t ucycom_devclass; 170 171 static driver_t ucycom_driver = { 172 .name = "ucycom", 173 .methods = ucycom_methods, 174 .size = sizeof(struct ucycom_softc), 175 }; 176 177 DRIVER_MODULE(ucycom, uhub, ucycom_driver, ucycom_devclass, NULL, 0); 178 MODULE_DEPEND(ucycom, ucom, 1, 1, 1); 179 MODULE_DEPEND(ucycom, usb, 1, 1, 1); 180 MODULE_VERSION(ucycom, 1); 181 182 /* 183 * Supported devices 184 */ 185 static const STRUCT_USB_HOST_ID ucycom_devs[] = { 186 {USB_VPI(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013)}, 187 }; 188 189 #define UCYCOM_DEFAULT_RATE 4800 190 #define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */ 191 192 static int 193 ucycom_probe(device_t dev) 194 { 195 struct usb_attach_arg *uaa = device_get_ivars(dev); 196 197 if (uaa->usb_mode != USB_MODE_HOST) { 198 return (ENXIO); 199 } 200 if (uaa->info.bConfigIndex != 0) { 201 return (ENXIO); 202 } 203 if (uaa->info.bIfaceIndex != UCYCOM_IFACE_INDEX) { 204 return (ENXIO); 205 } 206 return (usbd_lookup_id_by_uaa(ucycom_devs, sizeof(ucycom_devs), uaa)); 207 } 208 209 static int 210 ucycom_attach(device_t dev) 211 { 212 struct usb_attach_arg *uaa = device_get_ivars(dev); 213 struct ucycom_softc *sc = device_get_softc(dev); 214 void *urd_ptr = NULL; 215 int32_t error; 216 uint16_t urd_len; 217 uint8_t iface_index; 218 219 sc->sc_udev = uaa->device; 220 221 device_set_usb_desc(dev); 222 mtx_init(&sc->sc_mtx, "ucycom", NULL, MTX_DEF); 223 ucom_ref(&sc->sc_super_ucom); 224 225 DPRINTF("\n"); 226 227 /* get chip model */ 228 sc->sc_model = USB_GET_DRIVER_INFO(uaa); 229 if (sc->sc_model == 0) { 230 device_printf(dev, "unsupported device\n"); 231 goto detach; 232 } 233 device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model); 234 235 /* get report descriptor */ 236 237 error = usbd_req_get_hid_desc(uaa->device, NULL, 238 &urd_ptr, &urd_len, M_USBDEV, 239 UCYCOM_IFACE_INDEX); 240 241 if (error) { 242 device_printf(dev, "failed to get report " 243 "descriptor: %s\n", 244 usbd_errstr(error)); 245 goto detach; 246 } 247 /* get report sizes */ 248 249 sc->sc_flen = hid_report_size(urd_ptr, urd_len, hid_feature, &sc->sc_fid); 250 sc->sc_ilen = hid_report_size(urd_ptr, urd_len, hid_input, &sc->sc_iid); 251 sc->sc_olen = hid_report_size(urd_ptr, urd_len, hid_output, &sc->sc_oid); 252 253 if ((sc->sc_ilen > UCYCOM_MAX_IOLEN) || (sc->sc_ilen < 1) || 254 (sc->sc_olen > UCYCOM_MAX_IOLEN) || (sc->sc_olen < 2) || 255 (sc->sc_flen > UCYCOM_MAX_IOLEN) || (sc->sc_flen < 5)) { 256 device_printf(dev, "invalid report size i=%d, o=%d, f=%d, max=%d\n", 257 sc->sc_ilen, sc->sc_olen, sc->sc_flen, 258 UCYCOM_MAX_IOLEN); 259 goto detach; 260 } 261 sc->sc_iface_no = uaa->info.bIfaceNum; 262 263 iface_index = UCYCOM_IFACE_INDEX; 264 error = usbd_transfer_setup(uaa->device, &iface_index, 265 sc->sc_xfer, ucycom_config, UCYCOM_N_TRANSFER, 266 sc, &sc->sc_mtx); 267 if (error) { 268 device_printf(dev, "allocating USB " 269 "transfers failed\n"); 270 goto detach; 271 } 272 error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, 273 &ucycom_callback, &sc->sc_mtx); 274 if (error) { 275 goto detach; 276 } 277 ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); 278 279 if (urd_ptr) { 280 free(urd_ptr, M_USBDEV); 281 } 282 283 return (0); /* success */ 284 285 detach: 286 if (urd_ptr) { 287 free(urd_ptr, M_USBDEV); 288 } 289 ucycom_detach(dev); 290 return (ENXIO); 291 } 292 293 static int 294 ucycom_detach(device_t dev) 295 { 296 struct ucycom_softc *sc = device_get_softc(dev); 297 298 ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); 299 usbd_transfer_unsetup(sc->sc_xfer, UCYCOM_N_TRANSFER); 300 301 device_claim_softc(dev); 302 303 ucycom_free_softc(sc); 304 305 return (0); 306 } 307 308 UCOM_UNLOAD_DRAIN(ucycom); 309 310 static void 311 ucycom_free_softc(struct ucycom_softc *sc) 312 { 313 if (ucom_unref(&sc->sc_super_ucom)) { 314 mtx_destroy(&sc->sc_mtx); 315 device_free_softc(sc); 316 } 317 } 318 319 static void 320 ucycom_free(struct ucom_softc *ucom) 321 { 322 ucycom_free_softc(ucom->sc_parent); 323 } 324 325 static void 326 ucycom_cfg_open(struct ucom_softc *ucom) 327 { 328 struct ucycom_softc *sc = ucom->sc_parent; 329 330 /* set default configuration */ 331 ucycom_cfg_write(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG); 332 } 333 334 static void 335 ucycom_start_read(struct ucom_softc *ucom) 336 { 337 struct ucycom_softc *sc = ucom->sc_parent; 338 339 usbd_transfer_start(sc->sc_xfer[UCYCOM_INTR_RD]); 340 } 341 342 static void 343 ucycom_stop_read(struct ucom_softc *ucom) 344 { 345 struct ucycom_softc *sc = ucom->sc_parent; 346 347 usbd_transfer_stop(sc->sc_xfer[UCYCOM_INTR_RD]); 348 } 349 350 static void 351 ucycom_start_write(struct ucom_softc *ucom) 352 { 353 struct ucycom_softc *sc = ucom->sc_parent; 354 355 usbd_transfer_start(sc->sc_xfer[UCYCOM_CTRL_RD]); 356 } 357 358 static void 359 ucycom_stop_write(struct ucom_softc *ucom) 360 { 361 struct ucycom_softc *sc = ucom->sc_parent; 362 363 usbd_transfer_stop(sc->sc_xfer[UCYCOM_CTRL_RD]); 364 } 365 366 static void 367 ucycom_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) 368 { 369 struct ucycom_softc *sc = usbd_xfer_softc(xfer); 370 struct usb_device_request req; 371 struct usb_page_cache *pc0, *pc1; 372 uint8_t data[2]; 373 uint8_t offset; 374 uint32_t actlen; 375 376 pc0 = usbd_xfer_get_frame(xfer, 0); 377 pc1 = usbd_xfer_get_frame(xfer, 1); 378 379 switch (USB_GET_STATE(xfer)) { 380 case USB_ST_TRANSFERRED: 381 tr_transferred: 382 case USB_ST_SETUP: 383 384 switch (sc->sc_model) { 385 case MODEL_CY7C63743: 386 offset = 1; 387 break; 388 case MODEL_CY7C64013: 389 offset = 2; 390 break; 391 default: 392 offset = 0; 393 break; 394 } 395 396 if (ucom_get_data(&sc->sc_ucom, pc1, offset, 397 sc->sc_olen - offset, &actlen)) { 398 399 req.bmRequestType = UT_WRITE_CLASS_INTERFACE; 400 req.bRequest = UR_SET_REPORT; 401 USETW2(req.wValue, UHID_OUTPUT_REPORT, sc->sc_oid); 402 req.wIndex[0] = sc->sc_iface_no; 403 req.wIndex[1] = 0; 404 USETW(req.wLength, sc->sc_olen); 405 406 switch (sc->sc_model) { 407 case MODEL_CY7C63743: 408 data[0] = actlen; 409 break; 410 case MODEL_CY7C64013: 411 data[0] = 0; 412 data[1] = actlen; 413 break; 414 default: 415 break; 416 } 417 418 usbd_copy_in(pc0, 0, &req, sizeof(req)); 419 usbd_copy_in(pc1, 0, data, offset); 420 421 usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); 422 usbd_xfer_set_frame_len(xfer, 1, sc->sc_olen); 423 usbd_xfer_set_frames(xfer, sc->sc_olen ? 2 : 1); 424 usbd_transfer_submit(xfer); 425 } 426 return; 427 428 default: /* Error */ 429 if (error == USB_ERR_CANCELLED) { 430 return; 431 } 432 DPRINTF("error=%s\n", 433 usbd_errstr(error)); 434 goto tr_transferred; 435 } 436 } 437 438 static void 439 ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg) 440 { 441 struct usb_device_request req; 442 uint16_t len; 443 usb_error_t err; 444 445 len = sc->sc_flen; 446 if (len > sizeof(sc->sc_temp_cfg)) { 447 len = sizeof(sc->sc_temp_cfg); 448 } 449 sc->sc_cfg = cfg; 450 451 req.bmRequestType = UT_WRITE_CLASS_INTERFACE; 452 req.bRequest = UR_SET_REPORT; 453 USETW2(req.wValue, UHID_FEATURE_REPORT, sc->sc_fid); 454 req.wIndex[0] = sc->sc_iface_no; 455 req.wIndex[1] = 0; 456 USETW(req.wLength, len); 457 458 sc->sc_temp_cfg[0] = (baud & 0xff); 459 sc->sc_temp_cfg[1] = (baud >> 8) & 0xff; 460 sc->sc_temp_cfg[2] = (baud >> 16) & 0xff; 461 sc->sc_temp_cfg[3] = (baud >> 24) & 0xff; 462 sc->sc_temp_cfg[4] = cfg; 463 464 err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, 465 &req, sc->sc_temp_cfg, 0, 1000); 466 if (err) { 467 DPRINTFN(0, "device request failed, err=%s " 468 "(ignored)\n", usbd_errstr(err)); 469 } 470 } 471 472 static int 473 ucycom_pre_param(struct ucom_softc *ucom, struct termios *t) 474 { 475 switch (t->c_ospeed) { 476 case 600: 477 case 1200: 478 case 2400: 479 case 4800: 480 case 9600: 481 case 19200: 482 case 38400: 483 case 57600: 484 #if 0 485 /* 486 * Stock chips only support standard baud rates in the 600 - 57600 487 * range, but higher rates can be achieved using custom firmware. 488 */ 489 case 115200: 490 case 153600: 491 case 192000: 492 #endif 493 break; 494 default: 495 return (EINVAL); 496 } 497 return (0); 498 } 499 500 static void 501 ucycom_cfg_param(struct ucom_softc *ucom, struct termios *t) 502 { 503 struct ucycom_softc *sc = ucom->sc_parent; 504 uint8_t cfg; 505 506 DPRINTF("\n"); 507 508 if (t->c_cflag & CIGNORE) { 509 cfg = sc->sc_cfg; 510 } else { 511 cfg = 0; 512 switch (t->c_cflag & CSIZE) { 513 default: 514 case CS8: 515 ++cfg; 516 case CS7: 517 ++cfg; 518 case CS6: 519 ++cfg; 520 case CS5: 521 break; 522 } 523 524 if (t->c_cflag & CSTOPB) 525 cfg |= UCYCOM_CFG_STOPB; 526 if (t->c_cflag & PARENB) 527 cfg |= UCYCOM_CFG_PAREN; 528 if (t->c_cflag & PARODD) 529 cfg |= UCYCOM_CFG_PARODD; 530 } 531 532 ucycom_cfg_write(sc, t->c_ospeed, cfg); 533 } 534 535 static void 536 ucycom_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) 537 { 538 struct ucycom_softc *sc = usbd_xfer_softc(xfer); 539 struct usb_page_cache *pc; 540 uint8_t buf[2]; 541 uint32_t offset; 542 int len; 543 int actlen; 544 545 usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); 546 pc = usbd_xfer_get_frame(xfer, 0); 547 548 switch (USB_GET_STATE(xfer)) { 549 case USB_ST_TRANSFERRED: 550 switch (sc->sc_model) { 551 case MODEL_CY7C63743: 552 if (actlen < 1) { 553 goto tr_setup; 554 } 555 usbd_copy_out(pc, 0, buf, 1); 556 557 sc->sc_ist = buf[0] & ~0x07; 558 len = buf[0] & 0x07; 559 560 actlen--; 561 offset = 1; 562 563 break; 564 565 case MODEL_CY7C64013: 566 if (actlen < 2) { 567 goto tr_setup; 568 } 569 usbd_copy_out(pc, 0, buf, 2); 570 571 sc->sc_ist = buf[0] & ~0x07; 572 len = buf[1]; 573 574 actlen -= 2; 575 offset = 2; 576 577 break; 578 579 default: 580 DPRINTFN(0, "unsupported model number\n"); 581 goto tr_setup; 582 } 583 584 if (len > actlen) 585 len = actlen; 586 if (len) 587 ucom_put_data(&sc->sc_ucom, pc, offset, len); 588 /* FALLTHROUGH */ 589 case USB_ST_SETUP: 590 tr_setup: 591 usbd_xfer_set_frame_len(xfer, 0, sc->sc_ilen); 592 usbd_transfer_submit(xfer); 593 return; 594 595 default: /* Error */ 596 if (error != USB_ERR_CANCELLED) { 597 /* try to clear stall first */ 598 usbd_xfer_set_stall(xfer); 599 goto tr_setup; 600 } 601 return; 602 603 } 604 } 605 606 static void 607 ucycom_poll(struct ucom_softc *ucom) 608 { 609 struct ucycom_softc *sc = ucom->sc_parent; 610 usbd_transfer_poll(sc->sc_xfer, UCYCOM_N_TRANSFER); 611 } 612