[WIP] uhidcom(4) driver for USB HID based UART

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

[WIP] uhidcom(4) driver for USB HID based UART

SASANO Takayoshi
Hi,

I am trying to write a driver for Silicon Labs CP2110 USB HID based UART.
Here is work-in-progress code, and it seems to set uca.uhidev properly.
(I wrote code/tested on 5.6-release and ported to -current.)

Exar's XR21B1421 uses similar protocol so I named the driver uhidcom(4),
but currently it is not supported --- too expensive to buy evaluation board.

--
SASANO Takayoshi <[hidden email]>

Index: arch/i386/conf/GENERIC
===================================================================
RCS file: /cvs/src/sys/arch/i386/conf/GENERIC,v
retrieving revision 1.792
diff -u -p -u -p -r1.792 GENERIC
--- arch/i386/conf/GENERIC 11 Dec 2014 19:44:17 -0000 1.792
+++ arch/i386/conf/GENERIC 17 Dec 2014 09:57:54 -0000
@@ -274,6 +274,8 @@ ukbd* at uhidev? # USB keyboard
 wskbd* at ukbd? mux 1
 ucycom* at uhidev? # Cypress serial
 ucom* at ucycom?
+uhidcom* at uhidev? # Silicon Labs CP2110 USB HID UART
+ucom* at uhidcom?
 uticom* at uhub? # TI serial
 ucom* at uticom?
 uhid* at uhidev? # USB generic HID support
Index: dev/usb/files.usb
===================================================================
RCS file: /cvs/src/sys/dev/usb/files.usb,v
retrieving revision 1.120
diff -u -p -u -p -r1.120 files.usb
--- dev/usb/files.usb 11 Dec 2014 19:44:17 -0000 1.120
+++ dev/usb/files.usb 17 Dec 2014 09:58:00 -0000
@@ -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 uhidcom: hid, ucombus
+attach uhidcom at uhidbus
+file dev/usb/uhidcom.c uhidcom needs-flag
+
 # Printers
 device ulpt: firmload
 attach ulpt at uhub
