From d1ca3b9ebee9c6f3fe447e6ea00816f655678519 Mon Sep 17 00:00:00 2001 From: Michael Panzlaff Date: Mon, 12 Aug 2019 15:48:18 +0200 Subject: allow additional ns16550a config via device tree This commit makes bbl read some additional fields from the device tree if it detects an ns16550a: - reg-shift - reg-offset - clock-frequency For explanation of these check out the Linux Kernel doc: https://www.kernel.org/doc/Documentation/devicetree/bindings/serial/8250.txt In particular this allows the Xilinx AXI UART 16550 to act as serial console with bbl and the Linux early boot console. This also fixes a bug in which bbl will ignore any other than the first "compatible" string when iterating over the nodes. Previously this line would not have worked: compatible = "xlnx,xps-uart16550-2.00.a", "ns16550a"; Before bbl would have just checked the first field instead of checking all strings in the list. --- machine/fdt.c | 5 +++++ machine/fdt.h | 1 + machine/uart16550.c | 62 +++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 54 insertions(+), 14 deletions(-) (limited to 'machine') diff --git a/machine/fdt.c b/machine/fdt.c index e8a504f..d5beda1 100644 --- a/machine/fdt.c +++ b/machine/fdt.c @@ -126,6 +126,11 @@ const uint32_t *fdt_get_size(const struct fdt_scan_node *node, const uint32_t *v return value; } +uint32_t fdt_get_value(const struct fdt_scan_prop *prop, uint32_t index) +{ + return bswap(prop->value[index]); +} + int fdt_string_list_index(const struct fdt_scan_prop *prop, const char *str) { const char *list = (const char *)prop->value; diff --git a/machine/fdt.h b/machine/fdt.h index 97aa70d..056f292 100644 --- a/machine/fdt.h +++ b/machine/fdt.h @@ -54,6 +54,7 @@ uint32_t fdt_size(uintptr_t fdt); // Extract fields const uint32_t *fdt_get_address(const struct fdt_scan_node *node, const uint32_t *base, uint64_t *value); const uint32_t *fdt_get_size(const struct fdt_scan_node *node, const uint32_t *base, uint64_t *value); +uint32_t fdt_get_value(const struct fdt_scan_prop *prop, uint32_t index); int fdt_string_list_index(const struct fdt_scan_prop *prop, const char *str); // -1 if not found // Setup memory+clint+plic diff --git a/machine/uart16550.c b/machine/uart16550.c index 16c6186..44685eb 100644 --- a/machine/uart16550.c +++ b/machine/uart16550.c @@ -1,26 +1,42 @@ // See LICENSE for license details. #include +#include +#include #include "uart16550.h" #include "fdt.h" volatile uint8_t* uart16550; +// some devices require a shifted register index +// (e.g. 32 bit registers instead of 8 bit registers) +static uint32_t uart16550_reg_shift; +static uint32_t uart16550_clock = 1843200; // a "common" base clock -#define UART_REG_QUEUE 0 -#define UART_REG_LINESTAT 5 +#define UART_REG_QUEUE 0 // rx/tx fifo data +#define UART_REG_DLL 0 // divisor latch (LSB) +#define UART_REG_IER 1 // interrupt enable register +#define UART_REG_DLM 1 // divisor latch (MSB) +#define UART_REG_FCR 2 // fifo control register +#define UART_REG_LCR 3 // line control register +#define UART_REG_MCR 4 // modem control register +#define UART_REG_LSR 5 // line status register +#define UART_REG_MSR 6 // modem status register +#define UART_REG_SCR 7 // scratch register #define UART_REG_STATUS_RX 0x01 #define UART_REG_STATUS_TX 0x20 +#define UART_DEFAULT_BAUD 38400 + void uart16550_putchar(uint8_t ch) { - while ((uart16550[UART_REG_LINESTAT] & UART_REG_STATUS_TX) == 0); - uart16550[UART_REG_QUEUE] = ch; + while ((uart16550[UART_REG_LSR << uart16550_reg_shift] & UART_REG_STATUS_TX) == 0); + uart16550[UART_REG_QUEUE << uart16550_reg_shift] = ch; } int uart16550_getchar() { - if (uart16550[UART_REG_LINESTAT] & UART_REG_STATUS_RX) - return uart16550[UART_REG_QUEUE]; + if (uart16550[UART_REG_LSR << uart16550_reg_shift] & UART_REG_STATUS_RX) + return uart16550[UART_REG_QUEUE << uart16550_reg_shift]; return -1; } @@ -28,6 +44,9 @@ struct uart16550_scan { int compat; uint64_t reg; + uint32_t reg_offset; + uint32_t reg_shift; + uint32_t clock_freq; }; static void uart16550_open(const struct fdt_scan_node *node, void *extra) @@ -39,26 +58,41 @@ static void uart16550_open(const struct fdt_scan_node *node, void *extra) static void uart16550_prop(const struct fdt_scan_prop *prop, void *extra) { struct uart16550_scan *scan = (struct uart16550_scan *)extra; - if (!strcmp(prop->name, "compatible") && !strcmp((const char*)prop->value, "ns16550a")) { + if (!strcmp(prop->name, "compatible") && fdt_string_list_index(prop, "ns16550a") != -1) { scan->compat = 1; } else if (!strcmp(prop->name, "reg")) { fdt_get_address(prop->node->parent, prop->value, &scan->reg); + } else if (!strcmp(prop->name, "reg-shift")) { + scan->reg_shift = fdt_get_value(prop, 0); + } else if (!strcmp(prop->name, "reg-offset")) { + scan->reg_offset = fdt_get_value(prop, 0); + } else if (!strcmp(prop->name, "clock-frequency")) { + scan->clock_freq = fdt_get_value(prop, 0); } } static void uart16550_done(const struct fdt_scan_node *node, void *extra) { + uint32_t clock_freq; struct uart16550_scan *scan = (struct uart16550_scan *)extra; if (!scan->compat || !scan->reg || uart16550) return; - uart16550 = (void*)(uintptr_t)scan->reg; + if (scan->clock_freq != 0) + uart16550_clock = scan->clock_freq; + // if device tree doesn't supply a clock, fallback to default clock of 1843200 + + uint32_t divisor = uart16550_clock / (16 * UART_DEFAULT_BAUD); + assert (divisor < 0x10000u); + + uart16550 = (void*)((uintptr_t)scan->reg + scan->reg_offset); + uart16550_reg_shift = scan->reg_shift; // http://wiki.osdev.org/Serial_Ports - uart16550[1] = 0x00; // Disable all interrupts - uart16550[3] = 0x80; // Enable DLAB (set baud rate divisor) - uart16550[0] = 0x03; // Set divisor to 3 (lo byte) 38400 baud - uart16550[1] = 0x00; // (hi byte) - uart16550[3] = 0x03; // 8 bits, no parity, one stop bit - uart16550[2] = 0xC7; // Enable FIFO, clear them, with 14-byte threshold + uart16550[UART_REG_IER << uart16550_reg_shift] = 0x00; // Disable all interrupts + uart16550[UART_REG_LCR << uart16550_reg_shift] = 0x80; // Enable DLAB (set baud rate divisor) + uart16550[UART_REG_DLL << uart16550_reg_shift] = (uint8_t)divisor; // Set divisor (lo byte) + uart16550[UART_REG_DLM << uart16550_reg_shift] = (uint8_t)(divisor >> 8); // (hi byte) + uart16550[UART_REG_LCR << uart16550_reg_shift] = 0x03; // 8 bits, no parity, one stop bit + uart16550[UART_REG_FCR << uart16550_reg_shift] = 0xC7; // Enable FIFO, clear them, with 14-byte threshold } void query_uart16550(uintptr_t fdt) -- cgit v1.1