Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 1 Apr 2014 15:56:31 +0000 (UTC)
From:      Ian Lepore <ian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r264010 - head/sys/dev/usb/serial
Message-ID:  <201404011556.s31FuVYk052661@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ian
Date: Tue Apr  1 15:56:31 2014
New Revision: 264010
URL: http://svnweb.freebsd.org/changeset/base/264010

Log:
  Support serial speeds up to 12mbaud with newer FTDI chips.
  
  Recent FDTI chips have the ability to operate at up to 12mbps.  The newer
  chips with faster clocks have the same usb vendor/product IDs as the older
  chips; the bcdDevice field must be used to detect the newer versions.  This
  change includes a new function to do that instead of using just the IDs from
  the vendor/product table.
  
  The code to choose the baud clock divisor is completely rewritten.  In
  addition to supporting the new higher clock rates, the rewrite fixes a
  longstanding bug in the old code which put the high bits of the fractional
  part of the divisor into the wrong place in the wIndex field.  That bug
  was mostly harmless -- it accidentally didn't affect standard baud rates
  and would only show up when using relatively fast non-standard rates.

Modified:
  head/sys/dev/usb/serial/uftdi.c
  head/sys/dev/usb/serial/uftdi_reg.h

Modified: head/sys/dev/usb/serial/uftdi.c
==============================================================================
--- head/sys/dev/usb/serial/uftdi.c	Tue Apr  1 15:54:03 2014	(r264009)
+++ head/sys/dev/usb/serial/uftdi.c	Tue Apr  1 15:56:31 2014	(r264010)
@@ -92,6 +92,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;
@@ -105,15 +120,16 @@ struct uftdi_softc {
 
 	uint16_t sc_last_lcr;
 
-	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;
@@ -135,8 +151,8 @@ static void	uftdi_cfg_open(struct ucom_s
 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 *,
@@ -145,7 +161,6 @@ static void	uftdi_start_read(struct ucom
 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] = {
@@ -846,6 +861,80 @@ 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;
+
+	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)
 {
@@ -885,6 +974,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);
@@ -893,34 +984,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) {
@@ -928,8 +996,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]);
@@ -1192,57 +1258,161 @@ 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;
@@ -1293,12 +1463,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
@@ -1309,7 +1478,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;
 	}
@@ -1319,8 +1488,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);
@@ -1386,75 +1555,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)
 {

Modified: head/sys/dev/usb/serial/uftdi_reg.h
==============================================================================
--- head/sys/dev/usb/serial/uftdi_reg.h	Tue Apr  1 15:54:03 2014	(r264009)
+++ head/sys/dev/usb/serial/uftdi_reg.h	Tue Apr  1 15:56:31 2014	(r264010)
@@ -75,30 +75,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



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