151 lines
4.7 KiB
C
151 lines
4.7 KiB
C
/* 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;
|
|
}
|
|
}
|
|
}
|