sdhci: service the watchdog and re-arm the timeout in the SDMA data phase

pull/916/head
David Garske 2026-10-20 09:54:49 -07:00
parent 47e8f57dcb
commit ac5d2fe4a0
2 changed files with 27 additions and 1 deletions

View File

@ -115,6 +115,13 @@
#define SDHCI_DATA_TIMEOUT_US 3000000 /* 3000ms for data operations */
#endif
/* Polls between watchdog refreshes in the data-phase wait. The error
* recovery waits pet on every pass; this one runs on every transfer, so it
* is throttled to keep the refresh cost off the transfer time. */
#ifndef SDHCI_WDT_PET_INTERVAL
#define SDHCI_WDT_PET_INTERVAL 4096U
#endif
/* Clock frequencies (kHz) */
#ifndef SDHCI_CLK_400KHZ
#define SDHCI_CLK_400KHZ 400
@ -234,6 +241,7 @@
#define SDHCI_SRS10_BVS_1_8V (0x5U << 9)
#define SDHCI_SRS10_BVS_3_0V (0x6U << 9)
#define SDHCI_SRS10_BVS_3_3V (0x7U << 9)
#define SDHCI_SRS10_DMA_MASK (0x3U << 3) /* DMA Select field */
#define SDHCI_SRS10_DMA_SDMA (0x0U << 3)
/* SRS11 - Clock Control / Timeout / Software Reset */

View File

@ -248,6 +248,8 @@ static void sdhci_disable_sdma_interrupts(void)
* - Polling mode: directly reads SRS12 status register and calls handler */
static int sdhci_wait_irq(uint32_t expected_flags, uint32_t timeout)
{
uint32_t pet = 0;
while (timeout-- > 0) {
/* Poll SRS12 directly for platforms without interrupt routing.
* In interrupt mode this is redundant (bits already cleared by ISR). */
@ -270,6 +272,12 @@ static int sdhci_wait_irq(uint32_t expected_flags, uint32_t timeout)
return 0;
}
}
/* A watchdog that cannot be disabled counts from the last refresh, so
* an unserviced data phase resets the chip before the timeout below. */
if (++pet >= SDHCI_WDT_PET_INTERVAL) {
pet = 0;
sdhci_platform_wdt_pet();
}
asm volatile("nop");
}
return -1; /* Timeout */
@ -1636,6 +1644,10 @@ static int sdhci_transfer(int dir, uint32_t cmd_index, uint32_t block_addr,
* A64 is cleared in SRS15 to use 32-bit DMA addressing.
* Note: Platform may redirect SRS22/SRS23 to SRS00 for legacy
* SDMA on controllers that don't support HV4E. */
/* DMA Select is a field, not a flag: SDHCI_SRS10_DMA_SDMA is 0,
* so an OR cannot clear a non-SDMA selection left by whatever
* ran before us. Clear the field first. */
sdhci_reg_and(SDHCI_SRS10, ~SDHCI_SRS10_DMA_MASK);
sdhci_reg_or(SDHCI_SRS10, SDHCI_SRS10_DMA_SDMA);
sdhci_reg_or(SDHCI_SRS15, SDHCI_SRS15_HV4E);
sdhci_reg_and(SDHCI_SRS15, ~SDHCI_SRS15_A64);
@ -1667,7 +1679,8 @@ static int sdhci_transfer(int dir, uint32_t cmd_index, uint32_t block_addr,
if (cmd_reg & SDHCI_SRS03_DMAE) {
/* DMA mode with interrupt support */
while (1) {
status = sdhci_wait_irq(SDHCI_IRQ_FLAG_TC, 0x00FFFFFF);
status = sdhci_wait_irq(
SDHCI_IRQ_FLAG_TC | SDHCI_IRQ_FLAG_DMAINT, 0x00FFFFFF);
if (status != 0) {
wolfBoot_printf("sdhci_transfer: SDMA timeout/error\n");
status = -1;
@ -1677,6 +1690,11 @@ static int sdhci_transfer(int dir, uint32_t cmd_index, uint32_t block_addr,
g_mmc_irq_status &= ~SDHCI_IRQ_FLAG_TC;
break;
}
/* An SDMA buffer-boundary pause. The handler has re-armed the
* address, so loop and wait again. Each segment gets its own
* budget rather than one budget bounding the whole data phase,
* which is why the wait above services the watchdog. */
g_mmc_irq_status &= ~SDHCI_IRQ_FLAG_DMAINT;
}
sdhci_disable_sdma_interrupts();