Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 28 Apr 2014 12:46:24 +0000 (UTC)
From:      Ian Lepore <ian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-10@freebsd.org
Subject:   svn commit: r265047 - in stable/10: share/man/man4 sys/dev/usb sys/dev/usb/serial
Message-ID:  <201404281246.s3SCkORV030885@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ian
Date: Mon Apr 28 12:46:23 2014
New Revision: 265047
URL: http://svnweb.freebsd.org/changeset/base/265047

Log:
  MFC uftdi(4) driver changes...
    r264010: Support speeds up to 12mbaud on newer chips.
    r264031: Use 2K IO buffers for improved throughput.
    r264149: Add ioctl(2) calls to access bitbang, MPSSE, CPU_FIFO,
             and other modes.

Added:
  stable/10/sys/dev/usb/uftdiio.h
     - copied unchanged from r264149, head/sys/dev/usb/uftdiio.h
Modified:
  stable/10/share/man/man4/uftdi.4
  stable/10/sys/dev/usb/serial/uftdi.c
  stable/10/sys/dev/usb/serial/uftdi_reg.h
Directory Properties:
  stable/10/   (props changed)

Modified: stable/10/share/man/man4/uftdi.4
==============================================================================
--- stable/10/share/man/man4/uftdi.4	Mon Apr 28 09:00:00 2014	(r265046)
+++ stable/10/share/man/man4/uftdi.4	Mon Apr 28 12:46:23 2014	(r265047)
@@ -61,6 +61,87 @@ The device is accessed through the
 .Xr ucom 4
 driver which makes it behave like a
 .Xr tty 4 .
+.Pp
+Many of the supported chips provide additional functionality
+such as bitbang mode and the MPSSE engine for serial bus emulation.
+The
+.Nm
+driver provides access to that functionality with the following
+.Xr ioctl 2
+calls, defined in
+.In dev/usb/uftdiio.h :
+.Bl -tag -width indent
+.It Dv UFTDIIOC_RESET_IO Pq Vt int
+Reset the channel to its default configuration, flush RX and TX FIFOs.
+.It Dv UFTDIIOC_RESET_RX Pq Vt int
+Flush the RX FIFO.
+.It Dv UFTDIIOC_RESET_TX Pq Vt int
+Flush the TX FIFO.
+.It Dv UFTDIIOC_SET_BITMODE Pq Vt "struct uftdi_bitmode"
+Put the channel into the operating mode specified in
+.Va mode ,
+and set the pins indicated by ones in
+.Va iomask
+to output mode.
+The
+.Va mode
+must be one of the
+.Va uftdi_bitmodes
+values.
+.Bd -literal
+enum uftdi_bitmodes
+{
+	UFTDI_BITMODE_ASYNC = 0,
+	UFTDI_BITMODE_MPSSE = 1,
+	UFTDI_BITMODE_SYNC = 2,
+	UFTDI_BITMODE_CPU_EMUL = 3,
+	UFTDI_BITMODE_FAST_SERIAL = 4,
+	UFTDI_BITMODE_CBUS = 5,
+	UFTDI_BITMODE_NONE = 0xff,
+};
+
+struct uftdi_bitmode
+{
+	uint8_t mode;
+	uint8_t iomask;
+};
+.Ed
+.Pp
+Manuals and application notes published by FTDI describe these
+modes in detail.
+To use most of these modes, you first put the channel into
+the desired mode, then you
+.Xr read 2
+and
+.Xr write 2
+data which either reflects pin state or is interpreted 
+as MPSSE commands and parameters, depending on the mode.
+.It Dv UFTDIIOC_GET_BITMODE Pq Vt "struct uftdi_bitmode"
+Return the state of the bitbang pins at the time of the call in the
+.Va iomask
+member.
+The
+.Va mode
+member is unused.
+.It Dv UFTDIIOC_SET_ERROR_CHAR Pq Vt int
+Set the character which is inserted into the buffer to mark
+the point of an error such as FIFO overflow.
+.It Dv UFTDIIOC_SET_EVENT_CHAR Pq Vt int
+Set the character which causes a partial FIFO full of data 
+to be returned immediately even if the FIFO is not full.
+.It Dv UFTDIIOC_SET_LATENCY Pq Vt int
+Set the amount of time to wait for a full FIFO,
+in milliseconds.
+If more than this much time elapses without receiving a new
+character, any characters in the FIFO are returned.
+.It Dv UFTDIIOC_GET_LATENCY Pq Vt int
+Get the current value of the latency timer.
+.It Dv UFTDIIOC_GET_HWREV Pq Vt int
+Get the hardware revision number.
+This is the
+.Va bcdDevice
+value from the
+.Va usb_device_descriptor .
 .Sh HARDWARE
 The
 .Nm

