Jelajahi Sumber

sdmmc: Add erase command-38. Support erase/trim/discard/sanitize
options.

Erase command (38) for SD cards allows option for erase/dicard/fule
operation at block level and for MMC cards supports option for
discard/trim at block level. When Sanitize is executed only the
portion of data that was unmapped by a Discard command shall be
removed by the Sanitize command.

Unit test cases added to verify ERASE feature in SD/SDSPI mode.
TRIM/DISCARD/SANITIZE tests for eMMC devices.

Closes https://github.com/espressif/esp-idf/pull/7635
Closes https://github.com/espressif/esp-idf/issues/7623

Vamshi Gajjela 3 tahun lalu
induk
melakukan
ffdbeee9f6

+ 30 - 6
components/driver/include/driver/sdmmc_defs.h

@@ -47,12 +47,17 @@
 #define MMC_SET_BLOCK_COUNT             23      /* R1 */
 #define MMC_WRITE_BLOCK_SINGLE          24      /* R1 */
 #define MMC_WRITE_BLOCK_MULTIPLE        25      /* R1 */
+#define MMC_ERASE_GROUP_START           35      /* R1 */
+#define MMC_ERASE_GROUP_END             36      /* R1 */
+#define MMC_ERASE                       38      /* R1B */
 #define MMC_APP_CMD                     55      /* R1 */
 
 /* SD commands */                               /* response type */
 #define SD_SEND_RELATIVE_ADDR           3       /* R6 */
 #define SD_SEND_SWITCH_FUNC             6       /* R1 */
 #define SD_SEND_IF_COND                 8       /* R7 */
+#define SD_ERASE_GROUP_START            32      /* R1 */
+#define SD_ERASE_GROUP_END              33      /* R1 */
 #define SD_READ_OCR                     58      /* R3 */
 #define SD_CRC_ON_OFF                   59      /* R1 */
 
@@ -141,21 +146,26 @@
 #define SD_ARG_BUS_WIDTH_4              2
 
 /* EXT_CSD fields */
+#define EXT_CSD_SANITIZE_START          165     /* WO */
+#define EXT_CSD_ERASED_MEM_CONT         181     /* RO */
 #define EXT_CSD_BUS_WIDTH               183     /* WO */
 #define EXT_CSD_HS_TIMING               185     /* R/W */
+#define EXT_CSD_POWER_CLASS             187     /* R/W */
+#define EXT_CSD_CMD_SET                 191     /* R/W */
 #define EXT_CSD_REV                     192     /* RO */
 #define EXT_CSD_STRUCTURE               194     /* RO */
 #define EXT_CSD_CARD_TYPE               196     /* RO */
-#define EXT_CSD_SEC_COUNT               212     /* RO */
-#define EXT_CSD_PWR_CL_26_360           203     /* RO */
-#define EXT_CSD_PWR_CL_52_360           202     /* RO */
-#define EXT_CSD_PWR_CL_26_195           201     /* RO */
 #define EXT_CSD_PWR_CL_52_195           200     /* RO */
-#define EXT_CSD_POWER_CLASS             187     /* R/W */
-#define EXT_CSD_CMD_SET                 191     /* R/W */
+#define EXT_CSD_PWR_CL_26_195           201     /* RO */
+#define EXT_CSD_PWR_CL_52_360           202     /* RO */
+#define EXT_CSD_PWR_CL_26_360           203     /* RO */
+#define EXT_CSD_SEC_COUNT               212     /* RO */
+#define EXT_CSD_SEC_FEATURE_SUPPORT     231     /* RO */
 #define EXT_CSD_S_CMD_SET               504     /* RO */
 
 /* EXT_CSD field definitions */
+#define EXT_CSD_REV_1_6                 6       /* Revision 1.6 (for MMC v4.5, v4.51) */
+
 #define EXT_CSD_CMD_SET_NORMAL          (1U << 0)
 #define EXT_CSD_CMD_SET_SECURE          (1U << 1)
 #define EXT_CSD_CMD_SET_CPSECURE        (1U << 2)
@@ -186,6 +196,12 @@
 #define EXT_CSD_CARD_TYPE_52M_V12       0x0b
 #define EXT_CSD_CARD_TYPE_52M_V12_18    0x0f
 
+/* EXT_CSD_SEC_FEATURE_SUPPORT */
+#define EXT_CSD_SECURE_ER_EN            (uint8_t)(1 << 0)
+#define EXT_CSD_SEC_BD_BLK_EN           (uint8_t)(1 << 2)
+#define EXT_CSD_SEC_GB_CL_EN            (uint8_t)(1 << 4)
+#define EXT_CSD_SEC_SANITIZE            (uint8_t)(1 << 6)
+
 /* EXT_CSD MMC */
 #define EXT_CSD_MMC_SIZE 512
 
@@ -336,6 +352,12 @@
 #define SCR_CMD_SUPPORT_CMD20(scr)      MMC_RSP_BITS((scr), 32, 1)
 #define SCR_RESERVED2(scr)              MMC_RSP_BITS((scr), 0, 32)
 
+/* 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_DISCARD_SUPPORT(ssr)        MMC_RSP_BITS((ssr), 313, 1)
+#define SSR_FULE_SUPPORT(ssr)           MMC_RSP_BITS((ssr), 312, 1)
+
 /* Max supply current in SWITCH_FUNC response (in mA) */
 #define SD_SFUNC_I_MAX(status) (MMC_RSP_BITS((uint32_t *)(status), 496, 16))
 
@@ -365,6 +387,8 @@
 #define SD_ACCESS_MODE_SDR104   3       /* UHS-I, 208 MHz clock */
 #define SD_ACCESS_MODE_DDR50    4       /* UHS-I, 50 MHz clock, DDR */
 
