Port

An overview of CH341A devices with driver and tools download links CH341A is an USB interface chip that can emulate UART communication, standard parallel port, memory parallel port and synchronous serial (I2C, SPI). The chip is manufactured by Chinese company Jiangsu QinHeng Ltd. CH341A is used by some cheap memory programmers. The Prolific Winchiphead CH340 / CH341 / HL340 Serial Driver for Apple Mac OS X driver is the reliable interface between your CH340 / CH341 / HL340 based cable and your Mac. The driver supports many CH340 / CH341 / HL340 based dongles and USB interfaces, like clone Arduino’s. The cables provide a fast, simple way to connect devices with a RS232 level serial UART interface to USB. Specifications Winchiphead CH340 / CH341 / HL340 driver The Prolific Winchiphead CH340 / CH341 / HL340 Serial Driver for Apple Mac OS X driver is the reliable interface between your CH340 / CH341 / HL340 based cable and your Mac.

Dec 21, 2020 • Filed to: USB Recovery • Proven solutions

Have you ever had difficulty connecting your devices to your computer using a Prolific USB to Serial adapter? If yes you are lucky to find this page, it will give you all the information need to fix an unresponsive Serial driver adapter. You may have noticed a yellow exclamation mark hovering over or beside the USB to Serial Driver when searching your Device Manager and if you have we can all agree that it signifies a prevailing problem.

Overview of the Error

If you are not able to connect your device to your PC using a USB to Serial adapter it may be that

  • The USB driver is outdated
  • The USB Driver is missing
  • Or even a corrupted driver

While you can't be so sure what the exact problem is there are solutions you can adopt to fix the issue once and for all. Read on to find quick fixes to resolve the problem.

1# Fix Prolific USB to Serial Driver not Working

Solution: Roll your Prolific USB to Serial Driver back to an older version

Updating your drivers often work wonders for your system because it makes sure that it runs on the latest features that guarantee maximum functionality. Unfortunately updating your drivers can cause this kind of error especially if your latest update does not work too well with your hardware. Once you upgrade your PC to the latest Windows version your Prolific USB to Serial Driver also updates to the latest driver software and if the driver is not compatible with your Hardware issues may arise. To correct the error, you will have to download the driver again by following these steps.

  • Right-click on your PC Start button and open Device Manager
  • Double click LPT and COM ports then locate the Prolific USB to Serial Driver and right-click it so you can select Update Driver
  • For driver software click My computer
  • From My computer select 'Let me pick from a list of drivers available'
  • Choose an older version from the list then click Next
  • The driver you selected will be installed automatically
  • Once it has been downloaded reconnect the driver to see if it works.

2# Fix Prolific USB to Serial Driver 'Coder 10'

Winchiphead

Driver issues reveal themselves in so many ways or through messages such as

Winchiphead port devices driver license testWinchiphead Port Devices Driver
  • This device will not start (Code 10)
  • There is no driver installed in this device
  • The driver was not successfully installed

A code 10 error may arise because the chip you are using is not an original one. If you are using a fake, the manufacturer has a way of disabling it due to copy write infringement and the copy write takes effect once you download a recent update. To protect your PC from getting this code, you will have to make sure that your Windows 10 never updates a driver without approval. Updates come in the form of 64 bit and 32-bit drivers. Below we will show you how to work your way around the problem.

Window 64 bit Fix

To fix Windows 64 bit OS including Prolific USB to Serial Adapter, follow through these steps.

  • Download 'PL2303_64bit_Installer.exe and Save
  • Remove every USB to Serial adapter from your PC and double click the 'PL2303_64bit_installer.exe
  • Follow the prompt by plugging in one USB to Serial adapter the click on Continue
  • Reboot your PC to get Windows up and running

If you have followed these process through and you still see the same error go to your device manager

  • Open the control panel and navigate to Device Manager
  • From your control panel navigate to the System category and click the Hardware tab
  • Scroll to the LPT/COM port and double click Prolific USB to Serial Comm Port
  • Click Drivers in the Properties section
  • The Driver you choose should read '3.3.2.102 with an appropriate date attached
  • If the date is wrong then it means the wrong driver was installed
  • Unplug the Serial adapter and run the steps again to install the correct driver

Winchiphead Port Devices Driver License Test

Window 32-bit

For Windows 32-bit OS systems follow these steps

  • You will have to download the 'PL-2303_Driver_Installer.exe and save from Microsoft download link
  • Run the program and eradicate the driver if you can
  • Run the installer once more to install a correct driver

Winchiphead Port Devices Driver Device

To Troubleshoot for error if your device still does not work

  • Go to Control panel, enter System to access your Device Manager
  • Scroll down to the LPT/ COM port then double click Prolific USB to Serial Comm Port
  • Click Driver in the Windows Properties section
  • The driver must be numbered as '2.0.2.8' and dates accordingly

If the driver was not installed, remove the Serial adapter then run 'PL2303_Driver_Installer.exe' again. Follow directions in Device Manager to download the correct driver

The three solutions listed in this article have proven to be quite helpful in fixing a Prolific USB to Serial driver not working on Windows 10. They may appear confusing at first but if you follow each step in detail you can resolve the issue.

Winchiphead Port Devices Driver

Video Tutorial on How to Fix USB Device Not Recognized

Winchiphead Port Devices Driver Updater

USB Solution

Winchiphead Port Devices Drivers

Recover USB Drive
Format USB Drive
Fix/Repair USB Drive
Know USB Drive
The old driver for usb-serial chips ch34x which submitted by kernel volunteer
Frank A Kingswood <[email protected]> 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 <[email protected]>
---
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
- [email protected]
+ WCH Tech Group<[email protected]>.
+
+ 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 <[email protected]>
- * Copyright 2007, Werner Cornelius <[email protected]>
- * Copyright 2009, Boris Hajduk <[email protected]>
- *
- * 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 urbn', __func__);
- r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
- if (r) {
- dev_err(&port->dev, '%s - failed to submit interrupt urb: %dn',
- __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: %xn',
- __func__, break_reg[0], break_reg[1]);
- if (break_state != 0) {
- dev_dbg(&port->dev, '%s - Enter break state requestedn', __func__);
- break_reg[0] &= ~CH341_NBREAK_BITS_REG1;
- break_reg[1] &= ~CH341_NBREAK_BITS_REG2;
- } else {
- dev_dbg(&port->dev, '%s - Leave break state requestedn', __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: %xn',
- __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 changen', __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: %dn',
- __func__, urb->status);
- return;
- default:
- dev_dbg(&urb->dev->dev, '%s - nonzero urb status: %dn',
- __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: %dn',
- __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 = %xn', __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 <[email protected]>
+ * Copyright 2007, Werner Cornelius <[email protected]>
+ * Copyright 2009, Boris Hajduk <[email protected]>
+ * Copyright 2016, WCH Tech Group <[email protected]>
+ *
+ * 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 <[email protected]>'
+
+#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%xn',
+ __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 %dn',
+ __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 %dn',
+ __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 %dn'
+ , __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 %dn',
+ __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 %dn',
+ __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 %dn',
+ __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:%dn',
+ __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