From 79a5fde52dbb07382c96ff49f9324071a1b50eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=27vesim=27=20Kuli=C5=84ski?= Date: Sun, 14 Dec 2025 19:31:33 +0100 Subject: [PATCH] rp2xxx: Add I2S output implementation based on PIO --- port/raspberrypi/rp2xxx/src/extra.zig | 1 + .../rp2xxx/src/extra/I2S_Output.zig | 115 ++++++++++++++++++ port/raspberrypi/rp2xxx/src/hal.zig | 1 + 3 files changed, 117 insertions(+) create mode 100644 port/raspberrypi/rp2xxx/src/extra.zig create mode 100644 port/raspberrypi/rp2xxx/src/extra/I2S_Output.zig diff --git a/port/raspberrypi/rp2xxx/src/extra.zig b/port/raspberrypi/rp2xxx/src/extra.zig new file mode 100644 index 000000000..23521bb7b --- /dev/null +++ b/port/raspberrypi/rp2xxx/src/extra.zig @@ -0,0 +1 @@ +pub const I2S_Output = @import("extra/I2S_Output.zig"); diff --git a/port/raspberrypi/rp2xxx/src/extra/I2S_Output.zig b/port/raspberrypi/rp2xxx/src/extra/I2S_Output.zig new file mode 100644 index 000000000..151545c00 --- /dev/null +++ b/port/raspberrypi/rp2xxx/src/extra/I2S_Output.zig @@ -0,0 +1,115 @@ +pub const I2S_Output = @This(); + +pio: Pio, +sm: StateMachine, + +sample_rate: u32, +sample_size: u6, + +const program = blk: { + @setEvalBranchQuota(20_000); + break :blk pio_.assemble( + \\.program i2s_output + \\.side_set 2 + \\ + \\.wrap_target + \\; 0bWB W - Word Clock B - Bit clock + \\ mov x, y side 0b01 + \\left_channel: + \\ out pins, 1 side 0b00 + \\ jmp x-- left_channel side 0b01 + \\ out pins 1 side 0b10 + \\ mov x, y side 0b11 + \\right_channel: + \\ out pins 1 side 0b10 + \\ jmp x-- right_channel side 0b11 + \\ out pins 1 side 0b00 + \\.wrap + , .{}).get_program_by_name("i2s_output"); +}; + +pub const InitConfig = struct { + clock_config: clocks.config.Global, + pio: Pio, + sm: StateMachine, + + data_pin: Pin, + mclk_pin: Pin, + lrclk_pin: Pin, + + sample_rate: u32 = 44100, + sample_size: u6 = 32, +}; + +pub fn init(config: InitConfig) !I2S_Output { + if (@intFromEnum(config.lrclk_pin) + 1 != @intFromEnum(config.mclk_pin)) return error.MclkNotAfterLrclk; + + const sample_rate_multiplier: u32 = switch (config.sample_size) { + // 8, TODO: borked for some reasons + 16 => 1, + // 24 => // TODO + 32 => 2, + else => return error.InvalidSampleSize, + }; + + const system_frequency = config.clock_config.sys.?.frequency(); + const divider = system_frequency * 4 / (config.sample_rate * sample_rate_multiplier); + + try config.pio.sm_load_and_start_program(config.sm, program, .{ + .clkdiv = .{ .int = @intCast(divider >> 8), .frac = @intCast(divider & 0xff) }, + .pin_mappings = .{ + .out = .single(config.data_pin), + .side_set = .{ .low = config.lrclk_pin, .high = config.mclk_pin }, + }, + .shift = .{ + .out_shiftdir = .left, + .autopull = true, + .pull_threshold = if (config.sample_size == 32) 0 else @truncate(config.sample_size), + .join_tx = true, + }, + }); + + // there are 2 instructions outside the loop + config.pio.sm_exec_set_y(config.sm, config.sample_size - 2); + + try config.pio.sm_set_pindir(config.sm, config.lrclk_pin, 2, .out); + try config.pio.sm_set_pindir(config.sm, config.data_pin, 1, .out); + config.pio.sm_set_enabled(config.sm, true); + + return I2S_Output{ + .pio = config.pio, + .sm = config.sm, + .sample_rate = config.sample_rate, + .sample_size = config.sample_size, + }; +} + +pub fn dma_data_size(self: I2S_Output) DataSize { + return switch (self.sample_size) { + 8 => .size_8, + 16 => .size_16, + 32 => .size_32, + else => unreachable, + }; +} + +pub inline fn write_sample(self: I2S_Output, sample: anytype) void { + switch (@TypeOf(sample)) { + i8, i16, i32 => {}, + else => comptime unreachable, + } + std.debug.assert(@bitSizeOf(@TypeOf(sample)) == self.sample_size); + + const tx_fifo: *volatile @TypeOf(sample) = @ptrCast(self.pio.sm_get_tx_fifo(self.sm)); + tx_fifo.* = sample; +} + +const std = @import("std"); +const clocks = @import("../hal/clocks.zig"); +const dma = @import("../hal/dma.zig"); +const DataSize = dma.DataSize; +const gpio = @import("../hal/gpio.zig"); +const Pin = gpio.Pin; +const pio_ = @import("../hal/pio.zig"); +const Pio = pio_.Pio; +const StateMachine = pio_.StateMachine; diff --git a/port/raspberrypi/rp2xxx/src/hal.zig b/port/raspberrypi/rp2xxx/src/hal.zig index 5b0e39b78..7e4460e75 100644 --- a/port/raspberrypi/rp2xxx/src/hal.zig +++ b/port/raspberrypi/rp2xxx/src/hal.zig @@ -38,6 +38,7 @@ pub const time = @import("hal/time.zig"); pub const uart = @import("hal/uart.zig"); pub const usb = @import("hal/usb.zig"); pub const watchdog = @import("hal/watchdog.zig"); +pub const extra = @import("extra.zig"); comptime { // HACK: tests can't access microzig. maybe there's a better way to do this.