diff --git a/components/driver/sdmmc_host.c b/components/driver/sdmmc_host.c index 4c297a58f..e33da7750 100644 --- a/components/driver/sdmmc_host.c +++ b/components/driver/sdmmc_host.c @@ -115,22 +115,30 @@ void sdmmc_host_reset() * field of card's CSD register. * * 50 MHz can not be obtained exactly, closest we can get is 53 MHz. - * For now set the first stage divider to generate 40MHz, and then configure - * the second stage dividers to generate the frequency requested. + * + * 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 sdmmc_host_input_clk_enable() +static void sdmmc_host_set_clk_div(int div) { - // Set frequency to 160MHz / (p + 1) = 40MHz, duty cycle (h + 1)/(p + 1) = 1/2 - SDMMC.clock.div_factor_p = 3; - SDMMC.clock.div_factor_h = 1; - SDMMC.clock.div_factor_m = 3; + // Set frequency to 160MHz / div + // div = p + 1 + // duty cycle = (h + 1)/(p + 1) (should be = 1/2) + assert (div > 1 && div <= 16); + int p = div - 1; + int h = div / 2 - 1; + + SDMMC.clock.div_factor_p = p; + SDMMC.clock.div_factor_h = h; + SDMMC.clock.div_factor_m = p; // Set phases for in/out clocks - SDMMC.clock.phase_dout = 4; - SDMMC.clock.phase_din = 4; + SDMMC.clock.phase_dout = 4; // 180 degree phase on the output clock + SDMMC.clock.phase_din = 4; // 180 degree phase on the input clock SDMMC.clock.phase_core = 0; // Wait for the clock to propagate ets_delay_us(10); @@ -181,26 +189,40 @@ esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz) 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) */ + // Calculate new dividers - int div = 0; - if (freq_khz < clk40m) { - // round up; extra *2 is because clock divider divides by 2*n - div = (clk40m + freq_khz * 2 - 1) / (freq_khz * 2); + 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 div=%d freq=%dkHz", slot, div, - (div == 0) ? clk40m : clk40m / (2 * div)); + + 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); // Program CLKDIV and CLKSRC, send them to the CIU switch(slot) { case 0: SDMMC.clksrc.card0 = 0; - SDMMC.clkdiv.div0 = div; + SDMMC.clkdiv.div0 = card_div; break; case 1: SDMMC.clksrc.card1 = 1; - SDMMC.clkdiv.div1 = div; + SDMMC.clkdiv.div1 = card_div; break; } + sdmmc_host_set_clk_div(host_div); sdmmc_host_clock_update_command(slot); // Re-enable clocks @@ -243,8 +265,8 @@ esp_err_t sdmmc_host_init() periph_module_enable(PERIPH_SDMMC_MODULE); - // Enable clock to peripheral - sdmmc_host_input_clk_enable(); + // Enable clock to peripheral. Use smallest divider first. + sdmmc_host_set_clk_div(2); // Reset sdmmc_host_reset();