From 6d2bca3cda09fca9c0a2a2c84dbf64c6090b3da5 Mon Sep 17 00:00:00 2001
From: Johan Hovold <johan@kernel.org>
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@kingswood-consulting.co.uk>
  * Copyright 2007, Werner Cornelius <werner@cornelius-consult.de>
  * Copyright 2009, Boris Hajduk <boris@hajduk.org>
+ * Copyright 2016, WCH Tech Group <tech@winchiphead.com>
  *
- * 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@winchiphead.com>"
+
+#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

