1/*
2 * Copyright 2004-2008, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 *
5 * Copyright 2012, Alexander von Gluck, kallisti5@unixzen.com
6 * Distributed under the terms of the MIT License.
7 */
8
9
10#include "serial.h"
11
12#include <debug_uart_8250.h>
13#include <board_config.h>
14#include <boot/platform.h>
15#include <arch/cpu.h>
16#include <boot/stage2.h>
17#include <new>
18#include <string.h>
19
20extern "C" {
21#include <fdt.h>
22#include <libfdt.h>
23#include <libfdt_env.h>
24};
25
26
27DebugUART* gUART;
28
29static int32 sSerialEnabled = 0;
30static char sBuffer[16384];
31static uint32 sBufferPosition;
32
33
34static void
35serial_putc(char c)
36{
37	gUART->PutChar(c);
38}
39
40
41extern "C" void
42serial_puts(const char* string, size_t size)
43{
44	if (sSerialEnabled <= 0)
45		return;
46
47	if (sBufferPosition + size < sizeof(sBuffer)) {
48		memcpy(sBuffer + sBufferPosition, string, size);
49		sBufferPosition += size;
50	}
51
52	while (size-- != 0) {
53		char c = string[0];
54
55		if (c == '\n') {
56			serial_putc('\r');
57			serial_putc('\n');
58		} else if (c != '\r')
59			serial_putc(c);
60
61		string++;
62	}
63}
64
65
66extern "C" void
67serial_disable(void)
68{
69	sSerialEnabled = 0;
70}
71
72
73extern "C" void
74serial_enable(void)
75{
76	/* should already be initialized by U-Boot */
77	/*
78	gUART->InitEarly();
79	gUART->InitPort(9600);
80	*/
81	sSerialEnabled++;
82}
83
84
85extern "C" void
86serial_cleanup(void)
87{
88	if (sSerialEnabled <= 0)
89		return;
90
91	gKernelArgs.debug_output = kernel_args_malloc(sBufferPosition);
92	if (gKernelArgs.debug_output != NULL) {
93		memcpy(gKernelArgs.debug_output, sBuffer, sBufferPosition);
94		gKernelArgs.debug_size = sBufferPosition;
95	}
96}
97
98
99static void
100serial_init_fdt(const void *fdt)
101{
102	const char *name;
103	const char *type;
104	int node;
105	int len;
106	phys_addr_t regs;
107	int32 clock = 0;
108	int32 speed = 0;
109	const void *prop;
110
111	if (fdt == NULL)
112		return;
113
114	name = fdt_get_alias(fdt, "serial");
115	if (name == NULL)
116		name = fdt_get_alias(fdt, "serial0");
117	if (name == NULL)
118		name = fdt_get_alias(fdt, "serial1");
119	// TODO: else use /chosen linux,stdout-path
120	if (name == NULL)
121		return;
122
123	node = fdt_path_offset(fdt, name);
124	//dprintf("serial: using '%s', node %d\n", name, node);
125	if (node < 0)
126		return;
127
128	type = (const char *)fdt_getprop(fdt, node, "device_type", &len);
129	//dprintf("serial: type: '%s'\n", type);
130	if (type == NULL || strcmp(type, "serial"))
131		return;
132
133	// determine the MMIO address
134	// TODO: ppc640 use 64bit addressing, but U-Boot seems to map it below 4G,
135	// and the FDT is not very clear. libfdt is also getting 64bit addr support.
136	// so FIXME someday.
137	prop = fdt_getprop(fdt, node, "virtual-reg", &len);
138	if (prop && len == 4) {
139		regs = fdt32_to_cpu(*(uint32_t *)prop);
140		//dprintf("serial: virtual-reg 0x%08llx\n", (int64)regs);
141	} else {
142		prop = fdt_getprop(fdt, node, "reg", &len);
143		if (prop && len >= 4) {
144			regs = fdt32_to_cpu(*(uint32_t *)prop);
145			//dprintf("serial: reg 0x%08llx\n", (int64)regs);
146		} else
147			return;
148	}
149
150	// get the UART clock rate
151	prop = fdt_getprop(fdt, node, "clock-frequency", &len);
152	if (prop && len == 4) {
153		clock = fdt32_to_cpu(*(uint32_t *)prop);
154		//dprintf("serial: clock %ld\n", clock);
155	}
156
157	// get current speed (XXX: not yet passed over)
158	prop = fdt_getprop(fdt, node, "current-speed", &len);
159	if (prop && len == 4) {
160		speed = fdt32_to_cpu(*(uint32_t *)prop);
161		//dprintf("serial: speed %ld\n", speed);
162	}
163
164	if (fdt_node_check_compatible(fdt, node, "ns16550a") == 1
165		|| fdt_node_check_compatible(fdt, node, "ns16550") == 1) {
166		gUART = arch_get_uart_8250(regs, clock);
167		//dprintf("serial: using 8250\n");
168		return;
169	}
170
171}
172
173
174extern "C" void
175serial_init(const void *fdt)
176{
177	// first try with hints from the FDT
178	serial_init_fdt(fdt);
179
180#ifdef BOARD_UART_DEBUG
181	// fallback to hardcoded board UART
182	if (gUART == NULL)
183		gUART = arch_get_uart_8250(BOARD_UART_DEBUG, BOARD_UART_CLOCK);
184#endif
185
186	if (gUART == NULL)
187		return;
188
189	serial_enable();
190}
191