Commit 8c4dd350 authored by Johan Hovold's avatar Johan Hovold Committed by Phil Elwell
Browse files

USB: serial: ch341: change initial line-control settings



commit 7c61b0d5 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: default avatarJohan Hovold <johan@kernel.org>
parent 3c1164cc
Loading
Loading
Loading
Loading
+103 −84
Original line number Diff line number Diff line
@@ -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);
	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]);

	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);
	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);