sdmmc: set highest possible host clock divider
SDMMC host suffers from an issue that it outputs data near the rising edge of the card clock, which is the edge used by the card to sample data. If sampling time constraint is not satisfied, card may read data after the transition. The phases of output/input data can, in fact, be adjusted. However this adjustment happens in the clock generation block outside of the host. So the maximum phase change which can be created this way is equal to half of the host clock period. So if the host clock is set to the lowest possible frequency (for the given card frequency), then the phase offset (and hence the hold time) will be the highest. This change modifies the logic used to determine clock dividers accordingly. sdmmc host: set correct dout phase and print correct frequency
This commit is contained in:
parent
84d6793f77
commit
f02cff13cc
1 changed files with 41 additions and 19 deletions
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue