diff mbox

[U-Boot] WIP: fdt: Add serial port controlled by device tree

Message ID 1329806481-12628-1-git-send-email-sjg@chromium.org
State RFC
Headers show

Commit Message

Simon Glass Feb. 21, 2012, 6:41 a.m. UTC
This adds a new console serial port which is implemented by the driver
selected in the device tree. It works by redirecting to another driver.
It might be useful while we don't have proper serial device model and
the drivers that do exist don't understand the device tree.

This patch is provided to assist Hanumant <hanumant07@gmail.com>. A
suitable fdt fragment might be something like:

	aliases {
		console = "/serial@70006300";
	};

	serial@70006300 {
		compatible = "nvidia,tegra250-uart", "ns16550";
		reg = <0x70006000 0x40>;
		id = <0>;
		reg-shift = <2>;
		interrupts = < 68 >;
		clock-frequency = <216000000>;
	};

Signed-off-by: Simon Glass <sjg@chromium.org>
---
 common/fdt_decode.c         |  214 ++++++++++++++++++++++++++++++++++++++++
 drivers/serial/Makefile     |    1 +
 drivers/serial/serial_fdt.c |  228 +++++++++++++++++++++++++++++++++++++++++++
 include/fdtdec.h            |    1 +
 include/serial_fdt.h        |   36 +++++++
 lib/fdtdec.c                |    1 +
 6 files changed, 481 insertions(+), 0 deletions(-)
 create mode 100644 common/fdt_decode.c
 create mode 100644 drivers/serial/serial_fdt.c
 create mode 100644 include/serial_fdt.h

Comments

Mike Frysinger Feb. 21, 2012, 3:47 p.m. UTC | #1
On Tuesday 21 February 2012 01:41:21 Simon Glass wrote:
> --- /dev/null
> +++ b/common/fdt_decode.c
>
> +static struct fdt_compat compat_types[] = {

const

> +void fdt_decode_uart_calc_divisor(struct fdt_uart *uart)
> +{
> +	if (uart->multiplier && uart->baudrate)
> +		uart->divisor = (uart->clock_freq +
> +				(uart->baudrate * (uart->multiplier / 2))) /
> +			(uart->multiplier * uart->baudrate);
> +}

does the multiplier really need to be part of device tree ?

> +int fdt_decode_get_spi_switch(const void *blob, struct fdt_spi_uart
> *config) +{
> +	int node, uart_node;
> +	const u32 *gpio;
> +
> +	node = fdt_node_offset_by_compatible(blob, 0,
> +					     "nvidia,spi-uart-switch");

what's with the hardcoded SoC names in common code ?

> --- /dev/null
> +++ b/drivers/serial/serial_fdt.c
>
> +void uart_calc_divisor(struct fdt_uart *uart)
> +{
> +	if (uart->multiplier && uart->baudrate)
> +		uart->divisor = (uart->clock_freq +
> +				(uart->baudrate * (uart->multiplier / 2))) /
> +			(uart->multiplier * uart->baudrate);
> +}

isn't this exactly the same as fdt_decode_uart_calc_divisor ?
-mike
Simon Glass March 15, 2012, 4:28 a.m. UTC | #2
Hi Mike,

On Tue, Feb 21, 2012 at 7:47 AM, Mike Frysinger <vapier@gentoo.org> wrote:
> On Tuesday 21 February 2012 01:41:21 Simon Glass wrote:
>> --- /dev/null
>> +++ b/common/fdt_decode.c

This whole file was not supposed to be there. I removed it from being
needed but somehow not from the patch, sorry.

>>
>> +static struct fdt_compat compat_types[] = {
>
> const
>
>> +void fdt_decode_uart_calc_divisor(struct fdt_uart *uart)
>> +{
>> +     if (uart->multiplier && uart->baudrate)
>> +             uart->divisor = (uart->clock_freq +
>> +                             (uart->baudrate * (uart->multiplier / 2))) /
>> +                     (uart->multiplier * uart->baudrate);
>> +}
>
> does the multiplier really need to be part of device tree ?
>
>> +int fdt_decode_get_spi_switch(const void *blob, struct fdt_spi_uart
>> *config) +{
>> +     int node, uart_node;
>> +     const u32 *gpio;
>> +
>> +     node = fdt_node_offset_by_compatible(blob, 0,
>> +                                          "nvidia,spi-uart-switch");
>
> what's with the hardcoded SoC names in common code ?
>
>> --- /dev/null
>> +++ b/drivers/serial/serial_fdt.c
>>
>> +void uart_calc_divisor(struct fdt_uart *uart)
>> +{
>> +     if (uart->multiplier && uart->baudrate)
>> +             uart->divisor = (uart->clock_freq +
>> +                             (uart->baudrate * (uart->multiplier / 2))) /
>> +                     (uart->multiplier * uart->baudrate);
>> +}
>
> isn't this exactly the same as fdt_decode_uart_calc_divisor ?

Yes, I copied these functions out.

This patch was just for hanumant but I am going to create a proper
version of this and will post to the list soon.

> -mike

Regards,
Simon
diff mbox

Patch

diff --git a/common/fdt_decode.c b/common/fdt_decode.c
new file mode 100644
index 0000000..2541147
--- /dev/null
+++ b/common/fdt_decode.c
@@ -0,0 +1,214 @@ 
+/*
+ * Copyright (c) 2011 The Chromium OS Authors.
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <common.h>
+#include <serial.h>
+#include <libfdt.h>
+#include <fdt_support.h>
+#include <fdt_decode.h>
+
+/*
+ * Here are the type we know about. One day we might allow drivers to
+ * register. For now we just put them here.
+ */
+static struct fdt_compat compat_types[] = {
+	{ COMPAT_UNKNOWN, "<none>" },
+	{ COMPAT_SPI_UART_SWITCH, "spi-uart-select" },
+	{ COMPAT_SERIAL_NS16550, "ns16550" },
+};
+
+/**
+ * Look in the FDT for an alias with the given name and return its node.
+ *
+ * @param blob	FDT blob
+ * @param name	alias name to look up
+ * @return node offset if found, or an error code < 0 otherwise
+ */
+static int find_alias_node(const void *blob, const char *name)
+{
+	const char *path;
+	int alias_node;
+
+	alias_node = fdt_path_offset(blob, "/aliases");
+	if (alias_node < 0)
+		return alias_node;
+	path = fdt_getprop(blob, alias_node, name, NULL);
+	if (!path)
+		return -FDT_ERR_NOTFOUND;
+	return fdt_path_offset(blob, path);
+}
+
+/**
+ * Look up an address property in a node and return it as an address.
+ * The property must hold exactly one address with no trailing data.
+ * This is only tested on 32-bit machines.
+ *
+ * @param blob	FDT blob
+ * @param node	node to examine
+ * @param prop_name	name of property to find
+ * @return address, if found, or ADDR_T_NONE if not
+ */
+static addr_t get_addr(const void *blob, int node, const char *prop_name)
+{
+	const addr_t *cell;
+	int len;
+
+	cell = fdt_getprop(blob, node, prop_name, &len);
+	if (cell && len != sizeof(addr_t))
+		return addr_to_cpu(*cell);
+	return ADDR_T_NONE;
+}
+
+/**
+ * Look up a 32-bit integer property in a node and return it. The property
+ * must have at least 4 bytes of data. The value of the first cell is
+ * returned.
+ *
+ * @param blob	FDT blob
+ * @param node	node to examine
+ * @param prop_name	name of property to find
+ * @param default_val	default value to return if the property is not found
+ * @return integer value, if found, or default_val if not
+ */
+static s32 get_int(const void *blob, int node, const char *prop_name,
+		s32 default_val)
+{
+	const s32 *cell;
+	int len;
+
+	cell = fdt_getprop(blob, node, prop_name, &len);
+	if (cell && len >= sizeof(s32))
+		return fdt32_to_cpu(cell[0]);
+	return default_val;
+}
+
+/**
+ * Look up a phandle and follow it to its node. Then return the offset
+ * of that node.
+ *
+ * @param blob		FDT blob
+ * @param node		node to examine
+ * @param prop_name	name of property to find
+ * @return node offset if found, -ve error code on error
+ */
+static int lookup_phandle(const void *blob, int node, const char *prop_name)
+{
+	const u32 *phandle;
+	int lookup;
+
+	phandle = fdt_getprop(blob, node, prop_name, NULL);
+	if (!phandle)
+		return -FDT_ERR_NOTFOUND;
+
+	lookup = fdt_node_offset_by_phandle(blob, fdt32_to_cpu(*phandle));
+	return lookup;
+}
+
+/**
+ * Checks whether a node is enabled.
+ * This looks for a 'status' property. If this exists, then returns 1 if
+ * the status is 'ok' and 0 otherwise. If there is no status property,
+ * it returns the default value.
+ *
+ * @param blob	FDT blob
+ * @param node	node to examine
+ * @param default_val	default value to return if no 'status' property exists
+ * @return integer value 0/1, if found, or default_val if not
+ */
+static int get_is_enabled(const void *blob, int node, int default_val)
+{
+	const char *cell;
+
+	cell = fdt_getprop(blob, node, "status", NULL);
+	if (cell)
+		return 0 == strcmp(cell, "ok");
+	return default_val;
+}
+
+void fdt_decode_uart_calc_divisor(struct fdt_uart *uart)
+{
+	if (uart->multiplier && uart->baudrate)
+		uart->divisor = (uart->clock_freq +
+				(uart->baudrate * (uart->multiplier / 2))) /
+			(uart->multiplier * uart->baudrate);
+}
+
+int fdt_decode_uart_console(const void *blob, struct fdt_uart *uart,
+		int default_baudrate)
+{
+	int node;
+
+	node = find_alias_node(blob, "console");
+	if (node < 0)
+		return node;
+	uart->reg = get_addr(blob, node, "reg");
+	uart->id = get_int(blob, node, "id", 0);
+	uart->reg_shift = get_int(blob, node, "reg_shift", 2);
+	uart->baudrate = get_int(blob, node, "baudrate", default_baudrate);
+	uart->clock_freq = get_int(blob, node, "clock-frequency", -1);
+	uart->multiplier = get_int(blob, node, "multiplier", 16);
+	uart->divisor = get_int(blob, node, "divisor", -1);
+	uart->enabled = get_is_enabled(blob, node, 1);
+	uart->interrupt = get_int(blob, node, "interrupts", -1);
+	uart->silent = get_int(blob, node, "silent", 0);
+	uart->compat = fdt_decode_lookup(blob, node);
+
+	/* Calculate divisor if required */
+	if (uart->divisor == -1)
+		fdt_decode_uart_calc_divisor(uart);
+	return 0;
+}
+
+enum fdt_compat_id fdt_decode_lookup(const void *blob, int node)
+{
+	enum fdt_compat_id id;
+
+	/* Search our drivers */
+	for (id = COMPAT_UNKNOWN; id < COMPAT_COUNT; id++)
+		if (0 == fdt_node_check_compatible(blob, node,
+				compat_types[id].name))
+			return id;
+	return COMPAT_UNKNOWN;
+}
+
+int fdt_decode_get_spi_switch(const void *blob, struct fdt_spi_uart *config)
+{
+	int node, uart_node;
+	const u32 *gpio;
+
+	node = fdt_node_offset_by_compatible(blob, 0,
+					     "nvidia,spi-uart-switch");
+	if (node < 0)
+		return node;
+
+	uart_node = lookup_phandle(blob, node, "uart");
+	if (uart_node < 0)
+		return uart_node;
+	config->port = get_int(blob, uart_node, "id", -1);
+	if (config->port == -1)
+		return -FDT_ERR_NOTFOUND;
+	config->gpio = -1;
+	config->regs = (NS16550_t)get_addr(blob, uart_node, "reg");
+	gpio = fdt_getprop(blob, node, "gpios", NULL);
+	if (gpio)
+		config->gpio = fdt32_to_cpu(gpio[1]);
+	return 0;
+}
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 616b857..9135b91 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -55,6 +55,7 @@  COBJS-$(CONFIG_S3C44B0_SERIAL) += serial_s3c44b0.o
 COBJS-$(CONFIG_XILINX_UARTLITE) += serial_xuartlite.o
 COBJS-$(CONFIG_SANDBOX_SERIAL) += sandbox.o
 COBJS-$(CONFIG_SCIF_CONSOLE) += serial_sh.o
+COBJS-$(CONFIG_OF_SERIAL) += serial_fdt.o
 
 ifndef CONFIG_SPL_BUILD
 COBJS-$(CONFIG_USB_TTY) += usbtty.o
diff --git a/drivers/serial/serial_fdt.c b/drivers/serial/serial_fdt.c
new file mode 100644
index 0000000..1ba5bb3
--- /dev/null
+++ b/drivers/serial/serial_fdt.c
@@ -0,0 +1,228 @@ 
+/*
+ * Copyright (c) 2011 The Chromium OS Authors.
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+/*
+ * This is a serial port provided by the flattened device tree. It works
+ * by selecting a compiled in driver according to the setting in the FDT.
+ */
+
+#include <common.h>
+#include <fdtdec.h>
+#include <ns16550.h>
+#include <serial.h>
+#include <serial_fdt.h>
+
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* Information obtained about a UART from the FDT */
+struct fdt_uart {
+	fdt_addr_t reg;	/* address of registers in physical memory */
+	int id;		/* id or port number (numbered from 0, default -1) */
+	int reg_shift;	/* each register is (1 << reg_shift) apart */
+	int baudrate;	/* baud rate, will be gd->baudrate if not defined */
+	int clock_freq;	/* clock frequency, -1 if not provided */
+	int multiplier;	/* divisor multiplier, default 16 */
+	int divisor;	/* baud rate divisor, default calculated */
+	int enabled;	/* 1 to enable, 0 to disable */
+	int interrupt;	/* interrupt line */
+	int silent;	/* 1 for silent UART (supresses output by default) */
+	enum fdt_compat_id compat; /* our selected driver */
+};
+
+/*
+ * We need these structures to be outside BSS since they are accessed before
+ * relocation.
+ */
+static struct serial_device console = {
+	"not_in_bss"
+};
+
+struct fdt_uart console_uart = {
+	-1U
+};
+
+void uart_calc_divisor(struct fdt_uart *uart)
+{
+	if (uart->multiplier && uart->baudrate)
+		uart->divisor = (uart->clock_freq +
+				(uart->baudrate * (uart->multiplier / 2))) /
+			(uart->multiplier * uart->baudrate);
+}
+
+static int decode_uart_console(const void *blob, struct fdt_uart *uart,
+		int default_baudrate)
+{
+	int node;
+
+	node = fdtdec_find_alias_node(blob, "console");
+	if (node < 0)
+		return node;
+	uart->reg = fdtdec_get_addr(blob, node, "reg");
+	uart->id = fdtdec_get_int(blob, node, "id", 0);
+	uart->reg_shift = fdtdec_get_int(blob, node, "reg_shift", 2);
+	uart->baudrate = fdtdec_get_int(blob, node, "baudrate",
+					default_baudrate);
+	uart->clock_freq = fdtdec_get_int(blob, node, "clock-frequency", -1);
+	uart->multiplier = fdtdec_get_int(blob, node, "multiplier", 16);
+	uart->divisor = fdtdec_get_int(blob, node, "divisor", -1);
+	uart->enabled = fdtdec_get_is_enabled(blob, node, 1);
+	uart->interrupt = fdtdec_get_int(blob, node, "interrupts", -1);
+	uart->compat = fdtdec_lookup(blob, node);
+
+	/* Calculate divisor if required */
+	if ((uart->divisor == -1) && (uart->clock_freq != -1))
+		uart_calc_divisor(uart);
+	return 0;
+}
+
+/* Access the console - this may need to be a function */
+#define DECLARE_CONSOLE	struct fdt_uart *uart = &console_uart
+
+/* Initialize the serial port */
+static int fserial_init(void)
+{
+	DECLARE_CONSOLE;
+
+	switch (uart->compat) {
+#ifdef CONFIG_SYS_NS16550
+	case COMPAT_SERIAL_NS16550:
+		NS16550_init((NS16550_t)uart->reg, uart->divisor);
+		break;
+#endif
+	default:
+		break;
+	}
+	return 0;
+}
+
+static void fserial_putc(const char c)
+{
+	DECLARE_CONSOLE;
+
+	if (c == '\n')
+		fserial_putc('\r');
+	switch (uart->compat) {
+#ifdef CONFIG_SYS_NS16550
+	case COMPAT_SERIAL_NS16550:
+		NS16550_putc((NS16550_t)uart->reg, c);
+		break;
+#endif
+	default:
+		break;
+	}
+}
+
+static void fserial_puts(const char *s)
+{
+	while (*s)
+		fserial_putc(*s++);
+}
+
+static int fserial_getc(void)
+{
+	DECLARE_CONSOLE;
+
+	switch (uart->compat) {
+#ifdef CONFIG_SYS_NS16550
+	case COMPAT_SERIAL_NS16550:
+		return NS16550_getc((NS16550_t)uart->reg);
+#endif
+	default:
+		break;
+	}
+	hang();
+
+	return 0;
+}
+
+static int fserial_tstc(void)
+{
+	DECLARE_CONSOLE;
+
+	switch (uart->compat) {
+#ifdef CONFIG_SYS_NS16550
+	case COMPAT_SERIAL_NS16550:
+		return NS16550_tstc((NS16550_t)uart->reg);
+#endif
+	default:
+		break;
+	}
+	return 0;
+}
+
+static void fserial_setbrg(void)
+{
+	DECLARE_CONSOLE;
+
+	uart->baudrate = gd->baudrate;
+	uart_calc_divisor(uart);
+	switch (uart->compat) {
+#ifdef CONFIG_SYS_NS16550
+	case COMPAT_SERIAL_NS16550:
+		NS16550_reinit((NS16550_t)uart->reg, uart->divisor);
+		break;
+#endif
+	default:
+		break;
+	}
+}
+
+static struct serial_device *get_console(void)
+{
+	if (decode_uart_console(gd->fdt_blob, &console_uart,
+			gd->baudrate))
+		return NULL;
+	strcpy(console.name, "serial");
+	console.init = fserial_init;
+	console.uninit = NULL;
+	console.setbrg = fserial_setbrg;
+	console.getc = fserial_getc;
+	console.tstc = fserial_tstc;
+	console.putc = fserial_putc;
+	console.puts = fserial_puts;
+	return &console;
+}
+
+struct serial_device *serial_fdt_get_console_f(void)
+{
+	/* if the uart isn't already set up, do it now */
+	if (console_uart.reg == -1U)
+		return get_console();
+
+	/* otherwise just return the current information */
+	return &console;
+}
+
+
+struct serial_device *serial_fdt_get_console_r(void)
+{
+	/*
+	 * Relocation moves all our function pointers, so we need to set up
+	 * things again. This function will only be called once.
+	 *
+	 * We cannot do the -1 check as in serial_fdt_get_console_f
+	 * because it will be -1 if that function has been ever been called.
+	 * However, the function pointers set up by serial_fdt_get_console_f
+	 * will be pre-relocation values, so we must re-calculate them.
+	 */
+	return get_console();
+}
diff --git a/include/fdtdec.h b/include/fdtdec.h
index d871cdd..e7e8d84 100644
--- a/include/fdtdec.h
+++ b/include/fdtdec.h
@@ -57,6 +57,7 @@  struct fdt_memory {
  */
 enum fdt_compat_id {
 	COMPAT_UNKNOWN,
+	COMPAT_SERIAL_NS16550,
 
 	COMPAT_COUNT,
 };
diff --git a/include/serial_fdt.h b/include/serial_fdt.h
new file mode 100644
index 0000000..c53e0a5
--- /dev/null
+++ b/include/serial_fdt.h
@@ -0,0 +1,36 @@ 
+/*
+ * Copyright (c) 2011 The Chromium OS Authors.
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+
+/*
+ * Provides a serial port defined by an FDT. This makes calls to the
+ * driver selected by the FDT.
+ */
+
+/**
+ * Return a pointer to the serial console device before relocation.
+ */
+struct serial_device *serial_fdt_get_console_f(void);
+
+/**
+ * Return a pointer to the serial console device after relocation.
+ */
+struct serial_device *serial_fdt_get_console_r(void);
diff --git a/lib/fdtdec.c b/lib/fdtdec.c
index 0f87163..d9cb1ad 100644
--- a/lib/fdtdec.c
+++ b/lib/fdtdec.c
@@ -33,6 +33,7 @@  DECLARE_GLOBAL_DATA_PTR;
  */
 #define COMPAT(id, name) name
 static const char * const compat_names[COMPAT_COUNT] = {
+	COMPAT(SERIAL_NS16550, "ns16550"),
 };
 
 /**