[PATCH] usb: serial: update CH34x driver in drivers/usb/serial

From: tech
Date: Thu Jun 23 2016 - 21:54:58 EST


The old driver for usb-serial chips ch34x which submitted by kernel volunteer
Frank A Kingswood <frank@xxxxxxxxxxxxxxxxxxxxxxxxxx> is too old to use, and the
driver has bugs when receiving chracters, thus after communicating with the author
we decide to revoke the old driver ch341.c and submit the new one ch34x.c.

Add the relevant details to Documentation/usb/usb-serial.txt.
Change the relevant files Kconfig and Makefile in drivers/usb/serial.

Signed-off-by: WCH Tech Group <tech@xxxxxxxxxxxxxxx>

---
Documentation/usb/usb-serial.txt | 27 +-
drivers/usb/serial/Kconfig | 8 +-
drivers/usb/serial/Makefile | 2 +-
drivers/usb/serial/ch341.c | 580 -----------------------
drivers/usb/serial/ch34x.c | 985 +++++++++++++++++++++++++++++++++++++++
5 files changed, 1008 insertions(+), 594 deletions(-)
delete mode 100644 drivers/usb/serial/ch341.c
create mode 100644 drivers/usb/serial/ch34x.c

diff --git a/Documentation/usb/usb-serial.txt b/Documentation/usb/usb-serial.txt
index 349f310..c21437b 100644
--- a/Documentation/usb/usb-serial.txt
+++ b/Documentation/usb/usb-serial.txt
@@ -1,4 +1,4 @@
-INTRODUCTION
+INTRODUCTION

The USB serial driver currently supports a number of different USB to
serial converter products, as well as some devices that use a serial
@@ -430,15 +430,24 @@ Options supported:
See http://www.uuhaus.de/linux/palmconnect.html for up-to-date
information on this driver.

-Winchiphead CH341 Driver
-
- This driver is for the Winchiphead CH341 USB-RS232 Converter. This chip
- also implements an IEEE 1284 parallel port, I2C and SPI, but that is not
- supported by the driver. The protocol was analyzed from the behaviour
- of the Windows driver, no datasheet is available at present.
- The manufacturer's website: http://www.winchiphead.com/.
+WCH CH34x Driver
+
+ CH340 and CH341 are USB bus convert chips, CH340 can realize USB convert to
+ serial interface, IrDA infrared and printer interface.
+ CH341 can realize USB convert to serial interface, printer interface, parallel
+ port interface which suppots EPP and MEM, I2C and SPI.
+ But this driver only supports USB-SERIAL function, the manufacturer's website:
+ http://www.wch.cn and http://www.winchiphead.com.
+ You can visit it for CH34x datasheets and more drivers on the chips.
+ In serial interface mode, CH340 and CH341 supply common MODEM liaison signal,
+ used to enlarge asynchronous serial interface of computer or upgrade the common
+ serial device to USB bus directly.
For any questions or problems with this driver, please contact
- frank@xxxxxxxxxxxxxxxxxxxxxxxxxxx
+ WCH Tech Group<tech@xxxxxxxxxxxxxxx>.
+
+ TODO:
+ - support more features
+ - checkpatch.pl fixes

Moschip MCS7720, MCS7715 driver

diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
index 56ecb8b..b98041f 100644
--- a/drivers/usb/serial/Kconfig
+++ b/drivers/usb/serial/Kconfig
@@ -99,14 +99,14 @@ config USB_SERIAL_BELKIN
To compile this driver as a module, choose M here: the
module will be called belkin_sa.

-config USB_SERIAL_CH341
- tristate "USB Winchiphead CH341 Single Port Serial Driver"
+config USB_SERIAL_CH34x
+ tristate "USB Winchiphead CH34x Single Port Serial Driver"
help
- Say Y here if you want to use a Winchiphead CH341 single port
+ Say Y here if you want to use a Winchiphead CH34x single port
USB to serial adapter.

To compile this driver as a module, choose M here: the
- module will be called ch341.
+ module will be called ch34x.

