x86: early_serial: extend to support MMIO UART

This expands the early_serial to support MMIO UART, in addition to
port I/O, by duplicating part of the hardware initialization from
the NS16550 UART driver. This allows enabling of early console on
hardware with MMIO-based UARTs.

Signed-off-by: Daniel Leung <daniel.leung@intel.com>
This commit is contained in:
Daniel Leung 2020-05-05 14:14:34 -07:00 committed by Ioannis Glaropoulos
commit 94b744cc0a

View file

@ -8,29 +8,92 @@
#include <kernel.h>
#include <sys/util.h>
#include <soc.h>
#if DT_PROP(DT_CHOSEN(zephyr_console), pcie)
BUILD_ASSERT(IS_ENABLED(CONFIG_PCIE), "NS16550(s) in DT need CONFIG_PCIE");
#define UART_NS16550_PCIE_ENABLED
#include <drivers/pcie/pcie.h>
#define UART_PCIE_BDF (DT_REG_ADDR(DT_CHOSEN(zephyr_console)))
#define UART_PCIE_ID (DT_REG_SIZE(DT_CHOSEN(zephyr_console)))
#endif
/* Super-primitive 8250/16550 serial output-only driver, 115200 8n1 */
#define PORT ((io_port_t)DT_REG_ADDR(DT_INST(0, ns16550)))
#define REG_IER 0x01 /* Interrupt enable reg. */
#define REG_LCR 0x03 /* Line control reg. */
#define REG_MCR 0x04 /* Modem control reg. */
#define REG_LSR 0x05 /* Line status reg. */
#define REG_DL_LO 0x00 /* Divisor latch low byte */
#define REG_DL_HI 0x01 /* Divisor latch high byte */
#define REG_OFFSET_THR 0x00 /* Transmitter holding reg. */
#define REG_OFFSET_IER 0x01 /* Interrupt enable reg. */
#define REG_OFFSET_FCR 0x02 /* FIFO control reg. */
#define REG_OFFSET_LCR 0x03 /* Line control reg. */
#define REG_OFFSET_MCR 0x04 /* Modem control reg. */
#define REG_OFFSET_LSR 0x05 /* Line status reg. */
#define REG_OFFSET_BRDL 0x00 /* Baud rate divisor (LSB) */
#define REG_OFFSET_BRDH 0x01 /* Baud rate divisor (MSB) */
#define IER_DISABLE 0x00
#define LCR_8N1 (BIT(0) | BIT(1))
#define LCR_DLAB_SELECT BIT(7)
#define MCR_DTR BIT(0)
#define MCR_RTS BIT(1)
#define LCR_THRE BIT(5)
#define LSR_THRE BIT(5)
#define FCR_FIFO BIT(0) /* enable XMIT and RCVR FIFO */
#define FCR_RCVRCLR BIT(1) /* clear RCVR FIFO */
#define FCR_XMITCLR BIT(2) /* clear XMIT FIFO */
#define FCR_FIFO_1 0 /* 1 byte in RCVR FIFO */
/* convenience defines */
#define REG_THR(x) (x + REG_OFFSET_THR * UART_REG_ADDR_INTERVAL)
#define REG_IER(x) (x + REG_OFFSET_IER * UART_REG_ADDR_INTERVAL)
#define REG_FCR(x) (x + REG_OFFSET_FCR * UART_REG_ADDR_INTERVAL)
#define REG_LCR(x) (x + REG_OFFSET_LCR * UART_REG_ADDR_INTERVAL)
#define REG_MCR(x) (x + REG_OFFSET_MCR * UART_REG_ADDR_INTERVAL)
#define REG_LSR(x) (x + REG_OFFSET_LSR * UART_REG_ADDR_INTERVAL)
#define REG_BRDL(x) (x + REG_OFFSET_BRDL * UART_REG_ADDR_INTERVAL)
#define REG_BRDH(x) (x + REG_OFFSET_BRDH * UART_REG_ADDR_INTERVAL)
#if DT_NODE_HAS_PROP(DT_CHOSEN(zephyr_console), reg_shift)
#define UART_REG_ADDR_INTERVAL \
(1 << DT_PROP(DT_CHOSEN(zephyr_console), reg_shift))
#endif
#ifdef UART_NS16550_ACCESS_IOPORT
#define PORT ((io_port_t)DT_REG_ADDR(DT_CHOSEN(zephyr_console)))
#define INBYTE(x) sys_in8(x)
#define INWORD(x) sys_in32(x)
#define OUTBYTE(x, d) sys_out8(d, x)
#define OUTWORD(x, d) sys_out32(d, x)
#ifndef UART_REG_ADDR_INTERVAL
#define UART_REG_ADDR_INTERVAL 1 /* address diff of adjacent regs. */
#endif /* UART_REG_ADDR_INTERVAL */
#else
#define PORT ((mm_reg_t)DT_REG_ADDR(DT_CHOSEN(zephyr_console)))
#define INBYTE(x) sys_read8(x)
#define INWORD(x) sys_read32(x)
#define OUTBYTE(x, d) sys_write8(d, x)
#define OUTWORD(x, d) sys_write32(d, x)
#ifndef UART_REG_ADDR_INTERVAL
#define UART_REG_ADDR_INTERVAL 4 /* address diff of adjacent regs. */
#endif
#endif /* UART_NS16550_ACCESS_IOPORT */
#ifdef CONFIG_UART_NS16550_ACCESS_WORD_ONLY
#undef INBYTE
#define INBYTE(x) INWORD(x)
#undef OUTBYTE
#define OUTBYTE(x, d) OUTWORD(x, d)
#endif
#ifdef UART_NS16550_PCIE_ENABLED
static mm_reg_t base;
#else
#define base PORT
#endif
static void serout(int c)
{
while (!(sys_in8(PORT + REG_LSR) & LCR_THRE)) {
while ((INBYTE(REG_LSR(base)) & LSR_THRE) == 0) {
}
sys_out8(c, PORT);
OUTBYTE(REG_THR(base), c);
}
static int console_out(int c)
@ -46,15 +109,25 @@ extern void __printk_hook_install(int (*fn)(int));
void z_x86_early_serial_init(void)
{
/* In fact Qemu already has most of this set up and works by
* default
*/
sys_out8(IER_DISABLE, PORT + REG_IER); /* Disable interrupts */
sys_out8(LCR_DLAB_SELECT, PORT + REG_LCR); /* DLAB select */
sys_out8(1, PORT + REG_DL_LO); /* Baud divisor = 1 */
sys_out8(0, PORT + REG_DL_HI);
sys_out8(LCR_8N1, PORT + REG_LCR); /* LCR = 8n1 + DLAB off */
sys_out8(MCR_DTR | MCR_RTS, PORT + REG_MCR);
#ifdef UART_NS16550_PCIE_ENABLED
if (!pcie_probe(UART_PCIE_BDF, UART_PCIE_ID)) {
return;
}
base = pcie_get_mbar(UART_PCIE_BDF, 0);
pcie_set_cmd(UART_PCIE_BDF, PCIE_CONF_CMDSTAT_MEM, true);
#endif
OUTBYTE(REG_IER(base), IER_DISABLE); /* Disable interrupts */
OUTBYTE(REG_LCR(base), LCR_DLAB_SELECT);/* DLAB select */
OUTBYTE(REG_BRDL(base), 1); /* Baud divisor = 1 */
OUTBYTE(REG_BRDH(base), 0);
OUTBYTE(REG_LCR(base), LCR_8N1); /* LCR = 8n1 + DLAB off */
OUTBYTE(REG_MCR(base), MCR_DTR | MCR_RTS);
/* Turn on FIFO. Some hardware needs this before transmitting */
OUTBYTE(REG_FCR(base),
FCR_FIFO | FCR_FIFO_1 | FCR_RCVRCLR | FCR_XMITCLR);
/* Will be replaced later when a real serial driver comes up */
__printk_hook_install(console_out);