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