/* 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; } } }