* 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);
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
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();