All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] tty/serial/ar933x_uart: fix baud rate calculation
@ 2012-11-14  9:38 Gabor Juhos
  0 siblings, 0 replies; only message in thread
From: Gabor Juhos @ 2012-11-14  9:38 UTC (permalink / raw)
  To: Alan Cox; +Cc: Greg Kroah-Hartman, linux-serial, Gabor Juhos

The UART of the AR933x SoC implements a fractional divisor
for generating the desired baud rate.

The current code uses a fixed value for the fractional
part of the divisor, and this leads to improperly
calculated baud rates:

   baud    scale   step  real baud         diff
     300   5207*   8192     17756     17456   5818.66%
     600   2603*   8192     35511     34911   5818.50%
    1200   1301*   8192     71023     69823   5818.58%
    2400    650*   8192     11241      8841    368.37%
    4800    324*   8192     22645     17845    371.77%
    9600    161    8192      9645        45      0.46%
   14400    107    8192     14468        68      0.47%
   19200     80    8192     19290        90      0.46%
   28800     53    8192     28935       135      0.46%
   38400     39    8192     39063       663      1.72%
   57600     26    8192     57870       270      0.46%
  115200     12    8192    120192      4992      4.33%
  230400      5    8192    260417     30017     13.02%
  460800      2    8192    520833     60033     13.02%
  921600      0    8192   1562500    640900     69.93%

After the patch, the integer and fractional parts of the
divisor will be calculated dynamically. This ensures that
the UART will use correct baud rates:

   baud    scale   step  real baud         diff
     300      6      11       300         0      0.00%
     600     54     173       600         0      0.00%
    1200     30     195      1200         0      0.00%
    2400     30     390      2400         0      0.00%
    4800     48    1233      4800         0      0.00%
    9600     78    3976      9600         0      0.00%
   14400     98    7474     14400         0      0.00%
   19200     55    5637     19200         0      0.00%
   28800    130   19780     28800         0      0.00%
   38400     36    7449     38400         0      0.00%
   57600     78   23857     57600         0      0.00%
  115200     43   26575    115200         0      0.00%
  230400     23   28991    230400         0      0.00%
  460800     11   28991    460800         0      0.00%
  921600      5   28991    921599        -1      0.00%

Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
---
 drivers/tty/serial/ar933x_uart.c |   90 +++++++++++++++++++++++++++++++++++---
 1 file changed, 85 insertions(+), 5 deletions(-)

diff --git a/drivers/tty/serial/ar933x_uart.c b/drivers/tty/serial/ar933x_uart.c
index e4f60e2b..0f5b28a 100644
--- 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_driver;
 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 uart_port *port)
 {
 }
 
+/*
+ * 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(struct uart_port *port,
 	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(struct uart_port *port,
 	/* 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(struct uart_port *port,
 	 */
 	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(struct uart_port *port,
 		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(struct uart_port *port,
 	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(struct uart_port *port, int flags)
 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(struct uart_port *port,
 	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(struct platform_device *pdev)
 	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(struct platform_device *pdev)
 	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);
-- 
1.7.10


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2012-11-14  9:47 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-11-14  9:38 [PATCH] tty/serial/ar933x_uart: fix baud rate calculation Gabor Juhos

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.