USB: serial: ch341: change initial line-control settings
authorJohan Hovold <johan@kernel.org>
Fri, 6 Jan 2017 18:15:23 +0000 (19:15 +0100)
committerRaspbian kernel package updater <root@raspbian.org>
Sun, 23 Jul 2017 03:00:02 +0000 (03:00 +0000)
commit 7c61b0d5e82bfe29b8dda55745afbf65b6ccc901 upstream.

Some CH340 devices appear unable to change the initial LCR settings, so
set a sane 8N1 default during probe to enable basic support for such
devices.

Also drop a redundant LCR read during device initialisation.

Signed-off-by: Johan Hovold <johan@kernel.org>
drivers/usb/serial/ch341.c

index 4f25aba5bdfcabe7304b18860be8527587390467..b27593a02734a1def9412e848c91feb553088e3c 100644 (file)
  * the Net/FreeBSD uchcom.c driver by Takanori Watanabe.  Domo arigato.
  */
 
+#define CH341_REQ_READ_VERSION 0x5F
 #define CH341_REQ_WRITE_REG    0x9A
 #define CH341_REQ_READ_REG     0x95
-#define CH341_REG_BREAK1       0x05
-#define CH341_REG_BREAK2       0x18
-#define CH341_NBREAK_BITS_REG1 0x01
-#define CH341_NBREAK_BITS_REG2 0x40
-
+#define CH341_REQ_SERIAL_INIT  0xA1
+#define CH341_REQ_MODEM_CTRL   0xA4
+
+#define CH341_REG_BREAK        0x05
+#define CH341_REG_LCR          0x18
+#define CH341_NBREAK_BITS      0x01
+
+#define CH341_LCR_ENABLE_RX    0x80
+#define CH341_LCR_ENABLE_TX    0x40
+#define CH341_LCR_MARK_SPACE   0x20
+#define CH341_LCR_PAR_EVEN     0x10
+#define CH341_LCR_ENABLE_PAR   0x08
+#define CH341_LCR_STOP_BITS_2  0x04
+#define CH341_LCR_CS8          0x03
+#define CH341_LCR_CS7          0x02
+#define CH341_LCR_CS6          0x01
+#define CH341_LCR_CS5          0x00
 
 static const struct usb_device_id id_table[] = {
        { USB_DEVICE(0x4348, 0x5523) },
@@ -82,6 +95,7 @@ struct ch341_private {
        unsigned baud_rate; /* set baud rate */
        u8 line_control; /* set line control value RTS/DTR */
        u8 line_status; /* active status of modem control inputs */
+       u8 lcr;
 };
 
 static void ch341_set_termios(struct tty_struct *tty,
@@ -134,10 +148,10 @@ static int ch341_control_in(struct usb_device *dev,
        return 0;
 }
 
-static int ch341_set_baudrate(struct usb_device *dev,
-                             struct ch341_private *priv)
+static int ch341_set_baudrate_lcr(struct usb_device *dev,
+                                 struct ch341_private *priv, u8 lcr)
 {
-       short a, b;
+       short a;
        int r;
        unsigned long factor;
        short divisor;
@@ -157,18 +171,27 @@ static int ch341_set_baudrate(struct usb_device *dev,
 
        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);
+       /*
+        * CH341A buffers data until a full endpoint-size packet (32 bytes)
+        * has been received unless bit 7 is set.
+        */
+       a |= BIT(7);
+
+       r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x1312, a);
+       if (r)
+               return r;
+
+       r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x2518, lcr);
+       if (r)
+               return r;
 
        return r;
 }
 
 static int ch341_set_handshake(struct usb_device *dev, u8 control)
 {
-       return ch341_control_out(dev, 0xa4, ~control, 0);
+       return ch341_control_out(dev, CH341_REQ_MODEM_CTRL, ~control, 0);
 }
 
 static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv)
@@ -182,7 +205,7 @@ static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv)
        if (!buffer)
                return -ENOMEM;
 
-       r = ch341_control_in(dev, 0x95, 0x0706, 0, buffer, size);
+       r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x0706, 0, buffer, size);
        if (r < 0)
                goto out;
 
@@ -207,46 +230,20 @@ static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
                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);
+       r = ch341_control_in(dev, CH341_REQ_READ_VERSION, 0, 0, buffer, size);
        if (r < 0)
                goto out;
+       dev_dbg(&dev->dev, "Chip version: 0x%02x\n", buffer[0]);
 
-       /* expect two bytes 0x56 0x00 */
-       r = ch341_control_in(dev, 0x95, 0x2518, 0, buffer, size);
+       r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0);
        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);
+       r = ch341_set_baudrate_lcr(dev, priv, priv->lcr);
        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;
@@ -263,6 +260,11 @@ static int ch341_port_probe(struct usb_serial_port *port)
 
        spin_lock_init(&priv->lock);
        priv->baud_rate = DEFAULT_BAUD_RATE;
+       /*
+        * Some CH340 devices appear unable to change the initial LCR
+        * settings, so set a sane 8N1 default.
+        */
+       priv->lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8;
 
        r = ch341_configure(port->serial->dev, priv);
        if (r < 0)
