commit 0c7e16ca24bc194c3902be9b967bbcd2a1d62adc
parent 5047cb829dd4d38eab3c4c454d73772af562f594
Author: Sylvia Ivory <git@sivory.net>
Date: Sun, 11 Jan 2026 23:37:13 -0800
Idea of GPIO
Diffstat:
5 files changed, 160 insertions(+), 3 deletions(-)
diff --git a/Justfile b/Justfile
@@ -0,0 +1,5 @@
+build:
+ zig build
+
+list: build
+ arm-none-eabi-objdump -D zig-out/bin/SylveOS
diff --git a/build.zig b/build.zig
@@ -1,14 +1,20 @@
const std = @import("std");
+const config = @import("build.zig.zon");
+
const fs = std.fs;
+// BCM2835 is **very specifically** the ARM1176JZF-S
+// https://www.raspberrypi.com/documentation/computers/processors.html
const target: std.Target.Query = .{
.cpu_arch = .arm,
+ .cpu_model = .{ .explicit = &std.Target.arm.cpu.arm1176jzf_s },
+ .cpu_features_add = std.Target.arm.cpu.arm1176jzf_s.features,
.os_tag = .freestanding,
};
pub fn build(b: *std.Build) !void {
const exe = b.addExecutable(.{
- .name = "SylveOS",
+ .name = @tagName(config.name),
.root_module = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = b.resolveTargetQuery(target),
@@ -18,7 +24,10 @@ pub fn build(b: *std.Build) !void {
}),
.linkage = .static,
});
-
exe.setLinkerScript(b.path("linker.ld"));
+
+ const install_asm = b.addInstallBinFile(exe.getEmittedAsm(), @tagName(config.name) ++ ".s");
+ b.getInstallStep().dependOn(&install_asm.step);
+
b.installArtifact(exe);
}
diff --git a/src/gpio.zig b/src/gpio.zig
@@ -0,0 +1,124 @@
+const std = @import("std");
+const util = @import("util.zig");
+
+// Page 90, Table 6-1: GPIO Register Assignment
+const BASE_ADDR: u32 = 0x2020_0000;
+
+const GPFSEL0: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x0000);
+const GPFSEL1: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x0004);
+const GPFSEL2: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x0008);
+const GPFSEL3: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x000C);
+const GPFSEL4: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x0010);
+const GPFSEL5: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x0014);
+
+const GPSET0: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x001C);
+const GPSET1: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x0020);
+
+const GPCLR0: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x0028);
+const GPCLR1: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x002C);
+
+const GPLEV0: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x0034);
+const GPLEV1: *volatile u32 = @ptrFromInt(BASE_ADDR + 0x0038);
+
+// 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,
+};
+
+fn fn_sel(pin: u8, sel: FunctionSelect) void {
+ // TODO; this can be array assignment
+ const address: *volatile u32 = switch (pin) {
+ 0...9 => GPFSEL0,
+ 10...19 => GPFSEL1,
+ 20...29 => GPFSEL2,
+ 30...39 => GPFSEL3,
+ 40...49 => GPFSEL4,
+ 50...53 => GPFSEL5,
+ else => @panic("pin out of range (0...53)"),
+ };
+ const offset: u5 = @truncate((pin % 10) * 3);
+ const mask = ~(@as(u32, 0b111) << offset);
+
+ var state = address.*; // util.get_32(@ptrFromInt(address));
+ state &= mask;
+ state |= (@as(u32, @intCast(@intFromEnum(sel))) << offset);
+ // util.put_32(@ptrFromInt(address), state);
+ address.* = state;
+}
+
+fn addr_bitset(pin: u8, address: *volatile u32) void {
+ const offset: u5 = @truncate(pin % 32);
+ const mask = (@as(u32, 1) << offset);
+
+ // We don't want to preserve the old value
+ // SET and CLR are separated for this purpose
+ address.* = mask;
+}
+
+// Turn on
+fn output_set(pin: u8) void {
+ const address: *volatile u32 = switch (pin) {
+ 0...31 => GPSET0,
+ 32...53 => GPSET1,
+ else => @panic("pin out of range (0...53)"),
+ };
+
+ addr_bitset(pin, address);
+}
+
+// Turn off
+fn output_clear(pin: u8) void {
+ const address: *volatile u32 = switch (pin) {
+ 0...31 => GPCLR0,
+ 32...53 => GPCLR1,
+ else => @panic("pin out of range (0...53)"),
+ };
+
+ addr_bitset(pin, address);
+}
+
+pub fn set_output(pin: u8) void {
+ fn_sel(pin, .output);
+}
+
+pub fn set_input(pin: u8) void {
+ fn_sel(pin, .input);
+}
+
+pub fn set_on(pin: u8) void {
+ output_set(pin);
+}
+
+pub fn set_off(pin: u8) void {
+ output_clear(pin);
+}
+
+pub fn read(pin: u8) bool {
+ const address: *volatile u32 = switch (pin) {
+ 0...31 => GPLEV0,
+ 32...53 => GPLEV1,
+ else => @panic("pin out of range (0...53)"),
+ };
+
+ const offset: u5 = @truncate(pin % 32);
+ const mask = (@as(u32, 1) << offset);
+
+ return (address.* & mask) == mask;
+}
+
+pub fn write(pin: u8, state: bool) void {
+ if (state) {
+ set_on(pin);
+ } else {
+ set_off(pin);
+ }
+}
diff --git a/src/main.zig b/src/main.zig
@@ -1,3 +1,14 @@
const util = @import("util.zig");
+const gpio = @import("gpio.zig");
-pub fn main() void {}
+pub fn main() void {
+ const led = 20;
+ gpio.set_output(led);
+ for (0..10) |_| {
+ gpio.set_on(led);
+ util.delay_cycles(1000000);
+ gpio.set_off(led);
+ util.delay_cycles(1000000);
+ }
+ gpio.set_on(led);
+}
diff --git a/src/util.zig b/src/util.zig
@@ -16,3 +16,11 @@ pub fn put_32(addr: *u32, value: u32) void {
pub fn nop() void {
asm volatile ("bx lr");
}
+
+pub fn delay_cycles(ticks: usize) void {
+ var counter = ticks;
+ while (counter > 0) {
+ counter -= 1;
+ nop(); // Prevent optimization
+ }
+}