]> granicus.if.org Git - esp-idf/commitdiff
sdmmc: add support for high speed (HS) mode
authorIvan Grokhotkov <ivan@espressif.com>
Wed, 12 Jul 2017 13:17:28 +0000 (21:17 +0800)
committerIvan Grokhotkov <ivan@espressif.com>
Wed, 16 Aug 2017 10:59:04 +0000 (18:59 +0800)
By default SD cards are initialized in default speed (DS) mode. Enabling
HS mode requires SWITCH_FUNC command to be sent twice: first time to
query if the card supports switching to HS mode, second time to perform
the switch.

This change implements SWITCH_FUNC command and adds the procedure to
switch to HS mode.

components/driver/include/driver/sdmmc_defs.h
components/driver/include/driver/sdmmc_types.h
components/sdmmc/sdmmc_cmd.c

index 30df7e2fc051a3776829f8237c839d9846074a29..c13df6d1aaf429988415bdbe9df8d4bc292a7384 100644 (file)
 #define SCR_CMD_SUPPORT_CMD20(scr)      MMC_RSP_BITS((scr), 32, 1)
 #define SCR_RESERVED2(scr)              MMC_RSP_BITS((scr), 0, 32)
 
-/* Status of Switch Function */
-#define SFUNC_STATUS_GROUP(status, group) \
-        (__bitfield((uint32_t *)(status), 400 + (group - 1) * 16, 16))
-
-#define SD_ACCESS_MODE_SDR12    0
-#define SD_ACCESS_MODE_SDR25    1
-#define SD_ACCESS_MODE_SDR50    2
-#define SD_ACCESS_MODE_SDR104   3
-#define SD_ACCESS_MODE_DDR50    4
+/* Max supply current in SWITCH_FUNC response (in mA) */
+#define SD_SFUNC_I_MAX(status) (MMC_RSP_BITS((uint32_t *)(status), 496, 16))
+
+/* Supported flags in SWITCH_FUNC response */
+#define SD_SFUNC_SUPPORTED(status, group) \
+        (MMC_RSP_BITS((uint32_t *)(status), 400 + (group - 1) * 16, 16))
+
+/* Selected function in SWITCH_FUNC response */
+#define SD_SFUNC_SELECTED(status, group) \
+        (MMC_RSP_BITS((uint32_t *)(status), 376 + (group - 1) * 4, 4))
+
+/* Busy flags in SWITCH_FUNC response */
+#define SD_SFUNC_BUSY(status, group) \
+        (MMC_RSP_BITS((uint32_t *)(status), 272 + (group - 1) * 16, 16))
+
+/* Version of SWITCH_FUNC response */
+#define SD_SFUNC_VER(status)    (MMC_RSP_BITS((uint32_t *)(status), 368, 8))
+
+#define SD_SFUNC_GROUP_MAX      6
+#define SD_SFUNC_FUNC_MAX       15
+
+#define SD_ACCESS_MODE          1       /* Function group 1, Access Mode */
+
+#define SD_ACCESS_MODE_SDR12    0       /* 25 MHz clock */
+#define SD_ACCESS_MODE_SDR25    1       /* 50 MHz clock */
+#define SD_ACCESS_MODE_SDR50    2       /* UHS-I, 100 MHz clock */
+#define SD_ACCESS_MODE_SDR104   3       /* UHS-I, 208 MHz clock */
+#define SD_ACCESS_MODE_DDR50    4       /* UHS-I, 50 MHz clock, DDR */
 
 /**
  * @brief Extract up to 32 sequential bits from an array of 32-bit words
index d9ffde3ffd408816ca679f09af26f26b3f227d2c..4bc1e05e5105a5a007379c35e5b9027a61682aaf 100644 (file)
@@ -60,6 +60,13 @@ typedef struct {
  */
 typedef uint32_t sdmmc_response_t[4];
 
+/**
+ * SD SWITCH_FUNC response buffer
+ */
+typedef struct {
+    uint32_t data[512 / 8 / sizeof(uint32_t)];  /*!< response data */
+} sdmmc_switch_func_rsp_t;
+
 /**
  * SD/MMC command information
  */
index 3cefe5ad97c08a72cfe7f22e69f334313afe0cb8..87da936e164451102618a9e3fcffeda529e1a086 100644 (file)
@@ -41,6 +41,10 @@ static esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid);
 static esp_err_t sddmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_cid_t* out_cid);
 static esp_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca);
 static esp_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd);
