Re: [PATCH 06/13] USB: serial: ch341: fix initial line settings

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Dec 19, 2016 at 02:12:05PM -0800, Russell Senior wrote:
> >> Apart from the two additional tests mentioned above, can you also
> >> provide a log from when connecting the device using the following commit
> >> that I just pushed to the ch341 branch:
> >>
> >>         f341ee36198d ("dbg: ch341: add register dumps to probe")
> >>
> >> which provides dumps of the register settings during initialisation.
> >> (Make sure ch341 dynamic debugging is not enabled to avoid cluttering
> >> the log.)
> >
> > I'll send this in a followup, need to rebuild.
> 
> 00018-gf341ee3:
> 
> Dec 19 13:51:13 willard kernel: usbcore: registered new interface driver ch341
> Dec 19 13:51:13 willard kernel: usbserial: USB Serial support
> registered for ch341-uart
> Dec 19 13:51:23 willard kernel: usb 6-2: new full-speed USB device
> number 10 using uhci_hcd
> Dec 19 13:51:23 willard kernel: usb 6-2: New USB device found,
> idVendor=1a86, idProduct=7523
> Dec 19 13:51:23 willard kernel: usb 6-2: New USB device strings:
> Mfr=0, Product=2, SerialNumber=0
> Dec 19 13:51:23 willard kernel: usb 6-2: Product: USB2.0-Ser!
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: ch341-uart converter detected
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [00] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [01] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [02] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [03] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [04] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [05] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [06] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [07] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [08] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [09] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0a] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0b] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0c] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0d] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0e] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0f] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [10] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [11] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [12] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [13] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [14] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [15] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [16] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [17] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [18] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [19] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1a] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1b] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1c] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1d] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1e] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1f] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [20] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [21] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [22] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [23] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [24] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [25] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [26] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [27] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [28] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [29] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2a] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2b] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2c] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2d] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2e] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2f] = 00
> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: init 0 0

> Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: write 0x31 0xb282

Ok, so no direct register read works (as they do on CH340G and CH341A)
and instead always return zero. Except, when LCR is read back together
with register 0x25:

> Dec 19 13:51:23 willard kernel: usb 6-2: ch341_dbg - [0x2518] = f1 00,
> [0x1213] = 00 00 (pre version)

Note that the 0x31 LCR is read back as 0xf1, that is, with the tx and rx
enable bits set.

My CH341A behaves similarly and always has rx enabled.

> Dec 19 13:51:23 willard kernel: usb 6-2: ch341_dbg - [0x2518] = f1 00,
> [0x1213] = 00 00 (post init-0)
> Dec 19 13:51:23 willard kernel: usb 6-2: ch341_set_baudrate_lcr -
> speed = 9600, lcr = c3, a = b202
> Dec 19 13:51:23 willard kernel: usb 6-2: ch341_dbg - [0x2518] = f1 00,
> [0x1213] = 00 00 (post init - lcr reset)

And the init command does not seem to have any effect on either LCR or
the divisors as previous tests also show.

I'm not sure what device we're dealing with here, but it seems it would
not be supported by the vendor (whose version of this driver also uses
the init-command).

Perhaps you could give the attached vendor driver a quick spin just to
confirm that? It's a rebased version against usb-next.

I've also pushed a commit that tries to dump the registers differently
(reading together with register 0x25):

	3baa1eff4245 ("dbg: ch341: dump registers differently")

Could you provide a log from when connecting the device with this commit
as well?

Thanks,
Johan
>From 6d2bca3cda09fca9c0a2a2c84dbf64c6090b3da5 Mon Sep 17 00:00:00 2001
From: Johan Hovold <johan@xxxxxxxxxx>
Date: Mon, 21 Nov 2016 16:28:22 +0100
Subject: [PATCH] tmp: ch341: replace with vendor driver

---
 drivers/usb/serial/ch341.c | 1238 ++++++++++++++++++++++++++++----------------
 1 file changed, 803 insertions(+), 435 deletions(-)

diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c
index 2597b83a8ae2..b193b78c27bb 100644
--- a/drivers/usb/serial/ch341.c
+++ b/drivers/usb/serial/ch341.c
@@ -1,11 +1,15 @@
 /*
+ * Winchiphead CH34x USB to serial adaptor driver
+ *
  * Copyright 2007, Frank A Kingswood <frank@xxxxxxxxxxxxxxxxxxxxxxxxxx>
  * Copyright 2007, Werner Cornelius <werner@xxxxxxxxxxxxxxxxxxxx>
  * Copyright 2009, Boris Hajduk <boris@xxxxxxxxxx>
+ * Copyright 2016, WCH Tech Group <tech@xxxxxxxxxxxxxxx>
  *
- * ch341.c implements a serial port driver for the Winchiphead CH341.
+ * ch34x.c implements a serial port driver for the Winchiphead CH340 and
+ * CH341.
  *
- * The CH341 device can be used to implement an RS232 asynchronous
+ * The CH34x device can be used to implement an RS232 asynchronous
  * serial port, an IEEE-1284 parallel printer port or a memory-like
  * interface. In all cases the CH341 supports an I2C interface as well.
  * This driver only supports the asynchronous serial interface.
@@ -16,134 +20,270 @@
  */
 
 #include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
 #include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
 #include <linux/module.h>
-#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
-#include <linux/serial.h>
-#include <asm/unaligned.h>
-
-#define DEFAULT_BAUD_RATE 9600
-#define DEFAULT_TIMEOUT   1000
-
-/* flags for IO-Bits */
-#define CH341_BIT_RTS (1 << 6)
-#define CH341_BIT_DTR (1 << 5)
-
-/******************************/
-/* interrupt pipe definitions */
-/******************************/
-/* always 4 interrupt bytes */
-/* first irq byte normally 0x08 */
-/* second irq byte base 0x7d + below */
-/* third irq byte base 0x94 + below */
-/* fourth irq byte normally 0xee */
-
-/* second interrupt byte */
-#define CH341_MULT_STAT 0x04 /* multiple status since last interrupt event */
-
-/* status returned in third interrupt answer byte, inverted in data
-   from irq */
-#define CH341_BIT_CTS 0x01
-#define CH341_BIT_DSR 0x02
-#define CH341_BIT_RI  0x04
-#define CH341_BIT_DCD 0x08
-#define CH341_BITS_MODEM_STAT 0x0f /* all bits */
-
-/*******************************/
-/* baudrate calculation factor */
-/*******************************/
-#define CH341_BAUDBASE_FACTOR 1532620800
-#define CH341_BAUDBASE_DIVMAX 3
-
-/* Break support - the information used to implement this was gleaned from
- * the Net/FreeBSD uchcom.c driver by Takanori Watanabe.  Domo arigato.
- */
 
