Oliver Neukum 4dd714
From c432df155919582a3cefa35a8f86256c830fa9a4 Mon Sep 17 00:00:00 2001
Oliver Neukum 4dd714
From: Johan Hovold <johan@kernel.org>
Oliver Neukum 4dd714
Date: Thu, 14 May 2020 11:36:45 +0200
Oliver Neukum 4dd714
Subject: [PATCH] USB: serial: ch341: fix lockup of devices with limited
Oliver Neukum 4dd714
 prescaler
Oliver Neukum 4dd714
Git-commit: c432df155919582a3cefa35a8f86256c830fa9a4
Oliver Neukum 4dd714
References: git-fixes
Oliver Neukum 4dd714
Patch-mainline: v5.8-rc1
Oliver Neukum 4dd714
Oliver Neukum 4dd714
Michael Hanselmann reports that
Oliver Neukum 4dd714
Oliver Neukum 4dd714
	[a] subset of all CH341 devices stop responding to bulk
Oliver Neukum 4dd714
	transfers, usually after the third byte, when the highest
Oliver Neukum 4dd714
	prescaler bit (0b100) is set. There is one exception, namely a
Oliver Neukum 4dd714
	prescaler of exactly 0b111 (fact=1, ps=3).
Oliver Neukum 4dd714
Oliver Neukum 4dd714
Fix this by forcing a lower base clock (fact = 0) whenever needed.
Oliver Neukum 4dd714
Oliver Neukum 4dd714
This specifically makes the standard rates 110, 134 and 200 bps work
Oliver Neukum 4dd714
again with these devices.
Oliver Neukum 4dd714
Oliver Neukum 4dd714
Fixes: 35714565089e ("USB: serial: ch341: reimplement line-speed handling")
Oliver Neukum 4dd714
Cc: stable <stable@vger.kernel.org>	# 5.5
Oliver Neukum 4dd714
Reported-by: Michael Hanselmann <public@hansmi.ch>
Oliver Neukum 4dd714
Link: https://lore.kernel.org/r/20200514141743.GE25962@localhost
Oliver Neukum 4dd714
Signed-off-by: Johan Hovold <johan@kernel.org>
Oliver Neukum 4dd714
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Oliver Neukum 4dd714
---
Oliver Neukum 4dd714
 drivers/usb/serial/ch341.c | 15 ++++++++++++---
Oliver Neukum 4dd714
 1 file changed, 12 insertions(+), 3 deletions(-)
Oliver Neukum 4dd714
Oliver Neukum 4dd714
diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c
Oliver Neukum 4dd714
index f2b93f4554a7..89675ee29645 100644
Oliver Neukum 4dd714
--- a/drivers/usb/serial/ch341.c
Oliver Neukum 4dd714
+++ b/drivers/usb/serial/ch341.c
Oliver Neukum 4dd714
@@ -73,6 +73,8 @@
Oliver Neukum 4dd714
 #define CH341_LCR_CS6          0x01
Oliver Neukum 4dd714
 #define CH341_LCR_CS5          0x00
Oliver Neukum 4dd714
 
Oliver Neukum 4dd714
+#define CH341_QUIRK_LIMITED_PRESCALER	BIT(0)
Oliver Neukum 4dd714
+
Oliver Neukum 4dd714
 static const struct usb_device_id id_table[] = {
Oliver Neukum 4dd714
 	{ USB_DEVICE(0x4348, 0x5523) },
Oliver Neukum 4dd714
 	{ USB_DEVICE(0x1a86, 0x7523) },
Oliver Neukum 4dd714
@@ -160,9 +162,11 @@ static const speed_t ch341_min_rates[] = {
Oliver Neukum 4dd714
  *		2 <= div <= 256 if fact = 0, or
Oliver Neukum 4dd714
  *		9 <= div <= 256 if fact = 1
Oliver Neukum 4dd714
  */
Oliver Neukum 4dd714
-static int ch341_get_divisor(speed_t speed)
Oliver Neukum 4dd714
+static int ch341_get_divisor(struct ch341_private *priv)
Oliver Neukum 4dd714
 {
Oliver Neukum 4dd714
 	unsigned int fact, div, clk_div;
Oliver Neukum 4dd714
+	speed_t speed = priv->baud_rate;
Oliver Neukum 4dd714
+	bool force_fact0 = false;
Oliver Neukum 4dd714
 	int ps;
Oliver Neukum 4dd714
 
Oliver Neukum 4dd714
 	/*
Oliver Neukum 4dd714
@@ -188,8 +192,12 @@ static int ch341_get_divisor(speed_t speed)
Oliver Neukum 4dd714
 	clk_div = CH341_CLK_DIV(ps, fact);
Oliver Neukum 4dd714
 	div = CH341_CLKRATE / (clk_div * speed);
Oliver Neukum 4dd714
 
Oliver Neukum 4dd714
+	/* Some devices require a lower base clock if ps < 3. */
Oliver Neukum 4dd714
+	if (ps < 3 && (priv->quirks & CH341_QUIRK_LIMITED_PRESCALER))
Oliver Neukum 4dd714
+		force_fact0 = true;
Oliver Neukum 4dd714
+
Oliver Neukum 4dd714
 	/* Halve base clock (fact = 0) if required. */
Oliver Neukum 4dd714
-	if (div < 9 || div > 255) {
Oliver Neukum 4dd714
+	if (div < 9 || div > 255 || force_fact0) {
Oliver Neukum 4dd714
 		div /= 2;
Oliver Neukum 4dd714
 		clk_div *= 2;
Oliver Neukum 4dd714
 		fact = 0;
Oliver Neukum 4dd714
@@ -228,7 +236,7 @@ static int ch341_set_baudrate_lcr(struct usb_device *dev,
Oliver Neukum 4dd714
 	if (!priv->baud_rate)
Oliver Neukum 4dd714
 		return -EINVAL;
Oliver Neukum 4dd714
 
Oliver Neukum 4dd714
-	val = ch341_get_divisor(priv->baud_rate);
Oliver Neukum 4dd714
+	val = ch341_get_divisor(priv);
Oliver Neukum 4dd714
 	if (val < 0)
Oliver Neukum 4dd714
 		return -EINVAL;
Oliver Neukum 4dd714
 
Oliver Neukum 4dd714
@@ -333,6 +341,7 @@ static int ch341_detect_quirks(struct usb_serial_port *port)
Oliver Neukum 4dd714
 			    CH341_REG_BREAK, 0, buffer, size, DEFAULT_TIMEOUT);
Oliver Neukum 4dd714
 	if (r == -EPIPE) {
Oliver Neukum 4dd714
 		dev_dbg(&port->dev, "break control not supported\n");
Oliver Neukum 4dd714
+		quirks = CH341_QUIRK_LIMITED_PRESCALER;
Oliver Neukum 4dd714
 		r = 0;
Oliver Neukum 4dd714
 		goto out;
Oliver Neukum 4dd714
 	}
Oliver Neukum 4dd714
-- 
Oliver Neukum 4dd714
2.35.3
Oliver Neukum 4dd714