openmv/lib/alif/Device/common/source/dcd.c
2025-08-13 14:12:16 -07:00

692 lines
25 KiB
C

/* Copyright (C) 2024 Alif Semiconductor - All Rights Reserved.
* Use, distribution and modification of this code is permitted under the
* terms stated in the Alif Semiconductor Software License Agreement
*
* You should have received a copy of the Alif Semiconductor Software
* License Agreement with this file. If not, please write to:
* contact@alifsemi.com, or visit: https://alifsemi.com/license
*
*/
#include "tusb_option.h"
#if CFG_TUD_ENABLED
#if defined(CORE_M55_HE)
#include "M55_HE.h"
#elif defined(CORE_M55_HP)
#include "M55_HP.h"
#else
#error "Unsupported core!"
#endif
#include "device/dcd.h"
#include "dcd.h"
#include "clk.h"
#include "power.h"
// #define TUSB_ALIF_DEBUG
// #define TUSB_ALIF_DEBUG_DEPTH (2048)
#if defined(TUSB_ALIF_DEBUG)
#if (1 < TUSB_ALIF_DEBUG_DEPTH)
#define LOG(...) memset(logbuf[bi % TUSB_ALIF_DEBUG_DEPTH], ' ', 48);\
snprintf(logbuf[(bi++) % TUSB_ALIF_DEBUG_DEPTH], 48, __VA_ARGS__);
char logbuf[TUSB_ALIF_DEBUG_DEPTH][48];
int bi = 0;
#else
#define LOG(...) memset(logbuf, ' ', 48);\
snprintf(logbuf, 48, __VA_ARGS__)
char logbuf[48];
#endif
#else
#define LOG(...)
#endif
/// Structs and Buffers --------------------------------------------------------
static uint32_t _evnt_buf[1024] CFG_TUSB_MEM_SECTION __attribute__((aligned(4096))); // [TODO] runtime alloc
static volatile uint32_t* _evnt_tail;
static uint8_t _ctrl_buf[64] CFG_TUSB_MEM_SECTION __attribute__((aligned(32))); // [TODO] runtime alloc
static uint32_t _xfer_trb[8][4] CFG_TUSB_MEM_SECTION __attribute__((aligned(32))); // [TODO] runtime alloc
static uint16_t _xfer_bytes[8];
static bool _ctrl_long_data = false;
static bool _xfer_cfgd = false;
// static bool _addr_req = false;
static uint32_t _sts_stage = 0;
/// Private Functions ----------------------------------------------------------
static uint8_t _dcd_cmd_wait(uint8_t ep, uint8_t typ, uint16_t param);
static uint8_t _dcd_start_xfer(uint8_t ep, void* buf, uint32_t size, uint8_t type);
static void _dcd_handle_depevt(uint8_t ep, uint8_t evt, uint8_t sts, uint16_t par);
static void _dcd_handle_devt(uint8_t evt, uint16_t info);
/// API Extension --------------------------------------------------------------
void dcd_uninit(void);
/// Device Setup ---------------------------------------------------------------
// Initializes the USB peripheral for device mode and enables it.
// This function should enable internal D+/D- pull-up for enumeration.
void dcd_init(uint8_t rhport)
{
// enable 20mhz clock
enable_cgu_clk20m();
// enable usb peripheral clock
enable_usb_periph_clk();
// power up usb phy
enable_usb_phy_power();
// disable usb phy isolation
disable_usb_phy_isolation();
// clear usb phy power-on-reset signal
CLKCTL_PER_MST->USB_CTRL2 &= ~(1 << 8);
// force stop/disconnect
udev->dctl_b.run_stop = 0;
// dctl set csftrst to 1 and wait for 0
udev->dctl_b.csftrst = 1;
while(0 != udev->dctl_b.csftrst);
sys_busy_loop_us(50000);
ugbl->gctl_b.coresoftreset = 1;
ugbl->gusb2phycfg0_b.physoftrst = 1;
sys_busy_loop_us(50000);
ugbl->gusb2phycfg0_b.physoftrst = 0;
sys_busy_loop_us(50000);
ugbl->gctl_b.coresoftreset = 0;
sys_busy_loop_us(50000);
ugbl->gsbuscfg0 = 0x00000009;
// ugbl->gusb2phycfg0 = 0x4000154F; // [TODO] document as bits
uint32_t reg = ugbl->gusb2phycfg0;
reg &= ~((1 << 3) | (1 << 4) | (0xF << 10)); // clear phyif, ulpi_utmi_sel and usbtrdtim
reg |= ((1 << 3) | (5 << 10));
ugbl->gusb2phycfg0 = reg;
// set device speed (USBHS only)
udev->dcfg_b.devspd = 0x0; // HS, this will need #if condition [TODO]
// allocate ring buffer for events
memset(_evnt_buf, 0, sizeof(_evnt_buf));
RTSS_CleanDCache_by_Addr(_evnt_buf, sizeof(_evnt_buf));
_evnt_tail = _evnt_buf;
ugbl->gevntadrlo0 = (uint32_t) LocalToGlobal(_evnt_buf);
ugbl->gevntsiz0_b.eventsiz = (uint32_t) sizeof(_evnt_buf);
ugbl->gevntcount0_b.evntcount = 0;
// write devten to enable usb reset, conn done, link state change...
udev->devten_b.dissconnevten = 1;
udev->devten_b.usbrstevten = 1;
udev->devten_b.connectdoneevten = 1;
udev->devten_b.ulstcngen = 1;
// begin endpoint setup
_dcd_cmd_wait(0, CMDTYP_DEPSTARTCFG, 0);
// configure CONTROL IN and OUT eps
udev->depcmd[0].par1 = (0 << 25) | (1 << 10) | (1 << 8);
udev->depcmd[0].par0 = (0 << 22) | (0 << 17) | (512 << 3) | (0 << 1);
_dcd_cmd_wait(0, CMDTYP_DEPCFG, 0);
udev->depcmd[1].par1 = (1 << 25) | (1 << 10) | (1 << 8);
udev->depcmd[1].par0 = (0 << 22) | (0 << 17) | (512 << 3) | (0 << 1);
_dcd_cmd_wait(1, CMDTYP_DEPCFG, 0);
// set initial xfer configuration for CONTROL eps
udev->depcmd[0].par0 = 1;
_dcd_cmd_wait(0, CMDTYP_DEPXFERCFG, 0);
udev->depcmd[1].par0 = 1;
_dcd_cmd_wait(1, CMDTYP_DEPXFERCFG, 0);
// enable pull-ups
dcd_connect(rhport);
// prepare trb for the first setup packet
memset(_ctrl_buf, 0, sizeof(_ctrl_buf));
_xfer_trb[0][0] = (uint32_t) LocalToGlobal(_ctrl_buf);
_xfer_trb[0][1] = 0;
_xfer_trb[0][2] = 8;
_xfer_trb[0][3] = (1 << 11) | (1 << 10) | (TRBCTL_CTL_SETUP << 4) | (1 << 1) | (1 << 0);
RTSS_CleanDCache_by_Addr(_xfer_trb[0], sizeof(_xfer_trb[0]));
// send trb to the usb dma
udev->depcmd[0].par1 = (uint32_t) LocalToGlobal(_xfer_trb[0]);
udev->depcmd[0].par0 = 0;
_dcd_cmd_wait(0, CMDTYP_DEPSTRTXFER, 0);
// enable ep event interrupts for CONTROL OUT and IN
udev->dalepena_b.usbactep = (1 << 1) | (1 << 0);
// enable interrupts in the NVIC
#if !defined(TUSB_ALIF_NO_IRQ_CFG)
NVIC_ClearPendingIRQ(USB_IRQ_IRQn);
NVIC_SetPriority(USB_IRQ_IRQn, 5);
#endif
dcd_int_enable(rhport);
}
// Processes all the hardware generated events e.g bus reset, new data packet
// from host... It will be called by application in the MCU USB interrupt handler.
void dcd_int_handler(uint8_t rhport)
{
LOG("%010u IRQ enter, evntcount %u", DWT->CYCCNT, ugbl->gevntcount0_b.evntcount);
// process failures first
if (ugbl->gsts_b.device_ip) {
if (ugbl->gsts_b.csrtimeout || ugbl->gsts_b.buserraddrvld) {
// buserraddrvld is usually set when USB tries to write to protected
// memory region. Check linker script to ensure USB event buffer and
// TRBs reside in bulk memory or other NS-allowed region
__BKPT(0);
}
}
// cycle through event queue
// evaluate on every iteration to prevent unnecessary isr exit/reentry
while (0 < ugbl->gevntcount0_b.evntcount) {
RTSS_InvalidateDCache_by_Addr(_evnt_buf, sizeof(_evnt_buf));
volatile evt_t e = {.val = *_evnt_tail++};
LOG("%010u IRQ loop, evntcount %u evnt %08x", DWT->CYCCNT,
ugbl->gevntcount0_b.evntcount, e.val);
// wrap around
if (_evnt_tail >= (_evnt_buf + 1024)) _evnt_tail = _evnt_buf;
// dispatch the right handler for the event type
if (0 == e.depevt.sig) { // DEPEVT
_dcd_handle_depevt(e.depevt.ep, e.depevt.evt, e.depevt.sts, e.depevt.par);
} else if (1 == e.devt.sig) { // DEVT
_dcd_handle_devt(e.devt.evt, e.devt.info);
} else {
// bad event??
LOG("Unknown event %u", e.val);
__BKPT(0);
}
// consume one event
ugbl->gevntcount0 = 4;
}
LOG("%010u IRQ exit, evntcount %u", DWT->CYCCNT, ugbl->gevntcount0_b.evntcount);
}
// Enables the USB device interrupt.
// May be used to prevent concurrency issues when mutating data structures
// shared between main code and the interrupt handler.
void dcd_int_enable (uint8_t rhport)
{
NVIC_EnableIRQ(USB_IRQ_IRQn);
(void) rhport;
}
// Disables the USB device interrupt.
// May be used to prevent concurrency issues when mutating data structures
// shared between main code and the interrupt handler.
void dcd_int_disable(uint8_t rhport)
{
NVIC_DisableIRQ(USB_IRQ_IRQn);
(void) rhport;
}
// Receive Set Address request, mcu port must also include status IN response.
// If your peripheral automatically changes address during enumeration you may
// leave this empty and also no queue an event for the corresponding SETUP packet.
void dcd_set_address(uint8_t rhport, uint8_t dev_addr)
{
LOG("%010u >%s", DWT->CYCCNT, __func__);
// udev->dcfg_b.devaddr = dev_addr; // <- handled from xfernotready ISR
dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0);
}
// Called to remote wake up host when suspended (e.g hid keyboard)
void dcd_remote_wakeup(uint8_t rhport)
{
LOG("%010u >%s", DWT->CYCCNT, __func__);
}
// Connect by enabling internal pull-up resistor on D+/D-
void dcd_connect(uint8_t rhport)
{
udev->dctl_b.run_stop = 1;
(void) rhport;
}
// Disconnect by disabling internal pull-up resistor on D+/D-
void dcd_disconnect(uint8_t rhport)
{
// [TODO] clear all xfers and eps first
udev->dctl_b.run_stop = 0;
(void) rhport;
}
// Enable/Disable Start-of-frame interrupt. Default is disabled
void dcd_sof_enable(uint8_t rhport, bool en)
{
LOG("%010u >%s", DWT->CYCCNT, __func__);
}
/// Endpoint Management --------------------------------------------------------
// Invoked when a control transfer's status stage is complete.
// May help DCD to prepare for next control transfer, this API is optional.
void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request)
{
LOG("%010u >%s", DWT->CYCCNT, __func__);
_ctrl_long_data = false;
_dcd_start_xfer(TUSB_DIR_OUT, _ctrl_buf, 8, TRBCTL_CTL_SETUP);
}
// Opening an endpoint is done for all non-control endpoints once the host picks
// a configuration that the device should use.
// At this point, the endpoint should be enabled in the peripheral and
// configured to match the endpoint descriptor.
// Pay special attention to the direction of the endpoint you can get from the
// helper methods above. It will likely change what registers you are setting.
// Also make sure to enable endpoint specific interrupts.
bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep)
{
LOG("%010u >%s %u %s %u %u", DWT->CYCCNT, __func__, desc_ep->bEndpointAddress,
desc_ep->bmAttributes.xfer == TUSB_XFER_BULK ? "bulk" : "int",
desc_ep->wMaxPacketSize, desc_ep->bInterval);
if (TUSB_XFER_ISOCHRONOUS == desc_ep->bmAttributes.xfer)
return false;
uint8_t ep = (tu_edpt_number(desc_ep->bEndpointAddress) << 1) |
tu_edpt_dir(desc_ep->bEndpointAddress);
// [TODO] verify that the num doesn't exceed hw max
if (false == _xfer_cfgd) {
_dcd_cmd_wait(0, CMDTYP_DEPSTARTCFG, 2);
_xfer_cfgd = true;
}
uint8_t fifo_num = TUSB_DIR_IN == tu_edpt_dir(desc_ep->bEndpointAddress) ?
tu_edpt_number(desc_ep->bEndpointAddress) : 0;
uint8_t interval = 0 < desc_ep->bInterval ? (desc_ep->bInterval - 1) : 0;
udev->depcmd[ep].par1 = (ep << 25) | (interval << 16) |
(1 << 10) | (1 << 8);
udev->depcmd[ep].par0 = (0 << 30) | (0 << 22) | (fifo_num << 17) |
((desc_ep->wMaxPacketSize & 0x7FF) << 3) |
(desc_ep->bmAttributes.xfer << 1);
_dcd_cmd_wait(ep, CMDTYP_DEPCFG, 0);
udev->depcmd[ep].par0 = 1;
_dcd_cmd_wait(ep, CMDTYP_DEPXFERCFG, 0);
udev->dalepena_b.usbactep |= (1 << ep);
return true;
}
// Close all non-control endpoints, cancel all pending transfers if any.
// Invoked when switching from a non-zero Configuration by SET_CONFIGURE therefore
// required for multiple configuration support.
void dcd_edpt_close_all(uint8_t rhport)
{
LOG("%010u >%s", DWT->CYCCNT, __func__);
}
// Close an endpoint. his function is used for implementing alternate settings.
// After calling this, the device should not respond to any packets directed
// towards this endpoint. When called, this function must abort any transfers in
// progress through this endpoint, before returning.
// Implementation is optional. Must be called from the USB task.
// Interrupts could be disabled or enabled during the call.
void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) TU_ATTR_WEAK;
// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to
// notify the stack
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes)
{
// DEPSTRTXFER command
LOG("%010u >%s %u %x %u", DWT->CYCCNT, __func__, ep_addr, (uint32_t) buffer, total_bytes);
uint8_t ep = (tu_edpt_number(ep_addr) << 1) | tu_edpt_dir(ep_addr);
switch (ep) {
case 0: { // CONTROL OUT
if (0 < total_bytes) {
// DATA OUT request
_xfer_bytes[0] = total_bytes;
_dcd_start_xfer(0, buffer, total_bytes, TRBCTL_CTL_STAT3);
} else {
// TinyUSB explicitly requests STATUS OUT fetch after DATA IN
if (2 == ++_sts_stage) {
_sts_stage = 0;
dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_OUT),
0, XFER_RESULT_SUCCESS, true);
}
}
} break;
case 1: { // CONTROL IN
_xfer_bytes[1] = total_bytes;
if (0 < total_bytes) {
RTSS_CleanDCache_by_Addr(buffer, total_bytes);
uint8_t type = _ctrl_long_data ? TRBCTL_NORMAL : TRBCTL_CTL_DATA;
if (64 == total_bytes) {
_ctrl_long_data = true;
}
_dcd_start_xfer(1, buffer, total_bytes, type);
} else {
// status events are handled directly from the ISR when USB
// controller triggers XferNotReady event for status stage
if (2 == ++_sts_stage) {
_sts_stage = 0;
dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_IN),
0, XFER_RESULT_SUCCESS, true);
}
// dcd_event_xfer_complete(rhport, tu_edpt_addr(0, TUSB_DIR_IN),
// 0, XFER_RESULT_SUCCESS, false);
}
} break;
default: { // DATA EPs (BULK & INTERRUPT only)
_xfer_bytes[ep] = total_bytes;
if (TUSB_DIR_IN == tu_edpt_dir(ep_addr)) {
RTSS_CleanDCache_by_Addr(buffer, total_bytes);
} else {
total_bytes = 512; // temporary hack, controller requires max
// size requests on OUT endpoints [FIXME]
}
uint8_t ret = _dcd_start_xfer(ep, buffer, total_bytes,
total_bytes ? TRBCTL_NORMAL : TRBCTL_NORMAL_ZLP);
(void) ret; LOG("start xfer sts %u", ret);
}
}
return true;
}
// Submit a transfer using fifo, When complete dcd_event_xfer_complete() is invoked to notify the stack
// This API is optional, may be useful for register-based for transferring data.
bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) TU_ATTR_WEAK;
// Stall endpoint, any queuing transfer should be removed from endpoint
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
{
// DEPSSTALL command
LOG(">%s", __func__);
uint8_t ep = (tu_edpt_number(ep_addr) << 1) | tu_edpt_dir(ep_addr);
_dcd_cmd_wait(ep, CMDTYP_DEPSSTALL, 0);
if (0 == tu_edpt_number(ep_addr)) {
_ctrl_long_data = false;
_dcd_start_xfer(TUSB_DIR_OUT, _ctrl_buf, 8, TRBCTL_CTL_SETUP);
}
}
// clear stall, data toggle is also reset to DATA0
// This API never calls with control endpoints, since it is auto cleared when
// receiving setup packet
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
{
// DEPCSTALL command
LOG(">%s", __func__);
uint8_t ep = (tu_edpt_number(ep_addr) << 1) | tu_edpt_dir(ep_addr);
_dcd_cmd_wait(ep, CMDTYP_DEPCSTALL, 0);
}
void dcd_uninit(void)
{
CLKCTL_PER_MST->USB_CTRL2 |= 1 << 8; // set usb phy power-on-reset signal
enable_usb_phy_isolation(); // enable usb phy isolation
disable_usb_phy_power(); // power down usb phy
disable_usb_periph_clk(); // disable usb peripheral clock
dcd_int_disable(TUD_OPT_RHPORT);
NVIC_ClearPendingIRQ(USB_IRQ_IRQn);
}
__attribute__ ((weak)) void USB_IRQHandler(void)
{
dcd_int_handler(TUD_OPT_RHPORT);
}
static uint8_t _dcd_cmd_wait(uint8_t ep, uint8_t typ, uint16_t param)
{
// capture phy state and disable lpm and suspend
uint32_t phycfg = ugbl->gusb2phycfg0;
ugbl->gusb2phycfg0_b.enblslpm = 0;
ugbl->gusb2phycfg0_b.suspendusb20 = 0;
// set up command in depcmd register
udev->depcmd[ep].depcmd_b.cmdtyp = typ;
udev->depcmd[ep].depcmd_b.cmdioc = 0;
udev->depcmd[ep].depcmd_b.commandparam = param;
// dispatch command and wait for completion
udev->depcmd[ep].depcmd_b.cmdact = 1;
while(0 != udev->depcmd[ep].depcmd_b.cmdact);
// restore phy state
ugbl->gusb2phycfg0 = phycfg;
return udev->depcmd[ep].depcmd_b.cmdstatus;
}
static void _dcd_handle_depevt(uint8_t ep, uint8_t evt, uint8_t sts, uint16_t par)
{
LOG("%010u DEPEVT ep%u evt%u sts%u par%u", DWT->CYCCNT, ep, evt, sts, par);
switch (evt) {
case DEPEVT_XFERCOMPLETE: {
LOG("Transfer complete");
RTSS_InvalidateDCache_by_Addr(_xfer_trb[ep], sizeof(_xfer_trb[0]));
if (0 == ep) {
uint8_t trbctl = (_xfer_trb[0][3] >> 4) & 0x3F;
LOG("ep0 xfer trb3 = %08x", _xfer_trb[0][3]);
if (TRBCTL_CTL_SETUP == trbctl) {
RTSS_InvalidateDCache_by_Addr(_ctrl_buf, sizeof(_ctrl_buf));
LOG("%02x %02x %02x %02x %02x %02x %02x %02x",
_ctrl_buf[0], _ctrl_buf[1], _ctrl_buf[2], _ctrl_buf[3],
_ctrl_buf[4], _ctrl_buf[5], _ctrl_buf[6], _ctrl_buf[7]);
dcd_event_setup_received(TUD_OPT_RHPORT, _ctrl_buf, true);
} else if (TRBCTL_CTL_STAT3 == trbctl) {
if (0 < _xfer_bytes[0]) {
RTSS_InvalidateDCache_by_Addr((void*) _xfer_trb[0][0], _xfer_bytes[0]);
dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_OUT),
_xfer_bytes[0] - (_xfer_trb[0][2] & 0xFFFFFF),
XFER_RESULT_SUCCESS, true);
} else {
if (2 == ++_sts_stage) {
_sts_stage = 0;
dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_OUT),
0, XFER_RESULT_SUCCESS, true);
// *(volatile uint32_t*) 0x4900C000 ^= 8; // [TEMP]
}
}
} else {
// invalid TRBCTL value
__BKPT(0);
}
} else if (1 == ep) {
uint8_t trbctl = (_xfer_trb[1][3] >> 4) & 0x3F;
LOG("ep1 xfer trb3 = %08x trb2 = %08x", _xfer_trb[1][3], _xfer_trb[1][2]);
if (TRBCTL_CTL_STAT2 != trbctl) { // STATUS IN notification is done at xfer request
dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_IN),
_xfer_bytes[1] - (_xfer_trb[1][2] & 0xFFFFFF),
XFER_RESULT_SUCCESS, true);
} else {
if (2 == ++_sts_stage) {
_sts_stage = 0;
dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_IN),
0, XFER_RESULT_SUCCESS, true);
// *(volatile uint32_t*) 0x4900C000 ^= 8; // [TEMP]
}
}
} else {
// [TODO] check if ep is open
LOG("ep%u xfer trb3 = %08x trb2 = %08x", ep, _xfer_trb[ep][3], _xfer_trb[ep][2]);
if (TUSB_DIR_OUT == tu_edpt_dir(tu_edpt_addr(ep >> 1, ep & 1))) {
RTSS_InvalidateDCache_by_Addr((void*) _xfer_trb[ep][0],
512 - _xfer_trb[ep][2]);
// _xfer_bytes[ep] - _xfer_trb[ep][2]);
dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(ep >> 1, ep & 1),
512 - _xfer_trb[ep][2],
XFER_RESULT_SUCCESS, true);
} else
dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(ep >> 1, ep & 1),
_xfer_bytes[ep] - _xfer_trb[ep][2],
XFER_RESULT_SUCCESS, true);
}
} break;
case DEPEVT_XFERINPROGRESS: {
LOG("Transfer in progress");
} break;
case DEPEVT_XFERNOTREADY: {
LOG("Transfer not ready: %s", sts & 8 ? "no TRB" : "no XFER");
// XferNotReady NotActive for status stage
if ((1 == ep) && (0b0010 == (sts & 0b1011))) {
if (0x00 == _ctrl_buf[0] && 0x05 == _ctrl_buf[1]) {
udev->dcfg_b.devaddr = _ctrl_buf[2];
}
_dcd_start_xfer(1, NULL, 0, TRBCTL_CTL_STAT2);
break;
}
if ((0 == ep) && (0b0010 == (sts & 0b1011))) {
_xfer_bytes[0] = 0;
_dcd_start_xfer(0, _ctrl_buf, 64, TRBCTL_CTL_STAT3);
break;
}
if ((1 > ep) && (sts & (1 << 3))) {
if (_xfer_trb[ep][3] & (1 << 0)) { // transfer was configured
// dependxfer can only block when actbitlater is set
ugbl->guctl2_b.rst_actbitlater = 1;
_dcd_cmd_wait(ep, CMDTYP_DEPENDXFER, 0);
ugbl->guctl2_b.rst_actbitlater = 0;
// reset the trb byte count and clean the cache
RTSS_InvalidateDCache_by_Addr(_xfer_trb[ep], sizeof(_xfer_trb[0]));
_xfer_trb[ep][2] = _xfer_bytes[ep];
RTSS_CleanDCache_by_Addr(_xfer_trb[ep], sizeof(_xfer_trb[ep]));
// prepare ep command
udev->depcmd[ep].par1 = (uint32_t) LocalToGlobal(_xfer_trb[ep]);
udev->depcmd[ep].par0 = 0;
// issue the block command and pass the status
_dcd_cmd_wait(ep, CMDTYP_DEPSTRTXFER, 0);
*(volatile uint32_t*) 0x49007000 ^= 16; // [TEMP]
}
}
} break;
case DEPEVT_EPCMDCMPLT: {
// redundant, currently no commands are issued with IOC bit set
} break;
}
}
static void _dcd_handle_devt(uint8_t evt, uint16_t info)
{
LOG("%010u DEVT evt%u info%u", DWT->CYCCNT, evt, info);
switch (evt) {
case DEVT_USBRST: {
_xfer_cfgd = false;
// [TODO] issue depcstall for any ep in stall mode
udev->dcfg_b.devaddr = 0;
LOG("USB reset");
dcd_event_bus_reset(TUD_OPT_RHPORT, TUSB_SPEED_HIGH, true); // [TODO] actual speed
} break;
case DEVT_CONNECTDONE: {
// read conn speed from dsts
// program ramclksel in gctl if needed
LOG("Connect done");
udev->depcmd[0].par1 = (0 << 25) | (1 << 10) | (1 << 8);
udev->depcmd[0].par0 = (2 << 30) | (0 << 22) | (0 << 17) | (64 << 3) | (0 << 1);
_dcd_cmd_wait(0, CMDTYP_DEPCFG, 0);
udev->depcmd[1].par1 = (1 << 25) | (1 << 10) | (1 << 8);
udev->depcmd[1].par0 = (2 << 30) | (0 << 22) | (0 << 17) | (64 << 3) | (0 << 1);
_dcd_cmd_wait(1, CMDTYP_DEPCFG, 0);
} break;
case DEVT_ULSTCHNG: {
LOG("Link status change");
switch (info) {
case 0x3: { // suspend (L2)
dcd_event_bus_signal(TUD_OPT_RHPORT, DCD_EVENT_SUSPEND, true);
} break;
case 0x4: { // disconnected
dcd_event_bus_signal(TUD_OPT_RHPORT, DCD_EVENT_UNPLUGGED, true);
} break;
case 0xF: { // resume
dcd_event_bus_signal(TUD_OPT_RHPORT, DCD_EVENT_RESUME, true);
} break;
default: {}
}
// 0x0: ON state
// 0x2: L1 state (sleep)
// 0x3: L2 state (suspend)
// 0x4: disconnected state
// 0x5: early suspend
// 0xE: reset
// 0xF: resume
} break;
case DEVT_ERRTICERR: {
__BKPT(0);
} break;
default: {
LOG("Unknown DEVT event");
}
}
}
static uint8_t _dcd_start_xfer(uint8_t ep, void* buf, uint32_t size, uint8_t type)
{
dcd_int_disable(TUD_OPT_RHPORT); // prevent race conditions
// program the trb and clean the cache
_xfer_trb[ep][0] = buf ? (uint32_t) LocalToGlobal(buf) : 0;
_xfer_trb[ep][1] = 0;
_xfer_trb[ep][2] = size;
_xfer_trb[ep][3] = (1 << 11) | (1 << 10) | (type << 4) | (1 << 1) | (1 << 0);
RTSS_CleanDCache_by_Addr(_xfer_trb[ep], sizeof(_xfer_trb[ep]));
// prepare ep command
udev->depcmd[ep].par1 = (uint32_t) LocalToGlobal(_xfer_trb[ep]);
udev->depcmd[ep].par0 = 0;
dcd_int_enable(TUD_OPT_RHPORT);
// issue the block command and pass the status
return _dcd_cmd_wait(ep, CMDTYP_DEPSTRTXFER, 0);
}
#endif // CFG_TUD_ENABLED