-#define CH341_REQ_READ_VERSION 0x5F
-#define CH341_REQ_WRITE_REG    0x9A
-#define CH341_REQ_READ_REG     0x95
-#define CH341_REQ_SERIAL_INIT  0xA1
-#define CH341_REQ_MODEM_CTRL   0xA4
-
-#define CH341_REG_BREAK        0x05
-#define CH341_REG_LCR          0x18
-#define CH341_NBREAK_BITS      0x01
-
-#define CH341_LCR_ENABLE_RX    0x80
-#define CH341_LCR_ENABLE_TX    0x40
-#define CH341_LCR_MARK_SPACE   0x20
-#define CH341_LCR_PAR_EVEN     0x10
-#define CH341_LCR_ENABLE_PAR   0x08
-#define CH341_LCR_STOP_BITS_2  0x04
-#define CH341_LCR_CS8          0x03
-#define CH341_LCR_CS7          0x02
-#define CH341_LCR_CS6          0x01
-#define CH341_LCR_CS5          0x00
-
-static const struct usb_device_id id_table[] = {
-	{ USB_DEVICE(0x4348, 0x5523) },
-	{ USB_DEVICE(0x1a86, 0x7523) },
-	{ USB_DEVICE(0x1a86, 0x5523) },
-	{ },
+#define DRIVER_DESC		"WCH CH34x USB to serial adaptor driver"
+#define DRIVER_AUTHOR	"WCH Tech Group <tech@xxxxxxxxxxxxxxx>"
+
+#define CH34x_VENDOR_ID        0x1A86
+#define CH340_PRODUCT_ID       0x7523
+#define CH341_PRODUCT_ID       0x5523
+
+#define CH34x_CLOSING_WAIT     (30 * HZ)
+
+#define CH34x_BUF_SIZE	1024
+#define CH34x_TMP_BUF_SIZE 1024
+
+#define VENDOR_WRITE_TYPE	0x40
+#define VENDOR_READ_TYPE	0xC0
+
+#define VENDOR_READ		0x95
+#define VENDOR_WRITE		0x9A
+#define VENDOR_SERIAL_INIT	0xA1
+#define VENDOR_MODEM_OUT	0xA4
+#define VENDOR_VERSION		0x5F
+
+/* for command 0xA4 */
+#define UART_CTS		0x01
+#define UART_DSR		0x02
+#define UART_RING		0x04
+#define UART_DCD		0x08
+#define CONTROL_OUT		0x10
+#define CONTROL_DTR		0x20
+#define CONTROL_RTS		0x40
+
+/* uart state */
+#define UART_STATE		0x00
+#define UART_OVERRUN_ERROR  0x01
+#define UART_BREAK_ERROR
+#define UART_PARITY_ERROR       0x02
+#define UART_FRAME_ERROR        0x06
+#define UART_RECV_ERROR		0x02
+#define UART_STATE_TRANSIENT_MASK 0x07
+
+/* port state */
+#define PORTA_STATE		0x01
+#define PORTB_STATE		0x02
+#define PORTC_STATE		0x03
+
+/* Baud Rate */
+#define CH34x_BAUDRATE_FACTOR	1532620800
+#define CH34x_BAUDRATE_DIVMAX	3
+
+static DECLARE_WAIT_QUEUE_HEAD(wq);
+static int wait_flag;
+
+struct ch34x_buf {
+	unsigned int buf_size;
+	char *buf_buf;
+	char *buf_get;
+	char *buf_put;
+};
+
+struct ch34x_private {
+	spinlock_t lock;       /* access lock */
+	struct ch34x_buf *buf;
+	int write_urb_in_use;
+	unsigned int baud_rate;
+	wait_queue_head_t delta_msr_wait;
+	u8 line_control;       /* set line control value RTS/DTR */
+	u8 line_status;	       /* active status of modem control inputs */
+	u8 termios_initialized;
 };
-MODULE_DEVICE_TABLE(usb, id_table);
 
-struct ch341_private {
-	spinlock_t lock; /* access lock */
-	unsigned baud_rate; /* set baud rate */
-	u8 line_control; /* set line control value RTS/DTR */
-	u8 line_status; /* active status of modem control inputs */
+static struct usb_device_id id_table[] = {
+	{ USB_DEVICE(CH34x_VENDOR_ID, CH340_PRODUCT_ID) },
+	{ USB_DEVICE(CH34x_VENDOR_ID, CH341_PRODUCT_ID) },
+	{ }
 };
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct ch34x_buf *ch34x_buf_alloc(unsigned int size)
+{
+	struct ch34x_buf *pb;
+
+	if (size == 0)
+		return NULL;
+
+	pb = kmalloc(sizeof(struct ch34x_buf), GFP_KERNEL);
+	if (pb == NULL)
+		return NULL;
+
+	pb->buf_buf = kmalloc(size, GFP_KERNEL);
+	if (pb->buf_buf == NULL) {
+		kfree(pb);
+		return NULL;
+	}
+
+	pb->buf_size = size;
+	pb->buf_get = pb->buf_put = pb->buf_buf;
+
+	return pb;
+}
+
+static void ch34x_buf_free(struct ch34x_buf *pb)
+{
+	if (pb) {
+		kfree(pb->buf_buf);
+		kfree(pb);
+	}
+}
+
+static void ch34x_buf_clear(struct ch34x_buf *pb)
+{
+	if (pb != NULL)
+		pb->buf_get = pb->buf_put;
+}
+
+static unsigned int ch34x_buf_data_avail(struct ch34x_buf *pb)
+{
+	if (pb == NULL)
+		return 0;
+
+	return ((pb->buf_size + pb->buf_put - pb->buf_get) % pb->buf_size);
+}
+
+static unsigned int ch34x_buf_space_avail(struct ch34x_buf *pb)
+{
+	if (pb == NULL)
+		return 0;
+
+	return ((pb->buf_size + pb->buf_get - pb->buf_put - 1)
+			% pb->buf_size);
+}
+
+static unsigned int ch34x_buf_put(struct ch34x_buf *pb,
+		const char *buf, unsigned int count)
+{
+	unsigned int len;
+
+	if (pb == NULL)
+		return 0;
+
+	len = ch34x_buf_space_avail(pb);
+	if (count > len)
+		count = len;
+	else if (count == 0)
+		return 0;
+
+	len = pb->buf_buf + pb->buf_size - pb->buf_put;
+	if (count > len) {
+		memcpy(pb->buf_put, buf, len);
+		memcpy(pb->buf_buf, buf+len, count - len);
+		pb->buf_put = pb->buf_buf + count - len;
+	} else {
+		memcpy(pb->buf_put, buf, count);
+		if (count < len)
+			pb->buf_put += count;
+		else if (count == len)
+			pb->buf_put = pb->buf_buf;
+	}
 
-static void ch341_set_termios(struct tty_struct *tty,
-			      struct usb_serial_port *port,
-			      struct ktermios *old_termios);
+	return count;
+}
 
-static int ch341_control_out(struct usb_device *dev, u8 request,
-			     u16 value, u16 index)
+static unsigned int ch34x_buf_get(struct ch34x_buf *pb,
+		char *buf, unsigned int count)
 {
-	int r;
+	unsigned int len;
+
+	if (pb == NULL)
+		return 0;
+
+	len = ch34x_buf_data_avail(pb);
+	if (count > len)
+		count = len;
+	else if (count == 0)
+		return 0;
+
+	len = pb->buf_buf + pb->buf_size - pb->buf_get;
+	if (count > len) {
+		memcpy(buf, pb->buf_get, len);
+		memcpy(buf + len, pb->buf_buf, count - len);
+		pb->buf_get = pb->buf_buf + count - len;
+	} else {
+		memcpy(buf, pb->buf_get, count);
+		if (count < len)
+			pb->buf_get += count;
+		else if (count == len)
+			pb->buf_get = pb->buf_buf;
+	}
+
+	return count;
+}
 
-	dev_dbg(&dev->dev, "ch341_control_out(%02x,%02x,%04x,%04x)\n",
-		USB_DIR_OUT|0x40, (int)request, (int)value, (int)index);
+static int ch34x_vendor_read(__u8 request,
+		__u16 value,
+		__u16 index,
+		struct usb_serial *serial,
+		unsigned char *buf,
+		__u16 len)
+{
+	int retval;
 
-	r = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request,
-			    USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
-			    value, index, NULL, 0, DEFAULT_TIMEOUT);
+	retval = usb_control_msg(serial->dev,
+			usb_rcvctrlpipe(serial->dev, 0),
+			request,
+			VENDOR_READ_TYPE,
+			value, index, buf, len, 1000);
 
-	return r;
+	return retval;
 }
 
-static int ch341_control_in(struct usb_device *dev,
-			    u8 request, u16 value, u16 index,
-			    char *buf, unsigned bufsize)
+static int ch34x_vendor_write(__u8 request,
+		__u16 value,
+		__u16 index,
+		struct usb_serial *serial,
+		unsigned char *buf,
+		__u16 len)
 {
-	int r;
+	int retval;
 
-	dev_dbg(&dev->dev, "ch341_control_in(%02x,%02x,%04x,%04x,%p,%u)\n",
-		USB_DIR_IN|0x40, (int)request, (int)value, (int)index, buf,
-		(int)bufsize);
+	retval = usb_control_msg(serial->dev,
+			usb_sndctrlpipe(serial->dev, 0),
+			request,
+			VENDOR_WRITE_TYPE,
+			value, index, buf, len, 1000);
 
-	r = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request,
-			    USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
-			    value, index, buf, bufsize, DEFAULT_TIMEOUT);
-	return r;
+	return retval;
 }
 
