/* 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"); }