Modified: stable/10/sys/dev/usb/serial/uftdi.c
==============================================================================
--- stable/10/sys/dev/usb/serial/uftdi.c	Mon Apr 28 09:00:00 2014	(r265046)
+++ stable/10/sys/dev/usb/serial/uftdi.c	Mon Apr 28 12:46:23 2014	(r265047)
@@ -38,7 +38,14 @@ __FBSDID("$FreeBSD$");
  */
 
 /*
- * FTDI FT2232x, FT8U100AX and FT8U232AM serial adapter driver
+ * FTDI FT232x, FT2232x, FT4232x, FT8U100AX and FT8U232xM serial adapters.
+ *
+ * Note that we specifically do not do a reset or otherwise alter the state of
+ * the chip during attach, detach, open, and close, because it could be
+ * pre-initialized (via an attached serial eeprom) to power-on into a mode such
+ * as bitbang in which the pins are being driven to a specific state which we
+ * must not perturb.  The device gets reset at power-on, and doesn't need to be
+ * reset again after that to function, except as directed by ioctl() calls.
  */
 
 #include <sys/stdint.h>
@@ -63,6 +70,8 @@ __FBSDID("$FreeBSD$");
 #include <dev/usb/usb.h>
 #include <dev/usb/usbdi.h>
 #include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_ioctl.h>
 #include "usbdevs.h"
 
 #define	USB_DEBUG_VAR uftdi_debug
@@ -71,6 +80,7 @@ __FBSDID("$FreeBSD$");
 
 #include <dev/usb/serial/usb_serial.h>
 #include <dev/usb/serial/uftdi_reg.h>
+#include <dev/usb/uftdiio.h>
 
 #ifdef USB_DEBUG
 static int uftdi_debug = 0;
@@ -83,8 +93,34 @@ SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, debu
 #define	UFTDI_CONFIG_INDEX	0
 #define	UFTDI_IFACE_INDEX_JTAG	0
 
