ucycom.c revision 184736
1#include <sys/cdefs.h> 2__FBSDID("$FreeBSD: head/sys/dev/usb2/serial/ucycom2.c 184736 2008-11-06 17:26:12Z imp $"); 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 <dev/usb2/include/usb2_devid.h> 38#include <dev/usb2/include/usb2_standard.h> 39#include <dev/usb2/include/usb2_mfunc.h> 40#include <dev/usb2/include/usb2_error.h> 41#include <dev/usb2/include/usb2_cdc.h> 42#include <dev/usb2/include/usb2_ioctl.h> 43#include <dev/usb2/include/usb2_hid.h> 44 45#define USB_DEBUG_VAR usb2_debug 46 47#include <dev/usb2/core/usb2_core.h> 48#include <dev/usb2/core/usb2_debug.h> 49#include <dev/usb2/core/usb2_process.h> 50#include <dev/usb2/core/usb2_config_td.h> 51#include <dev/usb2/core/usb2_request.h> 52#include <dev/usb2/core/usb2_lookup.h> 53#include <dev/usb2/core/usb2_util.h> 54#include <dev/usb2/core/usb2_busdma.h> 55#include <dev/usb2/core/usb2_hid.h> 56 57#include <dev/usb2/serial/usb2_serial.h> 58 59#define UCYCOM_MAX_IOLEN (1024 + 2) /* bytes */ 60 61#define UCYCOM_ENDPT_MAX 3 /* units */ 62#define UCYCOM_IFACE_INDEX 0 63 64struct ucycom_softc { 65 struct usb2_com_super_softc sc_super_ucom; 66 struct usb2_com_softc sc_ucom; 67 68 struct usb2_device *sc_udev; 69 struct usb2_xfer *sc_xfer[UCYCOM_ENDPT_MAX]; 70 71 uint32_t sc_model; 72#define MODEL_CY7C63743 0x63743 73#define MODEL_CY7C64013 0x64013 74 75 uint16_t sc_flen; /* feature report length */ 76 uint16_t sc_ilen; /* input report length */ 77 uint16_t sc_olen; /* output report length */ 78 79 uint8_t sc_fid; /* feature report id */ 80 uint8_t sc_iid; /* input report id */ 81 uint8_t sc_oid; /* output report id */ 82 uint8_t sc_cfg; 83#define UCYCOM_CFG_RESET 0x80 84#define UCYCOM_CFG_PARODD 0x20 85#define UCYCOM_CFG_PAREN 0x10 86#define UCYCOM_CFG_STOPB 0x08 87#define UCYCOM_CFG_DATAB 0x03 88 uint8_t sc_ist; /* status flags from last input */ 89 uint8_t sc_flags; 90#define UCYCOM_FLAG_INTR_STALL 0x01 91 uint8_t sc_name[16]; 92 uint8_t sc_iface_no; 93 uint8_t sc_temp_cfg[32]; 94}; 95 96/* prototypes */ 97 98static device_probe_t ucycom_probe; 99static device_attach_t ucycom_attach; 100static device_detach_t ucycom_detach; 101 102static usb2_callback_t ucycom_ctrl_write_callback; 103static usb2_callback_t ucycom_intr_read_clear_stall_callback; 104static usb2_callback_t ucycom_intr_read_callback; 105 106static void ucycom_cfg_open(struct usb2_com_softc *ucom); 107static void ucycom_start_read(struct usb2_com_softc *ucom); 108static void ucycom_stop_read(struct usb2_com_softc *ucom); 109static void ucycom_start_write(struct usb2_com_softc *ucom); 110static void ucycom_stop_write(struct usb2_com_softc *ucom); 111static void ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg); 112static int ucycom_pre_param(struct usb2_com_softc *ucom, struct termios *t); 113static void ucycom_cfg_param(struct usb2_com_softc *ucom, struct termios *t); 114 115static const struct usb2_config ucycom_config[UCYCOM_ENDPT_MAX] = { 116 117 [0] = { 118 .type = UE_CONTROL, 119 .endpoint = 0x00, /* Control pipe */ 120 .direction = UE_DIR_ANY, 121 .mh.bufsize = (sizeof(struct usb2_device_request) + UCYCOM_MAX_IOLEN), 122 .mh.flags = {}, 123 .mh.callback = &ucycom_ctrl_write_callback, 124 .mh.timeout = 1000, /* 1 second */ 125 }, 126 127 [1] = { 128 .type = UE_INTERRUPT, 129 .endpoint = UE_ADDR_ANY, 130 .direction = UE_DIR_IN, 131 .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, 132 .mh.bufsize = UCYCOM_MAX_IOLEN, 133 .mh.callback = &ucycom_intr_read_callback, 134 }, 135 136 [2] = { 137 .type = UE_CONTROL, 138 .endpoint = 0x00, /* Control pipe */ 139 .direction = UE_DIR_ANY, 140 .mh.bufsize = sizeof(struct usb2_device_request), 141 .mh.flags = {}, 142 .mh.callback = &ucycom_intr_read_clear_stall_callback, 143 .mh.timeout = 1000, /* 1 second */ 144 .mh.interval = 50, /* 50ms */ 145 }, 146}; 147 148static const struct usb2_com_callback ucycom_callback = { 149 .usb2_com_cfg_param = &ucycom_cfg_param, 150 .usb2_com_cfg_open = &ucycom_cfg_open, 151 .usb2_com_pre_param = &ucycom_pre_param, 152 .usb2_com_start_read = &ucycom_start_read, 153 .usb2_com_stop_read = &ucycom_stop_read, 154 .usb2_com_start_write = &ucycom_start_write, 155 .usb2_com_stop_write = &ucycom_stop_write, 156}; 157 158static device_method_t ucycom_methods[] = { 159 DEVMETHOD(device_probe, ucycom_probe), 160 DEVMETHOD(device_attach, ucycom_attach), 161 DEVMETHOD(device_detach, ucycom_detach), 162 {0, 0} 163}; 164 165static devclass_t ucycom_devclass; 166 167static driver_t ucycom_driver = { 168 .name = "ucycom", 169 .methods = ucycom_methods, 170 .size = sizeof(struct ucycom_softc), 171}; 172 173DRIVER_MODULE(ucycom, ushub, ucycom_driver, ucycom_devclass, NULL, 0); 174MODULE_DEPEND(ucycom, usb2_serial, 1, 1, 1); 175MODULE_DEPEND(ucycom, usb2_core, 1, 1, 1); 176 177/* 178 * Supported devices 179 */ 180static const struct usb2_device_id ucycom_devs[] = { 181 {USB_VPI(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013)}, 182}; 183 184#define UCYCOM_DEFAULT_RATE 4800 185#define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */ 186 187static int 188ucycom_probe(device_t dev) 189{ 190 struct usb2_attach_arg *uaa = device_get_ivars(dev); 191 192 if (uaa->usb2_mode != USB_MODE_HOST) { 193 return (ENXIO); 194 } 195 if (uaa->info.bConfigIndex != 0) { 196 return (ENXIO); 197 } 198 if (uaa->info.bIfaceIndex != UCYCOM_IFACE_INDEX) { 199 return (ENXIO); 200 } 201 return (usb2_lookup_id_by_uaa(ucycom_devs, sizeof(ucycom_devs), uaa)); 202} 203 204static int 205ucycom_attach(device_t dev) 206{ 207 struct usb2_attach_arg *uaa = device_get_ivars(dev); 208 struct ucycom_softc *sc = device_get_softc(dev); 209 void *urd_ptr = NULL; 210 int32_t error; 211 uint16_t urd_len; 212 uint8_t iface_index; 213 214 if (sc == NULL) { 215 return (ENOMEM); 216 } 217 sc->sc_udev = uaa->device; 218 219 device_set_usb2_desc(dev); 220 221 snprintf(sc->sc_name, sizeof(sc->sc_name), 222 "%s", device_get_nameunit(dev)); 223 224 DPRINTF("\n"); 225 226 /* get chip model */ 227 sc->sc_model = USB_GET_DRIVER_INFO(uaa); 228 if (sc->sc_model == 0) { 229 device_printf(dev, "unsupported device\n"); 230 goto detach; 231 } 232 device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model); 233 234 /* get report descriptor */ 235 236 error = usb2_req_get_hid_desc 237 (uaa->device, &Giant, 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 usb2_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 = usb2_transfer_setup(uaa->device, &iface_index, 265 sc->sc_xfer, ucycom_config, UCYCOM_ENDPT_MAX, 266 sc, &Giant); 267 if (error) { 268 device_printf(dev, "allocating USB " 269 "transfers failed!\n"); 270 goto detach; 271 } 272 error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, 273 &ucycom_callback, &Giant); 274 275 if (error) { 276 goto detach; 277 } 278 if (urd_ptr) { 279 free(urd_ptr, M_USBDEV); 280 } 281 return (0); /* success */ 282 283detach: 284 if (urd_ptr) { 285 free(urd_ptr, M_USBDEV); 286 } 287 ucycom_detach(dev); 288 return (ENXIO); 289} 290 291static int 292ucycom_detach(device_t dev) 293{ 294 struct ucycom_softc *sc = device_get_softc(dev); 295 296 usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); 297 298 usb2_transfer_unsetup(sc->sc_xfer, UCYCOM_ENDPT_MAX); 299 300 return (0); 301} 302 303static void 304ucycom_cfg_open(struct usb2_com_softc *ucom) 305{ 306 struct ucycom_softc *sc = ucom->sc_parent; 307 308 /* set default configuration */ 309 ucycom_cfg_write(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG); 310 return; 311} 312 313static void 314ucycom_start_read(struct usb2_com_softc *ucom) 315{ 316 struct ucycom_softc *sc = ucom->sc_parent; 317 318 usb2_transfer_start(sc->sc_xfer[1]); 319 return; 320} 321 322static void 323ucycom_stop_read(struct usb2_com_softc *ucom) 324{ 325 struct ucycom_softc *sc = ucom->sc_parent; 326 327 usb2_transfer_stop(sc->sc_xfer[2]); 328 usb2_transfer_stop(sc->sc_xfer[1]); 329 return; 330} 331 332static void 333ucycom_start_write(struct usb2_com_softc *ucom) 334{ 335 struct ucycom_softc *sc = ucom->sc_parent; 336 337 usb2_transfer_start(sc->sc_xfer[0]); 338 return; 339} 340 341static void 342ucycom_stop_write(struct usb2_com_softc *ucom) 343{ 344 struct ucycom_softc *sc = ucom->sc_parent; 345 346 usb2_transfer_stop(sc->sc_xfer[0]); 347 return; 348} 349 350static void 351ucycom_ctrl_write_callback(struct usb2_xfer *xfer) 352{ 353 struct ucycom_softc *sc = xfer->priv_sc; 354 struct usb2_device_request req; 355 uint8_t data[2]; 356 uint8_t offset; 357 uint32_t actlen; 358 359 switch (USB_GET_STATE(xfer)) { 360 case USB_ST_TRANSFERRED: 361tr_transferred: 362 case USB_ST_SETUP: 363 364 switch (sc->sc_model) { 365 case MODEL_CY7C63743: 366 offset = 1; 367 break; 368 case MODEL_CY7C64013: 369 offset = 2; 370 break; 371 default: 372 offset = 0; 373 break; 374 } 375 376 if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers + 1, offset, 377 sc->sc_olen - offset, &actlen)) { 378 379 req.bmRequestType = UT_WRITE_CLASS_INTERFACE; 380 req.bRequest = UR_SET_REPORT; 381 USETW2(req.wValue, UHID_OUTPUT_REPORT, sc->sc_oid); 382 req.wIndex[0] = sc->sc_iface_no; 383 req.wIndex[1] = 0; 384 USETW(req.wLength, sc->sc_olen); 385 386 switch (sc->sc_model) { 387 case MODEL_CY7C63743: 388 data[0] = actlen; 389 break; 390 case MODEL_CY7C64013: 391 data[0] = 0; 392 data[1] = actlen; 393 break; 394 default: 395 break; 396 } 397 398 usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); 399 usb2_copy_in(xfer->frbuffers + 1, 0, data, offset); 400 401 xfer->frlengths[0] = sizeof(req); 402 xfer->frlengths[1] = sc->sc_olen; 403 xfer->nframes = xfer->frlengths[1] ? 2 : 1; 404 usb2_start_hardware(xfer); 405 } 406 return; 407 408 default: /* Error */ 409 if (xfer->error == USB_ERR_CANCELLED) { 410 return; 411 } 412 DPRINTF("error=%s\n", 413 usb2_errstr(xfer->error)); 414 goto tr_transferred; 415 } 416} 417 418static void 419ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg) 420{ 421 struct usb2_device_request req; 422 uint16_t len; 423 usb2_error_t err; 424 425 len = sc->sc_flen; 426 if (len > sizeof(sc->sc_temp_cfg)) { 427 len = sizeof(sc->sc_temp_cfg); 428 } 429 sc->sc_cfg = cfg; 430 431 req.bmRequestType = UT_WRITE_CLASS_INTERFACE; 432 req.bRequest = UR_SET_REPORT; 433 USETW2(req.wValue, UHID_FEATURE_REPORT, sc->sc_fid); 434 req.wIndex[0] = sc->sc_iface_no; 435 req.wIndex[1] = 0; 436 USETW(req.wLength, len); 437 438 sc->sc_temp_cfg[0] = (baud & 0xff); 439 sc->sc_temp_cfg[1] = (baud >> 8) & 0xff; 440 sc->sc_temp_cfg[2] = (baud >> 16) & 0xff; 441 sc->sc_temp_cfg[3] = (baud >> 24) & 0xff; 442 sc->sc_temp_cfg[4] = cfg; 443 444 if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { 445 return; 446 } 447 err = usb2_do_request_flags 448 (sc->sc_udev, &Giant, &req, sc->sc_temp_cfg, 0, NULL, 1000); 449 450 if (err) { 451 DPRINTFN(0, "device request failed, err=%s " 452 "(ignored)\n", usb2_errstr(err)); 453 } 454 return; 455} 456 457static int 458ucycom_pre_param(struct usb2_com_softc *ucom, struct termios *t) 459{ 460 switch (t->c_ospeed) { 461 case 600: 462 case 1200: 463 case 2400: 464 case 4800: 465 case 9600: 466 case 19200: 467 case 38400: 468 case 57600: 469#if 0 470 /* 471 * Stock chips only support standard baud rates in the 600 - 57600 472 * range, but higher rates can be achieved using custom firmware. 473 */ 474 case 115200: 475 case 153600: 476 case 192000: 477#endif 478 break; 479 default: 480 return (EINVAL); 481 } 482 return (0); 483} 484 485static void 486ucycom_cfg_param(struct usb2_com_softc *ucom, struct termios *t) 487{ 488 struct ucycom_softc *sc = ucom->sc_parent; 489 uint8_t cfg; 490 491 DPRINTF("\n"); 492 493 if (t->c_cflag & CIGNORE) { 494 cfg = sc->sc_cfg; 495 } else { 496 cfg = 0; 497 switch (t->c_cflag & CSIZE) { 498 default: 499 case CS8: 500 ++cfg; 501 case CS7: 502 ++cfg; 503 case CS6: 504 ++cfg; 505 case CS5: 506 break; 507 } 508 509 if (t->c_cflag & CSTOPB) 510 cfg |= UCYCOM_CFG_STOPB; 511 if (t->c_cflag & PARENB) 512 cfg |= UCYCOM_CFG_PAREN; 513 if (t->c_cflag & PARODD) 514 cfg |= UCYCOM_CFG_PARODD; 515 } 516 517 ucycom_cfg_write(sc, t->c_ospeed, cfg); 518 return; 519} 520 521static void 522ucycom_intr_read_clear_stall_callback(struct usb2_xfer *xfer) 523{ 524 struct ucycom_softc *sc = xfer->priv_sc; 525 struct usb2_xfer *xfer_other = sc->sc_xfer[1]; 526 527 if (usb2_clear_stall_callback(xfer, xfer_other)) { 528 DPRINTF("stall cleared\n"); 529 sc->sc_flags &= ~UCYCOM_FLAG_INTR_STALL; 530 usb2_transfer_start(xfer_other); 531 } 532 return; 533} 534 535static void 536ucycom_intr_read_callback(struct usb2_xfer *xfer) 537{ 538 struct ucycom_softc *sc = xfer->priv_sc; 539 uint8_t buf[2]; 540 uint32_t offset; 541 uint32_t len; 542 543 switch (USB_GET_STATE(xfer)) { 544 case USB_ST_TRANSFERRED: 545 switch (sc->sc_model) { 546 case MODEL_CY7C63743: 547 if (xfer->actlen < 1) { 548 goto tr_setup; 549 } 550 usb2_copy_out(xfer->frbuffers, 0, buf, 1); 551 552 sc->sc_ist = buf[0] & ~0x07; 553 len = buf[0] & 0x07; 554 555 (xfer->actlen)--; 556 557 offset = 1; 558 559 break; 560 561 case MODEL_CY7C64013: 562 if (xfer->actlen < 2) { 563 goto tr_setup; 564 } 565 usb2_copy_out(xfer->frbuffers, 0, buf, 2); 566 567 sc->sc_ist = buf[0] & ~0x07; 568 len = buf[1]; 569 570 (xfer->actlen) -= 2; 571 572 offset = 2; 573 574 break; 575 576 default: 577 DPRINTFN(0, "unsupported model number!\n"); 578 goto tr_setup; 579 } 580 581 if (len > xfer->actlen) { 582 len = xfer->actlen; 583 } 584 if (len) { 585 usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 586 offset, len); 587 } 588 case USB_ST_SETUP: 589tr_setup: 590 if (sc->sc_flags & UCYCOM_FLAG_INTR_STALL) { 591 usb2_transfer_start(sc->sc_xfer[2]); 592 } else { 593 xfer->frlengths[0] = sc->sc_ilen; 594 usb2_start_hardware(xfer); 595 } 596 return; 597 598 default: /* Error */ 599 if (xfer->error != USB_ERR_CANCELLED) { 600 sc->sc_flags |= UCYCOM_FLAG_INTR_STALL; 601 usb2_transfer_start(sc->sc_xfer[2]); 602 } 603 return; 604 605 } 606} 607