Index: dev/usb/uhidcom.c
===================================================================
RCS file: dev/usb/uhidcom.c
diff -N dev/usb/uhidcom.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ dev/usb/uhidcom.c 17 Dec 2014 09:58:01 -0000
@@ -0,0 +1,489 @@
+/* $OpenBSD: */
+
+/*
+ * Copyright (c) 2014 SASANO Takayoshi <[hidden email]>
+ *
+ * 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/uhidcomreg.h>
+
+#define UHIDCOM_DEBUG
+#ifdef UHIDCOM_DEBUG
+#define DPRINTFN(n, x) do { if(uhidcomdebug > (n)) printf x; } while (0)
+int uhidcomdebug = 10;
+#else
+#define DPRINTFN(n, x)
+#endif
+#define DPRINTF(x) DPRINTFN(0, x)
+
+struct uhidcom_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 uhidcom_get_status(void *, int, u_char *, u_char *);
+void uhidcom_set(void *, int, int, int);
+int uhidcom_param(void *, int, struct termios *);
+int uhidcom_open(void *, int);
+void uhidcom_close(void *, int);
+void uhidcom_write(void *, int, u_char *, u_char *, u_int32_t *);
+void uhidcom_read(void *, int, u_char **, u_int32_t *);
+void uhidcom_intr(struct uhidev *, void *, u_int);
+
+int uhidcom_match(struct device *, void *, void *);
+void uhidcom_attach(struct device *, struct device *, void *);
+int uhidcom_detach(struct device *, int);
+
+usbd_status uhidcom_uart_endis(struct uhidcom_softc *, int);
+usbd_status uhidcom_clear_fifo(struct uhidcom_softc *, int);
+usbd_status uhidcom_get_version(struct uhidcom_softc *, struct uhidcom_version_info *);
+usbd_status uhidcom_get_uart_status(struct uhidcom_softc *, struct uhidcom_uart_status *);
+usbd_status uhidcom_set_break(struct uhidcom_softc *, int);
+usbd_status uhidcom_set_config(struct uhidcom_softc *, struct uhidcom_uart_config *);
+void uhidcom_set_baud_rate(struct uhidcom_uart_config *, u_int32_t);
+int uhidcom_create_config(struct uhidcom_uart_config *, struct termios *);
+int uhidcom_setup(struct uhidcom_softc *, struct uhidcom_uart_config *);
+
+struct ucom_methods uhidcom_methods = {
+ uhidcom_get_status,
+ uhidcom_set,
+ uhidcom_param,
+ NULL,
+ uhidcom_open,
+ uhidcom_close,
+ uhidcom_read,
+ uhidcom_write,
+};
+
+static const struct usb_devno uhidcom_devs[] = {
+ { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2110 },
+};
+
+struct cfdriver uhidcom_cd = {
+ NULL, "uhidcom", DV_DULL
+};
+
+const struct cfattach uhidcom_ca = {
+ sizeof(struct uhidcom_softc),
+ uhidcom_match, uhidcom_attach, uhidcom_detach
+};
+
+/* ----------------------------------------------------------------------
+ * driver entry points
+ */
+
+int
+uhidcom_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(uhidcom_devs,
+   uha->uaa->vendor, uha->uaa->product) != NULL ?
+ UMATCH_VENDOR_PRODUCT : UMATCH_NONE);
+}
+
+void
+uhidcom_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct uhidcom_softc *sc = (struct uhidcom_softc *)self;
+ struct uhidev_attach_arg *uha = aux;
+ struct usbd_device *dev = uha->parent->sc_udev;
+ struct ucom_attach_args uca;
+ struct uhidcom_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 = uhidcom_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(("uhidcom_attach: uhidev_open %d\n", err));
+ return;
+ }
+
+ DPRINTF(("uhidcom_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(("uhidcom_attach: isize %d osize %d fsize %d\n",
+ sc->sc_hdev.sc_isize, sc->sc_hdev.sc_osize,
+ sc->sc_hdev.sc_fsize));
+
+ uhidcom_uart_endis(sc, UART_DISABLE);
+ uhidcom_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 = UHIDCOM_TX_HEADER_SIZE;
+ uca.uhidev = sc->sc_hdev.sc_parent;
+ uca.device = uha->uaa->device;
+ uca.iface = uha->uaa->iface;
+ uca.methods = &uhidcom_methods;
+ uca.arg = sc;
+ uca.info = NULL;
+
+ sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
+}
+
+int
+uhidcom_detach(struct device *self, int flags)
+{
+ struct uhidcom_softc *sc = (struct uhidcom_softc *)self;
+
+ DPRINTF(("uhidcom_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
+ */
+
+usbd_status
+uhidcom_uart_endis(struct uhidcom_softc *sc, int enable)
+{
+ u_char val;
+
+ val = enable;
+ return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
+ GET_SET_UART_ENABLE, &val, sizeof(val));
+}
+
+usbd_status
+uhidcom_clear_fifo(struct uhidcom_softc *sc, int fifo)
+{
+ u_char val;
+
+ val = fifo;
+ return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
+ SET_CLEAR_FIFOS, &val, sizeof(val));
+}
+
+usbd_status
+uhidcom_get_version(struct uhidcom_softc *sc, struct uhidcom_version_info *version)
+{
+ return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
+ GET_VERSION, version, sizeof(*version));
+}
+
+usbd_status
+uhidcom_get_uart_status(struct uhidcom_softc *sc, struct uhidcom_uart_status *status)
+{
+ return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
+ GET_UART_STATUS, &status, sizeof(*status));
+}
+
+usbd_status
+uhidcom_set_break(struct uhidcom_softc *sc, int onoff)
+{
+ int reportid;
+ u_char 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, sizeof(val));
+}
+
+usbd_status
+uhidcom_set_config(struct uhidcom_softc *sc, struct uhidcom_uart_config *config)
+{
+ return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
+ GET_SET_UART_CONFIG, &config->baud_rate,
+ sizeof(struct uhidcom_uart_config) -
+ sizeof(config->report_id));
+}
+
+void
+uhidcom_set_baud_rate(struct uhidcom_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
+uhidcom_create_config(struct uhidcom_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;
+
+ uhidcom_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
+uhidcom_setup(struct uhidcom_softc *sc, struct uhidcom_uart_config *config)
+{
+ struct uhidcom_uart_status status;
+
+ if (uhidcom_uart_endis(sc, UART_DISABLE))
+ return EIO;
+
+ if (uhidcom_set_config(sc, config))
+ return EIO;
+
+ if (uhidcom_clear_fifo(sc, CLEAR_TX_FIFO | CLEAR_RX_FIFO))
+ return EIO;
+
+ if (uhidcom_get_uart_status(sc, &status))
+ return EIO;
+
+ if (uhidcom_uart_endis(sc, UART_ENABLE))
+ return EIO;
+
+ return 0;
+}
+
+/* ----------------------------------------------------------------------
+ * methods for ucom
+ */
+
+void
+uhidcom_get_status(void *arg, int portno, u_char *rlsr, u_char *rmsr)
+{
+ struct uhidcom_softc *sc = arg;
+
+ if (usbd_is_dying(sc->sc_udev))
+ return;
+
+ *rlsr = sc->sc_lsr;
+ *rmsr = sc->sc_msr;
+}
+
+void
+uhidcom_set(void *arg, int portno, int reg, int onoff)
+{
+ struct uhidcom_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:
+ uhidcom_set_break(sc, onoff);
+ break;
+ }
+}
+
+int
+uhidcom_param(void *arg, int portno, struct termios *t)
+{
+ struct uhidcom_softc *sc = arg;
+ struct uhidcom_uart_config config;
+ int ret;
+
+ if (usbd_is_dying(sc->sc_udev))
+ return 0;
+
+ ret = uhidcom_create_config(&config, t);
+ if (ret)
+ return ret;
+
+ ret = uhidcom_setup(sc, &config);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int
+uhidcom_open(void *arg, int portno)
+{
+ struct uhidcom_softc *sc = arg;
+ struct uhidcom_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);
+
+ uhidcom_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 = uhidcom_set_config(sc, &config);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+void
+uhidcom_close(void *arg, int portno)
+{
+ struct uhidcom_softc *sc = arg;
+ int s;
+
+ if (usbd_is_dying(sc->sc_udev))
+ return;
+
+ uhidcom_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
+uhidcom_read(void *arg, int portno, u_char **ptr, u_int32_t *cnt)
+{
+ struct uhidcom_softc *sc = arg;
+
+ *ptr = sc->sc_ibuf;
+ *cnt = sc->sc_icnt;
+}
+
+void
+uhidcom_write(void *arg, int portno, u_char *to, u_char *data, u_int32_t *cnt)
+{
+ bcopy(data, &to[UHIDCOM_TX_HEADER_SIZE], *cnt);
+ to[0] = *cnt; /* add Report ID (= transmit length) */
+ *cnt += UHIDCOM_TX_HEADER_SIZE;
+}
+
+void
+uhidcom_intr(struct uhidev *addr, void *ibuf, u_int len)
+{
+ extern void ucomreadcb(struct usbd_xfer *, void *, usbd_status);
+ struct uhidcom_softc *sc = (struct uhidcom_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);
+}
Index: dev/usb/uhidcomreg.h
===================================================================
RCS file: dev/usb/uhidcomreg.h
diff -N dev/usb/uhidcomreg.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ dev/usb/uhidcomreg.h 17 Dec 2014 09:58:01 -0000
@@ -0,0 +1,106 @@
+/* $OpenBSD: */
+
+/*
+ * Copyright (c) 2014 SASANO Takayoshi <[hidden email]>
+ *
+ * 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 UHIDCOM_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 uhidcom_uart_status {
+ u_char report_id; /* GET_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 uhidcom_version_info {
+ u_char report_id; /* GET_VERSION */
+ u_char product_id;
+ u_char product_revision;
+} __packed;
+
+/* GET_SET_UART_CONFIG */
+struct uhidcom_uart_config {
+ u_char report_id; /* GET_SET_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

Reply | Threaded
Open this post in threaded view
|

Re: [WIP] uhidcom(4) driver for USB HID based UART

Jonathan Gray-11
On Wed, Dec 17, 2014 at 07:49:59PM +0900, SASANO Takayoshi wrote:
> Hi,
>
> I am trying to write a driver for Silicon Labs CP2110 USB HID based UART.
> Here is work-in-progress code, and it seems to set uca.uhidev properly.
> (I wrote code/tested on 5.6-release and ported to -current.)
>
> Exar's XR21B1421 uses similar protocol so I named the driver uhidcom(4),
> but currently it is not supported --- too expensive to buy evaluation board.

Isn't that name a bit too generic?  We already have a USB HID based UART
driver in the tree with ucycom(4), which apparently uses a different protocol?

Reply | Threaded
Open this post in threaded view
|

Re: [WIP] uhidcom(4) driver for USB HID based UART

SASANO Takayoshi
At Wed, 17 Dec 2014 22:43:40 +1100,
Jonathan Gray wrote:

>
> On Wed, Dec 17, 2014 at 07:49:59PM +0900, SASANO Takayoshi wrote:
> > Hi,
> >
> > I am trying to write a driver for Silicon Labs CP2110 USB HID based UART.
> > Here is work-in-progress code, and it seems to set uca.uhidev properly.
> > (I wrote code/tested on 5.6-release and ported to -current.)
> >
> > Exar's XR21B1421 uses similar protocol so I named the driver uhidcom(4),
> > but currently it is not supported --- too expensive to buy evaluation board.
>
> Isn't that name a bit too generic?  We already have a USB HID based UART
> driver in the tree with ucycom(4), which apparently uses a different protocol?
>

uslcom(4) is already used by CP210x, how about uslhcom(4)?
slh means Silicon Lab's Hid protocol.

Otherwise, uxrcom(4).

Regards,
--
SASANO Takayoshi <[hidden email]>

Reply | Threaded
Open this post in threaded view
|

uslhcom(4) driver for USB HID based UART

SASANO Takayoshi
In reply to this post by SASANO Takayoshi
Hello,

Here is the driver for Silicon Labs CP2110 USB HID based UART.
It called uhidcom(4) and now renamed to uslhcom(4).

Now working on -current.

--
SASANO Takayoshi <[hidden email]>

Index: arch/i386/conf/GENERIC
===================================================================
RCS file: /cvs/src/sys/arch/i386/conf/GENERIC,v
retrieving revision 1.792
diff -u -p -r1.792 GENERIC
--- arch/i386/conf/GENERIC 11 Dec 2014 19:44:17 -0000 1.792
+++ arch/i386/conf/GENERIC 2 Jan 2015 20:36:19 -0000
@@ -274,6 +274,8 @@ ukbd* at uhidev? # USB keyboard
 wskbd* at ukbd? mux 1
 ucycom* at uhidev? # Cypress serial
 ucom* at ucycom?
+uslhcom* at uhidev? # Silicon Labs CP2110 USB HID UART
+ucom* at uslhcom?
 uticom* at uhub? # TI serial
 ucom* at uticom?
 uhid* at uhidev? # USB generic HID support
Index: dev/usb/files.usb
===================================================================
RCS file: /cvs/src/sys/dev/usb/files.usb,v
retrieving revision 1.120
diff -u -p -r1.120 files.usb
--- dev/usb/files.usb 11 Dec 2014 19:44:17 -0000 1.120
+++ dev/usb/files.usb 2 Jan 2015 20:36:23 -0000
@@ -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
Index: dev/usb/uslhcom.c
===================================================================
RCS file: dev/usb/uslhcom.c
diff -N dev/usb/uslhcom.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ dev/usb/uslhcom.c 2 Jan 2015 20:36:24 -0000
@@ -0,0 +1,504 @@
+/* $OpenBSD: */
+
+/*
+ * Copyright (c) 2015 SASANO Takayoshi <[hidden email]>
+ *
+ * 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);
+}
Index: dev/usb/uslhcomreg.h
===================================================================
RCS file: dev/usb/uslhcomreg.h
diff -N dev/usb/uslhcomreg.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ dev/usb/uslhcomreg.h 2 Jan 2015 20:36:24 -0000
@@ -0,0 +1,103 @@
+/* $OpenBSD: */
+
+/*
+ * Copyright (c) 2015 SASANO Takayoshi <[hidden email]>
+ *
+ * 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