+static esp_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card,
+        uint32_t mode, uint32_t group, uint32_t function,
+        sdmmc_switch_func_rsp_t* resp);
+static esp_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card);
 static esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd);
 static esp_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd);
 static esp_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card, uint32_t rca);
@@ -153,7 +157,7 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card)
 
     err = sdmmc_send_cmd_send_csd(card, &card->csd);
     if (err != ESP_OK) {
-        ESP_LOGE(TAG, "%s: send_csd returned 0x%x", __func__, err);
+        ESP_LOGE(TAG, "%s: send_csd (1) returned 0x%x", __func__, err);
         return err;
     }
     const size_t max_sdsc_capacity = UINT32_MAX / card->csd.sector_size + 1;
@@ -214,32 +218,87 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card)
             ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
         }
     }
-    if (config->max_freq_khz >= SDMMC_FREQ_HIGHSPEED &&
-        card->csd.tr_speed / 1000 >= SDMMC_FREQ_HIGHSPEED) {
-        ESP_LOGD(TAG, "switching to HS bus mode");
-        err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_HIGHSPEED);
-        if (err != ESP_OK) {
-            ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode");
+
+    /* So far initialization has been done using 400kHz clock. Determine the
+     * clock rate which both host and the card support, and switch to it.
+     */
+    bool freq_switched = false;
+    if (config->max_freq_khz >= SDMMC_FREQ_HIGHSPEED) {
+        /* This will determine if the card supports SWITCH_FUNC command,
+         * and high speed mode. If the cards supports both, this will enable
+         * high speed mode at the card side.
+         */
+        err = sdmmc_enable_hs_mode(card);
+        if (err == ESP_ERR_NOT_SUPPORTED) {
+            ESP_LOGD(TAG, "%s: host supports HS mode, but card doesn't", __func__);
+        } else if (err != ESP_OK) {
+            /* some other error */
             return err;
+        } else { /* ESP_OK */
+            /* HS mode has been enabled on the card.
+             * Read CSD again, it should now indicate that the card supports
+             * 50MHz clock.
+             * Since SEND_CSD is allowed only in standby mode, and the card is
+             * currently in data transfer more, deselect the card first, then
+             * get the CSD, then select the card again.
+             */
+            err = sdmmc_send_cmd_select_card(card, 0);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "%s: select_card (2) returned 0x%x", __func__, err);
+                return err;
+            }
+            err = sdmmc_send_cmd_send_csd(card, &card->csd);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "%s: send_csd (2) returned 0x%x", __func__, err);
+                return err;
+            }
+            err = sdmmc_send_cmd_select_card(card, card->rca);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "%s: select_card (3) returned 0x%x", __func__, err);
+                return err;
+            }
+
+            if (card->csd.tr_speed != 50000000) {
+                ESP_LOGW(TAG, "unexpected: after enabling HS mode, tr_speed=%d", card->csd.tr_speed);
+            } else {
+                /* Finally can switch the host to HS mode */
+                err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_HIGHSPEED);
+                if (err != ESP_OK) {
+                    ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode");
+                    return err;
+                }
+                freq_switched = true;
+            }
         }
-    } else if (config->max_freq_khz >= SDMMC_FREQ_DEFAULT &&
-        card->csd.tr_speed / 1000 >= SDMMC_FREQ_DEFAULT) {
+    }
+    /* All SD cards must support default speed mode (25MHz).
+     * config->max_freq_khz may be used to limit the clock frequency.
+     */
+    if (!freq_switched &&
+        config->max_freq_khz >= SDMMC_FREQ_DEFAULT) {
         ESP_LOGD(TAG, "switching to DS bus mode");
         err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_DEFAULT);
         if (err != ESP_OK) {
             ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode");
             return err;
         }
+        freq_switched = true;
     }
