Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 22 Oct 2018 11:58:31 +0000 (UTC)
From:      Hans Petter Selasky <hselasky@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r339587 - head/sys/dev/usb/serial
Message-ID:  <201810221158.w9MBwVs7031461@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: hselasky
Date: Mon Oct 22 11:58:30 2018
New Revision: 339587
URL: https://svnweb.freebsd.org/changeset/base/339587

Log:
  Added support for formula-based arbitrary baud rates, in contrast to
  the current fixed values, which enables use of rates above 1 Mbps.
  Improved the detection of HXD chips, and the status flag handling as
  well.
  
  Submitted by:		Gabor Simon <gabor.simon75@gmail.com>
  PR:			225932
  Differential revision:	https://reviews.freebsd.org/D16639
  MFC after:		1 week
  Sponsored by:		Mellanox Technologies

Modified:
  head/sys/dev/usb/serial/uplcom.c

Modified: head/sys/dev/usb/serial/uplcom.c
==============================================================================
--- head/sys/dev/usb/serial/uplcom.c	Mon Oct 22 11:43:43 2018	(r339586)
+++ head/sys/dev/usb/serial/uplcom.c	Mon Oct 22 11:58:30 2018	(r339587)
@@ -134,12 +134,20 @@ SYSCTL_INT(_hw_usb_uplcom, OID_AUTO, debug, CTLFLAG_RW
 #define	UPLCOM_SET_CRTSCTS		0x41
 #define	UPLCOM_SET_CRTSCTS_PL2303X	0x61
 #define	RSAQ_STATUS_CTS			0x80
+#define	RSAQ_STATUS_OVERRUN_ERROR	0x40
+#define	RSAQ_STATUS_PARITY_ERROR	0x20 
+#define	RSAQ_STATUS_FRAME_ERROR	0x10
+#define	RSAQ_STATUS_RING		0x08
+#define	RSAQ_STATUS_BREAK_ERROR	0x04
 #define	RSAQ_STATUS_DSR			0x02
 #define	RSAQ_STATUS_DCD			0x01
 
 #define	TYPE_PL2303			0
 #define	TYPE_PL2303HX			1
+#define	TYPE_PL2303HXD			2
 
+#define	UPLCOM_STATE_INDEX		8
+
 enum {
 	UPLCOM_BULK_DT_WR,
 	UPLCOM_BULK_DT_RD,
@@ -369,18 +377,49 @@ uplcom_attach(device_t dev)
 
 	sc->sc_udev = uaa->device;
 
-	/* Determine the chip type.  This algorithm is taken from Linux. */
 	dd = usbd_get_device_descriptor(sc->sc_udev);
-	if (dd->bDeviceClass == 0x02)
-		sc->sc_chiptype = TYPE_PL2303;
-	else if (dd->bMaxPacketSize == 0x40)
+
+	switch (UGETW(dd->bcdDevice)) {
+	case 0x0300:
 		sc->sc_chiptype = TYPE_PL2303HX;
-	else
-		sc->sc_chiptype = TYPE_PL2303;
+		/* or TA, that is HX with external crystal */
+		break;
+	case 0x0400:
+		sc->sc_chiptype = TYPE_PL2303HXD;
+		/* or EA, that is HXD with ESD protection */
+		/* or RA, that has internal voltage level converter that works only up to 1Mbaud (!) */
+		break;
+	case 0x0500:
+		sc->sc_chiptype = TYPE_PL2303HXD;
+		/* in fact it's TB, that is HXD with external crystal */
+		break;
+	default:
+		/* NOTE: I have no info about the bcdDevice for the base PL2303 (up to 1.2Mbaud,
+		   only fixed rates) and for PL2303SA (8-pin chip, up to 115200 baud */
+		/* Determine the chip type.  This algorithm is taken from Linux. */
+		if (dd->bDeviceClass == 0x02)
+			sc->sc_chiptype = TYPE_PL2303;
+		else if (dd->bMaxPacketSize == 0x40)
+			sc->sc_chiptype = TYPE_PL2303HX;
+		else
+			sc->sc_chiptype = TYPE_PL2303;
+		break;
+	}
 
-	DPRINTF("chiptype: %s\n",
-	    (sc->sc_chiptype == TYPE_PL2303HX) ?
-	    "2303X" : "2303");
+	switch (sc->sc_chiptype) {
+	case TYPE_PL2303:
+		DPRINTF("chiptype: 2303\n");
+		break;
+	case TYPE_PL2303HX:
+		DPRINTF("chiptype: 2303HX/TA\n");
+		break;
+	case TYPE_PL2303HXD:
+		DPRINTF("chiptype: 2303HXD/TB/RA/EA\n");
+		break;
+	default:
+		DPRINTF("chiptype: unknown %d\n", sc->sc_chiptype);
+		break;
+	}
 
 	/*
 	 * USB-RSAQ1 has two interface
@@ -429,13 +468,14 @@ uplcom_attach(device_t dev)
 		goto detach;
 	}
 
-	if (sc->sc_chiptype != TYPE_PL2303HX) {
+	if (sc->sc_chiptype == TYPE_PL2303) {
 		/* HX variants seem to lock up after a clear stall request. */
 		mtx_lock(&sc->sc_mtx);
 		usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_WR]);
 		usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_RD]);
 		mtx_unlock(&sc->sc_mtx);
 	} else {
+		/* reset upstream data pipes */
 		if (uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE,
 		    UPLCOM_SET_REQUEST, 8, 0, 0) ||
 		    uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE,
@@ -554,7 +594,7 @@ uplcom_pl2303_init(struct usb_device *udev, uint8_t ch
 	    || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 1, 0, 0))
 		return (EIO);
 
