From 9dde50b7de97ac71eeea95e2afc02de023a344eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20W=C3=B3jcik?= Date: Fri, 22 Nov 2024 01:16:29 +0100 Subject: [PATCH] Comptime USB endpoints number (#284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Comptime USB endpoints number * Minor fix --------- Co-authored-by: Arkadiusz Wójcik --- core/src/core/usb.zig | 4 +- .../rp2xxx/src/rp2040_only/usb_cdc.zig | 8 +- .../rp2xxx/src/rp2040_only/usb_hid.zig | 8 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 831 +++++++++--------- 4 files changed, 438 insertions(+), 413 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 3e789e5c..5b222355 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -53,8 +53,8 @@ pub fn Usb(comptime f: anytype) type { var usb_config: ?*DeviceConfiguration = null; /// The clock has been initialized [Y/n] var clk_init: bool = false; - var itf_to_drv: [16]u8 = .{0} ** 16; - var ep_to_drv: [4][2]u8 = .{.{0} ** 2} ** 4; + var itf_to_drv: [f.cfg_max_interfaces_count]u8 = .{0} ** f.cfg_max_interfaces_count; + var ep_to_drv: [f.cfg_max_endpoints_count][2]u8 = .{.{0} ** 2} ** f.cfg_max_endpoints_count; /// The callbacks passed provided by the caller pub const callbacks = f; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_cdc.zig index 2b6202d9..67aed98f 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_cdc.zig @@ -14,6 +14,8 @@ const baud_rate = 115200; const uart_tx_pin = gpio.num(0); const uart_rx_pin = gpio.num(1); +const usb_dev = rp2xxx.usb.Usb(.{}); + const usb_config_len = usb.templates.config_descriptor_len + usb.templates.cdc_descriptor_len; const usb_config_descriptor = usb.templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, 100) ++ @@ -78,9 +80,9 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); // First we initialize the USB clock - rp2xxx.usb.Usb.init_clk(); + usb_dev.init_clk(); // Then initialize the USB device using the configuration defined above - rp2xxx.usb.Usb.init_device(&DEVICE_CONFIGURATION) catch unreachable; + usb_dev.init_device(&DEVICE_CONFIGURATION) catch unreachable; var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; @@ -88,7 +90,7 @@ pub fn main() !void { var buf: [1024]u8 = undefined; while (true) { // You can now poll for USB events - rp2xxx.usb.Usb.task( + usb_dev.task( false, // debug output over UART [Y/n] ) catch unreachable; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 1afd27a2..35acb23b 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -14,6 +14,8 @@ const baud_rate = 115200; const uart_tx_pin = gpio.num(0); const uart_rx_pin = gpio.num(1); +const usb_dev = rp2xxx.usb.Usb(.{}); + const usb_packet_size = 64; const usb_config_len = usb.templates.config_descriptor_len + usb.templates.hid_in_out_descriptor_len; const usb_config_descriptor = @@ -80,14 +82,14 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); // First we initialize the USB clock - rp2xxx.usb.Usb.init_clk(); + usb_dev.init_clk(); // Then initialize the USB device using the configuration defined above - rp2xxx.usb.Usb.init_device(&DEVICE_CONFIGURATION) catch unreachable; + usb_dev.init_device(&DEVICE_CONFIGURATION) catch unreachable; var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; while (true) { // You can now poll for USB events - rp2xxx.usb.Usb.task( + usb_dev.task( false, // debug output over UART [Y/n] ) catch unreachable; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 94bbe687..022602d1 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -19,9 +19,17 @@ pub const utils = usb.UsbUtils; const rom = @import("rom.zig"); const resets = @import("resets.zig"); +pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; + pub const EP0_OUT_IDX = 0; pub const EP0_IN_IDX = 1; +pub const UsbConfig = struct { + // Comptime defined supported max endpoints number, can be reduced to save RAM space + max_endpoints_count: u8 = RP2XXX_MAX_ENDPOINTS_COUNT, + max_interfaces_count: u8 = 16 +}; + // +++++++++++++++++++++++++++++++++++++++++++++++++ // User Interface // +++++++++++++++++++++++++++++++++++++++++++++++++ @@ -31,7 +39,9 @@ pub const EP0_IN_IDX = 1; /// We create a concrete implementaion by passing a handful /// of system specific functions to Usb(). Those functions /// are used by the abstract USB impl of microzig. -pub const Usb = usb.Usb(F); +pub fn Usb(comptime config: UsbConfig) type { + return usb.Usb(F(config)); +} pub const DeviceConfiguration = usb.DeviceConfiguration; pub const DeviceDescriptor = usb.DeviceDescriptor; @@ -106,354 +116,435 @@ pub const buffers = struct { /// A set of functions required by the abstract USB impl to /// create a concrete one. -pub const F = struct { - // Fixed number 4, probably should be comptime number up to 16 - var endpoints: [4][2]HardwareEndpoint = undefined; - - /// Initialize the USB clock to 48 MHz - /// - /// This requres that the system clock has been set up before hand - /// using the 12 MHz crystal. - pub fn usb_init_clk() void { - // Bring PLL_USB up to 48MHz. PLL_USB is clocked from refclk, which we've - // already moved over to the 12MHz XOSC. We just need to make it x4 that - // clock. - // - // Configure it: - // - // RFDIV = 1 - // FBDIV = 100 => FOUTVC0 = 1200 MHz - peripherals.PLL_USB.CS.modify(.{ .REFDIV = 1 }); - peripherals.PLL_USB.FBDIV_INT.modify(.{ .FBDIV_INT = 100 }); - peripherals.PLL_USB.PWR.modify(.{ .PD = 0, .VCOPD = 0 }); - // Wait for lock - while (peripherals.PLL_USB.CS.read().LOCK == 0) {} - // Set up post dividers to enable output - // - // POSTDIV1 = POSTDIV2 = 5 - // PLL_USB FOUT = 1200 MHz / 25 = 48 MHz - peripherals.PLL_USB.PRIM.modify(.{ .POSTDIV1 = 5, .POSTDIV2 = 5 }); - peripherals.PLL_USB.PWR.modify(.{ .POSTDIVPD = 0 }); - // Switch usbclk to be derived from PLLUSB - peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .{ .value = .clksrc_pll_usb } }); - - // We now have the stable 48MHz reference clock required for USB: - } +pub fn F(comptime config: UsbConfig) type { - pub fn usb_init_device(_: *usb.DeviceConfiguration) void { - // Clear the control portion of DPRAM. This may not be necessary -- the - // datasheet is ambiguous -- but the C examples do it, and so do we. - peripherals.USBCTRL_DPRAM.SETUP_PACKET_LOW.write_raw(0); - peripherals.USBCTRL_DPRAM.SETUP_PACKET_HIGH.write_raw(0); - - peripherals.USBCTRL_DPRAM.EP1_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP1_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP2_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP2_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP3_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP3_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP4_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP4_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP5_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP5_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP6_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP6_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP7_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP7_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP8_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP8_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP9_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP9_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP10_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP10_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP11_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP11_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP12_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP12_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP13_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP13_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP14_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP14_OUT_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP15_IN_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP15_OUT_CONTROL.write_raw(0); - - peripherals.USBCTRL_DPRAM.EP0_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP0_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP1_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP1_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP2_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP2_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP3_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP3_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP4_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP4_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP5_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP5_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP6_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP6_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP7_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP7_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP8_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP8_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP9_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP9_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP10_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP10_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP11_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP11_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP12_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP12_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP13_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP13_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP14_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP14_OUT_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP15_IN_BUFFER_CONTROL.write_raw(0); - peripherals.USBCTRL_DPRAM.EP15_OUT_BUFFER_CONTROL.write_raw(0); - - // Mux the controller to the onboard USB PHY. I was surprised that there are - // alternatives to this, but, there are. - peripherals.USBCTRL_REGS.USB_MUXING.modify(.{ - .TO_PHY = 1, - // This bit is also set in the SDK example, without any discussion. It's - // undocumented (being named does not count as being documented). - .SOFTCON = 1, - }); - - // Force VBUS detect. Not all RP2040 boards wire up VBUS detect, which would - // let us detect being plugged into a host (the Pi Pico, to its credit, - // does). For maximum compatibility, we'll set the hardware to always - // pretend VBUS has been detected. - peripherals.USBCTRL_REGS.USB_PWR.modify(.{ - .VBUS_DETECT = 1, - .VBUS_DETECT_OVERRIDE_EN = 1, - }); - - // Enable controller in device mode. - peripherals.USBCTRL_REGS.MAIN_CTRL.modify(.{ - .CONTROLLER_EN = 1, - .HOST_NDEVICE = 0, - }); - - // Request to have an interrupt (which really just means setting a bit in - // the `buff_status` register) every time a buffer moves through EP0. - peripherals.USBCTRL_REGS.SIE_CTRL.modify(.{ - .EP0_INT_1BUF = 1, - }); - - // Enable interrupts (bits set in the `ints` register) for other conditions - // we use: - peripherals.USBCTRL_REGS.INTE.modify(.{ - // A buffer is done - .BUFF_STATUS = 1, - // The host has reset us - .BUS_RESET = 1, - // We've gotten a setup request on EP0 - .SETUP_REQ = 1, - }); - - @memset(std.mem.asBytes(&endpoints), 0); - endpoint_init(Endpoint.EP0_IN_ADDR, 64, types.TransferType.Control); - endpoint_init(Endpoint.EP0_OUT_ADDR, 64, types.TransferType.Control); - - // Present full-speed device by enabling pullup on DP. This is the point - // where the host will notice our presence. - peripherals.USBCTRL_REGS.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); + comptime { + if (config.max_endpoints_count > RP2XXX_MAX_ENDPOINTS_COUNT) { + @compileError("RP2XXX USB endpoints number can't be grater than RP2XXX_MAX_ENDPOINTS_COUNT"); + } } - /// Configures a given endpoint to send data (device-to-host, IN) when the host - /// next asks for it. - /// - /// The contents of `buffer` will be _copied_ into USB SRAM, so you can - /// reuse `buffer` immediately after this returns. No need to wait for the - /// packet to be sent. - pub fn usb_start_tx( - ep_addr: u8, - buffer: []const u8, - ) void { - // It is technically possible to support longer buffers but this demo - // doesn't bother. - // TODO: assert!(buffer.len() <= 64); - // You should only be calling this on IN endpoints. - // TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::In); - - const ep = hardware_endpoint_get_by_address(ep_addr); - - // Copy the given data into the corresponding ep buffer - const epbuffer = buffers.B.get(ep.data_buffer_index); - _ = rom.memcpy(epbuffer[0..buffer.len], buffer); - - // Configure the IN: - const np: u1 = if (ep.next_pid_1) 1 else 0; - - // The AVAILABLE bit in the buffer control register should be set - // separately to the rest of the data in the buffer control register, - // so that the rest of the data in the buffer control register is - // accurate when the AVAILABLE bit is set. - - // Write the buffer information to the buffer control register - modify_buffer_control(ep.buffer_control_index, .{ - .PID_0 = np, // DATA0/1, depending - .FULL_0 = 1, // We have put data in - .LENGTH_0 = @as(u10, @intCast(buffer.len)), // There are this many bytes - }); - - // Nop for some clock cycles - // use volatile so the compiler doesn't optimize the nops away - asm volatile ( - \\ nop - \\ nop - \\ nop - ); - - // Set available bit - modify_buffer_control(ep.buffer_control_index, .{ - .AVAILABLE_0 = 1, // The data is for the computer to use now - }); - - ep.next_pid_1 = !ep.next_pid_1; - } + return struct { + pub const cfg_max_endpoints_count: u8 = config.max_endpoints_count; + pub const cfg_max_interfaces_count: u8 = config.max_interfaces_count; + + var endpoints: [config.max_endpoints_count][2]HardwareEndpoint = undefined; + + /// Initialize the USB clock to 48 MHz + /// + /// This requres that the system clock has been set up before hand + /// using the 12 MHz crystal. + pub fn usb_init_clk() void { + // Bring PLL_USB up to 48MHz. PLL_USB is clocked from refclk, which we've + // already moved over to the 12MHz XOSC. We just need to make it x4 that + // clock. + // + // Configure it: + // + // RFDIV = 1 + // FBDIV = 100 => FOUTVC0 = 1200 MHz + peripherals.PLL_USB.CS.modify(.{ .REFDIV = 1 }); + peripherals.PLL_USB.FBDIV_INT.modify(.{ .FBDIV_INT = 100 }); + peripherals.PLL_USB.PWR.modify(.{ .PD = 0, .VCOPD = 0 }); + // Wait for lock + while (peripherals.PLL_USB.CS.read().LOCK == 0) {} + // Set up post dividers to enable output + // + // POSTDIV1 = POSTDIV2 = 5 + // PLL_USB FOUT = 1200 MHz / 25 = 48 MHz + peripherals.PLL_USB.PRIM.modify(.{ .POSTDIV1 = 5, .POSTDIV2 = 5 }); + peripherals.PLL_USB.PWR.modify(.{ .POSTDIVPD = 0 }); + // Switch usbclk to be derived from PLLUSB + peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .{ .value = .clksrc_pll_usb } }); + + // We now have the stable 48MHz reference clock required for USB: + } - pub fn usb_start_rx( - ep_addr: u8, - len: usize, - ) void { - // It is technically possible to support longer buffers but this demo - // doesn't bother. - // TODO: assert!(len <= 64); - // You should only be calling this on OUT endpoints. - // TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::Out); - - const ep = hardware_endpoint_get_by_address(ep_addr); - - // Check which DATA0/1 PID this endpoint is expecting next. - const np: u1 = if (ep.next_pid_1) 1 else 0; - // Configure the OUT: - modify_buffer_control(ep.buffer_control_index, .{ - .PID_0 = np, // DATA0/1 depending - .FULL_0 = 0, // Buffer is NOT full, we want the computer to fill it - .AVAILABLE_0 = 1, // It is, however, available to be filled - .LENGTH_0 = @as(u10, @intCast(len)), // Up tho this many bytes - }); - - // Flip the DATA0/1 PID for the next receive - ep.next_pid_1 = !ep.next_pid_1; - } + pub fn usb_init_device(_: *usb.DeviceConfiguration) void { + // Clear the control portion of DPRAM. This may not be necessary -- the + // datasheet is ambiguous -- but the C examples do it, and so do we. + peripherals.USBCTRL_DPRAM.SETUP_PACKET_LOW.write_raw(0); + peripherals.USBCTRL_DPRAM.SETUP_PACKET_HIGH.write_raw(0); + + peripherals.USBCTRL_DPRAM.EP1_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP1_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP2_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP2_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP3_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP3_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP4_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP4_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP5_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP5_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP6_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP6_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP7_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP7_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP8_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP8_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP9_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP9_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP10_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP10_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP11_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP11_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP12_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP12_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP13_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP13_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP14_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP14_OUT_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP15_IN_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP15_OUT_CONTROL.write_raw(0); + + peripherals.USBCTRL_DPRAM.EP0_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP0_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP1_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP1_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP2_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP2_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP3_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP3_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP4_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP4_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP5_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP5_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP6_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP6_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP7_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP7_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP8_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP8_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP9_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP9_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP10_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP10_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP11_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP11_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP12_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP12_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP13_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP13_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP14_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP14_OUT_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP15_IN_BUFFER_CONTROL.write_raw(0); + peripherals.USBCTRL_DPRAM.EP15_OUT_BUFFER_CONTROL.write_raw(0); + + // Mux the controller to the onboard USB PHY. I was surprised that there are + // alternatives to this, but, there are. + peripherals.USBCTRL_REGS.USB_MUXING.modify(.{ + .TO_PHY = 1, + // This bit is also set in the SDK example, without any discussion. It's + // undocumented (being named does not count as being documented). + .SOFTCON = 1, + }); + + // Force VBUS detect. Not all RP2040 boards wire up VBUS detect, which would + // let us detect being plugged into a host (the Pi Pico, to its credit, + // does). For maximum compatibility, we'll set the hardware to always + // pretend VBUS has been detected. + peripherals.USBCTRL_REGS.USB_PWR.modify(.{ + .VBUS_DETECT = 1, + .VBUS_DETECT_OVERRIDE_EN = 1, + }); + + // Enable controller in device mode. + peripherals.USBCTRL_REGS.MAIN_CTRL.modify(.{ + .CONTROLLER_EN = 1, + .HOST_NDEVICE = 0, + }); + + // Request to have an interrupt (which really just means setting a bit in + // the `buff_status` register) every time a buffer moves through EP0. + peripherals.USBCTRL_REGS.SIE_CTRL.modify(.{ + .EP0_INT_1BUF = 1, + }); + + // Enable interrupts (bits set in the `ints` register) for other conditions + // we use: + peripherals.USBCTRL_REGS.INTE.modify(.{ + // A buffer is done + .BUFF_STATUS = 1, + // The host has reset us + .BUS_RESET = 1, + // We've gotten a setup request on EP0 + .SETUP_REQ = 1, + }); + + @memset(std.mem.asBytes(&endpoints), 0); + endpoint_init(Endpoint.EP0_IN_ADDR, 64, types.TransferType.Control); + endpoint_init(Endpoint.EP0_OUT_ADDR, 64, types.TransferType.Control); + + // Present full-speed device by enabling pullup on DP. This is the point + // where the host will notice our presence. + peripherals.USBCTRL_REGS.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); + } - /// Check which interrupt flags are set - pub fn get_interrupts() usb.InterruptStatus { - const ints = peripherals.USBCTRL_REGS.INTS.read(); - - return .{ - .BuffStatus = if (ints.BUFF_STATUS == 1) true else false, - .BusReset = if (ints.BUS_RESET == 1) true else false, - .DevConnDis = if (ints.DEV_CONN_DIS == 1) true else false, - .DevSuspend = if (ints.DEV_SUSPEND == 1) true else false, - .DevResumeFromHost = if (ints.DEV_RESUME_FROM_HOST == 1) true else false, - .SetupReq = if (ints.SETUP_REQ == 1) true else false, - }; - } + /// Configures a given endpoint to send data (device-to-host, IN) when the host + /// next asks for it. + /// + /// The contents of `buffer` will be _copied_ into USB SRAM, so you can + /// reuse `buffer` immediately after this returns. No need to wait for the + /// packet to be sent. + pub fn usb_start_tx( + ep_addr: u8, + buffer: []const u8, + ) void { + // It is technically possible to support longer buffers but this demo + // doesn't bother. + // TODO: assert!(buffer.len() <= 64); + // You should only be calling this on IN endpoints. + // TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::In); + + const ep = hardware_endpoint_get_by_address(ep_addr); + + // Copy the given data into the corresponding ep buffer + const epbuffer = buffers.B.get(ep.data_buffer_index); + _ = rom.memcpy(epbuffer[0..buffer.len], buffer); + + // Configure the IN: + const np: u1 = if (ep.next_pid_1) 1 else 0; + + // The AVAILABLE bit in the buffer control register should be set + // separately to the rest of the data in the buffer control register, + // so that the rest of the data in the buffer control register is + // accurate when the AVAILABLE bit is set. + + // Write the buffer information to the buffer control register + modify_buffer_control(ep.buffer_control_index, .{ + .PID_0 = np, // DATA0/1, depending + .FULL_0 = 1, // We have put data in + .LENGTH_0 = @as(u10, @intCast(buffer.len)), // There are this many bytes + }); + + // Nop for some clock cycles + // use volatile so the compiler doesn't optimize the nops away + asm volatile ( + \\ nop + \\ nop + \\ nop + ); + + // Set available bit + modify_buffer_control(ep.buffer_control_index, .{ + .AVAILABLE_0 = 1, // The data is for the computer to use now + }); + + ep.next_pid_1 = !ep.next_pid_1; + } - /// Returns a received USB setup packet - /// - /// Side effect: The setup request status flag will be cleared - /// - /// One can assume that this function is only called if the - /// setup request falg is set. - pub fn get_setup_packet() usb.types.SetupPacket { - // Clear the status flag (write-one-to-clear) - peripherals.USBCTRL_REGS.SIE_STATUS.modify(.{ .SETUP_REC = 1 }); - - // This assumes that the setup packet is arriving on EP0, our - // control endpoint. Which it should be. We don't have any other - // Control endpoints. - - // Copy the setup packet out of its dedicated buffer at the base of - // USB SRAM. The PAC models this buffer as two 32-bit registers, - // which is, like, not _wrong_ but slightly awkward since it means - // we can't just treat it as bytes. Instead, copy it out to a byte - // array. - var setup_packet: [8]u8 = .{0} ** 8; - const spl: u32 = peripherals.USBCTRL_DPRAM.SETUP_PACKET_LOW.raw; - const sph: u32 = peripherals.USBCTRL_DPRAM.SETUP_PACKET_HIGH.raw; - _ = rom.memcpy(setup_packet[0..4], std.mem.asBytes(&spl)); - _ = rom.memcpy(setup_packet[4..8], std.mem.asBytes(&sph)); - // Reinterpret as setup packet - return std.mem.bytesToValue(usb.types.SetupPacket, &setup_packet); - } + pub fn usb_start_rx( + ep_addr: u8, + len: usize, + ) void { + // It is technically possible to support longer buffers but this demo + // doesn't bother. + // TODO: assert!(len <= 64); + // You should only be calling this on OUT endpoints. + // TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::Out); + + const ep = hardware_endpoint_get_by_address(ep_addr); + + // Check which DATA0/1 PID this endpoint is expecting next. + const np: u1 = if (ep.next_pid_1) 1 else 0; + // Configure the OUT: + modify_buffer_control(ep.buffer_control_index, .{ + .PID_0 = np, // DATA0/1 depending + .FULL_0 = 0, // Buffer is NOT full, we want the computer to fill it + .AVAILABLE_0 = 1, // It is, however, available to be filled + .LENGTH_0 = @as(u10, @intCast(len)), // Up tho this many bytes + }); + + // Flip the DATA0/1 PID for the next receive + ep.next_pid_1 = !ep.next_pid_1; + } - /// Called on a bus reset interrupt - pub fn bus_reset() void { - // Acknowledge by writing the write-one-to-clear status bit. - peripherals.USBCTRL_REGS.SIE_STATUS.modify(.{ .BUS_RESET = 1 }); - peripherals.USBCTRL_REGS.ADDR_ENDP.modify(.{ .ADDRESS = 0 }); - } + /// Check which interrupt flags are set + pub fn get_interrupts() usb.InterruptStatus { + const ints = peripherals.USBCTRL_REGS.INTS.read(); + + return .{ + .BuffStatus = if (ints.BUFF_STATUS == 1) true else false, + .BusReset = if (ints.BUS_RESET == 1) true else false, + .DevConnDis = if (ints.DEV_CONN_DIS == 1) true else false, + .DevSuspend = if (ints.DEV_SUSPEND == 1) true else false, + .DevResumeFromHost = if (ints.DEV_RESUME_FROM_HOST == 1) true else false, + .SetupReq = if (ints.SETUP_REQ == 1) true else false, + }; + } - pub fn set_address(addr: u7) void { - peripherals.USBCTRL_REGS.ADDR_ENDP.modify(.{ .ADDRESS = addr }); - } + /// Returns a received USB setup packet + /// + /// Side effect: The setup request status flag will be cleared + /// + /// One can assume that this function is only called if the + /// setup request falg is set. + pub fn get_setup_packet() usb.types.SetupPacket { + // Clear the status flag (write-one-to-clear) + peripherals.USBCTRL_REGS.SIE_STATUS.modify(.{ .SETUP_REC = 1 }); + + // This assumes that the setup packet is arriving on EP0, our + // control endpoint. Which it should be. We don't have any other + // Control endpoints. + + // Copy the setup packet out of its dedicated buffer at the base of + // USB SRAM. The PAC models this buffer as two 32-bit registers, + // which is, like, not _wrong_ but slightly awkward since it means + // we can't just treat it as bytes. Instead, copy it out to a byte + // array. + var setup_packet: [8]u8 = .{0} ** 8; + const spl: u32 = peripherals.USBCTRL_DPRAM.SETUP_PACKET_LOW.raw; + const sph: u32 = peripherals.USBCTRL_DPRAM.SETUP_PACKET_HIGH.raw; + _ = rom.memcpy(setup_packet[0..4], std.mem.asBytes(&spl)); + _ = rom.memcpy(setup_packet[4..8], std.mem.asBytes(&sph)); + // Reinterpret as setup packet + return std.mem.bytesToValue(usb.types.SetupPacket, &setup_packet); + } - pub fn reset_ep0() void { - var ep = hardware_endpoint_get_by_address(Endpoint.EP0_IN_IDX); - ep.next_pid_1 = true; - } + /// Called on a bus reset interrupt + pub fn bus_reset() void { + // Acknowledge by writing the write-one-to-clear status bit. + peripherals.USBCTRL_REGS.SIE_STATUS.modify(.{ .BUS_RESET = 1 }); + peripherals.USBCTRL_REGS.ADDR_ENDP.modify(.{ .ADDRESS = 0 }); + } - pub fn get_EPBIter(dc: *const usb.DeviceConfiguration) usb.EPBIter { - return .{ - .bufbits = peripherals.USBCTRL_REGS.BUFF_STATUS.raw, - .device_config = dc, - .next = next, - }; - } + pub fn set_address(addr: u7) void { + peripherals.USBCTRL_REGS.ADDR_ENDP.modify(.{ .ADDRESS = addr }); + } - fn hardware_endpoint_get_by_address(ep_addr: u8) *HardwareEndpoint { - const num = Endpoint.num_from_address(ep_addr); - const dir = Endpoint.dir_from_address(ep_addr); - return &endpoints[num][dir.as_number()]; - } + pub fn reset_ep0() void { + var ep = hardware_endpoint_get_by_address(Endpoint.EP0_IN_IDX); + ep.next_pid_1 = true; + } - pub fn endpoint_init(ep_addr: u8, max_packet_size: u16, transfer_type: types.TransferType) void { - const ep_num = Endpoint.num_from_address(ep_addr); - const ep_dir = Endpoint.dir_from_address(ep_addr); - - var ep = hardware_endpoint_get_by_address(ep_addr); - ep.ep_addr = ep_addr; - ep.max_packet_size = max_packet_size; - ep.transfer_type = transfer_type; - ep.next_pid_1 = false; - - ep.buffer_control_index = 2 * ep_num + ep_dir.as_number_reversed(); - // TODO - some other way to deal with it - ep.data_buffer_index = 2 * ep_num + ep_dir.as_number(); - - if (ep_num == 0) { - ep.endpoint_control_index = 0; - } else { - ep.endpoint_control_index = 2*ep_num - ep_dir.as_number(); - endpoint_alloc(ep, transfer_type); + pub fn get_EPBIter(dc: *const usb.DeviceConfiguration) usb.EPBIter { + return .{ + .bufbits = peripherals.USBCTRL_REGS.BUFF_STATUS.raw, + .device_config = dc, + .next = next, + }; } - } - fn endpoint_alloc(ep: *HardwareEndpoint, transfer_type: TransferType) void { - const buf_base = @intFromPtr(buffers.B.get(ep.data_buffer_index)); - const dpram_base = @intFromPtr(peripherals.USBCTRL_DPRAM); - // The offset _should_ fit in a u16, but if we've gotten something - // wrong in the past few lines, a common symptom will be integer - // overflow producing a Very Large Number, - const dpram_offset = @as(u16, @intCast(buf_base - dpram_base)); - - // Configure the endpoint! - modify_endpoint_control(ep.endpoint_control_index, .{ - .ENABLE = 1, - // Please set the corresponding bit in buff_status when a - // buffer is done, thx. - .INTERRUPT_PER_BUFF = 1, - // Select bulk vs control (or interrupt as soon as implemented). - .ENDPOINT_TYPE = .{ .raw = transfer_type.as_number() }, - // And, designate our buffer by its offset. - .BUFFER_ADDRESS = dpram_offset, - }); - } -}; + fn hardware_endpoint_get_by_address(ep_addr: u8) *HardwareEndpoint { + const num = Endpoint.num_from_address(ep_addr); + const dir = Endpoint.dir_from_address(ep_addr); + return &endpoints[num][dir.as_number()]; + } + + pub fn endpoint_init(ep_addr: u8, max_packet_size: u16, transfer_type: types.TransferType) void { + const ep_num = Endpoint.num_from_address(ep_addr); + const ep_dir = Endpoint.dir_from_address(ep_addr); + + std.debug.assert(ep_num <= cfg_max_endpoints_count); + + var ep = hardware_endpoint_get_by_address(ep_addr); + ep.ep_addr = ep_addr; + ep.max_packet_size = max_packet_size; + ep.transfer_type = transfer_type; + ep.next_pid_1 = false; + + ep.buffer_control_index = 2 * ep_num + ep_dir.as_number_reversed(); + // TODO - some other way to deal with it + ep.data_buffer_index = 2 * ep_num + ep_dir.as_number(); + + if (ep_num == 0) { + ep.endpoint_control_index = 0; + } else { + ep.endpoint_control_index = 2*ep_num - ep_dir.as_number(); + endpoint_alloc(ep, transfer_type); + } + } + + fn endpoint_alloc(ep: *HardwareEndpoint, transfer_type: TransferType) void { + const buf_base = @intFromPtr(buffers.B.get(ep.data_buffer_index)); + const dpram_base = @intFromPtr(peripherals.USBCTRL_DPRAM); + // The offset _should_ fit in a u16, but if we've gotten something + // wrong in the past few lines, a common symptom will be integer + // overflow producing a Very Large Number, + const dpram_offset = @as(u16, @intCast(buf_base - dpram_base)); + + // Configure the endpoint! + modify_endpoint_control(ep.endpoint_control_index, .{ + .ENABLE = 1, + // Please set the corresponding bit in buff_status when a + // buffer is done, thx. + .INTERRUPT_PER_BUFF = 1, + // Select bulk vs control (or interrupt as soon as implemented). + .ENDPOINT_TYPE = .{ .raw = transfer_type.as_number() }, + // And, designate our buffer by its offset. + .BUFFER_ADDRESS = dpram_offset, + }); + } + + pub fn next(self: *usb.EPBIter) ?usb.EPB { + if (self.last_bit) |lb| { + // Acknowledge the last handled buffer + peripherals.USBCTRL_REGS.BUFF_STATUS.write_raw(lb); + self.last_bit = null; + } + // All input buffers handled? + if (self.bufbits == 0) return null; + + // Who's still outstanding? Find their bit index by counting how + // many LSBs are zero. + var lowbit_index: u5 = 0; + while ((self.bufbits >> lowbit_index) & 0x01 == 0) : (lowbit_index += 1) {} + // Remove their bit from our set. + const lowbit = @as(u32, @intCast(1)) << lowbit_index; + self.last_bit = lowbit; + self.bufbits ^= lowbit; + + // Here we exploit knowledge of the ordering of buffer control + // registers in the peripheral. Each endpoint has a pair of + // registers, so we can determine the endpoint number by: + const epnum = @as(u8, @intCast(lowbit_index >> 1)); + // Of the pair, the IN endpoint comes first, followed by OUT, so + // we can get the direction by: + const dir = if (lowbit_index & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out; + + const ep_addr = Endpoint.to_address(epnum, dir); + // Process the buffer-done event. + + // Process the buffer-done event. + // + // Scan the device table to figure out which endpoint struct + // corresponds to this address. We could use a smarter + // method here, but in practice, the number of endpoints is + // small so a linear scan doesn't kill us. + + const endpoint = hardware_endpoint_get_by_address(ep_addr); + + // Buffer event for unknown EP?! + // TODO: if (endpoint == null) return EPBError.UnknownEndpoint; + // Read the buffer control register to check status. + const bc = read_raw_buffer_control(endpoint.buffer_control_index); + + // We should only get here if we've been notified that + // the buffer is ours again. This is indicated by the hw + // _clearing_ the AVAILABLE bit. + // + // This ensures that we can return a shared reference to + // the databuffer contents without races. + // TODO: if ((bc & (1 << 10)) == 1) return EPBError.NotAvailable; + + // Cool. Checks out. + + // Get a pointer to the buffer in USB SRAM. This is the + // buffer _contents_. See the safety comments below. + const epbuffer = buffers.B.get(endpoint.data_buffer_index); + + // Get the actual length of the data, which may be less + // than the buffer size. + const len = @as(usize, @intCast(bc & 0x3ff)); + + // Copy the data from SRAM + return usb.EPB{ + .endpoint_address = ep_addr, + .buffer = epbuffer[0..len], + }; + } + }; +} // +++++++++++++++++++++++++++++++++++++++++++++++++ // Utility functions @@ -581,73 +672,3 @@ pub fn modify_endpoint_control( else => {}, // TODO: We'll just ignore it for now } } - -// ----------------------------------------------------------- - -pub fn next(self: *usb.EPBIter) ?usb.EPB { - if (self.last_bit) |lb| { - // Acknowledge the last handled buffer - peripherals.USBCTRL_REGS.BUFF_STATUS.write_raw(lb); - self.last_bit = null; - } - // All input buffers handled? - if (self.bufbits == 0) return null; - - // Who's still outstanding? Find their bit index by counting how - // many LSBs are zero. - var lowbit_index: u5 = 0; - while ((self.bufbits >> lowbit_index) & 0x01 == 0) : (lowbit_index += 1) {} - // Remove their bit from our set. - const lowbit = @as(u32, @intCast(1)) << lowbit_index; - self.last_bit = lowbit; - self.bufbits ^= lowbit; - - // Here we exploit knowledge of the ordering of buffer control - // registers in the peripheral. Each endpoint has a pair of - // registers, so we can determine the endpoint number by: - const epnum = @as(u8, @intCast(lowbit_index >> 1)); - // Of the pair, the IN endpoint comes first, followed by OUT, so - // we can get the direction by: - const dir = if (lowbit_index & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out; - - const ep_addr = Endpoint.to_address(epnum, dir); - // Process the buffer-done event. - - // Process the buffer-done event. - // - // Scan the device table to figure out which endpoint struct - // corresponds to this address. We could use a smarter - // method here, but in practice, the number of endpoints is - // small so a linear scan doesn't kill us. - - const endpoint = F.hardware_endpoint_get_by_address(ep_addr); - - // Buffer event for unknown EP?! - // TODO: if (endpoint == null) return EPBError.UnknownEndpoint; - // Read the buffer control register to check status. - const bc = read_raw_buffer_control(endpoint.buffer_control_index); - - // We should only get here if we've been notified that - // the buffer is ours again. This is indicated by the hw - // _clearing_ the AVAILABLE bit. - // - // This ensures that we can return a shared reference to - // the databuffer contents without races. - // TODO: if ((bc & (1 << 10)) == 1) return EPBError.NotAvailable; - - // Cool. Checks out. - - // Get a pointer to the buffer in USB SRAM. This is the - // buffer _contents_. See the safety comments below. - const epbuffer = buffers.B.get(endpoint.data_buffer_index); - - // Get the actual length of the data, which may be less - // than the buffer size. - const len = @as(usize, @intCast(bc & 0x3ff)); - - // Copy the data from SRAM - return usb.EPB{ - .endpoint_address = ep_addr, - .buffer = epbuffer[0..len], - }; -}