In principle this hardware implements the standard USB Communication
Device Class interface which is supported by umodem(4).  However, the
hardware enables hardware flow control bu default in CDC mode.  This
means that if the flow control lines are not (properly) connected, the
device isn't fully functional.  This is the case on the HiKey 970
board, where serial output works fine, but no input is accepted.

Hardware flow control can be disabled by writing to a device-specific
register.  It seems easiest to just implement a dedicated driver for
this device rather than quirking umodem(4).  

ok?


Index: dev/usb/files.usb
===================================================================
RCS file: /cvs/src/sys/dev/usb/files.usb,v
retrieving revision 1.136
diff -u -p -r1.136 files.usb
--- dev/usb/files.usb   25 Aug 2018 20:31:31 -0000      1.136
+++ dev/usb/files.usb   24 Mar 2019 17:26:33 -0000
@@ -342,6 +342,11 @@ device     uscom: ucombus
 attach uscom at uhub
 file   dev/usb/uscom.c                 uscom
 
+# Exar XR21V1410
+device uxrcom: ucombus
+attach uxrcom at uhub
+file   dev/usb/uxrcom.c                uxrcom
+
 # iPAQ PDAs
 # Generic ipaq support
 device uipaq: ucombus
Index: dev/usb/usbdevs
===================================================================
RCS file: /cvs/src/sys/dev/usb/usbdevs,v
retrieving revision 1.696
diff -u -p -r1.696 usbdevs
--- dev/usb/usbdevs     22 Mar 2019 12:02:38 -0000      1.696
+++ dev/usb/usbdevs     24 Mar 2019 17:26:33 -0000
@@ -143,6 +143,7 @@ vendor HOLTEK               0x04d9  Holtek
 vendor PANASONIC       0x04da  Panasonic (Matsushita)
 vendor SHARP           0x04dd  Sharp
 vendor IIYAMA          0x04e1  Iiyama
+vendor EXAR            0x04e2  Exar
 vendor SHUTTLE         0x04e6  Shuttle Technology
 vendor SAMSUNG2                0x04e8  Samsung Electronics
 vendor ANNABOOKS       0x04ed  Annabooks
@@ -1759,6 +1760,9 @@ product EVOLUTION RCM4_2  0x0303  RCM4 int
 
 /* Extended Systems products */
 product EXTENDED XTNDACCESS    0x0100  XTNDAccess IrDA
+
+/* Exar products */
+product EXAR XR21V1410         0x1410  XR21V1410
 
 /* Falcom Wireless Communications GmbH products */
 product FALCOM TWIST           0x0001  Twist
