[PATCH 2/2] serial: 8250_dw: Improve clock rate setting

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Currently dw8250_set_termios sets the input clock to the nearest
achievable rate to baudx16.  If necessary, the input clock is then
divided down to baudx16 using an integer divider within the UART
device, with the divisor calculated in the 8250 core driver.

However, the clock rate set by dw8250_set_termios and subsequently
divided down could be considerably different to the target baudx16
rate, resulting in incorrect operation.  This patch fixes this by
iteratively searching for an input clock rate that is within +/-1.6%
of an integer multiple of the target baudx16 rate.

Signed-off-by: Ed Blake <ed.blake@xxxxxxxxxxx>
---
 drivers/tty/serial/8250/8250_dw.c | 30 ++++++++++++++++++------------
 1 file changed, 18 insertions(+), 12 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 7e63899..10b0aca 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -256,25 +256,31 @@ static void dw8250_set_termios(struct uart_port *p, struct ktermios *termios,
 			       struct ktermios *old)
 {
 	unsigned int baud = tty_termios_baud_rate(termios);
+	unsigned int target_rate, min_rate, max_rate;
 	struct dw8250_data *d = p->private_data;
 	long rate;
-	int ret;
+	int i, ret;
 
 	if (IS_ERR(d->clk) || !old)
 		goto out;
 
-	clk_disable_unprepare(d->clk);
-	rate = clk_round_rate(d->clk, baud * 16);
-	if (rate < 0)
-		ret = rate;
-	else if (rate == 0)
-		ret = -ENOENT;
-	else
-		ret = clk_set_rate(d->clk, rate);
-	clk_prepare_enable(d->clk);
+	/* Find a clk rate within +/-1.6% of an integer multiple of baudx16 */
+	target_rate = baud * 16;
+	min_rate = target_rate - (target_rate >> 6);
+	max_rate = target_rate + (target_rate >> 6);
 
-	if (!ret)
-		p->uartclk = rate;
+	for (i = 1; i <= UART_DIV_MAX; i++) {
+		rate = clk_round_rate(d->clk, i * target_rate);
+		if (rate >= i * min_rate && rate <= i * max_rate)
+			break;
+	}
+	if (i <= UART_DIV_MAX) {
+		clk_disable_unprepare(d->clk);
+		ret = clk_set_rate(d->clk, rate);
+		clk_prepare_enable(d->clk);
+		if (!ret)
+			p->uartclk = rate;
+	}
 
 out:
 	p->status &= ~UPSTAT_AUTOCTS;
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux PPP]     [Linux FS]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Linmodem]     [Device Mapper]     [Linux Kernel for ARM]

  Powered by Linux