696 lines
16 KiB
C
696 lines
16 KiB
C
/* Copyright 2013-2014 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <skiboot.h>
|
|
#include <lpc.h>
|
|
#include <console.h>
|
|
#include <opal.h>
|
|
#include <device.h>
|
|
#include <interrupts.h>
|
|
#include <processor.h>
|
|
#include <errorlog.h>
|
|
#include <trace.h>
|
|
#include <timebase.h>
|
|
#include <cpu.h>
|
|
#include <chip.h>
|
|
#include <io.h>
|
|
#include <nvram.h>
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_UART_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_UART,
|
|
OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
|
|
OPAL_NA);
|
|
|
|
/* UART reg defs */
|
|
#define REG_RBR 0
|
|
#define REG_THR 0
|
|
#define REG_DLL 0
|
|
#define REG_IER 1
|
|
#define REG_DLM 1
|
|
#define REG_FCR 2
|
|
#define REG_IIR 2
|
|
#define REG_LCR 3
|
|
#define REG_MCR 4
|
|
#define REG_LSR 5
|
|
#define REG_MSR 6
|
|
#define REG_SCR 7
|
|
|
|
#define LSR_DR 0x01 /* Data ready */
|
|
#define LSR_OE 0x02 /* Overrun */
|
|
#define LSR_PE 0x04 /* Parity error */
|
|
#define LSR_FE 0x08 /* Framing error */
|
|
#define LSR_BI 0x10 /* Break */
|
|
#define LSR_THRE 0x20 /* Xmit holding register empty */
|
|
#define LSR_TEMT 0x40 /* Xmitter empty */
|
|
#define LSR_ERR 0x80 /* Error */
|
|
|
|
#define LCR_DLAB 0x80 /* DLL access */
|
|
|
|
#define IER_RX 0x01
|
|
#define IER_THRE 0x02
|
|
#define IER_ALL 0x0f
|
|
|
|
static struct lock uart_lock = LOCK_UNLOCKED;
|
|
static struct dt_node *uart_node;
|
|
static uint32_t uart_base;
|
|
static bool has_irq = false, irq_ok, rx_full, tx_full;
|
|
static uint8_t tx_room;
|
|
static uint8_t cached_ier;
|
|
static void *mmio_uart_base;
|
|
static int uart_console_policy = UART_CONSOLE_OPAL;
|
|
static int lpc_irq = -1;
|
|
|
|
void uart_set_console_policy(int policy)
|
|
{
|
|
uart_console_policy = policy;
|
|
}
|
|
|
|
static void uart_trace(u8 ctx, u8 cnt, u8 irq_state, u8 in_count)
|
|
{
|
|
union trace t;
|
|
|
|
t.uart.ctx = ctx;
|
|
t.uart.cnt = cnt;
|
|
t.uart.irq_state = irq_state;
|
|
t.uart.in_count = cpu_to_be16(in_count);
|
|
trace_add(&t, TRACE_UART, sizeof(struct trace_uart));
|
|
}
|
|
|
|
static inline uint8_t uart_read(unsigned int reg)
|
|
{
|
|
if (mmio_uart_base)
|
|
return in_8(mmio_uart_base + reg);
|
|
else
|
|
return lpc_inb(uart_base + reg);
|
|
}
|
|
|
|
static inline void uart_write(unsigned int reg, uint8_t val)
|
|
{
|
|
if (mmio_uart_base)
|
|
out_8(mmio_uart_base + reg, val);
|
|
else
|
|
lpc_outb(val, uart_base + reg);
|
|
}
|
|
|
|
static void uart_check_tx_room(void)
|
|
{
|
|
if (uart_read(REG_LSR) & LSR_THRE) {
|
|
/* FIFO is 16 entries */
|
|
tx_room = 16;
|
|
tx_full = false;
|
|
}
|
|
}
|
|
|
|
static void uart_wait_tx_room(void)
|
|
{
|
|
while (!tx_room) {
|
|
uart_check_tx_room();
|
|
if (!tx_room) {
|
|
smt_lowest();
|
|
do {
|
|
barrier();
|
|
uart_check_tx_room();
|
|
} while (!tx_room);
|
|
smt_medium();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void uart_update_ier(void)
|
|
{
|
|
uint8_t ier = 0;
|
|
|
|
if (!has_irq)
|
|
return;
|
|
|
|
/* If we have never got an interrupt, enable them all,
|
|
* the first interrupt received will tell us if interrupts
|
|
* are functional (some boards are missing an EC or FPGA
|
|
* programming causing LPC interrupts not to work).
|
|
*/
|
|
if (!irq_ok)
|
|
ier = IER_ALL;
|
|
if (!rx_full)
|
|
ier |= IER_RX;
|
|
if (tx_full)
|
|
ier |= IER_THRE;
|
|
if (ier != cached_ier) {
|
|
uart_write(REG_IER, ier);
|
|
cached_ier = ier;
|
|
}
|
|
}
|
|
|
|
bool uart_enabled(void)
|
|
{
|
|
return mmio_uart_base || uart_base;
|
|
}
|
|
|
|
/*
|
|
* Internal console driver (output only)
|
|
*/
|
|
static size_t uart_con_write(const char *buf, size_t len)
|
|
{
|
|
size_t written = 0;
|
|
|
|
/* If LPC bus is bad, we just swallow data */
|
|
if (!lpc_ok() && !mmio_uart_base)
|
|
return written;
|
|
|
|
lock(&uart_lock);
|
|
while(written < len) {
|
|
if (tx_room == 0) {
|
|
uart_wait_tx_room();
|
|
if (tx_room == 0)
|
|
goto bail;
|
|
} else {
|
|
uart_write(REG_THR, buf[written++]);
|
|
tx_room--;
|
|
}
|
|
}
|
|
bail:
|
|
unlock(&uart_lock);
|
|
return written;
|
|
}
|
|
|
|
static struct con_ops uart_con_driver = {
|
|
.write = uart_con_write,
|
|
};
|
|
|
|
/*
|
|
* OPAL console driver
|
|
*/
|
|
|
|
/*
|
|
* We implement a simple buffer to buffer input data as some bugs in
|
|
* Linux make it fail to read fast enough after we get an interrupt.
|
|
*
|
|
* We use it on non-interrupt operations as well while at it because
|
|
* it doesn't cost us much and might help in a few cases where Linux
|
|
* is calling opal_poll_events() but not actually reading.
|
|
*
|
|
* Most of the time I expect we'll flush it completely to Linux into
|
|
* it's tty flip buffers so I don't bother with a ring buffer.
|
|
*/
|
|
#define IN_BUF_SIZE 0x1000
|
|
static uint8_t *in_buf;
|
|
static uint32_t in_count;
|
|
|
|
/*
|
|
* We implement a ring buffer for output data as well to speed things
|
|
* up a bit. This allows us to have interrupt driven sends. This is only
|
|
* for the output data coming from the OPAL API, not the internal one
|
|
* which is already bufferred.
|
|
*/
|
|
#define OUT_BUF_SIZE 0x1000
|
|
static uint8_t *out_buf;
|
|
static uint32_t out_buf_prod;
|
|
static uint32_t out_buf_cons;
|
|
|
|
/* Asynchronous flush, uart_lock must be held */
|
|
static int64_t uart_con_flush(void)
|
|
{
|
|
bool tx_was_full = tx_full;
|
|
uint32_t out_buf_cons_initial = out_buf_cons;
|
|
|
|
while(out_buf_prod != out_buf_cons) {
|
|
if (tx_room == 0) {
|
|
/*
|
|
* If the interrupt is not functional,
|
|
* we force a full synchronous flush,
|
|
* otherwise the Linux console isn't
|
|
* usable (too slow).
|
|
*/
|
|
if (irq_ok)
|
|
uart_check_tx_room();
|
|
else
|
|
uart_wait_tx_room();
|
|
}
|
|
if (tx_room == 0) {
|
|
tx_full = true;
|
|
break;
|
|
}
|
|
uart_write(REG_THR, out_buf[out_buf_cons++]);
|
|
out_buf_cons %= OUT_BUF_SIZE;
|
|
tx_room--;
|
|
}
|
|
if (tx_full != tx_was_full)
|
|
uart_update_ier();
|
|
if (out_buf_prod != out_buf_cons) {
|
|
/* Return busy if nothing was flushed this call */
|
|
if (out_buf_cons == out_buf_cons_initial)
|
|
return OPAL_BUSY;
|
|
/* Return partial if there's more to flush */
|
|
return OPAL_PARTIAL;
|
|
}
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
static uint32_t uart_tx_buf_space(void)
|
|
{
|
|
return OUT_BUF_SIZE - 1 -
|
|
(out_buf_prod + OUT_BUF_SIZE - out_buf_cons) % OUT_BUF_SIZE;
|
|
}
|
|
|
|
static int64_t uart_opal_write(int64_t term_number, int64_t *length,
|
|
const uint8_t *buffer)
|
|
{
|
|
size_t written = 0, len = *length;
|
|
|
|
if (term_number != 0)
|
|
return OPAL_PARAMETER;
|
|
|
|
lock(&uart_lock);
|
|
|
|
/* Copy data to out buffer */
|
|
while (uart_tx_buf_space() && len--) {
|
|
out_buf[out_buf_prod++] = *(buffer++);
|
|
out_buf_prod %= OUT_BUF_SIZE;
|
|
written++;
|
|
}
|
|
|
|
/* Flush out buffer again */
|
|
uart_con_flush();
|
|
|
|
unlock(&uart_lock);
|
|
|
|
*length = written;
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
static int64_t uart_opal_write_buffer_space(int64_t term_number,
|
|
int64_t *length)
|
|
{
|
|
if (term_number != 0)
|
|
return OPAL_PARAMETER;
|
|
|
|
lock(&uart_lock);
|
|
*length = uart_tx_buf_space();
|
|
unlock(&uart_lock);
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/* Must be called with UART lock held */
|
|
static void uart_read_to_buffer(void)
|
|
{
|
|
/* As long as there is room in the buffer */
|
|
while(in_count < IN_BUF_SIZE) {
|
|
/* Read status register */
|
|
uint8_t lsr = uart_read(REG_LSR);
|
|
|
|
/* Nothing to read ... */
|
|
if ((lsr & LSR_DR) == 0)
|
|
break;
|
|
|
|
/* Read and add to buffer */
|
|
in_buf[in_count++] = uart_read(REG_RBR);
|
|
}
|
|
|
|
/* If the buffer is full disable the interrupt */
|
|
rx_full = (in_count == IN_BUF_SIZE);
|
|
uart_update_ier();
|
|
}
|
|
|
|
static void uart_adjust_opal_event(void)
|
|
{
|
|
if (in_count)
|
|
opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT,
|
|
OPAL_EVENT_CONSOLE_INPUT);
|
|
else
|
|
opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, 0);
|
|
}
|
|
|
|
/* This is called with the console lock held */
|
|
static int64_t uart_opal_read(int64_t term_number, int64_t *length,
|
|
uint8_t *buffer)
|
|
{
|
|
size_t req_count = *length, read_cnt = 0;
|
|
uint8_t lsr = 0;
|
|
|
|
if (term_number != 0)
|
|
return OPAL_PARAMETER;
|
|
if (!in_buf)
|
|
return OPAL_INTERNAL_ERROR;
|
|
|
|
lock(&uart_lock);
|
|
|
|
/* Read from buffer first */
|
|
if (in_count) {
|
|
read_cnt = in_count;
|
|
if (req_count < read_cnt)
|
|
read_cnt = req_count;
|
|
memcpy(buffer, in_buf, read_cnt);
|
|
req_count -= read_cnt;
|
|
if (in_count != read_cnt)
|
|
memmove(in_buf, in_buf + read_cnt, in_count - read_cnt);
|
|
in_count -= read_cnt;
|
|
}
|
|
|
|
/*
|
|
* If there's still room in the user buffer, read from the UART
|
|
* directly
|
|
*/
|
|
while(req_count) {
|
|
lsr = uart_read(REG_LSR);
|
|
if ((lsr & LSR_DR) == 0)
|
|
break;
|
|
buffer[read_cnt++] = uart_read(REG_RBR);
|
|
req_count--;
|
|
}
|
|
|
|
/* Finally, flush whatever's left in the UART into our buffer */
|
|
uart_read_to_buffer();
|
|
|
|
uart_trace(TRACE_UART_CTX_READ, read_cnt, tx_full, in_count);
|
|
|
|
unlock(&uart_lock);
|
|
|
|
/* Adjust the OPAL event */
|
|
uart_adjust_opal_event();
|
|
|
|
*length = read_cnt;
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
static int64_t uart_opal_flush(int64_t term_number)
|
|
{
|
|
int64_t rc;
|
|
|
|
if (term_number != 0)
|
|
return OPAL_PARAMETER;
|
|
|
|
lock(&uart_lock);
|
|
rc = uart_con_flush();
|
|
unlock(&uart_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void __uart_do_poll(u8 trace_ctx)
|
|
{
|
|
if (!in_buf)
|
|
return;
|
|
|
|
lock(&uart_lock);
|
|
uart_read_to_buffer();
|
|
uart_con_flush();
|
|
uart_trace(trace_ctx, 0, tx_full, in_count);
|
|
unlock(&uart_lock);
|
|
|
|
uart_adjust_opal_event();
|
|
}
|
|
|
|
static void uart_console_poll(void *data __unused)
|
|
{
|
|
__uart_do_poll(TRACE_UART_CTX_POLL);
|
|
}
|
|
|
|
static void uart_irq(uint32_t chip_id __unused, uint32_t irq_mask __unused)
|
|
{
|
|
if (!irq_ok) {
|
|
prlog(PR_DEBUG, "UART: IRQ functional !\n");
|
|
irq_ok = true;
|
|
}
|
|
__uart_do_poll(TRACE_UART_CTX_IRQ);
|
|
}
|
|
|
|
/*
|
|
* Common setup/inits
|
|
*/
|
|
|
|
static void uart_setup_os_passthrough(void)
|
|
{
|
|
char *path;
|
|
|
|
static struct lpc_client uart_lpc_os_client = {
|
|
.reset = NULL,
|
|
.interrupt = NULL,
|
|
.interrupts = 0
|
|
};
|
|
|
|
dt_add_property_strings(uart_node, "status", "ok");
|
|
path = dt_get_path(uart_node);
|
|
dt_add_property_string(dt_chosen, "linux,stdout-path", path);
|
|
free(path);
|
|
|
|
/* Setup LPC client for OS interrupts */
|
|
if (lpc_irq >= 0) {
|
|
uint32_t chip_id = dt_get_chip_id(uart_node);
|
|
uart_lpc_os_client.interrupts = LPC_IRQ(lpc_irq);
|
|
lpc_register_client(chip_id, &uart_lpc_os_client,
|
|
IRQ_ATTR_TARGET_LINUX);
|
|
}
|
|
prlog(PR_DEBUG, "UART: Enabled as OS pass-through\n");
|
|
}
|
|
|
|
static void uart_setup_opal_console(void)
|
|
{
|
|
static struct lpc_client uart_lpc_opal_client = {
|
|
.interrupt = uart_irq,
|
|
};
|
|
|
|
/* Add the opal console node */
|
|
add_opal_console_node(0, "raw", OUT_BUF_SIZE);
|
|
|
|
dt_add_property_string(dt_chosen, "linux,stdout-path",
|
|
"/ibm,opal/consoles/serial@0");
|
|
|
|
/*
|
|
* We mark the UART as reserved since we don't want the
|
|
* kernel to start using it with its own 8250 driver
|
|
*/
|
|
dt_add_property_strings(uart_node, "status", "reserved");
|
|
|
|
/* Allocate an input buffer */
|
|
in_buf = zalloc(IN_BUF_SIZE);
|
|
out_buf = zalloc(OUT_BUF_SIZE);
|
|
|
|
/* Setup LPC client for OPAL interrupts */
|
|
if (lpc_irq >= 0) {
|
|
uint32_t chip_id = dt_get_chip_id(uart_node);
|
|
uart_lpc_opal_client.interrupts = LPC_IRQ(lpc_irq);
|
|
lpc_register_client(chip_id, &uart_lpc_opal_client,
|
|
IRQ_ATTR_TARGET_OPAL);
|
|
has_irq = true;
|
|
}
|
|
|
|
/*
|
|
* If the interrupt is enabled, turn on RX interrupts (and
|
|
* only these for now
|
|
*/
|
|
tx_full = rx_full = false;
|
|
uart_update_ier();
|
|
|
|
/* Start console poller */
|
|
opal_add_poller(uart_console_poll, NULL);
|
|
}
|
|
|
|
static void uart_init_opal_console(void)
|
|
{
|
|
const char *nv_policy;
|
|
|
|
/* Update the policy if the corresponding nvram variable
|
|
* is present
|
|
*/
|
|
nv_policy = nvram_query_dangerous("uart-con-policy");
|
|
if (nv_policy) {
|
|
if (!strcmp(nv_policy, "opal"))
|
|
uart_console_policy = UART_CONSOLE_OPAL;
|
|
else if (!strcmp(nv_policy, "os"))
|
|
uart_console_policy = UART_CONSOLE_OS;
|
|
else
|
|
prlog(PR_WARNING,
|
|
"UART: Unknown console policy in NVRAM: %s\n",
|
|
nv_policy);
|
|
}
|
|
if (uart_console_policy == UART_CONSOLE_OPAL)
|
|
uart_setup_opal_console();
|
|
else
|
|
uart_setup_os_passthrough();
|
|
}
|
|
|
|
struct opal_con_ops uart_opal_con = {
|
|
.name = "OPAL UART console",
|
|
.init = uart_init_opal_console,
|
|
.read = uart_opal_read,
|
|
.write = uart_opal_write,
|
|
.space = uart_opal_write_buffer_space,
|
|
.flush = uart_opal_flush,
|
|
};
|
|
|
|
static bool uart_init_hw(unsigned int speed, unsigned int clock)
|
|
{
|
|
unsigned int dll = (clock / 16) / speed;
|
|
|
|
/* Clear line control */
|
|
uart_write(REG_LCR, 0x00);
|
|
|
|
/* Check if the UART responds */
|
|
uart_write(REG_IER, 0x01);
|
|
if (uart_read(REG_IER) != 0x01)
|
|
goto detect_fail;
|
|
uart_write(REG_IER, 0x00);
|
|
if (uart_read(REG_IER) != 0x00)
|
|
goto detect_fail;
|
|
|
|
uart_write(REG_LCR, LCR_DLAB);
|
|
uart_write(REG_DLL, dll & 0xff);
|
|
uart_write(REG_DLM, dll >> 8);
|
|
uart_write(REG_LCR, 0x03); /* 8N1 */
|
|
uart_write(REG_MCR, 0x03); /* RTS/DTR */
|
|
uart_write(REG_FCR, 0x07); /* clear & en. fifos */
|
|
|
|
/*
|
|
* On some UART implementations[1], we have observed that characters
|
|
* written to the UART during early boot (where no RX path is used,
|
|
* so we don't read from RBR) can cause a character timeout interrupt
|
|
* once we eventually enable interrupts through the IER. This
|
|
* interrupt can only be cleared by reading from RBR (even though we've
|
|
* cleared the RX FIFO!).
|
|
*
|
|
* Unfortunately though, the LCR[DR] bit does *not* indicate that there
|
|
* are characters to be read from RBR, so we may never read it, so the
|
|
* interrupt continuously fires.
|
|
*
|
|
* So, manually clear the timeout interrupt by reading the RBR here.
|
|
* We discard the read data, but that shouldn't matter as we've just
|
|
* reset the FIFO anyway.
|
|
*
|
|
* 1: seen on the AST2500 SUART. I assume this applies to 2400 too.
|
|
*/
|
|
uart_read(REG_RBR);
|
|
|
|
return true;
|
|
|
|
detect_fail:
|
|
prerror("UART: Presence detect failed !\n");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* early_uart_init() is similar to uart_init() in that it configures skiboot
|
|
* console log to output via a UART. The main differences are that the early
|
|
* version only works with MMIO UARTs and will not setup interrupts or locks.
|
|
*/
|
|
void early_uart_init(void)
|
|
{
|
|
struct dt_node *uart_node;
|
|
u32 clk, baud;
|
|
|
|
uart_node = dt_find_compatible_node(dt_root, NULL, "ns16550");
|
|
if (!uart_node)
|
|
return;
|
|
|
|
/* Try translate the address, if this fails then it's not a MMIO UART */
|
|
mmio_uart_base = (void *) dt_translate_address(uart_node, 0, NULL);
|
|
if (!mmio_uart_base)
|
|
return;
|
|
|
|
clk = dt_prop_get_u32(uart_node, "clock-frequency");
|
|
baud = dt_prop_get_u32(uart_node, "current-speed");
|
|
|
|
if (uart_init_hw(baud, clk)) {
|
|
set_console(&uart_con_driver);
|
|
prlog(PR_DEBUG, "UART: Using UART at %p\n", mmio_uart_base);
|
|
} else {
|
|
prerror("UART: Early init failed!");
|
|
mmio_uart_base = NULL;
|
|
}
|
|
}
|
|
|
|
void uart_init(void)
|
|
{
|
|
const struct dt_property *prop;
|
|
struct dt_node *n;
|
|
char *path __unused;
|
|
const uint32_t *irqp;
|
|
|
|
/* Clean up after early_uart_init() */
|
|
mmio_uart_base = NULL;
|
|
|
|
/* UART lock is in the console path and thus must block
|
|
* printf re-entrancy
|
|
*/
|
|
uart_lock.in_con_path = true;
|
|
|
|
/* We support only one */
|
|
uart_node = n = dt_find_compatible_node(dt_root, NULL, "ns16550");
|
|
if (!n)
|
|
return;
|
|
|
|
/* Read the interrupts property if any */
|
|
irqp = dt_prop_get_def(n, "interrupts", NULL);
|
|
|
|
/* Now check if the UART is on the root bus. This is the case of
|
|
* directly mapped UARTs in simulation environments
|
|
*/
|
|
if (n->parent == dt_root) {
|
|
printf("UART: Found at root !\n");
|
|
mmio_uart_base = (void *)dt_translate_address(n, 0, NULL);
|
|
if (!mmio_uart_base) {
|
|
printf("UART: Failed to translate address !\n");
|
|
return;
|
|
}
|
|
|
|
/* If it has an interrupt properly, we consider this to be
|
|
* a direct XICS/XIVE interrupt
|
|
*/
|
|
if (irqp)
|
|
has_irq = true;
|
|
|
|
} else {
|
|
if (!lpc_present())
|
|
return;
|
|
|
|
/* Get IO base */
|
|
prop = dt_find_property(n, "reg");
|
|
if (!prop) {
|
|
log_simple_error(&e_info(OPAL_RC_UART_INIT),
|
|
"UART: Can't find reg property\n");
|
|
return;
|
|
}
|
|
if (dt_property_get_cell(prop, 0) != OPAL_LPC_IO) {
|
|
log_simple_error(&e_info(OPAL_RC_UART_INIT),
|
|
"UART: Only supports IO addresses\n");
|
|
return;
|
|
}
|
|
uart_base = dt_property_get_cell(prop, 1);
|
|
|
|
if (irqp) {
|
|
lpc_irq = be32_to_cpu(*irqp);
|
|
prlog(PR_DEBUG, "UART: Using LPC IRQ %d\n", lpc_irq);
|
|
}
|
|
}
|
|
|
|
|
|
if (!uart_init_hw(dt_prop_get_u32(n, "current-speed"),
|
|
dt_prop_get_u32(n, "clock-frequency"))) {
|
|
prerror("UART: Initialization failed\n");
|
|
dt_add_property_strings(n, "status", "bad");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Mark LPC used by the console (will mark the relevant
|
|
* locks to avoid deadlocks when flushing the console)
|
|
*/
|
|
lpc_used_by_console();
|
|
|
|
/* Install console backend for printf() */
|
|
set_console(&uart_con_driver);
|
|
}
|
|
|