@@ -318,14 +320,9 @@ static void ch341_close(struct usb_serial_port *port)
 /* 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)
-               return r;
-
        if (tty)
                ch341_set_termios(tty, port, NULL);
 
@@ -337,6 +334,12 @@ static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port)
                return r;
        }
 
+       r = ch341_get_status(port->serial->dev, priv);
+       if (r < 0) {
+               dev_err(&port->dev, "failed to read modem status: %d\n", r);
+               goto err_kill_interrupt_urb;
+       }
+
        r = usb_serial_generic_open(tty, port);
        if (r)
                goto err_kill_interrupt_urb;
@@ -358,44 +361,54 @@ static void ch341_set_termios(struct tty_struct *tty,
        struct ch341_private *priv = usb_get_serial_port_data(port);
        unsigned baud_rate;
        unsigned long flags;
-       unsigned int par_flags;
+       unsigned char ctrl;
+       int r;
+
+       /* redundant changes may cause the chip to lose bytes */
+       if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios))
+               return;
 
        baud_rate = tty_get_baud_rate(tty);
 
-       if (baud_rate) {
-               priv->baud_rate = baud_rate;
-               ch341_set_baudrate(port->serial->dev, priv);
-       }
+       ctrl = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX;
 
-       /* Unimplemented:
-        * (cflag & CSIZE) : data bits [5, 8]
-        * (cflag & CSTOPB) : stop bits [1, 2]
-        */
+       switch (C_CSIZE(tty)) {
+       case CS5:
+               ctrl |= CH341_LCR_CS5;
+               break;
+       case CS6:
+               ctrl |= CH341_LCR_CS6;
+               break;
+       case CS7:
+               ctrl |= CH341_LCR_CS7;
+               break;
+       case CS8:
+               ctrl |= CH341_LCR_CS8;
+               break;
+       }
 
-       /* CH340 doesn't appear to support variable stop bits or data bits */
        if (C_PARENB(tty)) {
-               if (C_PARODD(tty)) {
-                       if (tty->termios.c_cflag & CMSPAR) {
-                               dev_dbg(&port->dev, "parity = mark\n");
-                               par_flags = 0xeb;
-                       } else {
-                               dev_dbg(&port->dev, "parity = odd\n");
-                               par_flags = 0xcb;
-                       }
-               } else {
-                       if (tty->termios.c_cflag & CMSPAR) {
-                               dev_dbg(&port->dev, "parity = space\n");
-                               par_flags = 0xfb;
-                       } else {
-                               dev_dbg(&port->dev, "parity = even\n");
-                               par_flags = 0xdb;
-                       }
+               ctrl |= CH341_LCR_ENABLE_PAR;
+               if (C_PARODD(tty) == 0)
+                       ctrl |= CH341_LCR_PAR_EVEN;
+               if (C_CMSPAR(tty))
+                       ctrl |= CH341_LCR_MARK_SPACE;
+       }
+
+       if (C_CSTOPB(tty))
+               ctrl |= CH341_LCR_STOP_BITS_2;
+
+       if (baud_rate) {
+               priv->baud_rate = baud_rate;
+
+               r = ch341_set_baudrate_lcr(port->serial->dev, priv, ctrl);
+               if (r < 0 && old_termios) {
+                       priv->baud_rate = tty_termios_baud_rate(old_termios);
+                       tty_termios_copy_hw(&tty->termios, old_termios);
+               } else if (r == 0) {
+                       priv->lcr = ctrl;
                }
-       } else {
-               dev_dbg(&port->dev, "parity = none\n");
-               par_flags = 0xc3;
        }
-       ch341_control_out(port->serial->dev, 0x9a, 0x2518, par_flags);
 
        spin_lock_irqsave(&priv->lock, flags);
        if (C_BAUD(tty) == B0)
@@ -410,7 +423,7 @@ static void ch341_set_termios(struct tty_struct *tty,
 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;
+                       ((uint16_t) CH341_REG_LCR << 8) | CH341_REG_BREAK;
        struct usb_serial_port *port = tty->driver_data;
        int r;
        uint16_t reg_contents;
@@ -431,12 +444,12 @@ static void ch341_break_ctl(struct tty_struct *tty, int break_state)
                __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;
+               break_reg[0] &= ~CH341_NBREAK_BITS;
+               break_reg[1] &= ~CH341_LCR_ENABLE_TX;
        } else {
                dev_dbg(&port->dev, "%s - Leave break state requested\n", __func__);
-               break_reg[0] |= CH341_NBREAK_BITS_REG1;
-               break_reg[1] |= CH341_NBREAK_BITS_REG2;
+               break_reg[0] |= CH341_NBREAK_BITS;
+               break_reg[1] |= CH341_LCR_ENABLE_TX;
        }
        dev_dbg(&port->dev, "%s - New ch341 break register contents - reg1: %x, reg2: %x\n",
                __func__, break_reg[0], break_reg[1]);
@@ -593,6 +606,12 @@ static int ch341_reset_resume(struct usb_serial *serial)
                                ret);
                        return ret;
                }
+
+               ret = ch341_get_status(port->serial->dev, priv);
+               if (ret < 0) {
+                       dev_err(&port->dev, "failed to read modem status: %d\n",
+                               ret);
+               }
        }
 
        return usb_serial_generic_resume(serial);