-	if (chiptype == TYPE_PL2303HX)
+	if (chiptype != TYPE_PL2303)
 		err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x44, 0);
 	else
 		err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x24, 0);
@@ -634,23 +674,52 @@ uplcom_cfg_set_break(struct ucom_softc *ucom, uint8_t 
 	    &req, NULL, 0, 1000);
 }
 
+/*
+ * NOTE: These baud rates are officially supported, they can be written
+ * directly into dwDTERate register.
+ *
+ * Free baudrate setting is not supported by the base PL2303, and on
+ * other models it requires writing a divisor value to dwDTERate instead
+ * of the raw baudrate. The formula for divisor calculation is not published
+ * by the vendor, so it is speculative, though the official product homepage
+ * refers to the Linux module source as a reference implementation.
+ */
 static const uint32_t uplcom_rates[] = {
-	75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400,
-	19200, 28800, 38400, 57600, 115200,
 	/*
-	 * Higher speeds are probably possible. PL2303X supports up to
-	 * 6Mb and can set any rate
+	 * Basic 'standard' speed rates, supported by all models
+	 * NOTE: 900 and 56000 actually works as well
 	 */
-	230400, 460800, 614400, 921600, 1228800
+	75, 150, 300, 600, 900, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400,
+	19200, 28800, 38400, 56000, 57600, 115200,
+	/*
+	 * Advanced speed rates up to 6Mbs, supported by HX/TA and HXD/TB/EA/RA
+     * NOTE: regardless of the spec, 256000 does not work
+	 */
+	128000, 134400, 161280, 201600, 230400, 268800, 403200, 460800, 614400,
+	806400, 921600, 1228800, 2457600, 3000000, 6000000,
+	/*
+	 * Advanced speed rates up to 12, supported by HXD/TB/EA/RA
+	 */
+	12000000
 };
 
 #define	N_UPLCOM_RATES	nitems(uplcom_rates)
 
 static int