-    sdmmc_scr_t scr_tmp;
-    err = sdmmc_send_cmd_send_scr(card, &scr_tmp);
-    if (err != ESP_OK) {
-        ESP_LOGE(TAG, "%s: send_scr returned 0x%x", __func__, err);
-        return err;
-    }
-    if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) {
-        ESP_LOGE(TAG, "got corrupted data after increasing clock frequency");
-        return ESP_ERR_INVALID_RESPONSE;
+    /* If frequency switch has been performed, read SCR register one more time
+     * and compare the result with the previous one. Use this simple check as
+     * an indicator of potential signal integrity issues.
+     */
+    if (freq_switched) {
+        sdmmc_scr_t scr_tmp;
+        err = sdmmc_send_cmd_send_scr(card, &scr_tmp);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "%s: send_scr (2) returned 0x%x", __func__, err);
+            return err;
+        }
+        if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) {
+            ESP_LOGE(TAG, "got corrupted data after increasing clock frequency");
+            return ESP_ERR_INVALID_RESPONSE;
+        }
     }
     return ESP_OK;
 }
@@ -777,3 +836,92 @@ static esp_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst,
     }
     return ESP_OK;
 }
+
+static esp_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card,
+        uint32_t mode, uint32_t group, uint32_t function,
+        sdmmc_switch_func_rsp_t* resp)
+{
+    if (card->scr.sd_spec < SCR_SD_SPEC_VER_1_10 ||
+        ((card->csd.card_command_class & SD_CSD_CCC_SWITCH) == 0)) {
+            return ESP_ERR_NOT_SUPPORTED;
+    }
+
+    if (group == 0 ||
+        group > SD_SFUNC_GROUP_MAX ||
+        function > SD_SFUNC_FUNC_MAX) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (mode > 1) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    uint32_t group_shift = (group - 1) << 2;
+    /* all functions which should not be affected are set to 0xf (no change) */
+    uint32_t other_func_mask = (0x00ffffff & ~(0xf << group_shift));
+    uint32_t func_val = (function << group_shift) | other_func_mask;
+
+    sdmmc_command_t cmd = {
+            .opcode = MMC_SWITCH,
+            .flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1,
+            .blklen = sizeof(sdmmc_switch_func_rsp_t),
+            .data = resp->data,
+            .datalen = sizeof(sdmmc_switch_func_rsp_t),
+            .arg = (!!mode << 31) | func_val
+    };
+
+    esp_err_t err = sdmmc_send_cmd(card, &cmd);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
+        return err;
+    }
+    flip_byte_order(resp->data, sizeof(sdmmc_switch_func_rsp_t));
+    uint32_t resp_ver = SD_SFUNC_VER(resp->data);
+    if (resp_ver == 0) {
+        /* busy response is never sent */
+    } else if (resp_ver == 1) {
+        if (SD_SFUNC_BUSY(resp->data, group) & (1 << function)) {
+            ESP_LOGD(TAG, "%s: response indicates function %d:%d is busy",
+                    __func__, group, function);
+            return ESP_ERR_INVALID_STATE;
+        }
+    } else {
+        ESP_LOGD(TAG, "%s: got an invalid version of SWITCH_FUNC response: 0x%02x",
+                __func__, resp_ver);
+        return ESP_ERR_INVALID_RESPONSE;
+    }
+    return ESP_OK;
+}
+
+static esp_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card)
+{
+    if (card->scr.sd_spec < SCR_SD_SPEC_VER_1_10 ||
+        ((card->csd.card_command_class & SD_CSD_CCC_SWITCH) == 0)) {
+            return ESP_ERR_NOT_SUPPORTED;
+    }
+    sdmmc_switch_func_rsp_t* response = (sdmmc_switch_func_rsp_t*)
+            heap_caps_malloc(sizeof(*response), MALLOC_CAP_DMA);
+    if (response == NULL) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    esp_err_t err = sdmmc_send_cmd_switch_func(card, 0, SD_ACCESS_MODE, 0, response);
+    if (err != ESP_OK) {
+        ESP_LOGD(TAG, "%s: sdmmc_send_cmd_switch_func (1) returned 0x%x", __func__, err);
+        goto out;
+    }
+    uint32_t supported_mask = SD_SFUNC_SUPPORTED(response->data, 1);
+    if ((supported_mask & BIT(SD_ACCESS_MODE_SDR25)) == 0) {
+        err = ESP_ERR_NOT_SUPPORTED;
+        goto out;
+    }
+    err = sdmmc_send_cmd_switch_func(card, 1, SD_ACCESS_MODE, SD_ACCESS_MODE_SDR25, response);
+    if (err != ESP_OK) {
+        ESP_LOGD(TAG, "%s: sdmmc_send_cmd_switch_func (2) returned 0x%x", __func__, err);
+        goto out;
+    }
+
+out:
+    free(response);
+    return err;
+}