-static int ch341_init_set_baudrate(struct usb_device *dev,
-				   struct ch341_private *priv, unsigned ctrl)
+static int set_control_lines(struct usb_serial_port *port,
+		struct usb_serial *serial, u8 value)
 {
-	short a;
-	int r;
-	unsigned long factor;
-	short divisor;
+	int retval;
+
+	retval = ch34x_vendor_write(VENDOR_MODEM_OUT, (unsigned short)value,
+			0x0000, serial, NULL, 0x00);
+	dev_dbg(&port->dev, "%s - value=%d, retval=%d",
+		__func__, value, retval);
+
+	return retval;
+}
 
-	if (!priv->baud_rate)
+static int ch34x_get_baud_rate(unsigned int baud_rate,
+		unsigned char *a, unsigned char *b)
+{
+	unsigned long factor = 0;
+	short divisor = 0;
+
+	if (!baud_rate)
 		return -EINVAL;
-	factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate);
-	divisor = CH341_BAUDBASE_DIVMAX;
+
+	factor = CH34x_BAUDRATE_FACTOR / baud_rate;
+	divisor = CH34x_BAUDRATE_DIVMAX;
 
 	while ((factor > 0xfff0) && divisor) {
 		factor >>= 3;
@@ -154,464 +294,692 @@ static int ch341_init_set_baudrate(struct usb_device *dev,
 		return -EINVAL;
 
 	factor = 0x10000 - factor;
-	a = (factor & 0xff00) | divisor;
-
-	/* 0x9c is "enable SFR_UART Control register and timer" */
-	r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT,
-			      0x9c | (ctrl << 8), a | 0x80);
+	*a = (factor & 0xff00) >> 8;
+	*b = divisor;
 
-	return r;
+	return 0;
 }
 
-static int ch341_set_handshake(struct usb_device *dev, u8 control)
+static void ch34x_set_termios(struct tty_struct *tty,
+		struct usb_serial_port *port, struct ktermios *old_termios)
 {
-	return ch341_control_out(dev, CH341_REQ_MODEM_CTRL, ~control, 0);
-}
+	struct usb_serial *serial = port->serial;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
+	struct ktermios *termios = &tty->termios;
 
-static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv)
-{
-	char *buffer;
-	int r;
-	const unsigned size = 8;
+	unsigned int baud_rate;
+	unsigned int cflag;
 	unsigned long flags;
+	u8 control;
 
-	buffer = kmalloc(size, GFP_KERNEL);
-	if (!buffer)
-		return -ENOMEM;
+	unsigned char divisor = 0;
+	unsigned char reg_count = 0;
+	unsigned char factor = 0;
+	unsigned char reg_value = 0;
+	unsigned short value = 0;
+	unsigned short index = 0;
 
-	r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x0706, 0, buffer, size);
-	if (r < 0)
-		goto out;
+	dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
 
-	/* setup the private status if available */
-	if (r == 2) {
-		r = 0;
-		spin_lock_irqsave(&priv->lock, flags);
-		priv->line_status = (~(*buffer)) & CH341_BITS_MODEM_STAT;
-		spin_unlock_irqrestore(&priv->lock, flags);
-	} else
-		r = -EPROTO;
+	spin_lock_irqsave(&priv->lock, flags);
+	if (!priv->termios_initialized) {
+		*termios = tty_std_termios;
+		termios->c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+		termios->c_ispeed = 9600;
+		termios->c_ospeed = 9600;
+		priv->termios_initialized = 1;
+	}
+	spin_unlock_irqrestore(&priv->lock, flags);
 
-out:	kfree(buffer);
-	return r;
-}
+	/*
+	 * The ch34x is reported to lose bytes if you change serial setting
+	 * even to the same vaules as before. Thus we actually need to filter
+	 * in this specific case.
+	 */
+	if (!tty_termios_hw_change(termios, old_termios))
+		return;
 
-/* -------------------------------------------------------------------------- */
+	cflag = termios->c_cflag;
 
