commit 9b83763318de794a71d41cb2bbfee210b133bb10 Author: Anders Holck Date: Wed Apr 8 17:02:29 2026 +0200 First upload. Untested software diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ad08076 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +# Makefile for Amiga Joystick and Mouse Emulation on Milk-V Duo +# Baremetal Implementation (Targets SRAM at 0x0E000000) + +#/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + #* March 2026 - Anders Holck + #* This software has no license. If you choose to use, please include my name :) +#*/ + +CROSS_COMPILE = riscv64-unknown-elf- +CC = $(CROSS_COMPILE)gcc +AS = $(CROSS_COMPILE)as +LD = $(CROSS_COMPILE)ld +OBJCOPY = $(CROSS_COMPILE)objcopy + +# Baremetal flags +CFLAGS = -march=rv64gc -mabi=lp64d -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles -ffreestanding -O2 -T linker.ld +LDFLAGS = -static -nostdlib -T linker.ld + +TARGET = amiga_joystick_mouse.elf +BIN = amiga_joystick_mouse.bin +SRCS = startup.s main.c amiga_hw.c usb_dwc2.c + +all: $(BIN) + +$(TARGET): $(SRCS) + $(CC) $(CFLAGS) -o $@ $^ + +$(BIN): $(TARGET) + $(OBJCOPY) -O binary $< $@ + +clean: + rm -f *.o $(TARGET) $(BIN) + +.PHONY: all clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..b014980 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +Amiga Mouse + Joystick to USB adapter. +Using a CVITEK CV1800B (milk-v duo 64MB) Risc-V microcomputer/controller. +A level shifter is required to convert 3.3V to 5V. Joystick buttons and mouse buttons can be skipped. + +This software has no license. If you choose to use, please include my name :) diff --git a/amiga_hw.c b/amiga_hw.c new file mode 100644 index 0000000..75a6950 --- /dev/null +++ b/amiga_hw.c @@ -0,0 +1,77 @@ +/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + * March 2026 - Anders Holck + * This software has no license. If you choose to use, please include my name :) +*/ + +#include "amiga_hw.h" +#include "usb_dwc2.h" + +#define GPIOA_EXT_PORTA 0x03020050 + +// GPIOA Bit Mappings (From PINS.md) +#define BIT_JOY1_FIRE 28 +#define BIT_JOY1_UP 29 +#define BIT_JOY1_DOWN 14 +#define BIT_JOY1_LEFT 15 +#define BIT_JOY1_RIGHT 16 + +#define BIT_JOY2_FIRE 17 +#define BIT_JOY2_UP 18 +#define BIT_JOY2_DOWN 19 +#define BIT_JOY2_LEFT 20 +#define BIT_JOY2_RIGHT 21 + +#define BIT_MOUSE_H_P 22 +#define BIT_MOUSE_V_P 23 +#define BIT_MOUSE_H_D 24 +#define BIT_MOUSE_V_D 25 +#define BIT_MOUSE_BT1 26 +#define BIT_MOUSE_BT2 27 + +static uint8_t last_h_pulse = 0; +static uint8_t last_v_pulse = 0; + +void amiga_hw_poll(amiga_input_t *input) { + uint32_t val = read32(GPIOA_EXT_PORTA); + + // 1. Joystick 1 (Active Low - Amiga pulls to GND) + input->joy1 = 0; + if (!(val & (1 << BIT_JOY1_UP))) input->joy1 |= (1 << 0); + if (!(val & (1 << BIT_JOY1_DOWN))) input->joy1 |= (1 << 1); + if (!(val & (1 << BIT_JOY1_LEFT))) input->joy1 |= (1 << 2); + if (!(val & (1 << BIT_JOY1_RIGHT))) input->joy1 |= (1 << 3); + if (!(val & (1 << BIT_JOY1_FIRE))) input->joy1 |= (1 << 4); + + // 2. Joystick 2 + input->joy2 = 0; + if (!(val & (1 << BIT_JOY2_UP))) input->joy2 |= (1 << 0); + if (!(val & (1 << BIT_JOY2_DOWN))) input->joy2 |= (1 << 1); + if (!(val & (1 << BIT_JOY2_LEFT))) input->joy2 |= (1 << 2); + if (!(val & (1 << BIT_JOY2_RIGHT))) input->joy2 |= (1 << 3); + if (!(val & (1 << BIT_JOY2_FIRE))) input->joy2 |= (1 << 4); + + // 3. Mouse Buttons + input->buttons = 0; + if (!(val & (1 << BIT_MOUSE_BT1))) input->buttons |= (1 << 0); + if (!(val & (1 << BIT_MOUSE_BT2))) input->buttons |= (1 << 1); + + // 4. Mouse Quadrature Decoding (Simplified) + // Amiga Mouse uses 2 pulses per axis (Clock/Data or Phase A/B) + // Here we treat H-Pulse and V-Pulse as "clocks" and H-Dir/V-Dir as "direction" + uint8_t curr_h_pulse = (val >> BIT_MOUSE_H_P) & 1; + uint8_t curr_v_pulse = (val >> BIT_MOUSE_V_P) & 1; + + input->mouse_x = 0; + if (curr_h_pulse != last_h_pulse) { + uint8_t h_dir = (val >> BIT_MOUSE_H_D) & 1; + input->mouse_x = (h_dir == curr_h_pulse) ? 1 : -1; + last_h_pulse = curr_h_pulse; + } + + input->mouse_y = 0; + if (curr_v_pulse != last_v_pulse) { + uint8_t v_dir = (val >> BIT_MOUSE_V_D) & 1; + input->mouse_y = (v_dir == curr_v_pulse) ? 1 : -1; + last_v_pulse = curr_v_pulse; + } +} diff --git a/amiga_hw.h b/amiga_hw.h new file mode 100644 index 0000000..4499d2d --- /dev/null +++ b/amiga_hw.h @@ -0,0 +1,21 @@ +/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + * March 2026 - Anders Holck + * This software has no license. If you choose to use, please include my name :) +*/ + +#ifndef AMIGA_HW_H +#define AMIGA_HW_H + +#include + +typedef struct { + int8_t mouse_x; + int8_t mouse_y; + uint8_t buttons; // Bit 0: Mouse Left, Bit 1: Mouse Right + uint8_t joy1; // Bits: 0:Up, 1:Down, 2:Left, 3:Right, 4:Fire + uint8_t joy2; // Bits: 0:Up, 1:Down, 2:Left, 3:Right, 4:Fire +} amiga_input_t; + +void amiga_hw_poll(amiga_input_t *input); + +#endif /* AMIGA_HW_H */ diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..9be116e --- /dev/null +++ b/linker.ld @@ -0,0 +1,53 @@ +/* linker.ld - Linker script for Milk-V Duo Baremetal (SRAM) */ + +/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + * March 2026 - Anders Holck + * This software has no license. If you choose to use, please include my name :) +*/ + +OUTPUT_ARCH("riscv") +ENTRY(_start) + +MEMORY +{ + /* Internal SRAM is 128KB or more, starting at 0x0E000000 */ + ram (rwx) : ORIGIN = 0x0E000000, LENGTH = 128K +} + +SECTIONS +{ + /* Read-only code and constants */ + .text : + { + *(.text.startup) + *(.text) + *(.text.*) + } > ram + + .rodata : + { + *(.rodata) + *(.rodata.*) + } > ram + + /* Read-write initialized data */ + .data : + { + *(.data) + *(.data.*) + } > ram + + /* Read-write zero-initialized data */ + .bss : + { + . = ALIGN(8); + _bss_start = .; + *(.bss) + *(.bss.*) + *(COMMON) + . = ALIGN(8); + _bss_end = .; + } > ram + + _end = .; +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..a9ab027 --- /dev/null +++ b/main.c @@ -0,0 +1,80 @@ +/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + * March 2026 - Anders Holck + * This software has no license. If you choose to use, please include my name :) +*/ + +#include +#include "usb_dwc2.h" +#include "pinmux.h" +#include "amiga_hw.h" + +void delay(int count) { + for (volatile int i = 0; i < count; i++); +} + +void usb_init_baremetal() { + // 1. Enable USB Clocks + uint32_t clk_en = read32(CLK_EN_1); + clk_en |= (1 << 13) | (1 << 14); + write32(CLK_EN_1, clk_en); + + // 2. Power on PHY and release reset + write32(USB_PHY_CTRL, 0x07); + delay(10000); + + // 3. Global Reset of DWC2 Core + write32(GRSTCTL, (1 << 0)); // Soft Reset + while (!(read32(GRSTCTL) & (1 << 29))); + delay(10000); + + // 4. Force Device Mode and Basic Config + write32(GUSBCFG, (1 << 30) | (1 << 29)); // Force Device, PHY Interface + write32(DCFG, 0x0); // Full Speed + write32(GINTMSK, (1 << 12) | (1 << 13) | (1 << 4) | (1 << 18)); // Reset, Enum, RXFLVL, IEPInt + + // 5. Connect + write32(DCTL, 0); // Clear Soft Disconnect +} + +int main() { + pinmux_init_amiga(); + usb_init_baremetal(); + + amiga_input_t input; + uint8_t hid_report[4] = {0}; // 2 bytes buttons, 2 bytes mouse + uint32_t report_timer = 0; + + while (1) { + // 1. Poll Amiga Hardware + amiga_hw_poll(&input); + + // 2. Poll USB State Machine + dwc2_poll(); + + // 3. Send HID Report every ~10ms (approximate) + if (++report_timer > 100) { + report_timer = 0; + + // Format HID Report (Match hid_report_desc in usb_descriptors.h) + // Byte 0: Joy1 (5 bits) + Joy2 (3 bits) + // Byte 1: Joy2 (remaining 2 bits) + Padding + // Byte 2: Mouse X + // Byte 3: Mouse Y + + hid_report[0] = input.joy1 | ((input.joy2 & 0x07) << 5); + hid_report[1] = (input.joy2 >> 3) & 0x03; + if (input.buttons & 0x01) hid_report[1] |= (1 << 2); // Mouse B1 + if (input.buttons & 0x02) hid_report[1] |= (1 << 3); // Mouse B2 + + hid_report[2] = (uint8_t)input.mouse_x; + hid_report[3] = (uint8_t)input.mouse_y; + + // Send via Endpoint 1 (Interrupt IN) + dwc2_send_packet(1, hid_report, 4); + } + + delay(100); + } + + return 0; +} diff --git a/pinmux.h b/pinmux.h new file mode 100644 index 0000000..c11421e --- /dev/null +++ b/pinmux.h @@ -0,0 +1,58 @@ +/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + * March 2026 - Anders Holck + * This software has no license. If you choose to use, please include my name :) +*/ + +#ifndef PINMUX_H +#define PINMUX_H + +#include "usb_dwc2.h" + +#define PINMUX_BASE 0x03001000 + +/* Pinmux offsets for GPIOA 14-29 */ +#define PINMUX_GPIOA14 0x050 +#define PINMUX_GPIOA15 0x054 +#define PINMUX_GPIOA16 0x058 +#define PINMUX_GPIOA17 0x05C +#define PINMUX_GPIOA18 0x060 +#define PINMUX_GPIOA19 0x064 +#define PINMUX_GPIOA20 0x068 +#define PINMUX_GPIOA21 0x06C +#define PINMUX_GPIOA22 0x070 +#define PINMUX_GPIOA23 0x074 +#define PINMUX_GPIOA24 0x078 +#define PINMUX_GPIOA25 0x07C +#define PINMUX_GPIOA26 0x080 +#define PINMUX_GPIOA27 0x084 +#define PINMUX_GPIOA28 0x088 +#define PINMUX_GPIOA29 0x08C + +/* Function Mode for GPIO (usually Mode 0 or 3) */ +#define MUX_MODE_GPIO 0 + +static inline void pinmux_config(uint32_t offset, uint32_t mode) { + write32(PINMUX_BASE + offset, mode); +} + +static inline void pinmux_init_amiga() { + // Config GP0-GP15 as GPIO + pinmux_config(PINMUX_GPIOA28, MUX_MODE_GPIO); // GP0 + pinmux_config(PINMUX_GPIOA29, MUX_MODE_GPIO); // GP1 + pinmux_config(PINMUX_GPIOA14, MUX_MODE_GPIO); // GP2 + pinmux_config(PINMUX_GPIOA15, MUX_MODE_GPIO); // GP3 + pinmux_config(PINMUX_GPIOA16, MUX_MODE_GPIO); // GP4 + pinmux_config(PINMUX_GPIOA17, MUX_MODE_GPIO); // GP5 + pinmux_config(PINMUX_GPIOA18, MUX_MODE_GPIO); // GP6 + pinmux_config(PINMUX_GPIOA19, MUX_MODE_GPIO); // GP7 + pinmux_config(PINMUX_GPIOA20, MUX_MODE_GPIO); // GP8 + pinmux_config(PINMUX_GPIOA21, MUX_MODE_GPIO); // GP9 + pinmux_config(PINMUX_GPIOA22, MUX_MODE_GPIO); // GP10 + pinmux_config(PINMUX_GPIOA23, MUX_MODE_GPIO); // GP11 + pinmux_config(PINMUX_GPIOA24, MUX_MODE_GPIO); // GP12 + pinmux_config(PINMUX_GPIOA25, MUX_MODE_GPIO); // GP13 + pinmux_config(PINMUX_GPIOA26, MUX_MODE_GPIO); // GP14 + pinmux_config(PINMUX_GPIOA27, MUX_MODE_GPIO); // GP15 +} + +#endif /* PINMUX_H */ diff --git a/startup.s b/startup.s new file mode 100644 index 0000000..4f5c432 --- /dev/null +++ b/startup.s @@ -0,0 +1,32 @@ +# startup.s - Baremetal startup code for Milk-V Duo + +#/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + #* March 2026 - Anders Holck + #* This software has no license. If you choose to use, please include my name :) +#*/ + +.section .text.startup +.global _start + +_start: + # 1. Disable interrupts + csrw mie, zero + + # 2. Set up stack pointer (end of SRAM) + li sp, 0x0E000000 + 128*1024 + + # 3. Clear BSS + la t0, _bss_start + la t1, _bss_end + +bss_clear_loop: + bgeu t0, t1, bss_clear_done + sd zero, 0(t0) + addi t0, t0, 8 + j bss_clear_loop + +bss_clear_done: + j main + +.section .text +# The main function will be in amiga_joystick_mouse.s diff --git a/usb_descriptors.h b/usb_descriptors.h new file mode 100644 index 0000000..7d88e13 --- /dev/null +++ b/usb_descriptors.h @@ -0,0 +1,125 @@ +/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + * March 2026 - Anders Holck + * This software has no license. If you choose to use, please include my name :) +*/ + +#ifndef USB_DESCRIPTORS_H +#define USB_DESCRIPTORS_H + +#include + +// USB Standard Request Codes +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 + +// Descriptor Types +#define USB_DESC_DEVICE 0x01 +#define USB_DESC_CONFIGURATION 0x02 +#define USB_DESC_STRING 0x03 +#define USB_DESC_INTERFACE 0x04 +#define USB_DESC_ENDPOINT 0x05 +#define USB_DESC_HID 0x21 +#define USB_DESC_REPORT 0x22 + +// Device Descriptor +const uint8_t device_desc[] = { + 18, // bLength + USB_DESC_DEVICE, // bDescriptorType + 0x00, 0x02, // bcdUSB (2.0) + 0x00, // bDeviceClass + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol + 64, // bMaxPacketSize0 + 0x6B, 0x1D, // idVendor (0x1D6B - Linux Foundation) + 0x04, 0x01, // idProduct (0x0104) + 0x00, 0x01, // bcdDevice + 1, // iManufacturer + 2, // iProduct + 3, // iSerialNumber + 1 // bNumConfigurations +}; + +// HID Report Descriptor (Combined Gamepad and Mouse) +const uint8_t hid_report_desc[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Application) + + // Joystick 1 & 2 Buttons (10 buttons total) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (Button 1) + 0x29, 0x0A, // Usage Maximum (Button 10) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0A, // Report Count (10) + 0x81, 0x02, // Input (Data, Var, Abs) + + // Padding for buttons (6 bits) + 0x75, 0x01, // Report Size (1) + 0x95, 0x06, // Report Count (6) + 0x81, 0x03, // Input (Cnst, Var, Abs) + + // Mouse X/Y (Relative) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Physical) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x81, // Logical Minimum (-127) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x06, // Input (Data, Var, Rel) + 0xC0, // End Collection + + 0xC0 // End Collection +}; + +// Configuration Descriptor +const uint8_t config_desc[] = { + 9, // bLength + USB_DESC_CONFIGURATION, + 34, 0, // wTotalLength (9+9+9+7) + 1, // bNumInterfaces + 1, // bConfigurationValue + 0, // iConfiguration + 0x80, // bmAttributes (Bus Powered) + 250, // bMaxPower (500mA) + + // Interface Descriptor + 9, // bLength + USB_DESC_INTERFACE, + 0, // bInterfaceNumber + 0, // bAlternateSetting + 1, // bNumEndpoints + 0x03, // bInterfaceClass (HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + + // HID Descriptor + 9, // bLength + USB_DESC_HID, + 0x11, 0x01, // bcdHID (1.11) + 0x00, // bCountryCode + 1, // bNumDescriptors + USB_DESC_REPORT, // bDescriptorType + sizeof(hid_report_desc), 0, // wItemLength + + // Endpoint Descriptor (IN 1) + 7, // bLength + USB_DESC_ENDPOINT, + 0x81, // bEndpointAddress (IN 1) + 0x03, // bmAttributes (Interrupt) + 8, 0, // wMaxPacketSize (8 bytes) + 10 // bInterval (10ms) +}; + +#endif /* USB_DESCRIPTORS_H */ diff --git a/usb_dwc2.c b/usb_dwc2.c new file mode 100644 index 0000000..4b4c1ab --- /dev/null +++ b/usb_dwc2.c @@ -0,0 +1,150 @@ +/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + * March 2026 - Anders Holck + * This software has no license. If you choose to use, please include my name :) +*/ + +#include "usb_dwc2.h" +#include "usb_descriptors.h" + +// Endpoint 0 State +#define EP0_IDLE 0 +#define EP0_SETUP 1 +#define EP0_DATA_IN 2 +#define EP0_DATA_OUT 3 +#define EP0_STATUS_IN 4 +#define EP0_STATUS_OUT 5 + +static uint8_t ep0_state = EP0_IDLE; +static uint8_t usb_address = 0; + +// Setup Packet Structure +typedef struct { + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} usb_setup_t; + +void dwc2_setup_fifo() { + // Total FIFO size is often 512 or 1024 words. + // Configure RX FIFO (Shared) + write32(GRXFSIZ, 128); // 128 words + + // Configure TX FIFO 0 (EP0 IN) + // Offset = GRXFSIZ + write32(GNPTXFSIZ, (128 << 16) | 128); // Size 128, Start 128 + + // Configure TX FIFO 1 (EP1 IN - HID) + // Offset = GRXFSIZ + GNPTXFSIZ + write32(USB_DWC2_BASE + 0x104, (128 << 16) | 256); // Size 128, Start 256 +} + +void dwc2_send_packet(uint8_t ep_num, const uint8_t *data, uint32_t len) { + uint32_t fifo_addr = USB_DWC2_BASE + 0x1000 + (ep_num * 0x1000); + + // 1. Set Transfer Size and Packet Count + uint32_t dieptsiz = (1 << 19) | len; // 1 packet, 'len' bytes + write32(USB_DWC2_BASE + 0x908 + (ep_num * 0x20), dieptsiz); + + // 2. Enable Endpoint and Clear NAK + uint32_t diepctl = read32(USB_DWC2_BASE + 0x900 + (ep_num * 0x20)); + diepctl |= (1 << 31) | (1 << 26); // EPEna, Cnak + write32(USB_DWC2_BASE + 0x900 + (ep_num * 0x20), diepctl); + + // 3. Write data to FIFO (Word-aligned writes) + uint32_t words = (len + 3) / 4; + uint32_t *data32 = (uint32_t *)data; + for (uint32_t i = 0; i < words; i++) { + write32(fifo_addr, data32[i]); + } +} + +void dwc2_handle_setup(usb_setup_t *setup) { + if ((setup->bmRequestType & 0x60) == 0) { // Standard Request + switch (setup->bRequest) { + case USB_REQ_GET_DESCRIPTOR: { + uint8_t type = setup->wValue >> 8; + const uint8_t *ptr = 0; + uint32_t len = 0; + + if (type == USB_DESC_DEVICE) { + ptr = device_desc; + len = sizeof(device_desc); + } else if (type == USB_DESC_CONFIGURATION) { + ptr = config_desc; + len = sizeof(config_desc); + } else if (type == USB_DESC_REPORT) { + ptr = hid_report_desc; + len = sizeof(hid_report_desc); + } + + if (ptr) { + if (len > setup->wLength) len = setup->wLength; + dwc2_send_packet(0, ptr, len); + ep0_state = EP0_DATA_IN; + } + break; + } + case USB_REQ_SET_ADDRESS: + usb_address = setup->wValue & 0x7F; + // Address is applied after the status phase + dwc2_send_packet(0, 0, 0); // ZLP Status + ep0_state = EP0_STATUS_IN; + break; + case USB_REQ_SET_CONFIGURATION: + dwc2_send_packet(0, 0, 0); // ZLP Status + ep0_state = EP0_STATUS_IN; + break; + } + } +} + +void dwc2_poll() { + uint32_t gintsts = read32(GINTSTS); + + // 1. USB Reset + if (gintsts & (1 << 12)) { + write32(GINTSTS, (1 << 12)); + usb_address = 0; + ep0_state = EP0_IDLE; + dwc2_setup_fifo(); + // Enable EP0 OUT + write32(DOEPCTL0, (1 << 31) | (1 << 26)); // EPEna, Cnak + } + + // 2. Enumeration Done + if (gintsts & (1 << 13)) { + write32(GINTSTS, (1 << 13)); + // High speed or Full speed detected... + } + + // 3. Receive FIFO Level (Data available) + if (gintsts & (1 << 4)) { + uint32_t grxstsp = read32(GRXSTSP); + uint8_t ep_num = grxstsp & 0xF; + uint32_t bcnt = (grxstsp >> 4) & 0x7FF; + uint8_t pktsts = (grxstsp >> 17) & 0xF; + + if (pktsts == 6) { // SETUP Packet Received + usb_setup_t setup; + uint32_t *setup_ptr = (uint32_t *)&setup; + uint32_t fifo_addr = USB_DWC2_BASE + 0x1000; + setup_ptr[0] = read32(fifo_addr); + setup_ptr[1] = read32(fifo_addr); + dwc2_handle_setup(&setup); + } + } + + // 4. Update Address after Status Phase + if (ep0_state == EP0_STATUS_IN) { + uint32_t daint = read32(DAINT); + if (daint & (1 << 0)) { // EP0 IN Complete + uint32_t dcfg = read32(DCFG); + dcfg &= ~(0x7F << 4); + dcfg |= (usb_address << 4); + write32(DCFG, dcfg); + ep0_state = EP0_IDLE; + } + } +} diff --git a/usb_dwc2.h b/usb_dwc2.h new file mode 100644 index 0000000..18d0333 --- /dev/null +++ b/usb_dwc2.h @@ -0,0 +1,63 @@ +/* Baremetal Risc-V USB <=> Amiga Joystick/Mouse HID converter. + * March 2026 - Anders Holck + * This software has no license. If you choose to use, please include my name :) +*/ + +#ifndef USB_DWC2_H +#define USB_DWC2_H + +#include + +/* System Control Registers */ +#define SYS_CTRL_BASE 0x03000000 +#define CLK_EN_1 (SYS_CTRL_BASE + 0x2004) +#define USB_PHY_CTRL (SYS_CTRL_BASE + 0x7000) + +/* USB DWC2 Register Base */ +#define USB_DWC2_BASE 0x04340000 + +/* Global Registers */ +#define GOTGCTL (USB_DWC2_BASE + 0x000) +#define GOTGINT (USB_DWC2_BASE + 0x004) +#define GAHBCFG (USB_DWC2_BASE + 0x008) +#define GUSBCFG (USB_DWC2_BASE + 0x00C) +#define GRSTCTL (USB_DWC2_BASE + 0x010) +#define GINTSTS (USB_DWC2_BASE + 0x014) +#define GINTMSK (USB_DWC2_BASE + 0x018) +#define GRXSTSR (USB_DWC2_BASE + 0x01C) +#define GRXSTSP (USB_DWC2_BASE + 0x020) +#define GRXFSIZ (USB_DWC2_BASE + 0x024) +#define GNPTXFSIZ (USB_DWC2_BASE + 0x028) +#define GSNPSID (USB_DWC2_BASE + 0x040) +#define GHWCFG1 (USB_DWC2_BASE + 0x044) +#define GHWCFG2 (USB_DWC2_BASE + 0x048) +#define GHWCFG3 (USB_DWC2_BASE + 0x04C) +#define GHWCFG4 (USB_DWC2_BASE + 0x050) + +/* Device Registers */ +#define DCFG (USB_DWC2_BASE + 0x800) +#define DCTL (USB_DWC2_BASE + 0x804) +#define DSTS (USB_DWC2_BASE + 0x808) +#define DIEPMSK (USB_DWC2_BASE + 0x810) +#define DOEPMSK (USB_DWC2_BASE + 0x814) +#define DAINT (USB_DWC2_BASE + 0x818) +#define DAINTMSK (USB_DWC2_BASE + 0x81C) + +/* Endpoints (EP0 is at 0x900/0xB00) */ +#define DIEPCTL0 (USB_DWC2_BASE + 0x900) +#define DOEPCTL0 (USB_DWC2_BASE + 0xB00) + +/* Register Helpers */ +static inline void write32(uintptr_t addr, uint32_t val) { + *(volatile uint32_t *)addr = val; +} + +static inline uint32_t read32(uintptr_t addr) { + return *(volatile uint32_t *)addr; +} + +/* Driver Functions */ +void dwc2_poll(); +void dwc2_send_packet(uint8_t ep_num, const uint8_t *data, uint32_t len); + +#endif /* USB_DWC2_H */