diff --git a/components/driver/sdmmc/include/driver/sdmmc_host.h b/components/driver/sdmmc/include/driver/sdmmc_host.h index 1a4beb892a..46b6f6af36 100644 --- a/components/driver/sdmmc/include/driver/sdmmc_host.h +++ b/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 * diff --git a/components/driver/sdmmc/include/driver/sdmmc_types.h b/components/driver/sdmmc/include/driver/sdmmc_types.h index 8a38d792e3..bc74a38c1d 100644 --- a/components/driver/sdmmc/include/driver/sdmmc_types.h +++ b/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 */ diff --git a/components/driver/sdmmc/sdmmc_host.c b/components/driver/sdmmc/sdmmc_host.c index 4c7fc7c4b2..af1dba30df 100644 --- a/components/driver/sdmmc/sdmmc_host.c +++ b/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; diff --git a/components/driver/spi/include/driver/sdspi_host.h b/components/driver/spi/include/driver/sdspi_host.h index 3b127fbfef..146cff69cd 100644 --- a/components/driver/spi/include/driver/sdspi_host.h +++ b/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, \ diff --git a/components/sdmmc/sdmmc_cmd.c b/components/sdmmc/sdmmc_cmd.c index 1ebf50e386..450c6e6c8a 100644 --- a/components/sdmmc/sdmmc_cmd.c +++ b/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)