Skip to content

Commit

Permalink
Merge branch 'feature/sdcard_custom_freq' into 'master'
Browse files Browse the repository at this point in the history
[Storage] Enable SD card custom frequency setup

Closes IDFGH-7089

See merge request espressif/esp-idf!19038
  • Loading branch information
pacucha42 committed Sep 23, 2022
2 parents 1ecddf5 + b84c7a5 commit 7d28aba
Show file tree
Hide file tree
Showing 15 changed files with 202 additions and 51 deletions.
18 changes: 18 additions & 0 deletions components/driver/include/driver/sdmmc_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
}

/**
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions components/driver/include/driver/sdmmc_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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 */
Expand Down
13 changes: 13 additions & 0 deletions components/driver/include/driver/sdspi_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
}

/**
Expand Down Expand Up @@ -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
*
Expand Down
12 changes: 12 additions & 0 deletions components/driver/include/driver/spi_master.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
74 changes: 55 additions & 19 deletions components/driver/sdmmc_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,38 +171,58 @@ 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);
sdmmc_host_clock_update_command(slot);

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) {
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions components/driver/sdspi_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions components/driver/spi_master.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
45 changes: 18 additions & 27 deletions components/sdmmc/sdmmc_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -197,32 +197,16 @@ 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;
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;
}
}

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);
err = (*card->host.get_real_freq)(card->host.slot, &(card->real_freq_khz));
if (err != ESP_OK) {
ESP_LOGE(TAG, "failed to switch bus frequency (0x%x)", err);
ESP_LOGE(TAG, "failed to get real working frequency (0x%x)", err);
return err;
}
}
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion components/sdmmc/sdmmc_sd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
35 changes: 33 additions & 2 deletions components/sdmmc/test/test_sd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]")
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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
Expand Down
Loading

0 comments on commit 7d28aba

Please sign in to comment.