commit 98fbf007c9bca4890db28e0c8ad734a00ff05694
parent c2118fc9db32521c05274210cc7d19c86e463c48
Author: Sylvia Ivory <git@sivory.net>
Date: Thu, 5 Mar 2026 14:45:39 -0800
Add mini SPI
Diffstat:
2 files changed, 290 insertions(+), 0 deletions(-)
diff --git a/pi/devices/aux.zig b/pi/devices/aux.zig
@@ -0,0 +1,57 @@
+const std = @import("std");
+
+const mem = @import("../mem.zig");
+const register = @import("../register.zig");
+
+pub const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0021_5000;
+const AUX_IRQ: register.Register(Aux, .ReadOnly) = .init(BASE_ADDRESS + 0x00);
+const AUX_ENABLES: register.Register(Aux, .ReadModifyWrite) = .init(BASE_ADDRESS + 0x04);
+
+const Aux = packed struct(u32) {
+ mini_uart: bool,
+ spi_1: bool,
+ spi_2: bool,
+ _reserved_31_3: u29,
+};
+
+pub fn enable_mini_uart() void {
+ var aux = AUX_ENABLES.get();
+ aux.mini_uart = true;
+ AUX_ENABLES.set(aux);
+}
+pub fn disable_mini_uart() void {
+ var aux = AUX_ENABLES.get();
+ aux.mini_uart = false;
+ AUX_ENABLES.set(aux);
+}
+pub fn has_irq_mini_uart() bool {
+ return AUX_IRQ.get().mini_uart;
+}
+
+pub fn enable_spi_1() void {
+ var aux = AUX_ENABLES.get();
+ aux.spi_1 = true;
+ AUX_ENABLES.set(aux);
+}
+pub fn disable_spi_1() void {
+ var aux = AUX_ENABLES.get();
+ aux.spi_1 = false;
+ AUX_ENABLES.set(aux);
+}
+pub fn has_irq_spi_1() bool {
+ return AUX_IRQ.get().spi_1;
+}
+
+pub fn enable_spi_2() void {
+ var aux = AUX_ENABLES.get();
+ aux.spi_2 = true;
+ AUX_ENABLES.set(aux);
+}
+pub fn disable_spi_2() void {
+ var aux = AUX_ENABLES.get();
+ aux.spi_1 = false;
+ AUX_ENABLES.set(aux);
+}
+pub fn has_irq_spi_2() bool {
+ return AUX_IRQ.get().spi_2;
+}
diff --git a/pi/devices/mini-spi.zig b/pi/devices/mini-spi.zig
@@ -0,0 +1,233 @@
+const std = @import("std");
+
+const aux = @import("./aux.zig");
+const gpio = @import("./gpio.zig");
+const mem = @import("../mem.zig");
+const register = @import("../register.zig");
+
+const BASE_ADDRESS_1: usize = aux.BASE_ADDRESS + 0x80;
+const BASE_ADDRESS_2: usize = aux.BASE_ADDRESS + 0xC0;
+
+const AUX_SPI1_CNTL0: register.Register(Control0, .ReadModifyWrite) = .init(BASE_ADDRESS_1 + 0x00);
+const AUX_SPI1_CNTL1: register.Register(Control1, .ReadModifyWrite) = .init(BASE_ADDRESS_1 + 0x04);
+const AUX_SPI1_STAT: register.Register(Status, .ReadModifyWrite) = .init(BASE_ADDRESS_1 + 0x08);
+const AUX_SPI1_PEEK: register.Register(FiFo, .ReadOnly) = .init(BASE_ADDRESS_1 + 0x0C);
+const AUX_SPI1_IO: register.Register(FiFo, .ReadModifyWrite) = .init(BASE_ADDRESS_1 + 0x20);
+
+const AUX_SPI2_CNTL0: register.Register(Control0, .ReadModifyWrite) = .init(BASE_ADDRESS_2 + 0x00);
+const AUX_SPI2_CNTL1: register.Register(Control1, .ReadModifyWrite) = .init(BASE_ADDRESS_2 + 0x04);
+const AUX_SPI2_STAT: register.Register(Status, .ReadModifyWrite) = .init(BASE_ADDRESS_2 + 0x08);
+const AUX_SPI2_PEEK: register.Register(FiFo, .ReadOnly) = .init(BASE_ADDRESS_2 + 0x0C);
+const AUX_SPI2_IO: register.Register(FiFo, .ReadModifyWrite) = .init(BASE_ADDRESS_2 + 0x20);
+
+// Unlike UART, only 1 pair
+const SPI1_CE2 = 16;
+const SPI1_CE1 = 17;
+const SPI1_CE0 = 18;
+const SPI1_MISO = 19;
+const SPI1_MOSI = 20;
+const SPI1_SCLK = 21;
+
+const SPI2_CE2 = 40;
+const SPI2_CE1 = 41;
+const SPI2_CE0 = 42;
+const SPI2_MISO = 43;
+const SPI2_MOSI = 44;
+const SPI2_SCLK = 45;
+
+const Control0 = packed struct(u32) {
+ pub const DOutHold = enum(u2) {
+ None = 0b00,
+ @"1" = 0b01,
+ @"4" = 0b10,
+ @"7" = 0b11,
+ };
+ pub const ChipSelect = packed struct(u3) {
+ @"0": bool,
+ @"1": bool,
+ @"2": bool,
+ };
+ shift_length: u6,
+ shift_out_ms: bool,
+ invert_spi_clk: bool,
+ out_rising: bool,
+ clear_fifos: bool,
+ in_rising: bool,
+ enable: bool,
+ dout_hold: DOutHold,
+ variable_width: bool,
+ variable_cs: bool,
+ post_input: bool,
+ chip_select: ChipSelect,
+ speed: u12,
+};
+const Control1 = packed struct(u32) {
+ keep_input: bool,
+ shift_in_ms: bool,
+ _reserved_5_2: u4 = 0,
+ done_irq: bool,
+ tx_empty_irq: bool,
+ cs_high_time: u3,
+ _reserved_31_11: u11 = 0,
+};
+const Status = packed struct(u32) {
+ bit_count: u6,
+ busy: bool,
+ rx_empty: bool,
+ rx_full: bool,
+ tx_empty: bool,
+ tx_full: bool,
+ _reserved_15_5: u16 = 0,
+ rx_fill_level: u4,
+ tx_fill_level: u4,
+};
+const FiFo = packed struct(u32) {
+ data: u16,
+ _reserved_16_31: u16 = 0,
+};
+
+pub const SpiDevice = enum {
+ SPI1,
+ SPI2,
+};
+
+pub const SpiConfig = struct {
+ speed: u12,
+ chip_select: Control0.ChipSelect = .{
+ .@"0" = false,
+ .@"1" = false,
+ .@"2" = false,
+ },
+ shift_length: u6 = 8,
+ out_rising: bool = true,
+ in_rising: bool = false,
+ invert_clk: bool = false,
+ shift_out_ms: bool = true,
+ shift_in_ms: bool = false,
+ cs_high_time: u3 = 0,
+};
+
+pub fn init(device: SpiDevice, config: SpiConfig) void {
+ const cntl0_reg = switch (device) {
+ .SPI1 => AUX_SPI1_CNTL0,
+ .SPI2 => AUX_SPI2_CNTL0,
+ };
+ const cntl1_reg = switch (device) {
+ .SPI1 => AUX_SPI1_CNTL1,
+ .SPI2 => AUX_SPI2_CNTL1,
+ };
+
+ switch (device) {
+ .SPI1 => {
+ gpio.fn_sel(SPI1_MISO, .alt_fn_4) catch {};
+ gpio.set_pull(SPI1_MISO, .Up) catch {};
+
+ gpio.fn_sel(SPI1_MOSI, .alt_fn_4) catch {};
+ gpio.set_pull(SPI1_MOSI, .Up) catch {};
+
+ gpio.fn_sel(SPI1_SCLK, .alt_fn_4) catch {};
+ if (config.invert_clk) {
+ gpio.set_pull(SPI1_SCLK, .Up) catch {};
+ } else {
+ gpio.set_pull(SPI1_SCLK, .Down) catch {};
+ }
+
+ gpio.fn_sel(SPI1_CE0, .alt_fn_4) catch {};
+ gpio.set_pull(SPI1_CE2, .Up) catch {};
+
+ gpio.fn_sel(SPI1_CE1, .alt_fn_4) catch {};
+ gpio.set_pull(SPI1_CE2, .Up) catch {};
+
+ gpio.fn_sel(SPI1_CE2, .alt_fn_4) catch {};
+ gpio.set_pull(SPI1_CE2, .Up) catch {};
+
+ aux.enable_spi_1();
+ },
+ .SPI2 => {
+ gpio.fn_sel(SPI2_MISO, .alt_fn_4) catch {};
+ gpio.set_pull(SPI2_MISO, .Up) catch {};
+
+ gpio.fn_sel(SPI2_MOSI, .alt_fn_4) catch {};
+ gpio.set_pull(SPI2_MOSI, .Up) catch {};
+
+ gpio.fn_sel(SPI2_SCLK, .alt_fn_4) catch {};
+ if (config.invert_clk) {
+ gpio.set_pull(SPI2_SCLK, .Up) catch {};
+ } else {
+ gpio.set_pull(SPI2_SCLK, .Down) catch {};
+ }
+
+ gpio.fn_sel(SPI2_CE0, .alt_fn_4) catch {};
+ gpio.set_pull(SPI2_CE2, .Up) catch {};
+
+ gpio.fn_sel(SPI2_CE1, .alt_fn_4) catch {};
+ gpio.set_pull(SPI2_CE1, .Up) catch {};
+
+ gpio.fn_sel(SPI2_CE2, .alt_fn_4) catch {};
+ gpio.set_pull(SPI2_CE2, .Up) catch {};
+
+ aux.enable_spi_2();
+ },
+ }
+
+ cntl0_reg.set(.{
+ .shift_length = config.shift_length,
+ .shift_out_ms = config.shift_out_ms,
+ .invert_spi_clk = config.invert_clk,
+ .out_rising = config.out_rising,
+ .clear_fifos = true,
+ .in_rising = config.in_rising,
+ .enable = true,
+ .dout_hold = .None,
+ .variable_width = false,
+ .variable_cs = false,
+ .post_input = false,
+ .chip_select = config.chip_select,
+ .speed = config.speed,
+ });
+
+ cntl1_reg.set(.{
+ .keep_input = false,
+ .shift_in_ms = config.shift_in_ms,
+ .done_irq = false,
+ .tx_empty_irq = false,
+ .cs_high_time = config.cs_high_time,
+ });
+}
+
+pub fn status(device: SpiDevice) Status {
+ switch (device) {
+ .SPI1 => AUX_SPI1_STAT.get(),
+ .SPI2 => AUX_SPI2_STAT.get(),
+ }
+}
+
+pub fn write_polled(device: SpiDevice, data: []const u8) void {
+ const io_reg = switch (device) {
+ .SPI1 => AUX_SPI1_IO,
+ .SPI2 => AUX_SPI2_IO,
+ };
+
+ for (data) |b| {
+ while (status(device).tx_full) {}
+ io_reg.set(.{
+ .data = @as(u16, b),
+ });
+ }
+}
+
+pub fn read_polled(device: SpiDevice, data: []u8) void {
+ const io_reg = switch (device) {
+ .SPI1 => AUX_SPI1_IO,
+ .SPI2 => AUX_SPI2_IO,
+ };
+
+ for (0..data.len) |i| {
+ while (status(device).rx_empty) {}
+ data[i] = @truncate(io_reg.get().data);
+ }
+}
+
+pub fn flush(device: SpiDevice) void {
+ while (status(device).busy) {}
+}