+#define SD_SSR_SIZE 64                 /* SD status register */
+
 /**
  * @brief Extract up to 32 sequential bits from an array of 32-bit words
  *

+ 46 - 4
components/driver/include/driver/sdmmc_types.h

@@ -56,17 +56,35 @@ typedef struct {
 
 /**
  * Decoded values from SD Configuration Register
+ * Note: When new member is added, update reserved bits accordingly
  */
 typedef struct {
-    int sd_spec;    /*!< SD Physical layer specification version, reported by card */
-    int bus_width;  /*!< bus widths supported by card: BIT(0) — 1-bit bus, BIT(2) — 4-bit bus */
+    uint32_t sd_spec: 4;            /*!< SD Physical layer specification version, reported by card */
+    uint32_t erase_mem_state: 1;    /*!< data state on card after erase whether 0 or 1 (card vendor dependent) */
+    uint32_t bus_width: 4;          /*!< bus widths supported by card: BIT(0) — 1-bit bus, BIT(2) — 4-bit bus */
+    uint32_t reserved: 23;          /*!< reserved for future expansion */
+    uint32_t rsvd_mnf;              /*!< reserved for manufacturer usage */
 } sdmmc_scr_t;
 
+/**
+ * Decoded values from SD Status Register
+ * Note: When new member is added, update reserved bits accordingly
+ */
+typedef struct {
+    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 */
+} sdmmc_ssr_t;
+
 /**
  * Decoded values of Extended Card Specific Data
  */
 typedef struct {
-    uint8_t power_class;    /*!< Power class used by the card */
+    uint8_t rev;                /*!< Extended CSD Revision */
+    uint8_t power_class;        /*!< Power class used by the card */
+    uint8_t erase_mem_state;    /*!< data state on card after erase whether 0 or 1 (card vendor dependent) */
+    uint8_t sec_feature;        /*!< secure data management features supported by the card */
 } sdmmc_ext_csd_t;
 
 /**
@@ -120,7 +138,7 @@ typedef struct {
 #define SCF_WAIT_BUSY    0x2000     /*!< Wait for completion of card busy signal before returning */
 /** @endcond */
         esp_err_t error;            /*!< error returned from transfer */
-        int timeout_ms;             /*!< response timeout, in milliseconds */
+        uint32_t timeout_ms;        /*!< response timeout, in milliseconds */
 } sdmmc_command_t;
 
 /**
@@ -173,6 +191,7 @@ typedef struct {
     };
     sdmmc_csd_t csd;            /*!< decoded CSD (Card-Specific Data) register value */
     sdmmc_scr_t scr;            /*!< decoded SCR (SD card Configuration Register) value */
+    sdmmc_ssr_t ssr;            /*!< decoded SSR (SD Status Register) value */
     sdmmc_ext_csd_t ext_csd;    /*!< decoded EXT_CSD (Extended Card Specific Data) register value */
     uint16_t rca;               /*!< RCA (Relative Card Address) */
     uint16_t max_freq_khz;      /*!< Maximum frequency, in kHz, supported by the card */
@@ -185,5 +204,28 @@ typedef struct {
     uint32_t reserved : 23;     /*!< Reserved for future expansion */
 } sdmmc_card_t;
 
+/**
+ * SD/MMC erase command(38) arguments
+ * SD:
+ *  ERASE: Erase the write blocks, physical/hard erase.
+ *
+ *  DISCARD: Card may deallocate the discarded blocks partially or completely.
+ *  After discard operation the previously written data may be partially or
+ *  fully read by the host depending on card implementation.
+ *
+ * MMC:
+ *  ERASE: Does TRIM, applies erase operation to write blocks instead of Erase Group.
+ *
+ *  DISCARD: The Discard function allows the host to identify data that is no
+ *  longer required so that the device can erase the data if necessary during
+ *  background erase events. Applies to write blocks instead of Erase Group
+ *  After discard operation, the original data may be remained partially or
+ *  fully accessible to the host dependent on device.
+ *
+ */
+typedef enum {
+    SDMMC_ERASE_ARG = 0,      /*!< Erase operation on SD, Trim operation on MMC */
+    SDMMC_DISCARD_ARG = 1,    /*!< Discard operation for SD/MMC */
+} sdmmc_erase_arg_t;
 
 #endif // _SDMMC_TYPES_H_

+ 3 - 3
components/driver/sdmmc_transaction.c

@@ -73,7 +73,7 @@ static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd,
 static void process_command_response(uint32_t status, sdmmc_command_t* cmd);
 static void fill_dma_descriptors(size_t num_desc);
 static size_t get_free_descriptors_count(void);
-static bool wait_for_busy_cleared(int timeout_ms);
+static bool wait_for_busy_cleared(uint32_t timeout_ms);
 
 esp_err_t sdmmc_host_transaction_handler_init(void)
 {
@@ -462,7 +462,7 @@ static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd,
     return ESP_OK;
 }
 
