From 78fab8a0f987e2894a34eb9246a003de8b3088b2 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 22 Aug 2018 18:16:32 +0800 Subject: [PATCH] sdmmc: implement partial DDR support Works for 3.3V eMMC in 4 line mode. Not implemented: - DDR mode for SD cards (UHS-I) also need voltage to be switched to 1.8V. - 8-line DDR mode for eMMC to be implemented later. --- components/driver/include/driver/sdmmc_defs.h | 8 ++-- components/driver/include/driver/sdmmc_host.h | 16 +++++++- .../driver/include/driver/sdmmc_types.h | 7 +++- components/driver/include/driver/sdspi_host.h | 1 + components/driver/sdmmc_host.c | 30 +++++++++++++++ components/driver/sdmmc_private.h | 2 + components/driver/sdmmc_transaction.c | 25 +++++++++++++ components/sdmmc/sdmmc_common.c | 37 +++++++++++++++---- components/sdmmc/sdmmc_init.c | 18 ++++----- components/sdmmc/sdmmc_mmc.c | 26 ++++++++++--- components/soc/esp32/include/soc/sdmmc_reg.h | 1 + .../soc/esp32/include/soc/sdmmc_struct.h | 18 ++++++++- 12 files changed, 156 insertions(+), 33 deletions(-) diff --git a/components/driver/include/driver/sdmmc_defs.h b/components/driver/include/driver/sdmmc_defs.h index a6b135d6a..6ea567b8f 100644 --- a/components/driver/include/driver/sdmmc_defs.h +++ b/components/driver/include/driver/sdmmc_defs.h @@ -161,10 +161,10 @@ /* EXT_CSD_CARD_TYPE */ /* The only currently valid values for this field are 0x01, 0x03, 0x07, * 0x0B and 0x0F. */ -#define EXT_CSD_CARD_TYPE_F_26M (1 << 0) -#define EXT_CSD_CARD_TYPE_F_52M (1 << 1) -#define EXT_CSD_CARD_TYPE_F_52M_1_8V (1 << 2) -#define EXT_CSD_CARD_TYPE_F_52M_1_2V (1 << 3) +#define EXT_CSD_CARD_TYPE_F_26M (1 << 0) /* SDR at "rated voltages */ +#define EXT_CSD_CARD_TYPE_F_52M (1 << 1) /* SDR at "rated voltages */ +#define EXT_CSD_CARD_TYPE_F_52M_1_8V (1 << 2) /* DDR, 1.8V or 3.3V I/O */ +#define EXT_CSD_CARD_TYPE_F_52M_1_2V (1 << 3) /* DDR, 1.2V I/O */ #define EXT_CSD_CARD_TYPE_26M 0x01 #define EXT_CSD_CARD_TYPE_52M 0x03 #define EXT_CSD_CARD_TYPE_52M_V18 0x07 diff --git a/components/driver/include/driver/sdmmc_host.h b/components/driver/include/driver/sdmmc_host.h index f57b959cc..4d94d03c7 100644 --- a/components/driver/include/driver/sdmmc_host.h +++ b/components/driver/include/driver/sdmmc_host.h @@ -33,13 +33,17 @@ extern "C" { * Uses SDMMC peripheral, with 4-bit mode enabled, and max frequency set to 20MHz */ #define SDMMC_HOST_DEFAULT() {\ - .flags = SDMMC_HOST_FLAG_4BIT, \ + .flags = SDMMC_HOST_FLAG_8BIT | \ + SDMMC_HOST_FLAG_4BIT | \ + SDMMC_HOST_FLAG_1BIT | \ + SDMMC_HOST_FLAG_DDR, \ .slot = SDMMC_HOST_SLOT_1, \ .max_freq_khz = SDMMC_FREQ_DEFAULT, \ .io_voltage = 3.3f, \ .init = &sdmmc_host_init, \ .set_bus_width = &sdmmc_host_set_bus_width, \ .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, \ .do_transaction = &sdmmc_host_do_transaction, \ .deinit = &sdmmc_host_deinit, \ @@ -150,6 +154,16 @@ size_t sdmmc_host_get_slot_width(int slot); */ esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz); +/** + * @brief Enable or disable DDR mode of SD interface + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param ddr_enabled enable or disable DDR mode + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_SUPPORTED if DDR mode is not supported on this slot + */ +esp_err_t sdmmc_host_set_bus_ddr_mode(int slot, bool ddr_enabled); + /** * @brief Send command to the card and get response * diff --git a/components/driver/include/driver/sdmmc_types.h b/components/driver/include/driver/sdmmc_types.h index 1c584eff6..369a9210f 100644 --- a/components/driver/include/driver/sdmmc_types.h +++ b/components/driver/include/driver/sdmmc_types.h @@ -110,6 +110,8 @@ typedef struct { #define SCF_RSP_R5B (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX|SCF_RSP_BSY) #define SCF_RSP_R6 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) #define SCF_RSP_R7 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) +/* special flags */ +#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 */ @@ -127,6 +129,7 @@ typedef struct { #define SDMMC_HOST_FLAG_4BIT BIT(1) /*!< host supports 4-line SD and MMC protocol */ #define SDMMC_HOST_FLAG_8BIT BIT(2) /*!< host supports 8-line MMC protocol */ #define SDMMC_HOST_FLAG_SPI BIT(3) /*!< host supports SPI protocol */ +#define SDMMC_HOST_FLAG_DDR BIT(4) /*!< host supports DDR mode for SD/MMC */ int slot; /*!< slot number, to be passed to host functions */ int max_freq_khz; /*!< max frequency supported by the host */ #define SDMMC_FREQ_DEFAULT 20000 /*!< SD/MMC Default speed (limited by clock divider) */ @@ -138,6 +141,7 @@ typedef struct { esp_err_t (*init)(void); /*!< Host function to initialize the driver */ esp_err_t (*set_bus_width)(int slot, size_t width); /*!< host function to set bus width */ 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 (*do_transaction)(int slot, sdmmc_command_t* cmdinfo); /*!< host function to do a transaction */ esp_err_t (*deinit)(void); /*!< host function to deinitialize the driver */ @@ -163,7 +167,8 @@ typedef struct { uint32_t is_mmc : 1; /*!< Bit indicates if the card is MMC */ uint32_t num_io_functions : 3; /*!< If is_sdio is 1, contains the number of IO functions on the card */ uint32_t log_bus_width : 2; /*!< log2(bus width supported by card) */ - uint32_t reserved : 24; /*!< Reserved for future expansion */ + uint32_t is_ddr : 1; /*!< Card supports DDR mode */ + uint32_t reserved : 23; /*!< Reserved for future expansion */ } sdmmc_card_t; diff --git a/components/driver/include/driver/sdspi_host.h b/components/driver/include/driver/sdspi_host.h index 9f72cb0dc..1b3b67017 100644 --- a/components/driver/include/driver/sdspi_host.h +++ b/components/driver/include/driver/sdspi_host.h @@ -41,6 +41,7 @@ extern "C" { .init = &sdspi_host_init, \ .set_bus_width = NULL, \ .get_bus_width = NULL, \ + .set_bus_ddr_mode = NULL, \ .set_card_clk = &sdspi_host_set_card_clk, \ .do_transaction = &sdspi_host_do_transaction, \ .deinit = &sdspi_host_deinit, \ diff --git a/components/driver/sdmmc_host.c b/components/driver/sdmmc_host.c index 58fe9713f..2d276b409 100644 --- a/components/driver/sdmmc_host.c +++ b/components/driver/sdmmc_host.c @@ -267,6 +267,9 @@ esp_err_t sdmmc_host_init() SDMMC_INTMASK_RESP_ERR | SDMMC_INTMASK_HLE; //sdio is enabled only when use. SDMMC.ctrl.int_enable = 1; + // Disable generation of Busy Clear Interrupt + SDMMC.cardthrctl.busy_clr_int_en = 0; + // Enable DMA sdmmc_host_dma_init(); @@ -465,6 +468,28 @@ size_t sdmmc_host_get_slot_width(int slot) return s_slot_width[slot]; } +esp_err_t sdmmc_host_set_bus_ddr_mode(int slot, bool ddr_enabled) +{ + if (!(slot == 0 || slot == 1)) { + return ESP_ERR_INVALID_ARG; + } + if (s_slot_width[slot] == 8 && ddr_enabled) { + ESP_LOGW(TAG, "DDR mode with 8-bit bus width is not supported yet"); + // requires reconfiguring controller clock for 2x card frequency + return ESP_ERR_NOT_SUPPORTED; + } + uint32_t mask = BIT(slot); + if (ddr_enabled) { + SDMMC.uhs.ddr |= mask; + SDMMC.emmc_ddr_reg |= mask; + } else { + SDMMC.uhs.ddr &= ~mask; + SDMMC.emmc_ddr_reg &= ~mask; + } + ESP_LOGD(TAG, "slot=%d ddr=%d", slot, ddr_enabled ? 1 : 0); + return ESP_OK; +} + static void sdmmc_host_dma_init() { SDMMC.ctrl.dma_enable = 1; @@ -504,6 +529,11 @@ void sdmmc_host_dma_resume() SDMMC.pldmnd = 1; } +bool sdmmc_host_card_busy() +{ + return SDMMC.status.data_busy == 1; +} + esp_err_t sdmmc_host_io_int_enable(int slot) { configure_pin(sdmmc_slot_info[slot].d1_gpio); diff --git a/components/driver/sdmmc_private.h b/components/driver/sdmmc_private.h index 8f0aab78b..9223dce31 100644 --- a/components/driver/sdmmc_private.h +++ b/components/driver/sdmmc_private.h @@ -38,6 +38,8 @@ void sdmmc_host_dma_stop(); void sdmmc_host_dma_resume(); +bool sdmmc_host_card_busy(); + esp_err_t sdmmc_host_transaction_handler_init(); void sdmmc_host_transaction_handler_deinit(); diff --git a/components/driver/sdmmc_transaction.c b/components/driver/sdmmc_transaction.c index 43f74c086..6cddd4b8e 100644 --- a/components/driver/sdmmc_transaction.c +++ b/components/driver/sdmmc_transaction.c @@ -19,6 +19,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/queue.h" #include "freertos/semphr.h" +#include "freertos/task.h" #include "soc/sdmmc_periph.h" #include "soc/soc_memory_layout.h" #include "driver/sdmmc_types.h" @@ -80,6 +81,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(); +static bool wait_for_busy_cleared(int timeout_ms); esp_err_t sdmmc_host_transaction_handler_init() { @@ -165,6 +167,11 @@ esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo) break; } } + if (ret == ESP_OK && (cmdinfo->flags & SCF_WAIT_BUSY)) { + if (!wait_for_busy_cleared(cmdinfo->timeout_ms)) { + ret = ESP_ERR_TIMEOUT; + } + } s_is_app_cmd = (ret == ESP_OK && cmdinfo->opcode == MMC_APP_CMD); out: @@ -461,5 +468,23 @@ 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) +{ + if (timeout_ms == 0) { + return !sdmmc_host_card_busy(); + } + /* It would have been nice to do this without polling, however the peripheral + * 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; + while (timeout_ticks-- > 0) { + if (!sdmmc_host_card_busy()) { + return true; + } + vTaskDelay(1); + } + return false; +} diff --git a/components/sdmmc/sdmmc_common.c b/components/sdmmc/sdmmc_common.c index 601d7a433..564b00636 100644 --- a/components/sdmmc/sdmmc_common.c +++ b/components/sdmmc/sdmmc_common.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2006 Uwe Stuehler - * Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD + * Adaptations to ESP-IDF Copyright (c) 2016-2018 Espressif Systems (Shanghai) PTE LTD * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -214,6 +214,18 @@ esp_err_t sdmmc_init_host_frequency(sdmmc_card_t* card) return err; } } + + if (card->is_ddr) { + if (card->host.set_bus_ddr_mode == NULL) { + ESP_LOGE(TAG, "host doesn't support DDR mode or voltage switching"); + return ESP_ERR_NOT_SUPPORTED; + } + esp_err_t err = (*card->host.set_bus_ddr_mode)(card->host.slot, true); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to switch bus to DDR mode (0x%x)", err); + return err; + } + } return ESP_OK; } @@ -246,7 +258,12 @@ void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card) type = (card->ocr & SD_OCR_SDHC_CAP) ? "SDHC/SDXC" : "SDSC"; } fprintf(stream, "Type: %s\n", type); - fprintf(stream, "Speed: %s\n", (card->max_freq_khz > SDMMC_FREQ_26M) ? "high speed" : "default speed"); + if (card->max_freq_khz < 1000) { + fprintf(stream, "Speed: %d kHz\n", card->max_freq_khz); + } else { + fprintf(stream, "Speed: %d MHz%s\n", card->max_freq_khz / 1000, + card->is_ddr ? ", DDR" : ""); + } fprintf(stream, "Size: %lluMB\n", ((uint64_t) card->csd.capacity) * card->csd.sector_size / (1024 * 1024)); if (print_csd) { @@ -269,13 +286,17 @@ esp_err_t sdmmc_fix_host_flags(sdmmc_card_t* card) int slot_bit_width = card->host.get_bus_width(card->host.slot); if (slot_bit_width == 1 && (card->host.flags & (width_4bit | width_8bit))) { - ESP_LOGW(TAG, "host slot is configured in 1-bit mode"); card->host.flags &= ~width_mask; - card->host.flags |= ~(width_1bit); - } else if (slot_bit_width == 4 && (card->host.flags & width_8bit)){ - ESP_LOGW(TAG, "host slot is configured in 4-bit mode"); - card->host.flags &= ~width_mask; - card->host.flags |= width_4bit; + card->host.flags |= width_1bit; + } else if (slot_bit_width == 4 && (card->host.flags & width_8bit)) { + if ((card->host.flags & width_4bit) == 0) { + ESP_LOGW(TAG, "slot width set to 4, but host flags don't have 4 line mode enabled; using 1 line mode"); + card->host.flags &= ~width_mask; + card->host.flags |= width_1bit; + } else { + card->host.flags &= ~width_mask; + card->host.flags |= width_4bit; + } } return ESP_OK; } diff --git a/components/sdmmc/sdmmc_init.c b/components/sdmmc/sdmmc_init.c index 312ae88f4..6bd5a8853 100644 --- a/components/sdmmc/sdmmc_init.c +++ b/components/sdmmc/sdmmc_init.c @@ -93,6 +93,11 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card) /* MMC cards: read CXD */ SDMMC_INIT_STEP(is_mmc, sdmmc_init_mmc_read_ext_csd); + /* Try to switch card to HS mode if the card supports it. + * Set card->max_freq_khz value accordingly. + */ + SDMMC_INIT_STEP(always, sdmmc_init_card_hs_mode); + /* Set bus width. One call for every kind of card, then one for the host */ if (!is_spi) { SDMMC_INIT_STEP(is_sdmem, sdmmc_init_sd_bus_width); @@ -101,19 +106,10 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card) SDMMC_INIT_STEP(always, sdmmc_init_host_bus_width); } - SDMMC_INIT_STEP(is_sdmem, sdmmc_check_scr); - - /* Try to switch card to HS mode if the card supports it. - * Set card->max_freq_khz value accordingly. - */ - SDMMC_INIT_STEP(always, sdmmc_init_card_hs_mode); - - /* So far initialization has been done at probing frequency. - * Switch to the host to use card->max_freq_khz frequency. - */ + /* Switch to the host to use card->max_freq_khz frequency. */ SDMMC_INIT_STEP(always, sdmmc_init_host_frequency); - /* Sanity check after switching the frequency */ + /* Sanity check after switching the bus mode and frequency */ SDMMC_INIT_STEP(is_sdmem, sdmmc_check_scr); /* TODO: add similar checks for eMMC and SDIO */ diff --git a/components/sdmmc/sdmmc_mmc.c b/components/sdmmc/sdmmc_mmc.c index d54a463e5..e123763f0 100644 --- a/components/sdmmc/sdmmc_mmc.c +++ b/components/sdmmc/sdmmc_mmc.c @@ -48,9 +48,15 @@ esp_err_t sdmmc_init_mmc_read_ext_csd(sdmmc_card_t* card) } card_type = ext_csd[EXT_CSD_CARD_TYPE]; - /* TODO: add DDR support */ + card->is_ddr = 0; if (card_type & EXT_CSD_CARD_TYPE_F_52M_1_8V) { card->max_freq_khz = SDMMC_FREQ_52M; + if ((card->host.flags & SDMMC_HOST_FLAG_DDR) && + card->host.max_freq_khz >= SDMMC_FREQ_26M && + card->host.get_bus_width(card->host.slot) == 4) { + ESP_LOGD(TAG, "card and host support DDR mode"); + card->is_ddr = 1; + } } else if (card_type & EXT_CSD_CARD_TYPE_F_52M) { card->max_freq_khz = SDMMC_FREQ_52M; } else if (card_type & EXT_CSD_CARD_TYPE_F_26M) { @@ -60,7 +66,7 @@ esp_err_t sdmmc_init_mmc_read_ext_csd(sdmmc_card_t* card) } /* For MMC cards, use speed value from EXT_CSD */ card->csd.tr_speed = card->max_freq_khz * 1000; - ESP_LOGD(TAG, "MMC card supports %d khz bus frequency", card->max_freq_khz); + ESP_LOGD(TAG, "MMC card type %d, max_freq_khz=%d, is_ddr=%d", card_type, card->max_freq_khz, card->is_ddr); card->max_freq_khz = MIN(card->max_freq_khz, card->host.max_freq_khz); if (card->host.flags & SDMMC_HOST_FLAG_8BIT) { @@ -104,13 +110,21 @@ esp_err_t sdmmc_init_mmc_bus_width(sdmmc_card_t* card) } if (card->log_bus_width > 0) { - int csd_bus_width_value = 0; + int csd_bus_width_value = EXT_CSD_BUS_WIDTH_1; int bus_width = 1; if (card->log_bus_width == 2) { - csd_bus_width_value = EXT_CSD_BUS_WIDTH_4; + if (card->is_ddr) { + csd_bus_width_value = EXT_CSD_BUS_WIDTH_4_DDR; + } else { + csd_bus_width_value = EXT_CSD_BUS_WIDTH_4; + } bus_width = 4; } else if (card->log_bus_width == 3) { - csd_bus_width_value = EXT_CSD_BUS_WIDTH_8; + if (card->is_ddr) { + csd_bus_width_value = EXT_CSD_BUS_WIDTH_8_DDR; + } else { + csd_bus_width_value = EXT_CSD_BUS_WIDTH_8; + } bus_width = 8; } err = sdmmc_mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, @@ -205,7 +219,7 @@ esp_err_t sdmmc_mmc_switch(sdmmc_card_t* card, uint8_t set, uint8_t index, uint8 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, + .flags = SCF_RSP_R1B | SCF_CMD_AC | SCF_WAIT_BUSY, }; esp_err_t err = sdmmc_send_cmd(card, &cmd); if (err == ESP_OK) { diff --git a/components/soc/esp32/include/soc/sdmmc_reg.h b/components/soc/esp32/include/soc/sdmmc_reg.h index 2f9c68f80..0e92f6822 100644 --- a/components/soc/esp32/include/soc/sdmmc_reg.h +++ b/components/soc/esp32/include/soc/sdmmc_reg.h @@ -71,6 +71,7 @@ #define SDMMC_INTMASK_EBE BIT(15) #define SDMMC_INTMASK_ACD BIT(14) #define SDMMC_INTMASK_SBE BIT(13) +#define SDMMC_INTMASK_BCI BIT(13) #define SDMMC_INTMASK_HLE BIT(12) #define SDMMC_INTMASK_FRUN BIT(11) #define SDMMC_INTMASK_HTO BIT(10) diff --git a/components/soc/esp32/include/soc/sdmmc_struct.h b/components/soc/esp32/include/soc/sdmmc_struct.h index 7e3c6912e..6aa64b43c 100644 --- a/components/soc/esp32/include/soc/sdmmc_struct.h +++ b/components/soc/esp32/include/soc/sdmmc_struct.h @@ -283,7 +283,12 @@ typedef volatile struct { uint32_t usrid; ///< user ID uint32_t verid; ///< IP block version uint32_t hcon; ///< compile-time IP configuration - uint32_t uhs; ///< TBD + union { + struct { + uint32_t voltage: 16; ///< voltage control for slots; no-op on ESP32. + uint32_t ddr: 16; ///< bit N enables DDR mode for card N + }; + } uhs; ///< UHS related settings union { struct { @@ -348,7 +353,16 @@ typedef volatile struct { uint32_t bufaddrl; ///< unused uint32_t bufaddru; ///< unused uint32_t reserved_a8[22]; - uint32_t cardthrctl; + union { + struct { + uint32_t read_thr_en : 1; ///< initiate transfer only if FIFO has more space than the read threshold + uint32_t busy_clr_int_en : 1; ///< enable generation of busy clear interrupts + uint32_t write_thr_en : 1; ///< equivalent of read_thr_en for writes + uint32_t reserved1 : 13; + uint32_t card_threshold : 12; ///< threshold value for reads/writes, in bytes + }; + uint32_t val; + } cardthrctl; uint32_t back_end_power; uint32_t uhs_reg_ext; uint32_t emmc_ddr_reg;