Merge branch 'feature/sdcard_custom_freq' into 'master'

[Storage] Enable SD card custom frequency setup

Closes IDFGH-7089

See merge request espressif/esp-idf!19038
This commit is contained in:
Martin Vychodil 2022-09-23 17:45:30 +08:00
commit 7d28abae4e
15 changed files with 203 additions and 52 deletions

View File

@ -45,6 +45,7 @@ extern "C" {
.io_int_enable = sdmmc_host_io_int_enable, \
.io_int_wait = sdmmc_host_io_int_wait, \
.command_timeout_ms = 0, \
.get_real_freq = &sdmmc_host_get_real_freq \
}
/**
@ -259,6 +260,23 @@ esp_err_t sdmmc_host_io_int_wait(int slot, TickType_t timeout_ticks);
*/
esp_err_t sdmmc_host_deinit(void);
/**
* @brief Provides a real frequency used for an SD card installed on specific slot
* of SD/MMC host controller
*
* This function calculates real working frequency given by current SD/MMC host
* controller setup for required slot: it reads associated host and card dividers
* from corresponding SDMMC registers, calculates respective frequency and stores
* the value into the 'real_freq_khz' parameter
*
* @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1)
* @param[out] real_freq_khz output parameter for the result frequency (in kHz)
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG on real_freq_khz == NULL or invalid slot number used
*/
esp_err_t sdmmc_host_get_real_freq(int slot, int* real_freq_khz);
#ifdef __cplusplus
}
#endif

View File

@ -183,6 +183,7 @@ typedef struct {
esp_err_t (*io_int_enable)(int slot); /*!< Host function to enable SDIO interrupt line */
esp_err_t (*io_int_wait)(int slot, TickType_t timeout_ticks); /*!< Host function to wait for SDIO interrupt line to be active */
int command_timeout_ms; /*!< timeout, in milliseconds, of a single command. Set to 0 to use the default value. */
esp_err_t (*get_real_freq)(int slot, int* real_freq); /*!< Host function to provide real working freq, based on SDMMC controller setup */
} sdmmc_host_t;
/**
@ -202,6 +203,7 @@ typedef struct {
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 */
int real_freq_khz; /*!< Real working frequency, in kHz, configured on the host controller */
uint32_t is_mem : 1; /*!< Bit indicates if the card is a memory card */
uint32_t is_sdio : 1; /*!< Bit indicates if the card is an IO card */
uint32_t is_mmc : 1; /*!< Bit indicates if the card is MMC */

View File

