Flash 169 lines
// Mini-UART driver for Raspberry Pi 4
const std = #import("std")
const console = #import("console")
extern fn irq_disable() void
extern fn irq_enable() void
const LINEAR_MAP_BASE u64 = 0xFFFF000000000000
const DEVICE_BASE u64 = 0xFE000000
// Calculate kernel virtual address from physical address
fn pa_to_kva(pa u64) u64 {
return pa + LINEAR_MAP_BASE
}
// Mini-UART registers structure (volatile for MMIO)
const AuxRegs = extern struct {
irq_status u32,
enables u32,
reserved [14]u32,
mu_io u32,
mu_ier u32,
mu_iir u32,
mu_lcr u32,
mu_mcr u32,
mu_lsr u32,
mu_msr u32,
mu_scratch u32,
mu_control u32,
mu_status u32,
mu_baud_rate u32
}
// Get AuxRegs pointer (MMIO)
fn getAuxRegs() *mut volatile AuxRegs {
const aux_addr = pa_to_kva(DEVICE_BASE + 0x00215000)
return #as(*mut volatile AuxRegs, #ptrFromInt(aux_addr))
}
// GPIO functions (C interface)
extern fn gpio_pin_set_func(pin u8, func u8) void
extern fn gpio_pin_enable(pin u8) void
const GFAlt5 u8 = 2
const TXD0 u8 = 14
const RXD0 u8 = 15
/// Initialize mini-UART
export fn mini_uart_init() void {
const aux = getAuxRegs()
// Set GPIO 14 and 15 to UART1 (mini-uart)
gpio_pin_set_func(TXD0, GFAlt5)
gpio_pin_set_func(RXD0, GFAlt5)
// Clear pull-up/pull-down resistors.
gpio_pin_enable(TXD0)
gpio_pin_enable(RXD0)
// Enable mini-uart
aux.enables = 1
// Disable TX and RX and auto flow control
aux.mu_control = 0
// Enable receive interrupts, check bcm errata
aux.mu_ier = 0xD
// Set 8-bit mode
aux.mu_lcr = 3
// Set RTS to always high
aux.mu_mcr = 0
// 115200 @ 500 MHz
aux.mu_baud_rate = 541
// Enable TX and RX
aux.mu_control = 3
// Drain any garbage from the RX FIFO so the interrupt line goes low,
// ensuring we don't miss the first edge-triggered RX interrupt.
while ((aux.mu_lsr & 1) != 0) {
_ = aux.mu_io
}
mini_uart_send('\r')
mini_uart_send('\n')
mini_uart_send('\n')
}
/// Send a single character via UART
export fn mini_uart_send(c u8) void {
const aux = getAuxRegs()
// Keep looping if the 5th bit is 0 (TX FIFO full)
while ((aux.mu_lsr & 0x20) == 0) {}
aux.mu_io = c
}
/// Receive a character via UART
export fn mini_uart_recv() u8 {
const aux = getAuxRegs()
// Keep looping if the 1st bit is 0 (RX FIFO empty)
while ((aux.mu_lsr & 1) == 0) {}
return #as(u8, #truncate(aux.mu_io))
}
/// True iff the mini-UART RX FIFO has at least one byte ready.
/// mu_lsr bit 0 = "data ready" (BCM2837/2711 ARM-Peripherals §2.2.1).
/// Non-blocking — feeds the IRQ-side drain loop in board/rpi4b/irq.zig
/// so console_push can collect every queued byte in one IRQ slot
/// instead of relying on the level-triggered AUX line re-firing.
/// `export` (not `pub`) so irq.zig consumes it via the same
/// `extern fn` discipline as the rest of the UART helpers.
export fn mini_uart_rx_pending() bool {
const aux = getAuxRegs()
return (aux.mu_lsr & 1) != 0
}
/// Drain the mini-UART RX FIFO into the console ring from a non-IRQ
/// (idle-loop) caller — a defensive backstop. The AUX RX interrupt
/// (VC_AUX_IRQ) is the primary delivery path and does reach handle_irq on
/// real hardware, draining into console_push from irq.zig; this poll only
/// matters if a byte is ever left between IRQ slots. IRQs are masked across
/// the drain: console_push assumes an IRQ-masked caller (src/console.zig),
/// so this can't race the AUX handler on the same FIFO/ring. Cheap LSR
/// poll; no-op when the FIFO is empty.
pub fn poll_rx_into_console() void {
irq_disable()
while (mini_uart_rx_pending()) {
console.console_push(mini_uart_recv())
}
irq_enable()
}
/// Send a null-terminated string via UART
export fn mini_uart_send_string(str [*:0]u8) void {
var i usize = 0
while (str[i] != 0) {
const c = str[i]
if (c == '\n') {
// Also do CR if there's a '\n'
mini_uart_send('\r')
}
mini_uart_send(c)
i += 1
}
}
/// Print function compatible with Zig's std.fmt interface
pub fn print(comptime format []u8, args anytype) void {
var buffer [256]u8 = undefined
const formatted = std.fmt.bufPrint(&buffer, format, args) catch {
mini_uart_send_string("format error")
return
}
for c in formatted {
if (c == '\n') {
mini_uart_send('\r')
}
mini_uart_send(c)
}
}