sdmmc: handle card removal when CD is not used

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.
This commit is contained in:
Ivan Grokhotkov 2017-04-19 12:50:51 +08:00
parent f73c6f875c
commit 49848eaed5
2 changed files with 37 additions and 9 deletions

View file

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

View file

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