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