diff options
Diffstat (limited to 'drivers/serial/8250_early.c')
-rw-r--r-- | drivers/serial/8250_early.c | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/drivers/serial/8250_early.c b/drivers/serial/8250_early.c new file mode 100644 index 000000000000..b7a5dd710228 --- /dev/null +++ b/drivers/serial/8250_early.c | |||
@@ -0,0 +1,255 @@ | |||
1 | /* | ||
2 | * Early serial console for 8250/16550 devices | ||
3 | * | ||
4 | * (c) Copyright 2004 Hewlett-Packard Development Company, L.P. | ||
5 | * Bjorn Helgaas <bjorn.helgaas@hp.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 as | ||
9 | * published by the Free Software Foundation. | ||
10 | * | ||
11 | * Based on the 8250.c serial driver, Copyright (C) 2001 Russell King, | ||
12 | * and on early_printk.c by Andi Kleen. | ||
13 | * | ||
14 | * This is for use before the serial driver has initialized, in | ||
15 | * particular, before the UARTs have been discovered and named. | ||
16 | * Instead of specifying the console device as, e.g., "ttyS0", | ||
17 | * we locate the device directly by its MMIO or I/O port address. | ||
18 | * | ||
19 | * The user can specify the device directly, e.g., | ||
20 | * console=uart,io,0x3f8,9600n8 | ||
21 | * console=uart,mmio,0xff5e0000,115200n8 | ||
22 | * or platform code can call early_uart_console_init() to set | ||
23 | * the early UART device. | ||
24 | * | ||
25 | * After the normal serial driver starts, we try to locate the | ||
26 | * matching ttyS device and start a console there. | ||
27 | */ | ||
28 | |||
29 | #include <linux/tty.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/console.h> | ||
32 | #include <linux/serial_core.h> | ||
33 | #include <linux/serial_reg.h> | ||
34 | #include <linux/serial.h> | ||
35 | #include <asm/io.h> | ||
36 | #include <asm/serial.h> | ||
37 | |||
38 | struct early_uart_device { | ||
39 | struct uart_port port; | ||
40 | char options[16]; /* e.g., 115200n8 */ | ||
41 | unsigned int baud; | ||
42 | }; | ||
43 | |||
44 | static struct early_uart_device early_device __initdata; | ||
45 | static int early_uart_registered __initdata; | ||
46 | |||
47 | static unsigned int __init serial_in(struct uart_port *port, int offset) | ||
48 | { | ||
49 | if (port->iotype == UPIO_MEM) | ||
50 | return readb(port->membase + offset); | ||
51 | else | ||
52 | return inb(port->iobase + offset); | ||
53 | } | ||
54 | |||
55 | static void __init serial_out(struct uart_port *port, int offset, int value) | ||
56 | { | ||
57 | if (port->iotype == UPIO_MEM) | ||
58 | writeb(value, port->membase + offset); | ||
59 | else | ||
60 | outb(value, port->iobase + offset); | ||
61 | } | ||
62 | |||
63 | #define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE) | ||
64 | |||
65 | static void __init wait_for_xmitr(struct uart_port *port) | ||
66 | { | ||
67 | unsigned int status; | ||
68 | |||
69 | for (;;) { | ||
70 | status = serial_in(port, UART_LSR); | ||
71 | if ((status & BOTH_EMPTY) == BOTH_EMPTY) | ||
72 | return; | ||
73 | cpu_relax(); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | static void __init putc(struct uart_port *port, unsigned char c) | ||
78 | { | ||
79 | wait_for_xmitr(port); | ||
80 | serial_out(port, UART_TX, c); | ||
81 | } | ||
82 | |||
83 | static void __init early_uart_write(struct console *console, const char *s, unsigned int count) | ||
84 | { | ||
85 | struct uart_port *port = &early_device.port; | ||
86 | unsigned int ier; | ||
87 | |||
88 | /* Save the IER and disable interrupts */ | ||
89 | ier = serial_in(port, UART_IER); | ||
90 | serial_out(port, UART_IER, 0); | ||
91 | |||
92 | while (*s && count-- > 0) { | ||
93 | putc(port, *s); | ||
94 | if (*s == '\n') | ||
95 | putc(port, '\r'); | ||
96 | s++; | ||
97 | } | ||
98 | |||
99 | /* Wait for transmitter to become empty and restore the IER */ | ||
100 | wait_for_xmitr(port); | ||
101 | serial_out(port, UART_IER, ier); | ||
102 | } | ||
103 | |||
104 | static unsigned int __init probe_baud(struct uart_port *port) | ||
105 | { | ||
106 | unsigned char lcr, dll, dlm; | ||
107 | unsigned int quot; | ||
108 | |||
109 | lcr = serial_in(port, UART_LCR); | ||
110 | serial_out(port, UART_LCR, lcr | UART_LCR_DLAB); | ||
111 | dll = serial_in(port, UART_DLL); | ||
112 | dlm = serial_in(port, UART_DLM); | ||
113 | serial_out(port, UART_LCR, lcr); | ||
114 | |||
115 | quot = (dlm << 8) | dll; | ||
116 | return (port->uartclk / 16) / quot; | ||
117 | } | ||
118 | |||
119 | static void __init init_port(struct early_uart_device *device) | ||
120 | { | ||
121 | struct uart_port *port = &device->port; | ||
122 | unsigned int divisor; | ||
123 | unsigned char c; | ||
124 | |||
125 | serial_out(port, UART_LCR, 0x3); /* 8n1 */ | ||
126 | serial_out(port, UART_IER, 0); /* no interrupt */ | ||
127 | serial_out(port, UART_FCR, 0); /* no fifo */ | ||
128 | serial_out(port, UART_MCR, 0x3); /* DTR + RTS */ | ||
129 | |||
130 | divisor = port->uartclk / (16 * device->baud); | ||
131 | c = serial_in(port, UART_LCR); | ||
132 | serial_out(port, UART_LCR, c | UART_LCR_DLAB); | ||
133 | serial_out(port, UART_DLL, divisor & 0xff); | ||
134 | serial_out(port, UART_DLM, (divisor >> 8) & 0xff); | ||
135 | serial_out(port, UART_LCR, c & ~UART_LCR_DLAB); | ||
136 | } | ||
137 | |||
138 | static int __init parse_options(struct early_uart_device *device, char *options) | ||
139 | { | ||
140 | struct uart_port *port = &device->port; | ||
141 | int mapsize = 64; | ||
142 | int mmio, length; | ||
143 | |||
144 | if (!options) | ||
145 | return -ENODEV; | ||
146 | |||
147 | port->uartclk = BASE_BAUD * 16; | ||
148 | if (!strncmp(options, "mmio,", 5)) { | ||
149 | port->iotype = UPIO_MEM; | ||
150 | port->mapbase = simple_strtoul(options + 5, &options, 0); | ||
151 | port->membase = ioremap(port->mapbase, mapsize); | ||
152 | if (!port->membase) { | ||
153 | printk(KERN_ERR "%s: Couldn't ioremap 0x%lx\n", | ||
154 | __FUNCTION__, port->mapbase); | ||
155 | return -ENOMEM; | ||
156 | } | ||
157 | mmio = 1; | ||
158 | } else if (!strncmp(options, "io,", 3)) { | ||
159 | port->iotype = UPIO_PORT; | ||
160 | port->iobase = simple_strtoul(options + 3, &options, 0); | ||
161 | mmio = 0; | ||
162 | } else | ||
163 | return -EINVAL; | ||
164 | |||
165 | if ((options = strchr(options, ','))) { | ||
166 | options++; | ||
167 | device->baud = simple_strtoul(options, 0, 0); | ||
168 | length = min(strcspn(options, " "), sizeof(device->options)); | ||
169 | strncpy(device->options, options, length); | ||
170 | } else { | ||
171 | device->baud = probe_baud(port); | ||
172 | snprintf(device->options, sizeof(device->options), "%u", | ||
173 | device->baud); | ||
174 | } | ||
175 | |||
176 | printk(KERN_INFO "Early serial console at %s 0x%lx (options '%s')\n", | ||
177 | mmio ? "MMIO" : "I/O port", | ||
178 | mmio ? port->mapbase : (unsigned long) port->iobase, | ||
179 | device->options); | ||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | static int __init early_uart_setup(struct console *console, char *options) | ||
184 | { | ||
185 | struct early_uart_device *device = &early_device; | ||
186 | int err; | ||
187 | |||
188 | if (device->port.membase || device->port.iobase) | ||
189 | return 0; | ||
190 | |||
191 | if ((err = parse_options(device, options)) < 0) | ||
192 | return err; | ||
193 | |||
194 | init_port(device); | ||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | static struct console early_uart_console __initdata = { | ||
199 | .name = "uart", | ||
200 | .write = early_uart_write, | ||
201 | .setup = early_uart_setup, | ||
202 | .flags = CON_PRINTBUFFER, | ||
203 | .index = -1, | ||
204 | }; | ||
205 | |||
206 | static int __init early_uart_console_init(void) | ||
207 | { | ||
208 | if (!early_uart_registered) { | ||
209 | register_console(&early_uart_console); | ||
210 | early_uart_registered = 1; | ||
211 | } | ||
212 | return 0; | ||
213 | } | ||
214 | console_initcall(early_uart_console_init); | ||
215 | |||
216 | int __init early_serial_console_init(char *cmdline) | ||
217 | { | ||
218 | char *options; | ||
219 | int err; | ||
220 | |||
221 | options = strstr(cmdline, "console=uart,"); | ||
222 | if (!options) | ||
223 | return -ENODEV; | ||
224 | |||
225 | options = strchr(cmdline, ',') + 1; | ||
226 | if ((err = early_uart_setup(NULL, options)) < 0) | ||
227 | return err; | ||
228 | return early_uart_console_init(); | ||
229 | } | ||
230 | |||
231 | static int __init early_uart_console_switch(void) | ||
232 | { | ||
233 | struct early_uart_device *device = &early_device; | ||
234 | struct uart_port *port = &device->port; | ||
235 | int mmio, line; | ||
236 | |||
237 | if (!(early_uart_console.flags & CON_ENABLED)) | ||
238 | return 0; | ||
239 | |||
240 | /* Try to start the normal driver on a matching line. */ | ||
241 | mmio = (port->iotype == UPIO_MEM); | ||
242 | line = serial8250_start_console(port, device->options); | ||
243 | if (line < 0) | ||
244 | printk("No ttyS device at %s 0x%lx for console\n", | ||
245 | mmio ? "MMIO" : "I/O port", | ||
246 | mmio ? port->mapbase : | ||
247 | (unsigned long) port->iobase); | ||
248 | |||
249 | unregister_console(&early_uart_console); | ||
250 | if (mmio) | ||
251 | iounmap(port->membase); | ||
252 | |||
253 | return 0; | ||
254 | } | ||
255 | late_initcall(early_uart_console_switch); | ||