use errors; use fdt; use fmt; use io; use rt; def UART_TXFIFO_FULL: u32 = 0x80000000; def UART_RXFIFO_EMPTY: u32 = 0x80000000; def UART_RXFIFO_DATA: u32 = 0x000000ff; def UART_TXCTRL_TXEN: u32 = 0x1; def UART_RXCTRL_RXEN: u32 = 0x1; def UART_IP_RXWM: u32 = 0x2; export type uart_sifive = struct { txfifo: u32, rxfifo: u32, txctrl: u32, rxctrl: u32, ie: u32, ip: u32, div: u32, }; export type sifive_console = struct { io::stream, clock: u64, namebuf: [8]u8, // "sifive" + up to 2 digits regs: *uart_sifive, }; fn init_sifive(regs: *void) sifive_console = { let cons = sifive_console { writer = &sifive_console_write, regs = regs: *uart_sifive, ... }; rt::writeu32(&cons.regs.txctrl, UART_TXCTRL_TXEN); rt::writeu32(&cons.regs.rxctrl, UART_RXCTRL_RXEN); rt::writeu32(&cons.regs.ie, 0); return cons; }; // Sets the baud rate for a sifive console device. export fn sifive_set_baud(cons: *io::stream, clock: u64, baud: u64) void = { assert(cons.writer == &sifive_console_write); let cons = cons: *sifive_console; rt::writeu32(&cons.regs.div, min_clk_divisor(clock, baud)); }; fn sifive_console_write( cons: *io::stream, buf: const []u8, ) (size | io::error) = { assert(cons.writer == &sifive_console_write); let cons = cons: *sifive_console; let z = 0z; for (let i = 0z; i < len(buf); i += 1) { match (sifive_console_putc(cons, buf[i])) { case errors::busy => // XXX: Might be better to bubble this up i -= 1; continue; case let n: size => z += n; case let err: io::error => return err; }; }; return z; }; fn sifive_console_putc(cons: *sifive_console, c: u8) (size | io::error) = { let txstatus = rt::readu32(&cons.regs.txfifo); if (txstatus & UART_TXFIFO_FULL > 0) { return errors::busy; }; rt::writeu32(&cons.regs.txfifo, c); return 1; }; // Based on the uart driver in the SiFive FSBL fn min_clk_divisor(in_freq: u64, max_target_hz: u64) u32 = { const quot = (in_freq + max_target_hz - 1) / max_target_hz; if (quot == 0) { // Avoids underflow return 0; }; return (quot - 1): u32; }; def SIFIVE_MAX_CONS: size = 16; let sifive_consbuf: [SIFIVE_MAX_CONS]sifive_console = [ sifive_console { regs = null: *uart_sifive, ... }... ]; let sifive_consoles: []sifive_console = []; fn sifive_probe(scan: *fdt::scanner, addr: uintptr) void = { if (len(sifive_consoles) >= SIFIVE_MAX_CONS) { return; }; static append(sifive_consoles, init_sifive(addr: *void)); const cons = &sifive_consoles[len(sifive_consoles) - 1]; static let index: size = 0; register(fmt::bsprintf(cons.namebuf, "sifive{}", index), cons); index += 1; }; const sifive_prober: fdt::prober = fdt::prober { name = "serial", compat = [ "sifive,fu540-c000-uart0", "sifive,fu740-c000-uart", "sifive,uart0", ], probe = &sifive_probe, next = null, }; @init fn sifive_init() void = { sifive_consoles = sifive_consbuf[..0]; fdt::registerprobe(&sifive_prober); };