aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ar71xx/patches-3.6/211-ar933x_uart-improve-serial-clock-calculation.patch
blob: 510b75ed0f42ee2256d867d7638758da8bcb6362 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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);