* 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) },
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,
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;
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)
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;
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;
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)
/* 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);
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;
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)
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;
__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]);
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);