From 6baf8195ee78b5ce95ff68d9a61243424c6b06f5 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 12 Jul 2017 21:17:28 +0800 Subject: [PATCH] sdmmc: add support for high speed (HS) mode 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 | 37 +++- .../driver/include/driver/sdmmc_types.h | 7 + components/sdmmc/sdmmc_cmd.c | 184 ++++++++++++++++-- 3 files changed, 201 insertions(+), 27 deletions(-) diff --git a/components/driver/include/driver/sdmmc_defs.h b/components/driver/include/driver/sdmmc_defs.h index 30df7e2fc0..c13df6d1aa 100644 --- a/components/driver/include/driver/sdmmc_defs.h +++ b/components/driver/include/driver/sdmmc_defs.h @@ -294,15 +294,34 @@ #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 diff --git a/components/driver/include/driver/sdmmc_types.h b/components/driver/include/driver/sdmmc_types.h index d9ffde3ffd..4bc1e05e51 100644 --- a/components/driver/include/driver/sdmmc_types.h +++ b/components/driver/include/driver/sdmmc_types.h @@ -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 */ diff --git a/components/sdmmc/sdmmc_cmd.c b/components/sdmmc/sdmmc_cmd.c index 3cefe5ad97..87da936e16 100644 --- a/components/sdmmc/sdmmc_cmd.c +++ b/components/sdmmc/sdmmc_cmd.c @@ -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; +} -- 2.40.0