瀏覽代碼

sdmmc: keep clock enabled for the duration of ACMD41

SD specification requires that card clock is not disabled until the
card is idle, following ACMD41 command.
Ivan Grokhotkov 3 年之前
父節點
當前提交
7524f40b21

+ 14 - 0
components/driver/sdmmc/include/driver/sdmmc_host.h

@@ -40,6 +40,7 @@ extern "C" {
     .get_bus_width = &sdmmc_host_get_slot_width, \
     .set_bus_ddr_mode = &sdmmc_host_set_bus_ddr_mode, \
     .set_card_clk = &sdmmc_host_set_card_clk, \
+    .set_cclk_always_on = &sdmmc_host_set_cclk_always_on, \
     .do_transaction = &sdmmc_host_do_transaction, \
     .deinit = &sdmmc_host_deinit, \
     .io_int_enable = sdmmc_host_io_int_enable, \
@@ -204,6 +205,19 @@ esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz);
  */
 esp_err_t sdmmc_host_set_bus_ddr_mode(int slot, bool ddr_enabled);
 
+/**
+ * @brief Enable or disable always-on card clock
+ * When cclk_always_on is false, the host controller is allowed to shut down
+ * the card clock between the commands. When cclk_always_on is true, the clock
+ * is generated even if no command is in progress.
+ * @param slot  slot number
+ * @param cclk_always_on  enable or disable always-on clock
+ * @return
+ *      - ESP_OK on success
+ *      - ESP_ERR_INVALID_ARG if the slot number is invalid
+ */
+esp_err_t sdmmc_host_set_cclk_always_on(int slot, bool cclk_always_on);
+
 /**
  * @brief Send command to the card and get response
  *

+ 1 - 0
components/driver/sdmmc/include/driver/sdmmc_types.h

@@ -175,6 +175,7 @@ typedef struct {
     size_t (*get_bus_width)(int slot); /*!< host function to get bus width */
     esp_err_t (*set_bus_ddr_mode)(int slot, bool ddr_enable); /*!< host function to set DDR mode */
     esp_err_t (*set_card_clk)(int slot, uint32_t freq_khz); /*!< host function to set card clock frequency */
+    esp_err_t (*set_cclk_always_on)(int slot, bool cclk_always_on);     /*!< host function to set whether the clock is always enabled */
     esp_err_t (*do_transaction)(int slot, sdmmc_command_t* cmdinfo);    /*!< host function to do a transaction */
     union {
         esp_err_t (*deinit)(void);  /*!< host function to deinitialize the driver */

+ 14 - 0
components/driver/sdmmc/sdmmc_host.c

@@ -609,6 +609,20 @@ esp_err_t sdmmc_host_set_bus_ddr_mode(int slot, bool ddr_enabled)
     return ESP_OK;
 }
 
+esp_err_t sdmmc_host_set_cclk_always_on(int slot, bool cclk_always_on)
+{
+    if (!(slot == 0 || slot == 1)) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    if (cclk_always_on) {
+        SDMMC.clkena.cclk_low_power &= ~BIT(slot);
+    } else {
+        SDMMC.clkena.cclk_low_power |= BIT(slot);
+    }
+    sdmmc_host_clock_update_command(slot);
+    return ESP_OK;
+}
+
 static void sdmmc_host_dma_init(void)
 {
     SDMMC.ctrl.dma_enable = 1;

+ 1 - 0
components/driver/spi/include/driver/sdspi_host.h

@@ -45,6 +45,7 @@ typedef int sdspi_dev_handle_t;
     .get_bus_width = NULL, \
     .set_bus_ddr_mode = NULL, \
     .set_card_clk = &sdspi_host_set_card_clk, \
+    .set_cclk_always_on = NULL, \
     .do_transaction = &sdspi_host_do_transaction, \
     .deinit_p = &sdspi_host_remove_device, \
     .io_int_enable = &sdspi_host_io_int_enable, \

+ 32 - 3
components/sdmmc/sdmmc_cmd.c

@@ -107,6 +107,19 @@ esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t
 {
     esp_err_t err;
 
+    /* If the host supports this, keep card clock enabled
+     * from the start of ACMD41 until the card is idle.
+     * (Ref. SD spec, section 4.4 "Clock control".)
+     */
+    if (card->host.set_cclk_always_on != NULL) {
+        err = card->host.set_cclk_always_on(card->host.slot, true);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "%s: set_cclk_always_on (1) err=0x%x", __func__, err);
+            return err;
+        }
+        ESP_LOGV(TAG, "%s: keeping clock on during ACMD41", __func__);
+    }
+
     sdmmc_command_t cmd = {
             .arg = ocr,
             .flags = SCF_CMD_BCR | SCF_RSP_R3,
@@ -131,7 +144,7 @@ esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t
         if (err != ESP_OK) {
             if (--err_cnt == 0) {
                 ESP_LOGD(TAG, "%s: sdmmc_send_app_cmd err=0x%x", __func__, err);
-                return err;
+                goto done;
             } else {
                 ESP_LOGV(TAG, "%s: ignoring err=0x%x", __func__, err);
                 continue;
@@ -151,13 +164,29 @@ esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t
         }
         vTaskDelay(10 / portTICK_PERIOD_MS);
     }
+
     if (nretries == 0) {
-        return ESP_ERR_TIMEOUT;
+        err = ESP_ERR_TIMEOUT;
+        goto done;
     }
+
     if (ocrp) {
         *ocrp = MMC_R3(cmd.response);
     }
-    return ESP_OK;
+
+    err = ESP_OK;
+done:
+
+    if (card->host.set_cclk_always_on != NULL) {
+        esp_err_t err_cclk_dis = card->host.set_cclk_always_on(card->host.slot, false);
+        if (err_cclk_dis != ESP_OK) {
+            ESP_LOGE(TAG, "%s: set_cclk_always_on (2) err=0x%x", __func__, err);
+            /* If we failed to disable clock, don't overwrite 'err' to return the original error */
+        }
+        ESP_LOGV(TAG, "%s: clock always-on mode disabled", __func__);
+    }
+
+    return err;
 }
 
 esp_err_t sdmmc_send_cmd_read_ocr(sdmmc_card_t *card, uint32_t *ocrp)