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);
};