From f6af8fc492d4e37bd0157ec448f61cdc224e6e80 Mon Sep 17 00:00:00 2001
From: Gabor Juhos <juhosg@openwrt.org>
Date: Wed, 22 Aug 2012 19:11:10 +0000
Subject: ar71xx: fix serial clock calculation on AR933x

Closes #12031.

git-svn-id: svn://svn.openwrt.org/openwrt/trunk@33230 3c298f89-4303-0410-b956-a3cf2f4a3e73
---
 ...33x_uart-improve-serial-clock-calculation.patch | 181 +++++++++++++++++++++
 1 file changed, 181 insertions(+)
 create mode 100644 target/linux/ar71xx/patches-3.3/211-ar933x_uart-improve-serial-clock-calculation.patch

diff --git a/target/linux/ar71xx/patches-3.3/211-ar933x_uart-improve-serial-clock-calculation.patch b/target/linux/ar71xx/patches-3.3/211-ar933x_uart-improve-serial-clock-calculation.patch
new file mode 100644
index 0000000000..510b75ed0f
--- /dev/null
+++ b/target/linux/ar71xx/patches-3.3/211-ar933x_uart-improve-serial-clock-calculation.patch
@@ -0,0 +1,181 @@
+--- a/drivers/tty/serial/ar933x_uart.c
++++ b/drivers/tty/serial/ar933x_uart.c
+@@ -25,11 +25,19 @@
+ #include <linux/io.h>
+ #include <linux/irq.h>
+ 
++#include <asm/div64.h>
++
+ #include <asm/mach-ath79/ar933x_uart.h>
+ #include <asm/mach-ath79/ar933x_uart_platform.h>
+ 
+ #define DRIVER_NAME "ar933x-uart"
+ 
++#define AR933X_UART_MAX_SCALE	0xff
++#define AR933X_UART_MAX_STEP	0xffff
++
++#define AR933X_UART_MIN_BAUD	300
++#define AR933X_UART_MAX_BAUD	3000000
++
+ #define AR933X_DUMMY_STATUS_RD	0x01
+ 
+ static struct uart_driver ar933x_uart_driver;
+@@ -37,6 +45,8 @@ static struct uart_driver ar933x_uart_dr
+ struct ar933x_uart_port {
+ 	struct uart_port	port;
+ 	unsigned int		ier;	/* shadow Interrupt Enable Register */
++	unsigned int		min_baud;
++	unsigned int		max_baud;
+ };
+ 
+ static inline unsigned int ar933x_uart_read(struct ar933x_uart_port *up,
+@@ -162,6 +172,57 @@ static void ar933x_uart_enable_ms(struct
+ {
+ }
+ 
++/*
++ * baudrate = (clk / (scale + 1)) * (step * (1 / 2^17))
++ */
++static unsigned long ar933x_uart_get_baud(unsigned int clk,
++					  unsigned int scale,
++					  unsigned int step)
++{
++	u64 t;
++	u32 div;
++
++	div = (2 << 16) * (scale + 1);
++	t = clk;
++	t *= step;
++	t += (div / 2);
++	do_div(t, div);
++
++	return t;
++}
++
++static void ar933x_uart_get_scale_step(unsigned int clk,
++				       unsigned int baud,
++				       unsigned int *scale,
++				       unsigned int *step)
++{
++	unsigned int tscale;
++	long min_diff;
++
++	*scale = 0;
++	*step = 0;
++
++	min_diff = baud;
++	for (tscale = 0; tscale < AR933X_UART_MAX_SCALE; tscale++) {
++		u64 tstep;
++		int diff;
++
++		tstep = baud * (tscale + 1);
++		tstep *= (2 << 16);
++		do_div(tstep, clk);
++
++		if (tstep > AR933X_UART_MAX_STEP)
++			break;
++
++		diff = abs(ar933x_uart_get_baud(clk, tscale, tstep) - baud);
++		if (diff < min_diff) {
++			min_diff = diff;
++			*scale = tscale;
++			*step = tstep;
++		}
++	}
++}
++
+ static void ar933x_uart_set_termios(struct uart_port *port,
+ 				    struct ktermios *new,
+ 				    struct ktermios *old)
+@@ -169,7 +230,7 @@ static void ar933x_uart_set_termios(stru
+ 	struct ar933x_uart_port *up = (struct ar933x_uart_port *) port;
+ 	unsigned int cs;
+ 	unsigned long flags;
+-	unsigned int baud, scale;
++	unsigned int baud, scale, step;
+ 
+ 	/* Only CS8 is supported */
+ 	new->c_cflag &= ~CSIZE;
+@@ -191,8 +252,8 @@ static void ar933x_uart_set_termios(stru
+ 	/* Mark/space parity is not supported */
+ 	new->c_cflag &= ~CMSPAR;
+ 
+-	baud = uart_get_baud_rate(port, new, old, 0, port->uartclk / 16);
+-	scale = (port->uartclk / (16 * baud)) - 1;
++	baud = uart_get_baud_rate(port, new, old, up->min_baud, up->max_baud);
++	ar933x_uart_get_scale_step(port->uartclk, baud, &scale, &step);
+ 
+ 	/*
+ 	 * Ok, we're now changing the port state. Do it with
+@@ -200,6 +261,10 @@ static void ar933x_uart_set_termios(stru
+ 	 */
+ 	spin_lock_irqsave(&up->port.lock, flags);
+ 
++	/* disable the UART */
++	ar933x_uart_rmw_clear(up, AR933X_UART_CS_REG,
++		      AR933X_UART_CS_IF_MODE_M << AR933X_UART_CS_IF_MODE_S);
++
+ 	/* Update the per-port timeout. */
+ 	uart_update_timeout(port, new->c_cflag, baud);
+ 
+@@ -210,7 +275,7 @@ static void ar933x_uart_set_termios(stru
+ 		up->port.ignore_status_mask |= AR933X_DUMMY_STATUS_RD;
+ 
+ 	ar933x_uart_write(up, AR933X_UART_CLOCK_REG,
+-			  scale << AR933X_UART_CLOCK_SCALE_S | 8192);
++			  scale << AR933X_UART_CLOCK_SCALE_S | step);
+ 
+ 	/* setup configuration register */
+ 	ar933x_uart_rmw(up, AR933X_UART_CS_REG, AR933X_UART_CS_PARITY_M, cs);
+@@ -219,6 +284,11 @@ static void ar933x_uart_set_termios(stru
+ 	ar933x_uart_rmw_set(up, AR933X_UART_CS_REG,
+ 			    AR933X_UART_CS_HOST_INT_EN);
+ 
++	/* reenable the UART */
++	ar933x_uart_rmw(up, AR933X_UART_CS_REG,
++		        AR933X_UART_CS_IF_MODE_M << AR933X_UART_CS_IF_MODE_S,
++		        AR933X_UART_CS_IF_MODE_DCE << AR933X_UART_CS_IF_MODE_S);
++
+ 	spin_unlock_irqrestore(&up->port.lock, flags);
+ 
+ 	if (tty_termios_baud_rate(new))
+@@ -401,6 +471,8 @@ static void ar933x_uart_config_port(stru
+ static int ar933x_uart_verify_port(struct uart_port *port,
+ 				   struct serial_struct *ser)
+ {
++	struct ar933x_uart_port *up = (struct ar933x_uart_port *) port;
++
+ 	if (ser->type != PORT_UNKNOWN &&
+ 	    ser->type != PORT_AR933X)
+ 		return -EINVAL;
+@@ -408,7 +480,8 @@ static int ar933x_uart_verify_port(struc
+ 	if (ser->irq < 0 || ser->irq >= NR_IRQS)
+ 		return -EINVAL;
+ 
+-	if (ser->baud_base < 28800)
++	if (ser->baud_base < up->min_baud ||
++	    ser->baud_base > up->max_baud)
+ 		return -EINVAL;
+ 
+ 	return 0;
+@@ -561,6 +634,7 @@ static int __devinit ar933x_uart_probe(s
+ 	struct uart_port *port;
+ 	struct resource *mem_res;
+ 	struct resource *irq_res;
++	unsigned int baud;
+ 	int id;
+ 	int ret;
+ 
+@@ -611,6 +685,12 @@ static int __devinit ar933x_uart_probe(s
+ 	port->fifosize = AR933X_UART_FIFO_SIZE;
+ 	port->ops = &ar933x_uart_ops;
+ 
++	baud = ar933x_uart_get_baud(port->uartclk, AR933X_UART_MAX_SCALE, 1);
++	up->min_baud = max_t(unsigned int, baud, AR933X_UART_MIN_BAUD);
++
++	baud = ar933x_uart_get_baud(port->uartclk, 0, AR933X_UART_MAX_STEP);
++	up->max_baud = min_t(unsigned int, baud, AR933X_UART_MAX_BAUD);
++
+ 	ar933x_uart_add_console_port(up);
+ 
+ 	ret = uart_add_one_port(&ar933x_uart_driver, &up->port);
-- 
cgit v1.2.3