From 70bdc08ff7a0d5316e3f7c21aedf157f20ade90b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 6 Jan 2017 19:15:23 +0100 Subject: [PATCH] USB: serial: ch341: change initial line-control settings 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 --- drivers/usb/serial/ch341.c | 187 ++++++++++++++++++++----------------- 1 file changed, 103 insertions(+), 84 deletions(-) diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c index 4f25aba5bdfc..b27593a02734 100644 --- a/drivers/usb/serial/ch341.c +++ b/drivers/usb/serial/ch341.c @@ -61,13 +61,26 @@ * 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); -- 2.30.2