summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHiFiPhile <[email protected]>2025-10-02 13:54:01 +0200
committerHiFiPhile <[email protected]>2025-10-02 14:48:55 +0200
commite0a589cdcae6eaf790d05712a6f2b8c0352fb5fd (patch)
treee2335d3e3236b07b0654ed0e1c7565747e60bb4c /src
parent152d25ed621393a991561b7de61c2f01bbadeb71 (diff)
dwc2: support ISO IN transfer when bInterval > 1
dwc2 requires manually toggle Even/Odd bit manually for ISO IN transfer, that's poses a problem when bInterval > 1 mainly for audio class, as the moment the transfer is scheduled, we don't know when the host will issue IN token (bInterval vs bRefresh schenanigans). Linux driver use NAK interrupt to detect when the host is sending IN token and toggle the Even/Odd bit accordingly based on the current frame number and bInterval. However on ST's stripped down DWC2 FS controller (e.g STM32F4, STM32F7), NAK interrupt is not supported, even it's marked as always present in DWC2 databook. NAK interrupt is only supported on HS controller with external PHY. Instead I schedule all ISO IN transfer for next frame, if the transfer failed, incomplete isochronous IN transfer interrupt will be triggered and we can relaunch the transfer. Signed-off-by: HiFiPhile <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c41
1 files changed, 39 insertions, 2 deletions
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index f10f0bdc3..7d999e445 100644
--- a/src/portable/synopsys/dwc2/dcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/dcd_dwc2.c
@@ -51,6 +51,7 @@ typedef struct {
uint16_t total_len;
uint16_t max_size;
uint8_t interval;
+ uint8_t iso_retry; // ISO retry counter
} xfer_ctl_t;
// This variable is modified from ISR context, so it must be protected by critical section
@@ -357,7 +358,7 @@ static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uin
dwc2_depctl_t depctl = {.value = dep->ctl};
depctl.clear_nak = 1;
depctl.enable = 1;
- if (depctl.type == DEPCTL_EPTYPE_ISOCHRONOUS && xfer->interval == 1) {
+ if (depctl.type == DEPCTL_EPTYPE_ISOCHRONOUS) {
const dwc2_dsts_t dsts = {.value = dwc2->dsts};
const uint32_t odd_now = dsts.frame_number & 1u;
if (odd_now) {
@@ -597,6 +598,9 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to
_dcd_data.ep0_pending[dir] = total_bytes;
}
+ // Reset ISO retry counter to max frame interval value
+ xfer->iso_retry = 255;
+
// Schedule packets to be sent within interrupt
edpt_schedule_packets(rhport, epnum, dir);
ret = true;
@@ -629,6 +633,9 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t* ff, uint16_t
xfer->ff = ff;
xfer->total_len = total_bytes;
+ // Reset ISO retry counter to max frame interval value
+ xfer->iso_retry = 255;
+
// Schedule packets to be sent within interrupt
// TODO xfer fifo may only available for slave mode
edpt_schedule_packets(rhport, epnum, dir);
@@ -714,7 +721,7 @@ static void handle_bus_reset(uint8_t rhport) {
dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos);
}
- dwc2->gintmsk |= GINTMSK_OEPINT | GINTMSK_IEPINT;
+ dwc2->gintmsk |= GINTMSK_OEPINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM;
}
static void handle_enum_done(uint8_t rhport) {
@@ -1100,6 +1107,36 @@ void dcd_int_handler(uint8_t rhport) {
// IEPINT bit read-only, clear using DIEPINTn
handle_ep_irq(rhport, TUSB_DIR_IN);
}
+
+ // Incomplete isochronous IN transfer interrupt handling.
+ if (gintsts & GINTSTS_IISOIXFR) {
+ dwc2->gintsts = GINTSTS_IISOIXFR;
+ // Loop over all IN endpoints
+ const uint8_t ep_count = dwc2_ep_count(dwc2);
+ for (uint8_t epnum = 0; epnum < ep_count; epnum++) {
+ dwc2_dep_t* epin = &dwc2->epin[epnum];
+ dwc2_depctl_t depctl = {.value = epin->diepctl};
+ // Find enabled ISO endpoints
+ if (depctl.enable && depctl.type == DEPCTL_EPTYPE_ISOCHRONOUS) {
+ // Disable endpoint, flush fifo and restart transfer
+ depctl.set_nak = 1;
+ epin->diepctl = depctl.value;
+ depctl.disable = 1;
+ epin->diepctl = depctl.value;
+ while ((epin->diepint & DIEPINT_EPDISD_Msk) == 0) {}
+ epin->diepint = DIEPINT_EPDISD;
+ dfifo_flush_tx(dwc2, epnum);
+ xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN);
+ if (xfer->iso_retry) {
+ xfer->iso_retry--;
+ edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN);
+ } else {
+ // too many retries, give up
+ dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, 0, XFER_RESULT_FAILED, true);
+ }
+ }
+ }
+ }
}
#if CFG_TUD_TEST_MODE