* [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.