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