diff --git a/components/driver/include/driver/sdmmc_defs.h b/components/driver/include/driver/sdmmc_defs.h index 9b9c5b8023..04ba136d21 100644 --- a/components/driver/include/driver/sdmmc_defs.h +++ b/components/driver/include/driver/sdmmc_defs.h @@ -372,6 +372,9 @@ /* SSR (SD Status Register) */ #define SSR_DAT_BUS_WIDTH(ssr) MMC_RSP_BITS((ssr), 510, 2) #define SSR_AU_SIZE(ssr) MMC_RSP_BITS((ssr), 428, 4) +#define SSR_ERASE_SIZE(ssr) MMC_RSP_BITS((ssr), 408, 16) +#define SSR_ERASE_TIMEOUT(ssr) MMC_RSP_BITS((ssr), 402, 6) +#define SSR_ERASE_OFFSET(ssr) MMC_RSP_BITS((ssr), 400, 2) #define SSR_DISCARD_SUPPORT(ssr) MMC_RSP_BITS((ssr), 313, 1) #define SSR_FULE_SUPPORT(ssr) MMC_RSP_BITS((ssr), 312, 1) diff --git a/components/driver/include/driver/sdmmc_types.h b/components/driver/include/driver/sdmmc_types.h index 58ccfd4468..374790ef4a 100644 --- a/components/driver/include/driver/sdmmc_types.h +++ b/components/driver/include/driver/sdmmc_types.h @@ -71,10 +71,14 @@ typedef struct { * Note: When new member is added, update reserved bits accordingly */ typedef struct { + uint32_t alloc_unit_kb: 16; /*!< Allocation unit of the card, in multiples of kB (1024 bytes) */ + uint32_t erase_size_au: 16; /*!< Erase size for the purpose of timeout calculation, in multiples of allocation unit */ uint32_t cur_bus_width: 2; /*!< SD current bus width */ uint32_t discard_support: 1; /*!< SD discard feature support */ uint32_t fule_support: 1; /*!< SD FULE (Full User Area Logical Erase) feature support */ - uint32_t reserved: 28; /*!< reserved for future expansion */ + uint32_t erase_timeout: 6; /*!< Timeout (in seconds) for erase of a single allocation unit */ + uint32_t erase_offset: 2; /*!< Constant timeout offset (in seconds) for any erase operation */ + uint32_t reserved: 20; /*!< reserved for future expansion */ } sdmmc_ssr_t; /** diff --git a/components/sdmmc/sdmmc_cmd.c b/components/sdmmc/sdmmc_cmd.c index 9b08c3751b..139d0ed0cd 100644 --- a/components/sdmmc/sdmmc_cmd.c +++ b/components/sdmmc/sdmmc_cmd.c @@ -572,7 +572,7 @@ esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector, 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); + ESP_LOGE(TAG, "%s: sdmmc_send_cmd (ERASE_GROUP_START) returned 0x%x", __func__, err); return err; } @@ -582,7 +582,7 @@ esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector, err = sdmmc_send_cmd(card, &cmd); if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + ESP_LOGE(TAG, "%s: sdmmc_send_cmd (ERASE_GROUP_END) returned 0x%x", __func__, err); return err; } @@ -591,13 +591,11 @@ esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector, cmd.flags = SCF_CMD_AC | SCF_RSP_R1B | SCF_WAIT_BUSY; cmd.opcode = MMC_ERASE; cmd.arg = cmd38_arg; - // TODO: best way, application to compute timeout value. For this card - // structure should have a place holder for erase_timeout. - cmd.timeout_ms = (SDMMC_ERASE_BLOCK_TIMEOUT_MS + sector_count); + cmd.timeout_ms = sdmmc_get_erase_timeout_ms(card, cmd38_arg, sector_count * card->csd.sector_size / 1024); err = sdmmc_send_cmd(card, &cmd); if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + ESP_LOGE(TAG, "%s: sdmmc_send_cmd (ERASE) returned 0x%x", __func__, err); return err; } @@ -684,9 +682,10 @@ esp_err_t sdmmc_full_erase(sdmmc_card_t* card) if (card->is_mmc) { arg = sdmmc_mmc_can_sanitize(card) == ESP_OK ? SDMMC_MMC_DISCARD_ARG: SDMMC_MMC_TRIM_ARG; } - err = sdmmc_erase_sectors(card, 0, card->csd.capacity, arg); + err = sdmmc_erase_sectors(card, 0, card->csd.capacity, arg); if ((err == ESP_OK) && (arg == SDMMC_MMC_DISCARD_ARG)) { - return sdmmc_mmc_sanitize(card, SDMMC_ERASE_BLOCK_TIMEOUT_MS + card->csd.capacity); + uint32_t timeout_ms = sdmmc_get_erase_timeout_ms(card, SDMMC_MMC_DISCARD_ARG, card->csd.capacity * ((uint64_t) card->csd.sector_size) / 1024); + return sdmmc_mmc_sanitize(card, timeout_ms); } return err; } diff --git a/components/sdmmc/sdmmc_common.c b/components/sdmmc/sdmmc_common.c index 0d04ddbb26..341a0f4a9c 100644 --- a/components/sdmmc/sdmmc_common.c +++ b/components/sdmmc/sdmmc_common.c @@ -318,3 +318,12 @@ esp_err_t sdmmc_fix_host_flags(sdmmc_card_t* card) } return ESP_OK; } + +uint32_t sdmmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb) +{ + if (card->is_mmc) { + return sdmmc_mmc_get_erase_timeout_ms(card, arg, erase_size_kb); + } else { + return sdmmc_sd_get_erase_timeout_ms(card, arg, erase_size_kb); + } +} diff --git a/components/sdmmc/sdmmc_common.h b/components/sdmmc/sdmmc_common.h index a6d7dbbf29..759151e14c 100644 --- a/components/sdmmc/sdmmc_common.h +++ b/components/sdmmc/sdmmc_common.h @@ -37,7 +37,9 @@ */ #define SDMMC_DEFAULT_CMD_TIMEOUT_MS 1000 // Max timeout of ordinary commands #define SDMMC_WRITE_CMD_TIMEOUT_MS 5000 // Max timeout of write commands -#define SDMMC_ERASE_BLOCK_TIMEOUT_MS 500 // Max timeout of erase per block + + +#define SDMMC_SD_DISCARD_TIMEOUT 250 // SD erase (discard) timeout /* Maximum retry/error count for SEND_OP_COND (CMD1). * These are somewhat arbitrary, values originate from OpenBSD driver. @@ -79,6 +81,7 @@ esp_err_t sdmmc_write_sectors_dma(sdmmc_card_t* card, const void* src, size_t start_block, size_t block_count); esp_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst, size_t start_block, size_t block_count); +uint32_t sdmmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb); /* SD specific */ esp_err_t sdmmc_check_scr(sdmmc_card_t* card); @@ -86,6 +89,7 @@ esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid); esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd); esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr); esp_err_t sdmmc_decode_ssr(uint32_t *raw_ssr, sdmmc_ssr_t* out_ssr); +uint32_t sdmmc_sd_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb); /* SDIO specific */ esp_err_t sdmmc_io_reset(sdmmc_card_t* card); @@ -103,6 +107,7 @@ esp_err_t sdmmc_mmc_switch(sdmmc_card_t* card, uint8_t set, uint8_t index, uint8 esp_err_t sdmmc_mmc_decode_cid(int mmc_ver, sdmmc_response_t resp, sdmmc_cid_t* out_cid); esp_err_t sdmmc_mmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd); esp_err_t sdmmc_mmc_enable_hs_mode(sdmmc_card_t* card); +uint32_t sdmmc_mmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb); /* Parts of card initialization flow */ esp_err_t sdmmc_init_sd_if_cond(sdmmc_card_t* card); diff --git a/components/sdmmc/sdmmc_mmc.c b/components/sdmmc/sdmmc_mmc.c index 82c978aea6..4f726ea90a 100644 --- a/components/sdmmc/sdmmc_mmc.c +++ b/components/sdmmc/sdmmc_mmc.c @@ -288,3 +288,13 @@ out: free(ext_csd); return err; } + +uint32_t sdmmc_mmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb) +{ + /* TODO: calculate erase timeout based on ext_csd (trim_timeout) */ + uint32_t timeout_ms = SDMMC_SD_DISCARD_TIMEOUT * erase_size_kb / card->csd.sector_size; + timeout_ms = MAX(1000, timeout_ms); + ESP_LOGD(TAG, "%s: erase timeout %u s (erasing %u kB, %ums per sector)", + __func__, timeout_ms / 1000, erase_size_kb, SDMMC_SD_DISCARD_TIMEOUT); + return timeout_ms; +} diff --git a/components/sdmmc/sdmmc_sd.c b/components/sdmmc/sdmmc_sd.c index 84147fc74d..15bc866357 100644 --- a/components/sdmmc/sdmmc_sd.c +++ b/components/sdmmc/sdmmc_sd.c @@ -387,6 +387,16 @@ esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr) return ESP_OK; } +static const uint32_t s_au_to_size_kb[] = { + 0, 16, 32, 64, + 128, 256, 512, 1024, + 2 * 1024, 4 * 1024, + 8 * 1024, 12 * 1024, + 16 * 1024, 24 * 1024, + 32 * 1024, 64 * 1024 +}; +_Static_assert(sizeof(s_au_to_size_kb)/sizeof(s_au_to_size_kb[0]) == 16, "invalid number of elements in s_au_to_size_kb"); + esp_err_t sdmmc_decode_ssr(uint32_t *raw_ssr, sdmmc_ssr_t* out_ssr) { uint32_t ssr[(SD_SSR_SIZE/sizeof(uint32_t))] = { 0 }; @@ -399,6 +409,41 @@ esp_err_t sdmmc_decode_ssr(uint32_t *raw_ssr, sdmmc_ssr_t* out_ssr) out_ssr->cur_bus_width = SSR_DAT_BUS_WIDTH(ssr); out_ssr->discard_support = SSR_DISCARD_SUPPORT(ssr); out_ssr->fule_support = SSR_FULE_SUPPORT(ssr); + uint32_t au = SSR_AU_SIZE(ssr); + out_ssr->alloc_unit_kb = s_au_to_size_kb[au]; + out_ssr->erase_timeout = SSR_ERASE_TIMEOUT(ssr); + out_ssr->erase_size_au = SSR_ERASE_SIZE(ssr); + out_ssr->erase_offset = SSR_ERASE_OFFSET(ssr); return ESP_OK; } + +uint32_t sdmmc_sd_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb) +{ + if (arg == SDMMC_SD_DISCARD_ARG) { + return SDMMC_SD_DISCARD_TIMEOUT; + } else if (arg == SDMMC_SD_ERASE_ARG) { + if (card->ssr.alloc_unit_kb != 0 && + card->ssr.erase_size_au != 0 && + card->ssr.erase_timeout != 0 && + card->ssr.erase_offset != 0) { + /* Card supports erase timeout estimation. See the erase timeout equation in SD spec. */ + uint32_t timeout_sec = card->ssr.erase_offset + + card->ssr.erase_timeout * (erase_size_kb + card->ssr.alloc_unit_kb - 1) / + (card->ssr.erase_size_au * card->ssr.alloc_unit_kb); + ESP_LOGD(TAG, "%s: erase timeout %u s (erasing %u kB, ES=%u, ET=%u, EO=%u, AU=%u kB)", + __func__, timeout_sec, erase_size_kb, card->ssr.erase_size_au, + card->ssr.erase_timeout, card->ssr.erase_offset, card->ssr.alloc_unit_kb); + return timeout_sec * 1000; + } else { + uint32_t timeout_ms = SDMMC_SD_DISCARD_TIMEOUT * erase_size_kb / card->csd.sector_size; + timeout_ms = MAX(1000, timeout_ms); + ESP_LOGD(TAG, "%s: erase timeout %u s (erasing %u kB, %ums per sector)", + __func__, timeout_ms / 1000, erase_size_kb, SDMMC_SD_DISCARD_TIMEOUT); + return timeout_ms; + } + } else { + assert(false && "unexpected SD erase argument"); + return 0; + } +}