--- /home/lzenz/workspace/dhcom/am335x/kernel/cp210x.c
+++ /home/lzenz/cp210x.diff
@@ -38,6 +38,7 @@
 	unsigned int *cflagp, unsigned int *baudp);
 static void cp210x_change_speed(struct tty_struct *, struct usb_serial_port *,
 							struct ktermios *);
+static bool cp210x_tx_empty(struct usb_serial_port *);
 static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *,
 							struct ktermios*);
 static int cp210x_tiocmget(struct tty_struct *);
@@ -188,6 +189,7 @@
 struct cp210x_port_private {
 	__u8			bInterfaceNumber;
 	__u8			bPartNumber;
+	bool            Swap_GET_LINE_CTL; /* CP2108 returns swapped bytes due to a bug */
 };
 
 static struct usb_serial_driver cp210x_device = {
@@ -204,6 +206,7 @@
 	.ioctl			= cp210x_ioctl,
 	.break_ctl		= cp210x_break_ctl,
 	.set_termios		= cp210x_set_termios,
+	.tx_empty       = cp210x_tx_empty,
 	.tiocmget		= cp210x_tiocmget,
 	.tiocmset		= cp210x_tiocmset,
 	.attach			= cp210x_startup,
@@ -308,6 +311,20 @@
 #define CONTROL_WRITE_DTR	0x0100
 #define CONTROL_WRITE_RTS	0x0200
 
+// Data returned by CP210X_GET_COMM_STATUS --
+// same as SERIAL_STATUS in ntddser.h plus one byte
+// The h/w doc says it's 0x0013 bytes
+
+struct CP210X_COMM_STATUS {
+	u32     ulErrors;
+	u32     ulHoldReasons;
+	u32     ulAmountInInQueue;
+	u32     ulAmountInOutQueue;
+	u8      bEofReceived;
+	u8      bWaitForImmediate;
+	u8      bReserved;
+};
+
 /*
  * cp210x_get_config
  * Reads from the CP210x configuration registers
@@ -403,6 +420,26 @@
 	}
 
 	return 0;
+}
+
+static bool cp210x_tx_empty(struct usb_serial_port *port)
+{
+	int err;
+
+	/* Looks like get_config may write beyond end of buffer if it's not multiple of 4; pad it to 0x14 bytes */
+	struct CP210X_COMM_STATUS_CONTAINER {
+		struct CP210X_COMM_STATUS  sts;
+		u8                         pad_to_0x14_bytes;
+	} comm_sts_cont;
+
+	err = cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
+			CP210X_GET_COMM_STATUS, 0, (unsigned int *) &comm_sts_cont, 0x13);
+
+	if (!err) {
+		if (comm_sts_cont.sts.ulAmountInOutQueue)
+			return false;
+	}
+	return true;
 }
 
 /*
@@ -449,13 +486,13 @@
 
 static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)
 {
-	int result;
-
-	result = cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+	int err;
+
+	err = cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
 			CP210X_IFC_ENABLE, UART_ENABLE, NULL, 0);
-	if (result) {
+	if (err) {
 		dev_err(&port->dev, "%s - Unable to enable UART\n", __func__);
-		return result;
+		return err;
 	}
 
 	/* Configure the termios structure */
@@ -595,6 +632,14 @@
 	}
 }
 
+static void cp210x_maybe_swap_LINE_CTL(struct usb_serial_port *port, u16 *pval)
+{
+	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+
+	if (port_priv->Swap_GET_LINE_CTL)
+		*pval = ((*pval & 0xff) >> 8) | (*pval << 8);
+}
+
 /*
  * cp210x_get_termios_port
  * This is the heart of cp210x_get_termios which always uses a &usb_serial_port.
@@ -617,6 +662,8 @@
 
 	cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
 			CP210X_GET_LINE_CTL, 0, &bits, 2);
+	cp210x_maybe_swap_LINE_CTL( port, (u16*) &bits);
+
 	cflag &= ~CSIZE;
 	switch (bits & BITS_DATA_MASK) {
 	case BITS_DATA_5:
@@ -792,6 +839,7 @@
 	if ((cflag & CSIZE) != (old_cflag & CSIZE)) {
 		cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
 				CP210X_GET_LINE_CTL, 0, &bits, 2);
+		cp210x_maybe_swap_LINE_CTL( port, (u16*)  &bits);
 		bits &= ~BITS_DATA_MASK;
 		switch (cflag & CSIZE) {
 		case CS5:
@@ -828,6 +876,7 @@
 	    (old_cflag & (PARENB|PARODD|CMSPAR))) {
 		cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
 				CP210X_GET_LINE_CTL, 0, &bits, 2);
+		cp210x_maybe_swap_LINE_CTL( port, (u16*) &bits);
 		bits &= ~BITS_PARITY_MASK;
 		if (cflag & PARENB) {
 			if (cflag & CMSPAR) {
@@ -856,6 +905,7 @@
 	if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) {
 		cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
 				CP210X_GET_LINE_CTL, 0, &bits, 2);
+		cp210x_maybe_swap_LINE_CTL( port, (u16*) &bits);
 		bits &= ~BITS_STOP_MASK;
 		if (cflag & CSTOPB) {
 			bits |= BITS_STOP_2;
@@ -984,6 +1034,9 @@
 	unsigned int partNum;
 
 	for (i = 0; i < serial->num_ports; i++) {
+		unsigned int line_ctl;
+		int err;
+
 		port_priv = kzalloc(sizeof(*port_priv), GFP_KERNEL);
 		if (!port_priv)
 			return -ENOMEM;
@@ -994,12 +1047,32 @@
 		usb_set_serial_port_data(serial->port[i], port_priv);
 
 		/* Get the 1-byte part number of the cp210x device */
-		cp210x_get_config(serial->port[i],
+		err = cp210x_get_config(serial->port[i],
 			REQTYPE_DEVICE_TO_HOST, CP210X_VENDOR_SPECIFIC,
 			CP210X_GET_PARTNUM, &partNum, 1);
+		if (err)
+			return err;
 		port_priv->bPartNumber = partNum & 0xFF;
-	}
-
+
+		/* Detect CP2108 bug and activate workaround.
+		 * Write a known good value 0x800, read it back.
+		 * If it comes back swapped the bug is detected.
+		 */
+		port_priv->Swap_GET_LINE_CTL = false;
+
+		err = cp210x_set_config(serial->port[i], REQTYPE_HOST_TO_INTERFACE,
+			CP210X_SET_LINE_CTL, 0x800, NULL, 0);
+		if (err)
+			return err;
+		err = cp210x_get_config(serial->port[i], REQTYPE_INTERFACE_TO_HOST,
+			CP210X_GET_LINE_CTL, 0, &line_ctl, 2);
+		if (err)
+			return err;
+		if ((line_ctl & 0xffff) == 8) {
+			port_priv->Swap_GET_LINE_CTL = true;
+			dev_err(&serial->port[i]->dev, "%s Swap_GET_LINE_CTL ON\n", __func__);
+		}
+	}
 	return 0;
 }
 
