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