From 06c9d87f534ed67f13bb6b5ad830ba750f2296f4 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Mon, 9 Feb 2026 14:01:46 +0000 Subject: [PATCH] stm32h7: sdmmc: implement missing R1b response handling The driver didn't handle the busy signal at the end of some command responses (R1b response type), notably the CMD12 (STOP_TRANSMISSION) sent after a multiblock write would time out because DTIMER==0 and leave the DPSM in an incorrect state for the next command, causing a hang. Change-Id: I406337a7612f759418a4872979aa2de5aa2244c7 --- firmware/target/arm/stm32/sdmmc-stm32h7.c | 51 ++++++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/firmware/target/arm/stm32/sdmmc-stm32h7.c b/firmware/target/arm/stm32/sdmmc-stm32h7.c index b61c778376..56e7d70e4c 100644 --- a/firmware/target/arm/stm32/sdmmc-stm32h7.c +++ b/firmware/target/arm/stm32/sdmmc-stm32h7.c @@ -28,6 +28,7 @@ /* cmd_wait flags */ #define WAIT_CMD 0x01 #define WAIT_DATA 0x02 +#define WAIT_BUSY 0x04 /* Maximum number of bytes for 1 transfer */ #define MAX_DATA_LEN \ @@ -49,6 +50,14 @@ #define DATA_END_BITS \ (DATA_SUCCESS_BITS | DATA_ERROR_BITS) +/* IRQs for busy wait phase */ +#define BUSY_SUCCESS_BITS \ + __reg_orm(SDMMC_STAR, BUSYD0END) +#define BUSY_ERROR_BITS \ + __reg_orm(SDMMC_STAR, DTIMEOUT) +#define BUSY_END_BITS \ + (BUSY_SUCCESS_BITS | DATA_ERROR_BITS) + static size_t get_sdmmc_bus_freq(uint32_t clock) { switch (clock) @@ -262,6 +271,13 @@ int stm32h7_sdmmc_submit_command(void *controller, break; } + /* For R1b responses we need to wait for the busy signal. */ + if (cmd->flags & SDMMC_RESP_BUSY) + { + maskr |= BUSY_END_BITS; + cmd_wait |= WAIT_BUSY; + } + /* * Handle commands with data transfers */ @@ -313,9 +329,14 @@ int stm32h7_sdmmc_submit_command(void *controller, { /* Disable data transfer */ reg_assignlf(ctl->regs, SDMMC_IDMACTRLR, IDMAEN(0)); - reg_varl(ctl->regs, SDMMC_DTIMER) = 0; reg_varl(ctl->regs, SDMMC_DLENR) = 0; reg_varl(ctl->regs, SDMMC_DCTRL) = 0; + + /* DTIMER is the wait time for the busy signal */ + if (cmd->flags & SDMMC_RESP_BUSY) + reg_varl(ctl->regs, SDMMC_DTIMER) = 1 * ctl->bus_freq; + else + reg_varl(ctl->regs, SDMMC_DTIMER) = 0; } /* @@ -419,7 +440,7 @@ void stm32h7_sdmmc_irq_handler(struct stm32h7_sdmmc_controller *ctl) * to ensure the correct interrupt is used to detect command * completion (CMDSENT or CMDREND). */ - star &= maskr; + star &= maskr | __reg_orm(SDMMC_STAR, BUSYD0); if (ctl->cmd_wait & WAIT_CMD) { @@ -478,6 +499,32 @@ void stm32h7_sdmmc_irq_handler(struct stm32h7_sdmmc_controller *ctl) } } + if (ctl->cmd_wait & WAIT_BUSY) + { + if (reg_vreadf(star, SDMMC_STAR, CMDREND) && + !reg_vreadf(star, SDMMC_STAR, BUSYD0)) + { + /* + * Skip waiting if the busy signal is not asserted by + * the card when the command ends; in this case we'll + * never receive a BUSYD0END interrupt because it is + * only raised on a transition from busy -> not busy. + */ + ctl->cmd_wait &= ~WAIT_BUSY; + } + else if ((star & BUSY_END_BITS) || ctl->cmd_error) + { + if (!ctl->cmd_error) + { + if (reg_vreadf(star, SDMMC_STAR, DTIMEOUT)) + ctl->cmd_error = SDMMC_STATUS_TIMEOUT; + } + + ctl->cmd_wait &= ~WAIT_BUSY; + icr |= BUSY_END_BITS; + } + } + /* * Each interrupt should complete at least one phase * but in error cases we can get spurious interrupts.