Initial commit

This commit is contained in:
Puck Meerburg
2025-12-30 13:48:35 +00:00
commit 0f7b02c8a6
67 changed files with 20228 additions and 0 deletions

232
src/usb/core.c Normal file
View File

@@ -0,0 +1,232 @@
#include <stdlib.h>
#include "core.h"
#include "CH58x_common.h"
static volatile uint8_t *ep_t_len_regs[] = {
&R8_UEP0_T_LEN,
&R8_UEP1_T_LEN,
&R8_UEP2_T_LEN,
&R8_UEP3_T_LEN,
&R8_UEP4_T_LEN,
&R8_UEP5_T_LEN,
&R8_UEP6_T_LEN,
&R8_UEP7_T_LEN,
};
static volatile uint8_t *ep_ctrl_regs[] = {
&R8_UEP0_CTRL,
&R8_UEP1_CTRL,
&R8_UEP2_CTRL,
&R8_UEP3_CTRL,
&R8_UEP4_CTRL,
&R8_UEP5_CTRL,
&R8_UEP6_CTRL,
&R8_UEP7_CTRL,
};
static uint8_t recvlens[8];
uint8_t bConfigurationValue = 0;
// The hardware requires EP0 and EP4 to share a contiguous DMA buffer.
// Fuck if I know why.
__attribute__((aligned(4))) volatile uint8_t ep04_buf[64 + 64 + 64];
__attribute__((aligned(4))) volatile uint8_t epbuf[(8 - 2) * 2 * 64];
volatile uint8_t *buf_for_ep(uint8_t ep, int dir_in) {
if (ep == 0) return ep04_buf;
if (ep == 4) return ep04_buf + (dir_in ? 128 : 64);
// EP0 and EP4 aren't in here. so skip them.
ep -= 1;
if (ep > 3) ep -= 1;
return epbuf + (ep * 128 + (dir_in ? 64 : 0));
}
// Configure the response to set for the next token for the endpoint.
// The interrupt handler will reconfigure this to return NAK whenever
// the next token has been received.
enum usb_endpoint_state set_endpoint_state(uint8_t endpoint, enum usb_endpoint_state state) {
int dir_in = endpoint & 0x80; // 0 -> OUT endpoint, 1 -> IN endpoint
int ep_num = endpoint & 0x7F;
if (dir_in) {
uint8_t prev_state = *ep_ctrl_regs[ep_num] & MASK_UEP_T_RES;
*ep_ctrl_regs[ep_num] = (*ep_ctrl_regs[ep_num] & ~MASK_UEP_T_RES) | (state & 0x03);
return prev_state;
} else {
uint8_t prev_state = (*ep_ctrl_regs[ep_num] & MASK_UEP_R_RES) >> 2;
*ep_ctrl_regs[ep_num] = (*ep_ctrl_regs[ep_num] & ~MASK_UEP_R_RES) | ((state & 0x03) << 2);
return prev_state;
}
}
// Toggle the DATA0/DATA1 toggle bit, indicating which of
// DATA0 and DATA1 to use fo rthe next receiving or sending of data.
// These bits are only toggled once data has been successfully transferred;
// a la USB_EP_STATE_ACK.
//
// The SETUP control transfers on endoint 0 _always_ use DATA0.
static void flip_endpoint_data_toggle(uint8_t endpoint) {
int dir_in = endpoint & 0x80;
int ep_num = endpoint & 0x7F;
if (dir_in) {
*ep_ctrl_regs[ep_num] ^= RB_UEP_T_TOG;
} else {
*ep_ctrl_regs[ep_num] ^= RB_UEP_R_TOG;
}
}
static void handle_bus_reset() {
R8_USB_INT_FG = RB_UIF_BUS_RST;
R8_USB_DEV_AD = 0;
bConfigurationValue = 0;
}
static void handle_suspend() {
R8_USB_INT_FG = RB_UIF_SUSPEND;
// TODO
}
static void handle_sof() {
R8_USB_INT_FG = RB_UIF_HST_SOF;
}
__HIGH_CODE
static void handle_transfer(uint8_t ep_num, int is_in) {
uint8_t endpoint = ep_num | (is_in ? 0x80 : 0);
// We may have received a transfer.
// If we expected this transfer, toggle the DATA0/DATA1 bits.
enum usb_endpoint_state prev_state = set_endpoint_state(endpoint, USB_EP_STATE_NAK);
if (prev_state == USB_EP_STATE_ACK) {
flip_endpoint_data_toggle(endpoint);
}
if (ep_num == 0) {
handle_ctrl_transfer(is_in);
return;
}
if (!is_in) {
recvlens[endpoint] = R8_USB_RX_LEN;
}
}
void usb_init() {
R8_USB_CTRL = 0x00;
R8_UEP4_1_MOD = RB_UEP4_RX_EN | RB_UEP4_TX_EN |
RB_UEP1_RX_EN | RB_UEP1_TX_EN;
R8_UEP2_3_MOD = RB_UEP2_RX_EN | RB_UEP2_TX_EN |
RB_UEP3_RX_EN | RB_UEP3_TX_EN;
R8_UEP567_MOD = RB_UEP7_RX_EN | RB_UEP7_TX_EN |
RB_UEP6_RX_EN | RB_UEP6_TX_EN |
RB_UEP5_RX_EN | RB_UEP5_TX_EN;
R16_PIN_ANALOG_IE |= RB_PIN_USB_IE | RB_PIN_USB_DP_PU;
R8_UDEV_CTRL = RB_UD_PD_DIS | RB_UD_PORT_EN;
R8_USB_DEV_AD = 0x00;
R8_USB_INT_FG = 0xFF;
R16_UEP0_DMA = ep04_buf;
R16_UEP1_DMA = epbuf + 0 * 128;
R16_UEP2_DMA = epbuf + 1 * 128;
R16_UEP3_DMA = epbuf + 2 * 128;
R16_UEP5_DMA = epbuf + 3 * 128;
R16_UEP6_DMA = epbuf + 4 * 128;
R16_UEP7_DMA = epbuf + 5 * 128;
for (int i = 0; i < 8; i++) {
set_endpoint_state(i, USB_EP_STATE_NAK);
set_endpoint_state(i | 0x80, USB_EP_STATE_NAK);
}
R8_USB_CTRL = RB_UC_DEV_PU_EN | RB_UC_INT_BUSY | RB_UC_DMA_EN;
R8_USB_INT_EN = RB_UIE_SUSPEND | RB_UIE_BUS_RST | RB_UIE_TRANSFER;
PFIC_EnableIRQ(USB_IRQn);
}
__INTERRUPT
__HIGH_CODE
void USB_IRQHandler(void) {
uint8_t interrupt = R8_USB_INT_FG;
if (interrupt & RB_UIF_BUS_RST) {
handle_bus_reset();
}
if (interrupt & RB_UIF_SUSPEND) {
handle_suspend();
}
if (interrupt & RB_UIF_TRANSFER) {
uint8_t status = R8_USB_INT_ST;
uint8_t ep_num = status & MASK_UIS_ENDP;
uint8_t token = ((status & MASK_UIS_TOKEN) >> 4) & 0x03;
if (token == 0x00) { // OUT
if (status & RB_UIS_TOG_OK) handle_transfer(ep_num, 0);
R8_USB_INT_FG = RB_UIF_TRANSFER;
} else if (token == 0x01) { // SOF
handle_sof();
R8_USB_INT_FG = RB_UIF_TRANSFER;
} else if (token == 0x02) { // IN
handle_transfer(ep_num, 1);
R8_USB_INT_FG = RB_UIF_TRANSFER;
}
if ((status & RB_UIS_SETUP_ACT)) {
handle_setup_request();
R8_USB_INT_FG = RB_UIF_TRANSFER;
}
}
}
// Receive up to buflen bytes from selected OUT endpoint.
// If data is not yet available, returns -1.
int16_t usb_recv(uint8_t endpoint, void *buffer, size_t buflen) {
if (recvlens[endpoint] == 0xFF) {
set_endpoint_state(endpoint, USB_EP_STATE_ACK);
return -1;
}
if (recvlens[endpoint] < buflen) {
buflen = recvlens[endpoint];
}
recvlens[endpoint] = 0xFF;
memcpy(buffer, buf_for_ep(endpoint, 0), buflen);
return buflen;
}
// Sends up to buflen bytes to selected IN endpoint.
// If the buffer is still busy, returns -1.
int16_t usb_xmit(uint8_t endpoint, void *buffer, size_t buflen) {
if ((*ep_ctrl_regs[endpoint] & MASK_UEP_T_RES) != USB_EP_STATE_NAK) {
return -1;
}
if (buflen > 64) buflen = 64;
memcpy(buf_for_ep(endpoint, 1), buffer, buflen);
*ep_t_len_regs[endpoint] = buflen;
set_endpoint_state(endpoint | 0x80, USB_EP_STATE_ACK);
return buflen;
}
int usb_can_xmit(uint8_t endpoint) {
return (*ep_ctrl_regs[endpoint] & MASK_UEP_T_RES) == USB_EP_STATE_NAK;
}
int usb_connected(void) {
return !!(R8_USB_MIS_ST & RB_UMS_SUSPEND);
}