USB: serial: ch341: reinitialize chip on reconfiguration
[pandora-kernel.git] / drivers / usb / serial / ch341.c
index fef839b..9538181 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 int debug;
 
@@ -120,10 +133,10 @@ static int ch341_control_in(struct usb_device *dev,
        return r;
 }
 
-static int ch341_set_baudrate(struct usb_device *dev,
-                             struct ch341_private *priv)
+static int ch341_init_set_baudrate(struct usb_device *dev,
+                                  struct ch341_private *priv, unsigned ctrl)
 {
-       short a, b;
+       short a;
        int r;
        unsigned long factor;
        short divisor;
@@ -145,11 +158,10 @@ 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);
+       /* 0x9c is "enable SFR_UART Control register and timer" */
+       r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT,
+                             0x9c | (ctrl << 8), a | 0x80);
 
        return r;
 }
@@ -157,7 +169,7 @@ static int ch341_set_baudrate(struct usb_device *dev,
 static int ch341_set_handshake(struct usb_device *dev, u8 control)
 {
        dbg("ch341_set_handshake(0x%02x)", 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)
@@ -173,7 +185,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;
 
@@ -206,24 +218,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);
+       r = ch341_control_in(dev, CH341_REQ_READ_VERSION, 0, 0, buffer, size);
        if (r < 0)
                goto out;
 
-       r = ch341_set_baudrate(dev, priv);
+       r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0);
        if (r < 0)
                goto out;
 
        /* expect two bytes 0x56 0x00 */
-       r = ch341_control_in(dev, 0x95, 0x2518, 0, buffer, size);
+       r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x2518, 0, buffer, size);
        if (r < 0)
                goto out;
 
-       r = ch341_control_out(dev, 0x9a, 0x2518, 0x0050);
+       r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x2518, 0x0050);
        if (r < 0)
                goto out;
 
@@ -232,11 +240,7 @@ static int ch341_configure(struct usb_device *dev, struct ch341_private *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_init_set_baudrate(dev, priv, 0);
        if (r < 0)
                goto out;
 
@@ -352,6 +356,12 @@ 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 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;
 
        dbg("ch341_set_termios()");
 
@@ -359,11 +369,17 @@ static void ch341_set_termios(struct tty_struct *tty,
 
        priv->baud_rate = baud_rate;
 
+       ctrl = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8;
+
        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);
+               r = ch341_init_set_baudrate(port->serial->dev, priv, ctrl);
+               if (r < 0 && old_termios) {
+                       priv->baud_rate = tty_termios_baud_rate(old_termios);
+                       tty_termios_copy_hw(tty->termios, old_termios);
+               }
        } else {
                spin_lock_irqsave(&priv->lock, flags);
                priv->line_control &= ~(CH341_BIT_DTR | CH341_BIT_RTS);
@@ -382,7 +398,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 =
-               CH341_REG_BREAK1 | ((uint16_t) CH341_REG_BREAK2 << 8);
+                       ((uint16_t) CH341_REG_LCR << 8) | CH341_REG_BREAK;
        struct usb_serial_port *port = tty->driver_data;
        int r;
        uint16_t reg_contents;
@@ -407,12 +423,12 @@ static void ch341_break_ctl(struct tty_struct *tty, int break_state)
                        __func__, break_reg[0], break_reg[1]);
        if (break_state != 0) {
                dbg("%s - Enter break state requested", __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 {
                dbg("%s - Leave break state requested", __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;
        }
        dbg("%s - New ch341 break register contents - reg1: %x, reg2: %x",
                        __func__, break_reg[0], break_reg[1]);