openwrt/target/linux/ar71xx/patches-3.6/211-ar933x_uart-improve-ser...

182 lines
5.0 KiB
Diff

--- 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);