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