1/*
2 * Copyright 2012, Fran��ois Revol, revol@free.fr.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Fran��ois Revol, revol@free.fr
7 *		Alexander von Gluck IV, kallisti5@unixzen.com
8 */
9
10#include "fdt_serial.h"
11
12#include <KernelExport.h>
13#include <ByteOrder.h>
14#include <ctype.h>
15#include <stdio.h>
16#include <sys/cdefs.h>
17
18#include <arch/generic/debug_uart_8250.h>
19
20#if defined(__arm__)
21#include <arch/arm/arch_uart_pl011.h>
22#endif
23
24extern "C" {
25#include <fdt.h>
26#include <libfdt.h>
27#include <libfdt_env.h>
28};
29
30#include "fdt_support.h"
31
32
33//#define TRACE_SERIAL
34#ifdef TRACE_SERIAL
35#	define TRACE(x...) dprintf("INIT: " x)
36#else
37#	define TRACE(x...) ;
38#endif
39
40
41// If we dprintf before the UART is initalized there will be no output
42
43static DebugUART*
44debug_uart_from_node(const void *fdt, int node)
45{
46	int len;
47	const void *prop;
48	phys_addr_t regs;
49	int32 clock = 0;
50	int32 speed = 0;
51	DebugUART *uart = NULL;
52
53	if (node < 0 || fdt == NULL)
54		return NULL;
55
56	// determine the MMIO address
57	regs = fdt_get_device_reg(fdt, node, false);
58
59	if (regs == 0) {
60		TRACE("%s: FDT UART regs not found!\n", __func__);
61		return NULL;
62	}
63
64	TRACE("serial: checking '%s', node %d @ %" B_PRIxPHYSADDR "\n",
65		name, node, regs);
66
67	// get the UART clock rate
68	prop = fdt_getprop(fdt, node, "clock-frequency", &len);
69	if (prop && len == 4) {
70		clock = fdt32_to_cpu(*(uint32_t *)prop);
71		TRACE("serial: clock %ld\n", clock);
72	}
73
74	// get current speed (XXX: not yet passed over)
75	prop = fdt_getprop(fdt, node, "current-speed", &len);
76	if (prop && len == 4) {
77		speed = fdt32_to_cpu(*(uint32_t *)prop);
78		TRACE("serial: speed %ld\n", speed);
79	}
80
81	// fdt_node_check_compatible returns 0 on match.
82	if (fdt_node_check_compatible(fdt, node, "ns16550a") == 0
83		|| fdt_node_check_compatible(fdt, node, "ns16550") == 0
84		|| fdt_node_check_compatible(fdt, node, "snps,dw-apb-uart") == 0) {
85		TRACE("serial: Found 8250 serial UART!\n");
86		uart = arch_get_uart_8250(regs, clock);
87	#if defined(__arm__)
88	} else if (fdt_node_check_compatible(fdt, node, "ti,omap3-uart") == 0
89		|| fdt_node_check_compatible(fdt, node, "ti,omap4-uart") == 0
90		|| fdt_node_check_compatible(fdt, node, "ti,omap5-uart") == 0
91		|| fdt_node_check_compatible(fdt, node, "ti,am3352-uart") == 0
92		|| fdt_node_check_compatible(fdt, node, "ti,am4372-uart") == 0
93		|| fdt_node_check_compatible(fdt, node, "ti,dra742-uart") == 0) {
94		// TODO: ti,am* and ti,dr* have some special quirks.
95		TRACE("serial: Found omap 8250 serial UART!\n");
96		uart = arch_get_uart_8250_omap(regs, clock);
97	} else if (fdt_node_check_compatible(fdt, node, "arm,pl011") == 0
98		|| fdt_node_check_compatible(fdt, node, "arm,primecell") == 0) {
99		TRACE("serial: Found pl011 serial UART!\n");
100		uart = arch_get_uart_pl011(regs, clock);
101	#endif
102	}
103	return uart;
104}
105
106
107DebugUART*
108debug_uart_from_fdt(const void *fdt)
109{
110	int chosen_node;
111	int node;
112	int len;
113	const char *name;
114	const void *prop;
115	DebugUART *uart = NULL;
116
117	if (fdt == NULL) {
118		TRACE("%s: No FDT found!\n", __func__);
119		return NULL;
120	}
121
122	chosen_node = fdt_path_offset(fdt, "/chosen");
123	if (chosen_node >= 0) {
124		prop = fdt_getprop(fdt, chosen_node, "stdout-path", &len);
125		if (prop && len > 0) {
126			node = fdt_path_offset(fdt, (const char*)prop);
127			uart = debug_uart_from_node(fdt, node);
128		}
129		if (uart == NULL) {
130			prop = fdt_getprop(fdt, chosen_node, "linux,stdout-path", &len);
131			if (prop && len > 0) {
132				node = fdt_path_offset(fdt, (const char*)prop);
133				uart = debug_uart_from_node(fdt, node);
134			}
135		}
136
137		if (uart == NULL) {
138			// From what i've seen, stdout is generally an alias.
139			// we could check for "/..." in the prop, but not sure
140			// it's needed. If we *did* check for a prop starting
141			// with / we could make all three of these "the same"
142			prop = fdt_getprop(fdt, chosen_node, "stdout", &len);
143			if (prop && len > 0) {
144				name = fdt_get_alias(fdt, (const char*)prop);
145				if (name != NULL) {
146					node = fdt_path_offset(fdt, name);
147					uart = debug_uart_from_node(fdt, node);
148				}
149			}
150		}
151
152		// Whoo-hoo! Bail.
153		if (uart != NULL)
154			return uart;
155	}
156
157	// If we didn't find a /chosen serial device, lets search for some common aliases
158	char aliases[][8] = {
159		"serial",
160		"serial0",
161		"uart",
162		"uart0",
163		"serial1",
164		"serial2",
165		"serial3",
166		"uart1",
167		"uart2",
168		"uart3"
169	};
170
171	// For each known common serial alias, check it out and see if we have the
172	// needed driver for it. uart0 seems most common.
173	for (int index = 0; index < sizeof(aliases[0]) / sizeof(aliases); index++) {
174		name = fdt_get_alias(fdt, aliases[index]);
175		if (name == NULL)
176			continue;
177
178		node = fdt_path_offset(fdt, name);
179		if (node < 0) {
180			TRACE("%s: FDT node not found!\n", __func__);
181			continue;
182		}
183		uart = debug_uart_from_node(fdt, node);
184
185		// We found a valid serial device. bail.
186		if (uart != NULL)
187			break;
188	}
189
190	// It would be nice if we had *some* communication mechanism here if uart is still
191	// NULL to warn the user that we couldn't find a serial port.
192
193	return uart;
194}
195