From 49848eaed52c003a4f19fd1cc750c2b5c886ee7a Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 19 Apr 2017 12:50:51 +0800 Subject: [PATCH] sdmmc: handle card removal when CD is not used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When SD card is removed during transaction, SDMMC peripheral can report a range of errors, such as timeouts, CRC errors, start/end bit errors. Under normal conditions (card is inserted), SDMMC peripheral also generates command done or data done interrupts. When the card is removed, such interrupts may not be always generated. This change fixes handling of timeout interrupts and SBE interrupts. It also adds a one second timeout into the event processing loop. This timeout allows applications to recover in cases when the SDMMC peripheral doesn’t generate command/data done event on card removal. --- components/driver/sdmmc_host.c | 14 +++++++++--- components/driver/sdmmc_transaction.c | 32 ++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/components/driver/sdmmc_host.c b/components/driver/sdmmc_host.c index 706a1e3fe..2ef7b2e0c 100644 --- a/components/driver/sdmmc_host.c +++ b/components/driver/sdmmc_host.c @@ -202,6 +202,17 @@ esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz) SDMMC.clkena.cclk_enable |= BIT(slot); SDMMC.clkena.cclk_low_power |= BIT(slot); sdmmc_host_clock_update_command(slot); + + // set data timeout + const uint32_t data_timeout_ms = 100; + uint32_t data_timeout_cycles = data_timeout_ms * freq_khz; + const uint32_t data_timeout_cycles_max = 0xffffff; + if (data_timeout_cycles > data_timeout_cycles_max) { + data_timeout_cycles = data_timeout_cycles_max; + } + SDMMC.tmout.data = data_timeout_cycles; + // always set response timeout to highest value, it's small enough anyway + SDMMC.tmout.response = 255; return ESP_OK; } @@ -419,9 +430,6 @@ void sdmmc_host_dma_stop() void sdmmc_host_dma_prepare(sdmmc_desc_t* desc, size_t block_size, size_t data_size) { - // TODO: set timeout depending on data size - SDMMC.tmout.val = 0xffffffff; - // Set size of data and DMA descriptor pointer SDMMC.bytcnt = data_size; SDMMC.blksiz = block_size; diff --git a/components/driver/sdmmc_transaction.c b/components/driver/sdmmc_transaction.c index 59b439eec..92876f75a 100644 --- a/components/driver/sdmmc_transaction.c +++ b/components/driver/sdmmc_transaction.c @@ -32,6 +32,13 @@ */ #define SDMMC_DMA_DESC_CNT 4 +/* Max delay value is mostly useful for cases when CD pin is not used, and + * the card is removed. In this case, SDMMC peripheral may not always return + * CMD_DONE / DATA_DONE interrupts after signaling the error. This delay works + * as a safety net in such cases. + */ +#define SDMMC_MAX_EVT_WAIT_DELAY_MS 1000 + static const char* TAG = "sdmmc_req"; typedef enum { @@ -188,9 +195,12 @@ static esp_err_t handle_idle_state_events() static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* state) { sdmmc_event_t evt; - esp_err_t err = sdmmc_host_wait_for_event(portMAX_DELAY, &evt); + esp_err_t err = sdmmc_host_wait_for_event(SDMMC_MAX_EVT_WAIT_DELAY_MS / portTICK_PERIOD_MS, &evt); if (err != ESP_OK) { - ESP_LOGE(TAG, "sdmmc_host_wait_for_event returned %d", err); + ESP_LOGE(TAG, "sdmmc_host_wait_for_event returned 0x%x", err); + if (err == ESP_ERR_TIMEOUT) { + sdmmc_host_dma_stop(); + } return err; } ESP_LOGV(TAG, "sdmmc_handle_event: evt %08x %08x", evt.sdmmc_status, evt.dma_status); @@ -268,7 +278,7 @@ static void process_command_response(uint32_t status, sdmmc_command_t* cmd) if (cmd->data) { sdmmc_host_dma_stop(); } - ESP_LOGD(TAG, "%s: error %d", __func__, cmd->error); + ESP_LOGD(TAG, "%s: error 0x%x (status=%08x)", __func__, cmd->error, status); } } @@ -291,7 +301,7 @@ static void process_data_status(uint32_t status, sdmmc_command_t* cmd) if (cmd->data) { sdmmc_host_dma_stop(); } - ESP_LOGD(TAG, "%s: error %d", __func__, cmd->error); + ESP_LOGD(TAG, "%s: error 0x%x (status=%08x)", __func__, cmd->error, status); } } @@ -323,9 +333,14 @@ static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_r case SDMMC_SENDING_CMD: if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_CMD_ERR_MASK)) { process_command_response(orig_evt.sdmmc_status, cmd); - break; + if (cmd->error != ESP_ERR_TIMEOUT) { + // Unless this is a timeout error, we need to wait for the + // CMD_DONE interrupt + break; + } } - if (!mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_CMD_DONE)) { + if (!mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_CMD_DONE) && + cmd->error != ESP_ERR_TIMEOUT) { break; } process_command_response(orig_evt.sdmmc_status, cmd); @@ -352,6 +367,11 @@ static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_r next_state = SDMMC_BUSY; } } + if (orig_evt.sdmmc_status & (SDMMC_INTMASK_SBE | SDMMC_INTMASK_DATA_OVER)) { + // On start bit error, DATA_DONE interrupt will not be generated + next_state = SDMMC_IDLE; + break; + } break; case SDMMC_BUSY: