Bring up minos on the Allwinner D1 (Lichee RV) over FEL/xfel and add bidirectional UART so the console can read keystrokes, not just print. D1 / Lichee RV boot: - riscv arch.c: select UART0 base by -DBOARD_D1 (0x02500000 vs QEMU 0x10000000) and add a reg-shift abstraction (uart_rd/uart_wr): the D1's Synopsys DW-8250 uses 32-bit registers at 4-byte spacing, which is what made the first hardware boot silent. - d1_uart0_init(): gate/deassert UART0 clock, mux PB8/PB9 to UART0, set 115200 8N1, clear FIFOs, force non-loopback, drain stale RX. - link-d1.ld: link for DRAM at 0x40000000 (xfel exec target, M-mode). - Makefile: 'lichee' builds the flat binary; 'run-lichee' loads it via xfel (ddr d1 / write / exec). - arch_name() reports the concrete board. UART input (all archs): - arch contract gains arch_uart_getc() and arch_uart_rx_ready(), implemented for riscv, x86 (COM1) and arm (Cadence UART). - console gains getc() and drain_rx(). - kmain() runs an interactive echo loop (seed of the M3 console). Verified: echo works under QEMU on x86/riscv/arm, and on real D1 hardware (banner once, then live keystroke echo over UART0 @115200).
169 lines
5.2 KiB
C
169 lines
5.2 KiB
C
/* arch/riscv/arch.c — RISC-V implementation of the arch contract.
|
|
*
|
|
* QEMU 'virt' exposes a standard 16550-style UART at 0x10000000.
|
|
* (On the real Lichee RV / D1 this address becomes 0x02500000 — same
|
|
* driver, different base; that's the only line that changes for hardware.) */
|
|
#include "arch.h"
|
|
#include "console.h"
|
|
|
|
/* UART base differs by platform. Both are 16550-compatible, so the same
|
|
* driver works; only the MMIO base changes.
|
|
* QEMU 'virt' : 0x10000000
|
|
* Allwinner D1 (Lichee RV) UART0 : 0x02500000
|
|
* Select with -DBOARD_D1 at build time (see 'make lichee'). */
|
|
#ifdef BOARD_D1
|
|
#define UART0_BASE 0x02500000UL
|
|
#else
|
|
#define UART0_BASE 0x10000000UL
|
|
#endif
|
|
/* Register numbers (16550). The BYTE offset depends on the platform's
|
|
* register spacing:
|
|
* QEMU 'virt' : 8-bit registers, 1-byte spacing (reg-shift 0)
|
|
* Allwinner D1: Synopsys DW 8250, 32-bit registers, 4-byte spacing
|
|
* (reg-shift 2) — this is the difference that made the
|
|
* first hardware boot silent. */
|
|
#define UART_RBR 0 /* receive buffer register (read) */
|
|
#define UART_THR 0 /* transmit holding register (write) */
|
|
#define UART_LSR 5 /* line status register */
|
|
#define UART_LSR_DR 0x01 /* data ready (RX byte available) */
|
|
#define UART_LSR_THRE 0x20 /* THR empty */
|
|
|
|
#ifdef BOARD_D1
|
|
#define UART_SHIFT 2
|
|
static inline unsigned int uart_rd(int reg)
|
|
{
|
|
return *(volatile unsigned int *)(UART0_BASE + ((unsigned long)reg << UART_SHIFT));
|
|
}
|
|
static inline void uart_wr(int reg, unsigned int v)
|
|
{
|
|
*(volatile unsigned int *)(UART0_BASE + ((unsigned long)reg << UART_SHIFT)) = v;
|
|
}
|
|
#else
|
|
static inline unsigned int uart_rd(int reg)
|
|
{
|
|
return *(volatile unsigned char *)(UART0_BASE + reg);
|
|
}
|
|
static inline void uart_wr(int reg, unsigned int v)
|
|
{
|
|
*(volatile unsigned char *)(UART0_BASE + reg) = (unsigned char)v;
|
|
}
|
|
#endif
|
|
|
|
#ifdef BOARD_D1
|
|
/* Full UART0 bring-up on the Allwinner D1 (sun20i). The FEL boot ROM does
|
|
* NOT guarantee UART0 is clocked/muxed/baud-set, so we do it ourselves.
|
|
* Register map from the D1 user manual. */
|
|
#define CCU_BASE 0x02001000UL
|
|
#define CCU_UART_BGR (CCU_BASE + 0x090C) /* UART bus gating & reset */
|
|
|
|
#define PIO_BASE 0x02000000UL
|
|
#define PB_CFG1 (PIO_BASE + 0x0034) /* port B, pins 8..15, 4 bits each */
|
|
|
|
#define w32(a, v) (*(volatile unsigned int *)(a) = (unsigned int)(v))
|
|
#define r32(a) (*(volatile unsigned int *)(a))
|
|
|
|
/* DW-8250 register numbers (reg-shift 2 applied by uart_wr/uart_rd). */
|
|
#define UART_DLL 0 /* divisor low (DLAB=1) */
|
|
#define UART_DLH 1 /* divisor high (DLAB=1) */
|
|
#define UART_FCR 2 /* FIFO control (write) */
|
|
#define UART_LCR 3 /* line control */
|
|
#define UART_MCR 4 /* modem control */
|
|
#define UART_MCR_LOOP 0x10 /* internal loopback (TX->RX) */
|
|
|
|
static void d1_uart0_init(void)
|
|
{
|
|
unsigned int v;
|
|
|
|
/* 1. Enable UART0: bus gate (bit0) + deassert reset (bit16). */
|
|
v = r32(CCU_UART_BGR);
|
|
v |= (1u << 16) | (1u << 0);
|
|
w32(CCU_UART_BGR, v);
|
|
|
|
/* 2. Mux PB8=UART0_TX, PB9=UART0_RX to function 6. */
|
|
v = r32(PB_CFG1);
|
|
v &= ~0xFFu; /* clear PB8 [3:0] and PB9 [7:4] */
|
|
v |= (6u << 0) | (6u << 4);
|
|
w32(PB_CFG1, v);
|
|
|
|
/* 3. Program 115200 8N1. UART src = 24 MHz OSC ->
|
|
* divisor = 24000000 / (16 * 115200) ~= 13. */
|
|
uart_wr(UART_LCR, 0x80); /* DLAB = 1 */
|
|
uart_wr(UART_DLL, 13);
|
|
uart_wr(UART_DLH, 0);
|
|
uart_wr(UART_LCR, 0x03); /* 8N1, DLAB = 0 */
|
|
uart_wr(UART_FCR, 0x07); /* enable + clear RX/TX FIFOs */
|
|
|
|
/* Defensive: force normal (non-loopback) operation in case the boot
|
|
* ROM left the modem-control internal-loopback bit set, which would
|
|
* feed TX straight back into RX. */
|
|
uart_wr(UART_MCR, 0x00);
|
|
|
|
/* Drain any stale bytes sitting in the RX FIFO so the first getc()
|
|
* waits for a real keypress instead of returning garbage. */
|
|
while (uart_rd(UART_LSR) & UART_LSR_DR)
|
|
(void)uart_rd(UART_RBR);
|
|
}
|
|
#endif
|
|
|
|
void arch_early_init(void)
|
|
{
|
|
#ifdef BOARD_D1
|
|
d1_uart0_init();
|
|
#endif
|
|
/* QEMU's UART is usable from reset; nothing to do there. */
|
|
}
|
|
|
|
void arch_uart_putc(char c)
|
|
{
|
|
while ((uart_rd(UART_LSR) & UART_LSR_THRE) == 0)
|
|
;
|
|
uart_wr(UART_THR, (unsigned char)c);
|
|
}
|
|
|
|
char arch_uart_getc(void)
|
|
{
|
|
while ((uart_rd(UART_LSR) & UART_LSR_DR) == 0)
|
|
;
|
|
return (char)(uart_rd(UART_RBR) & 0xff);
|
|
}
|
|
|
|
int arch_uart_rx_ready(void)
|
|
{
|
|
return (uart_rd(UART_LSR) & UART_LSR_DR) ? 1 : 0;
|
|
}
|
|
|
|
/* M1: install a minimal machine trap vector (defined in trap.S). */
|
|
extern void trap_entry(void);
|
|
void arch_set_trap_vector(void)
|
|
{
|
|
unsigned long addr = (unsigned long)trap_entry;
|
|
/* mtvec = base | mode(0=direct) */
|
|
__asm__ volatile("csrw mtvec, %0" :: "r"(addr));
|
|
}
|
|
|
|
const char *arch_name(void)
|
|
{
|
|
#ifdef BOARD_D1
|
|
return "riscv64 (Allwinner D1 / Lichee RV)";
|
|
#else
|
|
return "riscv64 (rv64imac, QEMU virt)";
|
|
#endif
|
|
}
|
|
|
|
const char *arch_cpu_mode(void)
|
|
{
|
|
/* RISC-V has no register that reports the current privilege level to
|
|
* software (by design). But mhartid is an M-mode-only CSR: if this
|
|
* code is running and already read it in boot.S without trapping, we
|
|
* are in M-mode. (A trap here would prove otherwise.) */
|
|
unsigned long id;
|
|
__asm__ volatile("csrr %0, mhartid" : "=r"(id));
|
|
(void)id;
|
|
return "M-mode";
|
|
}
|
|
|
|
void arch_halt(void)
|
|
{
|
|
for (;;)
|
|
__asm__ volatile("wfi");
|
|
}
|