diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
commit | 1d880992fd8c8457a2d990ac6622cfd58fb1b261 (patch) | |
tree | c4c843b12e96b5612c315db5a23c5da1a900618c /hw/lpc-uart.c | |
download | skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.zip skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.gz skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.bz2 |
Initial commit of Open Source release
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'hw/lpc-uart.c')
-rw-r--r-- | hw/lpc-uart.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/hw/lpc-uart.c b/hw/lpc-uart.c new file mode 100644 index 0000000..f6037e1 --- /dev/null +++ b/hw/lpc-uart.c @@ -0,0 +1,343 @@ +/* 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 <fsp-elog.h> +#include <trace.h> + +DEFINE_LOG_ENTRY(OPAL_RC_UART_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_UART, + OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +/* 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 */ + +static uint32_t uart_base; +static bool has_irq, irq_disabled; + +/* + * 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; + +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 = in_count; + trace_add(&t, TRACE_UART, sizeof(struct trace_uart)); +} + +static inline uint8_t uart_read(unsigned int reg) +{ + return lpc_inb(uart_base + reg); +} + +static inline void uart_write(unsigned int reg, uint8_t val) +{ + lpc_outb(val, uart_base + reg); +} + +static size_t uart_con_write(const char *buf, size_t len) +{ + size_t written = 0; + + while(written < len) { + while ((uart_read(REG_LSR) & LSR_THRE) == 0) { + int i = 0; + + /* Give the simulator some breathing space */ + for (; i < 1000; ++i) + smt_very_low(); + } + smt_medium(); + uart_write(REG_THR, buf[written++]); + }; + + return written; +} + +/* Must be called with console 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 (!has_irq) + return; + + /* If the buffer is full disable the interrupt */ + if (in_count == IN_BUF_SIZE) { + if (!irq_disabled) + uart_write(REG_IER, 0x00); + irq_disabled = true; + } else { + /* Otherwise, enable it */ + if (irq_disabled) + uart_write(REG_IER, 0x01); + irq_disabled = false; + } +} + +/* This is called with the console lock held */ +static size_t uart_con_read(char *buf, size_t len) +{ + size_t read_cnt = 0; + uint8_t lsr = 0; + + if (!in_buf) + return 0; + + /* Read from buffer first */ + if (in_count) { + read_cnt = in_count; + if (len < read_cnt) + read_cnt = len; + memcpy(buf, in_buf, read_cnt); + len -= 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(len) { + lsr = uart_read(REG_LSR); + if ((lsr & LSR_DR) == 0) + break; + buf[read_cnt++] = uart_read(REG_RBR); + len--; + } + + /* Finally, flush whatever's left in the UART into our buffer */ + uart_read_to_buffer(); + + /* Adjust the OPAL event */ + 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); + + uart_trace(TRACE_UART_CTX_READ, read_cnt, irq_disabled, in_count); + + return read_cnt; +} + +static struct con_ops uart_con_driver = { + .read = uart_con_read, + .write = uart_con_write +}; + +bool uart_console_poll(void) +{ + if (!in_buf) + return false; + + /* Grab what's in the UART and stash it into our buffer */ + uart_read_to_buffer(); + + uart_trace(TRACE_UART_CTX_POLL, 0, irq_disabled, in_count); + + return !!in_count; +} + +void uart_irq(void) +{ + if (!in_buf) + return; + + /* This needs locking vs read() */ + lock(&con_lock); + + /* Grab what's in the UART and stash it into our buffer */ + uart_read_to_buffer(); + + /* Set the event if the buffer has anything in it */ + if (in_count) + opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, + OPAL_EVENT_CONSOLE_INPUT); + + uart_trace(TRACE_UART_CTX_IRQ, 0, irq_disabled, in_count); + unlock(&con_lock); +} + +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 */ + return true; + + detect_fail: + prerror("UART: Presence detect failed !\n"); + return false; +} + +void uart_init(bool enable_interrupt) +{ + const struct dt_property *prop; + struct dt_node *n; + char *path __unused; + uint32_t irqchip, irq; + + if (!lpc_present()) + return; + + /* We support only one */ + n = dt_find_compatible_node(dt_root, NULL, "ns16550"); + if (!n) + 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 (!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); + + /* Setup the interrupts properties since HB couldn't do it */ + irqchip = dt_prop_get_u32(n, "ibm,irq-chip-id"); + irq = get_psi_interrupt(irqchip) + P8_IRQ_PSI_HOST_ERR; + printf("UART: IRQ connected to chip %d, irq# is 0x%x\n", irqchip, irq); + if (enable_interrupt) { + dt_add_property_cells(n, "interrupts", irq); + dt_add_property_cells(n, "interrupt-parent", get_ics_phandle()); + } + + if (dummy_console_enabled()) { + /* + * If the dummy console is enabled, 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(n, "status", "reserved"); + + /* + * If the interrupt is enabled, turn on RX interrupts (and + * only these for now + */ + if (enable_interrupt) { + uart_write(REG_IER, 0x01); + has_irq = true; + irq_disabled = false; + } + + /* Allocate an input buffer */ + in_buf = zalloc(IN_BUF_SIZE); + printf("UART: Enabled as OS console\n"); + } else { + /* Else, we expose it as our chosen console */ + dt_add_property_strings(n, "status", "ok"); + path = dt_get_path(n); + dt_add_property_string(dt_chosen, "linux,stdout-path", path); + free(path); + printf("UART: Enabled as OS pass-through\n"); + } +} |