@ -50,6 +50,7 @@ typedef int sdspi_dev_handle_t;
.io_int_enable = &sdspi_host_io_int_enable, \
.io_int_wait = &sdspi_host_io_int_wait, \
.command_timeout_ms = 0, \
.get_real_freq = &sdspi_host_get_real_freq \
}
/**
@ -156,6 +157,18 @@ esp_err_t sdspi_host_do_transaction(sdspi_dev_handle_t handle, sdmmc_command_t *
*/
esp_err_t sdspi_host_set_card_clk(sdspi_dev_handle_t host, uint32_t freq_khz);
/**
* @brief Calculate working frequency for specific device
*
* @param handle SDSPI device handle
* @param[out] real_freq_khz output parameter to hold the calculated frequency (in kHz)
*
* @return
* - ESP_ERR_INVALID_ARG : ``handle`` is NULL or invalid or ``real_freq_khz`` parameter is NULL
* - ESP_OK : Success
*/
esp_err_t sdspi_host_get_real_freq(sdspi_dev_handle_t handle, int* real_freq_khz);
/**
* @brief Release resources allocated using sdspi_host_init
*

View File

@ -333,6 +333,18 @@ esp_err_t spi_device_acquire_bus(spi_device_handle_t device, TickType_t wait);
*/
void spi_device_release_bus(spi_device_handle_t dev);
/**
* @brief Calculate working frequency for specific device
*
* @param handle SPI device handle
* @param[out] freq_khz output parameter to hold calculated frequency in kHz
*
* @return
* - ESP_ERR_INVALID_ARG : ``handle`` or ``freq_khz`` parameter is NULL
* - ESP_OK : Success
*/
esp_err_t spi_device_get_actual_freq(spi_device_handle_t handle, int* freq_khz);
/**
* @brief Calculate the working frequency that is most close to desired frequency.
*

View File

@ -171,12 +171,47 @@ static void sdmmc_host_clock_update_command(int slot)
}
}
void sdmmc_host_get_clk_dividers(const uint32_t freq_khz, int *host_div, int *card_div)
{
// Calculate new dividers
if (freq_khz >= SDMMC_FREQ_HIGHSPEED) {
*host_div = 4; // 160 MHz / 4 = 40 MHz
*card_div = 0;
} else if (freq_khz == SDMMC_FREQ_DEFAULT) {
*host_div = 8; // 160 MHz / 8 = 20 MHz
*card_div = 0;
} else if (freq_khz == SDMMC_FREQ_PROBING) {
*host_div = 10; // 160 MHz / 10 / (20 * 2) = 400 kHz
*card_div = 20;
} else {
/*
* for custom frequencies use maximum range of host divider (1-16), find the closest <= div. combination
* if exceeded, combine with the card divider to keep reasonable precision (applies mainly to low frequencies)
* effective frequency range: 400 kHz - 32 MHz (32.1 - 39.9 MHz cannot be covered with given divider scheme)
*/
*host_div = (2 * APB_CLK_FREQ) / (freq_khz * 1000);
if (*host_div > 15 ) {
*host_div = 2;
*card_div = APB_CLK_FREQ / (2 * freq_khz * 1000);
if ( (APB_CLK_FREQ % (2 * freq_khz * 1000)) > 0 ) {
(*card_div)++;
}
} else if ( ((2 * APB_CLK_FREQ) % (freq_khz * 1000)) > 0 ) {
(*host_div)++;
}
}
}
static int sdmmc_host_calc_freq(const int host_div, const int card_div)
{
return 2 * APB_CLK_FREQ / host_div / ((card_div == 0) ? 1 : card_div * 2) / 1000;
}
esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz)
{
if (!(slot == 0 || slot == 1)) {
return ESP_ERR_INVALID_ARG;
}
const int clk40m = 40000;
// Disable clock first
SDMMC.clkena.cclk_enable &= ~BIT(slot);
@ -184,25 +219,10 @@ esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz)
int host_div = 0; /* clock divider of the host (SDMMC.clock) */
int card_div = 0; /* 1/2 of card clock divider (SDMMC.clkdiv) */
sdmmc_host_get_clk_dividers(freq_khz, &host_div, &card_div);
// Calculate new dividers
if (freq_khz >= SDMMC_FREQ_HIGHSPEED) {
host_div = 4; // 160 MHz / 4 = 40 MHz
card_div = 0;
} else if (freq_khz == SDMMC_FREQ_DEFAULT) {
host_div = 8; // 160 MHz / 8 = 20 MHz
card_div = 0;
} else if (freq_khz == SDMMC_FREQ_PROBING) {
host_div = 10; // 160 MHz / 10 / (20 * 2) = 400 kHz
card_div = 20;
} else {
host_div = 2;
card_div = (clk40m + freq_khz * 2 - 1) / (freq_khz * 2); // round up
}
ESP_LOGD(TAG, "slot=%d host_div=%d card_div=%d freq=%dkHz",
slot, host_div, card_div,
2 * APB_CLK_FREQ / host_div / ((card_div == 0) ? 1 : card_div * 2) / 1000);
int real_freq = sdmmc_host_calc_freq(host_div, card_div);
ESP_LOGD(TAG, "slot=%d host_div=%d card_div=%d freq=%dkHz (max %" PRIu32 "kHz)", slot, host_div, card_div, real_freq, freq_khz);
// Program CLKDIV and CLKSRC, send them to the CIU
switch(slot) {
@ -236,6 +256,22 @@ esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz)
return ESP_OK;
}
esp_err_t sdmmc_host_get_real_freq(int slot, int* real_freq_khz)
{
if (real_freq_khz == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!(slot == 0 || slot == 1)) {
return ESP_ERR_INVALID_ARG;
}
int host_div = SDMMC.clock.div_factor_p + 1;
int card_div = slot == 0 ? SDMMC.clkdiv.div0 : SDMMC.clkdiv.div1;
*real_freq_khz = sdmmc_host_calc_freq(host_div, card_div);
return ESP_OK;
}
esp_err_t sdmmc_host_start_command(int slot, sdmmc_hw_cmd_t cmd, uint32_t arg) {
if (!(slot == 0 || slot == 1)) {
return ESP_ERR_INVALID_ARG;

View File

@ -299,6 +299,16 @@ esp_err_t sdspi_host_set_card_clk(sdspi_dev_handle_t handle, uint32_t freq_khz)
return configure_spi_dev(slot, freq_khz * 1000);
}
esp_err_t sdspi_host_get_real_freq(sdspi_dev_handle_t handle, int* real_freq_khz)
{
slot_info_t *slot = get_slot_info(handle);
if (slot == NULL) {
return ESP_ERR_INVALID_ARG;
}
return spi_device_get_actual_freq(slot->spi_handle, real_freq_khz);
}
static void gpio_intr(void* arg)
{
BaseType_t awoken = pdFALSE;

View File

@ -470,6 +470,19 @@ esp_err_t spi_bus_remove_device(spi_device_handle_t handle)
return ESP_OK;
}
esp_err_t spi_device_get_actual_freq(spi_device_handle_t handle, int* freq_khz)
{
if ((spi_device_t*)handle == NULL || freq_khz == NULL) {
return ESP_ERR_INVALID_ARG;
}
int dev_required_freq = ((spi_device_t*)handle)->cfg.clock_speed_hz;
int dev_duty_cycle = ((spi_device_t*)handle)->cfg.duty_cycle_pos;
*freq_khz = spi_get_actual_clock(esp_clk_apb_freq(), dev_required_freq, dev_duty_cycle);
return ESP_OK;
}
int spi_get_actual_clock(int fapb, int hz, int duty_cycle)
{
return spi_hal_master_cal_clock(fapb, hz, duty_cycle);

View File

@ -197,34 +197,18 @@ esp_err_t sdmmc_init_host_frequency(sdmmc_card_t* card)
{
assert(card->max_freq_khz <= card->host.max_freq_khz);
/* Find highest frequency in the following list,
* which is below card->max_freq_khz.
*/
const uint32_t freq_values[] = {
SDMMC_FREQ_52M,
SDMMC_FREQ_HIGHSPEED,
SDMMC_FREQ_26M,
SDMMC_FREQ_DEFAULT
//NOTE: in sdspi mode, 20MHz may not work. in that case, add 10MHz here.
};
const int n_freq_values = sizeof(freq_values) / sizeof(freq_values[0]);
uint32_t selected_freq = SDMMC_FREQ_PROBING;
for (int i = 0; i < n_freq_values; ++i) {
uint32_t freq = freq_values[i];
if (card->max_freq_khz >= freq) {
selected_freq = freq;
break;
}
}
ESP_LOGD(TAG, "%s: using %d kHz bus frequency", __func__, selected_freq);
if (selected_freq > SDMMC_FREQ_PROBING) {
esp_err_t err = (*card->host.set_card_clk)(card->host.slot, selected_freq);
if (card->max_freq_khz > SDMMC_FREQ_PROBING) {
esp_err_t err = (*card->host.set_card_clk)(card->host.slot, card->max_freq_khz);
if (err != ESP_OK) {
ESP_LOGE(TAG, "failed to switch bus frequency (0x%x)", err);
return err;
}
err = (*card->host.get_real_freq)(card->host.slot, &(card->real_freq_khz));
if (err != ESP_OK) {
ESP_LOGE(TAG, "failed to get real working frequency (0x%x)", err);
return err;
}
}
if (card->is_ddr) {
@ -258,7 +242,9 @@ void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card)
bool print_scr = false;
bool print_csd = false;
const char* type;
fprintf(stream, "Name: %s\n", card->cid.name);
if (card->is_sdio) {
type = "SDIO";
print_scr = true;
@ -271,12 +257,17 @@ void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card)
print_csd = true;
}
fprintf(stream, "Type: %s\n", type);
if (card->max_freq_khz < 1000) {
fprintf(stream, "Speed: %d kHz\n", card->max_freq_khz);
if (card->real_freq_khz == 0) {
fprintf(stream, "Speed: N/A\n");
} else {
fprintf(stream, "Speed: %d MHz%s\n", card->max_freq_khz / 1000,
card->is_ddr ? ", DDR" : "");
const char *freq_unit = card->real_freq_khz < 1000 ? "kHz" : "MHz";
const float freq = card->real_freq_khz < 1000 ? card->real_freq_khz : card->real_freq_khz / 1000.0;
const char *max_freq_unit = card->max_freq_khz < 1000 ? "kHz" : "MHz";
const float max_freq = card->max_freq_khz < 1000 ? card->max_freq_khz : card->max_freq_khz / 1000.0;
fprintf(stream, "Speed: %.2f %s (limit: %.2f %s)%s\n", freq, freq_unit, max_freq, max_freq_unit, card->is_ddr ? ", DDR" : "");
}
fprintf(stream, "Size: %lluMB\n", ((uint64_t) card->csd.capacity) * card->csd.sector_size / (1024 * 1024));
if (print_csd) {

View File

@ -292,7 +292,7 @@ esp_err_t sdmmc_enable_hs_mode_and_check(sdmmc_card_t* card)
return ESP_ERR_NOT_SUPPORTED;
}
card->max_freq_khz = SDMMC_FREQ_HIGHSPEED;
card->max_freq_khz = MIN(card->host.max_freq_khz, SDMMC_FREQ_HIGHSPEED);
return ESP_OK;
}

View File

@ -132,6 +132,17 @@ static void probe_sd(int slot, int width, int freq_khz, int ddr)
free(card);
sd_test_board_power_off();
}
extern void sdmmc_host_get_clk_dividers(const int freq_khz, int *host_div, int *card_div);
static void sd_test_check_clk_dividers(const int freq_khz, const int expected_host_div, const int expected_card_div)
{
printf(" %6d | %2d | %2d\n", freq_khz, expected_host_div, expected_card_div);
int host_divider, card_divider;
sdmmc_host_get_clk_dividers(freq_khz, &host_divider, &card_divider);
TEST_ASSERT_EQUAL(host_divider, expected_host_div);
TEST_ASSERT_EQUAL(card_divider, expected_card_div);
}
#endif //WITH_SD_TEST || WITH_EMMC_TEST
#if WITH_SD_TEST
@ -140,6 +151,8 @@ TEST_CASE("probe SD, slot 1, 4-bit", "[sd][test_env=UT_T1_SDMODE]")
probe_sd(SDMMC_HOST_SLOT_1, 4, SDMMC_FREQ_PROBING, 0);
probe_sd(SDMMC_HOST_SLOT_1, 4, SDMMC_FREQ_DEFAULT, 0);
probe_sd(SDMMC_HOST_SLOT_1, 4, SDMMC_FREQ_HIGHSPEED, 0);
//custom frequency test
probe_sd(SDMMC_HOST_SLOT_1, 4, 10000, 0);
}
TEST_CASE("probe SD, slot 1, 1-bit", "[sd][test_env=UT_T1_SDMODE]")
@ -163,6 +176,21 @@ TEST_CASE("probe SD, slot 0, 1-bit", "[sd][ignore]")
probe_sd(SDMMC_HOST_SLOT_0, 1, SDMMC_FREQ_DEFAULT, 0);
probe_sd(SDMMC_HOST_SLOT_0, 1, SDMMC_FREQ_HIGHSPEED, 0);
}
TEST_CASE("SD clock dividers calculation", "[sd][test_env=UT_T1_SDMODE]")
{
printf("Frequency (kHz) | Expected host.div | Expected card.div\n");
sd_test_check_clk_dividers(SDMMC_FREQ_PROBING, 10, 20);
sd_test_check_clk_dividers(SDMMC_FREQ_DEFAULT, 8, 0);
sd_test_check_clk_dividers(SDMMC_FREQ_HIGHSPEED, 4, 0);
sd_test_check_clk_dividers(36000, 5, 0);
sd_test_check_clk_dividers(30000, 6, 0);
sd_test_check_clk_dividers(16000, 10, 0);
sd_test_check_clk_dividers(10000, 2, 4);
sd_test_check_clk_dividers(6000, 2, 7);
sd_test_check_clk_dividers(1000, 2, 40);
sd_test_check_clk_dividers(600, 2, 67);
}
#endif //WITH_SD_TEST
#if WITH_EMMC_TEST
@ -218,10 +246,11 @@ static void test_sdspi_deinit_bus(spi_host_device_t host)
TEST_ESP_OK(err);
}
static void probe_core(int slot)
static void probe_core(int slot, int freq_khz)
{
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
config.slot = slot;
config.max_freq_khz = freq_khz;
sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
TEST_ASSERT_NOT_NULL(card);
@ -242,7 +271,7 @@ static void probe_spi(int freq_khz, int pin_miso, int pin_mosi, int pin_sck, int
TEST_ESP_OK(sdspi_host_init());
TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle));
probe_core(handle);
probe_core(handle, freq_khz);
TEST_ESP_OK(sdspi_host_deinit());
test_sdspi_deinit_bus(dev_config.host_id);
@ -253,6 +282,8 @@ static void probe_spi(int freq_khz, int pin_miso, int pin_mosi, int pin_sck, int
TEST_CASE("probe SD in SPI mode", "[sd][test_env=UT_T1_SPIMODE]")
{
probe_spi(SDMMC_FREQ_DEFAULT, SDSPI_TEST_MISO_PIN, SDSPI_TEST_MOSI_PIN, SDSPI_TEST_SCLK_PIN, SDSPI_TEST_CS_PIN);
//custom frequency test
probe_spi(10000, SDSPI_TEST_MISO_PIN, SDSPI_TEST_MOSI_PIN, SDSPI_TEST_SCLK_PIN, SDSPI_TEST_CS_PIN);
}
// No runner for this

View File

@ -94,6 +94,9 @@ In the designs where communication at 40 MHz frequency can be achieved, it is po
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
If you need a specific frequency other than standard speeds, you are free to use any value from within appropriate range of the SD interface given (SDMMC or SDSPI). However, the real clock frequency shall be calculated by the underlying driver and the value can be different from the one required.
For the SDMMC, ``max_freq_khz`` works as the upper limit so the final frequency value shall be always lower or equal. For the SDSPI, the nearest fitting frequency is supplied and thus the value can be greater than / equal to / lower than ``max_freq_khz``.
To configure the bus width, set the ``width`` field of :cpp:class:`sdmmc_slot_config_t`. For example, to set 1-line mode::
sdmmc_slot_config_t slot = SDMMC_SLOT_CONFIG_DEFAULT();

View File

@ -1,6 +1,13 @@
Storage
=======
SDMMC/SDSPI
-----------
SD card frequency on SDMMC/SDSPI interface can be now configured through ``sdmmc_host_t.max_freq_khz`` to a specific value, not only ``SDMMC_FREQ_PROBING`` (400 kHz), ``SDMMC_FREQ_DEFAULT`` (20 MHz), or ``SDMMC_FREQ_HIGHSPEED`` (40 MHz). Previously, in case you have specified a custom frequency other than any of the above-mentioned values, the closest lower-or-equal one was selected anyway.
Now, the underlaying drivers calculate the nearest fitting value, given by available frequency dividers instead of an enumeration item selection. This could cause troubles in communication with your SD card without a change of the existing application code.If you encounter such an issue, please, keep trying different frequencies around your desired value unless you find the one working well. To check the frequency value calculated and actually applied, use ``void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card)`` function.
FatFs
-----
@ -9,7 +16,7 @@ FatFs is now updated to v0.14. As a result, the function signature of ``f_mkfs()
Partition Table
---------------
The partition table generator no longer supports misaligned partitions. When generating a partition table, ``esp-idf`` only accepts partitions with offsets that align to 4 KB. This change only affects generating new partition tables. Reading and writing to already existing partitions remains unchanged.
The partition table generator no longer supports misaligned partitions. When generating a partition table, ``ESP-IDF`` only accepts partitions with offsets that align to 4 KB. This change only affects generating new partition tables. Reading and writing to already existing partitions remains unchanged.
VFS

