From 95099d4622f8cb224d94e314c7a8e0df60b13f87 Mon Sep 17 00:00:00 2001
From: hc <hc@nodka.com>
Date: Sat, 09 Dec 2023 08:38:01 +0000
Subject: [PATCH] enable docker ppp
---
kernel/drivers/usb/serial/ch341.c | 296 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 268 insertions(+), 28 deletions(-)
diff --git a/kernel/drivers/usb/serial/ch341.c b/kernel/drivers/usb/serial/ch341.c
index f789b60..97a250e 100644
--- a/kernel/drivers/usb/serial/ch341.c
+++ b/kernel/drivers/usb/serial/ch341.c
@@ -48,12 +48,6 @@
#define CH341_BIT_DCD 0x08
#define CH341_BITS_MODEM_STAT 0x0f /* all bits */
-/*******************************/
-/* baudrate calculation factor */
-/*******************************/
-#define CH341_BAUDBASE_FACTOR 1532620800
-#define CH341_BAUDBASE_DIVMAX 3
-
/* Break support - the information used to implement this was gleaned from
* the Net/FreeBSD uchcom.c driver by Takanori Watanabe. Domo arigato.
*/
@@ -65,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
@@ -78,6 +76,9 @@
#define CH341_LCR_CS7 0x02
#define CH341_LCR_CS6 0x01
#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(0x1a86, 0x5523) },
@@ -96,6 +97,11 @@
u8 mcr;
u8 msr;
u8 lcr;
+
+ unsigned long quirks;
+ u8 version;
+
+ unsigned long break_end;
};
static void ch341_set_termios(struct tty_struct *tty,
@@ -147,41 +153,136 @@
return 0;
}
-static int ch341_set_baudrate_lcr(struct usb_device *dev,
- struct ch341_private *priv, u8 lcr)
+#define CH341_CLKRATE 48000000
+#define CH341_CLK_DIV(ps, fact) (1 << (12 - 3 * (ps) - (fact)))
+#define CH341_MIN_RATE(ps) (CH341_CLKRATE / (CH341_CLK_DIV((ps), 1) * 512))
+
+static const speed_t ch341_min_rates[] = {
+ CH341_MIN_RATE(0),
+ CH341_MIN_RATE(1),
+ CH341_MIN_RATE(2),
+ 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:
+ *
+ * baudrate = 48000000 / (2^(12 - 3 * ps - fact) * div), where
+ *
+ * 0 <= ps <= 3,
+ * 0 <= fact <= 1,
+ * 2 <= div <= 256 if fact = 0, or
+ * 9 <= div <= 256 if fact = 1
+ */
+static int ch341_get_divisor(struct ch341_private *priv, speed_t speed)
{
- short a;
- int r;
- unsigned long factor;
- short divisor;
+ unsigned int fact, div, clk_div;
+ bool force_fact0 = false;
+ int ps;
- if (!priv->baud_rate)
- return -EINVAL;
- factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate);
- divisor = CH341_BAUDBASE_DIVMAX;
+ /*
+ * Clamp to supported range, this makes the (ps < 0) and (div < 2)
+ * sanity checks below redundant.
+ */
+ speed = clamp_val(speed, CH341_MIN_BPS, CH341_MAX_BPS);
- while ((factor > 0xfff0) && divisor) {
- factor >>= 3;
- divisor--;
+ /*
+ * Start with highest possible base clock (fact = 1) that will give a
+ * divisor strictly less than 512.
+ */
+ fact = 1;
+ for (ps = 3; ps >= 0; ps--) {
+ if (speed > ch341_min_rates[ps])
+ break;
}
- if (factor > 0xfff0)
+ if (ps < 0)
return -EINVAL;
- factor = 0x10000 - factor;
- a = (factor & 0xff00) | divisor;
+ /* Determine corresponding divisor, rounding down. */
+ clk_div = CH341_CLK_DIV(ps, fact);
+ div = CH341_CLKRATE / (clk_div * speed);
+
+ /* Some devices require a lower base clock if ps < 3. */
+ if (ps < 3 && (priv->quirks & CH341_QUIRK_LIMITED_PRESCALER))
+ force_fact0 = true;
+
+ /* Halve base clock (fact = 0) if required. */
+ if (div < 9 || div > 255 || force_fact0) {
+ div /= 2;
+ clk_div *= 2;
+ fact = 0;
+ }
+
+ if (div < 2)
+ return -EINVAL;
+
+ /*
+ * Pick next divisor if resulting rate is closer to the requested one,
+ * scale up to avoid rounding errors on low rates.
+ */
+ if (16 * CH341_CLKRATE / (clk_div * div) - 16 * speed >=
+ 16 * speed - 16 * CH341_CLKRATE / (clk_div * (div + 1)))
+ div++;
+
+ /*
+ * Prefer lower base clock (fact = 0) if even divisor.
+ *
+ * Note that this makes the receiver more tolerant to errors.
+ */
+ if (fact == 1 && div % 2 == 0) {
+ div /= 2;
+ fact = 0;
+ }
+
+ return (0x100 - div) << 8 | fact << 2 | ps;
+}
+
+static int ch341_set_baudrate_lcr(struct usb_device *dev,
+ struct ch341_private *priv,
+ speed_t baud_rate, u8 lcr)
+{
+ int val;
+ int r;
+
+ if (!baud_rate)
+ return -EINVAL;
+
+ val = ch341_get_divisor(priv, baud_rate);
+ if (val < 0)
+ return -EINVAL;
/*
* CH341A buffers data until a full endpoint-size packet (32 bytes)
* has been received unless bit 7 is set.
+ *
+ * At least one device with version 0x27 appears to have this bit
+ * inverted.
*/
- a |= BIT(7);
+ if (priv->version > 0x27)
+ val |= BIT(7);
- r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x1312, a);
+ 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.
+ */
+ if (priv->version < 0x30)
+ return 0;
+
+ r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
+ CH341_REG_LCR2 << 8 | CH341_REG_LCR, lcr);
if (r)
return r;
@@ -232,19 +333,69 @@
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]);
+
+ priv->version = buffer[0];
+ dev_dbg(&dev->dev, "Chip version: 0x%02x\n", priv->version);
r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0);
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;
r = ch341_set_handshake(dev, priv->mcr);
out: kfree(buffer);
+ return r;
+}
+
+static int ch341_detect_quirks(struct usb_serial_port *port)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *udev = port->serial->dev;
+ const unsigned int size = 2;
+ unsigned long quirks = 0;
+ char *buffer;
+ int r;
+
+ buffer = kmalloc(size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ /*
+ * A subset of CH34x devices does not support all features. The
+ * prescaler is limited and there is no support for sending a RS232
+ * break condition. A read failure when trying to set up the latter is
+ * used to detect these devices.
+ */
+ r = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), CH341_REQ_READ_REG,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ CH341_REG_BREAK, 0, buffer, size, DEFAULT_TIMEOUT);
+ if (r == -EPIPE) {
+ 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;
+ }
+
+ if (r != size) {
+ if (r >= 0)
+ r = -EIO;
+ dev_err(&port->dev, "failed to read break control: %d\n", r);
+ goto out;
+ }
+
+ r = 0;
+out:
+ kfree(buffer);
+
+ if (quirks) {
+ dev_dbg(&port->dev, "enabling quirk flags: 0x%02lx\n", quirks);
+ priv->quirks |= quirks;
+ }
+
return r;
}
@@ -270,6 +421,11 @@
goto error;
usb_set_serial_port_data(port, priv);
+
+ r = ch341_detect_quirks(port);
+ if (r < 0)
+ goto error;
+
return 0;
error: kfree(priv);
@@ -400,7 +556,8 @@
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);
@@ -419,15 +576,98 @@
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;
--
Gitblit v1.6.2