diff options
| author | HiFiPhile <[email protected]> | 2025-10-02 13:54:01 +0200 |
|---|---|---|
| committer | HiFiPhile <[email protected]> | 2025-10-02 14:48:55 +0200 |
| commit | e0a589cdcae6eaf790d05712a6f2b8c0352fb5fd (patch) | |
| tree | e2335d3e3236b07b0654ed0e1c7565747e60bb4c /src | |
| parent | 152d25ed621393a991561b7de61c2f01bbadeb71 (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.c | 41 |
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 |
