serial: mediatek: enable baudrate accuracy compensation
The high-speed UART from MediaTek supports baudrate accuracy
compensation when using high-speed mode 3.
This is done by calculating the first digit of the fraction part of
sample count value. The fraction value will be then used as the
reference to insert 0 to 10 sample cycle(s) to one frame (assume
that frame format is 8n1, i.e. 10 bits per frame).
The fracdiv_[l/m] registers are used to determine whether a bit in one frame
should be inserted with one sample cycle.
With typical 40MHz source clock, the actual baudrates with/without
accuracy compensation are:
Ideal w/o compensation w/ compensation
======== ================ ===============
9600 9603 9600
115200 114942 115207
921600 930232 921659
3000000 3076923 3007519
diff --git a/drivers/serial/serial_mtk.c b/drivers/serial/serial_mtk.c
index 9d379f2..01cc415 100644
--- a/drivers/serial/serial_mtk.c
+++ b/drivers/serial/serial_mtk.c
@@ -99,10 +99,18 @@
bool upstream_highspeed_logic;
};
+static const unsigned short fraction_l_mapping[] = {
+ 0, 1, 0x5, 0x15, 0x55, 0x57, 0x57, 0x77, 0x7F, 0xFF, 0xFF
+};
+
+static const unsigned short fraction_m_mapping[] = {
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3
+};
+
static void _mtk_serial_setbrg(struct mtk_serial_priv *priv, int baud,
uint clk_rate)
{
- u32 quot, realbaud, samplecount = 1;
+ u32 quot, realbaud, samplecount = 1, fraction, frac_l = 0, frac_m = 0;
/* Special case for low baud clock */
if (baud <= 115200 && clk_rate == 12000000) {
@@ -147,7 +155,13 @@
writel(3, &priv->regs->highspeed);
quot = DIV_ROUND_UP(clk_rate, 256 * baud);
- samplecount = DIV_ROUND_CLOSEST(clk_rate, quot * baud);
+ samplecount = clk_rate / (quot * baud);
+
+ fraction = ((clk_rate * 100) / quot / baud) % 100;
+ fraction = DIV_ROUND_CLOSEST(fraction, 10);
+
+ frac_l = fraction_l_mapping[fraction];
+ frac_m = fraction_m_mapping[fraction];
}
set_baud:
@@ -159,7 +173,11 @@
/* set highspeed mode sample count & point */
writel(samplecount - 1, &priv->regs->sample_count);
- writel((samplecount - 2) >> 1, &priv->regs->sample_point);
+ writel((samplecount >> 1) - 1, &priv->regs->sample_point);
+
+ /* set baudrate fraction compensation */
+ writel(frac_l, &priv->regs->fracdiv_l);
+ writel(frac_m, &priv->regs->fracdiv_m);
}
static int _mtk_serial_putc(struct mtk_serial_priv *priv, const char ch)