1 /* $OpenBSD: uslhcom.c,v 1.4 2016/01/09 04:14:42 jcs Exp $ */ 2 3 /* 4 * Copyright (c) 2015 SASANO Takayoshi <uaa@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* 20 * Device driver for Silicon Labs CP2110 USB HID-UART bridge. 21 */ 22 23 #include <sys/param.h> 24 #include <sys/systm.h> 25 #include <sys/kernel.h> 26 #include <sys/malloc.h> 27 #include <sys/conf.h> 28 #include <sys/tty.h> 29 #include <sys/device.h> 30 31 #include <dev/usb/usb.h> 32 #include <dev/usb/usbdi.h> 33 #include <dev/usb/usbdi_util.h> 34 #include <dev/usb/usbdevs.h> 35 36 #include <dev/usb/usbhid.h> 37 #include <dev/usb/uhidev.h> 38 39 #include <dev/usb/ucomvar.h> 40 #include <dev/usb/uslhcomreg.h> 41 42 #ifdef USLHCOM_DEBUG 43 #define DPRINTF(x) if (uslhcomdebug) printf x 44 #else 45 #define DPRINTF(x) 46 #endif 47 48 struct uslhcom_softc { 49 struct uhidev sc_hdev; 50 struct usbd_device *sc_udev; 51 52 u_char *sc_ibuf; 53 u_int sc_icnt; 54 55 u_char sc_lsr; 56 u_char sc_msr; 57 58 struct device *sc_subdev; 59 }; 60 61 void uslhcom_get_status(void *, int, u_char *, u_char *); 62 void uslhcom_set(void *, int, int, int); 63 int uslhcom_param(void *, int, struct termios *); 64 int uslhcom_open(void *, int); 65 void uslhcom_close(void *, int); 66 void uslhcom_write(void *, int, u_char *, u_char *, u_int32_t *); 67 void uslhcom_read(void *, int, u_char **, u_int32_t *); 68 void uslhcom_intr(struct uhidev *, void *, u_int); 69 70 int uslhcom_match(struct device *, void *, void *); 71 void uslhcom_attach(struct device *, struct device *, void *); 72 int uslhcom_detach(struct device *, int); 73 74 int uslhcom_uart_endis(struct uslhcom_softc *, int); 75 int uslhcom_clear_fifo(struct uslhcom_softc *, int); 76 int uslhcom_get_version(struct uslhcom_softc *, struct uslhcom_version_info *); 77 int uslhcom_get_uart_status(struct uslhcom_softc *, struct uslhcom_uart_status *); 78 int uslhcom_set_break(struct uslhcom_softc *, int); 79 int uslhcom_set_config(struct uslhcom_softc *, struct uslhcom_uart_config *); 80 void uslhcom_set_baud_rate(struct uslhcom_uart_config *, u_int32_t); 81 int uslhcom_create_config(struct uslhcom_uart_config *, struct termios *); 82 int uslhcom_setup(struct uslhcom_softc *, struct uslhcom_uart_config *); 83 84 struct ucom_methods uslhcom_methods = { 85 uslhcom_get_status, 86 uslhcom_set, 87 uslhcom_param, 88 NULL, 89 uslhcom_open, 90 uslhcom_close, 91 uslhcom_read, 92 uslhcom_write, 93 }; 94 95 static const struct usb_devno uslhcom_devs[] = { 96 { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2110 }, 97 }; 98 99 struct cfdriver uslhcom_cd = { 100 NULL, "uslhcom", DV_DULL 101 }; 102 103 const struct cfattach uslhcom_ca = { 104 sizeof(struct uslhcom_softc), 105 uslhcom_match, uslhcom_attach, uslhcom_detach 106 }; 107 108 /* ---------------------------------------------------------------------- 109 * driver entry points 110 */ 111 112 int 113 uslhcom_match(struct device *parent, void *match, void *aux) 114 { 115 struct uhidev_attach_arg *uha = aux; 116 117 /* use all report IDs */ 118 if (uha->reportid != UHIDEV_CLAIM_ALLREPORTID) 119 return UMATCH_NONE; 120 121 return (usb_lookup(uslhcom_devs, 122 uha->uaa->vendor, uha->uaa->product) != NULL ? 123 UMATCH_VENDOR_PRODUCT : UMATCH_NONE); 124 } 125 126 void 127 uslhcom_attach(struct device *parent, struct device *self, void *aux) 128 { 129 struct uslhcom_softc *sc = (struct uslhcom_softc *)self; 130 struct uhidev_attach_arg *uha = aux; 131 struct usbd_device *dev = uha->parent->sc_udev; 132 struct ucom_attach_args uca; 133 struct uslhcom_version_info version; 134 int err, repid, size, rsize; 135 void *desc; 136 137 sc->sc_udev = dev; 138 sc->sc_lsr = sc->sc_msr = 0; 139 sc->sc_hdev.sc_intr = uslhcom_intr; 140 sc->sc_hdev.sc_parent = uha->parent; 141 sc->sc_hdev.sc_report_id = uha->reportid; 142 sc->sc_hdev.sc_isize = sc->sc_hdev.sc_osize = sc->sc_hdev.sc_fsize = 0; 143 144 uhidev_get_report_desc(uha->parent, &desc, &size); 145 for (repid = 0; repid < uha->parent->sc_nrepid; repid++) { 146 rsize = hid_report_size(desc, size, hid_input, repid); 147 if (sc->sc_hdev.sc_isize < rsize) sc->sc_hdev.sc_isize = rsize; 148 rsize = hid_report_size(desc, size, hid_output, repid); 149 if (sc->sc_hdev.sc_osize < rsize) sc->sc_hdev.sc_osize = rsize; 150 rsize = hid_report_size(desc, size, hid_feature, repid); 151 if (sc->sc_hdev.sc_fsize < rsize) sc->sc_hdev.sc_fsize = rsize; 152 } 153 154 printf("\n"); 155 156 err = uhidev_open(&sc->sc_hdev); 157 if (err) { 158 DPRINTF(("uslhcom_attach: uhidev_open %d\n", err)); 159 return; 160 } 161 162 DPRINTF(("uslhcom_attach: sc %p opipe %p ipipe %p report_id %d\n", 163 sc, sc->sc_hdev.sc_parent->sc_opipe, 164 sc->sc_hdev.sc_parent->sc_ipipe, uha->reportid)); 165 DPRINTF(("uslhcom_attach: isize %d osize %d fsize %d\n", 166 sc->sc_hdev.sc_isize, sc->sc_hdev.sc_osize, 167 sc->sc_hdev.sc_fsize)); 168 169 uslhcom_uart_endis(sc, UART_DISABLE); 170 uslhcom_get_version(sc, &version); 171 printf("%s: pid %#x rev %#x\n", sc->sc_hdev.sc_dev.dv_xname, 172 version.product_id, version.product_revision); 173 174 /* setup ucom layer */ 175 uca.portno = UCOM_UNK_PORTNO; 176 uca.bulkin = uca.bulkout = -1; 177 uca.ibufsize = uca.ibufsizepad = 0; 178 uca.obufsize = sc->sc_hdev.sc_osize; 179 uca.opkthdrlen = USLHCOM_TX_HEADER_SIZE; 180 uca.uhidev = sc->sc_hdev.sc_parent; 181 uca.device = uha->uaa->device; 182 uca.iface = uha->uaa->iface; 183 uca.methods = &uslhcom_methods; 184 uca.arg = sc; 185 uca.info = NULL; 186 187 sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch); 188 } 189 190 int 191 uslhcom_detach(struct device *self, int flags) 192 { 193 struct uslhcom_softc *sc = (struct uslhcom_softc *)self; 194 195 DPRINTF(("uslhcom_detach: sc=%p flags=%d\n", sc, flags)); 196 if (sc->sc_subdev != NULL) { 197 config_detach(sc->sc_subdev, flags); 198 sc->sc_subdev = NULL; 199 } 200 201 if (sc->sc_hdev.sc_state & UHIDEV_OPEN) 202 uhidev_close(&sc->sc_hdev); 203 204 return 0; 205 } 206 207 /* ---------------------------------------------------------------------- 208 * low level I/O 209 */ 210 211 int 212 uslhcom_uart_endis(struct uslhcom_softc *sc, int enable) 213 { 214 int len; 215 u_char val; 216 217 len = sizeof(val); 218 val = enable; 219 220 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 221 GET_SET_UART_ENABLE, &val, len) != len; 222 } 223 224 int 225 uslhcom_clear_fifo(struct uslhcom_softc *sc, int fifo) 226 { 227 int len; 228 u_char val; 229 230 len = sizeof(val); 231 val = fifo; 232 233 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 234 SET_CLEAR_FIFOS, &val, len) != len; 235 } 236 237 int 238 uslhcom_get_version(struct uslhcom_softc *sc, struct uslhcom_version_info *version) 239 { 240 int len; 241 242 len = sizeof(*version); 243 244 return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 245 GET_VERSION, version, len) < len; 246 } 247 248 int 249 uslhcom_get_uart_status(struct uslhcom_softc *sc, struct uslhcom_uart_status *status) 250 { 251 int len; 252 253 len = sizeof(*status); 254 255 return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 256 GET_UART_STATUS, status, len) < len; 257 } 258 259 int 260 uslhcom_set_break(struct uslhcom_softc *sc, int onoff) 261 { 262 int len, reportid; 263 u_char val; 264 265 len = sizeof(val); 266 267 if (onoff) { 268 val = 0; /* send break until SET_STOP_LINE_BREAK */ 269 reportid = SET_TRANSMIT_LINE_BREAK; 270 } else { 271 val = 0; /* any value can be accepted */ 272 reportid = SET_STOP_LINE_BREAK; 273 } 274 275 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 276 reportid, &val, len) != len; 277 } 278 279 int 280 uslhcom_set_config(struct uslhcom_softc *sc, struct uslhcom_uart_config *config) 281 { 282 int len; 283 284 len = sizeof(*config); 285 286 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 287 GET_SET_UART_CONFIG, config, len) != len; 288 } 289 290 void 291 uslhcom_set_baud_rate(struct uslhcom_uart_config *config, u_int32_t baud_rate) 292 { 293 config->baud_rate[0] = baud_rate >> 24; 294 config->baud_rate[1] = baud_rate >> 16; 295 config->baud_rate[2] = baud_rate >> 8; 296 config->baud_rate[3] = baud_rate >> 0; 297 } 298 299 int 300 uslhcom_create_config(struct uslhcom_uart_config *config, struct termios *t) 301 { 302 if (t->c_ospeed < UART_CONFIG_BAUD_RATE_MIN || 303 t->c_ospeed > UART_CONFIG_BAUD_RATE_MAX) 304 return EINVAL; 305 306 uslhcom_set_baud_rate(config, t->c_ospeed); 307 308 if (ISSET(t->c_cflag, PARENB)) { 309 if (ISSET(t->c_cflag, PARODD)) 310 config->parity = UART_CONFIG_PARITY_ODD; 311 else 312 config->parity = UART_CONFIG_PARITY_EVEN; 313 } else 314 config->parity = UART_CONFIG_PARITY_NONE; 315 316 if (ISSET(t->c_cflag, CRTSCTS)) 317 config->data_control = UART_CONFIG_DATA_CONTROL_HARD; 318 else 319 config->data_control = UART_CONFIG_DATA_CONTROL_NONE; 320 321 switch (ISSET(t->c_cflag, CSIZE)) { 322 case CS5: 323 config->data_bits = UART_CONFIG_DATA_BITS_5; 324 break; 325 case CS6: 326 config->data_bits = UART_CONFIG_DATA_BITS_6; 327 break; 328 case CS7: 329 config->data_bits = UART_CONFIG_DATA_BITS_7; 330 break; 331 case CS8: 332 config->data_bits = UART_CONFIG_DATA_BITS_8; 333 break; 334 default: 335 return EINVAL; 336 } 337 338 if (ISSET(t->c_cflag, CSTOPB)) 339 config->stop_bits = UART_CONFIG_STOP_BITS_2; 340 else 341 config->stop_bits = UART_CONFIG_STOP_BITS_1; 342 343 return 0; 344 } 345 346 int 347 uslhcom_setup(struct uslhcom_softc *sc, struct uslhcom_uart_config *config) 348 { 349 struct uslhcom_uart_status status; 350 351 if (uslhcom_uart_endis(sc, UART_DISABLE)) 352 return EIO; 353 354 if (uslhcom_set_config(sc, config)) 355 return EIO; 356 357 if (uslhcom_clear_fifo(sc, CLEAR_TX_FIFO | CLEAR_RX_FIFO)) 358 return EIO; 359 360 if (uslhcom_get_uart_status(sc, &status)) 361 return EIO; 362 363 if (uslhcom_uart_endis(sc, UART_ENABLE)) 364 return EIO; 365 366 return 0; 367 } 368 369 /* ---------------------------------------------------------------------- 370 * methods for ucom 371 */ 372 373 void 374 uslhcom_get_status(void *arg, int portno, u_char *rlsr, u_char *rmsr) 375 { 376 struct uslhcom_softc *sc = arg; 377 378 if (usbd_is_dying(sc->sc_udev)) 379 return; 380 381 *rlsr = sc->sc_lsr; 382 *rmsr = sc->sc_msr; 383 } 384 385 void 386 uslhcom_set(void *arg, int portno, int reg, int onoff) 387 { 388 struct uslhcom_softc *sc = arg; 389 390 if (usbd_is_dying(sc->sc_udev)) 391 return; 392 393 switch (reg) { 394 case UCOM_SET_DTR: 395 case UCOM_SET_RTS: 396 /* no support, do nothing */ 397 break; 398 case UCOM_SET_BREAK: 399 uslhcom_set_break(sc, onoff); 400 break; 401 } 402 } 403 404 int 405 uslhcom_param(void *arg, int portno, struct termios *t) 406 { 407 struct uslhcom_softc *sc = arg; 408 struct uslhcom_uart_config config; 409 int ret; 410 411 if (usbd_is_dying(sc->sc_udev)) 412 return 0; 413 414 ret = uslhcom_create_config(&config, t); 415 if (ret) 416 return ret; 417 418 ret = uslhcom_setup(sc, &config); 419 if (ret) 420 return ret; 421 422 return 0; 423 } 424 425 int 426 uslhcom_open(void *arg, int portno) 427 { 428 struct uslhcom_softc *sc = arg; 429 struct uslhcom_uart_config config; 430 int ret; 431 432 if (usbd_is_dying(sc->sc_udev)) 433 return EIO; 434 435 sc->sc_ibuf = malloc(sc->sc_hdev.sc_osize + sizeof(u_char), 436 M_USBDEV, M_WAITOK); 437 438 uslhcom_set_baud_rate(&config, 9600); 439 config.parity = UART_CONFIG_PARITY_NONE; 440 config.data_control = UART_CONFIG_DATA_CONTROL_NONE; 441 config.data_bits = UART_CONFIG_DATA_BITS_8; 442 config.stop_bits = UART_CONFIG_STOP_BITS_1; 443 444 ret = uslhcom_set_config(sc, &config); 445 if (ret) 446 return ret; 447 448 return 0; 449 } 450 451 void 452 uslhcom_close(void *arg, int portno) 453 { 454 struct uslhcom_softc *sc = arg; 455 int s; 456 457 if (usbd_is_dying(sc->sc_udev)) 458 return; 459 460 uslhcom_uart_endis(sc, UART_DISABLE); 461 462 s = splusb(); 463 if (sc->sc_ibuf != NULL) { 464 free(sc->sc_ibuf, M_USBDEV, 0); 465 sc->sc_ibuf = NULL; 466 } 467 splx(s); 468 } 469 470 void 471 uslhcom_read(void *arg, int portno, u_char **ptr, u_int32_t *cnt) 472 { 473 struct uslhcom_softc *sc = arg; 474 475 *ptr = sc->sc_ibuf; 476 *cnt = sc->sc_icnt; 477 } 478 479 void 480 uslhcom_write(void *arg, int portno, u_char *to, u_char *data, u_int32_t *cnt) 481 { 482 bcopy(data, &to[USLHCOM_TX_HEADER_SIZE], *cnt); 483 to[0] = *cnt; /* add Report ID (= transmit length) */ 484 *cnt += USLHCOM_TX_HEADER_SIZE; 485 } 486 487 void 488 uslhcom_intr(struct uhidev *addr, void *ibuf, u_int len) 489 { 490 extern void ucomreadcb(struct usbd_xfer *, void *, usbd_status); 491 struct uslhcom_softc *sc = (struct uslhcom_softc *)addr; 492 int s; 493 494 if (sc->sc_ibuf == NULL) 495 return; 496 497 s = spltty(); 498 sc->sc_icnt = len; /* Report ID is already stripped */ 499 bcopy(ibuf, sc->sc_ibuf, len); 500 ucomreadcb(addr->sc_parent->sc_ixfer, sc->sc_subdev, 501 USBD_NORMAL_COMPLETION); 502 splx(s); 503 } 504