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