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.
This commit is contained in:
ian 2014-04-28 12:46:23 +00:00
parent e852cd6938
commit cb4d7c77b6
4 changed files with 735 additions and 242 deletions

View File

@ -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

View File

@ -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, debug, CTLFLAG_RW,
#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_callback;
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_config[UFTDI_N_TRANSFER] = {
.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_callback = {
.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_devs[] = {
#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;
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);
/*
* 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 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("");
}
static void
uftdi_cfg_close(struct ucom_softc *ucom)
{
/*
* 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 *xfer, usb_error_t error)
{
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)) {
case USB_ST_SETUP:
case USB_ST_TRANSFERRED:
tr_setup:
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);
}
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;
}
return;
/* FALLTHROUGH */
case USB_ST_SETUP:
case USB_ST_TRANSFERRED:
/*
* 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 (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;
}
}
if (buflen != 0) {
usbd_xfer_set_frame_len(xfer, 0, buflen);
usbd_transfer_submit(xfer);
}
break;
}
}
@ -1072,23 +1189,47 @@ uftdi_read_callback(struct usb_xfer *xfer, usb_error_t error)
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);
ftdi_msr = FTDI_GET_MSR(buf);
lsr = FTDI_GET_LSR(buf);
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);
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 *xfer, usb_error_t error)
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,57 +1330,161 @@ uftdi_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
&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));
}
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
};
/*
* 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);
}
}
return (ERANGE);
}
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));
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);
}
break;
case UFTDI_TYPE_8U232AM:
if (uftdi_8u232am_getrate(t->c_ospeed, &cfg->rate)) {
return (EINVAL);
}
break;
}
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;
@ -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 termios *t)
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, struct termios *t)
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 *ucom, uint8_t *lsr, uint8_t *msr)
*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:
*(int *)data = sc->sc_bcdDevice;
err = 0;
break;
default:
return (ENOIOCTL);
}
if (err != USB_ERR_NORMAL_COMPLETION)
return (EIO);
return (0);
}
static void
uftdi_start_read(struct ucom_softc *ucom)
{
@ -1387,75 +1807,6 @@ uftdi_stop_write(struct ucom_softc *ucom)
usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_WR]);
}
/*------------------------------------------------------------------------*
* uftdi_8u232am_getrate
*
* Return values:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
static uint8_t
uftdi_8u232am_getrate(uint32_t speed, uint16_t *rate)
{
/* Table of the nearest even powers-of-2 for values 0..15. */
static const uint8_t roundoff[16] = {
0, 2, 2, 4, 4, 4, 8, 8,
8, 8, 8, 8, 16, 16, 16, 16,
};
uint32_t d;
uint32_t freq;
uint16_t result;
if ((speed < 178) || (speed > ((3000000 * 100) / 97)))
return (1); /* prevent numerical overflow */
/* Special cases for 2M and 3M. */
if ((speed >= ((3000000 * 100) / 103)) &&
(speed <= ((3000000 * 100) / 97))) {
result = 0;
goto done;
}
if ((speed >= ((2000000 * 100) / 103)) &&
(speed <= ((2000000 * 100) / 97))) {
result = 1;
goto done;
}
d = (FTDI_8U232AM_FREQ << 4) / speed;
d = (d & ~15) + roundoff[d & 15];
if (d < FTDI_8U232AM_MIN_DIV)
d = FTDI_8U232AM_MIN_DIV;
else if (d > FTDI_8U232AM_MAX_DIV)
d = FTDI_8U232AM_MAX_DIV;
/*
* Calculate the frequency needed for "d" to exactly divide down to
* our target "speed", and check that the actual frequency is within
* 3% of this.
*/
freq = (speed * d);
if ((freq < ((FTDI_8U232AM_FREQ * 1600ULL) / 103)) ||
(freq > ((FTDI_8U232AM_FREQ * 1600ULL) / 97)))
return (1);
/*
* Pack the divisor into the resultant value. The lower 14-bits
* hold the integral part, while the upper 2 bits encode the
* fractional component: either 0, 0.5, 0.25, or 0.125.
*/
result = (d >> 4);
if (d & 8)
result |= 0x4000;
else if (d & 4)
result |= 0x8000;
else if (d & 2)
result |= 0xc000;
done:
*rate = result;
return (0);
}
static void
uftdi_poll(struct ucom_softc *ucom)
{

View File

@ -28,6 +28,10 @@
* reg */
#define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */
#define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */
#define FTDI_SIO_SET_LATENCY 9 /* Set the latency timer */
#define FTDI_SIO_GET_LATENCY 10 /* Read the latency timer */
#define FTDI_SIO_SET_BITMODE 11 /* Set the bit bang I/O mode */
#define FTDI_SIO_GET_BITMODE 12 /* Read pin states in bit bang mode */
/* Port Identifier Table */
#define FTDI_PIT_DEFAULT 0 /* SIOA */
@ -75,30 +79,12 @@
/*
* BmRequestType: 0100 0000B
* bRequest: FTDI_SIO_SET_BAUDRATE
* wValue: BaudRate value - see below
* wIndex: Port
* wValue: BaudRate low bits
* wIndex: Port and BaudRate high bits
* wLength: 0
* Data: None
*/
/* FTDI_SIO_SET_BAUDRATE */
enum {
ftdi_sio_b300 = 0,
ftdi_sio_b600 = 1,
ftdi_sio_b1200 = 2,
ftdi_sio_b2400 = 3,
ftdi_sio_b4800 = 4,
ftdi_sio_b9600 = 5,
ftdi_sio_b19200 = 6,
ftdi_sio_b38400 = 7,
ftdi_sio_b57600 = 8,
ftdi_sio_b115200 = 9
};
#define FTDI_8U232AM_FREQ 3000000
/* Bounds for normal divisors as 4-bit fixed precision ints. */
#define FTDI_8U232AM_MIN_DIV 0x20
#define FTDI_8U232AM_MAX_DIV 0x3fff8
/*
* BmRequestType: 0100 0000B

75
sys/dev/usb/uftdiio.h Normal file
View File

@ -0,0 +1,75 @@
/*-
* Copyright 2008-2012 - Symmetricom, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*
* FTDI USB serial converter chip ioctl commands.
*/
#ifndef _USB_UFTDIIO_H_
#define _USB_UFTDIIO_H_
#include <sys/ioccom.h>
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,
};
/*
* For UFTDIIOC_SET_BITMODE:
* mode = One of the uftdi_bitmodes enum values.
* iomask = Mask of bits enabled for bitbang output.
*
* For UFTDIIOC_GET_BITMODE:
* mode = Unused.
* iomask = Returned snapshot of bitbang pin states at time of call.
*/
struct uftdi_bitmode
{
uint8_t mode;
uint8_t iomask;
};
#define UFTDIIOC_RESET_IO _IO('c', 0) /* Reset config, flush fifos.*/
#define UFTDIIOC_RESET_RX _IO('c', 1) /* Flush input fifo. */
#define UFTDIIOC_RESET_TX _IO('c', 2) /* Flush output fifo. */
#define UFTDIIOC_SET_BITMODE _IOW('c', 3, struct uftdi_bitmode)
#define UFTDIIOC_GET_BITMODE _IOR('c', 4, struct uftdi_bitmode)
#define UFTDIIOC_SET_ERROR_CHAR _IOW('c', 5, int) /* -1 to disable */
#define UFTDIIOC_SET_EVENT_CHAR _IOW('c', 6, int) /* -1 to disable */
#define UFTDIIOC_SET_LATENCY _IOW('c', 7, int) /* 1-255 ms */
#define UFTDIIOC_GET_LATENCY _IOR('c', 8, int)
#define UFTDIIOC_GET_HWREV _IOR('c', 9, int)
#endif