-#define	UFTDI_OBUFSIZE 64		/* bytes, cannot be increased due to
-					 * do size encoding */
+/*
+ * IO buffer sizes and FTDI device procotol sizes.
+ *
+ * Note that the output packet size in the following defines is not the usb
+ * protocol packet size based on bus speed, it is the size dictated by the FTDI
+ * device itself, and is used only on older chips.
+ *
+ * We allocate buffers bigger than the hardware's packet size, and process
+ * multiple packets within each buffer.  This allows the controller to make
+ * optimal use of the usb bus by conducting multiple transfers with the device
+ * during a single bus timeslice to fill or drain the chip's fifos.
+ *
+ * The output data on newer chips has no packet header, and we are able to pack
+ * any number of output bytes into a buffer.  On some older chips, each output
+ * packet contains a 1-byte header and up to 63 bytes of payload.  The size is
+ * encoded in 6 bits of the header, hence the 64-byte limit on packet size.  We
+ * loop to fill the buffer with many of these header+payload packets.
+ *
+ * The input data on all chips consists of packets which contain a 2-byte header
+ * followed by data payload.  The total size of the packet is wMaxPacketSize
+ * which can change based on the bus speed (e.g., 64 for full speed, 512 for
+ * high speed).  We loop to extract the headers and payloads from the packets
+ * packed into an input buffer.
+ */
+#define	UFTDI_IBUFSIZE	2048
+#define	UFTDI_IHDRSIZE	   2
+#define	UFTDI_OBUFSIZE	2048
+#define	UFTDI_OPKTSIZE	  64
 
 enum {
 	UFTDI_BULK_DT_WR,
@@ -92,6 +128,21 @@ enum {
 	UFTDI_N_TRANSFER,
 };
 
+enum {
+	DEVT_SIO,
+	DEVT_232A,
+	DEVT_232B,
+	DEVT_2232D,	/* Includes 2232C */
+	DEVT_232R,
+	DEVT_2232H,
+	DEVT_4232H,
+	DEVT_232H,
+	DEVT_230X,
+};
+
+#define	DEVF_BAUDBITS_HINDEX	0x01	/* Baud bits in high byte of index. */
+#define	DEVF_BAUDCLK_12M	0X02	/* Base baud clock is 12MHz. */
+
 struct uftdi_softc {
 	struct ucom_super_softc sc_super_ucom;
 	struct ucom_softc sc_ucom;
@@ -104,16 +155,18 @@ struct uftdi_softc {
 	uint32_t sc_unit;
 
 	uint16_t sc_last_lcr;
+	uint16_t sc_bcdDevice;
 
-	uint8_t sc_type;
-	uint8_t	sc_iface_index;
+	uint8_t sc_devtype;
+	uint8_t sc_devflags;
 	uint8_t	sc_hdrlen;
 	uint8_t	sc_msr;
 	uint8_t	sc_lsr;
 };
 
 struct uftdi_param_config {
-	uint16_t rate;
+	uint16_t baud_lobits;
+	uint16_t baud_hibits;
 	uint16_t lcr;
 	uint8_t	v_start;
 	uint8_t	v_stop;
@@ -132,20 +185,29 @@ static usb_callback_t uftdi_read_callbac
 
 static void	uftdi_free(struct ucom_softc *);
 static void	uftdi_cfg_open(struct ucom_softc *);
+static void	uftdi_cfg_close(struct ucom_softc *);
 static void	uftdi_cfg_set_dtr(struct ucom_softc *, uint8_t);
 static void	uftdi_cfg_set_rts(struct ucom_softc *, uint8_t);
 static void	uftdi_cfg_set_break(struct ucom_softc *, uint8_t);
-static int	uftdi_set_parm_soft(struct termios *,
-		    struct uftdi_param_config *, uint8_t);
+static int	uftdi_set_parm_soft(struct ucom_softc *, struct termios *,
+		    struct uftdi_param_config *);
 static int	uftdi_pre_param(struct ucom_softc *, struct termios *);
 static void	uftdi_cfg_param(struct ucom_softc *, struct termios *);
 static void	uftdi_cfg_get_status(struct ucom_softc *, uint8_t *,
 		    uint8_t *);
+static int	uftdi_reset(struct ucom_softc *, int);
+static int	uftdi_set_bitmode(struct ucom_softc *, uint8_t, uint8_t);
+static int	uftdi_get_bitmode(struct ucom_softc *, uint8_t *);
+static int	uftdi_set_latency(struct ucom_softc *, int);
+static int	uftdi_get_latency(struct ucom_softc *, int *);
+static int	uftdi_set_event_char(struct ucom_softc *, int);
+static int	uftdi_set_error_char(struct ucom_softc *, int);
+static int	uftdi_ioctl(struct ucom_softc *, uint32_t, caddr_t, int,
+		    struct thread *);
 static void	uftdi_start_read(struct ucom_softc *);
 static void	uftdi_stop_read(struct ucom_softc *);
 static void	uftdi_start_write(struct ucom_softc *);
 static void	uftdi_stop_write(struct ucom_softc *);
-static uint8_t	uftdi_8u232am_getrate(uint32_t, uint16_t *);
 static void	uftdi_poll(struct ucom_softc *ucom);
 
 static const struct usb_config uftdi_config[UFTDI_N_TRANSFER] = {
@@ -163,7 +225,7 @@ static const struct usb_config uftdi_con
 		.type = UE_BULK,
 		.endpoint = UE_ADDR_ANY,
 		.direction = UE_DIR_IN,
-		.bufsize = 0,		/* use wMaxPacketSize */
+		.bufsize = UFTDI_IBUFSIZE,
 		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
 		.callback = &uftdi_read_callback,
 	},
@@ -176,7 +238,9 @@ static const struct ucom_callback uftdi_
 	.ucom_cfg_set_break = &uftdi_cfg_set_break,
 	.ucom_cfg_param = &uftdi_cfg_param,
 	.ucom_cfg_open = &uftdi_cfg_open,
+	.ucom_cfg_close = &uftdi_cfg_close,
 	.ucom_pre_param = &uftdi_pre_param,
+	.ucom_ioctl = &uftdi_ioctl,
 	.ucom_start_read = &uftdi_start_read,
 	.ucom_stop_read = &uftdi_stop_read,
 	.ucom_start_write = &uftdi_start_write,
@@ -847,6 +911,82 @@ static const STRUCT_USB_HOST_ID uftdi_de
 #undef UFTDI_DEV
 };
 
+/*
+ * Set up softc fields whose value depends on the device type.
+ *
+ * Note that the 2232C and 2232D devices are the same for our purposes.  In the
+ * silicon the difference is that the D series has CPU FIFO mode and C doesn't.
+ * I haven't found any way of determining the C/D difference from info provided
+ * by the chip other than trying to set CPU FIFO mode and having it work or not.
+ * 
+ * Due to a hardware bug, a 232B chip without an eeprom reports itself as a 
+ * 232A, but if the serial number is also zero we know it's really a 232B. 
+ */
+static void
+uftdi_devtype_setup(struct uftdi_softc *sc, struct usb_attach_arg *uaa)
+{
+	struct usb_device_descriptor *dd;
+
+	sc->sc_bcdDevice = uaa->info.bcdDevice;
+
+	switch (uaa->info.bcdDevice) {
+	case 0x200:
+		dd = usbd_get_device_descriptor(sc->sc_udev);
+		if (dd->iSerialNumber == 0) {
+			sc->sc_devtype = DEVT_232B;
+		} else {
+			sc->sc_devtype = DEVT_232A;
+		}
+		sc->sc_ucom.sc_portno = 0;
+		break;
+	case 0x400:
+		sc->sc_devtype = DEVT_232B;
+		sc->sc_ucom.sc_portno = 0;
+		break;
+	case 0x500:
+		sc->sc_devtype = DEVT_2232D;
+		sc->sc_devflags |= DEVF_BAUDBITS_HINDEX;
+		sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+		break;
+	case 0x600:
+		sc->sc_devtype = DEVT_232R;
+		sc->sc_ucom.sc_portno = 0;
+		break;
+	case 0x700:
+		sc->sc_devtype = DEVT_2232H;
+		sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M;
+		sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+		break;
+	case 0x800:
+		sc->sc_devtype = DEVT_4232H;
+		sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M;
+		sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+		break;
+	case 0x900:
+		sc->sc_devtype = DEVT_232H;
+		sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M;
+		sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+		break;
+	case 0x1000:
+		sc->sc_devtype = DEVT_230X;
+		sc->sc_devflags |= DEVF_BAUDBITS_HINDEX;
+		sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+		break;
+	default:
+		if (uaa->info.bcdDevice < 0x200) {
+			sc->sc_devtype = DEVT_SIO;
+			sc->sc_hdrlen = 1;
+		} else {
+			sc->sc_devtype = DEVT_232R;
+			device_printf(sc->sc_dev, "Warning: unknown FTDI "
+			    "device type, bcdDevice=0x%04x, assuming 232R", 
+			    uaa->info.bcdDevice);
+		}
+		sc->sc_ucom.sc_portno = 0;
+		break;
+	}
+}
+
 static int
 uftdi_probe(device_t dev)
 {
@@ -886,6 +1026,8 @@ uftdi_attach(device_t dev)
 	struct uftdi_softc *sc = device_get_softc(dev);
 	int error;
 
+	DPRINTF("\n");
+
 	sc->sc_udev = uaa->device;
 	sc->sc_dev = dev;
 	sc->sc_unit = device_get_unit(dev);
@@ -894,34 +1036,11 @@ uftdi_attach(device_t dev)
 	mtx_init(&sc->sc_mtx, "uftdi", NULL, MTX_DEF);
 	ucom_ref(&sc->sc_super_ucom);
 
-	DPRINTF("\n");
 
-	sc->sc_iface_index = uaa->info.bIfaceIndex;
-	sc->sc_type = USB_GET_DRIVER_INFO(uaa) & UFTDI_TYPE_MASK;
-
-	switch (sc->sc_type) {
-	case UFTDI_TYPE_AUTO:
-		/* simplified type check */
-		if (uaa->info.bcdDevice >= 0x0200 ||
-		    usbd_get_iface(uaa->device, 1) != NULL) {
-			sc->sc_type = UFTDI_TYPE_8U232AM;
-			sc->sc_hdrlen = 0;
-		} else {
-			sc->sc_type = UFTDI_TYPE_SIO;
-			sc->sc_hdrlen = 1;
-		}
-		break;
-	case UFTDI_TYPE_SIO:
-		sc->sc_hdrlen = 1;
-		break;
-	case UFTDI_TYPE_8U232AM:
-	default:
-		sc->sc_hdrlen = 0;
-		break;
-	}
+	uftdi_devtype_setup(sc, uaa);
 
 	error = usbd_transfer_setup(uaa->device,
-	    &sc->sc_iface_index, sc->sc_xfer, uftdi_config,
+	    &uaa->info.bIfaceIndex, sc->sc_xfer, uftdi_config,
 	    UFTDI_N_TRANSFER, sc, &sc->sc_mtx);
 
 	if (error) {
@@ -929,8 +1048,6 @@ uftdi_attach(device_t dev)
 		    "transfers failed\n");
 		goto detach;
 	}
-	sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
-
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
 	usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_WR]);
