Skip to content

Commit 8f0e234

Browse files
committed
mimxrt/sdcard: Fix polling mode to use SDK blocking with scheduler yields.
The previous polling implementation tried to manually configure USDHC registers and poll hardware status flags, which conflicted with SDK internal state and caused intermittent EIO errors. This rewrites the polling transfer to use SDK's USDHC_TransferBlocking for correct register configuration and DMA handling. Scheduler yields are added in the wait-for-card-ready phase (polling Data0 line) to allow background tasks to run during SD card operations. The interrupt-based non-blocking mode is preserved but has cache coherency issues when SDRAM is disabled and GC heap is in OCRAM.
1 parent 95d1e92 commit 8f0e234

File tree

1 file changed

+31
-172
lines changed

1 file changed

+31
-172
lines changed

ports/mimxrt/sdcard.c

Lines changed: 31 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@
3737

3838
// Transfer mode configuration (MICROPY_HW_SDCARD_NONBLOCKING default in sdcard.h)
3939
// Mode 0: SDK blocking (USDHC_TransferBlocking) - simple, no scheduler yields
40-
// Mode 1: Interrupt-based non-blocking with callbacks (original, has cache issues)
41-
// Mode 2: Polling-based with scheduler yields (no interrupts)
40+
// Mode 1: Interrupt-based non-blocking with callbacks (has cache issues when SDRAM disabled)
41+
// Mode 2: Polling-based with scheduler yields (SDK blocking + wait-for-ready yields)
4242
#if MICROPY_HW_SDCARD_NONBLOCKING
4343
#ifndef MICROPY_HW_SDCARD_USE_DMA
4444
#define MICROPY_HW_SDCARD_USE_DMA (1)
4545
#endif
4646

47-
// Transfer status flags (Zephyr-style) - only used in interrupt mode
47+
// Transfer status flags - only used in interrupt-based non-blocking mode
4848
#define TRANSFER_CMD_COMPLETE (1U << 0)
4949
#define TRANSFER_CMD_FAILED (1U << 1)
5050
#define TRANSFER_DATA_COMPLETE (1U << 2)
@@ -54,8 +54,7 @@
5454
#define TRANSFER_DATA_FLAGS (TRANSFER_DATA_COMPLETE | TRANSFER_DATA_FAILED)
5555
#endif // MICROPY_HW_SDCARD_NONBLOCKING
5656

