Hi Jonathan, On Wed, Oct 30, 2019 at 08:18:48AM +1300, Jonathan Olds wrote: > Hi Johan, And apologies for the lack of feedback on this. I've started looking into this a few times this fall, but got side tracked. This past couple of weeks I did find some time, but never got around to posting before heading to ELCE this week. I've been working on a patch that generalises the vendor driver's algorithm based on your findings and my own experiments. I've verified it on both ch340g and ch341a, and it gives smaller (or equal) errors for all rates, including the following standard rates - 1152000 (4.0% instead of 15% error) - 921600 (0.16% instead of -7.5%) - 576000 (-0.80% instead of -5.6%) - 200 (0.16% instead of -0.69%) - 134 (-0.05% instead of -0.63%) - 110 (0.03% instead of -0.44%) but also on many non-standard ones by always picking the divisors with smallest error. My main issue with your proposed patch was that it was still based on the current fairly opaque implementation, and then special-cased the higher baud rates with some "magic" constants thrown in. I also noticed that we should always pick the higher base clock if the resulting divisor is odd (and within the valid range). No need to determine the actual rates and compare; the odd divisor with the higher base clock will always give a smaller error. If it's even, we can go with the lower base clock and halve the divisor if we want. When it comes to handling rounding, I opted for the brute force method also used by the current vendor driver (the one you also referred to earlier) by determining the actual rates for div and div + 1 and picking the closest one. This should always give the smallest error, even though we may not care about the midpoint rates for higher speeds that much (the current algorithm would pick 2 Mbaud instead of 3 MBaud for 2950000, resulting in an error of -32% instead of 1.7%, etc). > Michael has also produced a patch for the Linux kernel fixing the wrong > baud rate calculations. This can be found here... > > https://github.com/nospam2000/ch341-baudrate-calculation/blob/master/patches > /Linux_4.14.114_ch341.patch > > I haven't tested his code. > > He has done a write up of his investigations about the registers here... > > https://github.com/nospam2000/ch341-baudrate-calculation/blob/master/README. > md Interesting! From a very quick glance this looks very promising (although he doesn't seem to handle rounding yet). I'll take a closer look and compare with what I've done. Would you mind giving the below patch a try meanwhile? It's still lacking a proper commit message, and I wanted to take another look at it when I'm back from the conference. Just wanted to let you know that I'm on it and want to get this fixed in one way or another for v5.4. Thanks, Johan >From 718c9f8d6f45014dc0c14a297d1017ecc945ef26 Mon Sep 17 00:00:00 2001 From: Johan Hovold <johan@xxxxxxxxxx> Date: Wed, 30 Oct 2019 09:56:09 +0100 Subject: [PATCH] USB: ch341: reimplement line-speed handling Based on work by Jonathan Olds <jontio@xxxxxxxxxxxx> and the algorithm used by the vendor driver. FIXME: add commit message Signed-off-by: Johan Hovold <johan@xxxxxxxxxx> --- drivers/usb/serial/ch341.c | 97 +++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c index 3bb1fff02bed..3dcc579b2d2f 100644 --- a/drivers/usb/serial/ch341.c +++ b/drivers/usb/serial/ch341.c @@ -144,37 +144,102 @@ static int ch341_control_in(struct usb_device *dev, return 0; } +#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), +}; + +/* + * The device line speed is given by the following equation: + * + * baud = 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(speed_t speed) +{ + unsigned int fact, div, clk_div; + int ps; + + /* + * Clamp to supported range, this makes the (ps < 0) and (div < 2) + * sanity checks below redundant. + */ + speed = clamp(speed, 46U, 3000000U); + + /* + * 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 (ps < 0) + return -EINVAL; + + /* Determine corresponding divisor, rounding down. */ + clk_div = CH341_CLK_DIV(ps, fact); + div = CH341_CLKRATE / (clk_div * speed); + + /* Halve base clock (fact = 0) if required. */ + if (div < 9 || div > 255) { + div >>= 1; + clk_div <<= 1; + 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. */ + if (fact == 1 && div % 2 == 0) { + div >>= 1; + fact = 0; + } + + return (0x100 - div) << 8 | fact << 2 | ps; +} + static int ch341_set_baudrate_lcr(struct usb_device *dev, struct ch341_private *priv, u8 lcr) { - short a; + int val; int r; - unsigned long factor; - short divisor; if (!priv->baud_rate) return -EINVAL; - factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate); - divisor = CH341_BAUDBASE_DIVMAX; - while ((factor > 0xfff0) && divisor) { - factor >>= 3; - divisor--; - } - - if (factor > 0xfff0) + val = ch341_get_divisor(priv->baud_rate); + if (val < 0) return -EINVAL; - factor = 0x10000 - factor; - a = (factor & 0xff00) | divisor; - /* * CH341A buffers data until a full endpoint-size packet (32 bytes) * has been received unless bit 7 is set. */ - a |= BIT(7); + val |= BIT(7); - r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x1312, a); + r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x1312, val); if (r) return r; -- 2.23.0