svn commit: r265050 - in stable/9: share/man/man4 sys/dev/usb sys/dev/usb/serial
Ian Lepore
ian at FreeBSD.org
Mon Apr 28 13:28:11 UTC 2014
Author: ian
Date: Mon Apr 28 13:28:10 2014
New Revision: 265050
URL: http://svnweb.freebsd.org/changeset/base/265050
Log:
MFC uftdi(4) driver changes...
r264010: Support speeds up to 12mbaud on newer chips.
r264018: Update list of supported FTDI chips.
r264031: Use 2K IO buffers for improved throughput.
r264149: Add ioctl(2) calls to access bitbang, MPSSE, CPU_FIFO,
and other modes.
r264800: Various fixes to r264149 pointed out by Coverity scan.
Added:
stable/9/sys/dev/usb/uftdiio.h
- copied unchanged from r264149, head/sys/dev/usb/uftdiio.h
Modified:
stable/9/share/man/man4/uftdi.4
stable/9/sys/dev/usb/serial/uftdi.c
stable/9/sys/dev/usb/serial/uftdi_reg.h
Directory Properties:
stable/9/share/man/man4/ (props changed)
stable/9/sys/ (props changed)
stable/9/sys/dev/ (props changed)
Modified: stable/9/share/man/man4/uftdi.4
==============================================================================
--- stable/9/share/man/man4/uftdi.4 Mon Apr 28 13:18:30 2014 (r265049)
+++ stable/9/share/man/man4/uftdi.4 Mon Apr 28 13:28:10 2014 (r265050)
@@ -34,7 +34,8 @@
.Os
.Sh NAME
.Nm uftdi
-.Nd USB support for serial adapters based on the FT8U100AX and FT8U232AM chips
+.Nd USB support for serial adapters based on the FTDI family of USB
+serial adapter chips.
.Sh SYNOPSIS
To compile this driver into the kernel,
place the following lines in your
@@ -52,13 +53,115 @@ uftdi_load="YES"
.Sh DESCRIPTION
The
.Nm
-driver provides support for various serial adapters based on the FTDI
-FT2232C, FT8U100AX and FT8U232AM chips.
+driver provides support for various serial adapters based on the
+following FTDI chips:
+.Pp
+.Bl -bullet -compact
+.It
+FT8U100AX
+.It
+FT8U232AM
+.It
+FT8U232BM
+.It
+FT232R
+.It
+FT2232C
+.It
+FT2232D
+.It
+FT2232H
+.It
+FT4232H
+.It
+FT230X
+.El
.Pp
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/9/sys/dev/usb/serial/uftdi.c
==============================================================================
--- stable/9/sys/dev/usb/serial/uftdi.c Mon Apr 28 13:18:30 2014 (r265049)
+++ stable/9/sys/dev/usb/serial/uftdi.c Mon Apr 28 13:28:10 2014 (r265050)
@@ -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,53 @@ 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.
+ *
+ * NOTE: The FTDI chip doesn't accept zero length
+ * packets. This cannot happen because the "pktlen"
+ * will always be non-zero when "ucom_get_data()"
+ * returns non-zero which we check below.
+ */
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) {
+ if (ucom_get_data(&sc->sc_ucom, pc, 0, UFTDI_OBUFSIZE,
+ &buflen) == 0)
+ break;
+ } 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 +1195,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 +1256,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 +1336,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 & 0xf) == 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 +1541,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 +1556,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 +1566,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 +1601,187 @@ 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;
+
*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
More information about the svn-src-stable-9
mailing list