View File

@ -1,6 +1,13 @@
存储
=======
SDMMC/SDSPI
-----------
用户现可通过 ``sdmmc_host_t.max_freq_khz`` 将 SDMMC/SDSPI 接口上的 SD 卡频率配置为特定值,不再局限于之前的 ``SDMMC_FREQ_PROBING`` (400 kHz)、 ``SDMMC_FREQ_DEFAULT`` (20 MHz) 或是 ``SDMMC_FREQ_HIGHSPEED`` (40 MHz)。此前,如果用户配置了上述三个给定频率之外的值,用户所选频率将自动调整为与其最为接近的给定值。
更新后,底层驱动将计算与用户配置的特定值最为接近的合适频率。相对于枚举项选择,该频率现由可用的分频器提供。不过,如果尚未更新现有的应用代码,可能会导致与 SD 卡的通信过程出现问题。如发现上述问题,请继续尝试配置与期望值接近的不同频率,直到找到合适的频率。如需查看底层驱动的计算结果以及实际应用的频率,请使用 ``void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card)`` 函数。
FatFs
-----
@ -9,7 +16,7 @@ FatFs 已更新至 v0.14 ``f_mkfs()`` 函数签名也已变更。新签名为
分区表
---------------
分区表生成器不再支持未对齐的分区。生成分区表时, ``esp-idf`` 将只接受偏移量与 4 KB 对齐的分区。此变更仅影响新生成的分区表,不影响读写现有分区。
分区表生成器不再支持未对齐的分区。生成分区表时, ``ESP-IDF`` 将只接受偏移量与 4 KB 对齐的分区。此变更仅影响新生成的分区表,不影响读写现有分区。
VFS

View File

@ -46,6 +46,10 @@ void app_main(void)
// production applications.
ESP_LOGI(TAG, "Using SDMMC peripheral");
// By default, SD card frequency is initialized to SDMMC_FREQ_DEFAULT (20MHz)
// For setting a specific frequency, use host.max_freq_khz (range 400kHz - 40MHz for SDMMC)
// Example: for fixed frequency of 10MHz, use host.max_freq_khz = 10000;
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
// This initializes the slot without card detect (CD) and write protect (WP) signals.

View File

@ -52,7 +52,11 @@ void app_main(void)
// production applications.
ESP_LOGI(TAG, "Using SPI peripheral");
// By default, SD card frequency is initialized to SDMMC_FREQ_DEFAULT (20MHz)
// For setting a specific frequency, use host.max_freq_khz (range 400kHz - 20MHz for SDSPI)
// Example: for fixed frequency of 10MHz, use host.max_freq_khz = 10000;
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
spi_bus_config_t bus_cfg = {
.mosi_io_num = PIN_NUM_MOSI,
.miso_io_num = PIN_NUM_MISO,