First upload. Untested software
This commit is contained in:
@@ -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
|
||||||
@@ -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 :)
|
||||||
+77
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -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 <stdint.h>
|
||||||
|
|
||||||
|
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 */
|
||||||
@@ -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 = .;
|
||||||
|
}
|
||||||
@@ -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 <stdint.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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
|
||||||
@@ -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 <stdint.h>
|
||||||
|
|
||||||
|
// 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 */
|
||||||
+150
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -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 <stdint.h>
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
Reference in New Issue
Block a user