summaryrefslogtreecommitdiff
path: root/drivers/usb/serial/ch341.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/serial/ch341.c')
-rw-r--r--drivers/usb/serial/ch341.c99
1 files changed, 74 insertions, 25 deletions
diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c
index 29f4b87a9e74..7cc36f84821f 100644
--- a/drivers/usb/serial/ch341.c
+++ b/drivers/usb/serial/ch341.c
@@ -19,7 +19,7 @@
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/serial.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#define DEFAULT_BAUD_RATE 9600
#define DEFAULT_TIMEOUT 1000
@@ -63,6 +63,7 @@
#define CH341_REG_DIVISOR 0x13
#define CH341_REG_LCR 0x18
#define CH341_REG_LCR2 0x25
+#define CH341_REG_FLOW_CTL 0x27
#define CH341_NBREAK_BITS 0x01
@@ -77,14 +78,17 @@
#define CH341_LCR_CS6 0x01
#define CH341_LCR_CS5 0x00
+#define CH341_FLOW_CTL_NONE 0x00
+#define CH341_FLOW_CTL_RTSCTS 0x01
+
#define CH341_QUIRK_LIMITED_PRESCALER BIT(0)
#define CH341_QUIRK_SIMULATE_BREAK BIT(1)
static const struct usb_device_id id_table[] = {
- { USB_DEVICE(0x1a86, 0x5512) },
{ USB_DEVICE(0x1a86, 0x5523) },
{ USB_DEVICE(0x1a86, 0x7522) },
{ USB_DEVICE(0x1a86, 0x7523) },
+ { USB_DEVICE(0x2184, 0x0057) },
{ USB_DEVICE(0x4348, 0x5523) },
{ USB_DEVICE(0x9986, 0x7523) },
{ },
@@ -97,13 +101,16 @@ struct ch341_private {
u8 mcr;
u8 msr;
u8 lcr;
+
unsigned long quirks;
+ u8 version;
+
unsigned long break_end;
};
static void ch341_set_termios(struct tty_struct *tty,
struct usb_serial_port *port,
- struct ktermios *old_termios);
+ const struct ktermios *old_termios);
static int ch341_control_out(struct usb_device *dev, u8 request,
u16 value, u16 index)
@@ -250,8 +257,12 @@ static int ch341_set_baudrate_lcr(struct usb_device *dev,
/*
* CH341A buffers data until a full endpoint-size packet (32 bytes)
* has been received unless bit 7 is set.
+ *
+ * At least one device with version 0x27 appears to have this bit
+ * inverted.
*/
- val |= BIT(7);
+ if (priv->version > 0x27)
+ val |= BIT(7);
r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
CH341_REG_DIVISOR << 8 | CH341_REG_PRESCALER,
@@ -265,6 +276,9 @@ static int ch341_set_baudrate_lcr(struct usb_device *dev,
* (stop bits, parity and word length). Version 0x30 and above use
* CH341_REG_LCR only and CH341_REG_LCR2 is always set to zero.
*/
+ if (priv->version < 0x30)
+ return 0;
+
r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
CH341_REG_LCR2 << 8 | CH341_REG_LCR, lcr);
if (r)
@@ -308,7 +322,9 @@ static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
r = ch341_control_in(dev, CH341_REQ_READ_VERSION, 0, 0, buffer, size);
if (r)
return r;
- dev_dbg(&dev->dev, "Chip version: 0x%02x\n", buffer[0]);
+
+ priv->version = buffer[0];
+ dev_dbg(&dev->dev, "Chip version: 0x%02x\n", priv->version);
r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0);
if (r < 0)
@@ -466,11 +482,34 @@ err_kill_interrupt_urb:
return r;
}
+static void ch341_set_flow_control(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ const struct ktermios *old_termios)
+{
+ u16 flow_ctl;
+ int r;
+
+ if (C_CRTSCTS(tty))
+ flow_ctl = CH341_FLOW_CTL_RTSCTS;
+ else
+ flow_ctl = CH341_FLOW_CTL_NONE;
+
+ r = ch341_control_out(port->serial->dev,
+ CH341_REQ_WRITE_REG,
+ (CH341_REG_FLOW_CTL << 8) | CH341_REG_FLOW_CTL,
+ (flow_ctl << 8) | flow_ctl);
+ if (r < 0 && old_termios) {
+ tty->termios.c_cflag &= ~CRTSCTS;
+ tty->termios.c_cflag |= (old_termios->c_cflag & CRTSCTS);
+ }
+}
+
/* 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 usb_serial_port *port,
+ const struct ktermios *old_termios)
{
struct ch341_private *priv = usb_get_serial_port_data(port);
unsigned baud_rate;
@@ -533,6 +572,8 @@ static void ch341_set_termios(struct tty_struct *tty,
spin_unlock_irqrestore(&priv->lock, flags);
ch341_set_handshake(port->serial->dev, priv->mcr);
+
+ ch341_set_flow_control(tty, port, old_termios);
}
/*
@@ -549,12 +590,12 @@ static void ch341_set_termios(struct tty_struct *tty,
* TCSBRKP. Due to how the simulation is implemented the duration can't be
* controlled. The duration is always about (1s / 46bd * 9bit) = 196ms.
*/
-static void ch341_simulate_break(struct tty_struct *tty, int break_state)
+static int ch341_simulate_break(struct tty_struct *tty, int break_state)
{
struct usb_serial_port *port = tty->driver_data;
struct ch341_private *priv = usb_get_serial_port_data(port);
unsigned long now, delay;
- int r;
+ int r, r2;
if (break_state != 0) {
dev_dbg(&port->dev, "enter break state requested\n");
@@ -586,7 +627,7 @@ static void ch341_simulate_break(struct tty_struct *tty, int break_state)
*/
priv->break_end = jiffies + (11 * HZ / CH341_MIN_BPS);
- return;
+ return 0;
}
dev_dbg(&port->dev, "leave break state requested\n");
@@ -602,37 +643,41 @@ static void ch341_simulate_break(struct tty_struct *tty, int break_state)
schedule_timeout_interruptible(delay);
}
+ r = 0;
restore:
/* Restore original baud rate */
- r = ch341_set_baudrate_lcr(port->serial->dev, priv, priv->baud_rate,
- priv->lcr);
- if (r < 0)
+ r2 = ch341_set_baudrate_lcr(port->serial->dev, priv, priv->baud_rate,
+ priv->lcr);
+ if (r2 < 0) {
dev_err(&port->dev,
"restoring original baud rate of %u failed: %d\n",
- priv->baud_rate, r);
+ priv->baud_rate, r2);
+ return r2;
+ }
+
+ return r;
}
-static void ch341_break_ctl(struct tty_struct *tty, int break_state)
+static int ch341_break_ctl(struct tty_struct *tty, int break_state)
{
- const uint16_t ch341_break_reg =
- ((uint16_t) CH341_REG_LCR << 8) | CH341_REG_BREAK;
+ const u16 ch341_break_reg = (CH341_REG_LCR << 8) | CH341_REG_BREAK;
struct usb_serial_port *port = tty->driver_data;
struct ch341_private *priv = usb_get_serial_port_data(port);
+ u16 reg_contents;
+ u8 break_reg[2];
int r;
- uint16_t reg_contents;
- uint8_t break_reg[2];
- if (priv->quirks & CH341_QUIRK_SIMULATE_BREAK) {
- ch341_simulate_break(tty, break_state);
- return;
- }
+ if (priv->quirks & CH341_QUIRK_SIMULATE_BREAK)
+ return ch341_simulate_break(tty, break_state);
r = ch341_control_in(port->serial->dev, CH341_REQ_READ_REG,
ch341_break_reg, 0, break_reg, 2);
if (r) {
dev_err(&port->dev, "%s - USB control read error (%d)\n",
__func__, r);
- return;
+ if (r > 0)
+ r = -EIO;
+ return r;
}
dev_dbg(&port->dev, "%s - initial ch341 break register contents - reg1: %x, reg2: %x\n",
__func__, break_reg[0], break_reg[1]);
@@ -650,9 +695,13 @@ static void ch341_break_ctl(struct tty_struct *tty, int break_state)
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)
+ if (r < 0) {
dev_err(&port->dev, "%s - USB control write error (%d)\n",
__func__, r);
+ return r;
+ }
+
+ return 0;
}
static int ch341_tiocmset(struct tty_struct *tty,
@@ -815,7 +864,6 @@ static int ch341_reset_resume(struct usb_serial *serial)
static struct usb_serial_driver ch341_device = {
.driver = {
- .owner = THIS_MODULE,
.name = "ch341-uart",
},
.id_table = id_table,
@@ -841,4 +889,5 @@ static struct usb_serial_driver * const serial_drivers[] = {
module_usb_serial_driver(serial_drivers, id_table);
+MODULE_DESCRIPTION("Winchiphead CH341 USB Serial driver");
MODULE_LICENSE("GPL v2");