-static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
-{
-	char *buffer;
-	int r;
-	const unsigned size = 8;
-
-	buffer = kmalloc(size, GFP_KERNEL);
-	if (!buffer)
-		return -ENOMEM;
-
-	/* expect two bytes 0x27 0x00 */
-	r = ch341_control_in(dev, CH341_REQ_READ_VERSION, 0, 0, buffer, size);
-	if (r < 0)
-		goto out;
-	dev_dbg(&dev->dev, "Chip version: 0x%02x\n", buffer[0]);
-
-	r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0);
-	if (r < 0)
-		goto out;
-
-	/* expect two bytes 0x56 0x00 */
-	r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x2518, 0, buffer, size);
-	if (r < 0)
-		goto out;
-
-	r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x2518, 0x0050);
-	if (r < 0)
-		goto out;
-
-	/* expect 0xff 0xee */
-	r = ch341_get_status(dev, priv);
-	if (r < 0)
-		goto out;
-
-	r = ch341_init_set_baudrate(dev, priv, 0);
-	if (r < 0)
-		goto out;
-
-	r = ch341_set_handshake(dev, priv->line_control);
-	if (r < 0)
-		goto out;
-
-	/* expect 0x9f 0xee */
-	r = ch341_get_status(dev, priv);
-
-out:	kfree(buffer);
-	return r;
-}
+	dev_dbg(&port->dev, "%s(%d) cflag = 0x%x\n",
+		__func__, port->port_number, cflag);
 
-static int ch341_port_probe(struct usb_serial_port *port)
-{
-	struct ch341_private *priv;
-	int r;
+	/* Get the byte size */
+	switch (cflag & CSIZE) {
+	case CS5:
+		reg_value |= 0x00;
+		break;
+	case CS6:
+		reg_value |= 0x01;
+		break;
+	case CS7:
+		reg_value |= 0x02;
+		break;
+	case CS8:
+		reg_value |= 0x03;
+		break;
+	default:
+		reg_value |= 0x03;
+		break;
+	}
+	dev_dbg(&port->dev, "%s - data bits = %d", __func__, reg_value + 0x05);
 
-	priv = kzalloc(sizeof(struct ch341_private), GFP_KERNEL);
-	if (!priv)
-		return -ENOMEM;
+	if (cflag & CSTOPB) {
+		reg_value |= 0x04;
+		dev_dbg(&port->dev, "%s - stop bits = 2", __func__);
+	} else
+		dev_dbg(&port->dev, "%s - stop bits = 1", __func__);
+
+	if (cflag & PARENB) {
+		if (cflag & PARODD) {
+			reg_value |= 0x08 | 0x00;
+			dev_dbg(&port->dev, "%s - parity = odd", __func__);
+		} else {
+			reg_value |= 0x08 | 0x10;
+			dev_dbg(&port->dev, "%s - parity = even", __func__);
+		}
+	} else
+		dev_dbg(&port->dev, "%s - parity = none", __func__);
 
-	spin_lock_init(&priv->lock);
-	priv->baud_rate = DEFAULT_BAUD_RATE;
-	priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR;
+	baud_rate = tty_get_baud_rate(tty);
+	dev_dbg(&port->dev, "%s = baud_rate = %d", __func__, baud_rate);
+	ch34x_get_baud_rate(baud_rate, &factor, &divisor);
+	dev_dbg(&port->dev, "----->>>> baud_rate = %d, factor:0x%x, divisor:0x%x",
+			baud_rate, factor, divisor);
+
+	/* enable SFR_UART RX and TX */
+	reg_value |= 0xc0;
+	/* enable SFR_UART Control register and timer */
+	reg_count |= 0x9c;
+
+	value |= reg_count;
+	value |= (unsigned short)reg_value << 8;
+	index |= 0x80 | divisor;
+	index |= (unsigned short)factor << 8;
+	ch34x_vendor_write(VENDOR_SERIAL_INIT, value, index, serial, NULL, 0);
+
+	/* change control lines if we are switching to or from B0 */
+	spin_lock_irqsave(&priv->lock, flags);
+	control = priv->line_control;
+	if ((cflag & CBAUD) == B0)
+		priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS);
+	else
+		priv->line_control |= (CONTROL_DTR | CONTROL_RTS);
 
-	r = ch341_configure(port->serial->dev, priv);
-	if (r < 0)
-		goto error;
+	if (control != priv->line_control) {
+		control = priv->line_control;
+		spin_unlock_irqrestore(&priv->lock, flags);
+		set_control_lines(port, serial, control);
+	} else
+		spin_unlock_irqrestore(&priv->lock, flags);
 
-	usb_set_serial_port_data(port, priv);
-	return 0;
+	if (cflag & CRTSCTS)
+		ch34x_vendor_write(VENDOR_WRITE, 0x2727, 0x0101,
+						serial, NULL, 0);
 
-error:	kfree(priv);
-	return r;
+	/* FIXME: Need to read back resulting baud rate */
+	if (baud_rate)
+		tty_encode_baud_rate(tty, baud_rate, baud_rate);
 }
 
-static int ch341_port_remove(struct usb_serial_port *port)
+static int ch34x_tiocmget(struct tty_struct *tty)
 {
-	struct ch341_private *priv;
+	struct usb_serial_port *port = tty->driver_data;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
+	unsigned long flags;
+	unsigned int mcr;
+	unsigned int retval;
 
-	priv = usb_get_serial_port_data(port);
-	kfree(priv);
+	dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
+	if (!usb_get_intfdata(port->serial->interface))
+		return -ENODEV;
 
-	return 0;
-}
+	spin_lock_irqsave(&priv->lock, flags);
+	mcr = priv->line_control;
+	spin_unlock_irqrestore(&priv->lock, flags);
 
-static int ch341_carrier_raised(struct usb_serial_port *port)
-{
-	struct ch341_private *priv = usb_get_serial_port_data(port);
-	if (priv->line_status & CH341_BIT_DCD)
-		return 1;
-	return 0;
+	retval = ((mcr & CONTROL_DTR) ? TIOCM_DTR : 0) |
+		((mcr & CONTROL_RTS) ? TIOCM_RTS : 0) |
+		((mcr & UART_CTS) ? TIOCM_CTS : 0) |
+		((mcr & UART_DSR) ? TIOCM_DSR : 0) |
+		((mcr & UART_RING) ? TIOCM_RI : 0) |
+		((mcr & UART_DCD) ? TIOCM_CD : 0);
+
+	dev_dbg(&port->dev, "%s-retval = 0x%x", __func__, retval);
+
+	return retval;
 }
 
