USB: serial: ch341: reinitialize chip on reconfiguration
[pandora-kernel.git] / drivers / usb / serial / ch341.c
index 6ae1c06..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;
 
@@ -82,13 +95,16 @@ MODULE_DEVICE_TABLE(usb, id_table);
 
 struct ch341_private {
        spinlock_t lock; /* access lock */
-       wait_queue_head_t delta_msr_wait; /* wait queue for modem status */
        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 multi_status_change; /* status changed multiple since last call */
 };
 
+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)
 {
@@ -117,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;
@@ -142,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;
 }
@@ -154,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)
@@ -170,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;
 
@@ -203,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;
 
@@ -229,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;
 
@@ -262,7 +269,6 @@ static int ch341_attach(struct usb_serial *serial)
                return -ENOMEM;
 
        spin_lock_init(&priv->lock);
-       init_waitqueue_head(&priv->delta_msr_wait);
        priv->baud_rate = DEFAULT_BAUD_RATE;
        priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR;
 
@@ -299,7 +305,7 @@ static void ch341_dtr_rts(struct usb_serial_port *port, int on)
                priv->line_control &= ~(CH341_BIT_RTS | CH341_BIT_DTR);
        spin_unlock_irqrestore(&priv->lock, flags);
        ch341_set_handshake(port->serial->dev, priv->line_control);
-       wake_up_interruptible(&priv->delta_msr_wait);
+       wake_up_interruptible(&port->delta_msr_wait);
 }
 
 static void ch341_close(struct usb_serial_port *port)
@@ -320,19 +326,12 @@ static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port)
 
        dbg("ch341_open()");
 
-       priv->baud_rate = DEFAULT_BAUD_RATE;
-
        r = ch341_configure(serial->dev, priv);
        if (r)
                goto out;
 
-       r = ch341_set_handshake(serial->dev, priv->line_control);
-       if (r)
-               goto out;
-
-       r = ch341_set_baudrate(serial->dev, priv);
-       if (r)
-               goto out;
+       if (tty)
+               ch341_set_termios(tty, port, NULL);
 
        dbg("%s - submitting interrupt urb", __func__);
        port->interrupt_in_urb->dev = serial->dev;
@@ -340,8 +339,7 @@ static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port)
        if (r) {
                dev_err(&port->dev, "%s - failed submitting interrupt urb,"
                        " error %d\n", __func__, r);
-               ch341_close(port);
-               return -EPROTO;
+               goto out;
        }
 
        r = usb_serial_generic_open(tty, port);
@@ -358,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()");
 
@@ -365,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);
@@ -388,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;
@@ -413,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]);
@@ -503,7 +513,7 @@ static void ch341_read_int_callback(struct urb *urb)
                        tty_kref_put(tty);
                }
 
-               wake_up_interruptible(&priv->delta_msr_wait);
+               wake_up_interruptible(&port->delta_msr_wait);
        }
 
 exit:
@@ -529,11 +539,14 @@ static int wait_modem_info(struct usb_serial_port *port, unsigned int arg)
        spin_unlock_irqrestore(&priv->lock, flags);
 
        while (!multi_change) {
-               interruptible_sleep_on(&priv->delta_msr_wait);
+               interruptible_sleep_on(&port->delta_msr_wait);
                /* see if a signal did it */
                if (signal_pending(current))
                        return -ERESTARTSYS;
 
+               if (port->serial->disconnected)
+                       return -EIO;
+
                spin_lock_irqsave(&priv->lock, flags);
                status = priv->line_status;
                multi_change = priv->multi_status_change;