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