]>
Commit | Line | Data |
---|---|---|
3d3befa7 WD |
1 | /* |
2 | * (C) Copyright 2000 | |
3 | * Rob Taylor, Flying Pig Systems. robt@flyingpig.com. | |
4 | * | |
5 | * (C) Copyright 2004 | |
6 | * ARM Ltd. | |
7 | * Philippe Robin, <philippe.robin@arm.com> | |
8 | * | |
1a459660 | 9 | * SPDX-License-Identifier: GPL-2.0+ |
3d3befa7 WD |
10 | */ |
11 | ||
48d0192f | 12 | /* Simple U-Boot driver for the PrimeCell PL010/PL011 UARTs */ |
3d3befa7 WD |
13 | |
14 | #include <common.h> | |
8b616edb | 15 | #include <watchdog.h> |
249d5219 | 16 | #include <asm/io.h> |
39f61477 MV |
17 | #include <serial.h> |
18 | #include <linux/compiler.h> | |
20c9226c | 19 | #include "serial_pl01x.h" |
3d3befa7 | 20 | |
20c9226c AE |
21 | /* |
22 | * Integrator AP has two UARTs, we use the first one, at 38400-8-N-1 | |
23 | * Integrator CP has two UARTs, use the first one, at 38400-8-N-1 | |
24 | * Versatile PB has four UARTs. | |
25 | */ | |
3d3befa7 | 26 | #define CONSOLE_PORT CONFIG_CONS_INDEX |
6705d81e WD |
27 | static volatile unsigned char *const port[] = CONFIG_PL01x_PORTS; |
28 | #define NUM_PORTS (sizeof(port)/sizeof(port[0])) | |
3d3befa7 | 29 | |
20c9226c AE |
30 | static void pl01x_putc (int portnum, char c); |
31 | static int pl01x_getc (int portnum); | |
32 | static int pl01x_tstc (int portnum); | |
249d5219 MW |
33 | unsigned int baudrate = CONFIG_BAUDRATE; |
34 | DECLARE_GLOBAL_DATA_PTR; | |
3d3befa7 | 35 | |
72d5e44c RV |
36 | static struct pl01x_regs *pl01x_get_regs(int portnum) |
37 | { | |
38 | return (struct pl01x_regs *) port[portnum]; | |
39 | } | |
40 | ||
48d0192f | 41 | #ifdef CONFIG_PL010_SERIAL |
3d3befa7 | 42 | |
39f61477 | 43 | static int pl01x_serial_init(void) |
3d3befa7 | 44 | { |
72d5e44c | 45 | struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT); |
42dfe7a1 WD |
46 | unsigned int divisor; |
47 | ||
249d5219 | 48 | /* First, disable everything */ |
72d5e44c | 49 | writel(0, ®s->pl010_cr); |
42dfe7a1 | 50 | |
249d5219 MW |
51 | /* Set baud rate */ |
52 | switch (baudrate) { | |
42dfe7a1 WD |
53 | case 9600: |
54 | divisor = UART_PL010_BAUD_9600; | |
55 | break; | |
56 | ||
57 | case 19200: | |
58 | divisor = UART_PL010_BAUD_9600; | |
59 | break; | |
60 | ||
61 | case 38400: | |
62 | divisor = UART_PL010_BAUD_38400; | |
63 | break; | |
64 | ||
65 | case 57600: | |
66 | divisor = UART_PL010_BAUD_57600; | |
67 | break; | |
68 | ||
69 | case 115200: | |
70 | divisor = UART_PL010_BAUD_115200; | |
71 | break; | |
72 | ||
73 | default: | |
74 | divisor = UART_PL010_BAUD_38400; | |
75 | } | |
76 | ||
72d5e44c RV |
77 | writel((divisor & 0xf00) >> 8, ®s->pl010_lcrm); |
78 | writel(divisor & 0xff, ®s->pl010_lcrl); | |
42dfe7a1 | 79 | |
249d5219 | 80 | /* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */ |
72d5e44c | 81 | writel(UART_PL010_LCRH_WLEN_8 | UART_PL010_LCRH_FEN, ®s->pl010_lcrh); |
42dfe7a1 | 82 | |
249d5219 | 83 | /* Finally, enable the UART */ |
72d5e44c | 84 | writel(UART_PL010_CR_UARTEN, ®s->pl010_cr); |
42dfe7a1 | 85 | |
20c9226c | 86 | return 0; |
3d3befa7 WD |
87 | } |
88 | ||
48d0192f | 89 | #endif /* CONFIG_PL010_SERIAL */ |
20c9226c | 90 | |
48d0192f | 91 | #ifdef CONFIG_PL011_SERIAL |
20c9226c | 92 | |
39f61477 | 93 | static int pl01x_serial_init(void) |
20c9226c | 94 | { |
72d5e44c | 95 | struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT); |
20c9226c AE |
96 | unsigned int temp; |
97 | unsigned int divider; | |
98 | unsigned int remainder; | |
99 | unsigned int fraction; | |
910f1ae3 JR |
100 | unsigned int lcr; |
101 | ||
102 | #ifdef CONFIG_PL011_SERIAL_FLUSH_ON_INIT | |
103 | /* Empty RX fifo if necessary */ | |
104 | if (readl(®s->pl011_cr) & UART_PL011_CR_UARTEN) { | |
105 | while (!(readl(®s->fr) & UART_PL01x_FR_RXFE)) | |
106 | readl(®s->dr); | |
107 | } | |
108 | #endif | |
20c9226c | 109 | |
249d5219 | 110 | /* First, disable everything */ |
72d5e44c | 111 | writel(0, ®s->pl011_cr); |
20c9226c AE |
112 | |
113 | /* | |
249d5219 MW |
114 | * Set baud rate |
115 | * | |
116 | * IBRD = UART_CLK / (16 * BAUD_RATE) | |
117 | * FBRD = RND((64 * MOD(UART_CLK,(16 * BAUD_RATE))) / (16 * BAUD_RATE)) | |
20c9226c | 118 | */ |
249d5219 | 119 | temp = 16 * baudrate; |
20c9226c AE |
120 | divider = CONFIG_PL011_CLOCK / temp; |
121 | remainder = CONFIG_PL011_CLOCK % temp; | |
249d5219 | 122 | temp = (8 * remainder) / baudrate; |
20c9226c AE |
123 | fraction = (temp >> 1) + (temp & 1); |
124 | ||
72d5e44c RV |
125 | writel(divider, ®s->pl011_ibrd); |
126 | writel(fraction, ®s->pl011_fbrd); | |
20c9226c | 127 | |
249d5219 | 128 | /* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */ |
910f1ae3 JR |
129 | lcr = UART_PL011_LCRH_WLEN_8 | UART_PL011_LCRH_FEN; |
130 | writel(lcr, ®s->pl011_lcrh); | |
131 | ||
132 | #ifdef CONFIG_PL011_SERIAL_RLCR | |
133 | { | |
134 | int i; | |
135 | ||
136 | /* | |
137 | * Program receive line control register after waiting | |
138 | * 10 bus cycles. Delay be writing to readonly register | |
139 | * 10 times | |
140 | */ | |
141 | for (i = 0; i < 10; i++) | |
142 | writel(lcr, ®s->fr); | |
143 | ||
144 | writel(lcr, ®s->pl011_rlcr); | |
84dee301 MP |
145 | /* lcrh needs to be set again for change to be effective */ |
146 | writel(lcr, ®s->pl011_lcrh); | |
910f1ae3 JR |
147 | } |
148 | #endif | |
249d5219 | 149 | /* Finally, enable the UART */ |
10501df0 JH |
150 | writel(UART_PL011_CR_UARTEN | UART_PL011_CR_TXE | UART_PL011_CR_RXE | |
151 | UART_PL011_CR_RTS, ®s->pl011_cr); | |
20c9226c AE |
152 | |
153 | return 0; | |
154 | } | |
155 | ||
48d0192f | 156 | #endif /* CONFIG_PL011_SERIAL */ |
20c9226c | 157 | |
39f61477 | 158 | static void pl01x_serial_putc(const char c) |
3d3befa7 WD |
159 | { |
160 | if (c == '\n') | |
20c9226c | 161 | pl01x_putc (CONSOLE_PORT, '\r'); |
3d3befa7 | 162 | |
20c9226c | 163 | pl01x_putc (CONSOLE_PORT, c); |
3d3befa7 WD |
164 | } |
165 | ||
39f61477 | 166 | static int pl01x_serial_getc(void) |
3d3befa7 | 167 | { |
20c9226c | 168 | return pl01x_getc (CONSOLE_PORT); |
3d3befa7 WD |
169 | } |
170 | ||
39f61477 | 171 | static int pl01x_serial_tstc(void) |
3d3befa7 | 172 | { |
20c9226c | 173 | return pl01x_tstc (CONSOLE_PORT); |
3d3befa7 WD |
174 | } |
175 | ||
39f61477 | 176 | static void pl01x_serial_setbrg(void) |
3d3befa7 | 177 | { |
96baa4c3 LW |
178 | struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT); |
179 | ||
249d5219 | 180 | baudrate = gd->baudrate; |
96baa4c3 LW |
181 | /* |
182 | * Flush FIFO and wait for non-busy before changing baudrate to avoid | |
183 | * crap in console | |
184 | */ | |
185 | while (!(readl(®s->fr) & UART_PL01x_FR_TXFE)) | |
186 | WATCHDOG_RESET(); | |
187 | while (readl(®s->fr) & UART_PL01x_FR_BUSY) | |
188 | WATCHDOG_RESET(); | |
249d5219 | 189 | serial_init(); |
3d3befa7 WD |
190 | } |
191 | ||
20c9226c | 192 | static void pl01x_putc (int portnum, char c) |
3d3befa7 | 193 | { |
72d5e44c RV |
194 | struct pl01x_regs *regs = pl01x_get_regs(portnum); |
195 | ||
42dfe7a1 | 196 | /* Wait until there is space in the FIFO */ |
72d5e44c | 197 | while (readl(®s->fr) & UART_PL01x_FR_TXFF) |
8b616edb | 198 | WATCHDOG_RESET(); |
42dfe7a1 WD |
199 | |
200 | /* Send the character */ | |
72d5e44c | 201 | writel(c, ®s->dr); |
3d3befa7 WD |
202 | } |
203 | ||
20c9226c | 204 | static int pl01x_getc (int portnum) |
3d3befa7 | 205 | { |
72d5e44c | 206 | struct pl01x_regs *regs = pl01x_get_regs(portnum); |
42dfe7a1 WD |
207 | unsigned int data; |
208 | ||
209 | /* Wait until there is data in the FIFO */ | |
72d5e44c | 210 | while (readl(®s->fr) & UART_PL01x_FR_RXFE) |
8b616edb | 211 | WATCHDOG_RESET(); |
42dfe7a1 | 212 | |
72d5e44c | 213 | data = readl(®s->dr); |
42dfe7a1 WD |
214 | |
215 | /* Check for an error flag */ | |
216 | if (data & 0xFFFFFF00) { | |
217 | /* Clear the error */ | |
72d5e44c | 218 | writel(0xFFFFFFFF, ®s->ecr); |
42dfe7a1 WD |
219 | return -1; |
220 | } | |
221 | ||
222 | return (int) data; | |
3d3befa7 WD |
223 | } |
224 | ||
20c9226c | 225 | static int pl01x_tstc (int portnum) |
3d3befa7 | 226 | { |
72d5e44c RV |
227 | struct pl01x_regs *regs = pl01x_get_regs(portnum); |
228 | ||
8b616edb | 229 | WATCHDOG_RESET(); |
72d5e44c | 230 | return !(readl(®s->fr) & UART_PL01x_FR_RXFE); |
3d3befa7 | 231 | } |
39f61477 | 232 | |
39f61477 MV |
233 | static struct serial_device pl01x_serial_drv = { |
234 | .name = "pl01x_serial", | |
235 | .start = pl01x_serial_init, | |
236 | .stop = NULL, | |
237 | .setbrg = pl01x_serial_setbrg, | |
238 | .putc = pl01x_serial_putc, | |
ec3fd689 | 239 | .puts = default_serial_puts, |
39f61477 MV |
240 | .getc = pl01x_serial_getc, |
241 | .tstc = pl01x_serial_tstc, | |
242 | }; | |
243 | ||
244 | void pl01x_serial_initialize(void) | |
245 | { | |
246 | serial_register(&pl01x_serial_drv); | |
247 | } | |
248 | ||
249 | __weak struct serial_device *default_serial_console(void) | |
250 | { | |
251 | return &pl01x_serial_drv; | |
252 | } |