-static bool wait_for_busy_cleared(int timeout_ms)
+static bool wait_for_busy_cleared(uint32_t timeout_ms)
 {
     if (timeout_ms == 0) {
         return !sdmmc_host_card_busy();
@@ -472,7 +472,7 @@ static bool wait_for_busy_cleared(int timeout_ms)
      * can only generate Busy Clear Interrupt for data write commands, and waiting
      * for busy clear is mostly needed for other commands such as MMC_SWITCH.
      */
-    int timeout_ticks = (timeout_ms + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS;
+    uint32_t timeout_ticks = (timeout_ms + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS;
     while (timeout_ticks-- > 0) {
         if (!sdmmc_host_card_busy()) {
             return true;

+ 78 - 0
components/sdmmc/include/sdmmc_cmd.h

@@ -80,6 +80,84 @@ esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src,
 esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst,
         size_t start_sector, size_t sector_count);
 
+/**
+ * Erase given number of sectors from the SD/MMC card
+ *
+ * @note When sdmmc_erase_sectors used with cards in SDSPI mode, it was
+ * observed that card requires re-init after erase operation.
+ *
+ * @param card  pointer to card information structure previously initialized
+ *              using sdmmc_card_init
+ * @param start_sector  sector where to start erase
+ * @param sector_count  number of sectors to erase
+ * @param arg  erase command (CMD38) argument
+ * @return
+ *      - ESP_OK on success
+ *      - One of the error codes from SDMMC host controller
+ */
+esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector,
+        size_t sector_count, sdmmc_erase_arg_t arg);
+
+/**
+ * Check if SD/MMC card supports discard
+ *
+ * @param card  pointer to card information structure previously initialized
+ *              using sdmmc_card_init
+ * @return
+ *      - ESP_OK if supported by the card/device
+ *      - ESP_FAIL if not supported by the card/device
+ */
+esp_err_t sdmmc_can_discard(sdmmc_card_t* card);
+
+/**
+ * Check if SD/MMC card supports trim
+ *
+ * @param card  pointer to card information structure previously initialized
+ *              using sdmmc_card_init
+ * @return
+ *      - ESP_OK if supported by the card/device
+ *      - ESP_FAIL if not supported by the card/device
+ */
+esp_err_t sdmmc_can_trim(sdmmc_card_t* card);
+
+/**
+ * Check if SD/MMC card supports sanitize
+ *
+ * @param card  pointer to card information structure previously initialized
+ *              using sdmmc_card_init
+ * @return
+ *      - ESP_OK if supported by the card/device
+ *      - ESP_FAIL if not supported by the card/device
+ */
+esp_err_t sdmmc_mmc_can_sanitize(sdmmc_card_t* card);
+
+/**
+ * Sanitize the data that was unmapped by a Discard command
+ *
+ * @note  Discard command has to precede sanitize operation. To discard, use
+ *        MMC_DICARD_ARG with sdmmc_erase_sectors argument
+ *
+ * @param card  pointer to card information structure previously initialized
+ *              using sdmmc_card_init
+ * @param timeout_ms timeout value in milliseconds required to sanitize the
+ *                   selected range of sectors.
+ * @return
+ *      - ESP_OK on success
+ *      - One of the error codes from SDMMC host controller
+ */
+esp_err_t sdmmc_mmc_sanitize(sdmmc_card_t* card, uint32_t timeout_ms);
+
+/**
+ * Erase complete SD/MMC card
+ *
+ * @param card  pointer to card information structure previously initialized
+ *              using sdmmc_card_init
+ * @return
+ *      - ESP_OK on success
+ *      - One of the error codes from SDMMC host controller
+ */
+esp_err_t sdmmc_full_erase(sdmmc_card_t* card);
+
 /**
  * Read one byte from an SDIO card using IO_RW_DIRECT (CMD52)
  *

+ 145 - 0
components/sdmmc/sdmmc_cmd.c

@@ -501,6 +501,151 @@ esp_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst,
     return ESP_OK;
 }
 
+esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector,
+        size_t sector_count, sdmmc_erase_arg_t arg)
+{
+    if (start_sector + sector_count > card->csd.capacity) {
+        return ESP_ERR_INVALID_SIZE;
+    }
+
+    if (arg == SDMMC_ERASE_ARG) {
+        arg = card->is_mmc ? SDMMC_MMC_TRIM_ARG : SDMMC_SD_ERASE_ARG;
+    } else {
+        arg = card->is_mmc ? SDMMC_MMC_DISCARD_ARG : SDMMC_SD_DISCARD_ARG;
+    }
+    /*
+     * validate the CMD38 argument against card supported features
+     */
+    if ((arg == SDMMC_MMC_TRIM_ARG) && (sdmmc_can_trim(card) != ESP_OK)) {
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+    if (((arg == SDMMC_MMC_DISCARD_ARG) || (arg == SDMMC_SD_DISCARD_ARG)) &&
+            ((sdmmc_can_discard(card) != ESP_OK) || host_is_spi(card))) {
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+
+    /* default as block unit address */
+    size_t addr_unit_mult = 1;
+
+    if (!(card->ocr & SD_OCR_SDHC_CAP)) {
+        addr_unit_mult = card->csd.sector_size;
+    }
+
+    /* prepare command to set the start address */
+    sdmmc_command_t cmd = {
+            .flags = SCF_CMD_AC | SCF_RSP_R1 | SCF_WAIT_BUSY,
+            .opcode = card->is_mmc ? MMC_ERASE_GROUP_START :
+                    SD_ERASE_GROUP_START,
+            .arg = (start_sector * addr_unit_mult),
+    };
+
+    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;
+    }
+
+    /* prepare command to set the end address */
+    cmd.opcode = card->is_mmc ? MMC_ERASE_GROUP_END : SD_ERASE_GROUP_END;
+    cmd.arg = ((start_sector + (sector_count - 1)) * addr_unit_mult);
+
+    err = sdmmc_send_cmd(card, &cmd);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
+        return err;
+    }
+
+    /* issue erase command */
+    memset((void *)&cmd, 0 , sizeof(sdmmc_command_t));
+    cmd.flags = SCF_CMD_AC | SCF_RSP_R1B | SCF_WAIT_BUSY;
+    cmd.opcode = MMC_ERASE;
+    cmd.arg = 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);
+
+    err = sdmmc_send_cmd(card, &cmd);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
+        return err;
+    }
+    return ESP_OK;
+}
+
+esp_err_t sdmmc_can_discard(sdmmc_card_t* card)
+{
+    if ((card->is_mmc) && (card->ext_csd.rev >= EXT_CSD_REV_1_6)) {
+         return ESP_OK;
+    }
+    // SD card
+    if (!host_is_spi(card) && (card->ssr.discard_support == 1)) {
+        return ESP_OK;
+    }
+    return ESP_FAIL;
+}
+
+esp_err_t sdmmc_can_trim(sdmmc_card_t* card)
+{
+    if ((card->is_mmc) && (card->ext_csd.sec_feature & EXT_CSD_SEC_GB_CL_EN)) {
+        return ESP_OK;
+    }
+    return ESP_FAIL;
+}
+
+esp_err_t sdmmc_mmc_can_sanitize(sdmmc_card_t* card)
+{
+    if ((card->is_mmc) && (card->ext_csd.sec_feature & EXT_CSD_SEC_SANITIZE)) {
+        return ESP_OK;
+    }
+    return ESP_FAIL;
+}
+
+esp_err_t sdmmc_mmc_sanitize(sdmmc_card_t* card, uint32_t timeout_ms)
+{
+    esp_err_t err;
+    uint8_t index = EXT_CSD_SANITIZE_START;
+    uint8_t set = EXT_CSD_CMD_SET_NORMAL;
+    uint8_t value = 0x01;
+
+    if (sdmmc_mmc_can_sanitize(card) != ESP_OK) {
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+    /*
+     * A Sanitize operation is initiated by writing a value to the extended
+     * CSD[165] SANITIZE_START. While the device is performing the sanitize
+     * operation, the busy line is asserted.
+     * SWITCH command is used to write the EXT_CSD register.
+     */
+    sdmmc_command_t cmd = {
+            .opcode = MMC_SWITCH,
+            .arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (index << 16) | (value << 8) | set,
+            .flags = SCF_RSP_R1B | SCF_CMD_AC | SCF_WAIT_BUSY,
+            .timeout_ms = timeout_ms,
+    };
+    err = sdmmc_send_cmd(card, &cmd);
+    if (err == ESP_OK) {
+        //check response bit to see that switch was accepted
+        if (MMC_R1(cmd.response) & MMC_R1_SWITCH_ERROR) {
+            err = ESP_ERR_INVALID_RESPONSE;
+        }
+    }
+    return err;
+}
+
+esp_err_t sdmmc_full_erase(sdmmc_card_t* card)
+{
+    sdmmc_erase_arg_t arg = SDMMC_SD_ERASE_ARG; // erase by default for SD card
+    esp_err_t err;
+    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);
+    if ((err == ESP_OK) && (arg == SDMMC_MMC_DISCARD_ARG)) {
+        return sdmmc_mmc_sanitize(card, SDMMC_ERASE_BLOCK_TIMEOUT_MS + card->csd.capacity);
+    }
+    return err;
+}
+
 esp_err_t sdmmc_get_status(sdmmc_card_t* card)
 {
     uint32_t stat;

+ 7 - 1
components/sdmmc/sdmmc_common.c

@@ -268,6 +268,7 @@ void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card)
         print_csd = true;
     } else {
         type = (card->ocr & SD_OCR_SDHC_CAP) ? "SDHC/SDXC" : "SDSC";
+        print_csd = true;
     }
     fprintf(stream, "Type: %s\n", type);
     if (card->max_freq_khz < 1000) {
@@ -280,8 +281,13 @@ void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card)
 
     if (print_csd) {
         fprintf(stream, "CSD: ver=%d, sector_size=%d, capacity=%d read_bl_len=%d\n",
-                card->csd.csd_ver,
+                (card->is_mmc ? card->csd.csd_ver : card->csd.csd_ver + 1),
                 card->csd.sector_size, card->csd.capacity, card->csd.read_block_len);
+        if (card->is_mmc) {
+            fprintf(stream, "EXT CSD: bus_width=%d\n", (1 << card->log_bus_width));
+        } else if (!card->is_sdio){ // make sure card is SD
+            fprintf(stream, "SSR: bus_width=%d\n", (card->ssr.cur_bus_width ? 4 : 1));
+        }
     }
     if (print_scr) {
         fprintf(stream, "SCR: sd_spec=%d, bus_width=%d\n", card->scr.sd_spec, card->scr.bus_width);

+ 9 - 0
components/sdmmc/sdmmc_common.h

@@ -37,6 +37,7 @@
  */
 #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
 
 /* Maximum retry/error count for SEND_OP_COND (CMD1).
  * These are somewhat arbitrary, values originate from OpenBSD driver.
@@ -44,6 +45,12 @@
 #define SDMMC_SEND_OP_COND_MAX_RETRIES  100
 #define SDMMC_SEND_OP_COND_MAX_ERRORS   3
 
+/* supported arguments for erase command 38 */
+#define SDMMC_SD_ERASE_ARG      0
+#define SDMMC_SD_DISCARD_ARG    1
+#define SDMMC_MMC_TRIM_ARG      1
+#define SDMMC_MMC_DISCARD_ARG   3
+
 /* Functions to send individual commands */
 esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd);
 esp_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd);