+uplcom_baud_supported(unsigned int speed)
+{
+	int i;
+	for (i = 0; i < N_UPLCOM_RATES; i++) {
+		if (uplcom_rates[i] == speed)
+			return 1;
+	}
+	return 0;
+}
+
+static int
 uplcom_pre_param(struct ucom_softc *ucom, struct termios *t)
 {
 	struct uplcom_softc *sc = ucom->sc_parent;
-	uint8_t i;
 
 	DPRINTF("\n");
 
@@ -658,26 +727,75 @@ uplcom_pre_param(struct ucom_softc *ucom, struct termi
 	 * Check requested baud rate.
 	 *
 	 * The PL2303 can only set specific baud rates, up to 1228800 baud.
-	 * The PL2303X can set any baud rate up to 6Mb.
+	 * The PL2303HX can set any baud rate up to 6Mb.
 	 * The PL2303HX rev. D can set any baud rate up to 12Mb.
 	 *
-	 * XXX: We currently cannot identify the PL2303HX rev. D, so treat
-	 *      it the same as the PL2303X.
 	 */
-	if (sc->sc_chiptype != TYPE_PL2303HX) {
-		for (i = 0; i < N_UPLCOM_RATES; i++) {
-			if (uplcom_rates[i] == t->c_ospeed)
+
+	/* accept raw divisor data, if someone wants to do the math in user domain */
+	if (t->c_ospeed & 0x80000000)
+		return 0;
+	switch (sc->sc_chiptype) {
+		case TYPE_PL2303HXD:
+			if (t->c_ospeed <= 12000000)
 				return (0);
-		}
- 	} else {
-		if (t->c_ospeed <= 6000000)
-			return (0);
+			break;
+		case TYPE_PL2303HX:
+			if (t->c_ospeed <= 6000000)
+				return (0);
+			break;
+		default:
+			if (uplcom_baud_supported(t->c_ospeed))
+				return (0);
+			break;
 	}
 
 	DPRINTF("uplcom_param: bad baud rate (%d)\n", t->c_ospeed);
 	return (EIO);
 }
 
+static unsigned int
+uplcom_encode_baud_rate_divisor(uint8_t *buf, unsigned int baud)
+{
+	unsigned int baseline, mantissa, exponent;
+
+	/* Determine the baud rate divisor. This algorithm is taken from Linux. */
+	/*
+	 * Apparently the formula is:
+	 *   baudrate = baseline / (mantissa * 4^exponent)
+	 * where
+	 *   mantissa = buf[8:0]
+	 *   exponent = buf[11:9]
+	 */
+	if (baud == 0)
+		baud = 1;
+	baseline = 383385600;
+	mantissa = baseline / baud;
+	if (mantissa == 0)
+		mantissa = 1;
+	exponent = 0;
+	while (mantissa >= 512) {
+		if (exponent < 7) {
+			mantissa >>= 2;	/* divide by 4 */
+			exponent++;
+		} else {
+			/* Exponent is maxed. Trim mantissa and leave. This gives approx. 45.8 baud */
+			mantissa = 511;
+			break;
+		}
+	}
+
+	buf[3] = 0x80;
+	buf[2] = 0;
+	buf[1] = exponent << 1 | mantissa >> 8;
+	buf[0] = mantissa & 0xff;
+
+	/* Calculate and return the exact baud rate. */
+	baud = (baseline / mantissa) >> (exponent << 1);
+	DPRINTF("real baud rate will be %u\n", baud);
+
+	return baud;
+}
 static void
 uplcom_cfg_param(struct ucom_softc *ucom, struct termios *t)
 {
@@ -689,10 +807,24 @@ uplcom_cfg_param(struct ucom_softc *ucom, struct termi
 
 	memset(&ls, 0, sizeof(ls));
 
-	USETDW(ls.dwDTERate, t->c_ospeed);
+	/*
+	 * NOTE: If unsupported baud rates are set directly, the PL2303* uses 9600 baud.
+	 */
+	if ((t->c_ospeed & 0x80000000) || uplcom_baud_supported(t->c_ospeed))
+		USETDW(ls.dwDTERate, t->c_ospeed);
+	else
+		t->c_ospeed = uplcom_encode_baud_rate_divisor((uint8_t*)&ls.dwDTERate, t->c_ospeed);
 
 	if (t->c_cflag & CSTOPB) {
-		ls.bCharFormat = UCDC_STOP_BIT_2;
+		if ((t->c_cflag & CSIZE) == CS5) {
+			/*
+			 * NOTE: Comply with "real" UARTs / RS232:
+			 *       use 1.5 instead of 2 stop bits with 5 data bits
+			 */
+			ls.bCharFormat = UCDC_STOP_BIT_1_5;
+		} else {
+			ls.bCharFormat = UCDC_STOP_BIT_2;
+		}
 	} else {
 		ls.bCharFormat = UCDC_STOP_BIT_1;
 	}
@@ -722,7 +854,7 @@ uplcom_cfg_param(struct ucom_softc *ucom, struct termi
 		break;
 	}
 
-	DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n",
+	DPRINTF("rate=0x%08x fmt=%d parity=%d bits=%d\n",
 	    UGETDW(ls.dwDTERate), ls.bCharFormat,
 	    ls.bParityType, ls.bDataBits);
 
@@ -743,7 +875,7 @@ uplcom_cfg_param(struct ucom_softc *ucom, struct termi
 		req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
 		req.bRequest = UPLCOM_SET_REQUEST;
 		USETW(req.wValue, 0);
-		if (sc->sc_chiptype == TYPE_PL2303HX)
+		if (sc->sc_chiptype != TYPE_PL2303)
 			USETW(req.wIndex, UPLCOM_SET_CRTSCTS_PL2303X);
 		else
 			USETW(req.wIndex, UPLCOM_SET_CRTSCTS);
@@ -809,7 +941,6 @@ uplcom_cfg_get_status(struct ucom_softc *ucom, uint8_t
 
 	DPRINTF("\n");
 
-	/* XXX Note: sc_lsr is always zero */
 	*lsr = sc->sc_lsr;
 	*msr = sc->sc_msr;
 }
@@ -834,18 +965,33 @@ uplcom_intr_callback(struct usb_xfer *xfer, usb_error_
 			pc = usbd_xfer_get_frame(xfer, 0);
 			usbd_copy_out(pc, 0, buf, sizeof(buf));
 
-			DPRINTF("status = 0x%02x\n", buf[8]);
+			DPRINTF("status = 0x%02x\n", buf[UPLCOM_STATE_INDEX]);
 
 			sc->sc_lsr = 0;
 			sc->sc_msr = 0;
 
-			if (buf[8] & RSAQ_STATUS_CTS) {
+			if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_CTS) {
 				sc->sc_msr |= SER_CTS;
 			}
-			if (buf[8] & RSAQ_STATUS_DSR) {
+			if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_OVERRUN_ERROR) {
+				sc->sc_lsr |= ULSR_OE;
+			}
+			if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_PARITY_ERROR) {
+				sc->sc_lsr |= ULSR_PE;
+			}
+			if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_FRAME_ERROR) {
+				sc->sc_lsr |= ULSR_FE;
+			}
+			if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_RING) {
+				sc->sc_msr |= SER_RI;
+			}
+			if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_BREAK_ERROR) {
+				sc->sc_lsr |= ULSR_BI;
+			}
+			if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_DSR) {
 				sc->sc_msr |= SER_DSR;
 			}
-			if (buf[8] & RSAQ_STATUS_DCD) {
+			if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_DCD) {
 				sc->sc_msr |= SER_DCD;
 			}
 			ucom_status_change(&sc->sc_ucom);



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