aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/serial/ch341.c
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-07-28 17:33:10 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-07-28 17:33:10 +0200
commitfa56dd9152ef955bd21082c5330e4dff8621bca6 (patch)
tree1a4dfe6d934ed9149eb509c40ecbcb452c73c077 /drivers/usb/serial/ch341.c
parentxhci: dbgtty: Make some functions static (diff)
parentUSB: serial: qcserial: add EM7305 QDL product ID (diff)
downloadlinux-dev-fa56dd9152ef955bd21082c5330e4dff8621bca6.tar.xz
linux-dev-fa56dd9152ef955bd21082c5330e4dff8621bca6.zip
Merge tag 'usb-serial-5.9-rc1' of https://git.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial into usb-next
Johan writes: USB-serial updates for 5.9-rc1 Here are the USB-serial updates for 5.9-rc1, including: - console flow-control support - simulated line-breaks on some ch341 - hardware flow-control fixes for cp210x - break-detection and sysrq fixes for ftdi_sio - sysrq optimisations - input parity checking for cp210x Included are also some new device ids and various clean ups. All have been in linux-next with no reported issues. * tag 'usb-serial-5.9-rc1' of https://git.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial: (31 commits) USB: serial: qcserial: add EM7305 QDL product ID USB: serial: iuu_phoenix: fix led-activity helpers USB: serial: sierra: clean up special-interface handling USB: serial: cp210x: use in-kernel types in port data USB: serial: cp210x: drop unnecessary packed attributes USB: serial: cp210x: add support for TIOCGICOUNT USB: serial: cp210x: add support for line-status events USB: serial: cp210x: disable interface on errors in open USB: serial: drop redundant transfer-buffer casts USB: serial: drop extern keyword from function declarations USB: serial: drop unnecessary sysrq include USB: serial: add sysrq break-handler dummy USB: serial: inline sysrq dummy function USB: serial: only process sysrq when enabled USB: serial: only set sysrq timestamp for consoles USB: serial: ftdi_sio: fix break and sysrq handling USB: serial: ftdi_sio: clean up receive processing USB: serial: ftdi_sio: make process-packet buffer unsigned USB: serial: use fallthrough pseudo-keyword USB: serial: ch341: fix missing simulated-break margin ...
Diffstat (limited to 'drivers/usb/serial/ch341.c')
-rw-r--r--drivers/usb/serial/ch341.c127
1 files changed, 115 insertions, 12 deletions
diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c
index 8fbaef5c9d69..a2e2f56c88cd 100644
--- a/drivers/usb/serial/ch341.c
+++ b/drivers/usb/serial/ch341.c
@@ -59,7 +59,11 @@
#define CH341_REQ_MODEM_CTRL 0xA4
#define CH341_REG_BREAK 0x05
+#define CH341_REG_PRESCALER 0x12
+#define CH341_REG_DIVISOR 0x13
#define CH341_REG_LCR 0x18
+#define CH341_REG_LCR2 0x25
+
#define CH341_NBREAK_BITS 0x01
#define CH341_LCR_ENABLE_RX 0x80
@@ -74,6 +78,7 @@
#define CH341_LCR_CS5 0x00
#define CH341_QUIRK_LIMITED_PRESCALER BIT(0)
+#define CH341_QUIRK_SIMULATE_BREAK BIT(1)
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x4348, 0x5523) },
@@ -91,6 +96,7 @@ struct ch341_private {
u8 msr;
u8 lcr;
unsigned long quirks;
+ unsigned long break_end;
};
static void ch341_set_termios(struct tty_struct *tty,
@@ -153,6 +159,10 @@ static const speed_t ch341_min_rates[] = {
CH341_MIN_RATE(3),
};
+/* Supported range is 46 to 3000000 bps. */
+#define CH341_MIN_BPS DIV_ROUND_UP(CH341_CLKRATE, CH341_CLK_DIV(0, 0) * 256)
+#define CH341_MAX_BPS (CH341_CLKRATE / (CH341_CLK_DIV(3, 0) * 2))
+
/*
* The device line speed is given by the following equation:
*
@@ -163,10 +173,9 @@ static const speed_t ch341_min_rates[] = {
* 2 <= div <= 256 if fact = 0, or
* 9 <= div <= 256 if fact = 1
*/
-static int ch341_get_divisor(struct ch341_private *priv)
+static int ch341_get_divisor(struct ch341_private *priv, speed_t speed)
{
unsigned int fact, div, clk_div;
- speed_t speed = priv->baud_rate;
bool force_fact0 = false;
int ps;
@@ -174,7 +183,7 @@ static int ch341_get_divisor(struct ch341_private *priv)
* Clamp to supported range, this makes the (ps < 0) and (div < 2)
* sanity checks below redundant.
*/
- speed = clamp(speed, 46U, 3000000U);
+ speed = clamp_val(speed, CH341_MIN_BPS, CH341_MAX_BPS);
/*
* Start with highest possible base clock (fact = 1) that will give a
@@ -229,15 +238,16 @@ static int ch341_get_divisor(struct ch341_private *priv)
}
static int ch341_set_baudrate_lcr(struct usb_device *dev,
- struct ch341_private *priv, u8 lcr)
+ struct ch341_private *priv,
+ speed_t baud_rate, u8 lcr)
{
int val;
int r;
- if (!priv->baud_rate)
+ if (!baud_rate)
return -EINVAL;
- val = ch341_get_divisor(priv);
+ val = ch341_get_divisor(priv, baud_rate);
if (val < 0)
return -EINVAL;
@@ -247,11 +257,20 @@ static int ch341_set_baudrate_lcr(struct usb_device *dev,
*/
val |= BIT(7);
- r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x1312, val);
+ r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
+ CH341_REG_DIVISOR << 8 | CH341_REG_PRESCALER,
+ val);
if (r)
return r;
- r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x2518, lcr);
+ /*
+ * Chip versions before version 0x30 as read using
+ * CH341_REQ_READ_VERSION used separate registers for line control
+ * (stop bits, parity and word length). Version 0x30 and above use
+ * CH341_REG_LCR only and CH341_REG_LCR2 is always set to zero.
+ */
+ r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
+ CH341_REG_LCR2 << 8 | CH341_REG_LCR, lcr);
if (r)
return r;
@@ -308,7 +327,7 @@ static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
if (r < 0)
goto out;
- r = ch341_set_baudrate_lcr(dev, priv, priv->lcr);
+ r = ch341_set_baudrate_lcr(dev, priv, priv->baud_rate, priv->lcr);
if (r < 0)
goto out;
@@ -341,8 +360,8 @@ static int ch341_detect_quirks(struct usb_serial_port *port)
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
CH341_REG_BREAK, 0, buffer, size, DEFAULT_TIMEOUT);
if (r == -EPIPE) {
- dev_dbg(&port->dev, "break control not supported\n");
- quirks = CH341_QUIRK_LIMITED_PRESCALER;
+ dev_info(&port->dev, "break control not supported, using simulated break\n");
+ quirks = CH341_QUIRK_LIMITED_PRESCALER | CH341_QUIRK_SIMULATE_BREAK;
r = 0;
goto out;
}
@@ -523,7 +542,8 @@ static void ch341_set_termios(struct tty_struct *tty,
if (baud_rate) {
priv->baud_rate = baud_rate;
- r = ch341_set_baudrate_lcr(port->serial->dev, priv, lcr);
+ r = ch341_set_baudrate_lcr(port->serial->dev, priv,
+ priv->baud_rate, lcr);
if (r < 0 && old_termios) {
priv->baud_rate = tty_termios_baud_rate(old_termios);
tty_termios_copy_hw(&tty->termios, old_termios);
@@ -542,15 +562,98 @@ static void ch341_set_termios(struct tty_struct *tty,
ch341_set_handshake(port->serial->dev, priv->mcr);
}
+/*
+ * A subset of all CH34x devices don't support a real break condition and
+ * reading CH341_REG_BREAK fails (see also ch341_detect_quirks). This function
+ * simulates a break condition by lowering the baud rate to the minimum
+ * supported by the hardware upon enabling the break condition and sending
+ * a NUL byte.
+ *
+ * Incoming data is corrupted while the break condition is being simulated.
+ *
+ * Normally the duration of the break condition can be controlled individually
+ * by userspace using TIOCSBRK and TIOCCBRK or by passing an argument to
+ * TCSBRKP. Due to how the simulation is implemented the duration can't be
+ * controlled. The duration is always about (1s / 46bd * 9bit) = 196ms.
+ */
+static void ch341_simulate_break(struct tty_struct *tty, int break_state)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ unsigned long now, delay;
+ int r;
+
+ if (break_state != 0) {
+ dev_dbg(&port->dev, "enter break state requested\n");
+
+ r = ch341_set_baudrate_lcr(port->serial->dev, priv,
+ CH341_MIN_BPS,
+ CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8);
+ if (r < 0) {
+ dev_err(&port->dev,
+ "failed to change baud rate to %u: %d\n",
+ CH341_MIN_BPS, r);
+ goto restore;
+ }
+
+ r = tty_put_char(tty, '\0');
+ if (r < 0) {
+ dev_err(&port->dev,
+ "failed to write NUL byte for simulated break condition: %d\n",
+ r);
+ goto restore;
+ }
+
+ /*
+ * Compute expected transmission duration including safety
+ * margin. The original baud rate is only restored after the
+ * computed point in time.
+ *
+ * 11 bits = 1 start, 8 data, 1 stop, 1 margin
+ */
+ priv->break_end = jiffies + (11 * HZ / CH341_MIN_BPS);
+
+ return;
+ }
+
+ dev_dbg(&port->dev, "leave break state requested\n");
+
+ now = jiffies;
+
+ if (time_before(now, priv->break_end)) {
+ /* Wait until NUL byte is written */
+ delay = priv->break_end - now;
+ dev_dbg(&port->dev,
+ "wait %d ms while transmitting NUL byte at %u baud\n",
+ jiffies_to_msecs(delay), CH341_MIN_BPS);
+ schedule_timeout_interruptible(delay);
+ }
+
+restore:
+ /* Restore original baud rate */
+ r = ch341_set_baudrate_lcr(port->serial->dev, priv, priv->baud_rate,
+ priv->lcr);
+ if (r < 0)
+ dev_err(&port->dev,
+ "restoring original baud rate of %u failed: %d\n",
+ priv->baud_rate, r);
+}
+
static void ch341_break_ctl(struct tty_struct *tty, int break_state)
{
const uint16_t ch341_break_reg =
((uint16_t) CH341_REG_LCR << 8) | CH341_REG_BREAK;
struct usb_serial_port *port = tty->driver_data;
+ struct ch341_private *priv = usb_get_serial_port_data(port);
int r;
uint16_t reg_contents;
uint8_t *break_reg;
+ if (priv->quirks & CH341_QUIRK_SIMULATE_BREAK) {
+ ch341_simulate_break(tty, break_state);
+ return;
+ }
+
break_reg = kmalloc(2, GFP_KERNEL);
if (!break_reg)
return;