diff --git a/drivers/README.md b/drivers/README.md index cf9544924..3ad2cd1e3 100644 --- a/drivers/README.md +++ b/drivers/README.md @@ -17,6 +17,7 @@ A collection of device drivers for the use with MicroZig. - [ ] [ST7735](https://github.com/ZigEmbeddedGroup/microzig/issues/250) (WIP) - [ ] [ILI9488](https://github.com/ZigEmbeddedGroup/microzig/issues/249) - Wireless + - [ ] CYW43 (WIP) - [ ] [SX1276, SX1278](https://github.com/ZigEmbeddedGroup/microzig/issues/248) - Stepper - [x] A4988 diff --git a/drivers/build.zig.zon b/drivers/build.zig.zon index 2127d906f..5d25d2a74 100644 --- a/drivers/build.zig.zon +++ b/drivers/build.zig.zon @@ -12,5 +12,6 @@ "io_expander", "sensor", "stepper", + "wireless", }, } diff --git a/drivers/framework.zig b/drivers/framework.zig index 1686d77c7..e554ce6a8 100644 --- a/drivers/framework.zig +++ b/drivers/framework.zig @@ -51,6 +51,11 @@ pub const IO_expander = struct { }; pub const wireless = struct { + pub const cyw43_bus = @import("wireless/cyw43/bus.zig"); + pub const cyw43_runner = @import("wireless/cyw43/runner.zig"); + pub const Cyw43_Spi = cyw43_bus.Cyw43_Spi; + pub const Cyw43_Bus = cyw43_bus.Cyw43_Bus; + pub const Cyw43_Runner = cyw43_runner.Cyw43_Runner; // pub const sx1278 = @import("wireless/sx1278.zig"); }; diff --git a/drivers/wireless/cyw43/bus.zig b/drivers/wireless/cyw43/bus.zig new file mode 100644 index 000000000..73eb93e39 --- /dev/null +++ b/drivers/wireless/cyw43/bus.zig @@ -0,0 +1,386 @@ +const std = @import("std"); +const mdf = @import("../../framework.zig"); +const consts = @import("consts.zig"); +const DigitalIO = mdf.base.Digital_IO; + +/// Callback for microsecond delays +pub const delayus_callback = fn (delay: u32) void; + +/// SPI interface for CYW43 +pub const Cyw43_Spi = struct { + const Self = @This(); + ptr: *anyopaque, + vtable: *const VTable, + + /// Reads data from SPI into `buffer` using `cmd` + pub fn spi_read_blocking(self: *Self, cmd: u32, buffer: []u32) u32 { + return self.vtable.spi_read_blocking_fn(self.ptr, cmd, buffer); + } + + /// Writes `buffer` to SPI. + pub fn spi_write_blocking(self: *Self, buffer: []const u32) u32 { + return self.vtable.spi_write_blocking_fn(self.ptr, buffer); + } + + pub const VTable = struct { + spi_read_blocking_fn: *const fn (*anyopaque, cmd: u32, buffer: []u32) u32, + spi_write_blocking_fn: *const fn (*anyopaque, buffer: []const u32) u32, + }; +}; + +/// CYW43 bus controller (SPI + power management) +pub const Cyw43_Bus = struct { + const Self = @This(); + const log = std.log.scoped(.cyw43_bus); + + const TestPattern = 0x12345678; + pwr_pin: DigitalIO, + spi: *Cyw43_Spi, + internal_delay_ms: *const delayus_callback, + backplane_window: u32 = 0xAAAA_AAAA, + + pub fn init_bus(self: *Self) !void { + // Init sequence + try self.pwr_pin.write(.low); + self.internal_delay_ms(50); + try self.pwr_pin.write(.high); + self.internal_delay_ms(250); + + log.debug("read REG_BUS_TEST_RO", .{}); + while (true) { + const first_ro_test = self.read32_swapped(.bus, consts.REG_BUS_TEST_RO); + log.debug("0x{X}", .{first_ro_test}); + if (first_ro_test == consts.FEEDBEAD) + break; + } + + log.debug("write REG_BUS_TEST_RW", .{}); + self.write32_swapped(.bus, consts.REG_BUS_TEST_RW, consts.TEST_PATTERN); + const first_rw_test = self.read32_swapped(.bus, consts.REG_BUS_TEST_RW); + log.debug("0x{X}", .{first_rw_test}); + std.debug.assert(first_rw_test == consts.TEST_PATTERN); + + log.debug("read REG_BUS_CTRL", .{}); + const ctrl_reg_val = self.read32_swapped(.bus, consts.REG_BUS_CTRL); + log.debug("0b{b}", .{@as(u8, @truncate(ctrl_reg_val))}); + + // Set 32-bit word length and keep default endianness: little endian + const setup_regs = Cyw43FirstFourRegs{ + .ctrl = .{ + .word_length = .word_32, + .endianness = .little_endian, + .speed_mode = .high_speed, + .interrupt_polarity = .high_polarity, + .wake_up = true, + }, + .response_delay = .{ + .unknown = 0x4, // 32-bit response delay? + }, + .status_enable = .{ + .status_enable = true, + .interrupt_with_status = true, + }, + }; + + log.debug("write REG_BUS_CTRL", .{}); + self.write32_swapped(.bus, consts.REG_BUS_CTRL, @bitCast(setup_regs)); + + log.debug("read REG_BUS_TEST_RO", .{}); + const second_ro_test = self.read32(.bus, consts.REG_BUS_TEST_RO); + log.debug("0x{X}", .{second_ro_test}); + std.debug.assert(second_ro_test == consts.FEEDBEAD); + + log.debug("read REG_BUS_TEST_RW", .{}); + const second_rw_test = self.read32(.bus, consts.REG_BUS_TEST_RW); + log.debug("0x{X}", .{second_rw_test}); + std.debug.assert(second_rw_test == consts.TEST_PATTERN); + + log.debug("write SPI_RESP_DELAY_F1 CYW43_BACKPLANE_READ_PAD_LEN_BYTES", .{}); + self.write8(.bus, consts.SPI_RESP_DELAY_F1, consts.WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE); + + // TODO: Make sure error interrupt bits are clear? + // cyw43_write_reg_u8(self, BUS_FUNCTION, SPI_INTERRUPT_REGISTER, DATA_UNAVAILABLE | COMMAND_ERROR | DATA_ERROR | F1_OVERFLOW) != 0) + log.debug("Make sure error interrupt bits are clear", .{}); + self.write8(.bus, consts.REG_BUS_INTERRUPT, (consts.IRQ_DATA_UNAVAILABLE | consts.IRQ_COMMAND_ERROR | consts.IRQ_DATA_ERROR | consts.IRQ_F1_OVERFLOW)); + + // Enable a selection of interrupts + // TODO: why not all of these F2_F3_FIFO_RD_UNDERFLOW | F2_F3_FIFO_WR_OVERFLOW | COMMAND_ERROR | DATA_ERROR | F2_PACKET_AVAILABLE | F1_OVERFLOW | F1_INTR + log.debug("enable a selection of interrupts", .{}); + + const val: u16 = consts.IRQ_F2_F3_FIFO_RD_UNDERFLOW | + consts.IRQ_F2_F3_FIFO_WR_OVERFLOW | + consts.IRQ_COMMAND_ERROR | + consts.IRQ_DATA_ERROR | + consts.IRQ_F2_PACKET_AVAILABLE | + consts.IRQ_F1_OVERFLOW; + + //if bluetooth_enabled { + // val = val | IRQ_F1_INTR; + //} + + self.write16(.bus, consts.REG_BUS_INTERRUPT_ENABLE, val); + } + + pub inline fn read8(self: *Self, func: FuncType, addr: u17) u8 { + return @truncate(self.readn(func, addr, 1)); + } + + pub inline fn read16(self: *Self, func: FuncType, addr: u17) u16 { + return @truncate(self.readn(func, addr, 2)); + } + + pub inline fn read32(self: *Self, func: FuncType, addr: u17) u32 { + return self.readn(func, addr, 4); + } + + fn readn(self: *Self, func: FuncType, addr: u17, len: u11) u32 { + const cmd = Cyw43Cmd{ .cmd = .read, .incr = .incremental, .func = func, .addr = addr, .len = len }; + var buff = [_]u32{0} ** 2; + // if we are reading from the backplane, we need an extra word for the response delay + const buff_len: usize = if (func == .backplane) 2 else 1; + + _ = self.spi.spi_read_blocking(@bitCast(cmd), buff[0..buff_len]); + + return if (func == .backplane) buff[1] else buff[0]; + } + + pub inline fn write8(self: *Self, func: FuncType, addr: u17, value: u8) void { + return self.writen(func, addr, value, 1); + } + + pub inline fn write16(self: *Self, func: FuncType, addr: u17, value: u16) void { + return self.writen(func, addr, value, 2); + } + + pub inline fn write32(self: *Self, func: FuncType, addr: u17, value: u32) void { + return self.writen(func, addr, value, 4); + } + + fn writen(self: *Self, func: FuncType, addr: u17, value: u32, len: u11) void { + const cmd = Cyw43Cmd{ .cmd = .write, .incr = .incremental, .func = func, .addr = addr, .len = len }; + + _ = self.spi.spi_write_blocking(&[_]u32{ @bitCast(cmd), value }); + } + + pub fn bp_read(self: *Self, addr: u32, data: []u8) void { + log.debug("bp_read addr = 0x{X}", .{addr}); + + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + std.debug.assert(addr % 4 == 0); + + var buf: [consts.BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]u32 = undefined; + + var current_addr = addr; + var remaining_data = data; + + while (remaining_data.len > 0) { + const window_offs = current_addr & consts.BACKPLANE_ADDRESS_MASK; + const window_remaining = consts.BACKPLANE_WINDOW_SIZE - @as(usize, @intCast(window_offs)); + + const len: usize = @min(remaining_data.len, consts.BACKPLANE_MAX_TRANSFER_SIZE, window_remaining); + + self.backplane_set_window(current_addr); + + const cmd = Cyw43Cmd{ .cmd = .read, .incr = .incremental, .func = .backplane, .addr = @truncate(window_offs), .len = @truncate(len) }; + + // round `buf` to word boundary, add one extra word for the response delay + const words_to_send = (len + 3) / 4 + 1; + _ = self.spi.spi_read_blocking(@bitCast(cmd), buf[0..words_to_send]); + + const u32_data_slice = buf[1..]; + var u8_buf_view = std.mem.sliceAsBytes(u32_data_slice); + + @memcpy(remaining_data[0..len], u8_buf_view[0..len]); + + current_addr += @as(u32, @intCast(len)); + remaining_data = remaining_data[len..]; + } + } + + pub inline fn bp_read8(self: *Self, addr: u32) u8 { + return @truncate(self.backplane_readn(addr, 1)); + } + + pub inline fn bp_read16(self: *Self, addr: u32) u16 { + return @truncate(self.backplane_readn(addr, 2)); + } + + pub inline fn bp_read32(self: *Self, addr: u32) u32 { + return @truncate(self.backplane_readn(addr, 4)); + } + + pub fn backplane_readn(self: *Self, addr: u32, len: u11) u32 { + log.debug("backplane_readn addr = 0x{X} len = {}", .{ addr, len }); + + self.backplane_set_window(addr); + + var bus_addr = addr & consts.BACKPLANE_ADDRESS_MASK; + if (len == 4) { + bus_addr |= consts.BACKPLANE_ADDRESS_32BIT_FLAG; + } + + const val = self.readn(.backplane, @truncate(bus_addr), len); + + log.debug("backplane_readn addr = 0x{X} len = {} val = 0x{X}", .{ addr, len, val }); + + return val; + } + + pub fn bp_write(self: *Self, addr: u32, data: []const u8) void { + log.debug("bp_write addr = 0x{X} len = {}", .{ addr, data.len }); + + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + std.debug.assert(addr % 4 == 0); + + var buf: [consts.BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]u32 = undefined; + + var current_addr = addr; + var remaining_data = data; + + while (remaining_data.len > 0) { + const window_offs = current_addr & consts.BACKPLANE_ADDRESS_MASK; + const window_remaining = consts.BACKPLANE_WINDOW_SIZE - @as(usize, @intCast(window_offs)); + + const len: usize = @min(remaining_data.len, consts.BACKPLANE_MAX_TRANSFER_SIZE, window_remaining); + + const u32_data_slice = buf[1..]; + var u8_buf_view = std.mem.sliceAsBytes(u32_data_slice); + + @memcpy(u8_buf_view[0..len], remaining_data[0..len]); + + self.backplane_set_window(current_addr); + + const cmd = Cyw43Cmd{ .cmd = .write, .incr = .incremental, .func = .backplane, .addr = @truncate(window_offs), .len = @truncate(len) }; + buf[0] = @bitCast(cmd); + + const words_to_send = (len + 3) / 4 + 1; + _ = self.spi.spi_write_blocking(buf[0..words_to_send]); + + current_addr += @as(u32, @intCast(len)); + remaining_data = remaining_data[len..]; + } + } + + pub inline fn bp_write8(self: *Self, addr: u32, value: u8) void { + self.backplane_writen(addr, value, 1); + } + + pub inline fn bp_write16(self: *Self, addr: u32, value: u16) void { + self.backplane_writen(addr, value, 2); + } + + pub inline fn bp_write32(self: *Self, addr: u32, value: u32) void { + self.backplane_writen(addr, value, 4); + } + + pub fn backplane_writen(self: *Self, addr: u32, value: u32, len: u11) void { + log.debug("backplane_writen addr = 0x{X} len = {} val = 0x{X}", .{ addr, len, value }); + + self.backplane_set_window(addr); + + var bus_addr = addr & consts.BACKPLANE_ADDRESS_MASK; + if (len == 4) { + bus_addr |= consts.BACKPLANE_ADDRESS_32BIT_FLAG; + } + + self.writen(.backplane, @truncate(bus_addr), value, len); + } + + fn backplane_set_window(self: *Self, addr: u32) void { + const new_window = addr & ~consts.BACKPLANE_ADDRESS_MASK; + + if (@as(u8, @truncate(new_window >> 24)) != @as(u8, @truncate(self.backplane_window >> 24))) { + self.write8(.backplane, consts.REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH, @as(u8, @truncate(new_window >> 24))); + } + if (@as(u8, @truncate(new_window >> 16)) != @as(u8, @truncate(self.backplane_window >> 16))) { + self.write8(.backplane, consts.REG_BACKPLANE_BACKPLANE_ADDRESS_MID, @as(u8, @truncate(new_window >> 16))); + } + if (@as(u8, @truncate(new_window >> 8)) != @as(u8, @truncate(self.backplane_window >> 8))) { + self.write8(.backplane, consts.REG_BACKPLANE_BACKPLANE_ADDRESS_LOW, @as(u8, @truncate(new_window >> 8))); + } + + self.backplane_window = new_window; + } + + fn read32_swapped(self: *Self, func: FuncType, addr: u17) u32 { + const cmd = Cyw43Cmd{ .cmd = .read, .incr = .incremental, .func = func, .addr = addr, .len = 4 }; + const cmd_swapped = swap16(@bitCast(cmd)); + + var buff = [1]u32{0}; + _ = self.spi.spi_read_blocking(cmd_swapped, &buff); + + return swap16(buff[0]); + } + + fn write32_swapped(self: *Self, func: FuncType, addr: u17, value: u32) void { + const cmd = Cyw43Cmd{ .cmd = .write, .incr = .incremental, .func = func, .addr = addr, .len = 4 }; + + var buff: [2]u32 = .{ swap16(@bitCast(cmd)), swap16(value) }; + _ = self.spi.spi_write_blocking(&buff); + } + + inline fn swap16(x: u32) u32 { + return x << 16 | x >> 16; + } +}; + +const CmdType = enum(u1) { + read = 0, + write = 1, +}; + +const IncrMode = enum(u1) { + fixed = 0, // Fixed address (no increment) + incremental = 1, // Incremental burst +}; + +const FuncType = enum(u2) { bus = 0, backplane = 1, wlan = 2, bt = 3 }; + +const Cyw43Cmd = packed struct(u32) { + len: u11, + addr: u17, + func: FuncType = .bus, + incr: IncrMode = .fixed, + cmd: CmdType, +}; + +const CtrlWordLength = enum(u1) { word_16 = 0, word_32 = 1 }; + +const CtrlEndianness = enum(u1) { little_endian = 0, big_endian = 1 }; + +const CtrlSpeedMode = enum(u1) { normal = 0, high_speed = 1 }; + +const CtrlInterruptPolarity = enum(u1) { low_polarity = 0, high_polarity = 1 }; + +const CtrlReg = packed struct(u8) { + word_length: CtrlWordLength, + endianness: CtrlEndianness, + reserved1: u2 = 0, + speed_mode: CtrlSpeedMode, + interrupt_polarity: CtrlInterruptPolarity, + reserved3: u1 = 0, + wake_up: bool, +}; + +const ResponseDelay = packed struct(u8) { + unknown: u8, +}; + +const StatusEnableReg = packed struct(u8) { + status_enable: bool, + interrupt_with_status: bool, + reserved1: u6 = 0, +}; + +const Cyw43FirstFourRegs = packed struct(u32) { + ctrl: CtrlReg, + response_delay: ResponseDelay, + status_enable: StatusEnableReg, + reserved1: u8 = 0, +}; diff --git a/drivers/wireless/cyw43/consts.zig b/drivers/wireless/cyw43/consts.zig new file mode 100644 index 000000000..704cba626 --- /dev/null +++ b/drivers/wireless/cyw43/consts.zig @@ -0,0 +1,111 @@ +// Register addresses +pub const REG_BUS_CTRL: u32 = 0x0; +pub const REG_BUS_RESPONSE_DELAY: u32 = 0x1; +pub const REG_BUS_STATUS_ENABLE: u32 = 0x2; +pub const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status +pub const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask +pub const REG_BUS_STATUS: u32 = 0x8; +pub const REG_BUS_TEST_RO: u32 = 0x14; +pub const REG_BUS_TEST_RW: u32 = 0x18; +pub const REG_BUS_RESP_DELAY: u32 = 0x1c; + +// SPI_BUS_CONTROL Bits +pub const WORD_LENGTH_32: u32 = 0x1; +pub const ENDIAN_BIG: u32 = 0x2; +pub const CLOCK_PHASE: u32 = 0x4; +pub const CLOCK_POLARITY: u32 = 0x8; +pub const HIGH_SPEED: u32 = 0x10; +pub const INTERRUPT_POLARITY_HIGH: u32 = 0x20; +pub const WAKE_UP: u32 = 0x80; + +// SPI_STATUS_ENABLE bits +pub const STATUS_ENABLE: u32 = 0x01; +pub const INTR_WITH_STATUS: u32 = 0x02; +pub const RESP_DELAY_ALL: u32 = 0x04; +pub const DWORD_PKT_LEN_EN: u32 = 0x08; +pub const CMD_ERR_CHK_EN: u32 = 0x20; +pub const DATA_ERR_CHK_EN: u32 = 0x40; + +// SPI_STATUS_REGISTER bits +pub const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; +pub const STATUS_UNDERFLOW: u32 = 0x00000002; +pub const STATUS_OVERFLOW: u32 = 0x00000004; +pub const STATUS_F2_INTR: u32 = 0x00000008; +pub const STATUS_F3_INTR: u32 = 0x00000010; +pub const STATUS_F2_RX_READY: u32 = 0x00000020; +pub const STATUS_F3_RX_READY: u32 = 0x00000040; +pub const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; +pub const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; +pub const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; +pub const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; +pub const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; +pub const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; +pub const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; + +pub const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; +pub const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; +pub const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; +pub const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; +pub const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; +pub const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; +pub const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; +pub const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; +pub const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; +pub const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; +pub const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; +pub const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; +pub const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; +pub const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; +pub const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; + +pub const I_HMB_SW_MASK: u32 = 0x000000f0; +pub const I_HMB_FC_CHANGE: u32 = 1 << 5; +pub const SDIO_INT_STATUS: u32 = 0x20; +pub const SDIO_INT_HOST_MASK: u32 = 0x24; + +pub const SPI_F2_WATERMARK: u8 = 0x20; + +pub const BACKPLANE_WINDOW_SIZE: usize = 0x8000; +pub const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; +pub const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; +pub const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; +// Active Low Power (ALP) clock constants +pub const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; +pub const BACKPLANE_ALP_AVAIL: u8 = 0x40; + +// Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect +// (AI) pub (crate) constants +pub const AI_IOCTRL_OFFSET: u32 = 0x408; +pub const AI_IOCTRL_BIT_FGC: u8 = 0x0002; +pub const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; +pub const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; + +pub const AI_RESETCTRL_OFFSET: u32 = 0x800; +pub const AI_RESETCTRL_BIT_RESET: u8 = 1; + +pub const AI_RESETSTATUS_OFFSET: u32 = 0x804; + +pub const TEST_PATTERN: u32 = 0x12345678; +pub const FEEDBEAD: u32 = 0xFEEDBEAD; + +// SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits +pub const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" +pub const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; +pub const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; +pub const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 +pub const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 +pub const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; +pub const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; +pub const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests +pub const IRQ_MISC_INTR0: u16 = 0x0100; +pub const IRQ_MISC_INTR1: u16 = 0x0200; +pub const IRQ_MISC_INTR2: u16 = 0x0400; +pub const IRQ_MISC_INTR3: u16 = 0x0800; +pub const IRQ_MISC_INTR4: u16 = 0x1000; +pub const IRQ_F1_INTR: u16 = 0x2000; +pub const IRQ_F2_INTR: u16 = 0x4000; +pub const IRQ_F3_INTR: u16 = 0x8000; + +// Bluetooth constants. +pub const SPI_RESP_DELAY_F1: u32 = 0x001d; +pub const WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE: u8 = 4; diff --git a/drivers/wireless/cyw43/firmware/43439A0_7_95_61.bin b/drivers/wireless/cyw43/firmware/43439A0_7_95_61.bin new file mode 100644 index 000000000..a05482fe9 Binary files /dev/null and b/drivers/wireless/cyw43/firmware/43439A0_7_95_61.bin differ diff --git a/drivers/wireless/cyw43/firmware/43439A0_7_95_88.bin b/drivers/wireless/cyw43/firmware/43439A0_7_95_88.bin new file mode 100644 index 000000000..a5f65d6c6 Binary files /dev/null and b/drivers/wireless/cyw43/firmware/43439A0_7_95_88.bin differ diff --git a/drivers/wireless/cyw43/firmware/43439A0_btfw.bin b/drivers/wireless/cyw43/firmware/43439A0_btfw.bin new file mode 100644 index 000000000..290ce8ef0 Binary files /dev/null and b/drivers/wireless/cyw43/firmware/43439A0_btfw.bin differ diff --git a/drivers/wireless/cyw43/firmware/43439A0_clm.bin b/drivers/wireless/cyw43/firmware/43439A0_clm.bin new file mode 100644 index 000000000..dc4ee0252 Binary files /dev/null and b/drivers/wireless/cyw43/firmware/43439A0_clm.bin differ diff --git a/drivers/wireless/cyw43/firmware/LICENSE-permissive-binary-license-1.0.txt b/drivers/wireless/cyw43/firmware/LICENSE-permissive-binary-license-1.0.txt new file mode 100644 index 000000000..cbb51f9c9 --- /dev/null +++ b/drivers/wireless/cyw43/firmware/LICENSE-permissive-binary-license-1.0.txt @@ -0,0 +1,49 @@ +Permissive Binary License + +Version 1.0, July 2019 + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +1) Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. + +2) Unless to the extent explicitly permitted by law, no reverse + engineering, decompilation, or disassembly of this software is + permitted. + +3) Redistribution as part of a software development kit must include the + accompanying file named �DEPENDENCIES� and any dependencies listed in + that file. + +4) Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +Limited patent license. The copyright holders (and contributors) grant a +worldwide, non-exclusive, no-charge, royalty-free patent license to +make, have made, use, offer to sell, sell, import, and otherwise +transfer this software, where such license applies only to those patent +claims licensable by the copyright holders (and contributors) that are +necessarily infringed by this software. This patent license shall not +apply to any combinations that include this software. No hardware is +licensed hereunder. + +If you institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the software +itself infringes your patent(s), then your rights granted under this +license shall terminate as of the date such litigation is filed. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/drivers/wireless/cyw43/firmware/README.md b/drivers/wireless/cyw43/firmware/README.md new file mode 100644 index 000000000..59b12e34f --- /dev/null +++ b/drivers/wireless/cyw43/firmware/README.md @@ -0,0 +1,23 @@ +# WiFi firmware blobs + +Firmware sources: + +[Source 1 - Infineon](https://github.com/Infineon/wifi-host-driver/tree/master/WHD/COMPONENT_WIFI5/resources/firmware/COMPONENT_43439) + +[Source 2 - Embassy](https://github.com/embassy-rs/embassy/tree/main/cyw43-firmware) + +[Source 3 - georgerobotics](https://github.com/georgerobotics/cyw43-driver/tree/main/firmware) + +Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt) + +## Changelog + +* 2025-04-23: Firmware 7.95.88 (cf1d613 CY) obtained from Infineon GitHub repo +* 2025-04-23: Firmware 7.95.61 (abcd531 CY) obtained from Embassy GitHub repo + +## Notes +* Firmware version is determined by chip logs after boot sequence. For example: + + [1.035957] debug (cyw43_chip): 000000.055 wl0: Broadcom BCM43439 802.11 Wireless Controller 7.95.61 (abcd531 CY) + +* The Pico SDK and Embassy use a slightly older version of the CYW-43439 firmware. For development purposes, please use the older version to match the Embassy driver code, which is used as a reference. \ No newline at end of file diff --git a/drivers/wireless/cyw43/nvram.zig b/drivers/wireless/cyw43/nvram.zig new file mode 100644 index 000000000..a67047483 --- /dev/null +++ b/drivers/wireless/cyw43/nvram.zig @@ -0,0 +1,48 @@ +pub const NVRAM = + "NVRAMRev=$Rev$\x00" ++ + "manfid=0x2d0\x00" ++ + "prodid=0x0727\x00" ++ + "vendid=0x14e4\x00" ++ + "devid=0x43e2\x00" ++ + "boardtype=0x0887\x00" ++ + "boardrev=0x1100\x00" ++ + "boardnum=22\x00" ++ + "macaddr=00:A0:50:b5:59:5e\x00" ++ + "sromrev=11\x00" ++ + "boardflags=0x00404001\x00" ++ + "boardflags3=0x04000000\x00" ++ + "xtalfreq=37400\x00" ++ + "nocrc=1\x00" ++ + "ag0=255\x00" ++ + "aa2g=1\x00" ++ + "ccode=ALL\x00" ++ + "pa0itssit=0x20\x00" ++ + "extpagain2g=0\x00" ++ + "pa2ga0=-168,6649,-778\x00" ++ + "AvVmid_c0=0x0,0xc8\x00" ++ + "cckpwroffset0=5\x00" ++ + "maxp2ga0=84\x00" ++ + "txpwrbckof=6\x00" ++ + "cckbw202gpo=0\x00" ++ + "legofdmbw202gpo=0x66111111\x00" ++ + "mcsbw202gpo=0x77711111\x00" ++ + "propbw202gpo=0xdd\x00" ++ + "ofdmdigfilttype=18\x00" ++ + "ofdmdigfilttypebe=18\x00" ++ + "papdmode=1\x00" ++ + "papdvalidtest=1\x00" ++ + "pacalidx2g=45\x00" ++ + "papdepsoffset=-30\x00" ++ + "papdendidx=58\x00" ++ + "ltecxmux=0\x00" ++ + "ltecxpadnum=0x0102\x00" ++ + "ltecxfnsel=0x44\x00" ++ + "ltecxgcigpio=0x01\x00" ++ + "il0macaddr=00:90:4c:c5:12:38\x00" ++ + "wl0id=0x431b\x00" ++ + "deadman_to=0xffffffff\x00" ++ + "muxenab=0x100\x00" ++ + "spurconfig=0x3\x00" ++ + "glitch_based_crsmin=1\x00" ++ + "btc_mode=1\x00" ++ + "\x00"; diff --git a/drivers/wireless/cyw43/runner.zig b/drivers/wireless/cyw43/runner.zig new file mode 100644 index 000000000..33932a11c --- /dev/null +++ b/drivers/wireless/cyw43/runner.zig @@ -0,0 +1,307 @@ +const std = @import("std"); +const bus = @import("bus.zig"); +const consts = @import("consts.zig"); +const nvram = @import("nvram.zig"); + +/// Callback for microsecond delays +pub const delayus_callback = fn (delay: u32) void; + +pub const Cyw43_Runner = struct { + const Self = @This(); + const log = std.log.scoped(.cyw43_runner); + const chip_log = std.log.scoped(.cyw43_chip); + chip_log_state: LogState = .{}, + + bus: *bus.Cyw43_Bus, + internal_delay_ms: *const delayus_callback, + + pub fn init(self: *Self) !void { + try self.bus.init_bus(); + + // Init ALP (Active Low Power) clock + log.debug("init alp", .{}); + _ = self.bus.write8(.backplane, consts.REG_BACKPLANE_CHIP_CLOCK_CSR, consts.BACKPLANE_ALP_AVAIL_REQ); + + log.debug("set f2 watermark", .{}); + _ = self.bus.write8(.backplane, consts.REG_BACKPLANE_FUNCTION2_WATERMARK, 0x10); + const watermark = self.bus.read8(.backplane, consts.REG_BACKPLANE_FUNCTION2_WATERMARK); + log.debug("watermark = 0x{X}", .{watermark}); + std.debug.assert(watermark == 0x10); + + log.debug("waiting for clock...", .{}); + while (self.bus.read8(.backplane, consts.REG_BACKPLANE_CHIP_CLOCK_CSR) & consts.BACKPLANE_ALP_AVAIL == 0) {} + log.debug("clock ok", .{}); + + // clear request for ALP + log.debug("clear request for ALP", .{}); + self.bus.write8(.backplane, consts.REG_BACKPLANE_CHIP_CLOCK_CSR, 0); + + const chip_id = self.bus.bp_read16(0x1800_0000); + log.debug("chip ID: 0x{X}", .{chip_id}); + + // Upload firmware + self.core_disable(.wlan); + self.core_disable(.socram); // TODO: is this needed if we reset right after? + self.core_reset(.socram); + + // this is 4343x specific stuff: Disable remap for SRAM_3 + self.bus.bp_write32(CYW43439_Chip.socsram_base_address + 0x10, 3); + self.bus.bp_write32(CYW43439_Chip.socsram_base_address + 0x44, 0); + + const ram_addr = CYW43439_Chip.atcm_ram_base_address; + + log.debug("loading fw", .{}); + const firmware = @embedFile("firmware/43439A0_7_95_61.bin")[0..]; + self.bus.bp_write(ram_addr, firmware); + + log.debug("loading nvram", .{}); + // Round up to 4 bytes. + const nvram_len = (nvram.NVRAM.len + 3) / 4 * 4; + self.bus.bp_write(ram_addr + CYW43439_Chip.chip_ram_size - 4 - nvram_len, nvram.NVRAM); + + const nvram_len_words = nvram_len / 4; + const nvram_len_magic = (~nvram_len_words << 16) | nvram_len_words; + self.bus.bp_write32(ram_addr + CYW43439_Chip.chip_ram_size - 4, nvram_len_magic); + + log.debug("starting up core...", .{}); + self.core_reset(.wlan); + std.debug.assert(self.core_is_up(.wlan)); + + // wait until HT clock is available; takes about 29ms + log.debug("wait for HT clock", .{}); + while (self.bus.read8(.backplane, consts.REG_BACKPLANE_CHIP_CLOCK_CSR) & 0x80 == 0) {} + + // "Set up the interrupt mask and enable interrupts" + log.debug("setup interrupt mask", .{}); + self.bus.bp_write32(CYW43439_Chip.sdiod_core_base_address + consts.SDIO_INT_HOST_MASK, consts.I_HMB_SW_MASK); + + // Set up the interrupt mask and enable interrupts + // TODO - bluetooth interrupts + + self.bus.write16(.bus, consts.REG_BUS_INTERRUPT_ENABLE, consts.IRQ_F2_PACKET_AVAILABLE); + + // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." + // Sounds scary... + self.bus.write8(.backplane, consts.REG_BACKPLANE_FUNCTION2_WATERMARK, consts.SPI_F2_WATERMARK); + + log.debug("waiting for F2 to be ready...", .{}); + while (self.bus.read32(.bus, consts.REG_BUS_STATUS) & consts.STATUS_F2_RX_READY == 0) {} + + log.debug("clear pad pulls", .{}); + self.bus.write8(.backplane, consts.REG_BACKPLANE_PULL_UP, 0); + _ = self.bus.read8(.backplane, consts.REG_BACKPLANE_PULL_UP); + + // start HT clock + self.bus.write8(.backplane, consts.REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10); + log.debug("waiting for HT clock...", .{}); + while (self.bus.read8(.backplane, consts.REG_BACKPLANE_CHIP_CLOCK_CSR) & 0x80 == 0) {} + log.debug("clock ok", .{}); + + self.log_init(); + + // TODO: bluetooth setup + + log.debug("cyw43 runner init done", .{}); + } + + fn core_disable(self: *Self, core: Core) void { + const base = core.base_addr(); + + // Dummy read? + _ = self.bus.bp_read8(base + consts.AI_RESETCTRL_OFFSET); + + // Check it isn't already reset + const r = self.bus.bp_read8(base + consts.AI_RESETCTRL_OFFSET); + if (r & consts.AI_RESETCTRL_BIT_RESET != 0) { + return; + } + + self.bus.bp_write8(base + consts.AI_IOCTRL_OFFSET, 0); + _ = self.bus.bp_read8(base + consts.AI_IOCTRL_OFFSET); + + self.internal_delay_ms(1); + + self.bus.bp_write8(base + consts.AI_RESETCTRL_OFFSET, consts.AI_RESETCTRL_BIT_RESET); + _ = self.bus.bp_read8(base + consts.AI_RESETCTRL_OFFSET); + } + + fn core_reset(self: *Self, core: Core) void { + self.core_disable(core); + + const base = core.base_addr(); + + self.bus.bp_write8(base + consts.AI_IOCTRL_OFFSET, consts.AI_IOCTRL_BIT_FGC | consts.AI_IOCTRL_BIT_CLOCK_EN); + _ = self.bus.bp_read8(base + consts.AI_IOCTRL_OFFSET); + + self.bus.bp_write8(base + consts.AI_RESETCTRL_OFFSET, 0); + + self.internal_delay_ms(1); + + self.bus.bp_write8(base + consts.AI_IOCTRL_OFFSET, consts.AI_IOCTRL_BIT_CLOCK_EN); + _ = self.bus.bp_read8(base + consts.AI_IOCTRL_OFFSET); + + self.internal_delay_ms(1); + } + + fn core_is_up(self: *Self, core: Core) bool { + const base = core.base_addr(); + + const io = self.bus.bp_read8(base + consts.AI_IOCTRL_OFFSET); + + if (io & (consts.AI_IOCTRL_BIT_FGC | consts.AI_IOCTRL_BIT_CLOCK_EN) != consts.AI_IOCTRL_BIT_CLOCK_EN) { + log.debug("core_is_up: returning false due to bad ioctrl 0x{X}", .{io}); + return false; + } + + const r = self.bus.bp_read8(base + consts.AI_RESETCTRL_OFFSET); + if (r & (consts.AI_RESETCTRL_BIT_RESET) != 0) { + log.debug("core_is_up: returning false due to bad resetctrl 0x{X}", .{r}); + return false; + } + + return true; + } + + fn log_init(self: *Self) void { + const addr = CYW43439_Chip.atcm_ram_base_address + CYW43439_Chip.chip_ram_size - 4 - CYW43439_Chip.socram_srmem_size; + const shared_addr = self.bus.bp_read32(addr); + log.debug("shared_addr 0x{X}", .{shared_addr}); + + var shared: SharedMemData = undefined; + self.bus.bp_read(shared_addr, std.mem.asBytes(&shared)); + + self.chip_log_state.addr = shared.console_addr + 8; + } + + fn log_read(self: *Self) void { + var chip_log_mem: SharedMemLog = undefined; + self.bus.bp_read(self.chip_log_state.addr, std.mem.asBytes(&chip_log_mem)); + + const idx = chip_log_mem.idx; + + // If pointer hasn't moved, no need to do anything. + if (idx == self.chip_log_state.last_idx) { + return; + } + + // Read entire buf for now. We could read only what we need, but then we + // run into annoying alignment issues in `bp_read`. + var buf: [1024]u8 = undefined; + self.bus.bp_read(chip_log_mem.buf, &buf); + + while (self.chip_log_state.last_idx != idx) { + const b = buf[self.chip_log_state.last_idx]; + if (b == '\r' or b == '\n') { + if (self.chip_log_state.buf_count != 0) { + chip_log.debug("{s}", .{self.chip_log_state.buf[0..self.chip_log_state.buf_count]}); + self.chip_log_state.buf_count = 0; + } + } else if (self.chip_log_state.buf_count < self.chip_log_state.buf.len) { + self.chip_log_state.buf[self.chip_log_state.buf_count] = b; + self.chip_log_state.buf_count += 1; + } + + self.chip_log_state.last_idx += 1; + if (self.chip_log_state.last_idx == 1024) { + self.chip_log_state.last_idx = 0; + } + } + } + + pub fn run(self: *Self) void { + while (true) { + self.log_read(); + } + } +}; + +pub const Chip = struct { + arm_core_base_address: u32, + socsram_base_address: u32, + bluetooth_base_address: u32, + socsram_wrapper_base_address: u32, + sdiod_core_base_address: u32, + pmu_base_address: u32, + chip_ram_size: u32, + atcm_ram_base_address: u32, + socram_srmem_size: u32, + chanspec_band_mask: u32, + chanspec_band_2g: u32, + chanspec_band_5g: u32, + chanspec_band_shift: u32, + chanspec_bw_10: u32, + chanspec_bw_20: u32, + chanspec_bw_40: u32, + chanspec_bw_mask: u32, + chanspec_bw_shift: u32, + chanspec_ctl_sb_lower: u32, + chanspec_ctl_sb_upper: u32, + chanspec_ctl_sb_none: u32, + chanspec_ctl_sb_mask: u32, +}; + +const WRAPPER_REGISTER_OFFSET: u32 = 0x100000; + +const CYW43439_Chip: Chip = .{ + .arm_core_base_address = 0x18003000 + WRAPPER_REGISTER_OFFSET, + .socsram_base_address = 0x18004000, + .bluetooth_base_address = 0x19000000, + .socsram_wrapper_base_address = 0x18004000 + WRAPPER_REGISTER_OFFSET, + .sdiod_core_base_address = 0x18002000, + .pmu_base_address = 0x18000000, + .chip_ram_size = 512 * 1024, + .atcm_ram_base_address = 0, + .socram_srmem_size = 64 * 1024, + .chanspec_band_mask = 0xc000, + .chanspec_band_2g = 0x0000, + .chanspec_band_5g = 0xc000, + .chanspec_band_shift = 14, + .chanspec_bw_10 = 0x0800, + .chanspec_bw_20 = 0x1000, + .chanspec_bw_40 = 0x1800, + .chanspec_bw_mask = 0x3800, + .chanspec_bw_shift = 11, + .chanspec_ctl_sb_lower = 0x0000, + .chanspec_ctl_sb_upper = 0x0100, + .chanspec_ctl_sb_none = 0x0000, + .chanspec_ctl_sb_mask = 0x0700, +}; + +const LogState = struct { + addr: u32 = 0, + last_idx: usize = 0, + buf: [256]u8 = undefined, + buf_count: usize = 0, +}; + +const Core = enum(u2) { + wlan = 0, + socram = 1, + sdiod = 2, + + fn base_addr(self: Core) u32 { + return switch (self) { + .wlan => CYW43439_Chip.arm_core_base_address, + .socram => CYW43439_Chip.socsram_wrapper_base_address, + .sdiod => CYW43439_Chip.sdiod_core_base_address, + }; + } +}; + +pub const SharedMemData = extern struct { + flags: u32, + trap_addr: u32, + assert_exp_addr: u32, + assert_file_addr: u32, + assert_line: u32, + console_addr: u32, + msgtrace_addr: u32, + fwid: u32, +}; + +pub const SharedMemLog = extern struct { + buf: u32, + buf_size: u32, + idx: u32, + out_idx: u32, +}; diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index a21a5f4e9..a8deba1f1 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -55,6 +55,7 @@ pub fn build(b: *std.Build) void { .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, .{ .name = "usb-cdc", .file = "src/usb_cdc.zig" }, .{ .name = "dma", .file = "src/dma.zig" }, + .{ .name = "cyw43", .file = "src/cyw43.zig" }, }; var available_examples = std.ArrayList(Example).init(b.allocator); diff --git a/examples/raspberrypi/rp2xxx/src/cyw43.zig b/examples/raspberrypi/rp2xxx/src/cyw43.zig new file mode 100644 index 000000000..b8d260712 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/cyw43.zig @@ -0,0 +1,47 @@ +//! This example is work in progress on CYW43xx WiFi/BT driver +//! Tested on Pico W and Pico 2 W +//! Driver code based on: https://github.com/embassy-rs/embassy/tree/main/cyw43 +const std = @import("std"); +const microzig = @import("microzig"); +const rp2xxx = microzig.hal; +const time = rp2xxx.time; +const gpio = rp2xxx.gpio; +const pio = rp2xxx.pio; + +const drivers = microzig.hal.drivers; +const CYW43_Pio_Device = drivers.CYW43_Pio_Device; + +const uart = rp2xxx.uart.instance.num(0); +const baud_rate = 115200; +const uart_tx_pin = gpio.num(0); + +pub const microzig_options = microzig.Options{ + .log_level = .debug, + .logFn = rp2xxx.uart.logFn, +}; + +pub fn main() !void { + // init uart logging + uart_tx_pin.set_function(.uart); + uart.apply(.{ + .baud_rate = baud_rate, + .clock_config = rp2xxx.clock_config, + }); + rp2xxx.uart.init_logger(uart); + + const cyw43_config = drivers.CYW43_Pio_Device_Config{ + .spi = .{ + .pio = pio.num(0), + .cs_pin = gpio.num(25), + .io_pin = gpio.num(24), + .clk_pin = gpio.num(29), + }, + .pwr_pin = gpio.num(23), + }; + var cyw43: CYW43_Pio_Device = .{}; + try cyw43.init(cyw43_config); + + // The driver isn't finished yet, so we're using this infinite test loop to process all internal driver events. + // Eventually, this will be replaced by a dedicated driver task/thread. + cyw43.test_loop(); +} diff --git a/port/raspberrypi/rp2xxx/src/hal.zig b/port/raspberrypi/rp2xxx/src/hal.zig index ee9f7f0c7..143aaba20 100644 --- a/port/raspberrypi/rp2xxx/src/hal.zig +++ b/port/raspberrypi/rp2xxx/src/hal.zig @@ -28,6 +28,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 cyw49_pio_spi = @import("hal/cyw43_pio_spi.zig"); pub const drivers = @import("hal/drivers.zig"); pub const compatibility = @import("hal/compatibility.zig"); pub const image_def = @import("hal/image_def.zig"); diff --git a/port/raspberrypi/rp2xxx/src/hal/cyw43_pio_spi.zig b/port/raspberrypi/rp2xxx/src/hal/cyw43_pio_spi.zig new file mode 100644 index 000000000..7fe27e3c4 --- /dev/null +++ b/port/raspberrypi/rp2xxx/src/hal/cyw43_pio_spi.zig @@ -0,0 +1,260 @@ +//! CYW43 3-wire SPI driver build on PIO +//! Code based on embassy cyw43-pio driver https://github.com/embassy-rs/embassy/tree/main/cyw43-pio (last commit: d41eeea) +const std = @import("std"); +const microzig = @import("microzig"); +const hal = @import("../hal.zig"); + +const Cyw43_Spi = microzig.drivers.wireless.Cyw43_Spi; + +const chip = microzig.hal.compatibility.chip; + +const cyw43spi_program = blk: { + @setEvalBranchQuota(5000); + break :blk hal.pio.assemble( + \\.program cyw43spi + \\.side_set 1 + \\ + \\.wrap_target + \\ + \\; write out x-1 bits + \\lp: + \\out pins, 1 side 0 + \\jmp x-- lp side 1 + \\ + \\; switch directions + \\set pindirs, 0 side 0 + \\nop side 0 + \\ + \\; read in y-1 bits + \\lp2: + \\in pins, 1 side 1 + \\jmp y-- lp2 side 0 + \\ + \\; wait for event and irq host + \\wait 1 pin 0 side 0 + \\irq 0 side 0 + \\ + \\.wrap + , .{}).get_program_by_name("cyw43spi"); +}; + +pub const Cyw43PioSpi_Config = struct { + pio: hal.pio.Pio, + cs_pin: hal.gpio.Pin, + io_pin: hal.gpio.Pin, + clk_pin: hal.gpio.Pin, +}; + +pub fn init(config: Cyw43PioSpi_Config) !Cyw43PioSpi { + const sm = try config.pio.claim_unused_state_machine(); + + // Chip select pin setup + config.cs_pin.set_function(.sio); + config.cs_pin.set_direction(.out); + config.cs_pin.put(1); + + // IO pin setup + config.pio.gpio_init(config.io_pin); + config.io_pin.set_output_disabled(false); + config.io_pin.set_pull(.disabled); + config.io_pin.set_schmitt_trigger(.enabled); + + config.pio.set_input_sync_bypass(to_pio_pin_num(config.io_pin)); + + config.io_pin.set_drive_strength(.@"12mA"); + config.io_pin.set_slew_rate(.fast); + + // Clock pin setup + config.pio.gpio_init(config.clk_pin); + config.clk_pin.set_output_disabled(false); + config.clk_pin.set_drive_strength(.@"12mA"); + config.clk_pin.set_slew_rate(.fast); + + try config.pio.sm_load_and_start_program(sm, cyw43spi_program, .{ + // Default max value from pico-sdk 62.5Mhz + .clkdiv = .{ .int = 2, .frac = 0 }, + .pin_mappings = .{ + .out = .{ .base = to_pio_pin_num(config.io_pin), .count = 1 }, + .set = .{ .base = to_pio_pin_num(config.io_pin), .count = 1 }, + .side_set = .{ .base = to_pio_pin_num(config.clk_pin), .count = 1 }, + .in_base = to_pio_pin_num(config.io_pin), + }, + .shift = .{ + .out_shiftdir = .left, + .in_shiftdir = .left, + .autopull = true, + .autopush = true, + }, + }); + + config.pio.sm_set_pindir(sm, to_pio_pin_num(config.clk_pin), 1, .out); + config.pio.sm_set_pindir(sm, to_pio_pin_num(config.io_pin), 1, .out); + + config.pio.sm_set_pin(sm, to_pio_pin_num(config.clk_pin), 1, 0); + config.pio.sm_set_pin(sm, to_pio_pin_num(config.io_pin), 1, 0); + + return .{ + .pio = config.pio, + .sm = sm, + .cs_pin = config.cs_pin, + .io_pin = config.io_pin, + .clk_pin = config.clk_pin, + }; +} + +fn to_pio_pin_num(pin: hal.gpio.Pin) u5 { + return @truncate(@intFromEnum(pin)); +} + +pub const Cyw43PioSpi = struct { + const Self = @This(); + + pio: hal.pio.Pio, + sm: hal.pio.StateMachine, + cs_pin: hal.gpio.Pin, + io_pin: hal.gpio.Pin, + clk_pin: hal.gpio.Pin, + + pub fn spi_read_blocking(self: *Self, cmd: u32, buffer: []u32) u32 { + self.cs_pin.put(0); + defer self.cs_pin.put(1); + + return self.read_blocking(cmd, buffer); + } + + pub fn spi_write_blocking(self: *Self, buffer: []const u32) u32 { + self.cs_pin.put(0); + defer self.cs_pin.put(1); + + return self.write_blocking(buffer); + } + + fn read_blocking(self: *Self, cmd: u32, buffer: []u32) u32 { + self.pio.sm_set_enabled(self.sm, false); + + const write_bits = 31; + const read_bits = buffer.len * 32 + 32 - 1; + + self.pio.sm_exec_set_y(self.sm, read_bits); + self.pio.sm_exec_set_x(self.sm, write_bits); + self.pio.sm_exec_set_pindir(self.sm, 0b1); + self.pio.sm_exec_jmp(self.sm, cyw43spi_program.wrap_target.?); + + self.pio.sm_set_enabled(self.sm, true); + + const dma_ch = hal.dma.claim_unused_channel().?; + defer dma_ch.unclaim(); + + dma_ch.trigger_transfer(self.get_pio_tx_fifo_addr(), @intFromPtr(&cmd), 1, .{ + .data_size = .size_32, + .enable = true, + .read_increment = true, + .write_increment = false, + .dreq = self.get_pio_tx_dreq(), + }); + + dma_ch.wait_for_finish_blocking(); + + dma_ch.trigger_transfer(@intFromPtr(buffer.ptr), self.get_pio_rx_fifo_addr(), buffer.len, .{ + .data_size = .size_32, + .enable = true, + .read_increment = false, + .write_increment = true, + .dreq = self.get_pio_rx_dreq(), + }); + + dma_ch.wait_for_finish_blocking(); + + var status: u32 = 0; + dma_ch.trigger_transfer(@intFromPtr(&status), self.get_pio_rx_fifo_addr(), 1, .{ + .data_size = .size_32, + .enable = true, + .read_increment = false, + .write_increment = true, + .dreq = self.get_pio_rx_dreq(), + }); + + dma_ch.wait_for_finish_blocking(); + + return status; + } + + fn write_blocking(self: *Self, buffer: []const u32) u32 { + self.pio.sm_set_enabled(self.sm, false); + + const write_bits = buffer.len * 32 - 1; + const read_bits = 31; + + self.pio.sm_exec_set_x(self.sm, write_bits); + self.pio.sm_exec_set_y(self.sm, read_bits); + self.pio.sm_exec_set_pindir(self.sm, 0b1); + self.pio.sm_exec_jmp(self.sm, cyw43spi_program.wrap_target.?); + + self.pio.sm_set_enabled(self.sm, true); + + const dma_ch = hal.dma.claim_unused_channel().?; + defer dma_ch.unclaim(); + + dma_ch.trigger_transfer(self.get_pio_tx_fifo_addr(), @intFromPtr(buffer.ptr), buffer.len, .{ + .data_size = .size_32, + .enable = true, + .read_increment = true, + .write_increment = false, + .dreq = self.get_pio_tx_dreq(), + }); + + dma_ch.wait_for_finish_blocking(); + + var status: u32 = 0; + dma_ch.trigger_transfer(@intFromPtr(&status), self.get_pio_rx_fifo_addr(), 1, .{ + .data_size = .size_32, + .enable = true, + .read_increment = false, + .write_increment = true, + .dreq = self.get_pio_rx_dreq(), + }); + + dma_ch.wait_for_finish_blocking(); + + return status; + } + + inline fn get_pio_tx_fifo_addr(self: *Self) u32 { + return @intFromPtr(self.pio.sm_get_tx_fifo(self.sm)); + } + + inline fn get_pio_rx_fifo_addr(self: *Self) u32 { + return @intFromPtr(self.pio.sm_get_rx_fifo(self.sm)); + } + + inline fn get_pio_tx_dreq(self: *Self) hal.dma.Dreq { + return @enumFromInt(@intFromEnum(self.pio) * @as(u6, 8) + @intFromEnum(self.sm)); + } + + inline fn get_pio_rx_dreq(self: *Self) hal.dma.Dreq { + return @enumFromInt(@intFromEnum(self.pio) * @as(u6, 8) + @intFromEnum(self.sm) + 4); + } + + /// Cyw43_Spi interface implementation + pub fn cyw43_spi(self: *Self) Cyw43_Spi { + return .{ + .ptr = self, + .vtable = &vtable, + }; + } + + const vtable = Cyw43_Spi.VTable{ + .spi_read_blocking_fn = spi_read_blocking_fn, + .spi_write_blocking_fn = spi_write_blocking_fn, + }; + + fn spi_read_blocking_fn(spi: *anyopaque, cmd: u32, buffer: []u32) u32 { + const cyw43spi: *Self = @ptrCast(@alignCast(spi)); + return cyw43spi.spi_read_blocking(cmd, buffer); + } + + fn spi_write_blocking_fn(spi: *anyopaque, buffer: []const u32) u32 { + const cyw43spi: *Self = @ptrCast(@alignCast(spi)); + return cyw43spi.spi_write_blocking(buffer); + } +}; diff --git a/port/raspberrypi/rp2xxx/src/hal/dma.zig b/port/raspberrypi/rp2xxx/src/hal/dma.zig index 150ce8860..03451b25e 100644 --- a/port/raspberrypi/rp2xxx/src/hal/dma.zig +++ b/port/raspberrypi/rp2xxx/src/hal/dma.zig @@ -3,8 +3,8 @@ const assert = std.debug.assert; const microzig = @import("microzig"); const DMA = microzig.chip.peripherals.DMA; -const Dreq = microzig.chip.types.peripherals.DMA.Dreq; -const DataSize = microzig.chip.types.peripherals.DMA.DataSize; +pub const Dreq = microzig.chip.types.peripherals.DMA.Dreq; +pub const DataSize = microzig.chip.types.peripherals.DMA.DataSize; const chip = @import("compatibility.zig").chip; diff --git a/port/raspberrypi/rp2xxx/src/hal/drivers.zig b/port/raspberrypi/rp2xxx/src/hal/drivers.zig index 6215eaa9c..ab9c52dc1 100644 --- a/port/raspberrypi/rp2xxx/src/hal/drivers.zig +++ b/port/raspberrypi/rp2xxx/src/hal/drivers.zig @@ -323,3 +323,45 @@ pub const ClockDevice = struct { return @enumFromInt(t); } }; + +const Cyw43PioSpi = microzig.hal.cyw49_pio_spi.Cyw43PioSpi; +const Cyw43_Spi = microzig.drivers.wireless.Cyw43_Spi; +const Cyw43_Bus = microzig.drivers.wireless.Cyw43_Bus; +const Cyw43_Runner = microzig.drivers.wireless.Cyw43_Runner; + +pub const CYW43_Pio_Device_Config = struct { + spi: hal.cyw49_pio_spi.Cyw43PioSpi_Config, + pwr_pin: hal.gpio.Pin, +}; + +// TODO: CYW43 top level struct just for testing purpose (please redesign) +pub const CYW43_Pio_Device = struct { + const Self = @This(); + pwr_pin: GPIO_Device = undefined, + cyw43_pio_spi: Cyw43PioSpi = undefined, + cyw43_spi: Cyw43_Spi = undefined, + cyw43_bus: Cyw43_Bus = undefined, + cyw43_runner: Cyw43_Runner = undefined, + + pub fn init(this: *Self, config: CYW43_Pio_Device_Config) !void { + std.log.info("before gpio init", .{}); + + this.cyw43_pio_spi = try hal.cyw49_pio_spi.init(config.spi); + this.cyw43_spi = this.cyw43_pio_spi.cyw43_spi(); + + config.pwr_pin.set_function(.sio); + config.pwr_pin.set_direction(.out); + var pwr_gpio = GPIO_Device.init(config.pwr_pin); + + this.cyw43_bus = .{ .pwr_pin = pwr_gpio.digital_io(), .spi = &this.cyw43_spi, .internal_delay_ms = hal.time.sleep_ms }; + this.cyw43_runner = .{ .bus = &this.cyw43_bus, .internal_delay_ms = hal.time.sleep_ms }; + + try this.cyw43_runner.init(); + } + + pub fn test_loop(this: *Self) void { + while (true) { + this.cyw43_runner.run(); + } + } +}; diff --git a/port/raspberrypi/rp2xxx/src/hal/gpio.zig b/port/raspberrypi/rp2xxx/src/hal/gpio.zig index 4cb061f7b..f3b2e47ca 100644 --- a/port/raspberrypi/rp2xxx/src/hal/gpio.zig +++ b/port/raspberrypi/rp2xxx/src/hal/gpio.zig @@ -448,6 +448,11 @@ pub const Pin = enum(u6) { pads_reg.modify(.{ .IE = @intFromBool(enabled) }); } + pub inline fn set_output_disabled(pin: Pin, disabled: bool) void { + const pads_reg = pin.get_pads_reg(); + pads_reg.modify(.{ .OD = @intFromBool(disabled) }); + } + pub inline fn set_function(gpio: Pin, function: Function) void { const pads_reg = gpio.get_pads_reg(); pads_reg.modify(.{ diff --git a/port/raspberrypi/rp2xxx/src/hal/pio/common.zig b/port/raspberrypi/rp2xxx/src/hal/pio/common.zig index 1def10eee..b5f51931f 100644 --- a/port/raspberrypi/rp2xxx/src/hal/pio/common.zig +++ b/port/raspberrypi/rp2xxx/src/hal/pio/common.zig @@ -190,6 +190,13 @@ pub fn PioImpl(EnumType: type, chip: Chip) type { } else error.NoSpace; } + pub fn set_input_sync_bypass(self: EnumType, pin: u5) void { + const mask = @as(u32, 1) << pin; + var val = self.get_regs().INPUT_SYNC_BYPASS.raw; + val |= mask; + self.get_regs().INPUT_SYNC_BYPASS.write_raw(val); + } + pub fn get_sm_regs(self: EnumType, sm: StateMachine) *volatile StateMachine.Regs { const pio_regs = self.get_regs(); // SM0_CLKDIV is the first one, which is why we are taking its address @@ -561,6 +568,30 @@ pub fn PioImpl(EnumType: type, chip: Chip) type { }, }); } + + pub fn sm_exec_set_x(self: EnumType, sm: StateMachine, value: u32) void { + // 32 = .bit_count = 0 + const inst = Instruction(chip){ .tag = .out, .delay_side_set = 0, .payload = .{ .out = .{ .destination = .x, .bit_count = 0 } } }; + sm_write(self, sm, value); + sm_exec(self, sm, inst); + } + + pub fn sm_exec_set_y(self: EnumType, sm: StateMachine, value: u32) void { + // 32 = .bit_count = 0 + const inst = Instruction(chip){ .tag = .out, .delay_side_set = 0, .payload = .{ .out = .{ .destination = .y, .bit_count = 0 } } }; + sm_write(self, sm, value); + sm_exec(self, sm, inst); + } + + pub fn sm_exec_set_pindir(self: EnumType, sm: StateMachine, data: u5) void { + const inst = Instruction(chip){ .tag = .set, .delay_side_set = 0, .payload = .{ .set = .{ .destination = .pindirs, .data = data } } }; + sm_exec(self, sm, inst); + } + + pub fn sm_exec_jmp(self: EnumType, sm: StateMachine, to_addr: u5) void { + const inst = Instruction(chip){ .tag = .jmp, .delay_side_set = 0, .payload = .{ .jmp = .{ .address = to_addr, .condition = .always } } }; + sm_exec(self, sm, inst); + } }; } diff --git a/port/raspberrypi/rp2xxx/src/hal/pio/rp2040.zig b/port/raspberrypi/rp2xxx/src/hal/pio/rp2040.zig index 0cf3b4961..58c46ddc9 100644 --- a/port/raspberrypi/rp2xxx/src/hal/pio/rp2040.zig +++ b/port/raspberrypi/rp2xxx/src/hal/pio/rp2040.zig @@ -35,6 +35,7 @@ pub const Pio = enum(u1) { pub const add_program_at_offset_unlocked = PioImpl.add_program_at_offset_unlocked; pub const add_program = PioImpl.add_program; pub const claim_unused_state_machine = PioImpl.claim_unused_state_machine; + pub const set_input_sync_bypass = PioImpl.set_input_sync_bypass; pub const get_sm_regs = PioImpl.get_sm_regs; pub const get_irq_regs = PioImpl.get_irq_regs; pub const sm_set_clkdiv = PioImpl.sm_set_clkdiv; @@ -102,4 +103,9 @@ pub const Pio = enum(u1) { pub const sm_init = PioImpl.sm_init; pub const sm_exec = PioImpl.sm_exec; pub const sm_load_and_start_program = PioImpl.sm_load_and_start_program; + + pub const sm_exec_set_x = PioImpl.sm_exec_set_x; + pub const sm_exec_set_y = PioImpl.sm_exec_set_y; + pub const sm_exec_set_pindir = PioImpl.sm_exec_set_pindir; + pub const sm_exec_jmp = PioImpl.sm_exec_jmp; }; diff --git a/port/raspberrypi/rp2xxx/src/hal/pio/rp2350.zig b/port/raspberrypi/rp2xxx/src/hal/pio/rp2350.zig index ec182f9b8..e10c44f5b 100644 --- a/port/raspberrypi/rp2xxx/src/hal/pio/rp2350.zig +++ b/port/raspberrypi/rp2xxx/src/hal/pio/rp2350.zig @@ -41,6 +41,7 @@ pub const Pio = enum(u2) { pub const add_program_at_offset_unlocked = PioImpl.add_program_at_offset_unlocked; pub const add_program = PioImpl.add_program; pub const claim_unused_state_machine = PioImpl.claim_unused_state_machine; + pub const set_input_sync_bypass = PioImpl.set_input_sync_bypass; pub const get_sm_regs = PioImpl.get_sm_regs; pub const get_irq_regs = PioImpl.get_irq_regs; pub const sm_set_clkdiv = PioImpl.sm_set_clkdiv; @@ -115,4 +116,9 @@ pub const Pio = enum(u2) { pub const sm_init = PioImpl.sm_init; pub const sm_exec = PioImpl.sm_exec; pub const sm_load_and_start_program = PioImpl.sm_load_and_start_program; + + pub const sm_exec_set_x = PioImpl.sm_exec_set_x; + pub const sm_exec_set_y = PioImpl.sm_exec_set_y; + pub const sm_exec_set_pindir = PioImpl.sm_exec_set_pindir; + pub const sm_exec_jmp = PioImpl.sm_exec_jmp; };