From cb1ea1a22fde26653d4285d8e23fb0926bdcff72 Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Mon, 20 Jul 2026 18:35:17 -0400 Subject: [PATCH 1/7] ch32v: Add usbd peripheral --- core/src/core/usb.zig | 15 +- examples/wch/ch32v/build.zig | 1 + port/wch/ch32v/src/boards/LANA_TNY.zig | 6 + port/wch/ch32v/src/hals/ch32v20x.zig | 10 +- port/wch/ch32v/src/hals/clocks.zig | 18 + port/wch/ch32v/src/hals/usbd.zig | 810 +++++++++++++++++++++++++ port/wch/ch32v/src/hals/usbfs.zig | 21 +- port/wch/ch32v/src/hals/usbhs.zig | 26 + 8 files changed, 903 insertions(+), 4 deletions(-) create mode 100644 port/wch/ch32v/src/hals/usbd.zig diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index a21e9e554..740e6b43f 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -566,7 +566,7 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type { const desc_type: descriptor.Type = @fromBackingInt(@intCast(value >> 8)); const desc_idx: u8 = @truncate(value); log.debug("Request for {any} descriptor {}", .{ desc_type, desc_idx }); - return switch (desc_type) { + const result = switch (desc_type) { .device => asBytes(&device_descriptor), .device_qualifier => asBytes(comptime &device_descriptor.qualifier()), .configuration => asBytes(&config_descriptor), @@ -581,6 +581,19 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type { }, else => nak, }; + // DEBUG: log raw descriptor bytes for configuration descriptor + if (result) |data| { + if (desc_type == .Configuration) { + log.warn("CONFIG DESC ({} bytes):", .{data.len}); + var off: usize = 0; + while (off < data.len) { + const end = @min(off + 16, data.len); + log.warn(" [{x:0>4}] {any}", .{ off, data[off..end] }); + off = end; + } + } + } + return result; } fn process_set_config(self: *@This(), device_itf: *DeviceInterface, cfg_num: u16) void { diff --git a/examples/wch/ch32v/build.zig b/examples/wch/ch32v/build.zig index 995f2de11..fe16cf41d 100644 --- a/examples/wch/ch32v/build.zig +++ b/examples/wch/ch32v/build.zig @@ -42,6 +42,7 @@ pub fn build(b: *std.Build) void { .{ .target = mb.ports.ch32v.boards.ch32v203.nano_ch32v203, .name = "nano_ch32v203_usb_cdc", .file = "src/usb_cdc.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.suzuduino_uno_v1b, .name = "suzuduino_blinky", .file = "src/board_blinky.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.suzuduino_uno_v1b, .name = "suzuduino_usb_cdc", .file = "src/usb_cdc.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_usb_cdc", .file = "src/usb_cdc.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_dma", .file = "src/dma.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_ws2812", .file = "src/ws2812.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_uart_echo", .file = "src/uart_echo.zig" }, diff --git a/port/wch/ch32v/src/boards/LANA_TNY.zig b/port/wch/ch32v/src/boards/LANA_TNY.zig index 96333ee6a..62c61a2e8 100644 --- a/port/wch/ch32v/src/boards/LANA_TNY.zig +++ b/port/wch/ch32v/src/boards/LANA_TNY.zig @@ -5,6 +5,11 @@ pub const microzig = @import("microzig"); pub const chip = @import("chip"); const ch32v = microzig.hal; +pub const product_string = "LANA TNY"; + +/// CH32V203G6U6 has USBD (PMA-based), not USBFS/OTG +pub const usb_driver: ch32v.UsbDriver = .usbd; + /// Clock configuration for this board pub const clock_config: ch32v.clocks.Config = .{ .source = .hsi, @@ -18,6 +23,7 @@ pub const cpu_frequency = clock_config.target_frequency; pub fn init() void { ch32v.clocks.init(clock_config); ch32v.time.init(); + ch32v.clocks.enable_usbd_clock(); } /// Default UART: USART2 on PA2 (exposed on the board header) diff --git a/port/wch/ch32v/src/hals/ch32v20x.zig b/port/wch/ch32v/src/hals/ch32v20x.zig index 75de2b09b..e8ec6e2f1 100644 --- a/port/wch/ch32v/src/hals/ch32v20x.zig +++ b/port/wch/ch32v/src/hals/ch32v20x.zig @@ -9,7 +9,15 @@ pub const i2c = @import("i2c.zig"); pub const usart = @import("usart.zig"); pub const spi = @import("spi.zig"); pub const dma = @import("dma.zig"); -pub const usb = @import("usbfs.zig"); +pub const UsbDriver = enum { usbd, usbfs }; + +pub const usb = if (microzig.config.has_board and @hasDecl(microzig.board, "usb_driver")) + switch (microzig.board.usb_driver) { + .usbd => @import("usbd.zig"), + .usbfs => @import("usbfs.zig"), + } +else + @import("usbfs.zig"); /// HSI (High Speed Internal) oscillator frequency /// This is the fixed internal RC oscillator frequency for CH32V20x diff --git a/port/wch/ch32v/src/hals/clocks.zig b/port/wch/ch32v/src/hals/clocks.zig index d3e51f2be..c568c4ed5 100644 --- a/port/wch/ch32v/src/hals/clocks.zig +++ b/port/wch/ch32v/src/hals/clocks.zig @@ -55,6 +55,7 @@ const peripheral = enum { TIM2, TIM3, TIM4, + USBD, }; pub fn enable_peripheral_clock(p: peripheral) void { @@ -81,6 +82,7 @@ pub fn enable_peripheral_clock(p: peripheral) void { .TIM2 => RCC.APB1PCENR.modify(.{ .TIM2EN = 1 }), .TIM3 => RCC.APB1PCENR.modify(.{ .TIM3EN = 1 }), .TIM4 => RCC.APB1PCENR.modify(.{ .TIM4EN = 1 }), + .USBD => RCC.APB1PCENR.modify(.{ .USBDEN = 1 }), } } @@ -432,6 +434,22 @@ pub fn enable_usbfs_clock() void { // Enable the AHB clock gate for the USBFS peripheral block. enable_peripheral_clock(.USBOTG); } +// ============================================================================ +// Enable + configure USBD clocks (PMA-based USB device, APB1). +// ============================================================================ +pub fn enable_usbd_clock() void { + const sys_clock = get_sysclk(); + switch (sys_clock) { + 48_000_000 => RCC.CFGR0.modify(.{ .USBPRE = 0 }), + 96_000_000 => RCC.CFGR0.modify(.{ .USBPRE = 1 }), + 144_000_000 => RCC.CFGR0.modify(.{ .USBPRE = 2 }), + else => @panic("Unsupported sysclock for USBD"), + } + + // Enable the APB1 clock gate for the USBD peripheral. + enable_peripheral_clock(.USBD); +} + // ============================================================================ // Enable + configure USBHS clocks. // ============================================================================ diff --git a/port/wch/ch32v/src/hals/usbd.zig b/port/wch/ch32v/src/hals/usbd.zig new file mode 100644 index 000000000..a614d396f --- /dev/null +++ b/port/wch/ch32v/src/hals/usbd.zig @@ -0,0 +1,810 @@ +//! WCH CH32V203 USBD (PMA-based USB device) backend +//! +//! This driver targets the USBD peripheral at 0x40005C00, which is a +//! PMA-based full-speed USB device controller similar to the STM32F103. +//! It uses EPR registers with toggle-on-write semantics and a Packet Memory +//! Area (PMA) at 0x40006000 for buffer storage. +//! +//! This is NOT the same as the USBFS/OTG peripheral at 0x50000000 +//! (which uses DMA buffers in SRAM). +//! NOTE: Some variants of the ch32v203 have USBD, some have USBFS, and some +//! have both. + +const std = @import("std"); +const assert = std.debug.assert; +const log = std.log.scoped(.usb_dev); + +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; +const usb = microzig.core.usb; +const types = usb.types; +const descriptor = usb.descriptor; + +pub const max_packet_size: u11 = 64; + +pub const USB_MAX_ENDPOINTS_COUNT = 8; + +pub const Config = struct { + max_endpoints_count: comptime_int = USB_MAX_ENDPOINTS_COUNT, + prefer_high_speed: bool = false, + /// Unused for USBD (buffers live in PMA), kept for API compat. + buffer_bytes: comptime_int = 0, +}; + +// --- Hardware peripheral access --- + +/// USB peripheral (named "USBD" on CH32V103, "USB" on CH32V20x) +const USB_PERIPH = if (@hasDecl(peripherals, "USBD")) peripherals.USBD else peripherals.USB; + +/// Base address derived from the peripheral pointer (used for EPR indexed access) +const USBD_BASE: usize = @intFromPtr(USB_PERIPH); + +/// EXTEND peripheral (for D+ pull-up control via USBDPU) +const EXTEND = peripherals.EXTEND; + +/// Packet Memory Area base (CPU address space) +const PMA_BASE: usize = 0x40006000; +/// PMA size in bytes (peripheral address space) +const PMA_SIZE: usize = 512; +/// PMA access multiplier: each u16 PMA word occupies a u32 slot in CPU +/// address space (2x mapping), same as STM32F103. +const PMA_ACCESS_MULT: usize = 2; + +/// ISTR packed struct type (field names vary between chips: RST vs RESET) +const Istr = @TypeOf(USB_PERIPH.ISTR).underlying_type; + +// ISTR bit masks for rc_w0 clearing (write 0 to clear, write 1 to keep). +// Raw masks are used because field names differ between chip variants. +const ISTR_ESOF: u16 = 1 << 8; +const ISTR_SOF: u16 = 1 << 9; +const ISTR_RST: u16 = 1 << 10; +const ISTR_SUSP: u16 = 1 << 11; +const ISTR_ERR: u16 = 1 << 13; +const ISTR_PMAOVR: u16 = 1 << 14; + +// -- EPR bit positions / masks (raw access needed for toggle-on-write) -- +const EPR_EA_MASK: u16 = 0x000F; // [3:0] Endpoint Address +const EPR_STAT_TX_MASK: u16 = 0x0030; // [5:4] TX Status (toggle) +const EPR_DTOG_TX: u16 = 0x0040; // [6] TX Data Toggle (toggle) +const EPR_CTR_TX: u16 = 0x0080; // [7] Correct Transfer TX (W0C) +const EPR_EP_KIND: u16 = 0x0100; // [8] Endpoint Kind +const EPR_EP_TYPE_MASK: u16 = 0x0600; // [10:9] Endpoint Type +const EPR_SETUP: u16 = 0x0800; // [11] Setup transaction completed (RO) +const EPR_STAT_RX_MASK: u16 = 0x3000; // [13:12] RX Status (toggle) +const EPR_DTOG_RX: u16 = 0x4000; // [14] RX Data Toggle (toggle) +const EPR_CTR_RX: u16 = 0x8000; // [15] Correct Transfer RX (W0C) + +// Bits that are read/write (non-toggle, non-W0C): EA, EP_KIND, EP_TYPE +const EPR_RW_MASK: u16 = EPR_EA_MASK | EPR_EP_KIND | EPR_EP_TYPE_MASK; + +// EP_TYPE values (written into EPR bits [10:9]) +const EP_TYPE_BULK: u16 = 0x0000; +const EP_TYPE_CONTROL: u16 = 0x0200; +const EP_TYPE_ISO: u16 = 0x0400; +const EP_TYPE_INTERRUPT: u16 = 0x0600; + +// STAT_TX / STAT_RX values (2-bit field) +const STAT_DISABLED: u2 = 0b00; +const STAT_STALL: u2 = 0b01; +const STAT_NAK: u2 = 0b10; +const STAT_VALID: u2 = 0b11; + +/// ISTR helper (field names differ between chips: RST vs RESET) +fn istr_is_reset(istr: Istr) bool { + return if (@hasField(Istr, "RST")) istr.RST == 1 else istr.RESET == 1; +} + +// --- Raw EPR reg access (bypass MMIO due to toggle-on-write semantics) --- + +/// EPR registers: 0x00..0x1C (8 x u16 at u32 spacing). +/// These CANNOT use MMIO modify() because STAT_TX/RX, DTOG_TX/RX are +/// toggle-on-write, and CTR_TX/RX are write-0-to-clear. +fn epr_ptr(ep: u4) *volatile u16 { + return @ptrFromInt(USBD_BASE + @as(usize, ep) * 4); +} + +fn epr_read(ep: u4) u16 { + return epr_ptr(ep).*; +} + +fn epr_write(ep: u4, val: u16) void { + epr_ptr(ep).* = val; +} + +// --- EPR manipulation helpers (XOR trick for toggle bits) --- + +/// Set STAT_TX to desired value using XOR trick. +fn epr_set_stat_tx(ep: u4, stat: u2) void { + const val = epr_read(ep); + const current: u2 = @truncate((val & EPR_STAT_TX_MASK) >> 4); + const xor_val: u16 = @as(u16, current ^ stat) << 4; + // Preserve RW bits, set W0C bits to 1 (don't clear), zero other toggle bits + const write_val = (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | xor_val; + epr_write(ep, write_val); +} + +/// Set STAT_RX to desired value using XOR trick. +fn epr_set_stat_rx(ep: u4, stat: u2) void { + const val = epr_read(ep); + const current: u2 = @truncate((val & EPR_STAT_RX_MASK) >> 12); + const xor_val: u16 = @as(u16, current ^ stat) << 12; + const write_val = (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | xor_val; + epr_write(ep, write_val); +} + +/// Set both STAT_TX and STAT_RX simultaneously. +fn epr_set_stat_txrx(ep: u4, stat_tx: u2, stat_rx: u2) void { + const val = epr_read(ep); + const cur_tx: u2 = @truncate((val & EPR_STAT_TX_MASK) >> 4); + const cur_rx: u2 = @truncate((val & EPR_STAT_RX_MASK) >> 12); + const xor_tx: u16 = @as(u16, cur_tx ^ stat_tx) << 4; + const xor_rx: u16 = @as(u16, cur_rx ^ stat_rx) << 12; + const write_val = (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | xor_tx | xor_rx; + epr_write(ep, write_val); +} + +/// Clear CTR_TX (write 0 to the W0C bit, keep CTR_RX as 1). +fn epr_clear_ctr_tx(ep: u4) void { + const val = epr_read(ep); + epr_write(ep, (val & EPR_RW_MASK) | EPR_CTR_RX); +} + +/// Clear CTR_RX (write 0 to the W0C bit, keep CTR_TX as 1). +fn epr_clear_ctr_rx(ep: u4) void { + const val = epr_read(ep); + epr_write(ep, (val & EPR_RW_MASK) | EPR_CTR_TX); +} + +/// Clear DTOG_TX by toggling it if currently set. +fn epr_clear_dtog_tx(ep: u4) void { + const val = epr_read(ep); + if (val & EPR_DTOG_TX != 0) { + epr_write(ep, (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | EPR_DTOG_TX); + } +} + +/// Clear DTOG_RX by toggling it if currently set. +fn epr_clear_dtog_rx(ep: u4) void { + const val = epr_read(ep); + if (val & EPR_DTOG_RX != 0) { + epr_write(ep, (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | EPR_DTOG_RX); + } +} + +/// Configure an endpoint: set EA, EP_TYPE, clear toggles, set initial status. +fn epr_configure(ep: u4, ep_type: u16, stat_tx: u2, stat_rx: u2) void { + // First write: set EA, EP_TYPE, clear everything else + const base: u16 = (@as(u16, ep) & EPR_EA_MASK) | (ep_type & EPR_EP_TYPE_MASK) | EPR_CTR_TX | EPR_CTR_RX; + epr_write(ep, base); + + // Now set desired status bits using XOR trick + epr_set_stat_txrx(ep, stat_tx, stat_rx); + + // Clear data toggles + epr_clear_dtog_tx(ep); + epr_clear_dtog_rx(ep); +} + +// --- PMA (Packet Memory Area) access helpers --- + +// The BTABLE descriptor for each endpoint occupies 8 bytes in PMA space +// (4 x u16 words). With 2x mapping, each u16 occupies a u32 slot in CPU +// address space (only lower 16 bits valid). +// +// Layout per endpoint (PMA offsets): +// +0: TX_ADDR (u16) +// +2: TX_COUNT (u16) +// +4: RX_ADDR (u16) +// +6: RX_COUNT (u16) + +/// Read a u16 from PMA at the given PMA byte offset. +fn pma_read16(pma_offset: u16) u16 { + const addr: usize = PMA_BASE + @as(usize, pma_offset) * PMA_ACCESS_MULT; + const ptr: *volatile u32 = @ptrFromInt(addr); + return @truncate(ptr.*); +} + +/// Write a u16 to PMA at the given PMA byte offset. +fn pma_write16(pma_offset: u16, val: u16) void { + const addr: usize = PMA_BASE + @as(usize, pma_offset) * PMA_ACCESS_MULT; + const ptr: *volatile u32 = @ptrFromInt(addr); + ptr.* = val; +} + +/// BTABLE descriptor field offsets (in PMA u16 words = 2 bytes each) +fn btable_tx_addr_offset(ep: u4) u16 { + return @as(u16, ep) * 8 + 0; +} +fn btable_tx_count_offset(ep: u4) u16 { + return @as(u16, ep) * 8 + 2; +} +fn btable_rx_addr_offset(ep: u4) u16 { + return @as(u16, ep) * 8 + 4; +} +fn btable_rx_count_offset(ep: u4) u16 { + return @as(u16, ep) * 8 + 6; +} + +fn btable_set_tx_addr(ep: u4, pma_addr: u16) void { + pma_write16(btable_tx_addr_offset(ep), pma_addr); +} +fn btable_set_tx_count(ep: u4, count: u16) void { + pma_write16(btable_tx_count_offset(ep), count); +} +fn btable_get_tx_count(ep: u4) u16 { + return pma_read16(btable_tx_count_offset(ep)) & 0x03FF; +} +fn btable_set_rx_addr(ep: u4, pma_addr: u16) void { + pma_write16(btable_rx_addr_offset(ep), pma_addr); +} + +/// Set RX_COUNT with block size encoding for max receivable bytes. +/// For sizes <= 62: BL_SIZE=0, NUM_BLOCK = size/2 +/// For sizes > 62: BL_SIZE=1, NUM_BLOCK = size/32 - 1 +fn btable_set_rx_count(ep: u4, max_size: u16) void { + var val: u16 = 0; + if (max_size <= 62) { + // BL_SIZE=0, NUM_BLOCK = max_size/2 + val = (max_size / 2) << 10; + } else { + // BL_SIZE=1, NUM_BLOCK = max_size/32 - 1 + val = (1 << 15) | (((max_size / 32) - 1) << 10); + } + pma_write16(btable_rx_count_offset(ep), val); +} + +fn btable_get_rx_count(ep: u4) u16 { + return pma_read16(btable_rx_count_offset(ep)) & 0x03FF; +} + +/// Copy bytes from CPU memory into PMA. +fn pma_write_bytes(pma_offset: u16, data: []const u8) void { + var off = pma_offset; + var i: usize = 0; + while (i < data.len) { + const lo: u16 = data[i]; + const hi: u16 = if (i + 1 < data.len) data[i + 1] else 0; + pma_write16(off, lo | (hi << 8)); + off += 2; + i += 2; + } +} + +/// Copy bytes from PMA into CPU memory. +fn pma_read_bytes(pma_offset: u16, buf: []u8, count: usize) void { + var off = pma_offset; + var i: usize = 0; + while (i < count) { + const word = pma_read16(off); + buf[i] = @truncate(word); + if (i + 1 < count) { + buf[i + 1] = @truncate(word >> 8); + } + off += 2; + i += 2; + } +} + +// --- PMA buffer allocation (static layout, bump pointer) --- + +/// PMA memory layout: +/// 0x00 - 0x3F: BTABLE (8 eps x 8 bytes = 64 bytes) +/// 0x40+: endpoint buffers +const PMA_BTABLE_SIZE: u16 = 64; // 8 endpoints * 8 bytes + +// --- Endpoint state tracking (mirrors usbfs.zig pattern) --- + +const EP_State = struct { + pma_addr: u16 = 0, // PMA offset for this buffer + max_size: u16 = 0, // max packet size + // OUT: + rx_armed: bool = false, + rx_limit: u16 = 0, + rx_last_len: u16 = 0, + // IN: + tx_busy: bool = false, +}; + +fn PerEndpointArray(comptime N: comptime_int) type { + return [N][2]EP_State; // [ep][dir] +} + +fn epn(ep: types.Endpoint.Num) u4 { + return @as(u4, @intCast(@intFromEnum(ep))); +} + +/// Polled USBFS device backend for the MicroZig core USB controller. +pub fn Polled(comptime cfg: Config) type { + comptime { + if (cfg.max_endpoints_count < 1) + @compileError("USBD max_endpoints_count must include endpoint 0"); + if (cfg.prefer_high_speed) + @compileError("USBD only supports Full Speed, not High Speed"); + if (cfg.max_endpoints_count > USB_MAX_ENDPOINTS_COUNT) + @compileError("USBD max_endpoints_count cannot exceed 8"); + } + + return struct { + const Self = @This(); + + const vtable: usb.DeviceInterface.VTable = .{ + .ep_writev = ep_writev, + .ep_readv = ep_readv, + .ep_listen = ep_listen, + .ep_open = ep_open, + .set_address = set_address, + }; + + endpoints: PerEndpointArray(cfg.max_endpoints_count), + pma_next: u16, // next free PMA offset + interface: usb.DeviceInterface, + + // Temporary CPU-side buffer for PMA read/write (PMA cannot be + // accessed byte-by-byte, so we stage through this). + staging_buf: [64]u8 = undefined, + + pub fn init(self: *Self) void { + log.warn("USBD init starting", .{}); + self.interface = .{ .vtable = &vtable }; + self.endpoints = @splat(@splat(.{})); + self.pma_next = PMA_BTABLE_SIZE; + + usbd_hw_init(); + + // EP0 is required: open OUT then IN. + self.interface.ep_open(&.{ + .endpoint = .out(.ep0), + .max_packet_size = .from(64), + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .interval = 0, + }); + self.interface.ep_open(&.{ + .endpoint = .in(.ep0), + .max_packet_size = .from(64), + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .interval = 0, + }); + + // EP0 OUT always accepts packets + epr_set_stat_rx(0, STAT_VALID); + + // Enable interrupt masks: bus reset and correct transfer + USB_PERIPH.CNTR.write(.{ .FRES = 0, .PDWN = 0, .RESETM = 1, .CTRM = 1 }); + USB_PERIPH.ISTR.write_raw(0); // clear any flags raised during init + + // Force a clean disconnect/connect cycle so the host detects + // a fresh device attach (matches WCH EVT USB_Port_Set pattern). + usb_port_set(false); // pull-up off, drive D+/D- low (SE0) + { + // ~20ms delay at 48MHz + var d: u32 = 0; + while (d < 960_000) : (d += 1) { + asm volatile ("nop"); + } + } + usb_port_set(true); // pins to floating input, pull-up on + } + + fn pma_alloc(self: *Self, size: u16) u16 { + const addr = self.pma_next; + assert(addr + size <= PMA_SIZE); + self.pma_next += size; + return addr; + } + + fn st(self: *Self, ep_num: types.Endpoint.Num, dir: types.Dir) *EP_State { + return &self.endpoints[@intFromEnum(ep_num)][@intFromEnum(dir)]; + } + + fn on_bus_reset_local(self: *Self) void { + // Clear state + inline for (0..cfg.max_endpoints_count) |i| { + self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_armed = false; + self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_last_len = 0; + self.endpoints[i][@intFromEnum(types.Dir.In)].tx_busy = false; + } + + // Re-initialize BTABLE register and EP0 buffer descriptors + USB_PERIPH.BTABLE.write_raw(0); + const ep0_buf = self.st(.ep0, .Out).pma_addr; + btable_set_tx_addr(0, ep0_buf); + btable_set_tx_count(0, 0); + btable_set_rx_addr(0, ep0_buf); + btable_set_rx_count(0, 64); + + // Fully re-configure EP0: EA=0, CONTROL type, clear DTOGs + epr_configure(0, EP_TYPE_CONTROL, STAT_NAK, STAT_VALID); + + // Non-EP0 endpoint registers are already at 0x0000 (DISABLED) + // after hardware bus reset — leave them alone. Setting them to + // NAK with EA=0 would make them respond to EP0 traffic. + + // Set DADDR: enable function at address 0 + USB_PERIPH.DADDR.write(.{ .EF = 1, .ADD = 0 }); + } + + // --- comptime dispatch helpers --- + + fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4, controller: anytype) void { + switch (dir) { + .In => switch (ep) { + inline 0...15 => |i| { + const num: types.Endpoint.Num = @enumFromInt(i); + controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }); + }, + }, + .Out => switch (ep) { + inline 0...15 => |i| { + const num: types.Endpoint.Num = @enumFromInt(i); + controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }); + }, + }, + } + } + + // --- Poll loop --- + + pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { + _ = in_isr; + const istr = USB_PERIPH.ISTR.read(); + + if (istr_is_reset(istr)) { + USB_PERIPH.ISTR.write_raw(~ISTR_RST); + set_address(&self.interface, 0); + self.on_bus_reset_local(); + controller.on_bus_reset(&self.interface); + } + + if (istr.CTR == 1) { + self.handle_ctr(controller); + } + + if (istr.SUSP == 1) { + USB_PERIPH.ISTR.write_raw(~ISTR_SUSP); + } + + if (istr.ERR == 1) { + USB_PERIPH.ISTR.write_raw(~ISTR_ERR); + } + + if (istr.PMAOVR == 1) { + log.warn("PMA overrun", .{}); + USB_PERIPH.ISTR.write_raw(~ISTR_PMAOVR); + } + + // Clear SOF/ESOF so they don't accumulate + if (istr.SOF == 1 or istr.ESOF == 1) { + USB_PERIPH.ISTR.write_raw(~(ISTR_SOF | ISTR_ESOF)); + } + } + + fn handle_ctr(self: *Self, controller: anytype) void { + // Read ISTR to get EP_ID and DIR + const istr = USB_PERIPH.ISTR.read(); + const ep: u4 = istr.EP_ID; + _ = istr.DIR; + + if (ep >= cfg.max_endpoints_count) return; + + const val = epr_read(ep); + + if (val & EPR_CTR_RX != 0) { + // SETUP or OUT + const is_setup = (val & EPR_SETUP) != 0; + epr_clear_ctr_rx(ep); + + if (is_setup) { + self.handle_setup(ep, controller); + } else { + self.handle_out(ep, controller); + } + } + + if (val & EPR_CTR_TX != 0) { + epr_clear_ctr_tx(ep); + self.handle_in(ep, controller); + } + } + + fn handle_setup(self: *Self, ep: u4, controller: anytype) void { + // Read 8-byte SETUP packet from PMA + const rx_addr = pma_read16(btable_rx_addr_offset(ep)); + pma_read_bytes(rx_addr, &self.staging_buf, 8); + const setup: types.SetupPacket = @bitCast(self.staging_buf[0..8].*); + + // After SETUP, hardware forces STAT_TX=NAK, STAT_RX=NAK and + // clears DTOG_TX/DTOG_RX. We set RX to VALID so EP0 can + // receive the status stage or data stage. + epr_set_stat_rx(ep, STAT_VALID); + + const st_in = self.st(.ep0, .In); + st_in.tx_busy = false; + + controller.on_setup_req(&self.interface, &setup); + } + + fn handle_out(self: *Self, ep: u4, controller: anytype) void { + const len = btable_get_rx_count(ep); + + if (ep == 0) { + const st_out = self.st(.ep0, .Out); + // Read data from PMA into staging buffer + const rx_addr = pma_read16(btable_rx_addr_offset(ep)); + const n: usize = @min(@as(usize, len), 64); + pma_read_bytes(rx_addr, &self.staging_buf, n); + st_out.rx_last_len = @intCast(n); + // Re-arm EP0 RX + btable_set_rx_count(0, 64); + epr_set_stat_rx(0, STAT_VALID); + self.call_on_buffer(.Out, 0, controller); + return; + } + + const num: types.Endpoint.Num = @enumFromInt(ep); + const st_out = self.st(num, .Out); + + if (!st_out.rx_armed) return; + + // Read data from PMA into staging buffer + const rx_addr = pma_read16(btable_rx_addr_offset(ep)); + const n: usize = @min(@as(usize, len), @as(usize, st_out.max_size)); + pma_read_bytes(rx_addr, &self.staging_buf, n); + + st_out.rx_armed = false; + st_out.rx_last_len = @intCast(n); + // NAK until re-armed + epr_set_stat_rx(ep, STAT_NAK); + + self.call_on_buffer(.Out, ep, controller); + } + + fn handle_in(self: *Self, ep: u4, controller: anytype) void { + const num: types.Endpoint.Num = @enumFromInt(ep); + const st_in = self.st(num, .In); + + if (!st_in.tx_busy) return; + + st_in.tx_busy = false; + // NAK until next write + epr_set_stat_tx(ep, STAT_NAK); + + self.call_on_buffer(.In, ep, controller); + + // After EP0 IN, re-arm EP0 OUT for next SETUP/status + if (ep == 0) { + btable_set_rx_count(0, 64); + epr_set_stat_rx(0, STAT_VALID); + } + } + + // --- VTable functions --- + + fn set_address(_: *usb.DeviceInterface, addr: u7) void { + log.debug("set_address to {}", .{addr}); + USB_PERIPH.DADDR.write(.{ .EF = 1, .ADD = addr }); + } + + fn ep_open(itf: *usb.DeviceInterface, desc_ptr: *const descriptor.Endpoint) void { + const self: *Self = @fieldParentPtr("interface", itf); + const desc = desc_ptr.*; + const e = desc.endpoint; + const ep_i: u4 = epn(e.num); + assert(ep_i < cfg.max_endpoints_count); + log.debug("ep_open ep{} dir={}", .{ ep_i, e.dir }); + + const mps: u16 = desc.max_packet_size.into(); + assert(mps > 0 and mps <= 64); + + const out_st = self.st(e.num, .Out); + const in_st = self.st(e.num, .In); + + // Allocate PMA buffers on first open + if (ep_i == 0) { + // EP0 shares a single buffer for TX and RX + if (out_st.pma_addr == 0 and out_st.max_size == 0) { + const buf_addr = self.pma_alloc(64); + out_st.pma_addr = buf_addr; + out_st.max_size = 64; + in_st.pma_addr = buf_addr; + in_st.max_size = 64; + + btable_set_tx_addr(0, buf_addr); + btable_set_tx_count(0, 0); + btable_set_rx_addr(0, buf_addr); + btable_set_rx_count(0, 64); + + epr_configure(0, EP_TYPE_CONTROL, STAT_NAK, STAT_VALID); + } + } else { + // Non-EP0: separate TX and RX buffers + if (e.dir == .Out and out_st.max_size == 0) { + const buf_addr = self.pma_alloc(mps); + out_st.pma_addr = buf_addr; + out_st.max_size = mps; + + btable_set_rx_addr(ep_i, buf_addr); + btable_set_rx_count(ep_i, mps); + } + if (e.dir == .In and in_st.max_size == 0) { + const buf_addr = self.pma_alloc(mps); + in_st.pma_addr = buf_addr; + in_st.max_size = mps; + + btable_set_tx_addr(ep_i, buf_addr); + btable_set_tx_count(ep_i, 0); + } + + // Determine EP type + const ep_type: u16 = switch (desc.attributes.transfer_type) { + .Control => EP_TYPE_CONTROL, + .Isochronous => EP_TYPE_ISO, + .Bulk => EP_TYPE_BULK, + .Interrupt => EP_TYPE_INTERRUPT, + }; + + // Configure EPR with the endpoint type + // We need to read current EPR to check if already configured + const cur = epr_read(ep_i); + if (cur & EPR_EA_MASK != @as(u16, ep_i)) { + // First time configuring this endpoint + epr_configure(ep_i, ep_type, STAT_NAK, STAT_NAK); + } + + // Set the appropriate direction to desired state + switch (e.dir) { + .Out => epr_set_stat_rx(ep_i, STAT_NAK), + .In => epr_set_stat_tx(ep_i, STAT_NAK), + } + } + } + + fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { + log.debug("ep_listen ep{} len={}", .{ ep_num, len }); + const self: *Self = @fieldParentPtr("interface", itf); + + if (ep_num == .ep0) { + const st0 = self.st(.ep0, .Out); + st0.rx_limit = @intCast(len); + return; + } + + const ep_i: u4 = epn(ep_num); + if (ep_i >= cfg.max_endpoints_count) + @panic("ep_listen called for invalid endpoint"); + + const st_out = self.st(ep_num, .Out); + if (st_out.max_size == 0) + @panic("ep_listen called for endpoint with no buffer allocated"); + if (st_out.rx_armed) + @panic("ep_listen called while OUT endpoint already armed"); + + const limit: u16 = @intCast(@min(@as(usize, st_out.max_size), @as(usize, @intCast(len)))); + st_out.rx_limit = limit; + st_out.rx_armed = true; + st_out.rx_last_len = 0; + + // Prepare BTABLE RX count and set RX to VALID + btable_set_rx_count(ep_i, limit); + epr_set_stat_rx(ep_i, STAT_VALID); + } + + fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { + const self: *Self = @fieldParentPtr("interface", itf); + const st_out = self.st(ep_num, .Out); + + const want: usize = @as(usize, st_out.rx_last_len); + defer st_out.rx_last_len = 0; + + // Data was already read from PMA into staging_buf during handle_out/handle_setup + var remaining: []const u8 = self.staging_buf[0..want]; + var copied: usize = 0; + + for (data) |dst| { + if (remaining.len == 0) break; + const n = @min(dst.len, remaining.len); + @memcpy(dst[0..n], remaining[0..n]); + remaining = remaining[n..]; + copied += n; + } + + return @intCast(copied); + } + + fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { + log.debug("ep_writev called for ep{} with {} chunks", .{ ep_num, vec.len }); + const self: *Self = @fieldParentPtr("interface", itf); + assert(vec.len > 0); + + const ep_i: u4 = epn(ep_num); + assert(ep_i < cfg.max_endpoints_count); + + const st_in = self.st(ep_num, .In); + if (st_in.max_size == 0) + @panic("ep_writev called for endpoint with no buffer allocated"); + + if (st_in.tx_busy) { + log.warn("ep_writev called while {} IN endpoint busy, returning 0", .{ep_num}); + return 0; + } + + // Gather vector data into staging buffer + var w: usize = 0; + for (vec) |chunk| { + if (w >= st_in.max_size) break; + const n = @min(chunk.len, @as(usize, st_in.max_size) - w); + @memcpy(self.staging_buf[w .. w + n], chunk[0..n]); + w += n; + } + + // Write to PMA + const tx_addr = pma_read16(btable_tx_addr_offset(ep_i)); + pma_write_bytes(tx_addr, self.staging_buf[0..w]); + btable_set_tx_count(ep_i, @intCast(w)); + + st_in.tx_busy = true; + epr_set_stat_tx(ep_i, STAT_VALID); + + return @intCast(w); + } + + // --- USB port control (matches WCH EVT USB_Port_Set) --- + + const gpio = @import("gpio.zig"); + const pa11 = gpio.Pin.init(0, 11); // PA11 = USB D- + const pa12 = gpio.Pin.init(0, 12); // PA12 = USB D+ + + fn usb_port_set(enable: bool) void { + if (enable) { + // Set PA11/PA12 to floating input so USB peripheral drives them + pa11.set_input_mode(.floating); + pa12.set_input_mode(.floating); + // Enable D+ internal 1.5K pull-up + EXTEND.EXTEND_CTR.modify(.{ .USBDPU = 1 }); + } else { + // Disable D+ pull-up + EXTEND.EXTEND_CTR.modify(.{ .USBDPU = 0 }); + // Drive PA11/PA12 low as push-pull outputs → SE0 = disconnect + pa11.set_output_mode(.general_purpose_push_pull, .max_2MHz); + pa12.set_output_mode(.general_purpose_push_pull, .max_2MHz); + pa11.put(0); + pa12.put(0); + } + } + + // --- HW init --- + + fn usbd_hw_init() void { + // 1. Power up: clear PDWN while keeping FRES asserted. + // Reset value of CNTR is 0x0003 (FRES | PDWN). + USB_PERIPH.CNTR.write(.{ .FRES = 1, .PDWN = 0 }); + + // 2. Wait for analog transceiver startup (tSTARTUP >= 1us) + var i: u32 = 0; + while (i < 1000) : (i += 1) { + asm volatile ("nop"); + } + + // 3. Clear FRES to release USB from reset. + // EPR registers are held at 0 while FRES=1, so all + // endpoint configuration must happen AFTER this point. + USB_PERIPH.CNTR.write(.{ .FRES = 0, .PDWN = 0 }); + + // 4. Clear all interrupt flags + USB_PERIPH.ISTR.write_raw(0); + + // 5. Set BTABLE = 0 (BTABLE at start of PMA) + USB_PERIPH.BTABLE.write_raw(0); + + // 6. Enable function at address 0 + USB_PERIPH.DADDR.write(.{ .EF = 1, .ADD = 0 }); + + // 7. Zero out the BTABLE area in PMA + { + var off: u16 = 0; + while (off < PMA_BTABLE_SIZE) : (off += 2) { + pma_write16(off, 0); + } + } + } + }; +} diff --git a/port/wch/ch32v/src/hals/usbfs.zig b/port/wch/ch32v/src/hals/usbfs.zig index b933f18c1..1a566657a 100644 --- a/port/wch/ch32v/src/hals/usbfs.zig +++ b/port/wch/ch32v/src/hals/usbfs.zig @@ -142,7 +142,7 @@ pub fn Polled(comptime cfg: Config) type { if (cfg.max_endpoints_count < 1) @compileError("USBFS max_endpoints_count must include endpoint 0"); if (cfg.prefer_high_speed) - @compileError("This peripheral only supports Full Speed, not High Speed"); + @compileError("USBFS only supports Full Speed, not High Speed"); if (cfg.max_endpoints_count > USB_MAX_ENDPOINTS_COUNT) @compileError("USBFS max_endpoints_count cannot exceed 8"); if (cfg.buffer_bytes < 128) @@ -169,6 +169,7 @@ pub fn Polled(comptime cfg: Config) type { interface: usb.DeviceInterface, pub fn init(self: *Self) void { + log.warn("USBFS init starting", .{}); self.interface = .{ .vtable = &vtable }; self.endpoints = @splat(@splat(.{})); @memset(self.pool[0..64], 0x7e); @@ -203,6 +204,12 @@ pub fn Polled(comptime cfg: Config) type { .RB_UC_INT_BUSY = 1, .MASK_UC_SYS_CTRL_RB_UC_DEV_PU_EN = 0b10, // DEV_PU_EN }); + + log.warn("USBFS init done: CTRL=0x{x:0>2} EP0_DMA=0x{x:0>8} INT_FG=0x{x:0>2}", .{ + Regs.R8_USB_CTRL.raw, + UEP_DMA[0].raw, + Regs.R8_USB_INT_FG.raw, + }); } // TODO: replace with fixedbuffer allocator? @@ -270,6 +277,8 @@ pub fn Polled(comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- + var poll_dbg_counter: u32 = 0; + pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { _ = in_isr; const flags = Regs.R8_USB_INT_FG.read(); @@ -396,7 +405,6 @@ pub fn Polled(comptime cfg: Config) type { fn ep_open(itf: *usb.DeviceInterface, desc_ptr: *const descriptor.Endpoint) void { const self: *Self = @fieldParentPtr("interface", itf); const desc = desc_ptr.*; - const e = desc.endpoint; const ep_i: u4 = epn(e.num); assert(ep_i < cfg.max_endpoints_count); @@ -551,6 +559,8 @@ pub fn Polled(comptime cfg: Config) type { // ---- HW init --------------------------------------------------------- fn usbfs_hw_init() void { + log.warn("USBFS hw_init starting", .{}); + // 1. SIE reset + FIFO clear Regs.R8_USB_CTRL.write(.{ .RB_UC_RST_SIE = 1, @@ -599,6 +609,13 @@ pub fn Polled(comptime cfg: Config) type { .RB_UH_PORT_EN__RB_UD_PORT_EN = 1, .RB_UH_PD_DIS__RB_UD_PD_DIS = 1, }); + + log.warn("USBFS hw_init done: CTRL=0x{x:0>2} INT_EN=0x{x:0>2} INT_FG=0x{x:0>2} UDEV=0x{x:0>2}", .{ + Regs.R8_USB_CTRL.raw, + Regs.R8_USB_INT_EN.raw, + Regs.R8_USB_INT_FG.raw, + Regs.UDEV_CTRL__UHOST_CTRL.raw, + }); } }; } diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index c23ce2322..6ffe63347 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -154,6 +154,7 @@ pub fn Polled(comptime cfg: Config) type { interface: usb.DeviceInterface, pub fn init(self: *Self) void { + log.warn("USBHS init starting", .{}); self.interface = .{ .vtable = &vtable }; self.endpoints = @splat(@splat(.{})); @memset(self.pool[0..64], 0x7e); @@ -257,8 +258,23 @@ pub fn Polled(comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- + var poll_dbg_counter: u32 = 0; + pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { _ = in_isr; + + // Periodic raw register dump to verify HW is alive + poll_dbg_counter +%= 1; + if (poll_dbg_counter % 500_000 == 0) { + log.warn("USBHS poll heartbeat: raw_FG=0x{x:0>2} CTRL=0x{x:0>2} MIS=0x{x:0>2} INT_ST=0x{x:0>2} SPD={}", .{ + Regs.USB_INT_FG.raw, + Regs.USB_CTRL.raw, + Regs.USB_MIS_ST.raw, + Regs.USB_INT_ST.raw, + Regs.USB_CTRL.read().RB_UC_SPEED_TYPE, + }); + } + while (true) { const flags = Regs.USB_INT_FG.read(); if (flags.RB_UIF_HST_SOF == 0 and flags.RB_UIF_SUSPEND == 0 and @@ -619,6 +635,8 @@ pub fn Polled(comptime cfg: Config) type { // ---- HW init --------------------------------------------------------- fn usbhs_hw_init() void { + log.warn("USBHS hw_init starting", .{}); + // Reset SIE and clear FIFO Regs.UHOST_CTRL.raw = 0; Regs.UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); @@ -653,6 +671,14 @@ pub fn Polled(comptime cfg: Config) type { .RB_UIE_FIFO_OV = 1, // .RB_UIE_HST_SOF = 1, }); + + log.warn("USBHS hw_init done: CTRL=0x{x:0>2} INT_EN=0x{x:0>2} INT_FG=0x{x:0>2} SPD={} UHOST=0x{x:0>8}", .{ + Regs.USB_CTRL.raw, + Regs.USB_INT_EN.raw, + Regs.USB_INT_FG.raw, + Regs.USB_CTRL.read().RB_UC_SPEED_TYPE, + Regs.UHOST_CTRL.raw, + }); } }; } From d8101d99d48292a2b168199a90de3bbb8f40cfa1 Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Fri, 24 Jul 2026 15:55:32 -0400 Subject: [PATCH 2/7] clean up some debug logs --- core/src/core/usb.zig | 15 +-------------- port/wch/ch32v/src/hals/usbfs.zig | 15 --------------- port/wch/ch32v/src/hals/usbhs.zig | 22 ---------------------- 3 files changed, 1 insertion(+), 51 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 740e6b43f..a21e9e554 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -566,7 +566,7 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type { const desc_type: descriptor.Type = @fromBackingInt(@intCast(value >> 8)); const desc_idx: u8 = @truncate(value); log.debug("Request for {any} descriptor {}", .{ desc_type, desc_idx }); - const result = switch (desc_type) { + return switch (desc_type) { .device => asBytes(&device_descriptor), .device_qualifier => asBytes(comptime &device_descriptor.qualifier()), .configuration => asBytes(&config_descriptor), @@ -581,19 +581,6 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type { }, else => nak, }; - // DEBUG: log raw descriptor bytes for configuration descriptor - if (result) |data| { - if (desc_type == .Configuration) { - log.warn("CONFIG DESC ({} bytes):", .{data.len}); - var off: usize = 0; - while (off < data.len) { - const end = @min(off + 16, data.len); - log.warn(" [{x:0>4}] {any}", .{ off, data[off..end] }); - off = end; - } - } - } - return result; } fn process_set_config(self: *@This(), device_itf: *DeviceInterface, cfg_num: u16) void { diff --git a/port/wch/ch32v/src/hals/usbfs.zig b/port/wch/ch32v/src/hals/usbfs.zig index 1a566657a..2ac3b7c59 100644 --- a/port/wch/ch32v/src/hals/usbfs.zig +++ b/port/wch/ch32v/src/hals/usbfs.zig @@ -204,12 +204,6 @@ pub fn Polled(comptime cfg: Config) type { .RB_UC_INT_BUSY = 1, .MASK_UC_SYS_CTRL_RB_UC_DEV_PU_EN = 0b10, // DEV_PU_EN }); - - log.warn("USBFS init done: CTRL=0x{x:0>2} EP0_DMA=0x{x:0>8} INT_FG=0x{x:0>2}", .{ - Regs.R8_USB_CTRL.raw, - UEP_DMA[0].raw, - Regs.R8_USB_INT_FG.raw, - }); } // TODO: replace with fixedbuffer allocator? @@ -277,8 +271,6 @@ pub fn Polled(comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- - var poll_dbg_counter: u32 = 0; - pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { _ = in_isr; const flags = Regs.R8_USB_INT_FG.read(); @@ -609,13 +601,6 @@ pub fn Polled(comptime cfg: Config) type { .RB_UH_PORT_EN__RB_UD_PORT_EN = 1, .RB_UH_PD_DIS__RB_UD_PD_DIS = 1, }); - - log.warn("USBFS hw_init done: CTRL=0x{x:0>2} INT_EN=0x{x:0>2} INT_FG=0x{x:0>2} UDEV=0x{x:0>2}", .{ - Regs.R8_USB_CTRL.raw, - Regs.R8_USB_INT_EN.raw, - Regs.R8_USB_INT_FG.raw, - Regs.UDEV_CTRL__UHOST_CTRL.raw, - }); } }; } diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 6ffe63347..facc056ca 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -258,23 +258,9 @@ pub fn Polled(comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- - var poll_dbg_counter: u32 = 0; - pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { _ = in_isr; - // Periodic raw register dump to verify HW is alive - poll_dbg_counter +%= 1; - if (poll_dbg_counter % 500_000 == 0) { - log.warn("USBHS poll heartbeat: raw_FG=0x{x:0>2} CTRL=0x{x:0>2} MIS=0x{x:0>2} INT_ST=0x{x:0>2} SPD={}", .{ - Regs.USB_INT_FG.raw, - Regs.USB_CTRL.raw, - Regs.USB_MIS_ST.raw, - Regs.USB_INT_ST.raw, - Regs.USB_CTRL.read().RB_UC_SPEED_TYPE, - }); - } - while (true) { const flags = Regs.USB_INT_FG.read(); if (flags.RB_UIF_HST_SOF == 0 and flags.RB_UIF_SUSPEND == 0 and @@ -671,14 +657,6 @@ pub fn Polled(comptime cfg: Config) type { .RB_UIE_FIFO_OV = 1, // .RB_UIE_HST_SOF = 1, }); - - log.warn("USBHS hw_init done: CTRL=0x{x:0>2} INT_EN=0x{x:0>2} INT_FG=0x{x:0>2} SPD={} UHOST=0x{x:0>8}", .{ - Regs.USB_CTRL.raw, - Regs.USB_INT_EN.raw, - Regs.USB_INT_FG.raw, - Regs.USB_CTRL.read().RB_UC_SPEED_TYPE, - Regs.UHOST_CTRL.raw, - }); } }; } From 49c7b0df9cb072c6b59d1addb20595de74ab1b1c Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Sat, 25 Jul 2026 09:54:45 -0400 Subject: [PATCH 3/7] todos --- port/wch/ch32v/src/hals/usbd.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/port/wch/ch32v/src/hals/usbd.zig b/port/wch/ch32v/src/hals/usbd.zig index a614d396f..0b99ee6c2 100644 --- a/port/wch/ch32v/src/hals/usbd.zig +++ b/port/wch/ch32v/src/hals/usbd.zig @@ -78,12 +78,14 @@ const EPR_CTR_RX: u16 = 0x8000; // [15] Correct Transfer RX (W0C) const EPR_RW_MASK: u16 = EPR_EA_MASK | EPR_EP_KIND | EPR_EP_TYPE_MASK; // EP_TYPE values (written into EPR bits [10:9]) +// TODO: Enum const EP_TYPE_BULK: u16 = 0x0000; const EP_TYPE_CONTROL: u16 = 0x0200; const EP_TYPE_ISO: u16 = 0x0400; const EP_TYPE_INTERRUPT: u16 = 0x0600; // STAT_TX / STAT_RX values (2-bit field) +// TODO: Enum const STAT_DISABLED: u2 = 0b00; const STAT_STALL: u2 = 0b01; const STAT_NAK: u2 = 0b10; From 95807f279020672b9cb451617e3460a896c9904f Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Sat, 25 Jul 2026 09:57:46 -0400 Subject: [PATCH 4/7] generic for usb --- examples/wch/ch32v/src/usb_cdc.zig | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 192c31aa5..567a75fb2 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -4,15 +4,10 @@ const microzig = @import("microzig"); const hal = microzig.hal; const board = microzig.board; const time = hal.time; -const gpio = hal.gpio; const usb = microzig.core.usb; const USB_Serial = usb.drivers.CDC; -const RCC = microzig.chip.peripherals.RCC; - -const usart = hal.usart.instance.USART1; - -const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 +const uart_cfg: hal.usart.UartConfig = if (@hasDecl(board, "uart_config")) board.uart_config else .{}; pub const std_options = microzig.std_options(.{ .logFn = hal.usart.log, @@ -53,20 +48,9 @@ pub fn main() !void { // Board brings up clocks and time microzig.board.init(); microzig.hal.init(); - // Enable peripheral clocks for USART1 and GPIOA - RCC.APB2PCENR.modify(.{ - .IOPAEN = 1, // Enable GPIOA clock - .IOPCEN = 1, - .AFIOEN = 1, // Enable AFIO clock - .USART1EN = 1, // Enable USART1 clock - }); - // Configure TX pin as alternate function push-pull - usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); - - // Initialize USART1 at 115200 baud - usart.apply(.{ .baud_rate = 115200 }); - - hal.usart.init_logger(usart); + + hal.usart.setup_uart(uart_cfg); + hal.usart.init_logger(uart_cfg.instance); std.log.info("UART logging initialized.", .{}); std.log.info("Initializing USB device.", .{}); From 5c55300edf9a3c1d8fb0355590da17b223458d25 Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Sat, 12 Sep 2026 09:12:51 -0400 Subject: [PATCH 5/7] Define types and methods --- port/wch/ch32v/src/hals/usbd.zig | 541 +++++++++++++++---------------- 1 file changed, 257 insertions(+), 284 deletions(-) diff --git a/port/wch/ch32v/src/hals/usbd.zig b/port/wch/ch32v/src/hals/usbd.zig index 0b99ee6c2..fab0c235b 100644 --- a/port/wch/ch32v/src/hals/usbd.zig +++ b/port/wch/ch32v/src/hals/usbd.zig @@ -9,6 +9,9 @@ //! (which uses DMA buffers in SRAM). //! NOTE: Some variants of the ch32v203 have USBD, some have USBFS, and some //! have both. +//! +//! Prerequisite: `hal.time.init()` must be called before USB init (typically +//! done in `board.init()`) since we need SysTick for delays. const std = @import("std"); const assert = std.debug.assert; @@ -19,6 +22,7 @@ const peripherals = microzig.chip.peripherals; const usb = microzig.core.usb; const types = usb.types; const descriptor = usb.descriptor; +const time = @import("time.zig"); pub const max_packet_size: u11 = 64; @@ -42,14 +46,6 @@ const USBD_BASE: usize = @intFromPtr(USB_PERIPH); /// EXTEND peripheral (for D+ pull-up control via USBDPU) const EXTEND = peripherals.EXTEND; -/// Packet Memory Area base (CPU address space) -const PMA_BASE: usize = 0x40006000; -/// PMA size in bytes (peripheral address space) -const PMA_SIZE: usize = 512; -/// PMA access multiplier: each u16 PMA word occupies a u32 slot in CPU -/// address space (2x mapping), same as STM32F103. -const PMA_ACCESS_MULT: usize = 2; - /// ISTR packed struct type (field names vary between chips: RST vs RESET) const Istr = @TypeOf(USB_PERIPH.ISTR).underlying_type; @@ -62,237 +58,226 @@ const ISTR_SUSP: u16 = 1 << 11; const ISTR_ERR: u16 = 1 << 13; const ISTR_PMAOVR: u16 = 1 << 14; -// -- EPR bit positions / masks (raw access needed for toggle-on-write) -- -const EPR_EA_MASK: u16 = 0x000F; // [3:0] Endpoint Address -const EPR_STAT_TX_MASK: u16 = 0x0030; // [5:4] TX Status (toggle) -const EPR_DTOG_TX: u16 = 0x0040; // [6] TX Data Toggle (toggle) -const EPR_CTR_TX: u16 = 0x0080; // [7] Correct Transfer TX (W0C) -const EPR_EP_KIND: u16 = 0x0100; // [8] Endpoint Kind -const EPR_EP_TYPE_MASK: u16 = 0x0600; // [10:9] Endpoint Type -const EPR_SETUP: u16 = 0x0800; // [11] Setup transaction completed (RO) -const EPR_STAT_RX_MASK: u16 = 0x3000; // [13:12] RX Status (toggle) -const EPR_DTOG_RX: u16 = 0x4000; // [14] RX Data Toggle (toggle) -const EPR_CTR_RX: u16 = 0x8000; // [15] Correct Transfer RX (W0C) - -// Bits that are read/write (non-toggle, non-W0C): EA, EP_KIND, EP_TYPE -const EPR_RW_MASK: u16 = EPR_EA_MASK | EPR_EP_KIND | EPR_EP_TYPE_MASK; - -// EP_TYPE values (written into EPR bits [10:9]) -// TODO: Enum -const EP_TYPE_BULK: u16 = 0x0000; -const EP_TYPE_CONTROL: u16 = 0x0200; -const EP_TYPE_ISO: u16 = 0x0400; -const EP_TYPE_INTERRUPT: u16 = 0x0600; - -// STAT_TX / STAT_RX values (2-bit field) -// TODO: Enum -const STAT_DISABLED: u2 = 0b00; -const STAT_STALL: u2 = 0b01; -const STAT_NAK: u2 = 0b10; -const STAT_VALID: u2 = 0b11; +const EpType = enum(u2) { bulk = 0, control = 1, iso = 2, interrupt = 3 }; +const Stat = enum(u2) { disabled = 0b00, stall = 0b01, nak = 0b10, valid = 0b11 }; /// ISTR helper (field names differ between chips: RST vs RESET) fn istr_is_reset(istr: Istr) bool { return if (@hasField(Istr, "RST")) istr.RST == 1 else istr.RESET == 1; } -// --- Raw EPR reg access (bypass MMIO due to toggle-on-write semantics) --- +// --- EPR register access and manipulation --- -/// EPR registers: 0x00..0x1C (8 x u16 at u32 spacing). +/// EPR (Endpoint Register) namespace: raw register access and toggle-on-write helpers. +/// /// These CANNOT use MMIO modify() because STAT_TX/RX, DTOG_TX/RX are /// toggle-on-write, and CTR_TX/RX are write-0-to-clear. -fn epr_ptr(ep: u4) *volatile u16 { - return @ptrFromInt(USBD_BASE + @as(usize, ep) * 4); -} - -fn epr_read(ep: u4) u16 { - return epr_ptr(ep).*; -} +const Epr = struct { + // Bit positions / masks + const ea_mask: u16 = 0x000F; // [3:0] Endpoint Address + const stat_tx_mask: u16 = 0x0030; // [5:4] TX Status (toggle) + const dtog_tx: u16 = 0x0040; // [6] TX Data Toggle (toggle) + const ctr_tx: u16 = 0x0080; // [7] Correct Transfer TX (W0C) + const ep_kind: u16 = 0x0100; // [8] Endpoint Kind + const ep_type_mask: u16 = 0x0600; // [10:9] Endpoint Type + const setup: u16 = 0x0800; // [11] Setup transaction completed (RO) + const stat_rx_mask: u16 = 0x3000; // [13:12] RX Status (toggle) + const dtog_rx: u16 = 0x4000; // [14] RX Data Toggle (toggle) + const ctr_rx: u16 = 0x8000; // [15] Correct Transfer RX (W0C) + + // Bits that are read/write (non-toggle, non-W0C): EA, EP_KIND, EP_TYPE + const rw_mask: u16 = ea_mask | ep_kind | ep_type_mask; + + fn ptr(ep: u4) *volatile u16 { + return @ptrFromInt(USBD_BASE + @as(usize, ep) * 4); + } -fn epr_write(ep: u4, val: u16) void { - epr_ptr(ep).* = val; -} + fn read(ep: u4) u16 { + return ptr(ep).*; + } -// --- EPR manipulation helpers (XOR trick for toggle bits) --- + fn write(ep: u4, val: u16) void { + ptr(ep).* = val; + } -/// Set STAT_TX to desired value using XOR trick. -fn epr_set_stat_tx(ep: u4, stat: u2) void { - const val = epr_read(ep); - const current: u2 = @truncate((val & EPR_STAT_TX_MASK) >> 4); - const xor_val: u16 = @as(u16, current ^ stat) << 4; - // Preserve RW bits, set W0C bits to 1 (don't clear), zero other toggle bits - const write_val = (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | xor_val; - epr_write(ep, write_val); -} + /// Set STAT_TX to desired value using XOR trick. + fn set_stat_tx(ep: u4, stat: Stat) void { + const val = read(ep); + const current: u2 = @truncate((val & stat_tx_mask) >> 4); + const xor_val: u16 = @as(u16, current ^ @backingInt(stat)) << 4; + write(ep, (val & rw_mask) | ctr_tx | ctr_rx | xor_val); + } -/// Set STAT_RX to desired value using XOR trick. -fn epr_set_stat_rx(ep: u4, stat: u2) void { - const val = epr_read(ep); - const current: u2 = @truncate((val & EPR_STAT_RX_MASK) >> 12); - const xor_val: u16 = @as(u16, current ^ stat) << 12; - const write_val = (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | xor_val; - epr_write(ep, write_val); -} + /// Set STAT_RX to desired value using XOR trick. + fn set_stat_rx(ep: u4, stat: Stat) void { + const val = read(ep); + const current: u2 = @truncate((val & stat_rx_mask) >> 12); + const xor_val: u16 = @as(u16, current ^ @backingInt(stat)) << 12; + write(ep, (val & rw_mask) | ctr_tx | ctr_rx | xor_val); + } -/// Set both STAT_TX and STAT_RX simultaneously. -fn epr_set_stat_txrx(ep: u4, stat_tx: u2, stat_rx: u2) void { - const val = epr_read(ep); - const cur_tx: u2 = @truncate((val & EPR_STAT_TX_MASK) >> 4); - const cur_rx: u2 = @truncate((val & EPR_STAT_RX_MASK) >> 12); - const xor_tx: u16 = @as(u16, cur_tx ^ stat_tx) << 4; - const xor_rx: u16 = @as(u16, cur_rx ^ stat_rx) << 12; - const write_val = (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | xor_tx | xor_rx; - epr_write(ep, write_val); -} + /// Set both STAT_TX and STAT_RX simultaneously. + fn set_stat_txrx(ep: u4, s_tx: Stat, s_rx: Stat) void { + const val = read(ep); + const cur_tx: u2 = @truncate((val & stat_tx_mask) >> 4); + const cur_rx: u2 = @truncate((val & stat_rx_mask) >> 12); + const xor_tx: u16 = @as(u16, cur_tx ^ @backingInt(s_tx)) << 4; + const xor_rx: u16 = @as(u16, cur_rx ^ @backingInt(s_rx)) << 12; + write(ep, (val & rw_mask) | ctr_tx | ctr_rx | xor_tx | xor_rx); + } -/// Clear CTR_TX (write 0 to the W0C bit, keep CTR_RX as 1). -fn epr_clear_ctr_tx(ep: u4) void { - const val = epr_read(ep); - epr_write(ep, (val & EPR_RW_MASK) | EPR_CTR_RX); -} + /// Clear CTR_TX (write 0 to the W0C bit, keep CTR_RX as 1). + fn clear_ctr_tx(ep: u4) void { + const val = read(ep); + write(ep, (val & rw_mask) | ctr_rx); + } -/// Clear CTR_RX (write 0 to the W0C bit, keep CTR_TX as 1). -fn epr_clear_ctr_rx(ep: u4) void { - const val = epr_read(ep); - epr_write(ep, (val & EPR_RW_MASK) | EPR_CTR_TX); -} + /// Clear CTR_RX (write 0 to the W0C bit, keep CTR_TX as 1). + fn clear_ctr_rx(ep: u4) void { + const val = read(ep); + write(ep, (val & rw_mask) | ctr_tx); + } -/// Clear DTOG_TX by toggling it if currently set. -fn epr_clear_dtog_tx(ep: u4) void { - const val = epr_read(ep); - if (val & EPR_DTOG_TX != 0) { - epr_write(ep, (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | EPR_DTOG_TX); + /// Clear DTOG_TX by toggling it if currently set. + fn clear_dtog_tx(ep: u4) void { + const val = read(ep); + if (val & dtog_tx != 0) { + write(ep, (val & rw_mask) | ctr_tx | ctr_rx | dtog_tx); + } } -} -/// Clear DTOG_RX by toggling it if currently set. -fn epr_clear_dtog_rx(ep: u4) void { - const val = epr_read(ep); - if (val & EPR_DTOG_RX != 0) { - epr_write(ep, (val & EPR_RW_MASK) | EPR_CTR_TX | EPR_CTR_RX | EPR_DTOG_RX); + /// Clear DTOG_RX by toggling it if currently set. + fn clear_dtog_rx(ep: u4) void { + const val = read(ep); + if (val & dtog_rx != 0) { + write(ep, (val & rw_mask) | ctr_tx | ctr_rx | dtog_rx); + } } -} -/// Configure an endpoint: set EA, EP_TYPE, clear toggles, set initial status. -fn epr_configure(ep: u4, ep_type: u16, stat_tx: u2, stat_rx: u2) void { - // First write: set EA, EP_TYPE, clear everything else - const base: u16 = (@as(u16, ep) & EPR_EA_MASK) | (ep_type & EPR_EP_TYPE_MASK) | EPR_CTR_TX | EPR_CTR_RX; - epr_write(ep, base); + /// Configure an endpoint: set EA, EP_TYPE, clear toggles, set initial status. + fn configure(ep: u4, ep_type: EpType, s_tx: Stat, s_rx: Stat) void { + // First write: set EA, EP_TYPE, clear everything else + const base: u16 = (@as(u16, ep) & ea_mask) | (@as(u16, @backingInt(ep_type)) << 9) | ctr_tx | ctr_rx; + write(ep, base); - // Now set desired status bits using XOR trick - epr_set_stat_txrx(ep, stat_tx, stat_rx); + // Now set desired status bits using XOR trick + set_stat_txrx(ep, s_tx, s_rx); - // Clear data toggles - epr_clear_dtog_tx(ep); - epr_clear_dtog_rx(ep); -} + // Clear data toggles + clear_dtog_tx(ep); + clear_dtog_rx(ep); + } +}; // --- PMA (Packet Memory Area) access helpers --- -// The BTABLE descriptor for each endpoint occupies 8 bytes in PMA space -// (4 x u16 words). With 2x mapping, each u16 occupies a u32 slot in CPU -// address space (only lower 16 bits valid). -// -// Layout per endpoint (PMA offsets): -// +0: TX_ADDR (u16) -// +2: TX_COUNT (u16) -// +4: RX_ADDR (u16) -// +6: RX_COUNT (u16) - -/// Read a u16 from PMA at the given PMA byte offset. -fn pma_read16(pma_offset: u16) u16 { - const addr: usize = PMA_BASE + @as(usize, pma_offset) * PMA_ACCESS_MULT; - const ptr: *volatile u32 = @ptrFromInt(addr); - return @truncate(ptr.*); -} - -/// Write a u16 to PMA at the given PMA byte offset. -fn pma_write16(pma_offset: u16, val: u16) void { - const addr: usize = PMA_BASE + @as(usize, pma_offset) * PMA_ACCESS_MULT; - const ptr: *volatile u32 = @ptrFromInt(addr); - ptr.* = val; -} +/// PMA (Packet Memory Area) namespace: read/write helpers for the shared +/// buffer memory. Each u16 PMA word occupies a u32 slot in CPU address +/// space (2x mapping), same as STM32F103. +const Pma = struct { + const base: usize = 0x40006000; + const size: usize = 512; + const access_mult: usize = 2; + + /// Read a u16 from PMA at the given PMA byte offset. + fn read16(pma_offset: u16) u16 { + const addr: usize = base + @as(usize, pma_offset) * access_mult; + const p: *volatile u32 = @ptrFromInt(addr); + return @truncate(p.*); + } -/// BTABLE descriptor field offsets (in PMA u16 words = 2 bytes each) -fn btable_tx_addr_offset(ep: u4) u16 { - return @as(u16, ep) * 8 + 0; -} -fn btable_tx_count_offset(ep: u4) u16 { - return @as(u16, ep) * 8 + 2; -} -fn btable_rx_addr_offset(ep: u4) u16 { - return @as(u16, ep) * 8 + 4; -} -fn btable_rx_count_offset(ep: u4) u16 { - return @as(u16, ep) * 8 + 6; -} + /// Write a u16 to PMA at the given PMA byte offset. + fn write16(pma_offset: u16, val: u16) void { + const addr: usize = base + @as(usize, pma_offset) * access_mult; + const p: *volatile u32 = @ptrFromInt(addr); + p.* = val; + } -fn btable_set_tx_addr(ep: u4, pma_addr: u16) void { - pma_write16(btable_tx_addr_offset(ep), pma_addr); -} -fn btable_set_tx_count(ep: u4, count: u16) void { - pma_write16(btable_tx_count_offset(ep), count); -} -fn btable_get_tx_count(ep: u4) u16 { - return pma_read16(btable_tx_count_offset(ep)) & 0x03FF; -} -fn btable_set_rx_addr(ep: u4, pma_addr: u16) void { - pma_write16(btable_rx_addr_offset(ep), pma_addr); -} + /// Copy bytes from CPU memory into PMA. + fn write_bytes(pma_offset: u16, data: []const u8) void { + var off = pma_offset; + var i: usize = 0; + while (i < data.len) { + const lo: u16 = data[i]; + const hi: u16 = if (i + 1 < data.len) data[i + 1] else 0; + write16(off, lo | (hi << 8)); + off += 2; + i += 2; + } + } -/// Set RX_COUNT with block size encoding for max receivable bytes. -/// For sizes <= 62: BL_SIZE=0, NUM_BLOCK = size/2 -/// For sizes > 62: BL_SIZE=1, NUM_BLOCK = size/32 - 1 -fn btable_set_rx_count(ep: u4, max_size: u16) void { - var val: u16 = 0; - if (max_size <= 62) { - // BL_SIZE=0, NUM_BLOCK = max_size/2 - val = (max_size / 2) << 10; - } else { - // BL_SIZE=1, NUM_BLOCK = max_size/32 - 1 - val = (1 << 15) | (((max_size / 32) - 1) << 10); + /// Copy bytes from PMA into CPU memory. + fn read_bytes(pma_offset: u16, buf: []u8, count: usize) void { + var off = pma_offset; + var i: usize = 0; + while (i < count) { + const word = read16(off); + buf[i] = @truncate(word); + if (i + 1 < count) { + buf[i + 1] = @truncate(word >> 8); + } + off += 2; + i += 2; + } } - pma_write16(btable_rx_count_offset(ep), val); -} +}; -fn btable_get_rx_count(ep: u4) u16 { - return pma_read16(btable_rx_count_offset(ep)) & 0x03FF; -} +// --- BTABLE (Buffer Descriptor Table) --- + +/// BTABLE namespace: per-endpoint buffer descriptor access within PMA. +/// +/// Layout per endpoint (PMA offsets): +/// +0: TX_ADDR (u16) +/// +2: TX_COUNT (u16) +/// +4: RX_ADDR (u16) +/// +6: RX_COUNT (u16) +const Btable = struct { + /// Total BTABLE size: 8 endpoints * 8 bytes = 64 bytes + const size: u16 = 64; + + fn tx_addr_offset(ep: u4) u16 { + return @as(u16, ep) * 8 + 0; + } + fn tx_count_offset(ep: u4) u16 { + return @as(u16, ep) * 8 + 2; + } + fn rx_addr_offset(ep: u4) u16 { + return @as(u16, ep) * 8 + 4; + } + fn rx_count_offset(ep: u4) u16 { + return @as(u16, ep) * 8 + 6; + } -/// Copy bytes from CPU memory into PMA. -fn pma_write_bytes(pma_offset: u16, data: []const u8) void { - var off = pma_offset; - var i: usize = 0; - while (i < data.len) { - const lo: u16 = data[i]; - const hi: u16 = if (i + 1 < data.len) data[i + 1] else 0; - pma_write16(off, lo | (hi << 8)); - off += 2; - i += 2; + fn set_tx_addr(ep: u4, pma_addr: u16) void { + Pma.write16(tx_addr_offset(ep), pma_addr); + } + fn set_tx_count(ep: u4, count: u16) void { + Pma.write16(tx_count_offset(ep), count); + } + fn get_tx_count(ep: u4) u16 { + return Pma.read16(tx_count_offset(ep)) & 0x03FF; + } + fn set_rx_addr(ep: u4, pma_addr: u16) void { + Pma.write16(rx_addr_offset(ep), pma_addr); } -} -/// Copy bytes from PMA into CPU memory. -fn pma_read_bytes(pma_offset: u16, buf: []u8, count: usize) void { - var off = pma_offset; - var i: usize = 0; - while (i < count) { - const word = pma_read16(off); - buf[i] = @truncate(word); - if (i + 1 < count) { - buf[i + 1] = @truncate(word >> 8); + /// Set RX_COUNT with block size encoding for max receivable bytes. + /// For sizes <= 62: BL_SIZE=0, NUM_BLOCK = size/2 + /// For sizes > 62: BL_SIZE=1, NUM_BLOCK = size/32 - 1 + fn set_rx_count(ep: u4, max_size: u16) void { + var val: u16 = 0; + if (max_size <= 62) { + val = (max_size / 2) << 10; + } else { + val = (1 << 15) | (((max_size / 32) - 1) << 10); } - off += 2; - i += 2; + Pma.write16(rx_count_offset(ep), val); } -} -// --- PMA buffer allocation (static layout, bump pointer) --- - -/// PMA memory layout: -/// 0x00 - 0x3F: BTABLE (8 eps x 8 bytes = 64 bytes) -/// 0x40+: endpoint buffers -const PMA_BTABLE_SIZE: u16 = 64; // 8 endpoints * 8 bytes + fn get_rx_count(ep: u4) u16 { + return Pma.read16(rx_count_offset(ep)) & 0x03FF; + } +}; // --- Endpoint state tracking (mirrors usbfs.zig pattern) --- @@ -312,7 +297,7 @@ fn PerEndpointArray(comptime N: comptime_int) type { } fn epn(ep: types.Endpoint.Num) u4 { - return @as(u4, @intCast(@intFromEnum(ep))); + return @backingInt(ep); } /// Polled USBFS device backend for the MicroZig core USB controller. @@ -349,7 +334,7 @@ pub fn Polled(comptime cfg: Config) type { log.warn("USBD init starting", .{}); self.interface = .{ .vtable = &vtable }; self.endpoints = @splat(@splat(.{})); - self.pma_next = PMA_BTABLE_SIZE; + self.pma_next = Btable.size; usbd_hw_init(); @@ -368,7 +353,7 @@ pub fn Polled(comptime cfg: Config) type { }); // EP0 OUT always accepts packets - epr_set_stat_rx(0, STAT_VALID); + Epr.set_stat_rx(0, .valid); // Enable interrupt masks: bus reset and correct transfer USB_PERIPH.CNTR.write(.{ .FRES = 0, .PDWN = 0, .RESETM = 1, .CTRM = 1 }); @@ -377,45 +362,39 @@ pub fn Polled(comptime cfg: Config) type { // Force a clean disconnect/connect cycle so the host detects // a fresh device attach (matches WCH EVT USB_Port_Set pattern). usb_port_set(false); // pull-up off, drive D+/D- low (SE0) - { - // ~20ms delay at 48MHz - var d: u32 = 0; - while (d < 960_000) : (d += 1) { - asm volatile ("nop"); - } - } + time.delay_us(20_000); // ~20ms disconnect usb_port_set(true); // pins to floating input, pull-up on } - fn pma_alloc(self: *Self, size: u16) u16 { + fn pma_alloc(self: *Self, alloc_size: u16) u16 { const addr = self.pma_next; - assert(addr + size <= PMA_SIZE); - self.pma_next += size; + assert(addr + alloc_size <= Pma.size); + self.pma_next += alloc_size; return addr; } fn st(self: *Self, ep_num: types.Endpoint.Num, dir: types.Dir) *EP_State { - return &self.endpoints[@intFromEnum(ep_num)][@intFromEnum(dir)]; + return &self.endpoints[@backingInt(ep_num)][@backingInt(dir)]; } fn on_bus_reset_local(self: *Self) void { // Clear state inline for (0..cfg.max_endpoints_count) |i| { - self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_armed = false; - self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_last_len = 0; - self.endpoints[i][@intFromEnum(types.Dir.In)].tx_busy = false; + self.endpoints[i][@backingInt(types.Dir.Out)].rx_armed = false; + self.endpoints[i][@backingInt(types.Dir.Out)].rx_last_len = 0; + self.endpoints[i][@backingInt(types.Dir.In)].tx_busy = false; } // Re-initialize BTABLE register and EP0 buffer descriptors USB_PERIPH.BTABLE.write_raw(0); const ep0_buf = self.st(.ep0, .Out).pma_addr; - btable_set_tx_addr(0, ep0_buf); - btable_set_tx_count(0, 0); - btable_set_rx_addr(0, ep0_buf); - btable_set_rx_count(0, 64); + Btable.set_tx_addr(0, ep0_buf); + Btable.set_tx_count(0, 0); + Btable.set_rx_addr(0, ep0_buf); + Btable.set_rx_count(0, 64); // Fully re-configure EP0: EA=0, CONTROL type, clear DTOGs - epr_configure(0, EP_TYPE_CONTROL, STAT_NAK, STAT_VALID); + Epr.configure(0, .control, .nak, .valid); // Non-EP0 endpoint registers are already at 0x0000 (DISABLED) // after hardware bus reset — leave them alone. Setting them to @@ -431,13 +410,13 @@ pub fn Polled(comptime cfg: Config) type { switch (dir) { .In => switch (ep) { inline 0...15 => |i| { - const num: types.Endpoint.Num = @enumFromInt(i); + const num: types.Endpoint.Num = @fromBackingInt(@intCast(i)); controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }); }, }, .Out => switch (ep) { inline 0...15 => |i| { - const num: types.Endpoint.Num = @enumFromInt(i); + const num: types.Endpoint.Num = @fromBackingInt(@intCast(i)); controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }); }, }, @@ -488,12 +467,12 @@ pub fn Polled(comptime cfg: Config) type { if (ep >= cfg.max_endpoints_count) return; - const val = epr_read(ep); + const val = Epr.read(ep); - if (val & EPR_CTR_RX != 0) { + if (val & Epr.ctr_rx != 0) { // SETUP or OUT - const is_setup = (val & EPR_SETUP) != 0; - epr_clear_ctr_rx(ep); + const is_setup = (val & Epr.setup) != 0; + Epr.clear_ctr_rx(ep); if (is_setup) { self.handle_setup(ep, controller); @@ -502,80 +481,80 @@ pub fn Polled(comptime cfg: Config) type { } } - if (val & EPR_CTR_TX != 0) { - epr_clear_ctr_tx(ep); + if (val & Epr.ctr_tx != 0) { + Epr.clear_ctr_tx(ep); self.handle_in(ep, controller); } } fn handle_setup(self: *Self, ep: u4, controller: anytype) void { // Read 8-byte SETUP packet from PMA - const rx_addr = pma_read16(btable_rx_addr_offset(ep)); - pma_read_bytes(rx_addr, &self.staging_buf, 8); - const setup: types.SetupPacket = @bitCast(self.staging_buf[0..8].*); + const rx_addr = Pma.read16(Btable.rx_addr_offset(ep)); + Pma.read_bytes(rx_addr, &self.staging_buf, 8); + const setup_pkt: types.SetupPacket = @bitCast(self.staging_buf[0..8].*); // After SETUP, hardware forces STAT_TX=NAK, STAT_RX=NAK and // clears DTOG_TX/DTOG_RX. We set RX to VALID so EP0 can // receive the status stage or data stage. - epr_set_stat_rx(ep, STAT_VALID); + Epr.set_stat_rx(ep, .valid); const st_in = self.st(.ep0, .In); st_in.tx_busy = false; - controller.on_setup_req(&self.interface, &setup); + controller.on_setup_req(&self.interface, &setup_pkt); } fn handle_out(self: *Self, ep: u4, controller: anytype) void { - const len = btable_get_rx_count(ep); + const len = Btable.get_rx_count(ep); if (ep == 0) { const st_out = self.st(.ep0, .Out); // Read data from PMA into staging buffer - const rx_addr = pma_read16(btable_rx_addr_offset(ep)); + const rx_addr = Pma.read16(Btable.rx_addr_offset(ep)); const n: usize = @min(@as(usize, len), 64); - pma_read_bytes(rx_addr, &self.staging_buf, n); + Pma.read_bytes(rx_addr, &self.staging_buf, n); st_out.rx_last_len = @intCast(n); // Re-arm EP0 RX - btable_set_rx_count(0, 64); - epr_set_stat_rx(0, STAT_VALID); + Btable.set_rx_count(0, 64); + Epr.set_stat_rx(0, .valid); self.call_on_buffer(.Out, 0, controller); return; } - const num: types.Endpoint.Num = @enumFromInt(ep); + const num: types.Endpoint.Num = @fromBackingInt(@intCast(ep)); const st_out = self.st(num, .Out); if (!st_out.rx_armed) return; // Read data from PMA into staging buffer - const rx_addr = pma_read16(btable_rx_addr_offset(ep)); + const rx_addr = Pma.read16(Btable.rx_addr_offset(ep)); const n: usize = @min(@as(usize, len), @as(usize, st_out.max_size)); - pma_read_bytes(rx_addr, &self.staging_buf, n); + Pma.read_bytes(rx_addr, &self.staging_buf, n); st_out.rx_armed = false; st_out.rx_last_len = @intCast(n); // NAK until re-armed - epr_set_stat_rx(ep, STAT_NAK); + Epr.set_stat_rx(ep, .nak); self.call_on_buffer(.Out, ep, controller); } fn handle_in(self: *Self, ep: u4, controller: anytype) void { - const num: types.Endpoint.Num = @enumFromInt(ep); + const num: types.Endpoint.Num = @fromBackingInt(@intCast(ep)); const st_in = self.st(num, .In); if (!st_in.tx_busy) return; st_in.tx_busy = false; // NAK until next write - epr_set_stat_tx(ep, STAT_NAK); + Epr.set_stat_tx(ep, .nak); self.call_on_buffer(.In, ep, controller); // After EP0 IN, re-arm EP0 OUT for next SETUP/status if (ep == 0) { - btable_set_rx_count(0, 64); - epr_set_stat_rx(0, STAT_VALID); + Btable.set_rx_count(0, 64); + Epr.set_stat_rx(0, .valid); } } @@ -610,12 +589,12 @@ pub fn Polled(comptime cfg: Config) type { in_st.pma_addr = buf_addr; in_st.max_size = 64; - btable_set_tx_addr(0, buf_addr); - btable_set_tx_count(0, 0); - btable_set_rx_addr(0, buf_addr); - btable_set_rx_count(0, 64); + Btable.set_tx_addr(0, buf_addr); + Btable.set_tx_count(0, 0); + Btable.set_rx_addr(0, buf_addr); + Btable.set_rx_count(0, 64); - epr_configure(0, EP_TYPE_CONTROL, STAT_NAK, STAT_VALID); + Epr.configure(0, .control, .nak, .valid); } } else { // Non-EP0: separate TX and RX buffers @@ -624,38 +603,38 @@ pub fn Polled(comptime cfg: Config) type { out_st.pma_addr = buf_addr; out_st.max_size = mps; - btable_set_rx_addr(ep_i, buf_addr); - btable_set_rx_count(ep_i, mps); + Btable.set_rx_addr(ep_i, buf_addr); + Btable.set_rx_count(ep_i, mps); } if (e.dir == .In and in_st.max_size == 0) { const buf_addr = self.pma_alloc(mps); in_st.pma_addr = buf_addr; in_st.max_size = mps; - btable_set_tx_addr(ep_i, buf_addr); - btable_set_tx_count(ep_i, 0); + Btable.set_tx_addr(ep_i, buf_addr); + Btable.set_tx_count(ep_i, 0); } // Determine EP type - const ep_type: u16 = switch (desc.attributes.transfer_type) { - .Control => EP_TYPE_CONTROL, - .Isochronous => EP_TYPE_ISO, - .Bulk => EP_TYPE_BULK, - .Interrupt => EP_TYPE_INTERRUPT, + const ep_type: EpType = switch (desc.attributes.transfer_type) { + .Control => .control, + .Isochronous => .iso, + .Bulk => .bulk, + .Interrupt => .interrupt, }; // Configure EPR with the endpoint type // We need to read current EPR to check if already configured - const cur = epr_read(ep_i); - if (cur & EPR_EA_MASK != @as(u16, ep_i)) { + const cur = Epr.read(ep_i); + if (cur & Epr.ea_mask != @as(u16, ep_i)) { // First time configuring this endpoint - epr_configure(ep_i, ep_type, STAT_NAK, STAT_NAK); + Epr.configure(ep_i, ep_type, .nak, .nak); } // Set the appropriate direction to desired state switch (e.dir) { - .Out => epr_set_stat_rx(ep_i, STAT_NAK), - .In => epr_set_stat_tx(ep_i, STAT_NAK), + .Out => Epr.set_stat_rx(ep_i, .nak), + .In => Epr.set_stat_tx(ep_i, .nak), } } } @@ -686,8 +665,8 @@ pub fn Polled(comptime cfg: Config) type { st_out.rx_last_len = 0; // Prepare BTABLE RX count and set RX to VALID - btable_set_rx_count(ep_i, limit); - epr_set_stat_rx(ep_i, STAT_VALID); + Btable.set_rx_count(ep_i, limit); + Epr.set_stat_rx(ep_i, .valid); } fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { @@ -739,12 +718,12 @@ pub fn Polled(comptime cfg: Config) type { } // Write to PMA - const tx_addr = pma_read16(btable_tx_addr_offset(ep_i)); - pma_write_bytes(tx_addr, self.staging_buf[0..w]); - btable_set_tx_count(ep_i, @intCast(w)); + const tx_addr = Pma.read16(Btable.tx_addr_offset(ep_i)); + Pma.write_bytes(tx_addr, self.staging_buf[0..w]); + Btable.set_tx_count(ep_i, @intCast(w)); st_in.tx_busy = true; - epr_set_stat_tx(ep_i, STAT_VALID); + Epr.set_stat_tx(ep_i, .valid); return @intCast(w); } @@ -781,10 +760,7 @@ pub fn Polled(comptime cfg: Config) type { USB_PERIPH.CNTR.write(.{ .FRES = 1, .PDWN = 0 }); // 2. Wait for analog transceiver startup (tSTARTUP >= 1us) - var i: u32 = 0; - while (i < 1000) : (i += 1) { - asm volatile ("nop"); - } + time.delay_us(2); // 2us with margin // 3. Clear FRES to release USB from reset. // EPR registers are held at 0 while FRES=1, so all @@ -801,11 +777,8 @@ pub fn Polled(comptime cfg: Config) type { USB_PERIPH.DADDR.write(.{ .EF = 1, .ADD = 0 }); // 7. Zero out the BTABLE area in PMA - { - var off: u16 = 0; - while (off < PMA_BTABLE_SIZE) : (off += 2) { - pma_write16(off, 0); - } + inline for (0..Btable.size / 2) |i| { + Pma.write16(@intCast(i * 2), 0); } } }; From d8fa6df71b8e4f5b7061095e00bc18146047673c Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Sat, 12 Sep 2026 09:16:30 -0400 Subject: [PATCH 6/7] fix uart setup in usb_cdc --- examples/wch/ch32v/src/usb_cdc.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 567a75fb2..3b1fc34c4 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -7,7 +7,7 @@ const time = hal.time; const usb = microzig.core.usb; const USB_Serial = usb.drivers.CDC; -const uart_cfg: hal.usart.UartConfig = if (@hasDecl(board, "uart_config")) board.uart_config else .{}; +const uart = board.uart_setup; pub const std_options = microzig.std_options(.{ .logFn = hal.usart.log, @@ -49,8 +49,8 @@ pub fn main() !void { microzig.board.init(); microzig.hal.init(); - hal.usart.setup_uart(uart_cfg); - hal.usart.init_logger(uart_cfg.instance); + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); std.log.info("UART logging initialized.", .{}); std.log.info("Initializing USB device.", .{}); From ed29dfd348bbfc1160eb39cf1fdd4491749cbfd4 Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Sat, 12 Sep 2026 09:24:20 -0400 Subject: [PATCH 7/7] Fix rebase, add more board config --- .../wch/ch32v/src/boards/CH32V307V-R1-1v0.zig | 7 +++ .../ch32v/src/boards/Suzuduino_Uno_V1b.zig | 7 +++ port/wch/ch32v/src/hals/usbd.zig | 52 +++++++++---------- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig b/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig index bf7c63fb7..b501d7321 100644 --- a/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig +++ b/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig @@ -16,6 +16,13 @@ pub const clock_config: ch32v.clocks.Config = .{ /// CPU frequency is derived from clock config pub const cpu_frequency = clock_config.target_frequency; +/// Default UART: USART1 on PA9/PA10 +pub const uart_setup: ch32v.usart.Setup = .{ + .instance = .USART1, + .tx_pin = ch32v.gpio.Pin.init(0, 9), // PA9 + .rx_pin = ch32v.gpio.Pin.init(0, 10), // PA10 +}; + /// Board-specific init: set 48 MHz clocks for the CPU and USBHS, enable SysTick time pub fn init() void { ch32v.clocks.init(clock_config); diff --git a/port/wch/ch32v/src/boards/Suzuduino_Uno_V1b.zig b/port/wch/ch32v/src/boards/Suzuduino_Uno_V1b.zig index a2dfa9754..d0a3b379f 100644 --- a/port/wch/ch32v/src/boards/Suzuduino_Uno_V1b.zig +++ b/port/wch/ch32v/src/boards/Suzuduino_Uno_V1b.zig @@ -16,6 +16,13 @@ pub const clock_config: ch32v.clocks.Config = .{ /// CPU frequency is derived from clock config pub const cpu_frequency = clock_config.target_frequency; +/// Default UART: USART1 on PA9/PA10 +pub const uart_setup: ch32v.usart.Setup = .{ + .instance = .USART1, + .tx_pin = ch32v.gpio.Pin.init(0, 9), // PA9 + .rx_pin = ch32v.gpio.Pin.init(0, 10), // PA10 +}; + pub const pin_config = ch32v.pins.GlobalConfiguration{ .GPIOA = .{ .PIN5 = .{ diff --git a/port/wch/ch32v/src/hals/usbd.zig b/port/wch/ch32v/src/hals/usbd.zig index fab0c235b..0d5b403d6 100644 --- a/port/wch/ch32v/src/hals/usbd.zig +++ b/port/wch/ch32v/src/hals/usbd.zig @@ -380,14 +380,14 @@ pub fn Polled(comptime cfg: Config) type { fn on_bus_reset_local(self: *Self) void { // Clear state inline for (0..cfg.max_endpoints_count) |i| { - self.endpoints[i][@backingInt(types.Dir.Out)].rx_armed = false; - self.endpoints[i][@backingInt(types.Dir.Out)].rx_last_len = 0; - self.endpoints[i][@backingInt(types.Dir.In)].tx_busy = false; + self.endpoints[i][@backingInt(types.Dir.out)].rx_armed = false; + self.endpoints[i][@backingInt(types.Dir.out)].rx_last_len = 0; + self.endpoints[i][@backingInt(types.Dir.in)].tx_busy = false; } // Re-initialize BTABLE register and EP0 buffer descriptors USB_PERIPH.BTABLE.write_raw(0); - const ep0_buf = self.st(.ep0, .Out).pma_addr; + const ep0_buf = self.st(.ep0, .out).pma_addr; Btable.set_tx_addr(0, ep0_buf); Btable.set_tx_count(0, 0); Btable.set_rx_addr(0, ep0_buf); @@ -408,16 +408,16 @@ pub fn Polled(comptime cfg: Config) type { fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4, controller: anytype) void { switch (dir) { - .In => switch (ep) { + .in => switch (ep) { inline 0...15 => |i| { const num: types.Endpoint.Num = @fromBackingInt(@intCast(i)); - controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }); + controller.on_buffer(&self.interface, .{ .num = num, .dir = .in }); }, }, - .Out => switch (ep) { + .out => switch (ep) { inline 0...15 => |i| { const num: types.Endpoint.Num = @fromBackingInt(@intCast(i)); - controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }); + controller.on_buffer(&self.interface, .{ .num = num, .dir = .out }); }, }, } @@ -498,7 +498,7 @@ pub fn Polled(comptime cfg: Config) type { // receive the status stage or data stage. Epr.set_stat_rx(ep, .valid); - const st_in = self.st(.ep0, .In); + const st_in = self.st(.ep0, .in); st_in.tx_busy = false; controller.on_setup_req(&self.interface, &setup_pkt); @@ -508,7 +508,7 @@ pub fn Polled(comptime cfg: Config) type { const len = Btable.get_rx_count(ep); if (ep == 0) { - const st_out = self.st(.ep0, .Out); + const st_out = self.st(.ep0, .out); // Read data from PMA into staging buffer const rx_addr = Pma.read16(Btable.rx_addr_offset(ep)); const n: usize = @min(@as(usize, len), 64); @@ -517,12 +517,12 @@ pub fn Polled(comptime cfg: Config) type { // Re-arm EP0 RX Btable.set_rx_count(0, 64); Epr.set_stat_rx(0, .valid); - self.call_on_buffer(.Out, 0, controller); + self.call_on_buffer(.out, 0, controller); return; } const num: types.Endpoint.Num = @fromBackingInt(@intCast(ep)); - const st_out = self.st(num, .Out); + const st_out = self.st(num, .out); if (!st_out.rx_armed) return; @@ -536,12 +536,12 @@ pub fn Polled(comptime cfg: Config) type { // NAK until re-armed Epr.set_stat_rx(ep, .nak); - self.call_on_buffer(.Out, ep, controller); + self.call_on_buffer(.out, ep, controller); } fn handle_in(self: *Self, ep: u4, controller: anytype) void { const num: types.Endpoint.Num = @fromBackingInt(@intCast(ep)); - const st_in = self.st(num, .In); + const st_in = self.st(num, .in); if (!st_in.tx_busy) return; @@ -549,7 +549,7 @@ pub fn Polled(comptime cfg: Config) type { // NAK until next write Epr.set_stat_tx(ep, .nak); - self.call_on_buffer(.In, ep, controller); + self.call_on_buffer(.in, ep, controller); // After EP0 IN, re-arm EP0 OUT for next SETUP/status if (ep == 0) { @@ -573,11 +573,11 @@ pub fn Polled(comptime cfg: Config) type { assert(ep_i < cfg.max_endpoints_count); log.debug("ep_open ep{} dir={}", .{ ep_i, e.dir }); - const mps: u16 = desc.max_packet_size.into(); + const mps: u16 = desc.max_packet_size.native(); assert(mps > 0 and mps <= 64); - const out_st = self.st(e.num, .Out); - const in_st = self.st(e.num, .In); + const out_st = self.st(e.num, .out); + const in_st = self.st(e.num, .in); // Allocate PMA buffers on first open if (ep_i == 0) { @@ -598,7 +598,7 @@ pub fn Polled(comptime cfg: Config) type { } } else { // Non-EP0: separate TX and RX buffers - if (e.dir == .Out and out_st.max_size == 0) { + if (e.dir == .out and out_st.max_size == 0) { const buf_addr = self.pma_alloc(mps); out_st.pma_addr = buf_addr; out_st.max_size = mps; @@ -606,7 +606,7 @@ pub fn Polled(comptime cfg: Config) type { Btable.set_rx_addr(ep_i, buf_addr); Btable.set_rx_count(ep_i, mps); } - if (e.dir == .In and in_st.max_size == 0) { + if (e.dir == .in and in_st.max_size == 0) { const buf_addr = self.pma_alloc(mps); in_st.pma_addr = buf_addr; in_st.max_size = mps; @@ -633,8 +633,8 @@ pub fn Polled(comptime cfg: Config) type { // Set the appropriate direction to desired state switch (e.dir) { - .Out => Epr.set_stat_rx(ep_i, .nak), - .In => Epr.set_stat_tx(ep_i, .nak), + .out => Epr.set_stat_rx(ep_i, .nak), + .in => Epr.set_stat_tx(ep_i, .nak), } } } @@ -644,7 +644,7 @@ pub fn Polled(comptime cfg: Config) type { const self: *Self = @fieldParentPtr("interface", itf); if (ep_num == .ep0) { - const st0 = self.st(.ep0, .Out); + const st0 = self.st(.ep0, .out); st0.rx_limit = @intCast(len); return; } @@ -653,7 +653,7 @@ pub fn Polled(comptime cfg: Config) type { if (ep_i >= cfg.max_endpoints_count) @panic("ep_listen called for invalid endpoint"); - const st_out = self.st(ep_num, .Out); + const st_out = self.st(ep_num, .out); if (st_out.max_size == 0) @panic("ep_listen called for endpoint with no buffer allocated"); if (st_out.rx_armed) @@ -671,7 +671,7 @@ pub fn Polled(comptime cfg: Config) type { fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { const self: *Self = @fieldParentPtr("interface", itf); - const st_out = self.st(ep_num, .Out); + const st_out = self.st(ep_num, .out); const want: usize = @as(usize, st_out.rx_last_len); defer st_out.rx_last_len = 0; @@ -699,7 +699,7 @@ pub fn Polled(comptime cfg: Config) type { const ep_i: u4 = epn(ep_num); assert(ep_i < cfg.max_endpoints_count); - const st_in = self.st(ep_num, .In); + const st_in = self.st(ep_num, .in); if (st_in.max_size == 0) @panic("ep_writev called for endpoint with no buffer allocated");