@@ -78,6 +85,7 @@ esp_err_t sdmmc_check_scr(sdmmc_card_t* card);
 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);
 
 /* SDIO specific */
 esp_err_t sdmmc_io_reset(sdmmc_card_t* card);
@@ -108,6 +116,7 @@ esp_err_t sdmmc_init_spi_crc(sdmmc_card_t* card);
 esp_err_t sdmmc_init_io(sdmmc_card_t* card);
 esp_err_t sdmmc_init_sd_blocklen(sdmmc_card_t* card);
 esp_err_t sdmmc_init_sd_scr(sdmmc_card_t* card);
+esp_err_t sdmmc_init_sd_ssr(sdmmc_card_t* card);
 esp_err_t sdmmc_init_sd_wait_data_ready(sdmmc_card_t* card);
 esp_err_t sdmmc_init_mmc_read_ext_csd(sdmmc_card_t* card);
 esp_err_t sdmmc_init_mmc_read_cid(sdmmc_card_t* card);

+ 3 - 0
components/sdmmc/sdmmc_init.c

@@ -112,6 +112,9 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card)
         SDMMC_INIT_STEP(always, sdmmc_init_host_bus_width);
     }
 
+    /* SD card: read SD Status register */
+    SDMMC_INIT_STEP(is_sdmem, sdmmc_init_sd_ssr);
+
     /* Switch to the host to use card->max_freq_khz frequency. */
     SDMMC_INIT_STEP(always, sdmmc_init_host_frequency);
 

+ 7 - 1
components/sdmmc/sdmmc_mmc.c

@@ -91,6 +91,11 @@ esp_err_t sdmmc_init_mmc_read_ext_csd(sdmmc_card_t* card)
         card->csd.capacity = sectors;
     }
 
+    /* erased state of a bit, if 1 byte value read is 0xFF else 0x00 */
+    card->ext_csd.erase_mem_state = ext_csd[EXT_CSD_ERASED_MEM_CONT];
+    card->ext_csd.rev = ext_csd[EXT_CSD_REV];
+    card->ext_csd.sec_feature = ext_csd[EXT_CSD_SEC_FEATURE_SUPPORT];
+
 out:
     free(ext_csd);
     return err;
