From 18de92e6c275fcb0cccc95645a4fc616f7bca351 Mon Sep 17 00:00:00 2001 From: uaa Date: Sat, 3 Jan 2015 20:22:56 +0000 Subject: [PATCH] (new) device driver for Silicon Labs CP2110 USB HID based UART. ok by deraadt@ --- sys/dev/usb/files.usb | 7 +- sys/dev/usb/uslhcom.c | 504 +++++++++++++++++++++++++++++++++++++++ sys/dev/usb/uslhcomreg.h | 103 ++++++++ 3 files changed, 613 insertions(+), 1 deletion(-) create mode 100644 sys/dev/usb/uslhcom.c create mode 100644 sys/dev/usb/uslhcomreg.h diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb index abe78d1e0fd..89c67bad0a0 100644 --- a/sys/dev/usb/files.usb +++ b/sys/dev/usb/files.usb @@ -1,4 +1,4 @@ -# $OpenBSD: files.usb,v 1.120 2014/12/11 19:44:17 tedu Exp $ +# $OpenBSD: files.usb,v 1.121 2015/01/03 20:22:56 uaa Exp $ # $NetBSD: files.usb,v 1.16 2000/02/14 20:29:54 augustss Exp $ # # Config file and device description for machine-independent USB code. @@ -110,6 +110,11 @@ device ucycom: hid, ucombus attach ucycom at uhidbus file dev/usb/ucycom.c ucycom needs-flag +# Silicon Labs USB HID based UART controller +device uslhcom: hid, ucombus +attach uslhcom at uhidbus +file dev/usb/uslhcom.c uslhcom needs-flag + # Printers device ulpt: firmload attach ulpt at uhub diff --git a/sys/dev/usb/uslhcom.c b/sys/dev/usb/uslhcom.c new file mode 100644 index 00000000000..2478680be7a --- /dev/null +++ b/sys/dev/usb/uslhcom.c @@ -0,0 +1,504 @@ +/* $OpenBSD: */ + +/* + * Copyright (c) 2015 SASANO Takayoshi + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Device driver for Silicon Labs CP2110 USB HID-UART bridge. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#ifdef USLHCOM_DEBUG +#define DPRINTF(x) if (uslhcomdebug) printf x +#else +#define DPRINTF(x) +#endif + +struct uslhcom_softc { + struct uhidev sc_hdev; + struct usbd_device *sc_udev; + + u_char *sc_ibuf; + u_int sc_icnt; + + u_char sc_lsr; + u_char sc_msr; + + struct device *sc_subdev; +}; + +void uslhcom_get_status(void *, int, u_char *, u_char *); +void uslhcom_set(void *, int, int, int); +int uslhcom_param(void *, int, struct termios *); +int uslhcom_open(void *, int); +void uslhcom_close(void *, int); +void uslhcom_write(void *, int, u_char *, u_char *, u_int32_t *); +void uslhcom_read(void *, int, u_char **, u_int32_t *); +void uslhcom_intr(struct uhidev *, void *, u_int); + +int uslhcom_match(struct device *, void *, void *); +void uslhcom_attach(struct device *, struct device *, void *); +int uslhcom_detach(struct device *, int); + +int uslhcom_uart_endis(struct uslhcom_softc *, int); +int uslhcom_clear_fifo(struct uslhcom_softc *, int); +int uslhcom_get_version(struct uslhcom_softc *, struct uslhcom_version_info *); +int uslhcom_get_uart_status(struct uslhcom_softc *, struct uslhcom_uart_status *); +int uslhcom_set_break(struct uslhcom_softc *, int); +int uslhcom_set_config(struct uslhcom_softc *, struct uslhcom_uart_config *); +void uslhcom_set_baud_rate(struct uslhcom_uart_config *, u_int32_t); +int uslhcom_create_config(struct uslhcom_uart_config *, struct termios *); +int uslhcom_setup(struct uslhcom_softc *, struct uslhcom_uart_config *); + +struct ucom_methods uslhcom_methods = { + uslhcom_get_status, + uslhcom_set, + uslhcom_param, + NULL, + uslhcom_open, + uslhcom_close, + uslhcom_read, + uslhcom_write, +}; + +static const struct usb_devno uslhcom_devs[] = { + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2110 }, +}; + +struct cfdriver uslhcom_cd = { + NULL, "uslhcom", DV_DULL +}; + +const struct cfattach uslhcom_ca = { + sizeof(struct uslhcom_softc), + uslhcom_match, uslhcom_attach, uslhcom_detach +}; + +/* ---------------------------------------------------------------------- + * driver entry points + */ + +int +uslhcom_match(struct device *parent, void *match, void *aux) +{ + struct uhidev_attach_arg *uha = aux; + + /* use all report IDs */ + if (uha->reportid != UHIDEV_CLAIM_ALLREPORTID) + return UMATCH_NONE; + + return (usb_lookup(uslhcom_devs, + uha->uaa->vendor, uha->uaa->product) != NULL ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE); +} + +void +uslhcom_attach(struct device *parent, struct device *self, void *aux) +{ + struct uslhcom_softc *sc = (struct uslhcom_softc *)self; + struct uhidev_attach_arg *uha = aux; + struct usbd_device *dev = uha->parent->sc_udev; + struct ucom_attach_args uca; + struct uslhcom_version_info version; + int err, repid, size, rsize; + void *desc; + + sc->sc_udev = dev; + sc->sc_lsr = sc->sc_msr = 0; + sc->sc_hdev.sc_intr = uslhcom_intr; + sc->sc_hdev.sc_parent = uha->parent; + sc->sc_hdev.sc_report_id = uha->reportid; + sc->sc_hdev.sc_isize = sc->sc_hdev.sc_osize = sc->sc_hdev.sc_fsize = 0; + + uhidev_get_report_desc(uha->parent, &desc, &size); + for (repid = 0; repid < uha->parent->sc_nrepid; repid++) { + rsize = hid_report_size(desc, size, hid_input, repid); + if (sc->sc_hdev.sc_isize < rsize) sc->sc_hdev.sc_isize = rsize; + rsize = hid_report_size(desc, size, hid_output, repid); + if (sc->sc_hdev.sc_osize < rsize) sc->sc_hdev.sc_osize = rsize; + rsize = hid_report_size(desc, size, hid_feature, repid); + if (sc->sc_hdev.sc_fsize < rsize) sc->sc_hdev.sc_fsize = rsize; + } + + printf("\n"); + + err = uhidev_open(&sc->sc_hdev); + if (err) { + DPRINTF(("uslhcom_attach: uhidev_open %d\n", err)); + return; + } + + DPRINTF(("uslhcom_attach: sc %p opipe %p ipipe %p report_id %d\n", + sc, sc->sc_hdev.sc_parent->sc_opipe, + sc->sc_hdev.sc_parent->sc_ipipe, uha->reportid)); + DPRINTF(("uslhcom_attach: isize %d osize %d fsize %d\n", + sc->sc_hdev.sc_isize, sc->sc_hdev.sc_osize, + sc->sc_hdev.sc_fsize)); + + uslhcom_uart_endis(sc, UART_DISABLE); + uslhcom_get_version(sc, &version); + printf("%s: pid %#x rev %#x\n", sc->sc_hdev.sc_dev.dv_xname, + version.product_id, version.product_revision); + + /* setup ucom layer */ + uca.portno = UCOM_UNK_PORTNO; + uca.bulkin = uca.bulkout = -1; + uca.ibufsize = uca.ibufsizepad = 0; + uca.obufsize = sc->sc_hdev.sc_osize; + uca.opkthdrlen = USLHCOM_TX_HEADER_SIZE; + uca.uhidev = sc->sc_hdev.sc_parent; + uca.device = uha->uaa->device; + uca.iface = uha->uaa->iface; + uca.methods = &uslhcom_methods; + uca.arg = sc; + uca.info = NULL; + + sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch); +} + +int +uslhcom_detach(struct device *self, int flags) +{ + struct uslhcom_softc *sc = (struct uslhcom_softc *)self; + + DPRINTF(("uslhcom_detach: sc=%p flags=%d\n", sc, flags)); + if (sc->sc_subdev != NULL) { + config_detach(sc->sc_subdev, flags); + sc->sc_subdev = NULL; + } + + if (sc->sc_hdev.sc_state & UHIDEV_OPEN) + uhidev_close(&sc->sc_hdev); + + return 0; +} + +/* ---------------------------------------------------------------------- + * low level I/O + */ + +int +uslhcom_uart_endis(struct uslhcom_softc *sc, int enable) +{ + int len; + u_char val; + + len = sizeof(val); + val = enable; + + return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, + GET_SET_UART_ENABLE, &val, len) != len; +} + +int +uslhcom_clear_fifo(struct uslhcom_softc *sc, int fifo) +{ + int len; + u_char val; + + len = sizeof(val); + val = fifo; + + return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, + SET_CLEAR_FIFOS, &val, len) != len; +} + +int +uslhcom_get_version(struct uslhcom_softc *sc, struct uslhcom_version_info *version) +{ + int len; + + len = sizeof(*version); + + return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, + GET_VERSION, version, len) < len; +} + +int +uslhcom_get_uart_status(struct uslhcom_softc *sc, struct uslhcom_uart_status *status) +{ + int len; + + len = sizeof(*status); + + return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, + GET_UART_STATUS, status, len) < len; +} + +int +uslhcom_set_break(struct uslhcom_softc *sc, int onoff) +{ + int len, reportid; + u_char val; + + len = sizeof(val); + + if (onoff) { + val = 0; /* send break until SET_STOP_LINE_BREAK */ + reportid = SET_TRANSMIT_LINE_BREAK; + } else { + val = 0; /* any value can be accepted */ + reportid = SET_STOP_LINE_BREAK; + } + + return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, + reportid, &val, len) != len; +} + +int +uslhcom_set_config(struct uslhcom_softc *sc, struct uslhcom_uart_config *config) +{ + int len; + + len = sizeof(*config); + + return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, + GET_SET_UART_CONFIG, config, len) != len; +} + +void +uslhcom_set_baud_rate(struct uslhcom_uart_config *config, u_int32_t baud_rate) +{ + config->baud_rate[0] = baud_rate >> 24; + config->baud_rate[1] = baud_rate >> 16; + config->baud_rate[2] = baud_rate >> 8; + config->baud_rate[3] = baud_rate >> 0; +} + +int +uslhcom_create_config(struct uslhcom_uart_config *config, struct termios *t) +{ + if (t->c_ospeed < UART_CONFIG_BAUD_RATE_MIN || + t->c_ospeed > UART_CONFIG_BAUD_RATE_MAX) + return EINVAL; + + uslhcom_set_baud_rate(config, t->c_ospeed); + + if (ISSET(t->c_cflag, PARENB)) { + if (ISSET(t->c_cflag, PARODD)) + config->parity = UART_CONFIG_PARITY_ODD; + else + config->parity = UART_CONFIG_PARITY_EVEN; + } else + config->parity = UART_CONFIG_PARITY_NONE; + + if (ISSET(t->c_cflag, CRTSCTS)) + config->data_control = UART_CONFIG_DATA_CONTROL_HARD; + else + config->data_control = UART_CONFIG_DATA_CONTROL_NONE; + + switch (ISSET(t->c_cflag, CSIZE)) { + case CS5: + config->data_bits = UART_CONFIG_DATA_BITS_5; + break; + case CS6: + config->data_bits = UART_CONFIG_DATA_BITS_6; + break; + case CS7: + config->data_bits = UART_CONFIG_DATA_BITS_7; + break; + case CS8: + config->data_bits = UART_CONFIG_DATA_BITS_8; + break; + default: + return EINVAL; + } + + if (ISSET(t->c_cflag, CSTOPB)) + config->stop_bits = UART_CONFIG_STOP_BITS_2; + else + config->stop_bits = UART_CONFIG_STOP_BITS_1; + + return 0; +} + +int +uslhcom_setup(struct uslhcom_softc *sc, struct uslhcom_uart_config *config) +{ + struct uslhcom_uart_status status; + + if (uslhcom_uart_endis(sc, UART_DISABLE)) + return EIO; + + if (uslhcom_set_config(sc, config)) + return EIO; + + if (uslhcom_clear_fifo(sc, CLEAR_TX_FIFO | CLEAR_RX_FIFO)) + return EIO; + + if (uslhcom_get_uart_status(sc, &status)) + return EIO; + + if (uslhcom_uart_endis(sc, UART_ENABLE)) + return EIO; + + return 0; +} + +/* ---------------------------------------------------------------------- + * methods for ucom + */ + +void +uslhcom_get_status(void *arg, int portno, u_char *rlsr, u_char *rmsr) +{ + struct uslhcom_softc *sc = arg; + + if (usbd_is_dying(sc->sc_udev)) + return; + + *rlsr = sc->sc_lsr; + *rmsr = sc->sc_msr; +} + +void +uslhcom_set(void *arg, int portno, int reg, int onoff) +{ + struct uslhcom_softc *sc = arg; + + if (usbd_is_dying(sc->sc_udev)) + return; + + switch (reg) { + case UCOM_SET_DTR: + case UCOM_SET_RTS: + /* no support, do nothing */ + break; + case UCOM_SET_BREAK: + uslhcom_set_break(sc, onoff); + break; + } +} + +int +uslhcom_param(void *arg, int portno, struct termios *t) +{ + struct uslhcom_softc *sc = arg; + struct uslhcom_uart_config config; + int ret; + + if (usbd_is_dying(sc->sc_udev)) + return 0; + + ret = uslhcom_create_config(&config, t); + if (ret) + return ret; + + ret = uslhcom_setup(sc, &config); + if (ret) + return ret; + + return 0; +} + +int +uslhcom_open(void *arg, int portno) +{ + struct uslhcom_softc *sc = arg; + struct uslhcom_uart_config config; + int ret; + + if (usbd_is_dying(sc->sc_udev)) + return EIO; + + sc->sc_ibuf = malloc(sc->sc_hdev.sc_osize + sizeof(u_char), + M_USBDEV, M_WAITOK); + + uslhcom_set_baud_rate(&config, 9600); + config.parity = UART_CONFIG_PARITY_NONE; + config.data_control = UART_CONFIG_DATA_CONTROL_NONE; + config.data_bits = UART_CONFIG_DATA_BITS_8; + config.stop_bits = UART_CONFIG_STOP_BITS_1; + + ret = uslhcom_set_config(sc, &config); + if (ret) + return ret; + + return 0; +} + +void +uslhcom_close(void *arg, int portno) +{ + struct uslhcom_softc *sc = arg; + int s; + + if (usbd_is_dying(sc->sc_udev)) + return; + + uslhcom_uart_endis(sc, UART_DISABLE); + + s = splusb(); + if (sc->sc_ibuf != NULL) { + free(sc->sc_ibuf, M_USBDEV, 0); + sc->sc_ibuf = NULL; + } + splx(s); +} + +void +uslhcom_read(void *arg, int portno, u_char **ptr, u_int32_t *cnt) +{ + struct uslhcom_softc *sc = arg; + + *ptr = sc->sc_ibuf; + *cnt = sc->sc_icnt; +} + +void +uslhcom_write(void *arg, int portno, u_char *to, u_char *data, u_int32_t *cnt) +{ + bcopy(data, &to[USLHCOM_TX_HEADER_SIZE], *cnt); + to[0] = *cnt; /* add Report ID (= transmit length) */ + *cnt += USLHCOM_TX_HEADER_SIZE; +} + +void +uslhcom_intr(struct uhidev *addr, void *ibuf, u_int len) +{ + extern void ucomreadcb(struct usbd_xfer *, void *, usbd_status); + struct uslhcom_softc *sc = (struct uslhcom_softc *)addr; + int s; + + if (sc->sc_ibuf == NULL) + return; + + s = spltty(); + sc->sc_icnt = len; /* Report ID is already stripped */ + bcopy(ibuf, sc->sc_ibuf, len); + ucomreadcb(addr->sc_parent->sc_ixfer, sc->sc_subdev, + USBD_NORMAL_COMPLETION); + splx(s); +} diff --git a/sys/dev/usb/uslhcomreg.h b/sys/dev/usb/uslhcomreg.h new file mode 100644 index 00000000000..286cde277e7 --- /dev/null +++ b/sys/dev/usb/uslhcomreg.h @@ -0,0 +1,103 @@ +/* $OpenBSD: */ + +/* + * Copyright (c) 2015 SASANO Takayoshi + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define USLHCOM_TX_HEADER_SIZE sizeof(u_char) + +#define SET_TRANSMIT_DATA(x) (x) +#define GET_RECEIVE_DATA(x) (x) + +#define SET_DEVICE_RESET 0x40 +#define GET_SET_UART_ENABLE 0x41 +#define GET_UART_STATUS 0x42 +#define SET_CLEAR_FIFOS 0x43 +#define GET_GPIO_STATE 0x44 +#define SET_GPIO_STATE 0x45 +#define GET_VERSION 0x46 +#define GET_SET_OTP_LOCK_BYTE 0x47 + +#define GET_SET_UART_CONFIG 0x50 +#define SET_TRANSMIT_LINE_BREAK 0x51 +#define SET_STOP_LINE_BREAK 0x52 + + +/* SET_DEVICE_RESET */ +#define DEVICE_RESET_VALUE 0x00 + +/* GET_SET_UART_ENABLE */ +#define UART_DISABLE 0x00 +#define UART_ENABLE 0x01 + +/* GET_UART_STATUS */ +struct uslhcom_uart_status { + u_char tx_fifo[2]; /* (big endian) */ + u_char rx_fifo[2]; /* (big endian) */ + u_char error_status; + u_char break_status; +} __packed; + +#define ERROR_STATUS_PARITY 0x01 +#define ERROR_STATUS_OVERRUN 0x02 +#define BREAK_STATUS 0x01 + +/* SET_CLEAR_FIFO */ +#define CLEAR_TX_FIFO 0x01 +#define CLEAR_RX_FIFO 0x02 + +/* GET_VERSION */ +struct uslhcom_version_info { + u_char product_id; + u_char product_revision; +} __packed; + +/* GET_SET_UART_CONFIG */ +struct uslhcom_uart_config { + u_char baud_rate[4]; /* (big endian) */ + u_char parity; + u_char data_control; + u_char data_bits; + u_char stop_bits; +} __packed; + +/* + * Silicon Labs CP2110/4 Application Note (AN434) Rev 0.4 says that + * valid baud rate is 300bps to 500,000bps. + * But HidUartSample of CP2110 SDK accepts 50bps to 2,000,000bps. + */ +#define UART_CONFIG_BAUD_RATE_MIN 50 +#define UART_CONFIG_BAUD_RATE_MAX 2000000 + +#define UART_CONFIG_PARITY_NONE 0x00 +#define UART_CONFIG_PARITY_EVEN 0x01 +#define UART_CONFIG_PARITY_ODD 0x02 +#define UART_CONFIG_PARITY_MARK 0x03 +#define UART_CONFIG_PARITY_SPACE 0x04 + +#define UART_CONFIG_DATA_CONTROL_NONE 0x00 +#define UART_CONFIG_DATA_CONTROL_HARD 0x01 + +/* + * AN434 Rev 0.4 describes setting 0x05 ... 0x08 to configure data bits. + * But actually it requires different values. + */ +#define UART_CONFIG_DATA_BITS_5 0x00 +#define UART_CONFIG_DATA_BITS_6 0x01 +#define UART_CONFIG_DATA_BITS_7 0x02 +#define UART_CONFIG_DATA_BITS_8 0x03 + +#define UART_CONFIG_STOP_BITS_1 0x00 +#define UART_CONFIG_STOP_BITS_2 0x01 -- 2.20.1