]> granicus.if.org Git - esp-idf/commitdiff
sdmmc: handle card removal when CD is not used
authorIvan Grokhotkov <ivan@espressif.com>
Wed, 19 Apr 2017 04:50:51 +0000 (12:50 +0800)
committerIvan Grokhotkov <ivan@espressif.com>
Wed, 19 Apr 2017 04:50:51 +0000 (12:50 +0800)
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
components/driver/sdmmc_transaction.c

index 706a1e3fefed6740ab2260daf485403e4d7f9564..2ef7b2e0cf2d2ea1acaa5ae0c5c67a09675332fc 100644 (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;
index 59b439eec91c0eef97cf06e923679ef260524557..92876f75a2825dd3754a88440778659c908401e2 100644 (file)
  */
 #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: