sylveos

Toy Operating System
Log | Files | Refs

commit 028d6c9bb01216cb0faefbe9a5f0bff256b72cf1
parent 26e061b4f8959a6f009285f6ce88ccc628069065
Author: Sylvia Ivory <git@sivory.net>
Date:   Tue, 20 Jan 2026 17:27:19 -0800

Add UART maybe

Diffstat:
Msrc/devices/gpio.zig | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Asrc/devices/mini-uart.zig | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/devices/timer.zig | 6+++---
Msrc/main.zig | 7++++++-
4 files changed, 210 insertions(+), 22 deletions(-)

diff --git a/src/devices/gpio.zig b/src/devices/gpio.zig @@ -1,34 +1,22 @@ const std = @import("std"); const mem = @import("pi").mem; +pub const Error = error{ + PinOutOfRange, +}; + // Page 90, Table 6-1: GPIO Register Assignment const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0020_0000; const GPFSEL0: usize = BASE_ADDRESS + 0x0000; const GPSET0: usize = BASE_ADDRESS + 0x001C; const GPCLR0: usize = BASE_ADDRESS + 0x0028; const GPLEV0: usize = BASE_ADDRESS + 0x0034; +const GPPUD: usize = BASE_ADDRESS + 0x0094; +const GPPUDCLK0: usize = BASE_ADDRESS + 0x0098; pub const MAX_GPIO: u8 = 53; const GPIO_PER_FSEL: u8 = 10; -// Page 92, Table 6-2: GPIO Alternate function select register 0 -// All function select registers reflect this bit layout -const FunctionSelect = enum(u3) { - input = 0b000, - output = 0b001, - - alt_fn_0 = 0b100, - alt_fn_1 = 0b101, - alt_fn_2 = 0b110, - alt_fn_3 = 0b111, - alt_fn_4 = 0b011, - alt_fn_5 = 0b010, -}; - -pub const Error = error{ - PinOutOfRange, -}; - fn gpio_check(pin: u8) Error!void { if (pin > MAX_GPIO) return Error.PinOutOfRange; } @@ -43,6 +31,20 @@ fn get_address(pin: u8, offset: usize, chunk: ?u32) Error!*u32 { return address; } +// Page 92, Table 6-2: GPIO Alternate function select register 0 +// All function select registers reflect this bit layout +pub const FunctionSelect = enum(u3) { + input = 0b000, + output = 0b001, + + alt_fn_0 = 0b100, + alt_fn_1 = 0b101, + alt_fn_2 = 0b110, + alt_fn_3 = 0b111, + alt_fn_4 = 0b011, + alt_fn_5 = 0b010, +}; + pub fn fn_sel(pin: u8, sel: FunctionSelect) Error!void { const address = try get_address(pin, GPFSEL0, GPIO_PER_FSEL); @@ -107,3 +109,39 @@ pub fn write(pin: u8, state: bool) Error!void { try set_off(pin); } } + +pub const PullMode = enum(u2) { + Off = 0b00, + Down = 0b01, + Up = 0b10, +}; + +fn wait(count: usize) void { + var c = count; + while (c != 0) { + c -= 1; + // does this cook us? + std.atomic.spinLoopHint(); + } +} + +pub fn set_pull(pin: u8, pull: PullMode) Error!void { + try gpio_check(pin); + + // Set GPPUD + mem.put_u32(@ptrFromInt(GPPUD), @intFromEnum(pull)); + + // Wait 150 cycles + wait(150); + + // Set GPPUDCLK + const GPPUDCLK = try get_address(pin, GPPUDCLK0, null); + addr_bitset(pin, GPPUDCLK); + + // Wait 150 cycles + wait(150); + + // Clear GPPUD & GPPUDCLK + mem.put_u32(GPPUDCLK, 0); + mem.put_u32(@ptrFromInt(GPPUDCLK0), 0); +} diff --git a/src/devices/mini-uart.zig b/src/devices/mini-uart.zig @@ -0,0 +1,145 @@ +const std = @import("std"); +const mem = @import("pi").mem; + +const gpio = @import("./gpio.zig"); +const timer = @import("./timer.zig"); + +pub const Error = error{AlreadyInitialized} || gpio.Error; + +// Page 8: Auxiliary peripherals Register Map +const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0021_5000; +const AUX_ENABLES: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_IO_REG: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_IER_REG: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_IIR_REG: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_LCR_REG: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_MCR_REG: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_LSR_REG: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_MSR_REG: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_SCRATCH: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_CNTL_REG: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_STAT_REG: usize = BASE_ADDRESS + 0x0004; +const AUX_MU_BAUD_REG: usize = BASE_ADDRESS + 0x0004; + +const CLOCK_FREQ = 250_000_000; + +const TxPin = enum(u8) { + Gpio14 = 14, + Gpio32 = 32, + Gpio36 = 36, +}; +const RxPin = enum(u8) { + Gpio15 = 15, + Gpio33 = 33, + Gpio37 = 37, +}; + +var is_initialized = false; + +pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void { + if (is_initialized) return Error.AlreadyInitialized; + + // Set GPIO pins first (as specified by manual) + // Page 102 specifies which alt mode + const tx_fn: gpio.FunctionSelect = switch (tx_pin) { + .Gpio14 => .alt_fn_0, + .Gpio32 => .alt_fn_3, + .Gpio36 => .alt_fn_2, + }; + const rx_fn: gpio.FunctionSelect = switch (rx_pin) { + .Gpio15 => .alt_fn_0, + .Gpio33 => .alt_fn_3, + .Gpio37 => .alt_fn_2, + }; + + try gpio.fn_sel(@intFromEnum(tx_pin), tx_fn); + try gpio.fn_sel(@intFromEnum(rx_pin), rx_fn); + + try gpio.set_pull(@intFromEnum(tx_pin), .Off); + try gpio.set_pull(@intFromEnum(rx_pin), .Off); + + // AUX_ENABLES needs bit 0 set to 1 + // Page 9: AUXENB Register + mem.put_u32(@ptrFromInt(AUX_ENABLES), 1); + + // Disable interrupts + // Page 13: AUX_MU_IER_REG Register + // When bits 2:1 is 0, interrupts are disabled + mem.put_u32(@ptrFromInt(AUX_MU_IER_REG), 0); + + // Disable RX/TX during configuration + // Page 17: AUX_MU_CNTL_REG Register + // When bit 0/1 is 0, UART receiver/transmitter is disabled + mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0); + + // Set data size to 8 bits + // Page 14: AUX_MU_LCR_REG Register + // When bit 0 is 1, UART is in 8-bit mode, else 7-bit + mem.put_u32(@ptrFromInt(AUX_MU_LCR_REG), 1); + + // Put RTS high (indicate request to send) + // Page 14: AUX_MU_MCR_REG Register + // When bit 1 is 0, RTS line is high, else low + mem.put_u32(@ptrFromInt(AUX_MU_MCR_REG), 0); + + // Clear FIFO + // Page 12: AUX_MU_IER_REG Register (yes the page is mislabeled) + // When bit 1/2 is 1, receive/transmit will be cleared + mem.put_u32(@ptrFromInt(AUX_MU_IIR_REG), 0x6); + + // Set baud rate + // Page 11: 2.2.1 Mini UART Implementation Details + // The baudrate formula is given as (system_clock_freq)/(8 * (baudrate_reg + 1)) + mem.put_u32(@ptrFromInt(AUX_MU_BAUD_REG), CLOCK_FREQ / (8 * (baud + 1))); + + // Enable RX/TX again + // Page 17: AUX_MU_CNTL_REG Register + // When bit 0/1 is 1, UART receiver/transmitter is enabled + mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0x3); + + is_initialized = true; +} + +fn can_write() bool { + // Check if FIFO can accept data + // Page 15: AUX_MU_LSR_REG Register + // Bit 5 is set when FIFO can accept at least 1 byte + return mem.get_u32(@ptrFromInt(AUX_MU_LSR_REG)) & 0x20 != 0; +} + +fn can_read() bool { + // Check if FIFO has data + // Page 15: AUX_MU_LSR_REG Register + // Bit 1 is set when FIFO holds at least 1 byte + return mem.get_u32(@ptrFromInt(AUX_MU_LSR_REG)) & 1; +} + +pub fn write_byte(byte: u8) void { + // TODO; support timeout + while (!can_write()) { + std.atomic.spinLoopHint(); + } + + // Write into FIFO + // Page 11: AUX_MU_IO_REG Register + // Bits 7:0 holds data from the FIFO + mem.put_u32(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte)); +} + +pub fn read_byte() u8 { + // TODO; support timeout + while (!can_read()) { + std.atomic.spinLoopHint(); + } + + // Read from FIFO + // Page 11: AUX_MU_IO_REG Register + // Bits 7:0 holds data from the FIFO + return @truncate(mem.get_u32(AUX_MU_IO_REG)); +} + +pub fn write_slice(slice: []const u8) void { + for (slice) |b| { + write_byte(b); + } +} diff --git a/src/devices/timer.zig b/src/devices/timer.zig @@ -1,9 +1,9 @@ const std = @import("std"); const mem = @import("pi").mem; -const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x3000; -const COUNTER_LOWER: usize = 0x04; -const COUNTER_UPPER: usize = 0x08; +const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0000_3000; +const COUNTER_LOWER: usize = BASE_ADDRESS + 0x0004; +const COUNTER_UPPER: usize = BASE_ADDRESS + 0x0008; pub fn current_count() u64 { const upper = mem.get_u32_barrier(COUNTER_UPPER); diff --git a/src/main.zig b/src/main.zig @@ -1 +1,6 @@ -pub fn main() !void {} +const uart = @import("devices/mini-uart.zig"); + +pub fn main() !void { + try uart.initialize(115200, .Gpio14, .Gpio15); + uart.write_slice("Hello, World!"); +}