@@ -993,37 +1110,25 @@ uftdi_free(struct ucom_softc *ucom)
 static void
 uftdi_cfg_open(struct ucom_softc *ucom)
 {
-	struct uftdi_softc *sc = ucom->sc_parent;
-	uint16_t wIndex = ucom->sc_portno;
-	struct usb_device_request req;
 
+	/*
+	 * This do-nothing open routine exists for the sole purpose of this
+	 * DPRINTF() so that you can see the point at which open gets called
+	 * when debugging is enabled.
+	 */
 	DPRINTF("");
+}
 
-	/* perform a full reset on the device */
-
-	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
-	req.bRequest = FTDI_SIO_RESET;
-	USETW(req.wValue, FTDI_SIO_RESET_SIO);
-	USETW(req.wIndex, wIndex);
-	USETW(req.wLength, 0);
-	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, 
-	    &req, NULL, 0, 1000);
-
-	/* turn on RTS/CTS flow control */
-
-	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
-	req.bRequest = FTDI_SIO_SET_FLOW_CTRL;
-	USETW(req.wValue, 0);
-	USETW2(req.wIndex, FTDI_SIO_RTS_CTS_HS, wIndex);
-	USETW(req.wLength, 0);
-	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, 
-	    &req, NULL, 0, 1000);
+static void
+uftdi_cfg_close(struct ucom_softc *ucom)
+{
 
 	/*
-	 * NOTE: with the new UCOM layer there will always be a
-	 * "uftdi_cfg_param()" call after "open()", so there is no need for
-	 * "open()" to configure anything
+	 * This do-nothing close routine exists for the sole purpose of this
+	 * DPRINTF() so that you can see the point at which close gets called
+	 * when debugging is enabled.
 	 */
+	DPRINTF("");
 }
 
 static void
