--- /dev/null
+/* $OpenBSD: */
+
+/*
+ * Copyright (c) 2015 SASANO Takayoshi <uaa@openbsd.org>
+ *
+ * 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/conf.h>
+#include <sys/tty.h>
+#include <sys/device.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbdevs.h>
+
+#include <dev/usb/hid.h>
+#include <dev/usb/usbhid.h>
+#include <dev/usb/uhidev.h>
+
+#include <dev/usb/ucomvar.h>
+#include <dev/usb/uslhcomreg.h>
+
+#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);
+}
--- /dev/null
+/* $OpenBSD: */
+
+/*
+ * Copyright (c) 2015 SASANO Takayoshi <uaa@openbsd.org>
+ *
+ * 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