@@ -224,8 +229,9 @@ esp_err_t sdmmc_mmc_switch(sdmmc_card_t* card, uint8_t set, uint8_t index, uint8
     esp_err_t err = sdmmc_send_cmd(card, &cmd);
     if (err == ESP_OK) {
         //check response bit to see that switch was accepted
-        if (MMC_R1(cmd.response) & MMC_R1_SWITCH_ERROR)
+        if (MMC_R1(cmd.response) & MMC_R1_SWITCH_ERROR) {
             err = ESP_ERR_INVALID_RESPONSE;
+        }
     }
 
     return err;

+ 55 - 1
components/sdmmc/sdmmc_sd.c

@@ -80,6 +80,43 @@ esp_err_t sdmmc_init_sd_scr(sdmmc_card_t* card)
     return ESP_OK;
 }
 
+esp_err_t sdmmc_init_sd_ssr(sdmmc_card_t* card)
+{
+    esp_err_t err = ESP_OK;
+    /* Get the contents of SSR register: SD additional information
+     * ACMD13 to read 512byte SD status information
+     */
+    uint32_t* sd_ssr = heap_caps_calloc(1, SD_SSR_SIZE, MALLOC_CAP_DMA);
+    if (!sd_ssr) {
+        ESP_LOGE(TAG, "%s: could not allocate sd_ssr", __func__);
+        return ESP_ERR_NO_MEM;
+    }
+
+    sdmmc_command_t cmd = {
+        .data = sd_ssr,
+        .datalen = SD_SSR_SIZE,
+        .blklen = SD_SSR_SIZE,
+        .opcode = MMC_SEND_STATUS,
+        .arg = 0,
+        .flags = SCF_CMD_ADTC | SCF_RSP_R1 | SCF_CMD_READ
+    };
+
+    // read SD status register
+    err = sdmmc_send_app_cmd(card, &cmd);
+    if (err != ESP_OK) {
+        free(sd_ssr);
+        ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
+        return err;
+    }
+
+    err = sdmmc_decode_ssr(sd_ssr, &card->ssr);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "%s: error sdmmc_decode_scr returned 0x%x", __func__, err);
+    }
+    free(sd_ssr);
+    return err;
+}
+
 esp_err_t sdmmc_init_sd_bus_width(sdmmc_card_t* card)
 {
     int width = 1;
@@ -265,7 +302,7 @@ esp_err_t sdmmc_check_scr(sdmmc_card_t* card)
      * and compare the result with the previous one. Use this simple check as
      * an indicator of potential signal integrity issues.
      */
-    sdmmc_scr_t scr_tmp;
+    sdmmc_scr_t scr_tmp = { 0 };
     esp_err_t err = sdmmc_send_cmd_send_scr(card, &scr_tmp);
     if (err != ESP_OK) {
         ESP_LOGE(TAG, "%s: send_scr returned 0x%x", __func__, err);
@@ -345,6 +382,23 @@ esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr)
         return ESP_ERR_NOT_SUPPORTED;
     }
     out_scr->sd_spec = SCR_SD_SPEC(resp);
+    out_scr->erase_mem_state = SCR_DATA_STAT_AFTER_ERASE(resp);
     out_scr->bus_width = SCR_SD_BUS_WIDTHS(resp);
     return ESP_OK;
 }
+
+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 };
+    size_t j = (SD_SSR_SIZE/sizeof(uint32_t) - 1);
+
+    for(size_t i = 0; i < (SD_SSR_SIZE/sizeof(uint32_t)); i++) {
+        ssr[j - i] = __builtin_bswap32(raw_ssr[i]);
+    }
+
+    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);
+
+    return ESP_OK;
+}

+ 434 - 0
components/sdmmc/test/test_sd.c

@@ -694,3 +694,437 @@ TEST_CASE("WP input works in SPI mode", "[sd][test_env=UT_T1_SPIMODE]")
     sd_test_board_power_off();
 }
 #endif //WITH_SDSPI_TEST