config USB_SERIAL_WHITEHEAT
tristate "USB ConnectTech WhiteHEAT Serial Driver"
diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile
index 349d9df..51095e7 100644
--- a/drivers/usb/serial/Makefile
+++ b/drivers/usb/serial/Makefile
@@ -13,7 +13,7 @@ usbserial-$(CONFIG_USB_SERIAL_CONSOLE) += console.o
obj-$(CONFIG_USB_SERIAL_AIRCABLE) += aircable.o
obj-$(CONFIG_USB_SERIAL_ARK3116) += ark3116.o
obj-$(CONFIG_USB_SERIAL_BELKIN) += belkin_sa.o
-obj-$(CONFIG_USB_SERIAL_CH341) += ch341.o
+obj-$(CONFIG_USB_SERIAL_CH34x) += ch34x.o
obj-$(CONFIG_USB_SERIAL_CP210X) += cp210x.o
obj-$(CONFIG_USB_SERIAL_CYBERJACK) += cyberjack.o
obj-$(CONFIG_USB_SERIAL_CYPRESS_M8) += cypress_m8.o
diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c
deleted file mode 100644
index f139488..0000000
--- a/drivers/usb/serial/ch341.c
+++ /dev/null
@@ -1,580 +0,0 @@
-/*
- * Copyright 2007, Frank A Kingswood <frank@xxxxxxxxxxxxxxxxxxxxxxxxxx>
- * Copyright 2007, Werner Cornelius <werner@xxxxxxxxxxxxxxxxxxxx>
- * Copyright 2009, Boris Hajduk <boris@xxxxxxxxxx>
- *
- * ch341.c implements a serial port driver for the Winchiphead CH341.
- *
- * The CH341 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.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version
- * 2 as published by the Free Software Foundation.
- */
-
-#include <linux/kernel.h>
-#include <linux/tty.h>
-#include <linux/module.h>
-#include <linux/slab.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_WRITE_REG 0x9A
-#define CH341_REQ_READ_REG 0x95
-#define CH341_REG_BREAK1 0x05
-#define CH341_REG_BREAK2 0x18
-#define CH341_NBREAK_BITS_REG1 0x01
-#define CH341_NBREAK_BITS_REG2 0x40
-
-
-static const struct usb_device_id id_table[] = {
- { USB_DEVICE(0x4348, 0x5523) },
- { USB_DEVICE(0x1a86, 0x7523) },
- { USB_DEVICE(0x1a86, 0x5523) },
- { },
-};
-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 void ch341_set_termios(struct tty_struct *tty,
- struct usb_serial_port *port,
- struct ktermios *old_termios);
-
-static int ch341_control_out(struct usb_device *dev, u8 request,
- u16 value, u16 index)
-{
- int r;
-
- dev_dbg(&dev->dev, "ch341_control_out(%02x,%02x,%04x,%04x)\n",
- USB_DIR_OUT|0x40, (int)request, (int)value, (int)index);
-
- 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);
-
- return r;
-}
-
-static int ch341_control_in(struct usb_device *dev,
- u8 request, u16 value, u16 index,
- char *buf, unsigned bufsize)
-{
- int r;
-
- 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);
-
- 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;
-}
-
-static int ch341_set_baudrate(struct usb_device *dev,
- struct ch341_private *priv)
-{
- short a, b;
- int r;
- unsigned long factor;
- short divisor;
-
- if (!priv->baud_rate)
- return -EINVAL;
- factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate);
- divisor = CH341_BAUDBASE_DIVMAX;
-
- while ((factor > 0xfff0) && divisor) {
- factor >>= 3;
- divisor--;
- }
-
- if (factor > 0xfff0)
- return -EINVAL;
-
- factor = 0x10000 - factor;
- a = (factor & 0xff00) | divisor;
- b = factor & 0xff;
-
- r = ch341_control_out(dev, 0x9a, 0x1312, a);
- if (!r)
- r = ch341_control_out(dev, 0x9a, 0x0f2c, b);
-
- return r;
-}
-
-static int ch341_set_handshake(struct usb_device *dev, u8 control)
-{
- return ch341_control_out(dev, 0xa4, ~control, 0);
-}
-
-static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv)
-{
- char *buffer;
- int r;
- const unsigned size = 8;
- unsigned long flags;
-
- buffer = kmalloc(size, GFP_KERNEL);
- if (!buffer)
- return -ENOMEM;
-
- r = ch341_control_in(dev, 0x95, 0x0706, 0, buffer, size);
- if (r < 0)
- goto out;
-
- /* 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;
-
-out: kfree(buffer);
- return r;
-}
-
-/* -------------------------------------------------------------------------- */
-
-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, 0x5f, 0, 0, buffer, size);
- if (r < 0)
- goto out;
-
- r = ch341_control_out(dev, 0xa1, 0, 0);
- if (r < 0)
- goto out;
-
- r = ch341_set_baudrate(dev, priv);
- if (r < 0)
- goto out;
-
- /* expect two bytes 0x56 0x00 */
- r = ch341_control_in(dev, 0x95, 0x2518, 0, buffer, size);
- if (r < 0)
- goto out;
-
- r = ch341_control_out(dev, 0x9a, 0x2518, 0x0050);
- if (r < 0)
- goto out;
-
- /* expect 0xff 0xee */
- r = ch341_get_status(dev, priv);
- if (r < 0)
- goto out;
-
- r = ch341_control_out(dev, 0xa1, 0x501f, 0xd90a);
- if (r < 0)
- goto out;
-
- r = ch341_set_baudrate(dev, priv);
- 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;
-}
-
-static int ch341_port_probe(struct usb_serial_port *port)
-{
- struct ch341_private *priv;
- int r;
-
- priv = kzalloc(sizeof(struct ch341_private), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- spin_lock_init(&priv->lock);
- priv->baud_rate = DEFAULT_BAUD_RATE;
- priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR;
-
- r = ch341_configure(port->serial->dev, priv);
- if (r < 0)
- goto error;
-
- usb_set_serial_port_data(port, priv);
- return 0;
-
-error: kfree(priv);
- return r;
-}
-
-static int ch341_port_remove(struct usb_serial_port *port)
-{
- struct ch341_private *priv;
-
- priv = usb_get_serial_port_data(port);
- kfree(priv);
-
- return 0;
-}
-
-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;
-}
-
-static void ch341_dtr_rts(struct usb_serial_port *port, int on)
-{
- struct ch341_private *priv = usb_get_serial_port_data(port);
- unsigned long flags;
-
- /* drop DTR and RTS */
- 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);
- 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);
- usb_kill_urb(port->interrupt_in_urb);
-}
-
-
-/* open this device, set default parameters */
-static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port)
-{
- struct usb_serial *serial = port->serial;
- struct ch341_private *priv = usb_get_serial_port_data(port);
- int r;
-
- r = ch341_configure(serial->dev, priv);
- if (r)
- goto out;
-
- 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;
- }
-
- r = usb_serial_generic_open(tty, port);
-
-out: return r;
-}
-
-/* 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)
-{
- struct ch341_private *priv = usb_get_serial_port_data(port);
- unsigned baud_rate;
- unsigned long flags;
-
- baud_rate = tty_get_baud_rate(tty);
-
- priv->baud_rate = baud_rate;
-
- if (baud_rate) {
- spin_lock_irqsave(&priv->lock, flags);
- priv->line_control |= (CH341_BIT_DTR | CH341_BIT_RTS);
- spin_unlock_irqrestore(&priv->lock, flags);
- ch341_set_baudrate(port->serial->dev, priv);
- } else {
- spin_lock_irqsave(&priv->lock, flags);
- priv->line_control &= ~(CH341_BIT_DTR | CH341_BIT_RTS);
- spin_unlock_irqrestore(&priv->lock, flags);
- }
-
- ch341_set_handshake(port->serial->dev, priv->line_control);
-
- /* Unimplemented:
- * (cflag & CSIZE) : data bits [5, 8]
- * (cflag & PARENB) : parity {NONE, EVEN, ODD}
- * (cflag & CSTOPB) : stop bits [1, 2]
- */
-}
-
-static void ch341_break_ctl(struct tty_struct *tty, int break_state)
-{
- const uint16_t ch341_break_reg =
- ((uint16_t) CH341_REG_BREAK2 << 8) | CH341_REG_BREAK1;
- 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)
- 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;
- }
- 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_REG1;
- break_reg[1] &= ~CH341_NBREAK_BITS_REG2;
- } else {
- dev_dbg(&port->dev, "%s - Leave break state requested\n", __func__);
- break_reg[0] |= CH341_NBREAK_BITS_REG1;
- break_reg[1] |= CH341_NBREAK_BITS_REG2;
- }
- 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);
-}
-
-static int ch341_tiocmset(struct tty_struct *tty,
- unsigned int set, unsigned int clear)
-{
- struct usb_serial_port *port = tty->driver_data;
- struct ch341_private *priv = usb_get_serial_port_data(port);
- unsigned long flags;
- u8 control;
-
- 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;
- spin_unlock_irqrestore(&priv->lock, flags);
-
- return ch341_set_handshake(port->serial->dev, control);
-}
-
-static void ch341_update_line_status(struct usb_serial_port *port,
- unsigned char *data, size_t len)
-{
- struct ch341_private *priv = usb_get_serial_port_data(port);
- struct tty_struct *tty;
- unsigned long flags;
- u8 status;
- u8 delta;
-
- if (len < 4)
- return;
-
- status = ~data[2] & CH341_BITS_MODEM_STAT;
-
- spin_lock_irqsave(&priv->lock, flags);
- delta = status ^ priv->line_status;
- priv->line_status = status;
- spin_unlock_irqrestore(&priv->lock, flags);
-
- if (data[1] & CH341_MULT_STAT)
- dev_dbg(&port->dev, "%s - multiple status change\n", __func__);
-
- if (!delta)
- return;
-
- 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);
- }
- }
-
- wake_up_interruptible(&port->port.delta_msr_wait);
-}
-
-static void ch341_read_int_callback(struct urb *urb)
-{
- struct usb_serial_port *port = urb->context;
- unsigned char *data = urb->transfer_buffer;
- unsigned int len = urb->actual_length;
- int status;
-
- switch (urb->status) {
- case 0:
- /* success */
- break;
- case -ECONNRESET:
- case -ENOENT:
- case -ESHUTDOWN:
- /* this urb is terminated, clean up */
- dev_dbg(&urb->dev->dev, "%s - urb shutting down: %d\n",
- __func__, urb->status);
- return;
- default:
- dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n",
- __func__, urb->status);
- goto exit;
- }
-
- usb_serial_debug_data(&port->dev, __func__, len, data);
- ch341_update_line_status(port, data, len);
-exit:
- status = usb_submit_urb(urb, GFP_ATOMIC);
- if (status) {
- dev_err(&urb->dev->dev, "%s - usb_submit_urb failed: %d\n",
- __func__, status);
- }
-}
-
-static int ch341_tiocmget(struct tty_struct *tty)
-{
- struct usb_serial_port *port = tty->driver_data;
- struct ch341_private *priv = usb_get_serial_port_data(port);
- unsigned long flags;
- u8 mcr;
- u8 status;
- unsigned int result;
-
- spin_lock_irqsave(&priv->lock, flags);
- mcr = priv->line_control;
- status = priv->line_status;
- spin_unlock_irqrestore(&priv->lock, flags);
-
- 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);
-
- return result;
-}
-
-static int ch341_reset_resume(struct usb_serial *serial)
-{
- struct ch341_private *priv;
-
- priv = usb_get_serial_port_data(serial->port[0]);
-
- /* reconfigure ch341 serial port after bus-reset */
- ch341_configure(serial->dev, priv);
-
- return 0;
-}
-
-static struct usb_serial_driver ch341_device = {
- .driver = {
- .owner = THIS_MODULE,
- .name = "ch341-uart",
- },
- .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,
-};
-
-static struct usb_serial_driver * const serial_drivers[] = {
- &ch341_device, NULL
-};
-
-module_usb_serial_driver(serial_drivers, id_table);
-
-MODULE_LICENSE("GPL");
diff --git a/drivers/usb/serial/ch34x.c b/drivers/usb/serial/ch34x.c
new file mode 100644
index 0000000..b193b78
--- /dev/null
+++ b/drivers/usb/serial/ch34x.c
@@ -0,0 +1,985 @@
+/*
+ * 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>
+ *
+ * ch34x.c implements a serial port driver for the Winchiphead CH340 and
+ * CH341.
+ *
+ * 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.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ */
+
+#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/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+
+#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;
+};
+
+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;
+ }
+
+ return count;
+}
+
+static unsigned int ch34x_buf_get(struct ch34x_buf *pb,
+ char *buf, unsigned int count)
+{
+ 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;
+}
+
+static int ch34x_vendor_read(__u8 request,
+ __u16 value,
+ __u16 index,
+ struct usb_serial *serial,
+ unsigned char *buf,
+ __u16 len)
+{
+ int retval;
+
+ retval = usb_control_msg(serial->dev,
+ usb_rcvctrlpipe(serial->dev, 0),
+ request,
+ VENDOR_READ_TYPE,
+ value, index, buf, len, 1000);
+
+ return retval;
+}
+
+static int ch34x_vendor_write(__u8 request,
+ __u16 value,
+ __u16 index,
+ struct usb_serial *serial,
+ unsigned char *buf,
+ __u16 len)
+{
+ int retval;
+
+ retval = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ request,
+ VENDOR_WRITE_TYPE,
+ value, index, buf, len, 1000);
+
+ return retval;
+}
+
+static int set_control_lines(struct usb_serial_port *port,
+ struct usb_serial *serial, u8 value)
+{
+ 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;
+}
+
+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 = CH34x_BAUDRATE_FACTOR / baud_rate;
+ divisor = CH34x_BAUDRATE_DIVMAX;
+
+ while ((factor > 0xfff0) && divisor) {
+ factor >>= 3;
+ divisor--;
+ }
+
+ if (factor > 0xfff0)
+ return -EINVAL;
+
+ factor = 0x10000 - factor;
+ *a = (factor & 0xff00) >> 8;
+ *b = divisor;
+
+ return 0;
+}
+
+static void ch34x_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port, struct ktermios *old_termios)
+{
+ struct usb_serial *serial = port->serial;
+ struct ch34x_private *priv = usb_get_serial_port_data(port);
+ struct ktermios *termios = &tty->termios;
+
+ unsigned int baud_rate;
+ unsigned int cflag;
+ unsigned long flags;
+ u8 control;
+
+ 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;
+
+ dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
+
+ 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);
+
+ /*
+ * 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;
+
+ dev_dbg(&port->dev, "%s(%d) cflag = 0x%x\n",
+ __func__, port->port_number, cflag);
+
+ /* 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);
+
+ 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__);
+
+ 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);
+
+ 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);
+
+ if (cflag & CRTSCTS)
+ ch34x_vendor_write(VENDOR_WRITE, 0x2727, 0x0101,
+ serial, NULL, 0);
+
+ /* FIXME: Need to read back resulting baud rate */
+ if (baud_rate)
+ tty_encode_baud_rate(tty, baud_rate, baud_rate);
+}
+
+static int ch34x_tiocmget(struct tty_struct *tty)
+{
+ 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;
+
+ dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
+ if (!usb_get_intfdata(port->serial->interface))
+ return -ENODEV;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ mcr = priv->line_control;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ 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 ch34x_close(struct usb_serial_port *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;
+
+ /* clear out any remaining data in the buffer */
+ spin_lock_irqsave(&priv->lock, flags);
+ ch34x_buf_clear(priv->buf);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* 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);
+ }
+ }
+}
+
+static int ch34x_open(struct tty_struct *tty,
+ struct usb_serial_port *port)
+{
+ struct ktermios tmp_termios;
+ struct usb_serial *serial = port->serial;
+ int retval;
+
+ dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
+
+ usb_clear_halt(serial->dev, port->write_urb->pipe);
+ usb_clear_halt(serial->dev, port->read_urb->pipe);
+
+ if (tty)
+ 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;
+ }
+
+ 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;
+ }
+
+err_out:
+ return retval;
+}
+
+static int ch34x_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ch34x_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ u8 control;
+
+ dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
+
+ if (!usb_get_intfdata(port->serial->interface))
+ return -ENODEV;
+
+ 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);
+
+ return set_control_lines(port, port->serial, control);
+}
+
+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;
+
+ 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;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ status = priv->line_status;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ 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 int ch34x_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ usb_serial_port_softint(port);
+}
+
+static int ch34x_write(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const unsigned char *buf,
+ int count)
+{
+ struct ch34x_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ dev_dbg(&port->dev, "%s - port:%d, %d bytes",
+ __func__, port->port_number, count);
+
+ if (!count)
+ return count;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ count = ch34x_buf_put(priv->buf, buf, count);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ ch34x_send(port);
+
+ return count;
+}
+
+static int ch34x_write_room(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;
+
+ dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
+
+ 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);
+ chars = ch34x_buf_data_avail(priv->buf);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_dbg(&port->dev, "%s - chars:%d", __func__, chars);
+
+ return chars;
+}
+
+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);
+ }
+
+ return -ENOMEM;
+}
+
+static void ch34x_update_line_status(struct usb_serial_port *port,
+ unsigned char *data, unsigned int actual_length)
+{
+ 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 actual_length = urb->actual_length;
+ int status = urb->status;
+ int retval;
+
+ dev_dbg(&port->dev, "%s port:%d", __func__, port->port_number);
+
+ switch (status) {
+ /* success */
+ case 0:
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ /* this urb is terminated, clean up */
+ case -ESHUTDOWN:
+ dev_dbg(&port->dev, "%s - urb shutting down with status:%d",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(&port->dev, "%s - nonzero urb status received:%d",
+ __func__, status);
+ goto exit;
+ }
+
+ usb_serial_debug_data(&port->dev, __func__,
+ urb->actual_length, urb->transfer_buffer);
+ ch34x_update_line_status(port, data, actual_length);
+
+exit:
+ 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 void ch34x_read_bulk_callback(struct urb *urb)
+{
+ 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;
+ 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);
+ 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);
+ }
+
+ /* schedule the next read _if_ we are still open */
+
+ 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 void ch34x_write_bulk_callback(struct urb *urb)
+{
+ 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;
+
+ dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number);
+
+ 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;
+
+ /* send any buffered data */
+ ch34x_send(port);
+}
+
+static struct usb_serial_driver ch34x_device = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ch34x",
+ },
+ .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_driver[] = {
+ &ch34x_device, NULL
+};
+
+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.8.4.windows.1