-static void ch341_dtr_rts(struct usb_serial_port *port, int on)
+static void ch34x_close(struct usb_serial_port *port)
 {
-	struct ch341_private *priv = usb_get_serial_port_data(port);
+	struct tty_struct *tty = port->port.tty;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
 	unsigned long flags;
+	unsigned int c_cflag;
 
-	/* drop DTR and RTS */
+	/* clear out any remaining data in the buffer */
 	spin_lock_irqsave(&priv->lock, flags);
-	if (on)
-		priv->line_control |= CH341_BIT_RTS | CH341_BIT_DTR;
-	else
-		priv->line_control &= ~(CH341_BIT_RTS | CH341_BIT_DTR);
+	ch34x_buf_clear(priv->buf);
 	spin_unlock_irqrestore(&priv->lock, flags);
-	ch341_set_handshake(port->serial->dev, priv->line_control);
-}
 
-static void ch341_close(struct usb_serial_port *port)
-{
-	usb_serial_generic_close(port);
+	/* kill our urbs */
 	usb_kill_urb(port->interrupt_in_urb);
+	usb_kill_urb(port->read_urb);
+	usb_kill_urb(port->write_urb);
+
+	if (tty) {
+		c_cflag = tty->termios.c_cflag;
+		if (c_cflag & HUPCL) {
+			/* drop DTR and RTS */
+			spin_lock_irqsave(&priv->lock, flags);
+			priv->line_control = 0;
+			spin_unlock_irqrestore(&priv->lock, flags);
+			set_control_lines(port, port->serial, 0);
+		}
+	}
 }
 
-
-/* open this device, set default parameters */
-static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port)
+static int ch34x_open(struct tty_struct *tty,
+		struct usb_serial_port *port)
 {
+	struct ktermios tmp_termios;
 	struct usb_serial *serial = port->serial;
-	struct ch341_private *priv = usb_get_serial_port_data(port);
-	int r;
+	int retval;
+
+	dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
 
-	r = ch341_configure(serial->dev, priv);
-	if (r)
-		goto out;
+	usb_clear_halt(serial->dev, port->write_urb->pipe);
+	usb_clear_halt(serial->dev, port->read_urb->pipe);
 
 	if (tty)
-		ch341_set_termios(tty, port, NULL);
-
-	dev_dbg(&port->dev, "%s - submitting interrupt urb\n", __func__);
-	r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
-	if (r) {
-		dev_err(&port->dev, "%s - failed to submit interrupt urb: %d\n",
-			__func__, r);
-		goto out;
+		ch34x_set_termios(tty, port, &tmp_termios);
+
+	dev_dbg(&port->dev, "%s - submit read urb", __func__);
+	port->read_urb->dev = serial->dev;
+	retval = usb_submit_urb(port->read_urb, GFP_KERNEL);
+	if (retval) {
+		dev_err(&port->dev, "%s - failed submit read urb,error %d\n",
+				__func__, retval);
+		ch34x_close(port);
+		goto err_out;
 	}
 
-	r = usb_serial_generic_open(tty, port);
+	dev_dbg(&port->dev, "%s - submit interrupt urb", __func__);
+	port->interrupt_in_urb->dev = serial->dev;
+	retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+	if (retval) {
+		dev_err(&port->dev, "%s - failed submit interrupt urb,error %d\n",
+				__func__, retval);
+		ch34x_close(port);
+		goto err_out;
+	}
 
-out:	return r;
+err_out:
+	return retval;
 }
 
-/* Old_termios contains the original termios settings and
- * tty->termios contains the new setting to be used.
- */
-static void ch341_set_termios(struct tty_struct *tty,
-		struct usb_serial_port *port, struct ktermios *old_termios)
+static int ch34x_tiocmset(struct tty_struct *tty,
+		unsigned int set, unsigned int clear)
 {
-	struct ch341_private *priv = usb_get_serial_port_data(port);
-	unsigned baud_rate;
+	struct usb_serial_port *port = tty->driver_data;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
 	unsigned long flags;
-	unsigned char ctrl;
-	int r;
+	u8 control;
 
-	/* redundant changes may cause the chip to lose bytes */
-	if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios))
-		return;
+	dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
 
-	baud_rate = tty_get_baud_rate(tty);
+	if (!usb_get_intfdata(port->serial->interface))
+		return -ENODEV;
 
-	priv->baud_rate = baud_rate;
-	ctrl = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX;
+	spin_lock_irqsave(&priv->lock, flags);
+	if (set & TIOCM_RTS)
+		priv->line_control |= CONTROL_RTS;
+	if (set & TIOCM_DTR)
+		priv->line_control |= CONTROL_DTR;
+	if (clear & TIOCM_RTS)
+		priv->line_control &= ~CONTROL_RTS;
+	if (clear & TIOCM_DTR)
+		priv->line_control &= ~CONTROL_DTR;
+	control = priv->line_control;
+	spin_unlock_irqrestore(&priv->lock, flags);
 
-	switch (C_CSIZE(tty)) {
-	case CS5:
-		ctrl |= CH341_LCR_CS5;
-		break;
-	case CS6:
-		ctrl |= CH341_LCR_CS6;
-		break;
-	case CS7:
-		ctrl |= CH341_LCR_CS7;
-		break;
-	case CS8:
-		ctrl |= CH341_LCR_CS8;
-		break;
-	}
+	return set_control_lines(port, port->serial, control);
+}
 
-	if (C_PARENB(tty)) {
-		ctrl |= CH341_LCR_ENABLE_PAR;
-		if (C_PARODD(tty) == 0)
-			ctrl |= CH341_LCR_PAR_EVEN;
-		if (C_CMSPAR(tty))
-			ctrl |= CH341_LCR_MARK_SPACE;
-	}
+static int wait_modem_info(struct usb_serial_port *port,
+		unsigned int arg)
+{
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
+	unsigned long flags;
+	unsigned int prevstatus;
+	unsigned int status;
+	unsigned int changed;
 
-	if (C_CSTOPB(tty))
-		ctrl |= CH341_LCR_STOP_BITS_2;
+	dev_dbg(&port->dev, "%s -port:%d", __func__, port->port_number);
+
+	spin_lock_irqsave(&priv->lock, flags);
+	prevstatus = priv->line_status;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	while (1) {
+		wait_event_interruptible(wq, wait_flag != 0);
+		wait_flag = 0;
+		/* see if a signal did it */
+		if (signal_pending(current))
+			return -ERESTARTSYS;
 
-	if (baud_rate) {
-		spin_lock_irqsave(&priv->lock, flags);
-		priv->line_control |= (CH341_BIT_DTR | CH341_BIT_RTS);
-		spin_unlock_irqrestore(&priv->lock, flags);
-		r = ch341_init_set_baudrate(port->serial->dev, priv, ctrl);
-		if (r < 0 && old_termios) {
-			priv->baud_rate = tty_termios_baud_rate(old_termios);
-			tty_termios_copy_hw(&tty->termios, old_termios);
-		}
-	} else {
 		spin_lock_irqsave(&priv->lock, flags);
-		priv->line_control &= ~(CH341_BIT_DTR | CH341_BIT_RTS);
+		status = priv->line_status;
 		spin_unlock_irqrestore(&priv->lock, flags);
-	}
 
-	ch341_set_handshake(port->serial->dev, priv->line_control);
+		changed = prevstatus ^ status;
+
+		if (((arg & TIOCM_RNG) && (changed & UART_RING)) ||
+			((arg & TIOCM_DSR) && (changed & UART_DSR))  ||
+			((arg & TIOCM_CD)  && (changed & UART_DCD))  ||
+			((arg & TIOCM_CTS) && (changed & UART_CTS)))
+			return 0;
 
+		prevstatus = status;
+	}
 }
 
