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:
Ivan Grokhotkov 2017-08-18 00:18:55 +08:00
parent 84d6793f77
commit f02cff13cc

View file

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