+
+#if WITH_SD_TEST || WITH_EMMC_TEST
+
+#define PATTERN_SEED    0x12345678
+#define FLAG_ERASE_TEST_ADJACENT    (1 << 0)
+#define FLAG_VERIFY_ERASE_STATE     (1 << 1)
+bool do_sanitize_flag = false;
+static void ensure_sector_written(sdmmc_card_t* card, size_t sector,
+        uint8_t *pattern_buf, uint8_t *temp_buf)
+{
+    size_t block_size = card->csd.sector_size;
+    TEST_ESP_OK(sdmmc_write_sectors(card, pattern_buf, sector, 1));
+    memset((void *)temp_buf, 0x00, block_size);
+    TEST_ESP_OK(sdmmc_read_sectors(card, temp_buf, sector, 1));
+    check_buffer(PATTERN_SEED, temp_buf, block_size / sizeof(uint32_t));
+}
+
+static void ensure_sector_intact(sdmmc_card_t* card, size_t sector,
+        uint8_t *pattern_buf, uint8_t *temp_buf)
+{
+    size_t block_size = card->csd.sector_size;
+    memset((void *)temp_buf, 0x00, block_size);
+    TEST_ESP_OK(sdmmc_read_sectors(card, temp_buf, sector, 1));
+    check_buffer(PATTERN_SEED, temp_buf, block_size / sizeof(uint32_t));
+}
+
+static int32_t ensure_sector_erase(sdmmc_card_t* card, size_t sector,
+        uint8_t *pattern_buf, uint8_t *temp_buf)
+{
+    size_t block_size = card->csd.sector_size;
+    memset((void *)temp_buf, 0, block_size);
+    TEST_ESP_OK(sdmmc_read_sectors(card, temp_buf, sector, 1));
+    return memcmp(pattern_buf, temp_buf, block_size);
+}
+
+static void do_single_erase_test(sdmmc_card_t* card, size_t start_block,
+                    size_t block_count, uint8_t flags, sdmmc_erase_arg_t arg)
+{
+    size_t block_size = card->csd.sector_size;
+    uint8_t *temp_buf = NULL;
+    uint8_t *pattern_buf = NULL;
+    size_t end_block = (start_block + block_count - 1);
+
+    /*
+     * To ensure erase is successful/valid
+     * selected blocks after erase should have erase state data pattern
+     * data of blocks adjacent to selected region should remain intact
+     */
+    TEST_ESP_OK((start_block + block_count) > card->csd.capacity);
+
+    pattern_buf = (uint8_t *)heap_caps_malloc(block_size, MALLOC_CAP_DMA);
+    TEST_ASSERT_NOT_NULL(pattern_buf);
+    temp_buf = (uint8_t *)heap_caps_malloc(block_size, MALLOC_CAP_DMA);
+    TEST_ASSERT_NOT_NULL(temp_buf);
+
+    // create pattern buffer
+    fill_buffer(PATTERN_SEED, pattern_buf, block_size / sizeof(uint32_t));
+
+    // check if it's not the first block of device & write/read/verify pattern
+    if ((flags & FLAG_ERASE_TEST_ADJACENT) && start_block) {
+        ensure_sector_written(card, (start_block - 1), pattern_buf, temp_buf);
+    }
+
+    ensure_sector_written(card, start_block, pattern_buf, temp_buf);
+
+    // check if it's not the last block of device & write/read/verify pattern
+    if ((flags & FLAG_ERASE_TEST_ADJACENT) && (end_block < (card->csd.capacity - 1))) {
+        ensure_sector_written(card, (end_block + 1), pattern_buf, temp_buf);
+    }
+
+    // when block count is 1, start and end block is same, hence skip
+    if (block_count != 1) {
+        ensure_sector_written(card, end_block, pattern_buf, temp_buf);
+    }
+
+    // fill pattern to (start_block + end_block)/2 in the erase range
+    if(block_count > 2) {
+        ensure_sector_written(card, (start_block + end_block)/2, pattern_buf, temp_buf);
+    }
+
+    float total_size = (block_count/1024.0f) * block_size;
+    printf(" %10d |  %10d   |  %8.1f    ", start_block, block_count, total_size);
+    fflush(stdout);
+
+    // erase the blocks
+    struct timeval t_start_er;
+    gettimeofday(&t_start_er, NULL);
+    TEST_ESP_OK(sdmmc_erase_sectors(card, start_block, block_count, arg));
+    if (do_sanitize_flag) {
+        TEST_ESP_OK(sdmmc_mmc_sanitize(card, block_count * 500));
+    }
+    struct timeval t_stop_wr;
+    gettimeofday(&t_stop_wr, NULL);
+    float time_er = 1e3f * (t_stop_wr.tv_sec - t_start_er.tv_sec) + 1e-3f * (t_stop_wr.tv_usec - t_start_er.tv_usec);
+    printf(" |   %8.2f\n", time_er);
+
+    // ensure adjacent blocks are not affected
+    // block before start_block
+    if ((flags & FLAG_ERASE_TEST_ADJACENT) && start_block) {
+        ensure_sector_intact(card, (start_block - 1), pattern_buf, temp_buf);
+    }
+
+    // block after end_block
+    if ((flags & FLAG_ERASE_TEST_ADJACENT) && (end_block < (card->csd.capacity - 1))) {
+        ensure_sector_intact(card, (end_block + 1), pattern_buf, temp_buf);
+    }
+
+    uint8_t erase_mem_byte = 0xFF;
+    // ensure all the blocks are erased and are up to after erase state.
+    if (!card->is_mmc) {
+        erase_mem_byte = card->scr.erase_mem_state ? 0xFF : 0x00;
+    } else {
+        erase_mem_byte = card->ext_csd.erase_mem_state ? 0xFF : 0x00;
+    }
+
+    memset((void *)pattern_buf, erase_mem_byte, block_size);
+
+    // as it is block by block comparison, a time taking process. Really long
+    // when you do erase and verify on complete device.
+    if (flags & FLAG_VERIFY_ERASE_STATE) {
+        for (size_t i  = 0; i < block_count; i++) {
+            if (ensure_sector_erase(card, (start_block + i), pattern_buf, temp_buf)) {
+                printf("Error: Sector %d erase\n", (start_block + i));
+                break;
+            }
+        }
+    }
+
+    free(temp_buf);
+    free(pattern_buf);
+}
+#endif // WITH_SD_TEST || WITH_EMMC_TEST
+
+#if WITH_SDSPI_TEST
+static void test_sdspi_erase_blocks(size_t start_block, size_t block_count)
+{
+    sd_test_board_power_on();
+    sdmmc_host_t config = SDSPI_HOST_DEFAULT();
+    sdspi_dev_handle_t handle;
+    sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT();
+    dev_config.host_id = config.slot;
+    dev_config.gpio_cs = SDSPI_TEST_CS_PIN;
+    test_sdspi_init_bus(dev_config.host_id, SDSPI_TEST_MOSI_PIN, SDSPI_TEST_MISO_PIN, SDSPI_TEST_SCLK_PIN, SPI_DMA_CH_AUTO);
+    TEST_ESP_OK(sdspi_host_init());
+    TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle));
+
+    // This test can only run under 20MHz on ESP32, because the runner connects the card to
+    // non-IOMUX pins of HSPI.
+
+    sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
+    TEST_ASSERT_NOT_NULL(card);
+    TEST_ESP_OK(sdmmc_card_init(&config, card));
+    sdmmc_card_print_info(stdout, card);
+    printf("block size %d capacity %d\n", card->csd.sector_size, card->csd.capacity);
+    printf("Erasing sectors %d-%d\n", start_block, (start_block + block_count -1));
+    size_t block_size = card->csd.sector_size;
+    uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(block_size, MALLOC_CAP_DMA);
+    TEST_ASSERT_NOT_NULL(pattern_buf);
+    uint8_t *temp_buf = (uint8_t *)heap_caps_malloc(block_size, MALLOC_CAP_DMA);
+    TEST_ASSERT_NOT_NULL(temp_buf);
+
+    struct timeval t_start_er;
+    gettimeofday(&t_start_er, NULL);
+    TEST_ESP_OK(sdmmc_erase_sectors(card, start_block, block_count, SDMMC_ERASE_ARG));
+    struct timeval t_stop_wr;
+    gettimeofday(&t_stop_wr, NULL);
+    float time_er = 1e3f * (t_stop_wr.tv_sec - t_start_er.tv_sec) + 1e-3f * (t_stop_wr.tv_usec - t_start_er.tv_usec);
+    printf("Erase duration: %.2fms\n", time_er);
+
+    // nominal delay before re-init card
+    vTaskDelay(pdMS_TO_TICKS(1000));
+    // has to re-init card, after erase operation.
+    TEST_ESP_OK(sdmmc_card_init(&config, card));
+    printf("Verifying erase state...\n");
+    uint8_t erase_mem_byte = 0xFF;
+    // ensure all the blocks are erased and are up to after erase state.
+    if (!card->is_mmc) {
+        erase_mem_byte = card->scr.erase_mem_state ? 0xFF : 0x00;
+    } else {
+        erase_mem_byte = card->ext_csd.erase_mem_state ? 0xFF : 0x00;
+    }
+
+    memset((void *)pattern_buf, erase_mem_byte, block_size);
+
+    size_t i;
+    for (i = 0; i < block_count; i++) {
+        memset((void *)temp_buf, 0, block_size);
+        TEST_ESP_OK(sdmmc_read_sectors(card, temp_buf, (start_block + i), 1));
+        if (memcmp(pattern_buf, temp_buf, block_size)) {
+            printf("Error: Sector %d erase\n", (start_block + i));
+            break;
+        }
+    }
+    if (i == block_count) {
+        printf("Sectors erase success\n");
+    }
+    TEST_ESP_OK(sdspi_host_deinit());
+    test_sdspi_deinit_bus(dev_config.host_id);
+    free(card);
+    free(temp_buf);
+    free(pattern_buf);
+    sd_test_board_power_off();
+}
+
+TEST_CASE("SDMMC erase (SPI mode)", "[sdspi][test_env=UT_T1_SPIMODE]")
+{
+    test_sdspi_erase_blocks(0, 16);
+}
+#endif // WITH_SDSPI_TEST
+
+#if WITH_SD_TEST
+static void test_sd_erase_blocks(sdmmc_card_t* card)
+{
+    sdmmc_card_print_info(stdout, card);
+    printf("block size %d capacity %d\n", card->csd.sector_size, card->csd.capacity);
+    printf("  sector    |    count      |   size(kB)    |   er_time(ms) \n");
+    /*
+     * bit-0: verify adjacent blocks of given range
+     * bit-1: verify erase state of blocks in range
+     */
+    uint8_t flags = 0;
+    sdmmc_erase_arg_t arg = SDMMC_ERASE_ARG;
+
+    //check for adjacent blocks and erase state of blocks
+    flags |= (uint8_t)FLAG_ERASE_TEST_ADJACENT | (uint8_t)FLAG_VERIFY_ERASE_STATE;
+    do_single_erase_test(card, 1, 16, flags, arg);
+    do_single_erase_test(card, 1, 13, flags, arg);
+    do_single_erase_test(card, 16, 32, flags, arg);
+    do_single_erase_test(card, 48, 64, flags, arg);
+    do_single_erase_test(card, 128, 128, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 64, 32, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 64, 64, flags, arg);
+    // single sector erase is failing on different make cards
+    do_single_erase_test(card, card->csd.capacity - 8, 1, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 1, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 4, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 8, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 16, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 32, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 64, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 128, flags, arg);
+#ifdef SDMMC_FULL_ERASE_TEST
+    /*
+     * check for adjacent blocks, do not check erase state of blocks as it is
+     * time taking process to verify all the blocks.
+     */
+    flags &= ~(uint8_t)FLAG_VERIFY_ERASE_STATE; //comment this line to verify after-erase state
+    // erase complete card
+    do_single_erase_test(card, 0, card->csd.capacity, flags, arg);
+#endif //SDMMC_FULL_ERASE_TEST
+}
+
+TEST_CASE("SDMMC erase test (SD slot 1, 1 line)", "[sd][test_env=UT_T1_SDMODE]")
+{
+    sd_test_board_power_on();
+    sd_test_rw_blocks(1, 1, test_sd_erase_blocks);
+    sd_test_board_power_off();
+}
+
+TEST_CASE("SDMMC erase test (SD slot 1, 4 line)", "[sd][test_env=UT_T1_SDMODE]")
+{
+    sd_test_board_power_on();
+    sd_test_rw_blocks(1, 4, test_sd_erase_blocks);
+    sd_test_board_power_off();
+}
+#endif //WITH_SD_TEST
+
+#if WITH_EMMC_TEST
+static void test_mmc_sanitize_blocks(sdmmc_card_t* card)
+{
+    /* MMC dicard applies to write blocks */
+    sdmmc_card_print_info(stdout, card);
+    printf("block size %d capacity %d\n", card->csd.sector_size, card->csd.capacity);
+
+    if (sdmmc_mmc_can_sanitize(card)) {
+        printf("Card/device do not support sanitize\n");
+        return;
+    }
+    printf("  sector    |    count      |   size(kB)    |   er_time(ms) \n");
+    /*
+     * bit-0: verify adjacent blocks of given range
+     * bit-1: verify erase state of blocks in range
+     */
+    uint8_t flags = 0;
+    sdmmc_erase_arg_t arg = SDMMC_DISCARD_ARG;
+    do_sanitize_flag = true;
+
+    /*
+     * Check for adjacent blocks only.
+     * After discard operation, the original data may be remained partially or
+     * fully accessible to the host dependent on device. Hence do not verify
+     * the erased state of the blocks.
+     *
+     * Note: After sanitize blocks has to be in erased state
+     */
+    flags |= (uint8_t)FLAG_ERASE_TEST_ADJACENT | (uint8_t)FLAG_VERIFY_ERASE_STATE;
+    do_single_erase_test(card, 1, 16, flags, arg);
+    do_single_erase_test(card, 1, 13, flags, arg);
+    do_single_erase_test(card, 16, 32, flags, arg);
+    do_single_erase_test(card, 48, 64, flags, arg);
+    do_single_erase_test(card, 128, 128, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 64, 32, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 64, 64, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 8, 1, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 1, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 4, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 8, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 16, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 32, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 64, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 128, flags, arg);
+    do_sanitize_flag = false;
+}
+
+static void test_mmc_discard_blocks(sdmmc_card_t* card)
+{
+    /* MMC dicard applies to write blocks */
+    sdmmc_card_print_info(stdout, card);
+    printf("block size %d capacity %d\n", card->csd.sector_size, card->csd.capacity);
+    printf("  sector    |    count      |   size(kB)    |   er_time(ms) \n");
+    /*
+     * bit-0: verify adjacent blocks of given range
+     * bit-1: verify erase state of blocks in range
+     */
+    uint8_t flags = 0;
+    sdmmc_erase_arg_t arg = SDMMC_DISCARD_ARG;
+
+    /*
+     * Check for adjacent blocks only.
+     * After discard operation, the original data may be remained partially or
+     * fully accessible to the host dependent on device. Hence do not verify
+     * the erased state of the blocks.
+     */
+    flags |= (uint8_t)FLAG_ERASE_TEST_ADJACENT;
+    do_single_erase_test(card, 1, 16, flags, arg);
+    do_single_erase_test(card, 1, 13, flags, arg);
+    do_single_erase_test(card, 16, 32, flags, arg);
+    do_single_erase_test(card, 48, 64, flags, arg);
+    do_single_erase_test(card, 128, 128, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 64, 32, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 64, 64, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 8, 1, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 1, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 4, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 8, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 16, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 32, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 64, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 128, flags, arg);
+}
+
+static void test_mmc_trim_blocks(sdmmc_card_t* card)
+{
+    /* MMC trim applies to write blocks */
+    sdmmc_card_print_info(stdout, card);
+    printf("block size %d capacity %d\n", card->csd.sector_size, card->csd.capacity);
+    printf("  sector    |    count      |   size(kB)    |   er_time(ms) \n");
+    /*
+     * bit-0: verify adjacent blocks of given range
+     * bit-1: verify erase state of blocks in range
+     */
+    uint8_t flags = 0;
+    sdmmc_erase_arg_t arg = SDMMC_ERASE_ARG;
+
+    //check for adjacent blocks and erase state of blocks
+    flags |= (uint8_t)FLAG_ERASE_TEST_ADJACENT | (uint8_t)FLAG_VERIFY_ERASE_STATE;
+    do_single_erase_test(card, 1, 16, flags, arg);
+    do_single_erase_test(card, 1, 13, flags, arg);
+    do_single_erase_test(card, 16, 32, flags, arg);
+    do_single_erase_test(card, 48, 64, flags, arg);
+    do_single_erase_test(card, 128, 128, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 64, 32, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 64, 64, flags, arg);
+    do_single_erase_test(card, card->csd.capacity - 8, 1, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 1, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 4, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 8, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 16, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 32, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 64, flags, arg);
+    do_single_erase_test(card, card->csd.capacity/2, 128, flags, arg);
+#ifdef SDMMC_FULL_ERASE_TEST
+    /*
+     * check for adjacent blocks, do not check erase state of blocks as it is
+     * time taking process to verify all the blocks.
+     */
+    flags &= ~(uint8_t)FLAG_VERIFY_ERASE_STATE; //comment this line to verify after erase state
+    // erase complete card
+    do_single_erase_test(card, 0, card->csd.capacity, flags, arg);
+#endif //SDMMC_FULL_ERASE_TEST
+}
+
+TEST_CASE("SDMMC trim test (eMMC slot 0, 4 line)", "[sd][test_env=EMMC]")
+{
+    sd_test_board_power_on();
+    sd_test_rw_blocks(0, 4, test_mmc_trim_blocks);
+    sd_test_board_power_off();
+}
+
+TEST_CASE("SDMMC trim test (eMMC slot 0, 8 line)", "[sd][test_env=EMMC]")
+{
+    sd_test_board_power_on();
+    sd_test_rw_blocks(0, 8, test_mmc_trim_blocks);
+    sd_test_board_power_off();
+}
+
+TEST_CASE("SDMMC discard test (eMMC slot 0, 4 line)", "[sd][test_env=EMMC]")
+{
+    sd_test_board_power_on();
+    sd_test_rw_blocks(0, 4, test_mmc_discard_blocks);
+    sd_test_board_power_off();
+}
+
+TEST_CASE("SDMMC discard test (eMMC slot 0, 8 line)", "[sd][test_env=EMMC]")
+{
+    sd_test_board_power_on();
+    sd_test_rw_blocks(0, 8, test_mmc_discard_blocks);
+    sd_test_board_power_off();
+}
+
+TEST_CASE("SDMMC sanitize test (eMMC slot 0, 4 line)", "[sd][test_env=EMMC]")
+{
+    sd_test_board_power_on();
+    sd_test_rw_blocks(0, 4, test_mmc_sanitize_blocks);
+    sd_test_board_power_off();
+}
+
+TEST_CASE("SDMMC sanitize test (eMMC slot 0, 8 line)", "[sd][test_env=EMMC]")
+{
+    sd_test_board_power_on();
+    sd_test_rw_blocks(0, 8, test_mmc_sanitize_blocks);
+    sd_test_board_power_off();
+}
+#endif //WITH_EMMC_TEST

+ 8 - 0
tools/ci/check_copyright_config.yaml

@@ -104,6 +104,14 @@ spiffs:
     - MIT
     - Apache-2.0
 
+sdmmc:
+  include:
+    - 'components/driver/include/driver/'
+    - 'components/sdmmc/'
+  allowed_licenses:
+    - Apache-2.0
+    - ISC
+
 # files matching this section do not perform the check
 # file patterns starting with ! are negated, meaning files matching them won't match the section.
 ignore: