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
This commit is contained in:
Aidan MacDonald 2026-02-09 14:01:46 +00:00
parent c390467d19
commit 06c9d87f53

View file

@ -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.