-static void ch341_break_ctl(struct tty_struct *tty, int break_state)
+static int ch34x_ioctl(struct tty_struct *tty,
+		unsigned int cmd, unsigned long arg)
 {
-	const uint16_t ch341_break_reg =
-			((uint16_t) CH341_REG_LCR << 8) | CH341_REG_BREAK;
 	struct usb_serial_port *port = tty->driver_data;
-	int r;
-	uint16_t reg_contents;
-	uint8_t *break_reg;
 
-	break_reg = kmalloc(2, GFP_KERNEL);
-	if (!break_reg)
+	dev_dbg(&port->dev, "%s - port:%d, cmd=0x%04x",
+		__func__, port->port_number, cmd);
+
+	switch (cmd) {
+	case TIOCMIWAIT:
+		dev_dbg(&port->dev, "%s - port:%d TIOCMIWAIT",
+			__func__, port->port_number);
+		return wait_modem_info(port, arg);
+	default:
+		dev_dbg(&port->dev, "%s not supported=0x%04x", __func__, cmd);
+		break;
+	}
+	return -ENOIOCTLCMD;
+}
+
+static void ch34x_send(struct usb_serial_port *port)
+{
+	int count;
+	int retval;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
+	unsigned long flags;
+
+	dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
+
+	spin_lock_irqsave(&priv->lock, flags);
+	if (priv->write_urb_in_use) {
+		spin_unlock_irqrestore(&priv->lock, flags);
 		return;
+	}
 
-	r = ch341_control_in(port->serial->dev, CH341_REQ_READ_REG,
-			ch341_break_reg, 0, break_reg, 2);
-	if (r < 0) {
-		dev_err(&port->dev, "%s - USB control read error (%d)\n",
-				__func__, r);
-		goto out;
+	count = ch34x_buf_get(priv->buf, port->write_urb->transfer_buffer,
+			port->bulk_out_size);
+	if (count == 0) {
+		spin_unlock_irqrestore(&priv->lock, flags);
+		return;
 	}
-	dev_dbg(&port->dev, "%s - initial ch341 break register contents - reg1: %x, reg2: %x\n",
-		__func__, break_reg[0], break_reg[1]);
-	if (break_state != 0) {
-		dev_dbg(&port->dev, "%s - Enter break state requested\n", __func__);
-		break_reg[0] &= ~CH341_NBREAK_BITS;
-		break_reg[1] &= ~CH341_LCR_ENABLE_TX;
-	} else {
-		dev_dbg(&port->dev, "%s - Leave break state requested\n", __func__);
-		break_reg[0] |= CH341_NBREAK_BITS;
-		break_reg[1] |= CH341_LCR_ENABLE_TX;
+
+	priv->write_urb_in_use = 1;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	usb_serial_debug_data(&port->dev, __func__, count,
+			port->write_urb->transfer_buffer);
+
+	port->write_urb->transfer_buffer_length = count;
+	port->write_urb->dev = port->serial->dev;
+	retval = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+	if (retval) {
+		dev_err(&port->dev, "%s - failed submitting write urb,error %d\n"
+				, __func__, retval);
+		priv->write_urb_in_use = 0;
 	}
-	dev_dbg(&port->dev, "%s - New ch341 break register contents - reg1: %x, reg2: %x\n",
-		__func__, break_reg[0], break_reg[1]);
-	reg_contents = get_unaligned_le16(break_reg);
-	r = ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG,
-			ch341_break_reg, reg_contents);
-	if (r < 0)
-		dev_err(&port->dev, "%s - USB control write error (%d)\n",
-				__func__, r);
-out:
-	kfree(break_reg);
+
+	usb_serial_port_softint(port);
 }
 
-static int ch341_tiocmset(struct tty_struct *tty,
-			  unsigned int set, unsigned int clear)
+static int ch34x_write(struct tty_struct *tty,
+		struct usb_serial_port *port,
+		const unsigned char *buf,
+		int count)
 {
-	struct usb_serial_port *port = tty->driver_data;
-	struct ch341_private *priv = usb_get_serial_port_data(port);
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
 	unsigned long flags;
-	u8 control;
+
+	dev_dbg(&port->dev, "%s - port:%d, %d bytes",
+		__func__, port->port_number, count);
+
+	if (!count)
+		return count;
 
 	spin_lock_irqsave(&priv->lock, flags);
-	if (set & TIOCM_RTS)
-		priv->line_control |= CH341_BIT_RTS;
-	if (set & TIOCM_DTR)
-		priv->line_control |= CH341_BIT_DTR;
-	if (clear & TIOCM_RTS)
-		priv->line_control &= ~CH341_BIT_RTS;
-	if (clear & TIOCM_DTR)
-		priv->line_control &= ~CH341_BIT_DTR;
-	control = priv->line_control;
+	count = ch34x_buf_put(priv->buf, buf, count);
 	spin_unlock_irqrestore(&priv->lock, flags);
 
-	return ch341_set_handshake(port->serial->dev, control);
+	ch34x_send(port);
+
+	return count;
 }
 
-static void ch341_update_line_status(struct usb_serial_port *port,
-					unsigned char *data, size_t len)
+static int ch34x_write_room(struct tty_struct *tty)
 {
-	struct ch341_private *priv = usb_get_serial_port_data(port);
-	struct tty_struct *tty;
+	struct usb_serial_port *port = tty->driver_data;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
+	int room = 0;
 	unsigned long flags;
-	u8 status;
-	u8 delta;
 
-	if (len < 4)
-		return;
+	dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
 
-	status = ~data[2] & CH341_BITS_MODEM_STAT;
+	spin_lock_irqsave(&priv->lock, flags);
+	room = ch34x_buf_space_avail(priv->buf);
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	dev_dbg(&port->dev, "%s - room:%d", __func__, room);
+	return room;
+}
+
+static int ch34x_chars_in_buffer(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
+	int chars = 0;
+	unsigned long flags;
+
+	dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
 
 	spin_lock_irqsave(&priv->lock, flags);
-	delta = status ^ priv->line_status;
-	priv->line_status = status;
+	chars = ch34x_buf_data_avail(priv->buf);
 	spin_unlock_irqrestore(&priv->lock, flags);
 
-	if (data[1] & CH341_MULT_STAT)
-		dev_dbg(&port->dev, "%s - multiple status change\n", __func__);
+	dev_dbg(&port->dev, "%s - chars:%d", __func__, chars);
 
-	if (!delta)
-		return;
+	return chars;
+}
 
-	if (delta & CH341_BIT_CTS)
-		port->icount.cts++;
-	if (delta & CH341_BIT_DSR)
-		port->icount.dsr++;
-	if (delta & CH341_BIT_RI)
-		port->icount.rng++;
-	if (delta & CH341_BIT_DCD) {
-		port->icount.dcd++;
-		tty = tty_port_tty_get(&port->port);
-		if (tty) {
-			usb_serial_handle_dcd_change(port, tty,
-						status & CH341_BIT_DCD);
-			tty_kref_put(tty);
+static int ch34x_attach(struct usb_serial *serial)
+{
+	struct ch34x_private *priv;
+	int i;
+	char buf[8];
+
+	dev_dbg(&serial->interface->dev, "%s", __func__);
+
+	for (i = 0; i < serial->num_ports; ++i) {
+		priv = kzalloc(sizeof(struct ch34x_private), GFP_KERNEL);
+		if (!priv)
+			goto cleanup;
+		spin_lock_init(&priv->lock);
+		priv->buf = ch34x_buf_alloc(CH34x_BUF_SIZE);
+		if (priv->buf == NULL) {
+			kfree(priv);
+			goto cleanup;
 		}
+		init_waitqueue_head(&priv->delta_msr_wait);
+		usb_set_serial_port_data(serial->port[i], priv);
+	}
+
+	ch34x_vendor_read(VENDOR_VERSION, 0x0000, 0x0000,
+			serial, buf, 0x02);
+	ch34x_vendor_write(VENDOR_SERIAL_INIT, 0x0000, 0x0000,
+			serial, NULL, 0x00);
+	ch34x_vendor_write(VENDOR_WRITE, 0x1312, 0xD982,
+			serial, NULL, 0x00);
+	ch34x_vendor_write(VENDOR_WRITE, 0x0F2C, 0x0004,
+			serial, NULL, 0x00);
+	ch34x_vendor_read(VENDOR_READ, 0x2518, 0x0000,
+			serial, buf, 0x02);
+	ch34x_vendor_write(VENDOR_WRITE, 0x2727, 0x0000,
+			serial, NULL, 0x00);
+	ch34x_vendor_write(VENDOR_MODEM_OUT, 0x009F, 0x0000,
+			serial, NULL, 0x00);
+
+	return 0;
+
+cleanup:
+	for (--i; i >= 0; --i) {
+		priv = usb_get_serial_port_data(serial->port[i]);
+		ch34x_buf_free(priv->buf);
+		kfree(priv);
+		usb_set_serial_port_data(serial->port[i], NULL);
 	}
 
-	wake_up_interruptible(&port->port.delta_msr_wait);
+	return -ENOMEM;
 }
 
-static void ch341_read_int_callback(struct urb *urb)
+static void ch34x_update_line_status(struct usb_serial_port *port,
+		unsigned char *data, unsigned int actual_length)
 {
-	struct usb_serial_port *port = urb->context;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
+	unsigned long flags;
+	u8 length = UART_STATE + 0x04;
+
+	if (actual_length < length)
+		return;
+
+	/* Save off the uart status for others to look up */
+	spin_lock_irqsave(&priv->lock, flags);
+	priv->line_status = data[UART_STATE];
+	priv->line_control = data[PORTB_STATE];
+	spin_unlock_irqrestore(&priv->lock, flags);
+	wait_flag = 1;
+	wake_up_interruptible(&priv->delta_msr_wait);
+}
+
+static void ch34x_read_int_callback(struct urb *urb)
+{
+	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
 	unsigned char *data = urb->transfer_buffer;
-	unsigned int len = urb->actual_length;
-	int status;
+	unsigned int actual_length = urb->actual_length;
+	int status = urb->status;
+	int retval;
+
+	dev_dbg(&port->dev, "%s port:%d", __func__, port->port_number);
 
-	switch (urb->status) {
+	switch (status) {
+	/* success */
 	case 0:
-		/* success */
 		break;
 	case -ECONNRESET:
 	case -ENOENT:
+	/* this urb is terminated, clean up */
 	case -ESHUTDOWN:
-		/* this urb is terminated, clean up */
-		dev_dbg(&urb->dev->dev, "%s - urb shutting down: %d\n",
-			__func__, urb->status);
+		dev_dbg(&port->dev, "%s - urb shutting down with status:%d",
+			__func__, status);
 		return;
 	default:
-		dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n",
-			__func__, urb->status);
+		dev_dbg(&port->dev, "%s - nonzero urb status received:%d",
+			__func__, status);
 		goto exit;
 	}
 
-	usb_serial_debug_data(&port->dev, __func__, len, data);
-	ch341_update_line_status(port, data, len);
+	usb_serial_debug_data(&port->dev, __func__,
+			urb->actual_length, urb->transfer_buffer);
+	ch34x_update_line_status(port, data, actual_length);
+
 exit:
-	status = usb_submit_urb(urb, GFP_ATOMIC);
-	if (status) {
-		dev_err(&urb->dev->dev, "%s - usb_submit_urb failed: %d\n",
-			__func__, status);
-	}
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval)
+		dev_err(&urb->dev->dev, "%s - usb_submit_urb failed with result %d\n",
+			__func__, retval);
 }
 
-static int ch341_tiocmget(struct tty_struct *tty)
+static void ch34x_read_bulk_callback(struct urb *urb)
 {
-	struct usb_serial_port *port = tty->driver_data;
-	struct ch341_private *priv = usb_get_serial_port_data(port);
+	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
+	struct tty_struct *tty;
+	unsigned char *data = urb->transfer_buffer;
 	unsigned long flags;
-	u8 mcr;
-	u8 status;
-	unsigned int result;
+	int i;
+	int retval;
+	int status = urb->status;
+	u8 line_status;
+	char tty_flag;
+
+	dev_dbg(&urb->dev->dev, "%s - port:%d", __func__, port->port_number);
+
+	if (status) {
+		dev_dbg(&urb->dev->dev, "%s - urb status=%d", __func__, status);
+		if (status == -EPROTO) {
+			/*
+			 * CH34x mysteriously fails with -EPROTO
+			 * reschedule the read
+			 */
+			dev_err(&urb->dev->dev, "%s - caught -EPROTO, \
+				resubmitting the urb", __func__);
+			urb->dev = port->serial->dev;
+			retval = usb_submit_urb(urb, GFP_ATOMIC);
+			if (retval) {
+				dev_err(&urb->dev->dev,
+						"%s - failed resubmitting read urb, error %d\n",
+						__func__, retval);
+				return;
+			}
+		}
+
+		dev_dbg(&urb->dev->dev, "%s - unable to \
+			handle the error", __func__);
+		return;
+	}
+
+	usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
+
+	/* get tty_flag from status */
+	tty_flag = TTY_NORMAL;
 
 	spin_lock_irqsave(&priv->lock, flags);
-	mcr = priv->line_control;
-	status = priv->line_status;
+	line_status = priv->line_status;
+	priv->line_status &= ~UART_STATE_TRANSIENT_MASK;
 	spin_unlock_irqrestore(&priv->lock, flags);
+	wait_flag = 1;
+	wake_up_interruptible(&priv->delta_msr_wait);
+
+	/*
+	 * break takes precedence over parity,
+	 * which takes precedence over framing errors.
+	 */
+	if (line_status & UART_PARITY_ERROR)
+		tty_flag = TTY_PARITY;
+	else if (line_status & UART_OVERRUN_ERROR)
+		tty_flag = TTY_OVERRUN;
+	else if (line_status & UART_FRAME_ERROR)
+		tty_flag = TTY_FRAME;
+	dev_dbg(&urb->dev->dev, "%s - tty_flag=%d", __func__, tty_flag);
+
+	tty = port->port.tty;
+
+	if (tty && urb->actual_length) {
+		tty_buffer_request_room(tty->port, urb->actual_length + 1);
+
+		/* overrun is special, not associated with a char */
+		if (line_status & UART_OVERRUN_ERROR)
+			tty_insert_flip_char(tty->port, 0, TTY_OVERRUN);
+
+		for (i = 0; i < urb->actual_length; ++i)
+			tty_insert_flip_char(tty->port, data[i], tty_flag);
+
+		tty_flip_buffer_push(tty->port);
+	}
 
-	result = ((mcr & CH341_BIT_DTR)		? TIOCM_DTR : 0)
-		  | ((mcr & CH341_BIT_RTS)	? TIOCM_RTS : 0)
-		  | ((status & CH341_BIT_CTS)	? TIOCM_CTS : 0)
-		  | ((status & CH341_BIT_DSR)	? TIOCM_DSR : 0)
-		  | ((status & CH341_BIT_RI)	? TIOCM_RI  : 0)
-		  | ((status & CH341_BIT_DCD)	? TIOCM_CD  : 0);
-
-	dev_dbg(&port->dev, "%s - result = %x\n", __func__, result);
+	/* schedule the next read _if_ we are still open */
 
-	return result;
+	urb->dev = port->serial->dev;
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval)
+		dev_err(&urb->dev->dev,
+				"%s - fialed resubmitting read urb, error %d\n",
+				__func__, retval);
 }
 
-static int ch341_reset_resume(struct usb_serial *serial)
+static void ch34x_write_bulk_callback(struct urb *urb)
 {
-	struct ch341_private *priv;
+	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
+	struct ch34x_private *priv = usb_get_serial_port_data(port);
+	int retval;
+	int status = urb->status;
 
-	priv = usb_get_serial_port_data(serial->port[0]);
+	dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
 
-	/* reconfigure ch341 serial port after bus-reset */
-	ch341_configure(serial->dev, priv);
+	switch (status) {
+	/* success */
+	case 0:
+		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		/* this urb is terminated, clean up */
+		dev_dbg(&port->dev, "%s - urb shutting \
+			down with status:%d", __func__, status);
+		priv->write_urb_in_use = 0;
+		return;
+	default:
+		/* error in the urb, so we have to resubmit it */
+		dev_dbg(&port->dev, "%s - Overflow in write", __func__);
+		dev_dbg(&port->dev, "%s - nonzero write bulk \
+			status received:%d", __func__, status);
+		port->write_urb->transfer_buffer_length = 1;
+		port->write_urb->dev = port->serial->dev;
+		retval = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+		if (retval)
+			dev_err(&urb->dev->dev,
+					"%s - failed resubmitting write urv, error:%d\n",
+					__func__, retval);
+		else
+			return;
+	}
+	priv->write_urb_in_use = 0;
 
-	return 0;
+	/* send any buffered data */
+	ch34x_send(port);
 }
 
-static struct usb_serial_driver ch341_device = {
+static struct usb_serial_driver	ch34x_device = {
 	.driver = {
 		.owner	= THIS_MODULE,
-		.name	= "ch341-uart",
+		.name	= "ch34x",
 	},
-	.id_table          = id_table,
-	.num_ports         = 1,
-	.open              = ch341_open,
-	.dtr_rts	   = ch341_dtr_rts,
-	.carrier_raised	   = ch341_carrier_raised,
-	.close             = ch341_close,
-	.set_termios       = ch341_set_termios,
-	.break_ctl         = ch341_break_ctl,
-	.tiocmget          = ch341_tiocmget,
-	.tiocmset          = ch341_tiocmset,
-	.tiocmiwait        = usb_serial_generic_tiocmiwait,
-	.read_int_callback = ch341_read_int_callback,
-	.port_probe        = ch341_port_probe,
-	.port_remove       = ch341_port_remove,
-	.reset_resume      = ch341_reset_resume,
+	.id_table = id_table,
+	.num_ports		= 1,
+	.open			= ch34x_open,
+	.close			= ch34x_close,
+	.write			= ch34x_write,
+	.ioctl			= ch34x_ioctl,
+	.set_termios		= ch34x_set_termios,
+	.tiocmget		= ch34x_tiocmget,
+	.tiocmset		= ch34x_tiocmset,
+	.read_bulk_callback     = ch34x_read_bulk_callback,
+	.read_int_callback		= ch34x_read_int_callback,
+	.write_bulk_callback	= ch34x_write_bulk_callback,
+	.write_room		= ch34x_write_room,
+	.chars_in_buffer	= ch34x_chars_in_buffer,
+	.attach			= ch34x_attach,
 };
 
-static struct usb_serial_driver * const serial_drivers[] = {
-	&ch341_device, NULL
+static struct usb_serial_driver *const serial_driver[] = {
+	&ch34x_device, NULL
 };
 
-module_usb_serial_driver(serial_drivers, id_table);
+static int __init ch34x_init(void)
+{
+	return usb_serial_register_drivers(serial_driver,
+			KBUILD_MODNAME, id_table);
+}
 
+static void __exit ch34x_exit(void)
+{
+	usb_serial_deregister_drivers(serial_driver);
+}
+
+module_init(ch34x_init);
+module_exit(ch34x_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR(DRIVER_AUTHOR);
 MODULE_LICENSE("GPL");
+
-- 
2.10.2


[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]