Files
amiga-joymouse/usb_dwc2.c
T

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