57-
// Polling mode: use DMA but poll status flags instead of using interrupts
58-
// This avoids interrupt-related cache coherency issues while still allowing scheduler yields
57+
// Polling mode: uses SDK blocking transfer but yields to scheduler while waiting for card ready
5958
#ifndef MICROPY_HW_SDCARD_POLLING
6059
#define MICROPY_HW_SDCARD_POLLING (1)
6160
#endif
@@ -305,7 +304,7 @@ void sdcard_dummy_callback(USDHC_Type *base, void *userData) {
305304

306305
#if MICROPY_HW_SDCARD_NONBLOCKING
307306

308-
// Transfer complete callback (Zephyr-style)
307+
// Transfer complete callback - only used in interrupt-based non-blocking mode
309308
static void sdcard_transfer_complete_callback(USDHC_Type *base,
310309
usdhc_handle_t *handle, status_t status, void *userData) {
311310
mimxrt_sdcard_obj_t *card = (mimxrt_sdcard_obj_t *)userData;
@@ -450,47 +449,30 @@ static status_t sdcard_transfer_blocking(USDHC_Type *base, usdhc_transfer_t *tra
450449

451450
#elif MICROPY_HW_SDCARD_POLLING
452451
// ============================================================================
453-
// Polling-based transfer mode: DMA with scheduler-friendly flag polling
454-
// This mode manually configures DMA and polls status flags (no interrupts).
455-
// Uses mp_event_handle_nowait() in polling loops to allow scheduler yields.
452+
// Polling-based transfer mode: Uses SDK's USDHC_TransferBlocking for correct
453+
// register setup and DMA handling, but yields to scheduler before data transfers
454+
// to allow background tasks to run. The actual DMA transfer uses tight polling
455+
// (fast), but we yield during the wait-for-ready phase.
456456
// ============================================================================
457457

458-
// Read command response from USDHC registers (mirrors SDK's internal function)
459-
static status_t sdcard_polling_receive_response(USDHC_Type *base, usdhc_command_t *command) {
460-
uint32_t response0 = base->CMD_RSP0;
461-
uint32_t response1 = base->CMD_RSP1;
462-
uint32_t response2 = base->CMD_RSP2;
463-
464-
if (command->responseType != kCARD_ResponseTypeNone) {
465-
command->response[0U] = response0;
466-
if (command->responseType == kCARD_ResponseTypeR2) {
467-
// R3-R2-R1-R0 format (CID/CSD response)
468-
command->response[0U] <<= 8U;
469-
command->response[1U] = (response1 << 8U) | ((response0 & 0xFF000000U) >> 24U);
470-
command->response[2U] = (response2 << 8U) | ((response1 & 0xFF000000U) >> 24U);
471-
command->response[3U] = (base->CMD_RSP3 << 8U) | ((response2 & 0xFF000000U) >> 24U);
472-
}
473-
}
474-
475-
// Check response error flags
476-
if ((command->responseErrorFlags != 0U) &&
477-
((command->responseType == kCARD_ResponseTypeR1) || (command->responseType == kCARD_ResponseTypeR1b) ||
478-
(command->responseType == kCARD_ResponseTypeR6) || (command->responseType == kCARD_ResponseTypeR5))) {
479-
if (((command->responseErrorFlags) & (command->response[0U])) != 0U) {
480-
return kStatus_USDHC_SendCommandFailed;
458+
// Wait for card to be ready by polling Data0 line.
459+
// Data0 low indicates card is busy. Yields to scheduler while waiting.
460+
static bool sdcard_polling_wait_data0_ready(USDHC_Type *base, uint32_t timeout_ms) {
461+
uint32_t start = mp_hal_ticks_ms();
462+
while ((mp_hal_ticks_ms() - start) < timeout_ms) {
463+
if ((USDHC_GetPresentStatusFlags(base) & (uint32_t)kUSDHC_Data0LineLevelFlag) != 0) {
464+
return true;
481465
}
466+
mp_event_handle_nowait();
482467
}
483-
484-
return kStatus_Success;
468+
return false;
485469
}
486470

487-
// Polling-based transfer with scheduler yields
471+
// Scheduler-friendly blocking transfer.
472+
// Uses SDK's USDHC_TransferBlocking which handles all the complex register setup,
473+
// but waits for card ready state with scheduler yields beforehand.
488474
static status_t sdcard_transfer_blocking(USDHC_Type *base, usdhc_transfer_t *transfer, uint32_t timeout_ms) {
489-
usdhc_command_t *command = transfer->command;
490475
usdhc_data_t *data = transfer->data;
491-
status_t error = kStatus_Success;
492-
uint32_t interruptStatus = 0U;
493-
uint32_t start_ms;
494476

495477
usdhc_adma_config_t dma_config = {
496478
.dmaMode = kUSDHC_DmaModeAdma2,
@@ -501,144 +483,21 @@ static status_t sdcard_transfer_blocking(USDHC_Type *base, usdhc_transfer_t *tra
501483
.admaTableWords = DMA_DESCRIPTOR_BUFFER_SIZE,
502484
};
503485

504-
// Check for re-tuning request
505-
#if defined(FSL_FEATURE_USDHC_HAS_SDR50_MODE) && (FSL_FEATURE_USDHC_HAS_SDR50_MODE)
506-
if ((USDHC_GetInterruptStatusFlags(base) & (uint32_t)kUSDHC_ReTuningEventFlag) != 0UL) {
507-
USDHC_ClearInterruptStatusFlags(base, kUSDHC_ReTuningEventFlag);
508-
return kStatus_USDHC_ReTuningRequest;
509-
}
510-
#endif
511-
512-
// Configure DMA for data transfers
513-
if (data != NULL) {
514-
// Check data inhibit flag before configuring data transfer
515-
if ((USDHC_GetPresentStatusFlags(base) & (uint32_t)kUSDHC_DataInhibitFlag) != 0U) {
516-
return kStatus_USDHC_BusyTransferring;
517-
}
518-
519-
error = USDHC_SetAdmaTableConfig(base, &dma_config, data, kUSDHC_AdmaDescriptorSingleFlag);
520-
if (error != kStatus_Success) {
521-
return kStatus_USDHC_PrepareAdmaDescriptorFailed;
522-
}
523-
524-
// Configure data transfer parameters
525-
// Note: USDHC_SetDataConfig doesn't set all required flags, so we configure MIX_CTRL directly
526-
usdhc_transfer_direction_t direction = (data->txData != NULL) ?
527-
kUSDHC_TransferDirectionSend : kUSDHC_TransferDirectionReceive;
528-
529-
// Set block attributes
530-
base->BLK_ATT = ((base->BLK_ATT & ~(USDHC_BLK_ATT_BLKSIZE_MASK | USDHC_BLK_ATT_BLKCNT_MASK)) |
531-
(USDHC_BLK_ATT_BLKSIZE(data->blockSize) | USDHC_BLK_ATT_BLKCNT(data->blockCount)));
532-
533-
// Configure MIX_CTRL for data transfer
534-
uint32_t mixCtrl = base->MIX_CTRL;
535-
mixCtrl &= ~(USDHC_MIX_CTRL_MSBSEL_MASK | USDHC_MIX_CTRL_BCEN_MASK | USDHC_MIX_CTRL_DTDSEL_MASK |
536-
USDHC_MIX_CTRL_AC12EN_MASK | USDHC_MIX_CTRL_AC23EN_MASK);
537-
538-
// Set direction
539-
mixCtrl |= USDHC_MIX_CTRL_DTDSEL(direction);
540-
541-
// Multi-block transfers need MSBSEL and BCEN
542-
if (data->blockCount > 1U) {
543-
mixCtrl |= USDHC_MIX_CTRL_MSBSEL_MASK | USDHC_MIX_CTRL_BCEN_MASK;
544-
if (data->enableAutoCommand12) {
545-
mixCtrl |= USDHC_MIX_CTRL_AC12EN_MASK;
546-
}
547-
}
548-
549-
// Auto CMD23 setup
550-
if (data->enableAutoCommand23) {
551-
mixCtrl |= USDHC_MIX_CTRL_AC23EN_MASK;
552-
base->VEND_SPEC2 |= USDHC_VEND_SPEC2_ACMD23_ARGU2_EN_MASK;
553-
base->DS_ADDR = data->blockCount;
554-
} else {
555-
base->VEND_SPEC2 &= ~USDHC_VEND_SPEC2_ACMD23_ARGU2_EN_MASK;
556-
}
557-
558-
base->MIX_CTRL = mixCtrl;
559-
560-
// Enable DMA
561-
USDHC_EnableInternalDMA(base, true);
562-
563-
// Set data present flag for command
564-
command->flags |= (uint32_t)kUSDHC_DataPresentFlag;
565-
} else {
566-
// Command-only: check command inhibit flag
567-
if ((USDHC_GetPresentStatusFlags(base) & (uint32_t)kUSDHC_CommandInhibitFlag) != 0U) {
568-
return kStatus_USDHC_BusyTransferring;
569-
}
570-
571-
// Clear data flags for command-only transfers
572-
uint32_t mixCtrl = base->MIX_CTRL;
573-
mixCtrl &= ~(USDHC_MIX_CTRL_MSBSEL_MASK | USDHC_MIX_CTRL_BCEN_MASK | USDHC_MIX_CTRL_DTDSEL_MASK |
574-
USDHC_MIX_CTRL_AC12EN_MASK | USDHC_MIX_CTRL_AC23EN_MASK);
575-
base->MIX_CTRL = mixCtrl;
576-
}
577-
578-
// Clear any pending status flags before starting
579-
USDHC_ClearInterruptStatusFlags(base, kUSDHC_CommandFlag | kUSDHC_DataDMAFlag);
580-
581-
// Send command
582-
USDHC_SendCommand(base, command);
583-
584-
// Poll for command completion with scheduler yields
585-
start_ms = mp_hal_ticks_ms();
586-
while (!(interruptStatus & kUSDHC_CommandFlag)) {
587-
interruptStatus = USDHC_GetInterruptStatusFlags(base);
588-
if ((mp_hal_ticks_ms() - start_ms) >= timeout_ms) {
589-
sdcard_error_recovery(base);
590-
return kStatus_Timeout;
591-
}
592-
mp_event_handle_nowait(); // Allow scheduler to run
593-
}
486+
// For data transfers, wait for card to be ready (yields to scheduler)
487+
bool has_data = (data != NULL) && ((data->txData != NULL) || (data->rxData != NULL));
594488

595-
// Check for command errors
596-
if ((interruptStatus & (uint32_t)kUSDHC_CommandErrorFlag) != 0UL) {
597-
USDHC_ClearInterruptStatusFlags(base, kUSDHC_CommandFlag);
598-
sdcard_error_recovery(base);
599-
return kStatus_USDHC_SendCommandFailed;
489+
if (has_data && !sdcard_polling_wait_data0_ready(base, timeout_ms)) {
490+
return kStatus_Timeout;
600491
}
601492

602-
// Read command response
603-
error = sdcard_polling_receive_response(base, command);
604-
USDHC_ClearInterruptStatusFlags(base, kUSDHC_CommandFlag);
493+
// Use SDK blocking transfer - handles complex register setup correctly
494+
// and polls hardware status flags directly (no interrupts/callbacks)
495+
status_t error = USDHC_TransferBlocking(base, &dma_config, transfer);
605496

606497
if (error != kStatus_Success) {
607498
sdcard_error_recovery(base);
608-
return kStatus_USDHC_SendCommandFailed;
609499
}
610-
611-
// Poll for data DMA completion with scheduler yields
612-
if (data != NULL) {
613-
interruptStatus = 0U;
614-
start_ms = mp_hal_ticks_ms();
615-
while (!(interruptStatus & (kUSDHC_DataDMAFlag | kUSDHC_TuningErrorFlag))) {
616-
interruptStatus = USDHC_GetInterruptStatusFlags(base);
617-
if ((mp_hal_ticks_ms() - start_ms) >= timeout_ms) {
618-
sdcard_error_recovery(base);
619-
return kStatus_Timeout;
620-
}
621-
mp_event_handle_nowait(); // Allow scheduler to run
622-
}
623-
624-
#if defined(FSL_FEATURE_USDHC_HAS_SDR50_MODE) && (FSL_FEATURE_USDHC_HAS_SDR50_MODE)
625-
if (interruptStatus & kUSDHC_TuningErrorFlag) {
626-
USDHC_ClearInterruptStatusFlags(base, kUSDHC_TuningErrorFlag);
627-
sdcard_error_recovery(base);
628-
return kStatus_USDHC_TuningError;
629-
}
630-
#endif
631-
632-
if (interruptStatus & ((uint32_t)kUSDHC_DataErrorFlag | (uint32_t)kUSDHC_DmaErrorFlag)) {
633-
USDHC_ClearInterruptStatusFlags(base, kUSDHC_DataDMAFlag | kUSDHC_TuningErrorFlag);
634-
sdcard_error_recovery(base);
635-
return kStatus_USDHC_TransferDataFailed;
636-
}
637-
638-
USDHC_ClearInterruptStatusFlags(base, kUSDHC_DataDMAFlag | kUSDHC_TuningErrorFlag);
639-
}
640-
641-
return kStatus_Success;
500+
return error;
642501
}
643502

644503
#else // !MICROPY_HW_SDCARD_NONBLOCKING && !MICROPY_HW_SDCARD_POLLING
@@ -1201,14 +1060,14 @@ void sdcard_init(mimxrt_sdcard_obj_t *card, uint32_t base_clk) {
12011060
#if MICROPY_HW_SDCARD_NONBLOCKING
12021061
.TransferComplete = sdcard_transfer_complete_callback,
12031062
#else
1204-
.TransferComplete = NULL, // Not used - we use blocking/polling transfers
1063+
.TransferComplete = NULL, // Not used - polling/blocking modes don't use callbacks
12051064
#endif
12061065
.ReTuning = sdcard_dummy_callback,
12071066
};
12081067

12091068
#if MICROPY_HW_SDCARD_NONBLOCKING
12101069
// Enable USDHC interrupt for interrupt-based non-blocking transfers
1211-
IRQn_Type irqn;
1070+
IRQn_Type irqn = 0;
12121071
#if defined MICROPY_USDHC1 && USDHC1_AVAIL
12131072
if (card->usdhc_inst == USDHC1) {
12141073
irqn = USDHC1_IRQn;

0 commit comments

Comments
 (0)