Index: dev/usb/uxrcom.c
===================================================================
RCS file: dev/usb/uxrcom.c
diff -N dev/usb/uxrcom.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ dev/usb/uxrcom.c    24 Mar 2019 17:26:33 -0000
@@ -0,0 +1,436 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2006 Jonathan Gray <j...@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.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/tty.h>
+#include <sys/device.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbcdc.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbdevs.h>
+
+#include <dev/usb/ucomvar.h>
+
+#ifdef UXRCOM_DEBUG
+#define DPRINTFN(n, x)  do { if (uxrcomdebug > (n)) printf x; } while (0)
+int    uxrcomdebug = 0;
+#else
+#define DPRINTFN(n, x)
+#endif
+#define DPRINTF(x) DPRINTFN(0, x)
+
+#define UXRCOMBUFSZ            64
+#define UXRCOM_INTR_IFACE_NO   0
+#define UXRCOM_DATA_IFACE_NO   1
+
+#define XR_SET_REG             0
+#define XR_GET_REGN            1
+
+#define XR_FLOW_CONTROL                0x000c
+#define  XR_FLOW_CONTROL_ON    1
+#define  XR_FLOW_CONTROL_OFF   0
+#define XR_TX_BREAK            0x0014
+#define  XR_TX_BREAK_ON                1
+#define  XR_TX_BREAK_OFF       0
+#define XR_GPIO_SET            0x001d
+#define XR_GPIO_CLEAR          0x001e
+#define  XR_GPIO3              (1 << 3)
+#define  XR_GPIO5              (1 << 5)
+
+struct uxrcom_softc {
+       struct device           sc_dev;
+       struct usbd_device      *sc_udev;
+       struct usbd_interface   *sc_intr_iface;
+       struct usbd_interface   *sc_data_iface;
+       struct device           *sc_subdev;
+
+       struct usb_cdc_line_state sc_line_state;
+
+       int                     sc_intr_number;
+       struct usbd_pipe        *sc_intr_pipe;
+       struct usb_cdc_notification sc_intr_buf;
+       u_char                  sc_msr;
+       u_char                  sc_lsr;
+};
+
+void   uxrcom_get_status(void *, int, u_char *, u_char *);
+void   uxrcom_set(void *, int, int, int);
+int    uxrcom_param(void *, int, struct termios *);
+int    uxrcom_open(void *, int);
+void   uxrcom_close(void *, int);
+void   uxrcom_break(void *, int, int);
+void   uxrcom_intr(struct usbd_xfer *, void *, usbd_status);
+
+struct ucom_methods uxrcom_methods = {
+       uxrcom_get_status,
+       uxrcom_set,
+       uxrcom_param,
+       NULL,
+       uxrcom_open,
+       uxrcom_close,
+       NULL,
+       NULL,
+};
+
+static const struct usb_devno uxrcom_devs[] = {
+       { USB_VENDOR_EXAR,              USB_PRODUCT_EXAR_XR21V1410 },
+};
+
+int uxrcom_match(struct device *, void *, void *);
+void uxrcom_attach(struct device *, struct device *, void *);
+int uxrcom_detach(struct device *, int);
+
+struct cfdriver uxrcom_cd = {
+       NULL, "uxrcom", DV_DULL
+};
+
+const struct cfattach uxrcom_ca = {
+       sizeof(struct uxrcom_softc), uxrcom_match, uxrcom_attach, uxrcom_detach
+};
+
+int
+uxrcom_match(struct device *parent, void *match, void *aux)
+{
+       struct usb_attach_arg *uaa = aux;
+
+       if (uaa->iface == NULL)
+               return UMATCH_NONE;
+       if (uaa->ifaceno != UXRCOM_INTR_IFACE_NO)
+               return UMATCH_NONE;
+
+       return (usb_lookup(uxrcom_devs, uaa->vendor, uaa->product) != NULL) ?
+           UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
+}
+
+void
+uxrcom_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct uxrcom_softc *sc = (struct uxrcom_softc *)self;
+       struct usb_attach_arg *uaa = aux;
+       struct ucom_attach_args uca;
+       usb_interface_descriptor_t *id;
+       usb_endpoint_descriptor_t *ed;
+       usbd_status error;
+       int i;
+
+       memset(&uca, 0, sizeof(uca));
+       sc->sc_udev = uaa->device;
+       sc->sc_intr_iface = uaa->iface;
+       sc->sc_intr_number = -1;
+
+       id = usbd_get_interface_descriptor(sc->sc_intr_iface);
+       for (i = 0; i < id->bNumEndpoints; i++) {
+               ed = usbd_interface2endpoint_descriptor(sc->sc_intr_iface, i);
+               if (ed == NULL) {
+                       printf("%s: no endpoint descriptor found for %d\n",
+                           sc->sc_dev.dv_xname, i);
+                       usbd_deactivate(sc->sc_udev);
+                       return;
+               }
+
+               if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+                   UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT)
+                       sc->sc_intr_number = ed->bEndpointAddress;
+       }
+
+       error = usbd_device2interface_handle(sc->sc_udev, UXRCOM_DATA_IFACE_NO,
+           &sc->sc_data_iface);
+       if (error != 0) {
+               printf("%s: could not get data interface handle\n",
+                   sc->sc_dev.dv_xname);
+               usbd_deactivate(sc->sc_udev);
+               return;
+       }
+
+       id = usbd_get_interface_descriptor(sc->sc_data_iface);
+       uca.bulkin = uca.bulkout = -1;
+       for (i = 0; i < id->bNumEndpoints; i++) {
+               ed = usbd_interface2endpoint_descriptor(sc->sc_data_iface, i);
+               if (ed == NULL) {
+                       printf("%s: no endpoint descriptor found for %d\n",
+                           sc->sc_dev.dv_xname, i);
+                       usbd_deactivate(sc->sc_udev);
+                       return;
+               }
+
+               if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+                   UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
+                       uca.bulkin = ed->bEndpointAddress;
+               else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
+                   UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
+                       uca.bulkout = ed->bEndpointAddress;
+       }
+
+       if (uca.bulkin == -1 || uca.bulkout == -1) {
+               printf("%s: missing endpoint\n", sc->sc_dev.dv_xname);
+               usbd_deactivate(sc->sc_udev);
+               return;
+       }
+
+       usbd_claim_iface(sc->sc_udev, UXRCOM_DATA_IFACE_NO);
+
+       uca.ibufsize = UXRCOMBUFSZ;
+       uca.obufsize = UXRCOMBUFSZ;
+       uca.ibufsizepad = UXRCOMBUFSZ;
+       uca.opkthdrlen = 0;
+       uca.device = sc->sc_udev;
+       uca.iface = sc->sc_data_iface;
+       uca.methods = &uxrcom_methods;
+       uca.arg = sc;
+       uca.info = NULL;
+
+       sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
+}
+
+int
+uxrcom_detach(struct device *self, int flags)
+{
+       struct uxrcom_softc *sc = (struct uxrcom_softc *)self;
+       int rv = 0;
+
+       if (sc->sc_intr_pipe != NULL) {
+               usbd_abort_pipe(sc->sc_intr_pipe);
+               usbd_close_pipe(sc->sc_intr_pipe);
+               sc->sc_intr_pipe = NULL;
+       }
+
+       if (sc->sc_subdev != NULL)
+               rv = config_detach(sc->sc_subdev, flags);
+
+       return rv;
+}
+
+int
+uxrcom_open(void *vsc, int portno)
+{
+       struct uxrcom_softc *sc = vsc;
+       int err;
+
+       if (sc->sc_intr_number != -1 && sc->sc_intr_pipe == NULL) {
+               err = usbd_open_pipe_intr(sc->sc_intr_iface,
+                   sc->sc_intr_number, USBD_SHORT_XFER_OK, &sc->sc_intr_pipe,
+                   sc, &sc->sc_intr_buf, sizeof(sc->sc_intr_buf),
+                   uxrcom_intr, USBD_DEFAULT_INTERVAL);
+               if (err)
+                       return EIO;
+       }
+
+       return 0;
+}
+
+void
+uxrcom_close(void *vsc, int portno)
+{
+       struct uxrcom_softc *sc = vsc;
+       int err;
+
+       if (usbd_is_dying(sc->sc_udev))
+               return;
+
+       if (sc->sc_intr_pipe != NULL) {
+               usbd_abort_pipe(sc->sc_intr_pipe);
+               err = usbd_close_pipe(sc->sc_intr_pipe);
+               if (err)
+                       printf("%s: close intr pipe failed: %s\n",
+                           sc->sc_dev.dv_xname, usbd_errstr(err));
+               sc->sc_intr_pipe = NULL;
+       }
+}
+
+void
+uxrcom_set(void *vsc, int portno, int reg, int onoff)
+{
+       struct uxrcom_softc *sc = vsc;
+       usb_device_request_t req;
+       uint16_t index;
+       uint8_t value;
+
+       index = onoff ? XR_GPIO_SET : XR_GPIO_CLEAR;
+
+       switch (reg) {
+       case UCOM_SET_DTR:
+               value = XR_GPIO3;
+               break;
+       case UCOM_SET_RTS:
+               value = XR_GPIO5;
+               break;
+       case UCOM_SET_BREAK:
+               uxrcom_break(sc, portno, onoff);
+               return;
+       default:
+               return;
+       }
+       req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+       req.bRequest = XR_SET_REG;
+       USETW(req.wValue, value);
+       USETW(req.wIndex, index);
+       USETW(req.wLength, 0);
+       usbd_do_request(sc->sc_udev, &req, NULL);
+}
+
+usbd_status
+uxrcom_set_line_coding(struct uxrcom_softc *sc,
+    struct usb_cdc_line_state *state)
+{
+       usb_device_request_t req;
+       usbd_status err;
+
+       if (memcmp(state, &sc->sc_line_state, UCDC_LINE_STATE_LENGTH) == 0)
+               return USBD_NORMAL_COMPLETION;
+
+       req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+       req.bRequest = UCDC_SET_LINE_CODING;
+       USETW(req.wValue, 0);
+       USETW(req.wIndex, UXRCOM_INTR_IFACE_NO);
+       USETW(req.wLength, UCDC_LINE_STATE_LENGTH);
+
+       err = usbd_do_request(sc->sc_udev, &req, state);
+       if (err)
+               return err;
+
+       sc->sc_line_state = *state;
+
+       return USBD_NORMAL_COMPLETION;
+}
+
+int
+uxrcom_param(void *vsc, int portno, struct termios *t)
+{
+       struct uxrcom_softc *sc = (struct uxrcom_softc *)vsc;
+       usb_device_request_t req;
+       usbd_status err;
+       struct usb_cdc_line_state ls;
+       uint8_t flowctrl;
+
+       if (t->c_ospeed <= 0 || t->c_ospeed > 48000000)
+               return (EINVAL);
+
+       USETDW(ls.dwDTERate, t->c_ospeed);
+       if (ISSET(t->c_cflag, CSTOPB))
+               ls.bCharFormat = UCDC_STOP_BIT_2;
+       else
+               ls.bCharFormat = UCDC_STOP_BIT_1;
+       if (ISSET(t->c_cflag, PARENB)) {
+               if (ISSET(t->c_cflag, PARODD))
+                       ls.bParityType = UCDC_PARITY_ODD;
+               else
+                       ls.bParityType = UCDC_PARITY_EVEN;
+       } else
+               ls.bParityType = UCDC_PARITY_NONE;
+       switch (ISSET(t->c_cflag, CSIZE)) {
+       case CS5:
+               ls.bDataBits = 5;
+               break;
+       case CS6:
+               ls.bDataBits = 6;
+               break;
+       case CS7:
+               ls.bDataBits = 7;
+               break;
+       case CS8:
+               ls.bDataBits = 8;
+               break;
+       }
+
+       err = uxrcom_set_line_coding(sc, &ls);
+       if (err)
+               return (EIO);
+
+       if (ISSET(t->c_cflag, CRTSCTS)) {
+               /*  rts/cts flow ctl */
+               flowctrl = XR_FLOW_CONTROL_ON;
+       } else {
+               /* disable flow ctl */
+               flowctrl = XR_FLOW_CONTROL_OFF;
+       }
+       req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+       req.bRequest = XR_SET_REG;
+       USETW(req.wValue, flowctrl);
+       USETW(req.wIndex, XR_FLOW_CONTROL);
+       USETW(req.wLength, 0);
+       usbd_do_request(sc->sc_udev, &req, NULL);
+
+       return (0);
+}
+
+void
+uxrcom_intr(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+       struct uxrcom_softc *sc = priv;
+       uint8_t mstatus;
+
+       if (usbd_is_dying(sc->sc_udev))
+               return;
+
+       if (status != USBD_NORMAL_COMPLETION) {
+               if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
+                       return;
+               usbd_clear_endpoint_stall_async(sc->sc_intr_pipe);
+               return;
+       }
+
+       if (sc->sc_intr_buf.bmRequestType != UCDC_NOTIFICATION)
+               return;
+
+       switch (sc->sc_intr_buf.bNotification) {
+       case UCDC_N_SERIAL_STATE:
+               sc->sc_lsr = sc->sc_msr = 0;
+               mstatus = sc->sc_intr_buf.data[0];
+
+               if (ISSET(mstatus, UCDC_N_SERIAL_RI))
+                       sc->sc_msr |= UMSR_RI;
+               if (ISSET(mstatus, UCDC_N_SERIAL_DSR))
+                       sc->sc_msr |= UMSR_DSR;
+               if (ISSET(mstatus, UCDC_N_SERIAL_DCD))
+                       sc->sc_msr |= UMSR_DCD;
+               ucom_status_change((struct ucom_softc *)sc->sc_subdev);
+               break;
+       default:
+               break;
+       }
+}
+
+void
+uxrcom_get_status(void *vsc, int portno, u_char *lsr, u_char *msr)
+{
+       struct uxrcom_softc *sc = vsc;
+       
+       if (msr != NULL)
+               *msr = sc->sc_msr;
+       if (lsr != NULL)
+               *lsr = sc->sc_lsr;
+}
+
+void
+uxrcom_break(void *vsc, int portno, int onoff)
+{
+       struct uxrcom_softc *sc = vsc;
+       usb_device_request_t req;
+       uint8_t brk = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF;
+
+       req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+       req.bRequest = XR_SET_REG;
+       USETW(req.wValue, brk);
+       USETW(req.wIndex, XR_TX_BREAK);
+       USETW(req.wLength, 0);
+       usbd_do_request(sc->sc_udev, &req, NULL);
+}

Reply via email to