@@ -1031,35 +1136,47 @@ uftdi_write_callback(struct usb_xfer *xf
 {
 	struct uftdi_softc *sc = usbd_xfer_softc(xfer);
 	struct usb_page_cache *pc;
-	uint32_t actlen;
+	uint32_t pktlen;
+	uint32_t buflen;
 	uint8_t buf[1];
 
 	switch (USB_GET_STATE(xfer)) {
+	default:			/* Error */
+		if (error != USB_ERR_CANCELLED) {
+			/* try to clear stall first */
+			usbd_xfer_set_stall(xfer);
+		}
+		/* FALLTHROUGH */
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
-tr_setup:
+		/*
+		 * If output packets don't require headers (the common case) we
+		 * can just load the buffer up with payload bytes all at once.
+		 * Otherwise, loop to format packets into the buffer while there
+		 * is data available, and room for a packet header and at least
+		 * one byte of payload.
+		 */
 		pc = usbd_xfer_get_frame(xfer, 0);
-		if (ucom_get_data(&sc->sc_ucom, pc,
-		    sc->sc_hdrlen, UFTDI_OBUFSIZE - sc->sc_hdrlen,
-		    &actlen)) {
-
-			if (sc->sc_hdrlen > 0) {
-				buf[0] =
-				    FTDI_OUT_TAG(actlen, sc->sc_ucom.sc_portno);
-				usbd_copy_in(pc, 0, buf, 1);
+		if (sc->sc_hdrlen == 0) {
+			ucom_get_data(&sc->sc_ucom, pc, 0, UFTDI_OBUFSIZE, 
+			    &buflen);
+		} else {
+			buflen = 0;
+			while (buflen < UFTDI_OBUFSIZE - sc->sc_hdrlen - 1 &&
+			    ucom_get_data(&sc->sc_ucom, pc, buflen + 
+			    sc->sc_hdrlen, UFTDI_OPKTSIZE - sc->sc_hdrlen, 
+			    &pktlen) != 0) {
+				buf[0] = FTDI_OUT_TAG(pktlen, 
+				    sc->sc_ucom.sc_portno);
+				usbd_copy_in(pc, buflen, buf, 1);
+				buflen += pktlen + sc->sc_hdrlen;
 			}
-			usbd_xfer_set_frame_len(xfer, 0, actlen + sc->sc_hdrlen);
-			usbd_transfer_submit(xfer);
 		}
-		return;
-
-	default:			/* Error */
-		if (error != USB_ERR_CANCELLED) {
-			/* try to clear stall first */
-			usbd_xfer_set_stall(xfer);
-			goto tr_setup;
+		if (buflen != 0) {
+			usbd_xfer_set_frame_len(xfer, 0, buflen);
+			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 	}
 }
 
@@ -1072,23 +1189,47 @@ uftdi_read_callback(struct usb_xfer *xfe
 	uint8_t ftdi_msr;
 	uint8_t msr;
 	uint8_t lsr;
-	int actlen;
+	int buflen;
+	int pktlen;
+	int pktmax;
+	int offset;
 
-	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+	usbd_xfer_status(xfer, &buflen, NULL, NULL, NULL);
 
 	switch (USB_GET_STATE(xfer)) {
 	case USB_ST_TRANSFERRED:
-
-		if (actlen < 2) {
+		if (buflen < UFTDI_IHDRSIZE)
 			goto tr_setup;
-		}
 		pc = usbd_xfer_get_frame(xfer, 0);
-		usbd_copy_out(pc, 0, buf, 2);
-
+		pktmax = xfer->max_packet_size - UFTDI_IHDRSIZE;
+		lsr = 0;
+		msr = 0;
+		offset = 0;
+		/*
+		 * Extract packet headers and payload bytes from the buffer.
+		 * Feed payload bytes to ucom/tty layer; OR-accumulate header
+		 * status bits which are transient and could toggle with each
+		 * packet. After processing all packets in the buffer, process
+		 * the accumulated transient MSR and LSR values along with the
+		 * non-transient bits from the last packet header.
+		 */
+		while (buflen >= UFTDI_IHDRSIZE) {
+			usbd_copy_out(pc, offset, buf, UFTDI_IHDRSIZE);
+			offset += UFTDI_IHDRSIZE;
+			buflen -= UFTDI_IHDRSIZE;
+			lsr |= FTDI_GET_LSR(buf);
+			if (FTDI_GET_MSR(buf) & FTDI_SIO_RI_MASK)
+				msr |= SER_RI;
+			pktlen = min(buflen, pktmax);
+			if (pktlen != 0) {
+				ucom_put_data(&sc->sc_ucom, pc, offset, 
+				    pktlen);
+				offset += pktlen;
+				buflen -= pktlen;
+			}
+		}
 		ftdi_msr = FTDI_GET_MSR(buf);
-		lsr = FTDI_GET_LSR(buf);
 
-		msr = 0;
 		if (ftdi_msr & FTDI_SIO_CTS_MASK)
 			msr |= SER_CTS;
 		if (ftdi_msr & FTDI_SIO_DSR_MASK)
@@ -1109,11 +1250,7 @@ uftdi_read_callback(struct usb_xfer *xfe
 
 			ucom_status_change(&sc->sc_ucom);
 		}
-		actlen -= 2;
-
-		if (actlen > 0) {
-			ucom_put_data(&sc->sc_ucom, pc, 2, actlen);
-		}
+		/* FALLTHROUGH */
 	case USB_ST_SETUP:
 tr_setup:
 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
@@ -1193,58 +1330,162 @@ uftdi_cfg_set_break(struct ucom_softc *u
 	    &req, NULL, 0, 1000);
 }
 
-static int
-uftdi_set_parm_soft(struct termios *t,
-    struct uftdi_param_config *cfg, uint8_t type)
+/*
+ * Return true if the given speed is within operational tolerance of the target
+ * speed.  FTDI recommends that the hardware speed be within 3% of nominal.
+ */
+static inline boolean_t
+uftdi_baud_within_tolerance(uint64_t speed, uint64_t target)
 {
+	return ((speed >= (target * 100) / 103) &&
+	    (speed <= (target * 100) / 97));
+}
 
-	memset(cfg, 0, sizeof(*cfg));
+static int
+uftdi_sio_encode_baudrate(struct uftdi_softc *sc, speed_t speed,
+	struct uftdi_param_config *cfg)
+{
+	u_int i;
+	const speed_t sio_speeds[] = {
+		300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
+	};
 
-	switch (type) {
-	case UFTDI_TYPE_SIO:
-		switch (t->c_ospeed) {
-		case 300:
-			cfg->rate = ftdi_sio_b300;
-			break;
-		case 600:
-			cfg->rate = ftdi_sio_b600;
-			break;
-		case 1200:
-			cfg->rate = ftdi_sio_b1200;
-			break;
-		case 2400:
-			cfg->rate = ftdi_sio_b2400;
-			break;
-		case 4800:
-			cfg->rate = ftdi_sio_b4800;
-			break;
-		case 9600:
-			cfg->rate = ftdi_sio_b9600;
-			break;
-		case 19200:
-			cfg->rate = ftdi_sio_b19200;
-			break;
-		case 38400:
-			cfg->rate = ftdi_sio_b38400;
-			break;
-		case 57600:
-			cfg->rate = ftdi_sio_b57600;
-			break;
-		case 115200:
-			cfg->rate = ftdi_sio_b115200;
-			break;
-		default:
-			return (EINVAL);
+	/*
+	 * The original SIO chips were limited to a small choice of speeds
+	 * listed in an internal table of speeds chosen by an index value.
+	 */
+	for (i = 0; i < nitems(sio_speeds); ++i) {
+		if (speed == sio_speeds[i]) {
+			cfg->baud_lobits = i;
+			cfg->baud_hibits = 0;
+			return (0);
 		}
-		break;
+	}
+	return (ERANGE);
+}
 
-	case UFTDI_TYPE_8U232AM:
-		if (uftdi_8u232am_getrate(t->c_ospeed, &cfg->rate)) {
-			return (EINVAL);
-		}
-		break;
+static int
+uftdi_encode_baudrate(struct uftdi_softc *sc, speed_t speed,
+	struct uftdi_param_config *cfg)
+{
+	static const uint8_t encoded_fraction[8] = {0, 3, 2, 4, 1, 5, 6, 7};
+	static const uint8_t roundoff_232a[16] = {
+		0,  1,  0,  1,  0, -1,  2,  1,
+		0, -1, -2, -3,  4,  3,  2,  1,
+	};
+	uint32_t clk, divisor, fastclk_flag, frac, hwspeed;
+
+	/*
+	 * If this chip has the fast clock capability and the speed is within
+	 * range, use the 12MHz clock, otherwise the standard clock is 3MHz.
+	 */
+	if ((sc->sc_devflags & DEVF_BAUDCLK_12M) && speed >= 1200) {
+		clk = 12000000;
+		fastclk_flag = (1 << 17);
+	} else {
+		clk = 3000000;
+		fastclk_flag = 0;
 	}
 
+	/*
+	 * Make sure the requested speed is reachable with the available clock
+	 * and a 14-bit divisor.
+	 */
+	if (speed < (clk >> 14) || speed > clk)
+		return (ERANGE);
+
+	/*
+	 * Calculate the divisor, initially yielding a fixed point number with a
+	 * 4-bit (1/16ths) fraction, then round it to the nearest fraction the
+	 * hardware can handle.  When the integral part of the divisor is
+	 * greater than one, the fractional part is in 1/8ths of the base clock.
+	 * The FT8U232AM chips can handle only 0.125, 0.250, and 0.5 fractions.
+	 * Later chips can handle all 1/8th fractions.
+	 *
+	 * If the integral part of the divisor is 1, a special rule applies: the
+	 * fractional part can only be .0 or .5 (this is a limitation of the
+	 * hardware).  We handle this by truncating the fraction rather than
+	 * rounding, because this only applies to the two fastest speeds the
+	 * chip can achieve and rounding doesn't matter, either you've asked for
+	 * that exact speed or you've asked for something the chip can't do.
+	 *
+	 * For the FT8U232AM chips, use a roundoff table to adjust the result
+	 * to the nearest 1/8th fraction that is supported by the hardware,
+	 * leaving a fixed-point number with a 3-bit fraction which exactly
+	 * represents the math the hardware divider will do.  For later-series
+	 * chips that support all 8 fractional divisors, just round 16ths to
+	 * 8ths by adding 1 and dividing by 2.
+	 */
+	divisor = (clk << 4) / speed;
+	if ((divisor & 0xfffffff0) == 1)
+		divisor &= 0xfffffff8;
+	else if (sc->sc_devtype == DEVT_232A)
+		divisor += roundoff_232a[divisor & 0x0f];
+	else
+		divisor += 1;  /* Rounds odd 16ths up to next 8th. */
+	divisor >>= 1;
+
+	/*
+	 * Ensure the resulting hardware speed will be within operational
+	 * tolerance (within 3% of nominal).
+	 */
+	hwspeed = (clk << 3) / divisor;
+	if (!uftdi_baud_within_tolerance(hwspeed, speed))
+		return (ERANGE);
+
+	/*
+	 * Re-pack the divisor into hardware format. The lower 14-bits hold the
+	 * integral part, while the upper bits specify the fraction by indexing
+	 * a table of fractions within the hardware which is laid out as:
+	 *    {0.0, 0.5, 0.25, 0.125, 0.325, 0.625, 0.725, 0.875}
+	 * The A-series chips only have the first four table entries; the
+	 * roundoff table logic above ensures that the fractional part for those
+	 * chips will be one of the first four values.
+	 *
+	 * When the divisor is 1 a special encoding applies:  1.0 is encoded as
+	 * 0.0, and 1.5 is encoded as 1.0.  The rounding logic above has already
+	 * ensured that the fraction is either .0 or .5 if the integral is 1.
+	 */
+	frac = divisor & 0x07;
+	divisor >>= 3;
+	if (divisor == 1) {
+		if (frac == 0)
+			divisor = 0;  /* 1.0 becomes 0.0 */
+		else
+			frac = 0;     /* 1.5 becomes 1.0 */
+	}
+	divisor |= (encoded_fraction[frac] << 14) | fastclk_flag;
+        
+	cfg->baud_lobits = (uint16_t)divisor;
+	cfg->baud_hibits = (uint16_t)(divisor >> 16);
+
+	/*
+	 * If this chip requires the baud bits to be in the high byte of the
+	 * index word, move the bits up to that location.
+	 */
+	if (sc->sc_devflags & DEVF_BAUDBITS_HINDEX) {
+		cfg->baud_hibits <<= 8;
+	}
+
+	return (0);
+}
+
+static int
+uftdi_set_parm_soft(struct ucom_softc *ucom, struct termios *t,
+    struct uftdi_param_config *cfg)
+{
+	struct uftdi_softc *sc = ucom->sc_parent;
+	int err;
+
+	memset(cfg, 0, sizeof(*cfg));
+
+	if (sc->sc_devtype == DEVT_SIO)
+		err = uftdi_sio_encode_baudrate(sc, t->c_ospeed, cfg);
+	else
+		err = uftdi_encode_baudrate(sc, t->c_ospeed, cfg);
+	if (err != 0)
+		return (err);
+
 	if (t->c_cflag & CSTOPB)
 		cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_2;
 	else
@@ -1294,12 +1535,11 @@ uftdi_set_parm_soft(struct termios *t,
 static int
 uftdi_pre_param(struct ucom_softc *ucom, struct termios *t)
 {
-	struct uftdi_softc *sc = ucom->sc_parent;
 	struct uftdi_param_config cfg;
 
 	DPRINTF("\n");
 
-	return (uftdi_set_parm_soft(t, &cfg, sc->sc_type));
+	return (uftdi_set_parm_soft(ucom, t, &cfg));
 }
 
 static void
@@ -1310,7 +1550,7 @@ uftdi_cfg_param(struct ucom_softc *ucom,
 	struct uftdi_param_config cfg;
 	struct usb_device_request req;
 
-	if (uftdi_set_parm_soft(t, &cfg, sc->sc_type)) {
+	if (uftdi_set_parm_soft(ucom, t, &cfg)) {
 		/* should not happen */
 		return;
 	}
@@ -1320,8 +1560,8 @@ uftdi_cfg_param(struct ucom_softc *ucom,
 
 	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
 	req.bRequest = FTDI_SIO_SET_BAUD_RATE;
-	USETW(req.wValue, cfg.rate);
-	USETW(req.wIndex, wIndex);
+	USETW(req.wValue, cfg.baud_lobits);
+	USETW(req.wIndex, cfg.baud_hibits | wIndex);
 	USETW(req.wLength, 0);
 	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, 
 	    &req, NULL, 0, 1000);
@@ -1355,6 +1595,186 @@ uftdi_cfg_get_status(struct ucom_softc *
 	*lsr = sc->sc_lsr;
 }
 
+static int
+uftdi_reset(struct ucom_softc *ucom, int reset_type)
+{
+	struct uftdi_softc *sc = ucom->sc_parent;
+	usb_device_request_t req;
+
+	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+	req.bRequest = FTDI_SIO_RESET;
+
+	USETW(req.wIndex, sc->sc_ucom.sc_portno);
+	USETW(req.wLength, 0);
+	USETW(req.wValue, reset_type);
+
+	return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+uftdi_set_bitmode(struct ucom_softc *ucom, uint8_t bitmode, uint8_t iomask)
+{
+	struct uftdi_softc *sc = ucom->sc_parent;
+	usb_device_request_t req;
+
+	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+	req.bRequest = FTDI_SIO_SET_BITMODE;
+
+	USETW(req.wIndex, sc->sc_ucom.sc_portno);
+	USETW(req.wLength, 0);
+
+	if (bitmode == UFTDI_BITMODE_NONE)
+	    USETW2(req.wValue, 0, 0);
+	else
+	    USETW2(req.wValue, (1 << bitmode), iomask);
+
+	return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+uftdi_get_bitmode(struct ucom_softc *ucom, uint8_t *iomask)
+{
+	struct uftdi_softc *sc = ucom->sc_parent;
+	usb_device_request_t req;
+
+	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+	req.bRequest = FTDI_SIO_GET_BITMODE;
+
+	USETW(req.wIndex, sc->sc_ucom.sc_portno);
+	USETW(req.wLength, 1);
+	USETW(req.wValue,  0);
+
+	return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, iomask));
+}
+
+static int
+uftdi_set_latency(struct ucom_softc *ucom, int latency)
+{
+	struct uftdi_softc *sc = ucom->sc_parent;
+	usb_device_request_t req;
+
+	if (latency < 0 || latency > 255)
+		return (USB_ERR_INVAL);
+
+	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+	req.bRequest = FTDI_SIO_SET_LATENCY;
+
+	USETW(req.wIndex, sc->sc_ucom.sc_portno);
+	USETW(req.wLength, 0);
+	USETW2(req.wValue, 0, latency);
+
+	return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+uftdi_get_latency(struct ucom_softc *ucom, int *latency)
+{
+	struct uftdi_softc *sc = ucom->sc_parent;
+	usb_device_request_t req;
+	usb_error_t err;
+	uint8_t buf;
+
+	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+	req.bRequest = FTDI_SIO_GET_LATENCY;
+
+	USETW(req.wIndex, sc->sc_ucom.sc_portno);
+	USETW(req.wLength, 1);
+	USETW(req.wValue, 0);
+
+	err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &buf);
+	*latency = buf;
+
+	return (err);
+}
+
+static int
+uftdi_set_event_char(struct ucom_softc *ucom, int echar)
+{
+	struct uftdi_softc *sc = ucom->sc_parent;
+	usb_device_request_t req;
+	uint8_t enable;
+
+	enable = (echar == -1) ? 0 : 1;
+
+	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+	req.bRequest = FTDI_SIO_SET_EVENT_CHAR;
+
+	USETW(req.wIndex, sc->sc_ucom.sc_portno);
+	USETW(req.wLength, 0);
+	USETW2(req.wValue, enable, echar & 0xff);
+
+	return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+uftdi_set_error_char(struct ucom_softc *ucom, int echar)
+{
+	struct uftdi_softc *sc = ucom->sc_parent;
+	usb_device_request_t req;
+	uint8_t enable;
+
+	enable = (echar == -1) ? 0 : 1;
+
+	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+	req.bRequest = FTDI_SIO_SET_ERROR_CHAR;
+
+	USETW(req.wIndex, sc->sc_ucom.sc_portno);
+	USETW(req.wLength, 0);
+	USETW2(req.wValue, enable, echar & 0xff);
+
+	return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+uftdi_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data,
+    int flag, struct thread *td)
+{
+	struct uftdi_softc *sc = ucom->sc_parent;
+	int err;
+	struct uftdi_bitmode * mode;
+
+	DPRINTF("portno: %d cmd: %#x\n", ucom->sc_portno, cmd);
+
+	switch (cmd) {
+	case UFTDIIOC_RESET_IO:
+	case UFTDIIOC_RESET_RX:
+	case UFTDIIOC_RESET_TX:
+		err = uftdi_reset(ucom, 
+		    cmd == UFTDIIOC_RESET_IO ? FTDI_SIO_RESET_SIO :
+		    (cmd == UFTDIIOC_RESET_RX ? FTDI_SIO_RESET_PURGE_RX :
+		    FTDI_SIO_RESET_PURGE_TX));
+		break;
+	case UFTDIIOC_SET_BITMODE:
+		mode = (struct uftdi_bitmode *)data;
+		err = uftdi_set_bitmode(ucom, mode->mode, mode->iomask);
+		break;
+	case UFTDIIOC_GET_BITMODE:
+		mode = (struct uftdi_bitmode *)data;
+		err = uftdi_get_bitmode(ucom, &mode->iomask);
+		break;
+	case UFTDIIOC_SET_LATENCY:
+		err = uftdi_set_latency(ucom, *((int *)data));
+		break;
+	case UFTDIIOC_GET_LATENCY:
+		err = uftdi_get_latency(ucom, (int *)data);
+		break;
+	case UFTDIIOC_SET_ERROR_CHAR:
+		err = uftdi_set_event_char(ucom, *(int *)data);
+		break;
+	case UFTDIIOC_SET_EVENT_CHAR:
+		err = uftdi_set_error_char(ucom, *(int *)data);
+	case UFTDIIOC_GET_HWREV:

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201404281246.s3SCkORV030885>