Files
espressif--esp-idf/components/esp_driver_sdmmc/src/sd_host_sdmmc.c
T
2026-07-13 13:04:25 +08:00

1449 lines
56 KiB
C

/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <sys/param.h>
#include "esp_log.h"
#include "esp_intr_alloc.h"
#include "esp_check.h"
#include "esp_clk_tree.h"
#include "esp_timer.h"
#include "esp_memory_utils.h"
#include "soc/chip_revision.h"
#include "soc/sdmmc_pins.h"
#include "soc/soc_caps.h"
#include "soc/clk_tree_defs.h"
#include "hal/sdmmc_periph.h"
#include "hal/efuse_hal.h"
#include "hal/sd_types.h"
#include "hal/sdmmc_hal.h"
#include "hal/sdmmc_ll.h"
#include "driver/sd_host.h"
#include "driver/sd_host_sdmmc.h"
#include "esp_private/sd_driver_interface.h"
#include "esp_private/esp_clk_tree_common.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/esp_cache_private.h"
#include "esp_private/gpio.h"
#include "esp_private/sd_host_private.h"
#include "freertos/FreeRTOS.h"
#include "clk_ctrl_os.h"
typedef struct sd_platform_t {
_lock_t mutex;
sd_host_sdmmc_ctlr_t *controllers[SDMMC_LL_HOST_CTLR_NUMS];
} sd_platform_t;
static const char *TAG = "SD_HOST";
static sd_platform_t s_platform;
//Controller Allocation
static esp_err_t sd_host_claim_controller(sd_host_sdmmc_ctlr_t *controller);
static esp_err_t sd_host_declaim_controller(sd_host_sdmmc_ctlr_t *controller);
//ISR Handler
static void sd_host_isr(void *arg);
//HW Commands
static esp_err_t sd_host_slot_clock_update_command(sd_host_sdmmc_slot_t *slot, bool is_cmd11);
//Control
static esp_err_t sd_host_reset(sd_host_sdmmc_ctlr_t *ctlr);
//Clock
static void sd_host_set_clk_div(sd_host_sdmmc_ctlr_t *ctlr, soc_periph_sdmmc_clk_src_t src, int div);
static int sd_host_calc_freq(soc_periph_sdmmc_clk_src_t src, const int host_div, const int card_div);
static void sd_host_slot_get_clk_dividers(sd_host_sdmmc_slot_t *slot, uint32_t freq_khz, int *host_div, int *card_div, soc_periph_sdmmc_clk_src_t *src);
//IO
static void configure_pin(uint8_t gpio_num, uint8_t gpio_matrix_sig, gpio_mode_t mode, const char *name, bool use_gpio_matrix);
static esp_err_t sdmmc_slot_io_config(sd_host_sdmmc_slot_t *slot, const sd_host_slot_sdmmc_init_cfg_t *slot_init_config);
//Public APIs
static esp_err_t sd_host_slot_sdmmc_configure(sd_host_slot_handle_t slot, const sd_host_slot_cfg_t *config);
static esp_err_t sd_host_slot_sdmmc_register_event_callbacks(sd_host_slot_handle_t slot, const sd_host_evt_cbs_t *cbs, void *user_data);
static esp_err_t sd_host_controller_remove_sdmmc_slot(sd_host_slot_handle_t slot);
static esp_err_t sd_host_del_sdmmc_controller(sd_host_ctlr_handle_t ctlr);
static esp_err_t sd_host_slot_sdmmc_set_cclk_always_on(sd_host_slot_handle_t slot, bool always_on);
static esp_err_t sd_host_slot_sdmmc_io_int_enable(sd_host_slot_handle_t slot);
static esp_err_t sd_host_slot_sdmmc_io_int_wait(sd_host_slot_handle_t slot, TickType_t timeout_ticks);
static esp_err_t sd_host_slot_sdmmc_get_info(sd_host_slot_handle_t slot, sd_host_slot_info_t *info);
/*---------------------------------------------------------------
Public APIs
---------------------------------------------------------------*/
esp_err_t sd_host_create_sdmmc_controller(const sd_host_sdmmc_cfg_t *config, sd_host_ctlr_handle_t *ret_handle)
{
esp_err_t ret = ESP_FAIL;
ESP_RETURN_ON_FALSE(config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
sd_host_sdmmc_ctlr_t *ctlr = heap_caps_calloc(1, sizeof(sd_host_sdmmc_ctlr_t), SD_HOST_SDMMC_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(ctlr, ESP_ERR_NO_MEM, TAG, "no mem for sd host controller context");
ret = sd_host_claim_controller(ctlr);
if (ret != ESP_OK) {
//claim fail, clean and return directly
free(ctlr);
ESP_RETURN_ON_ERROR(ret, TAG, "no available sd host controller");
}
ctlr->dma_desc_num = config->dma_desc_num ? config->dma_desc_num : SD_HOST_SDMMC_DMA_DESC_CNT;
ctlr->dma_desc = heap_caps_calloc(1, sizeof(sdmmc_desc_t) * ctlr->dma_desc_num, SD_HOST_SDMMC_DMA_ALLOC_CAPS);
ESP_LOGD(TAG, "ctlr->dma_desc addr: %p", ctlr->dma_desc);
ESP_RETURN_ON_FALSE(ctlr->dma_desc, ESP_ERR_NO_MEM, TAG, "no mem for dma descriptors");
ctlr->io_intr_sem = xSemaphoreCreateBinaryWithCaps(SD_HOST_SDMMC_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(ctlr->io_intr_sem, ESP_ERR_NO_MEM, err, TAG, "no mem for io interrupt semaphore");
ctlr->mutex = xSemaphoreCreateMutexWithCaps(SD_HOST_SDMMC_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(ctlr->mutex, ESP_ERR_NO_MEM, err, TAG, "no mem for mutex");
ctlr->event_queue = xQueueCreateWithCaps(config->event_queue_items ? config->event_queue_items : 4, sizeof(sd_host_sdmmc_event_t), SD_HOST_SDMMC_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(ctlr->event_queue, ESP_ERR_NO_MEM, err, TAG, "no mem for mutex");
//use `ETS_SDIO_HOST_INTR_SOURCE` for now, replace with `sdmmc_periph.c` then. IDF-12585
ESP_GOTO_ON_ERROR(esp_intr_alloc(ETS_SDIO_HOST_INTR_SOURCE, 0, &sd_host_isr, ctlr, &ctlr->intr_handle), err, TAG, "failed to install interrupt service");
#if CONFIG_PM_ENABLE
ESP_GOTO_ON_ERROR(esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "sdmmc", &ctlr->pm_lock), err, TAG, "failed to create pm lock");
#endif //CONFIG_PM_ENABLE
sdmmc_hal_init(&ctlr->hal);
PERIPH_RCC_ATOMIC() {
sdmmc_ll_pad_set_pin_dedicated_ctrl(ctlr->hal.dev, true);
}
ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src(SDMMC_CLK_SRC_DEFAULT, true), err, TAG, "failed to acquire clk");
uint32_t src_freq_hz = 0;
esp_clk_tree_src_get_freq_hz(SDMMC_CLK_SRC_DEFAULT, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_freq_hz);
ESP_EARLY_LOGI(TAG, "src_freq_hz: %d", src_freq_hz);
sd_host_set_clk_div(ctlr, SDMMC_CLK_SRC_DEFAULT, SDMMC_LL_DEFAULT_DIV);
// Reset
esp_err_t err = sd_host_reset(ctlr);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sd_host_reset returned 0x%x", __func__, err);
return err;
}
ESP_LOGD(TAG, "peripheral version %"PRIx32", hardware config %08"PRIx32, sdmmc_ll_get_version_id(ctlr->hal.dev), sdmmc_ll_get_hw_config_info(ctlr->hal.dev));
sdmmc_ll_clear_interrupt(ctlr->hal.dev, 0xffffffff);
sdmmc_ll_enable_interrupt(ctlr->hal.dev, 0xffffffff, false);
sdmmc_ll_enable_global_interrupt(ctlr->hal.dev, false);
sdmmc_ll_init_dma(ctlr->hal.dev);
ctlr->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
ctlr->drv.del_ctlr = sd_host_del_sdmmc_controller;
*ret_handle = &ctlr->drv;
return ESP_OK;
err:
sd_host_del_sdmmc_controller(&ctlr->drv);
return ret;
}
esp_err_t sd_host_sdmmc_controller_add_slot(sd_host_ctlr_handle_t ctlr, const sd_host_slot_sdmmc_init_cfg_t *config, sd_host_slot_handle_t *ret_handle)
{
esp_err_t ret = ESP_FAIL;
sd_host_sdmmc_ctlr_t *ctlr_ctx = __containerof(ctlr, sd_host_sdmmc_ctlr_t, drv);
ESP_RETURN_ON_FALSE(ctlr && config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(config->slot_id >= 0 && config->slot_id < SOC_SDMMC_NUM_SLOTS, ESP_ERR_INVALID_ARG, TAG, "invalid slot id");
sd_host_sdmmc_slot_t *slot = heap_caps_calloc(1, sizeof(sd_host_sdmmc_slot_t), SD_HOST_SDMMC_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(slot, ESP_ERR_NO_MEM, TAG, "no mem for sd host slot context");
xSemaphoreTake(ctlr_ctx->mutex, portMAX_DELAY);
bool slot_available = false;
portENTER_CRITICAL(&ctlr_ctx->spinlock);
if (!ctlr_ctx->slot[config->slot_id]) {
slot_available = true;
ctlr_ctx->slot[config->slot_id] = slot;
}
portEXIT_CRITICAL(&ctlr_ctx->spinlock);
ESP_GOTO_ON_FALSE(slot_available, ESP_ERR_INVALID_STATE, err, TAG, "slot is not available");
portENTER_CRITICAL(&ctlr_ctx->spinlock);
slot->slot_id = config->slot_id;
slot->sd_mode = config->sd_mode;
slot->freq.freq_hz = 400 * 1000;
slot->freq.freq_state = SD_HOST_SLOT_STATE_INIT;
slot->ctlr = ctlr_ctx;
ctlr_ctx->registered_slot_nums = ctlr_ctx->registered_slot_nums + 1;
slot->drv.configure = sd_host_slot_sdmmc_configure;
slot->drv.do_transaction = sd_host_slot_sdmmc_do_transaction;
slot->drv.register_cbs = sd_host_slot_sdmmc_register_event_callbacks;
slot->drv.remove_slot = sd_host_controller_remove_sdmmc_slot;
slot->drv.set_cclk_always_on = sd_host_slot_sdmmc_set_cclk_always_on;
slot->drv.enable_io_int = sd_host_slot_sdmmc_io_int_enable;
slot->drv.wait_io_int = sd_host_slot_sdmmc_io_int_wait;
slot->drv.get_info = sd_host_slot_sdmmc_get_info;
portEXIT_CRITICAL(&ctlr_ctx->spinlock);
ESP_GOTO_ON_ERROR(sdmmc_slot_io_config(slot, config), err, TAG, "failed to config slot io");
portENTER_CRITICAL(&ctlr_ctx->spinlock);
if (ctlr_ctx->registered_slot_nums == 1) {
sdmmc_ll_clear_interrupt(ctlr_ctx->hal.dev, 0xffffffff);
sdmmc_ll_enable_interrupt(ctlr_ctx->hal.dev, SDMMC_LL_EVENT_DEFAULT, true);
sdmmc_ll_enable_global_interrupt(ctlr_ctx->hal.dev, true);
}
portEXIT_CRITICAL(&ctlr_ctx->spinlock);
*ret_handle = &slot->drv;
xSemaphoreGive(ctlr_ctx->mutex);
return ESP_OK;
err:
if (slot_available) {
portENTER_CRITICAL(&ctlr_ctx->spinlock);
ctlr_ctx->slot[config->slot_id] = NULL;
ctlr_ctx->registered_slot_nums -= 1;
portEXIT_CRITICAL(&ctlr_ctx->spinlock);
}
xSemaphoreGive(ctlr_ctx->mutex);
free(slot);
return ret;
}
/*---------------------------------------------------------------
Public API Impls
---------------------------------------------------------------*/
static esp_err_t sd_host_slot_sdmmc_configure(sd_host_slot_handle_t slot, const sd_host_slot_cfg_t *config)
{
ESP_RETURN_ON_FALSE(slot && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
#if SDMMC_LL_DELAY_PHASE_SUPPORTED
ESP_RETURN_ON_FALSE(config->delayphase < (SOC_SDMMC_DELAY_PHASE_NUM + 1), ESP_ERR_INVALID_ARG, TAG, "invalid delay phase");
#else
//DIG-217
ESP_LOGW(TAG, "esp32 doesn't support input phase delay, fallback to 0 delay");
#endif
#if SOC_SDMMC_UHS_I_SUPPORTED
ESP_RETURN_ON_FALSE(config->delayline < (SOC_SDMMC_DELAY_PHASE_NUM + 1), ESP_ERR_INVALID_ARG, TAG, "invalid delay line");
#else
ESP_LOGW(TAG, "input line delay not supported, fallback to 0 delay");
#endif
#if !SDMMC_LL_SDR104_SUPPORTED
if (config->sampling_mode == SD_SAMPLING_MODE_SDR) {
ESP_RETURN_ON_FALSE(config->freq_hz < SDMMC_FREQ_SDR104 * 1000, ESP_ERR_NOT_SUPPORTED, TAG, "UHS-I SDR104 is not supported");
}
#endif
#if !SDMMC_LL_DDR50_SUPPORTED
if (config->sampling_mode == SD_SAMPLING_MODE_DDR) {
ESP_RETURN_ON_FALSE(config->freq_hz < SDMMC_FREQ_DDR50 * 1000, ESP_ERR_NOT_SUPPORTED, TAG, "UHS-I DDR50 is not supported");
}
#endif
#if !SDMMC_LL_SDR50_SUPPORTED
if (config->sampling_mode == SD_SAMPLING_MODE_SDR) {
ESP_RETURN_ON_FALSE(config->freq_hz < SDMMC_FREQ_SDR50 * 1000, ESP_ERR_NOT_SUPPORTED, TAG, "UHS-I SDR50 is not supported");
}
#endif
#if CONFIG_IDF_TARGET_ESP32P4
if (config->freq_hz == SDMMC_FREQ_SDR104 * 1000) {
unsigned chip_version = efuse_hal_chip_revision();
ESP_LOGD(TAG, "chip_version: %d", chip_version);
if (!ESP_CHIP_REV_ABOVE(chip_version, 300)) {
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "UHS-I SDR104 is not supported on ESP32P4 chips prior than v3.0");
}
}
#endif
sd_host_sdmmc_slot_t *slot_ctx = __containerof(slot, sd_host_sdmmc_slot_t, drv);
portENTER_CRITICAL(&slot_ctx->ctlr->spinlock);
if (config->freq_hz != 0) {
slot_ctx->freq.freq_hz = config->freq_hz;
slot_ctx->freq.freq_state = SD_HOST_SLOT_STATE_READY;
}
if (config->width != 0) {
slot_ctx->width.width_state = SD_HOST_SLOT_STATE_READY;
}
if (config->sampling_mode != 0) {
slot_ctx->sampling_mode.mode = config->sampling_mode;
slot_ctx->sampling_mode.sampling_mode_state = SD_HOST_SLOT_STATE_READY;
}
if (config->delayphase != 0) {
#if SDMMC_LL_DELAY_PHASE_SUPPORTED
slot_ctx->delay_phase.delayphase = config->delayphase;
#else
slot_ctx->delay_phase.delayphase = SDMMC_DELAY_PHASE_0;
#endif
slot_ctx->delay_phase.delay_phase_state = SD_HOST_SLOT_STATE_READY;
}
if (config->delayline != 0) {
#if SOC_SDMMC_UHS_I_SUPPORTED
slot_ctx->delay_line.delayline = config->delayline;
#else
slot_ctx->delay_line.delayline = SDMMC_DELAY_LINE_0;
#endif
slot_ctx->delay_line.delay_line_state = SD_HOST_SLOT_STATE_READY;
}
portEXIT_CRITICAL(&slot_ctx->ctlr->spinlock);
return ESP_OK;
}
static esp_err_t sd_host_slot_sdmmc_register_event_callbacks(sd_host_slot_handle_t slot, const sd_host_evt_cbs_t *cbs, void *user_data)
{
ESP_RETURN_ON_FALSE(slot && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
#if CONFIG_CAM_CTLR_MIPI_CSI_ISR_CACHE_SAFE
if (cbs->on_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_trans_done callback not in IRAM");
}
if (cbs->on_io_interrupt) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_io_interrupt), ESP_ERR_INVALID_ARG, TAG, "on_io_interrupt callback not in IRAM");
}
#endif
sd_host_sdmmc_slot_t *slot_ctx = __containerof(slot, sd_host_sdmmc_slot_t, drv);
portENTER_CRITICAL(&slot_ctx->ctlr->spinlock);
slot_ctx->cbs.on_trans_done = cbs->on_trans_done;
slot_ctx->cbs.on_io_interrupt = cbs->on_io_interrupt;
slot_ctx->user_data = user_data;
portEXIT_CRITICAL(&slot_ctx->ctlr->spinlock);
return ESP_OK;
}
esp_err_t sd_host_slot_set_cclk_always_on_internal(sd_host_sdmmc_slot_t *slot, bool always_on)
{
ESP_RETURN_ON_FALSE(slot, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
// During initialization this is not protected by a mutex
if (always_on) {
sdmmc_ll_enable_card_clock_low_power(slot->ctlr->hal.dev, slot->slot_id, false);
} else {
sdmmc_ll_enable_card_clock_low_power(slot->ctlr->hal.dev, slot->slot_id, true);
}
sd_host_slot_clock_update_command(slot, false);
return ESP_OK;
}
static esp_err_t sd_host_slot_sdmmc_set_cclk_always_on(sd_host_slot_handle_t slot, bool always_on)
{
esp_err_t ret = ESP_FAIL;
sd_host_sdmmc_slot_t *slot_ctx = __containerof(slot, sd_host_sdmmc_slot_t, drv);
xSemaphoreTake(slot_ctx->ctlr->mutex, portMAX_DELAY);
ret = sd_host_slot_set_cclk_always_on_internal(slot_ctx, always_on);
xSemaphoreGive(slot_ctx->ctlr->mutex);
ESP_RETURN_ON_ERROR(ret, TAG, "failed to set cclk always on");
return ESP_OK;
}
static esp_err_t sd_host_controller_remove_sdmmc_slot(sd_host_slot_handle_t slot)
{
ESP_RETURN_ON_FALSE(slot, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
sd_host_sdmmc_slot_t *slot_ctx = __containerof(slot, sd_host_sdmmc_slot_t, drv);
sd_host_sdmmc_ctlr_t *ctlr = slot_ctx->ctlr;
xSemaphoreTake(ctlr->mutex, portMAX_DELAY);
bool slot_registered = false;
bool no_registered_slots = false;
portENTER_CRITICAL(&ctlr->spinlock);
if (ctlr->slot[slot_ctx->slot_id]) {
slot_registered = true;
ctlr->slot[slot_ctx->slot_id] = NULL;
ctlr->registered_slot_nums -= 1;
no_registered_slots = (ctlr->registered_slot_nums == 0);
if (no_registered_slots) {
sdmmc_ll_enable_global_interrupt(ctlr->hal.dev, false);
sdmmc_ll_enable_interrupt(ctlr->hal.dev, 0xffffffff, false);
sdmmc_ll_clear_interrupt(ctlr->hal.dev, 0xffffffff);
sdmmc_ll_clear_idsts_interrupt(ctlr->hal.dev, 0xffffffff);
}
}
portEXIT_CRITICAL(&ctlr->spinlock);
if (!slot_registered) {
xSemaphoreGive(ctlr->mutex);
ESP_LOGE(TAG, "slot is not registered");
return ESP_ERR_INVALID_STATE;
}
if (slot_ctx->io_config.clk_io != GPIO_NUM_NC) {
gpio_output_disable(slot_ctx->io_config.clk_io);
}
if (slot_ctx->io_config.cmd_io != GPIO_NUM_NC) {
gpio_output_disable(slot_ctx->io_config.cmd_io);
}
if (slot_ctx->io_config.cd_io != GPIO_NUM_NC) {
gpio_output_disable(slot_ctx->io_config.cd_io);
}
if (slot_ctx->io_config.wp_io != GPIO_NUM_NC) {
gpio_output_disable(slot_ctx->io_config.wp_io);
}
if (slot_ctx->io_config.width >= 1) {
gpio_output_disable(slot_ctx->io_config.d0_io);
}
if (slot_ctx->io_config.width >= 4) {
gpio_output_disable(slot_ctx->io_config.d1_io);
gpio_output_disable(slot_ctx->io_config.d2_io);
gpio_output_disable(slot_ctx->io_config.d3_io);
}
if (slot_ctx->io_config.width == 8) {
gpio_output_disable(slot_ctx->io_config.d4_io);
gpio_output_disable(slot_ctx->io_config.d5_io);
gpio_output_disable(slot_ctx->io_config.d6_io);
gpio_output_disable(slot_ctx->io_config.d7_io);
}
xSemaphoreGive(ctlr->mutex);
free(slot);
return ESP_OK;
}
static esp_err_t sd_host_del_sdmmc_controller(sd_host_ctlr_handle_t ctlr)
{
esp_err_t ret = ESP_OK;
sd_host_sdmmc_ctlr_t *ctlr_ctx = __containerof(ctlr, sd_host_sdmmc_ctlr_t, drv);
ESP_RETURN_ON_FALSE(ctlr, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
if (ctlr_ctx->mutex) {
xSemaphoreTake(ctlr_ctx->mutex, portMAX_DELAY);
}
ESP_GOTO_ON_FALSE(ctlr_ctx->registered_slot_nums == 0, ESP_ERR_INVALID_STATE, err, TAG, "host controller with slot registered");
ESP_GOTO_ON_ERROR(sd_host_declaim_controller(ctlr_ctx), err, TAG, "controller isn't in use");
ESP_GOTO_ON_ERROR(esp_intr_free(ctlr_ctx->intr_handle), err, TAG, "failed to delete interrupt service");
sdmmc_hal_deinit(&ctlr_ctx->hal);
if (ctlr_ctx->event_queue) {
vQueueDeleteWithCaps(ctlr_ctx->event_queue);
}
if (ctlr_ctx->io_intr_sem) {
vSemaphoreDeleteWithCaps(ctlr_ctx->io_intr_sem);
}
#if CONFIG_PM_ENABLE
if (ctlr_ctx->pm_lock) {
esp_pm_lock_delete(ctlr_ctx->pm_lock);
}
#endif
#if SDMMC_LL_MPLL_SUPPORTED
esp_clk_tree_enable_src(SOC_MOD_CLK_MPLL, false);
#endif
if (ctlr_ctx->mutex) {
vSemaphoreDeleteWithCaps(ctlr_ctx->mutex);
}
free(ctlr_ctx->dma_desc);
ctlr_ctx->dma_desc = NULL;
free(ctlr_ctx);
ctlr_ctx = NULL;
return ESP_OK;
err:
if (ctlr_ctx->mutex) {
xSemaphoreGive(ctlr_ctx->mutex);
}
return ret;
}
static esp_err_t sd_host_slot_sdmmc_io_int_enable(sd_host_slot_handle_t slot)
{
ESP_RETURN_ON_FALSE(slot, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
sd_host_sdmmc_slot_t *slot_ctx = __containerof(slot, sd_host_sdmmc_slot_t, drv);
configure_pin(slot_ctx->io_config.d1_io, sdmmc_slot_gpio_sig[slot_ctx->slot_id].d1, GPIO_MODE_INPUT_OUTPUT, "d1", slot_ctx->use_gpio_matrix);
return ESP_OK;
}
static esp_err_t sd_host_slot_sdmmc_io_int_wait(sd_host_slot_handle_t slot, TickType_t timeout_ticks)
{
ESP_RETURN_ON_FALSE(slot, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
esp_err_t ret = ESP_FAIL;
sd_host_sdmmc_slot_t *slot_ctx = __containerof(slot, sd_host_sdmmc_slot_t, drv);
/* SDIO interrupts are negedge sensitive ones: the status bit is only set
* when first interrupt triggered.
*
* If D1 GPIO is low when entering this function, we know that interrupt
* (in SDIO sense) has occurred and we don't need to use SDMMC peripheral
* interrupt.
*/
/* Disable SDIO interrupt */
if (slot_ctx->slot_id == 0) {
sdmmc_ll_enable_interrupt(slot_ctx->ctlr->hal.dev, SDMMC_INTMASK_IO_SLOT0, false);
sdmmc_ll_clear_interrupt(slot_ctx->ctlr->hal.dev, SDMMC_INTMASK_IO_SLOT0);
} else {
sdmmc_ll_enable_interrupt(slot_ctx->ctlr->hal.dev, SDMMC_INTMASK_IO_SLOT1, false);
sdmmc_ll_clear_interrupt(slot_ctx->ctlr->hal.dev, SDMMC_INTMASK_IO_SLOT1);
}
if (gpio_get_level(slot_ctx->io_config.d1_io) == 0) {
return ESP_OK;
}
/* Otherwise, need to wait for an interrupt. Since D1 was high,
* SDMMC peripheral interrupt is guaranteed to trigger on negedge.
*/
xSemaphoreTake(slot_ctx->ctlr->io_intr_sem, 0);
/* Re-enable SDIO interrupt */
if (slot_ctx->slot_id == 0) {
sdmmc_ll_enable_interrupt(slot_ctx->ctlr->hal.dev, SDMMC_INTMASK_IO_SLOT0, true);
} else {
sdmmc_ll_enable_interrupt(slot_ctx->ctlr->hal.dev, SDMMC_INTMASK_IO_SLOT1, true);
}
if (xSemaphoreTake(slot_ctx->ctlr->io_intr_sem, timeout_ticks) == pdTRUE) {
ret = ESP_OK;
} else {
ret = ESP_ERR_TIMEOUT;
}
return ret;
}
static esp_err_t sd_host_slot_sdmmc_get_info(sd_host_slot_handle_t slot, sd_host_slot_info_t *info)
{
ESP_RETURN_ON_FALSE(slot && info, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
sd_host_sdmmc_slot_t *slot_ctx = __containerof(slot, sd_host_sdmmc_slot_t, drv);
portENTER_CRITICAL(&slot_ctx->ctlr->spinlock);
info->freq_hz = slot_ctx->freq.real_freq_hz;
info->width = slot_ctx->width.width;
info->sd_mode = slot_ctx->sd_mode;
info->sampling_mode = slot_ctx->sampling_mode.mode;
portEXIT_CRITICAL(&slot_ctx->ctlr->spinlock);
return ESP_OK;
}
/*---------------------------------------------------------------
Internal APIs
---------------------------------------------------------------*/
bool sd_host_check_buffer_alignment(sd_host_sdmmc_slot_t *slot, const void *buf, size_t size)
{
//for future-proof
(void)slot;
if (!buf || !size) {
return false;
}
#if !SOC_SDMMC_PSRAM_DMA_CAPABLE
// The SDMMC peripheral's DMA cannot reach PSRAM on this target, so a PSRAM
// buffer can never be used directly regardless of its alignment. Reporting
// it as not directly usable makes the protocol layer fall back to an
// internal DMA-capable buffer.
if (esp_ptr_external_ram(buf)) {
return false;
}
#endif
esp_err_t ret = ESP_FAIL;
int cache_flags = 0;
size_t cache_alignment_bytes = 0;
if (esp_ptr_external_ram(buf)) {
cache_flags |= MALLOC_CAP_SPIRAM;
}
ret = esp_cache_get_alignment(cache_flags, &cache_alignment_bytes);
assert(ret == ESP_OK);
(void)ret;
bool is_aligned = false;
size_t alignment = 0;
if (cache_alignment_bytes != 0) {
alignment = cache_alignment_bytes;
} else {
alignment = SDMMC_DMA_ALIGNMENT;
}
is_aligned = ((intptr_t)buf % alignment == 0) && (size % alignment == 0);
return is_aligned;
}
esp_err_t sd_host_slot_set_card_clk(sd_host_sdmmc_slot_t *slot)
{
// Disable clock first
sdmmc_ll_enable_card_clock(slot->ctlr->hal.dev, slot->slot_id, false);
esp_err_t ret = sd_host_slot_clock_update_command(slot, false);
ESP_RETURN_ON_ERROR(ret, TAG, "%s: sd_host_slot_clock_update_command returned 0x%x, failed to disable clk, ", __func__, ret);
int freq_khz = 0;
if (slot->freq.freq_state == SD_HOST_SLOT_STATE_READY) {
freq_khz = slot->freq.freq_hz / 1000;
} else {
freq_khz = 400;
}
ESP_LOGD(TAG, "freq: %d khz, state: %d", freq_khz, slot->freq.freq_state);
soc_periph_sdmmc_clk_src_t clk_src = 0;
int host_div = 0; /* clock divider of the host (SDMMC.clock) */
int card_div = 0; /* 1/2 of card clock divider (SDMMC.clkdiv) */
sd_host_slot_get_clk_dividers(slot, freq_khz, &host_div, &card_div, &clk_src);
int real_freq_khz = sd_host_calc_freq(clk_src, host_div, card_div);
slot->freq.real_freq_hz = real_freq_khz * 1000;
ESP_LOGD(TAG, "slot_id=%d clk_src=%d host_div=%d card_div=%d freq=%dkHz (max %" PRIu32 "kHz)", slot->slot_id, clk_src, host_div, card_div, real_freq_khz, freq_khz);
// Program card clock settings, send them to the CIU
sdmmc_ll_set_card_clock_div(slot->ctlr->hal.dev, slot->slot_id, card_div);
sd_host_set_clk_div(slot->ctlr, clk_src, host_div);
ret = sd_host_slot_clock_update_command(slot, false);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "setting clk div failed");
ESP_LOGE(TAG, "%s: sd_host_slot_clock_update_command returned 0x%x", __func__, ret);
return ret;
}
// Re-enable clocks
sdmmc_ll_enable_card_clock(slot->ctlr->hal.dev, slot->slot_id, true);
sdmmc_ll_enable_card_clock_low_power(slot->ctlr->hal.dev, slot->slot_id, true);
ret = sd_host_slot_clock_update_command(slot, false);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "re-enabling clk failed");
ESP_LOGE(TAG, "%s: sd_host_slot_clock_update_command returned 0x%x", __func__, ret);
return ret;
}
const uint32_t data_timeout_ms = 100;
uint32_t data_timeout_cycles = data_timeout_ms * freq_khz;
sdmmc_ll_set_data_timeout(slot->ctlr->hal.dev, data_timeout_cycles);
// always set response timeout to highest value, it's small enough anyway
sdmmc_ll_set_response_timeout(slot->ctlr->hal.dev, 255);
return ESP_OK;
}
esp_err_t sd_host_slot_get_real_freq(sd_host_sdmmc_slot_t *slot, int *real_freq_khz)
{
ESP_RETURN_ON_FALSE(slot && real_freq_khz, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
int host_div = 0;
int card_div = 0;
host_div = sdmmc_ll_get_clock_div(slot->ctlr->hal.dev);
card_div = sdmmc_ll_get_card_clock_div(slot->ctlr->hal.dev, slot->slot_id);
*real_freq_khz = sd_host_calc_freq(slot->clk_src, host_div, card_div);
return ESP_OK;
}
esp_err_t sd_host_slot_get_calc_real_freq(sd_host_sdmmc_slot_t *slot, int *real_freq_khz)
{
ESP_RETURN_ON_FALSE(slot && real_freq_khz, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
soc_periph_sdmmc_clk_src_t clk_src = 0;
int host_div = 0;
int card_div = 0;
sd_host_slot_get_clk_dividers(slot, slot->freq.freq_hz / 1000, &host_div, &card_div, &clk_src);
*real_freq_khz = sd_host_calc_freq(slot->clk_src, host_div, card_div);
return ESP_OK;
}
esp_err_t sd_host_slot_set_bus_width(sd_host_sdmmc_slot_t *slot)
{
ESP_RETURN_ON_FALSE(slot, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
int width = slot->width.width_state == SD_HOST_SLOT_STATE_READY ? slot->width.width : 1;
ESP_LOGD(TAG, "width: %d, state: %d", width, slot->width.width_state);
sd_host_sdmmc_slot_io_cfg_t *slot_gpio = &slot->io_config;
if (width == 1) {
sdmmc_ll_set_card_width(slot->ctlr->hal.dev, slot->slot_id, SD_BUS_WIDTH_1_BIT);
} else if (width == 4) {
sdmmc_ll_set_card_width(slot->ctlr->hal.dev, slot->slot_id, SD_BUS_WIDTH_4_BIT);
// D3 was set to GPIO high to force slave into SD mode, until 4-bit mode is set
configure_pin(slot_gpio->d3_io, sdmmc_slot_gpio_sig[slot->slot_id].d3, GPIO_MODE_INPUT_OUTPUT, "d3", slot->use_gpio_matrix);
} else if (width == 8) {
sdmmc_ll_set_card_width(slot->ctlr->hal.dev, slot->slot_id, SD_BUS_WIDTH_8_BIT);
// D3 was set to GPIO high to force slave into SD mode, until 4-bit mode is set
configure_pin(slot_gpio->d3_io, sdmmc_slot_gpio_sig[slot->slot_id].d3, GPIO_MODE_INPUT_OUTPUT, "d3", slot->use_gpio_matrix);
} else {
return ESP_ERR_INVALID_ARG;
}
ESP_LOGD(TAG, "slot_id=%d width=%d", slot->slot_id, width);
return ESP_OK;
}
esp_err_t sd_host_slot_set_bus_sampling_mode(sd_host_sdmmc_slot_t *slot)
{
ESP_RETURN_ON_FALSE(slot, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
if (slot->sampling_mode.sampling_mode_state == SD_HOST_SLOT_STATE_READY) {
ESP_RETURN_ON_FALSE((slot->width.width != 8), ESP_ERR_NOT_SUPPORTED, TAG, "DDR mode with 8-bit bus width is not supported yet");
}
bool ddr = false;
if (slot->sampling_mode.sampling_mode_state == SD_HOST_SLOT_STATE_READY) {
if (slot->sampling_mode.mode == SD_SAMPLING_MODE_DDR) {
ddr = true;
}
}
sdmmc_ll_enable_ddr_mode(slot->ctlr->hal.dev, slot->slot_id, ddr);
ESP_LOGD(TAG, "slot_id=%d ddr=%d", slot->slot_id, ddr ? 1 : 0);
return ESP_OK;
}
esp_err_t sd_host_set_delay_phase(sd_host_sdmmc_slot_t *slot)
{
#if SDMMC_LL_DELAY_PHASE_SUPPORTED
ESP_RETURN_ON_FALSE(slot, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
sdmmc_delay_phase_t delay_phase = SDMMC_DELAY_PHASE_0;
if (slot->delay_phase.delay_phase_state == SD_HOST_SLOT_STATE_READY) {
delay_phase = slot->delay_phase.delayphase;
}
uint32_t clk_src_freq_hz = 0;
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(slot->clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_src_freq_hz),
TAG, "get source clock frequency failed");
//Now we're in high speed. Note ESP SDMMC Host HW only supports integer divider.
int delay_phase_num = 0;
sdmmc_ll_delay_phase_t phase = SDMMC_LL_DELAY_PHASE_0;
sdmmc_ll_speed_mode_t speed_mode = (slot->freq.freq_hz / 1000) == SDMMC_FREQ_SDR104 ? SDMMC_LL_SPEED_MODE_HS : SDMMC_LL_SPEED_MODE_LS;
switch (delay_phase) {
case SDMMC_DELAY_PHASE_1:
phase = SDMMC_LL_DELAY_PHASE_1;
delay_phase_num = 1;
break;
case SDMMC_DELAY_PHASE_2:
phase = SDMMC_LL_DELAY_PHASE_2;
delay_phase_num = 2;
break;
case SDMMC_DELAY_PHASE_3:
phase = SDMMC_LL_DELAY_PHASE_3;
delay_phase_num = 3;
break;
#if SOC_SDMMC_DELAY_PHASE_NUM > 4
case SDMMC_DELAY_PHASE_4:
phase = SDMMC_LL_DELAY_PHASE_4;
delay_phase_num = 4;
break;
case SDMMC_DELAY_PHASE_5:
phase = SDMMC_LL_DELAY_PHASE_5;
delay_phase_num = 5;
break;
case SDMMC_DELAY_PHASE_6:
phase = SDMMC_LL_DELAY_PHASE_6;
delay_phase_num = 6;
break;
case SDMMC_DELAY_PHASE_7:
phase = SDMMC_LL_DELAY_PHASE_7;
delay_phase_num = 7;
break;
#endif
default:
phase = SDMMC_LL_DELAY_PHASE_0;
delay_phase_num = 0;
break;
}
PERIPH_RCC_ATOMIC() {
sdmmc_ll_set_din_delay_phase(slot->ctlr->hal.dev, phase, speed_mode);
sdmmc_ll_set_dout_delay_phase(slot->ctlr->hal.dev, phase, speed_mode);
}
int src_clk_period_ps = (1 * 1000 * 1000) / (clk_src_freq_hz / (1 * 1000 * 1000));
int delay_phase_total_num = (slot->freq.freq_hz / 1000) == SDMMC_FREQ_SDR104 ? SOC_SDMMC_DELAY_PHASE_NUM : SDMMC_LL_DELAY_MAX_NUMS_LS;
ESP_LOGD(TAG, "slot->freq.freq_hz: %d, delay_phase_total_num: %d", slot->freq.freq_hz, delay_phase_total_num);
int phase_diff_ps = src_clk_period_ps * sdmmc_ll_get_clock_div(slot->ctlr->hal.dev) / delay_phase_total_num;
ESP_LOGD(TAG, "difference between delay phases is %d ps", phase_diff_ps);
ESP_LOGD(TAG, "host sampling edge is delayed by %d ps", phase_diff_ps * delay_phase_num);
return ESP_OK;
#else
//DIG-217
ESP_LOGD(TAG, "esp32 doesn't support phase delay, fallback to 0 delay");
return ESP_OK;
#endif
}
esp_err_t sd_host_set_delay_line(sd_host_sdmmc_slot_t *slot)
{
#if SOC_SDMMC_UHS_I_SUPPORTED
ESP_RETURN_ON_FALSE(slot, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
if ((slot->freq.freq_hz / 1000) == SDMMC_FREQ_SDR104) {
sdmmc_delay_line_t delay_line = SDMMC_DELAY_LINE_0;
if (slot->delay_line.delay_line_state == SD_HOST_SLOT_STATE_READY) {
delay_line = slot->delay_line.delayline;
}
sdmmc_ll_delay_line_t line = SDMMC_LL_DELAY_LINE_0;
switch (delay_line) {
case SDMMC_DELAY_LINE_1:
line = SDMMC_LL_DELAY_LINE_1;
break;
case SDMMC_DELAY_LINE_2:
line = SDMMC_LL_DELAY_LINE_2;
break;
case SDMMC_DELAY_LINE_3:
line = SDMMC_LL_DELAY_LINE_3;
break;
case SDMMC_DELAY_LINE_4:
line = SDMMC_LL_DELAY_LINE_4;
break;
case SDMMC_DELAY_LINE_5:
line = SDMMC_LL_DELAY_LINE_5;
break;
case SDMMC_DELAY_LINE_6:
line = SDMMC_LL_DELAY_LINE_6;
break;
case SDMMC_DELAY_LINE_7:
line = SDMMC_LL_DELAY_LINE_7;
break;
default:
line = SDMMC_LL_DELAY_LINE_0;
break;
}
sdmmc_ll_set_din_delay_line(slot->ctlr->hal.dev, line, SDMMC_LL_SPEED_MODE_HS);
sdmmc_ll_set_dout_delay_line(slot->ctlr->hal.dev, line, SDMMC_LL_SPEED_MODE_HS);
}
return ESP_OK;
#else
ESP_LOGD(TAG, "delay line not supported, fallback to 0 delay line");
return ESP_OK;
#endif
}
void sd_host_slot_enable_clk_cmd11(sd_host_sdmmc_slot_t *slot, bool enable)
{
sdmmc_ll_enable_card_clock(slot->ctlr->hal.dev, slot->slot_id, enable);
sd_host_slot_clock_update_command(slot, true);
sdmmc_ll_enable_1v8_mode(slot->ctlr->hal.dev, slot->slot_id, enable);
}
/*---------------------------------------------------------------
Static: Controller Allocation
---------------------------------------------------------------*/
static esp_err_t sd_host_claim_controller(sd_host_sdmmc_ctlr_t *controller)
{
assert(controller);
_lock_acquire(&s_platform.mutex);
bool found = false;
for (int i = 0; i < SDMMC_LL_HOST_CTLR_NUMS; i ++) {
found = !s_platform.controllers[i];
if (found) {
s_platform.controllers[i] = controller;
controller->host_id = i;
PERIPH_RCC_ATOMIC() {
sdmmc_ll_enable_bus_clock(i, true);
sdmmc_ll_reset_register(i);
}
break;
}
}
_lock_release(&s_platform.mutex);
if (!found) {
return ESP_ERR_NOT_FOUND;
}
return ESP_OK;
}
static esp_err_t sd_host_declaim_controller(sd_host_sdmmc_ctlr_t *controller)
{
assert(controller);
_lock_acquire(&s_platform.mutex);
s_platform.controllers[controller->host_id] = NULL;
PERIPH_RCC_ATOMIC() {
sdmmc_ll_enable_bus_clock(0, false);
}
_lock_release(&s_platform.mutex);
return ESP_OK;
}
/*---------------------------------------------------------------
Static: ISR Handler
---------------------------------------------------------------*/
static void sd_host_isr(void *arg)
{
sd_host_sdmmc_ctlr_t *ctlr = (sd_host_sdmmc_ctlr_t *)arg;
sd_host_sdmmc_slot_t *slot = ctlr->slot[ctlr->cur_slot_id];
if (slot == NULL) {
uint32_t pending = sdmmc_ll_get_intr_status(ctlr->hal.dev);
uint32_t dma_pending = sdmmc_ll_get_idsts_interrupt_raw(ctlr->hal.dev);
sdmmc_ll_clear_interrupt(ctlr->hal.dev, pending);
sdmmc_ll_clear_idsts_interrupt(ctlr->hal.dev, dma_pending);
return;
}
sd_host_sdmmc_event_t event = {};
bool need_yield = false;
int higher_priority_task_awoken = pdFALSE;
uint32_t pending = sdmmc_ll_get_intr_status(ctlr->hal.dev) & SDMMC_LL_SD_EVENT_MASK;
sdmmc_ll_clear_interrupt(ctlr->hal.dev, pending);
event.sdmmc_status = pending;
uint32_t dma_pending = sdmmc_ll_get_idsts_interrupt_raw(ctlr->hal.dev);
sdmmc_ll_clear_idsts_interrupt(ctlr->hal.dev, dma_pending);
if (dma_pending & SDMMC_LL_EVENT_DMA_NI) {
// refill DMA descriptors
size_t free_desc = sd_host_get_free_descriptors_count(slot);
if (free_desc > 0) {
sd_host_fill_dma_descriptors(slot, free_desc);
sd_host_dma_resume(slot);
}
//NI, logic OR of TI and RI. This is a sticky bit and must be cleared each time TI or RI is cleared.
dma_pending &= ~(SDMMC_LL_EVENT_DMA_NI | SDMMC_LL_EVENT_DMA_TI | SDMMC_LL_EVENT_DMA_RI);
}
event.dma_status = dma_pending & SDMMC_LL_EVENT_DMA_MASK;
if (pending != 0 || dma_pending != 0) {
xQueueSendFromISR(ctlr->event_queue, &event, &higher_priority_task_awoken);
if (slot->cbs.on_trans_done) {
sd_host_evt_data_t edata = {};
if (slot->cbs.on_trans_done(&slot->drv, &edata, slot->user_data)) {
need_yield |= true;
}
}
}
uint32_t sdio_pending = (sdmmc_ll_get_intr_status(ctlr->hal.dev) & (SDMMC_INTMASK_IO_SLOT1 | SDMMC_INTMASK_IO_SLOT0));
if (sdio_pending) {
// disable the interrupt (no need to clear here, this is done in sdmmc_host_io_int_wait)
sdmmc_ll_enable_interrupt(ctlr->hal.dev, sdio_pending, false);
xSemaphoreGiveFromISR(ctlr->io_intr_sem, &higher_priority_task_awoken);
if (slot->cbs.on_io_interrupt) {
sd_host_evt_data_t edata = {};
if (slot->cbs.on_io_interrupt(&slot->drv, &edata, slot->user_data)) {
need_yield |= true;
}
}
}
need_yield |= higher_priority_task_awoken == pdTRUE;
if (need_yield) {
portYIELD_FROM_ISR();
}
}
/*---------------------------------------------------------------
HW Commands
---------------------------------------------------------------*/
esp_err_t sd_host_slot_start_command(sd_host_sdmmc_slot_t *slot, sdmmc_hw_cmd_t cmd, uint32_t arg)
{
ESP_RETURN_ON_FALSE(slot, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
// if this isn't a clock update command, check the card detect status
if (!sdmmc_ll_is_card_detected(slot->ctlr->hal.dev, slot->slot_id) && !cmd.update_clk_reg) {
return ESP_ERR_NOT_FOUND;
}
if (cmd.data_expected && cmd.rw && sdmmc_ll_is_card_write_protected(slot->ctlr->hal.dev, slot->slot_id)) {
return ESP_ERR_INVALID_STATE;
}
/* Outputs should be synchronized to cclk_out */
cmd.use_hold_reg = 1;
int64_t yield_delay_us = 100 * 1000; // initially 100ms
int64_t t0 = esp_timer_get_time();
int64_t t1 = 0;
bool skip_wait = (cmd.volt_switch && cmd.update_clk_reg);
if (!skip_wait) {
while (!(sdmmc_ll_is_command_taken(slot->ctlr->hal.dev))) {
t1 = esp_timer_get_time();
if (t1 - t0 > SD_HOST_SDMMC_START_CMD_TIMEOUT_US) {
return ESP_ERR_TIMEOUT;
}
if (t1 - t0 > yield_delay_us) {
yield_delay_us *= 2;
vTaskDelay(1);
}
}
}
sdmmc_ll_set_command_arg(slot->ctlr->hal.dev, arg);
cmd.card_num = slot->slot_id;
cmd.start_command = 1;
sdmmc_ll_set_command(slot->ctlr->hal.dev, cmd);
while (!(sdmmc_ll_is_command_taken(slot->ctlr->hal.dev))) {
t1 = esp_timer_get_time();
if (t1 - t0 > SD_HOST_SDMMC_START_CMD_TIMEOUT_US) {
return ESP_ERR_TIMEOUT;
}
if (t1 - t0 > yield_delay_us) {
yield_delay_us *= 2;
vTaskDelay(1);
}
}
return ESP_OK;
}
static esp_err_t sd_host_slot_clock_update_command(sd_host_sdmmc_slot_t *slot, bool is_cmd11)
{
assert(slot);
// Clock update command (not a real command; just updates CIU registers)
sdmmc_hw_cmd_t cmd_val = {
.card_num = slot->slot_id,
.update_clk_reg = 1,
.wait_complete = 1
};
if (is_cmd11) {
cmd_val.volt_switch = 1;
}
ESP_RETURN_ON_ERROR(sd_host_slot_start_command(slot, cmd_val, 0), TAG, "sd_host_start_command returned 0x%x", err_rc_);
return ESP_OK;
}
/*---------------------------------------------------------------
Static: Controls
---------------------------------------------------------------*/
static void s_module_reset(sd_host_sdmmc_ctlr_t *ctlr)
{
// reset module
sdmmc_ll_reset_controller(ctlr->hal.dev);
sdmmc_ll_reset_dma(ctlr->hal.dev);
sdmmc_ll_reset_fifo(ctlr->hal.dev);
}
static bool s_is_module_reset_done(sd_host_sdmmc_ctlr_t *ctlr)
{
bool is_done = sdmmc_ll_is_controller_reset_done(ctlr->hal.dev) && sdmmc_ll_is_dma_reset_done(ctlr->hal.dev) && sdmmc_ll_is_fifo_reset_done(ctlr->hal.dev);
return is_done;
}
static esp_err_t sd_host_reset(sd_host_sdmmc_ctlr_t *ctlr)
{
s_module_reset(ctlr);
// Wait for the reset bits to be cleared by hardware
int64_t yield_delay_us = 100 * 1000; // initially 100ms
int64_t t0 = esp_timer_get_time();
int64_t t1 = 0;
while (!s_is_module_reset_done(ctlr)) {
t1 = esp_timer_get_time();
if (t1 - t0 > SD_HOST_SDMMC_RESET_TIMEOUT_US) {
return ESP_ERR_TIMEOUT;
}
if (t1 - t0 > yield_delay_us) {
yield_delay_us *= 2;
vTaskDelay(1);
}
}
return ESP_OK;
}
/*---------------------------------------------------------------
Static: Clock
---------------------------------------------------------------*/
/* We have two clock divider stages:
* - one is the clock generator which drives SDMMC peripheral,
* it can be configured using SDMMC.clock register. It can generate
* frequencies 160MHz/(N + 1), where 0 < N < 16, I.e. from 10 to 80 MHz.
* - 4 clock dividers inside SDMMC peripheral, which can divide clock
* from the first stage by 2 * M, where 0 < M < 255
* (they can also be bypassed).
*
* For cards which aren't UHS-1 or UHS-2 cards, which we don't support,
* maximum bus frequency in high speed (HS) mode is 50 MHz.
* Note: for non-UHS-1 cards, HS mode is optional.
* Default speed (DS) mode is mandatory, it works up to 25 MHz.
* Whether the card supports HS or not can be determined using TRAN_SPEED
* field of card's CSD register.
*
* 50 MHz can not be obtained exactly, closest we can get is 53 MHz.
*
* The first stage divider is set to the highest possible value for the given
* frequency, and the the second stage dividers are used if division factor
* is >16.
*
* Of the second stage dividers, div0 is used for card 0, and div1 is used
* for card 1.
*/
static void sd_host_set_clk_div(sd_host_sdmmc_ctlr_t *ctlr, soc_periph_sdmmc_clk_src_t src, int div)
{
ESP_ERROR_CHECK(esp_clk_tree_enable_src((soc_module_clk_t)src, true));
PERIPH_RCC_ATOMIC() {
sdmmc_ll_set_clock_div(ctlr->hal.dev, div);
sdmmc_ll_select_clk_source(ctlr->hal.dev, src);
sdmmc_ll_init_phase_delay(ctlr->hal.dev);
#if SDMMC_LL_SDIO_PLL_SUPPORTED
if (src == SDMMC_CLK_SRC_SDIO_200M) {
sdmmc_ll_enable_sdio_pll(ctlr->hal.dev, true);
}
#endif
}
// Wait for the clock to propagate
esp_rom_delay_us(10);
}
static void sd_host_slot_get_clk_dividers(sd_host_sdmmc_slot_t *slot, uint32_t freq_khz, int *host_div, int *card_div, soc_periph_sdmmc_clk_src_t *src)
{
uint32_t clk_src_freq_hz = 0;
soc_periph_sdmmc_clk_src_t clk_src = 0;
#if SOC_SDMMC_UHS_I_SUPPORTED && SDMMC_LL_SDIO_PLL_SUPPORTED
if (freq_khz > SDMMC_FREQ_HIGHSPEED) {
clk_src = SDMMC_CLK_SRC_SDIO_200M;
} else
#endif
{
clk_src = SDMMC_CLK_SRC_DEFAULT;
}
slot->clk_src = clk_src;
esp_err_t ret = esp_clk_tree_src_get_freq_hz(clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_src_freq_hz);
assert(ret == ESP_OK);
(void)ret;
ESP_LOGD(TAG, "clk_src_freq_hz: %"PRId32" hz", clk_src_freq_hz);
#if SDMMC_LL_MAX_FREQ_KHZ_FPGA
if (freq_khz >= SDMMC_LL_MAX_FREQ_KHZ_FPGA) {
ESP_LOGW(TAG, "working on FPGA, fallback to use the %d KHz", SDMMC_LL_MAX_FREQ_KHZ_FPGA);
freq_khz = SDMMC_LL_MAX_FREQ_KHZ_FPGA;
}
#endif
// Calculate new dividers
#if SOC_SDMMC_UHS_I_SUPPORTED
if (freq_khz == SDMMC_FREQ_SDR104) {
*host_div = clk_src_freq_hz / 200000000;
*card_div = 0;
} else if (freq_khz == SDMMC_FREQ_SDR50) {
*host_div = clk_src_freq_hz / 100000000;
*card_div = 0;
} else
#endif
#if !SDMMC_LL_MPLL_SUPPORTED
if (freq_khz >= SDMMC_FREQ_HIGHSPEED) {
*host_div = 4; // src_freq_hz / 4 = 40 MHz
*card_div = 0;
} else if (freq_khz == SDMMC_FREQ_DEFAULT) {
*host_div = 8; // src_freq_hz / 8 = 20 MHz
*card_div = 0;
} else if (freq_khz == SDMMC_FREQ_PROBING) {
*host_div = 10; // src_freq_hz / 10 / (20 * 2) = 400 kHz
*card_div = 20;
} else
#endif
{
uint32_t target_hz = (uint32_t)freq_khz * 1000;
// host_div: 1..16 (4-bit edge regs), card_div: 0=bypass or 1..255 (actual div = 2*card_div)
// actual_freq = clk_src / host_div when card_div == 0
// actual_freq = clk_src / host_div / (2 * card_div) when card_div > 0
uint32_t total_div = (clk_src_freq_hz + target_hz - 1) / target_hz;
if (total_div <= 16) {
*host_div = (int)total_div;
*card_div = 0;
} else {
// Two-stage: smallest host_div keeps highest intermediate freq for best precision
*host_div = 2;
*card_div = 255;
for (int h = 2; h <= 16; h++) {
uint32_t intermediate = clk_src_freq_hz / h;
int c = (int)((intermediate + 2 * target_hz - 1) / (2 * target_hz));
if (c < 1) {
c = 1;
}
if (c <= 255) {
*host_div = h;
*card_div = c;
break;
}
}
}
}
ESP_LOGD(TAG, "host_div: %d, card_div: %d", *host_div, *card_div);
*src = clk_src;
}
static int sd_host_calc_freq(soc_periph_sdmmc_clk_src_t src, const int host_div, const int card_div)
{
uint32_t clk_src_freq_hz = 0;
esp_err_t ret = esp_clk_tree_src_get_freq_hz(src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_src_freq_hz);
assert(ret == ESP_OK);
(void)ret;
return clk_src_freq_hz / host_div / ((card_div == 0) ? 1 : card_div * 2) / 1000;
}
/*---------------------------------------------------------------
Static: IO
---------------------------------------------------------------*/
static void configure_pin_iomux(uint8_t gpio_num)
{
assert(gpio_num != (uint8_t) GPIO_NUM_NC);
gpio_pulldown_dis(gpio_num);
gpio_input_enable(gpio_num);
gpio_iomux_output(gpio_num, SDMMC_LL_IOMUX_FUNC);
#if !CONFIG_IDF_TARGET_ESP32
/**
* On ESP32, the default pin drive value (2) works
*/
gpio_set_drive_capability(gpio_num, 3);
#endif
}
static void configure_pin_gpio_matrix(uint8_t gpio_num, uint8_t gpio_matrix_sig, gpio_mode_t mode, const char *name)
{
assert(gpio_num != (uint8_t) GPIO_NUM_NC);
ESP_LOGD(TAG, "using GPIO%d as %s pin", gpio_num, name);
//todo: IDF-11125
gpio_reset_pin(gpio_num);
gpio_set_direction(gpio_num, mode);
gpio_pulldown_dis(gpio_num);
if (mode == GPIO_MODE_INPUT || mode == GPIO_MODE_INPUT_OUTPUT) {
esp_rom_gpio_connect_in_signal(gpio_num, gpio_matrix_sig, false);
}
if (mode == GPIO_MODE_OUTPUT || mode == GPIO_MODE_INPUT_OUTPUT) {
esp_rom_gpio_connect_out_signal(gpio_num, gpio_matrix_sig, false, false);
}
}
static void configure_pin(uint8_t gpio_num, uint8_t gpio_matrix_sig, gpio_mode_t mode, const char *name, bool use_gpio_matrix)
{
if (use_gpio_matrix) {
configure_pin_gpio_matrix(gpio_num, gpio_matrix_sig, mode, name);
} else {
configure_pin_iomux(gpio_num);
}
}
//True: pins are all not set; False: one or more pins are set
static bool s_check_pin_not_set(const sd_host_sdmmc_slot_io_cfg_t *io_config)
{
#if SOC_SDMMC_USE_GPIO_MATRIX
bool pin_not_set = !io_config->clk_io && !io_config->cmd_io && !io_config->d0_io && !io_config->d1_io && !io_config->d2_io &&
!io_config->d3_io && !io_config->d4_io && !io_config->d5_io && !io_config->d6_io && !io_config->d7_io;
return pin_not_set;
#else
return true;
#endif
}
static esp_err_t sdmmc_host_pullup_en_internal(sd_host_sdmmc_slot_t *slot)
{
if (slot->width.width > sdmmc_slot_info[slot->slot_id].width) {
//in esp32 we only support 8 bit in slot 0, note this is occupied by the flash by default
return ESP_ERR_INVALID_ARG;
}
// according to the spec, the host controls the clk, we don't to pull it up here
gpio_pullup_en(slot->io_config.cmd_io);
gpio_pullup_en(slot->io_config.d0_io);
if (slot->width.width >= 4) {
gpio_pullup_en(slot->io_config.d1_io);
gpio_pullup_en(slot->io_config.d2_io);
gpio_pullup_en(slot->io_config.d3_io);
}
if (slot->width.width == 8) {
gpio_pullup_en(slot->io_config.d4_io);
gpio_pullup_en(slot->io_config.d5_io);
gpio_pullup_en(slot->io_config.d6_io);
gpio_pullup_en(slot->io_config.d7_io);
}
return ESP_OK;
}
static esp_err_t sdmmc_slot_io_config(sd_host_sdmmc_slot_t *slot, const sd_host_slot_sdmmc_init_cfg_t *slot_init_config)
{
bool gpio_wp_polarity = slot_init_config->slot_flags.wp_active_high;
sd_bus_width_t slot_width = slot_init_config->io_config.width;
int slot_id = slot_init_config->slot_id;
const sdmmc_slot_info_t *slot_info = &sdmmc_slot_info[slot_id];
ESP_RETURN_ON_FALSE(slot_width <= slot_info->width, ESP_ERR_INVALID_ARG, TAG, "wrong slot width");
if (slot_width == SD_HOST_SLOT_WIDTH_DEFAULT) {
slot_width = slot_info->width;
}
const sd_host_sdmmc_slot_io_cfg_t *io_config = &slot_init_config->io_config;
sd_host_sdmmc_slot_io_cfg_t *slot_gpio = &slot->io_config;
slot->io_config.clk_io = GPIO_NUM_NC;
slot->io_config.cmd_io = GPIO_NUM_NC;
slot->io_config.cd_io = GPIO_NUM_NC;
slot->io_config.wp_io = GPIO_NUM_NC;
slot->io_config.d0_io = GPIO_NUM_NC;
slot->io_config.d1_io = GPIO_NUM_NC;
slot->io_config.d2_io = GPIO_NUM_NC;
slot->io_config.d3_io = GPIO_NUM_NC;
slot->io_config.d4_io = GPIO_NUM_NC;
slot->io_config.d5_io = GPIO_NUM_NC;
slot->io_config.d6_io = GPIO_NUM_NC;
slot->io_config.d7_io = GPIO_NUM_NC;
slot->width.width = slot_width;
slot->width.width_state = SD_HOST_SLOT_STATE_INIT;
slot_gpio->cd_io = io_config->cd_io;
slot_gpio->wp_io = io_config->wp_io;
bool pin_not_set = s_check_pin_not_set(io_config);
//SD driver behaviour is: all pins not defined == using iomux
bool use_gpio_matrix = !pin_not_set;
if (slot_id == 0) {
#if !SDMMC_LL_SLOT_SUPPORT_GPIO_MATRIX(0)
if (use_gpio_matrix) {
if (slot_width >= 1) {
if (SDMMC_SLOT0_IOMUX_PIN_NUM_CLK == io_config->clk_io && SDMMC_SLOT0_IOMUX_PIN_NUM_CMD == io_config->cmd_io && SDMMC_SLOT0_IOMUX_PIN_NUM_D0 == io_config->d0_io) {
use_gpio_matrix = false;
} else {
use_gpio_matrix = true;
}
}
if (slot_width >= 4) {
if (SDMMC_SLOT0_IOMUX_PIN_NUM_D1 == io_config->d1_io && SDMMC_SLOT0_IOMUX_PIN_NUM_D2 == io_config->d2_io && SDMMC_SLOT0_IOMUX_PIN_NUM_D3 == io_config->d3_io) {
use_gpio_matrix = false;
} else {
use_gpio_matrix = true;
}
}
} else {
ESP_RETURN_ON_FALSE(!use_gpio_matrix, ESP_ERR_INVALID_ARG, TAG, "doesn't support routing from GPIO matrix, driver uses dedicated IOs");
}
#endif
} else {
#if !SDMMC_LL_SLOT_SUPPORT_GPIO_MATRIX(1)
ESP_RETURN_ON_FALSE(!use_gpio_matrix, ESP_ERR_INVALID_ARG, TAG, "doesn't support routing from GPIO matrix, driver uses dedicated IOs");
#endif
}
slot->use_gpio_matrix = use_gpio_matrix;
#if SOC_SDMMC_USE_GPIO_MATRIX
if (use_gpio_matrix) {
/* Save pin configuration for this slot */
slot_gpio->clk_io = io_config->clk_io;
slot_gpio->cmd_io = io_config->cmd_io;
slot_gpio->d0_io = io_config->d0_io;
/* Save d1 even in 1-line mode, it might be needed for SDIO INT line */
slot_gpio->d1_io = io_config->d1_io;
if (slot_width >= 4) {
slot_gpio->d2_io = io_config->d2_io;
}
/* Save d3 even for 1-line mode, as it needs to be set high */
slot_gpio->d3_io = io_config->d3_io;
if (slot_width >= 8) {
slot_gpio->d4_io = io_config->d4_io;
slot_gpio->d5_io = io_config->d5_io;
slot_gpio->d6_io = io_config->d6_io;
slot_gpio->d7_io = io_config->d7_io;
}
} else
#endif //#if SOC_SDMMC_USE_GPIO_MATRIX
{
/* init pin configuration for this slot */
slot_gpio->clk_io = sdmmc_slot_gpio_num[slot_id].clk;
slot_gpio->cmd_io = sdmmc_slot_gpio_num[slot_id].cmd;
slot_gpio->d0_io = sdmmc_slot_gpio_num[slot_id].d0;
slot_gpio->d1_io = sdmmc_slot_gpio_num[slot_id].d1;
slot_gpio->d2_io = sdmmc_slot_gpio_num[slot_id].d2;
slot_gpio->d3_io = sdmmc_slot_gpio_num[slot_id].d3;
slot_gpio->d4_io = sdmmc_slot_gpio_num[slot_id].d4;
slot_gpio->d5_io = sdmmc_slot_gpio_num[slot_id].d5;
slot_gpio->d6_io = sdmmc_slot_gpio_num[slot_id].d6;
slot_gpio->d7_io = sdmmc_slot_gpio_num[slot_id].d7;
}
bool pullup = slot_init_config->slot_flags.internal_pullup;
if (pullup) {
sdmmc_host_pullup_en_internal(slot);
}
if (slot_width >= 1) {
GPIO_NUM_CHECK(slot_gpio->clk_io);
GPIO_NUM_CHECK(slot_gpio->cmd_io);
GPIO_NUM_CHECK(slot_gpio->d0_io);
}
if (slot_width >= 4) {
GPIO_NUM_CHECK(slot_gpio->d1_io);
GPIO_NUM_CHECK(slot_gpio->d2_io);
GPIO_NUM_CHECK(slot_gpio->d3_io);
}
if (slot_width == 8) {
GPIO_NUM_CHECK(slot_gpio->d4_io);
GPIO_NUM_CHECK(slot_gpio->d5_io);
GPIO_NUM_CHECK(slot_gpio->d6_io);
GPIO_NUM_CHECK(slot_gpio->d7_io);
}
configure_pin(slot_gpio->clk_io, sdmmc_slot_gpio_sig[slot_id].clk, GPIO_MODE_OUTPUT, "clk", use_gpio_matrix);
configure_pin(slot_gpio->cmd_io, sdmmc_slot_gpio_sig[slot_id].cmd, GPIO_MODE_INPUT_OUTPUT, "cmd", use_gpio_matrix);
configure_pin(slot_gpio->d0_io, sdmmc_slot_gpio_sig[slot_id].d0, GPIO_MODE_INPUT_OUTPUT, "d0", use_gpio_matrix);
if (slot_width >= 4) {
configure_pin(slot_gpio->d1_io, sdmmc_slot_gpio_sig[slot_id].d1, GPIO_MODE_INPUT_OUTPUT, "d1", use_gpio_matrix);
configure_pin(slot_gpio->d2_io, sdmmc_slot_gpio_sig[slot_id].d2, GPIO_MODE_INPUT_OUTPUT, "d2", use_gpio_matrix);
if (slot->sd_mode == SD_MODE_UHS1) {
configure_pin(slot_gpio->d3_io, sdmmc_slot_gpio_sig[slot_id].d3, GPIO_MODE_INPUT_OUTPUT, "d3", use_gpio_matrix);
} else {
// Force D3 high to make slave enter SD mode.
// Connect to peripheral after width configuration.
if (slot_gpio->d3_io > GPIO_NUM_NC) {
gpio_config_t gpio_conf = {
.pin_bit_mask = BIT64(slot_gpio->d3_io),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&gpio_conf);
gpio_set_level(slot_gpio->d3_io, 1);
}
}
}
if (slot_width == 8) {
configure_pin(slot_gpio->d4_io, sdmmc_slot_gpio_sig[slot_id].d4, GPIO_MODE_INPUT_OUTPUT, "d4", use_gpio_matrix);
configure_pin(slot_gpio->d5_io, sdmmc_slot_gpio_sig[slot_id].d5, GPIO_MODE_INPUT_OUTPUT, "d5", use_gpio_matrix);
configure_pin(slot_gpio->d6_io, sdmmc_slot_gpio_sig[slot_id].d6, GPIO_MODE_INPUT_OUTPUT, "d6", use_gpio_matrix);
configure_pin(slot_gpio->d7_io, sdmmc_slot_gpio_sig[slot_id].d7, GPIO_MODE_INPUT_OUTPUT, "d7", use_gpio_matrix);
}
// SDIO slave interrupt is edge sensitive to ~(int_n | card_int | card_detect)
// set this and card_detect to high to enable sdio interrupt
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, slot_info->card_int, false);
// Set up Card Detect input
int matrix_in_cd = 0;
if (io_config->cd_io != GPIO_NUM_NC) {
ESP_LOGD(TAG, "using GPIO%d as CD pin", io_config->cd_io);
esp_rom_gpio_pad_select_gpio(io_config->cd_io);
gpio_set_direction(io_config->cd_io, GPIO_MODE_INPUT);
matrix_in_cd = io_config->cd_io;
} else {
// if not set, default to CD low (card present)
matrix_in_cd = GPIO_MATRIX_CONST_ZERO_INPUT;
}
esp_rom_gpio_connect_in_signal(matrix_in_cd, slot_info->card_detect, false);
// Set up Write Protect input
int matrix_in_wp;
if (io_config->wp_io != GPIO_NUM_NC) {
ESP_LOGD(TAG, "using GPIO%d as WP pin", io_config->wp_io);
esp_rom_gpio_pad_select_gpio(io_config->wp_io);
gpio_set_direction(io_config->wp_io, GPIO_MODE_INPUT);
matrix_in_wp = io_config->wp_io;
} else {
// if not set, default to WP high (not write protected)
matrix_in_wp = GPIO_MATRIX_CONST_ONE_INPUT;
}
// As hardware expects an active-high signal,
// if WP signal is active low, then invert it in GPIO matrix,
// else keep it in its default state
esp_rom_gpio_connect_in_signal(matrix_in_wp, slot_info->write_protect, (gpio_wp_polarity ? false : true));
return ESP_OK;
}