mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-11-09 22:56:35 +00:00
Rework sdmmc clocking configuration
Use 204 MHz as host clock in SDR104 mode instead of 136 MHz. Due to this, also change the frequency init divider so the initialization frequency is below 400 kHz. This makes the clocks for SDMMC1 in all modes to match the TRM table. Make it clear in the code that HS200/HS400 modes in fact use PLLP_OUT0 and not PLLC4_OUT2_LJ like the comment suggest. In fact selecting the PLLC4_OUT2_LJ as clock source results in freeze after switching to HS200/HS400 mode. This is most likely related to the PLLC4 not being enabled, but it should be checked later. Set the HS200/HS400 divider to 3, as this is what the code really did set prior to this change - so this commit does not change that. Configure Legacy 12 MHz clock to run at 12 MHz using the SW default configuration (as per TRM) for the SDMMC legacy timer. Introduce initial version of sdmmc_host_clock_delay() in order to use it in places where the wait is host clock dependent. The way it is implemented now does not change the sleep that was used instead.
This commit is contained in:
parent
d8c9399cff
commit
206c10f333
6 changed files with 138 additions and 72 deletions
|
@ -49,7 +49,10 @@ enum {
|
||||||
*/
|
*/
|
||||||
enum {
|
enum {
|
||||||
CLK_SOURCE_MASK = (0b111 << 29),
|
CLK_SOURCE_MASK = (0b111 << 29),
|
||||||
CLK_SOURCE_FIRST = (0b000 << 29),
|
CLK_SOURCE_SDMMC1_PLLP_OUT0 = (0b000 << 29), /* Fixed 408 MHz */
|
||||||
|
CLK_SOURCE_SDMMC4_PLLP_OUT0 = (0b000 << 29), /* Fixed 408 MHz */
|
||||||
|
CLK_SOURCE_SDMMC4_PLLC4_OUT2_LJ = (0b001 << 29), /* 199.68 MHz */
|
||||||
|
CLK_SOURCE_SDMMC_LEGACY_PLLP_OUT0 = (0b100 << 29), /* Fixed 408 MHz */
|
||||||
|
|
||||||
CLK_DIVIDER_MASK = (0xff << 0),
|
CLK_DIVIDER_MASK = (0xff << 0),
|
||||||
CLK_DIVIDER_UNITY = (0x00 << 0),
|
CLK_DIVIDER_UNITY = (0x00 << 0),
|
||||||
|
|
|
@ -142,34 +142,52 @@ enum sdmmc_clock_dividers {
|
||||||
MMC_CLOCK_DIVIDER_SDR12 = 31, // 16.5, from the TRM table
|
MMC_CLOCK_DIVIDER_SDR12 = 31, // 16.5, from the TRM table
|
||||||
MMC_CLOCK_DIVIDER_SDR25 = 15, // 8.5, from the table
|
MMC_CLOCK_DIVIDER_SDR25 = 15, // 8.5, from the table
|
||||||
MMC_CLOCK_DIVIDER_SDR50 = 7, // 4.5, from the table
|
MMC_CLOCK_DIVIDER_SDR50 = 7, // 4.5, from the table
|
||||||
MMC_CLOCK_DIVIDER_SDR104 = 4, // 2, from the datasheet
|
MMC_CLOCK_DIVIDER_SDR104 = 2, // 2, from the table
|
||||||
|
|
||||||
/* Clock dividers: MMC */
|
/* Clock dividers: MMC */
|
||||||
MMC_CLOCK_DIVIDER_HS26 = 30, // 16, from the TRM table
|
MMC_CLOCK_DIVIDER_HS26 = 30, // 16, from the TRM table
|
||||||
MMC_CLOCK_DIVIDER_HS52 = 14, // 8, from the table
|
MMC_CLOCK_DIVIDER_HS52 = 14, // 8, from the table
|
||||||
|
|
||||||
MMC_CLOCK_DIVIDER_HS200 = 2, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
|
#if 0
|
||||||
MMC_CLOCK_DIVIDER_HS400 = 2, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
|
// TODO: Figure out why PLLC4_OUT2_LJ doesn't work, most likely need to be enabled in hwinit
|
||||||
};
|
MMC_CLOCK_DIVIDER_HS200 = 0, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
|
||||||
|
MMC_CLOCK_DIVIDER_HS400 = 0, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
|
||||||
|
#else
|
||||||
|
MMC_CLOCK_DIVIDER_HS200 = 3,
|
||||||
|
MMC_CLOCK_DIVIDER_HS400 = 3,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Clock dividers: Legacy 12 MHz timer */
|
||||||
|
MMC_CLOCK_DIVIDER_LEGACY = 66, // 34 - to get 12 MHz out of 408 MHz
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SDMMC clock divider constants
|
* SDMMC clock divider constants
|
||||||
*/
|
*/
|
||||||
enum sdmmc_clock_sources {
|
enum sdmmc_clock_sources {
|
||||||
|
|
||||||
/* Clock dividers: SD */
|
/* Clock sources: SD */
|
||||||
MMC_CLOCK_SOURCE_SDR12 = 0, // PLLP
|
MMC_CLOCK_SOURCE_SDR12 = CLK_SOURCE_SDMMC1_PLLP_OUT0, // PLLP
|
||||||
MMC_CLOCK_SOURCE_SDR25 = 0,
|
MMC_CLOCK_SOURCE_SDR25 = CLK_SOURCE_SDMMC1_PLLP_OUT0,
|
||||||
MMC_CLOCK_SOURCE_SDR50 = 0,
|
MMC_CLOCK_SOURCE_SDR50 = CLK_SOURCE_SDMMC1_PLLP_OUT0,
|
||||||
MMC_CLOCK_SOURCE_SDR104 = 0,
|
MMC_CLOCK_SOURCE_SDR104 = CLK_SOURCE_SDMMC1_PLLP_OUT0,
|
||||||
|
|
||||||
/* Clock dividers: MMC */
|
/* Clock sources: MMC */
|
||||||
MMC_CLOCK_SOURCE_HS26 = 0, // PLLP
|
MMC_CLOCK_SOURCE_HS26 = CLK_SOURCE_SDMMC4_PLLP_OUT0, // PLLP
|
||||||
MMC_CLOCK_SOURCE_HS52 = 0,
|
MMC_CLOCK_SOURCE_HS52 = CLK_SOURCE_SDMMC4_PLLP_OUT0,
|
||||||
MMC_CLOCK_SOURCE_HS200 = 1, // PLLC4_OUT2_LJ
|
|
||||||
MMC_CLOCK_SOURCE_HS400 = 1,
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// TODO: Figure out why PLLC4_OUT2_LJ doesn't work, most likely need to be enabled in hwinit
|
||||||
|
MMC_CLOCK_SOURCE_HS200 = CLK_SOURCE_SDMMC4_PLLC4_OUT2_LJ, // PLLC4_OUT2_LJ
|
||||||
|
MMC_CLOCK_SOURCE_HS400 = CLK_SOURCE_SDMMC4_PLLC4_OUT2_LJ,
|
||||||
|
#else
|
||||||
|
// For the time being, use PLLP_OUT0
|
||||||
|
MMC_CLOCK_SOURCE_HS200 = CLK_SOURCE_SDMMC4_PLLP_OUT0,
|
||||||
|
MMC_CLOCK_SOURCE_HS400 = CLK_SOURCE_SDMMC4_PLLP_OUT0,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Clock sources: Legacy 12 MHz timer */
|
||||||
|
MMC_CLOCK_SOURCE_LEGACY = CLK_SOURCE_SDMMC_LEGACY_PLLP_OUT0,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -240,7 +258,7 @@ enum sdmmc_register_bits {
|
||||||
MMC_CLOCK_CONTROL_CARD_CLOCK_ENABLE = (1 << 2),
|
MMC_CLOCK_CONTROL_CARD_CLOCK_ENABLE = (1 << 2),
|
||||||
MMC_CLOCK_CONTROL_FREQUENCY_MASK = (0x3FF << 6),
|
MMC_CLOCK_CONTROL_FREQUENCY_MASK = (0x3FF << 6),
|
||||||
MMC_CLOCK_CONTROL_FREQUENCY_SHIFT = 8,
|
MMC_CLOCK_CONTROL_FREQUENCY_SHIFT = 8,
|
||||||
MMC_CLOCK_CONTROL_FREQUENCY_INIT = 0x18, // generates 400kHz from the TRM dividers
|
MMC_CLOCK_CONTROL_FREQUENCY_INIT = 0x1F, // generates 400kHz from the TRM dividers
|
||||||
MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH = 0x00, // passes through the CAR clock unmodified
|
MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH = 0x00, // passes through the CAR clock unmodified
|
||||||
|
|
||||||
/* Host control */
|
/* Host control */
|
||||||
|
@ -796,6 +814,20 @@ static int sdmmc_hardware_reset(struct mmc *mmc, uint32_t reset_flags)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delays for a given amount of host clock cycles.
|
||||||
|
*
|
||||||
|
* @param mmc The MMC controller whose clock cycles should be waited upon.
|
||||||
|
* @param clocks The number of clock cycles to wait.
|
||||||
|
*/
|
||||||
|
static void sdmmc_host_clock_delay(struct mmc *mmc, unsigned int clocks)
|
||||||
|
{
|
||||||
|
// For the time being simply wait for clocks * 50 us
|
||||||
|
// This covers clocks as slow as 20 kHz and hence should always be safe
|
||||||
|
// TODO: determine the actual wait time based on clock source and divider
|
||||||
|
udelay(50 * clocks);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs low-level initialization for SDMMC4, used for the eMMC.
|
* Performs low-level initialization for SDMMC4, used for the eMMC.
|
||||||
*/
|
*/
|
||||||
|
@ -803,23 +835,22 @@ static int sdmmc4_set_up_clock_and_io(struct mmc *mmc)
|
||||||
{
|
{
|
||||||
volatile struct tegra_car *car = car_get_regs();
|
volatile struct tegra_car *car = car_get_regs();
|
||||||
volatile struct tegra_padctl *padctl = padctl_get_regs();
|
volatile struct tegra_padctl *padctl = padctl_get_regs();
|
||||||
(void)mmc;
|
|
||||||
|
|
||||||
// Put SDMMC4 in reset
|
// Put SDMMC4 in reset
|
||||||
car->rst_dev_l_set |= 0x8000;
|
car->rst_dev_l_set |= 0x8000;
|
||||||
|
|
||||||
// Configure the clock to place the device into the initial mode.
|
// Configure the clock to place the device into the initial mode.
|
||||||
car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
|
car->clk_src[CLK_SOURCE_SDMMC4] = MMC_CLOCK_SOURCE_SDR12 | MMC_CLOCK_DIVIDER_SDR12;
|
||||||
|
|
||||||
// Set the legacy divier used for detecting timeouts.
|
// Set the legacy divier used for detecting timeouts.
|
||||||
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
|
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = MMC_CLOCK_SOURCE_LEGACY | MMC_CLOCK_DIVIDER_LEGACY;
|
||||||
|
|
||||||
// Set SDMMC4 clock enable
|
// Set SDMMC4 clock enable
|
||||||
car->clk_enb_l_set |= 0x8000;
|
car->clk_enb_l_set |= 0x8000;
|
||||||
car->clk_enb_y_set |= CAR_CONTROL_SDMMC_LEGACY;
|
car->clk_enb_y_set |= CAR_CONTROL_SDMMC_LEGACY;
|
||||||
|
|
||||||
// host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles
|
// Delay 100 host clock cycles
|
||||||
udelay(5000);
|
sdmmc_host_clock_delay(mmc, 100);
|
||||||
|
|
||||||
// Take SDMMC4 out of reset
|
// Take SDMMC4 out of reset
|
||||||
car->rst_dev_l_clr |= 0x8000;
|
car->rst_dev_l_clr |= 0x8000;
|
||||||
|
@ -1102,11 +1133,11 @@ static int sdmmc_always_fail(struct mmc *mmc)
|
||||||
* a divider of N results in a clock that's (N/2) + 1 slower.
|
* a divider of N results in a clock that's (N/2) + 1 slower.
|
||||||
* @param sdmmc_divisor An additional divisor applied in the SDMMC controller.
|
* @param sdmmc_divisor An additional divisor applied in the SDMMC controller.
|
||||||
*/
|
*/
|
||||||
static void sdmmc4_configure_clock(struct mmc *mmc, int source, int car_divisor, int sdmmc_divisor)
|
static void sdmmc4_configure_clock(struct mmc *mmc, uint32_t source, int car_divisor, int sdmmc_divisor)
|
||||||
{
|
{
|
||||||
volatile struct tegra_car *car = car_get_regs();
|
volatile struct tegra_car *car = car_get_regs();
|
||||||
|
|
||||||
// Set up the CAR aspect of the clock, and wait 2uS per change per the TRM.
|
// Set up the CAR aspect of the clock, and wait 2us per change per the TRM.
|
||||||
car->clk_enb_l_clr = CAR_CONTROL_SDMMC4;
|
car->clk_enb_l_clr = CAR_CONTROL_SDMMC4;
|
||||||
car->clk_src[CLK_SOURCE_SDMMC4] = source | car_divisor;
|
car->clk_src[CLK_SOURCE_SDMMC4] = source | car_divisor;
|
||||||
udelay(2);
|
udelay(2);
|
||||||
|
@ -1128,11 +1159,11 @@ static void sdmmc4_configure_clock(struct mmc *mmc, int source, int car_divisor,
|
||||||
* a divider of N results in a clock that's (N/2) + 1 slower.
|
* a divider of N results in a clock that's (N/2) + 1 slower.
|
||||||
* @param sdmmc_divisor An additional divisor applied in the SDMMC controller.
|
* @param sdmmc_divisor An additional divisor applied in the SDMMC controller.
|
||||||
*/
|
*/
|
||||||
static void sdmmc1_configure_clock(struct mmc *mmc, int source, int car_divisor, int sdmmc_divisor)
|
static void sdmmc1_configure_clock(struct mmc *mmc, uint32_t source, int car_divisor, int sdmmc_divisor)
|
||||||
{
|
{
|
||||||
volatile struct tegra_car *car = car_get_regs();
|
volatile struct tegra_car *car = car_get_regs();
|
||||||
|
|
||||||
// Set up the CAR aspect of the clock, and wait 2uS per change per the TRM.
|
// Set up the CAR aspect of the clock, and wait 2us per change per the TRM.
|
||||||
car->clk_enb_l_clr = CAR_CONTROL_SDMMC1;
|
car->clk_enb_l_clr = CAR_CONTROL_SDMMC1;
|
||||||
car->clk_src[CLK_SOURCE_SDMMC1] = source | car_divisor;
|
car->clk_src[CLK_SOURCE_SDMMC1] = source | car_divisor;
|
||||||
udelay(2);
|
udelay(2);
|
||||||
|
@ -1469,7 +1500,6 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc)
|
||||||
volatile struct tegra_car *car = car_get_regs();
|
volatile struct tegra_car *car = car_get_regs();
|
||||||
volatile struct tegra_pinmux *pinmux = pinmux_get_regs();
|
volatile struct tegra_pinmux *pinmux = pinmux_get_regs();
|
||||||
volatile struct tegra_padctl *padctl = padctl_get_regs();
|
volatile struct tegra_padctl *padctl = padctl_get_regs();
|
||||||
(void)mmc;
|
|
||||||
|
|
||||||
// Set up each of the relevant pins to be connected to output drivers,
|
// Set up each of the relevant pins to be connected to output drivers,
|
||||||
// and selected for SDMMC use.
|
// and selected for SDMMC use.
|
||||||
|
@ -1493,19 +1523,19 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc)
|
||||||
car->rst_dev_l_set = CAR_CONTROL_SDMMC1;
|
car->rst_dev_l_set = CAR_CONTROL_SDMMC1;
|
||||||
|
|
||||||
// Configure the clock to place the device into the initial mode.
|
// Configure the clock to place the device into the initial mode.
|
||||||
car->clk_src[CLK_SOURCE_SDMMC1] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
|
car->clk_src[CLK_SOURCE_SDMMC1] = MMC_CLOCK_SOURCE_SDR12 | MMC_CLOCK_DIVIDER_SDR12;
|
||||||
|
|
||||||
// Set the legacy divier used for detecting timeouts.
|
// Set the legacy divier used for detecting timeouts.
|
||||||
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
|
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = MMC_CLOCK_SOURCE_LEGACY | MMC_CLOCK_DIVIDER_LEGACY;
|
||||||
|
|
||||||
// Set SDMMC1 clock enable
|
// Set SDMMC1 clock enable
|
||||||
car->clk_enb_l_set = CAR_CONTROL_SDMMC1;
|
car->clk_enb_l_set = CAR_CONTROL_SDMMC1;
|
||||||
car->clk_enb_y_set = CAR_CONTROL_SDMMC_LEGACY;
|
car->clk_enb_y_set = CAR_CONTROL_SDMMC_LEGACY;
|
||||||
|
|
||||||
// host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles
|
// Delay 100 host clock cycles
|
||||||
udelay(5000);
|
sdmmc_host_clock_delay(mmc, 100);
|
||||||
|
|
||||||
// Take SDMMC4 out of reset
|
// Take SDMMC1 out of reset
|
||||||
car->rst_dev_l_clr |= CAR_CONTROL_SDMMC1;
|
car->rst_dev_l_clr |= CAR_CONTROL_SDMMC1;
|
||||||
|
|
||||||
// Enable clock loopback.
|
// Enable clock loopback.
|
||||||
|
@ -3443,7 +3473,7 @@ int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller, bool allow_vol
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to a timeout of 1S.
|
// Default to a timeout of 1s.
|
||||||
mmc->timeout = 1000000;
|
mmc->timeout = 1000000;
|
||||||
mmc->partition_switch_time = 1000;
|
mmc->partition_switch_time = 1000;
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ struct mmc {
|
||||||
|
|
||||||
/* Per-controller operations. */
|
/* Per-controller operations. */
|
||||||
int (*set_up_clock_and_io)(struct mmc *mmc);
|
int (*set_up_clock_and_io)(struct mmc *mmc);
|
||||||
void (*configure_clock)(struct mmc *mmc, int source, int car_divisor, int sdmmc_divisor);
|
void (*configure_clock)(struct mmc *mmc, uint32_t source, int car_divisor, int sdmmc_divisor);
|
||||||
int (*enable_supplies)(struct mmc *mmc);
|
int (*enable_supplies)(struct mmc *mmc);
|
||||||
int (*switch_to_low_voltage)(struct mmc *mmc);
|
int (*switch_to_low_voltage)(struct mmc *mmc);
|
||||||
bool (*card_present)(struct mmc *mmc);
|
bool (*card_present)(struct mmc *mmc);
|
||||||
|
|
|
@ -49,7 +49,10 @@ enum {
|
||||||
*/
|
*/
|
||||||
enum {
|
enum {
|
||||||
CLK_SOURCE_MASK = (0b111 << 29),
|
CLK_SOURCE_MASK = (0b111 << 29),
|
||||||
CLK_SOURCE_FIRST = (0b000 << 29),
|
CLK_SOURCE_SDMMC1_PLLP_OUT0 = (0b000 << 29), /* Fixed 408 MHz */
|
||||||
|
CLK_SOURCE_SDMMC4_PLLP_OUT0 = (0b000 << 29), /* Fixed 408 MHz */
|
||||||
|
CLK_SOURCE_SDMMC4_PLLC4_OUT2_LJ = (0b001 << 29), /* 199.68 MHz */
|
||||||
|
CLK_SOURCE_SDMMC_LEGACY_PLLP_OUT0 = (0b100 << 29), /* Fixed 408 MHz */
|
||||||
|
|
||||||
CLK_DIVIDER_MASK = (0xff << 0),
|
CLK_DIVIDER_MASK = (0xff << 0),
|
||||||
CLK_DIVIDER_UNITY = (0x00 << 0),
|
CLK_DIVIDER_UNITY = (0x00 << 0),
|
||||||
|
|
|
@ -141,34 +141,52 @@ enum sdmmc_clock_dividers {
|
||||||
MMC_CLOCK_DIVIDER_SDR12 = 31, // 16.5, from the TRM table
|
MMC_CLOCK_DIVIDER_SDR12 = 31, // 16.5, from the TRM table
|
||||||
MMC_CLOCK_DIVIDER_SDR25 = 15, // 8.5, from the table
|
MMC_CLOCK_DIVIDER_SDR25 = 15, // 8.5, from the table
|
||||||
MMC_CLOCK_DIVIDER_SDR50 = 7, // 4.5, from the table
|
MMC_CLOCK_DIVIDER_SDR50 = 7, // 4.5, from the table
|
||||||
MMC_CLOCK_DIVIDER_SDR104 = 4, // 2, from the datasheet
|
MMC_CLOCK_DIVIDER_SDR104 = 2, // 2, from the table
|
||||||
|
|
||||||
/* Clock dividers: MMC */
|
/* Clock dividers: MMC */
|
||||||
MMC_CLOCK_DIVIDER_HS26 = 30, // 16, from the TRM table
|
MMC_CLOCK_DIVIDER_HS26 = 30, // 16, from the TRM table
|
||||||
MMC_CLOCK_DIVIDER_HS52 = 14, // 8, from the table
|
MMC_CLOCK_DIVIDER_HS52 = 14, // 8, from the table
|
||||||
|
|
||||||
MMC_CLOCK_DIVIDER_HS200 = 2, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
|
#if 0
|
||||||
MMC_CLOCK_DIVIDER_HS400 = 2, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
|
// TODO: Figure out why PLLC4_OUT2_LJ doesn't work, most likely need to be enabled in hwinit
|
||||||
};
|
MMC_CLOCK_DIVIDER_HS200 = 0, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
|
||||||
|
MMC_CLOCK_DIVIDER_HS400 = 0, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
|
||||||
|
#else
|
||||||
|
MMC_CLOCK_DIVIDER_HS200 = 3,
|
||||||
|
MMC_CLOCK_DIVIDER_HS400 = 3,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Clock dividers: Legacy 12 MHz timer */
|
||||||
|
MMC_CLOCK_DIVIDER_LEGACY = 66, // 34 - to get 12 MHz out of 408 MHz
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SDMMC clock divider constants
|
* SDMMC clock divider constants
|
||||||
*/
|
*/
|
||||||
enum sdmmc_clock_sources {
|
enum sdmmc_clock_sources {
|
||||||
|
|
||||||
/* Clock dividers: SD */
|
/* Clock sources: SD */
|
||||||
MMC_CLOCK_SOURCE_SDR12 = 0, // PLLP
|
MMC_CLOCK_SOURCE_SDR12 = CLK_SOURCE_SDMMC1_PLLP_OUT0, // PLLP
|
||||||
MMC_CLOCK_SOURCE_SDR25 = 0,
|
MMC_CLOCK_SOURCE_SDR25 = CLK_SOURCE_SDMMC1_PLLP_OUT0,
|
||||||
MMC_CLOCK_SOURCE_SDR50 = 0,
|
MMC_CLOCK_SOURCE_SDR50 = CLK_SOURCE_SDMMC1_PLLP_OUT0,
|
||||||
MMC_CLOCK_SOURCE_SDR104 = 0,
|
MMC_CLOCK_SOURCE_SDR104 = CLK_SOURCE_SDMMC1_PLLP_OUT0,
|
||||||
|
|
||||||
/* Clock dividers: MMC */
|
/* Clock sources: MMC */
|
||||||
MMC_CLOCK_SOURCE_HS26 = 0, // PLLP
|
MMC_CLOCK_SOURCE_HS26 = CLK_SOURCE_SDMMC4_PLLP_OUT0, // PLLP
|
||||||
MMC_CLOCK_SOURCE_HS52 = 0,
|
MMC_CLOCK_SOURCE_HS52 = CLK_SOURCE_SDMMC4_PLLP_OUT0,
|
||||||
MMC_CLOCK_SOURCE_HS200 = 1, // PLLC4_OUT2_LJ
|
|
||||||
MMC_CLOCK_SOURCE_HS400 = 1,
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// TODO: Figure out why PLLC4_OUT2_LJ doesn't work, most likely need to be enabled in hwinit
|
||||||
|
MMC_CLOCK_SOURCE_HS200 = CLK_SOURCE_SDMMC4_PLLC4_OUT2_LJ, // PLLC4_OUT2_LJ
|
||||||
|
MMC_CLOCK_SOURCE_HS400 = CLK_SOURCE_SDMMC4_PLLC4_OUT2_LJ,
|
||||||
|
#else
|
||||||
|
// For the time being, use PLLP_OUT0
|
||||||
|
MMC_CLOCK_SOURCE_HS200 = CLK_SOURCE_SDMMC4_PLLP_OUT0,
|
||||||
|
MMC_CLOCK_SOURCE_HS400 = CLK_SOURCE_SDMMC4_PLLP_OUT0,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Clock sources: Legacy 12 MHz timer */
|
||||||
|
MMC_CLOCK_SOURCE_LEGACY = CLK_SOURCE_SDMMC_LEGACY_PLLP_OUT0,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -239,7 +257,7 @@ enum sdmmc_register_bits {
|
||||||
MMC_CLOCK_CONTROL_CARD_CLOCK_ENABLE = (1 << 2),
|
MMC_CLOCK_CONTROL_CARD_CLOCK_ENABLE = (1 << 2),
|
||||||
MMC_CLOCK_CONTROL_FREQUENCY_MASK = (0x3FF << 6),
|
MMC_CLOCK_CONTROL_FREQUENCY_MASK = (0x3FF << 6),
|
||||||
MMC_CLOCK_CONTROL_FREQUENCY_SHIFT = 8,
|
MMC_CLOCK_CONTROL_FREQUENCY_SHIFT = 8,
|
||||||
MMC_CLOCK_CONTROL_FREQUENCY_INIT = 0x18, // generates 400kHz from the TRM dividers
|
MMC_CLOCK_CONTROL_FREQUENCY_INIT = 0x1F, // generates 400kHz from the TRM dividers
|
||||||
MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH = 0x00, // passes through the CAR clock unmodified
|
MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH = 0x00, // passes through the CAR clock unmodified
|
||||||
|
|
||||||
/* Host control */
|
/* Host control */
|
||||||
|
@ -797,6 +815,20 @@ static int sdmmc_hardware_reset(struct mmc *mmc, uint32_t reset_flags)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delays for a given amount of host clock cycles.
|
||||||
|
*
|
||||||
|
* @param mmc The MMC controller whose clock cycles should be waited upon.
|
||||||
|
* @param clocks The number of clock cycles to wait.
|
||||||
|
*/
|
||||||
|
static void sdmmc_host_clock_delay(struct mmc *mmc, unsigned int clocks)
|
||||||
|
{
|
||||||
|
// For the time being simply wait for clocks * 50 us
|
||||||
|
// This covers clocks as slow as 20 kHz and hence should always be safe
|
||||||
|
// TODO: determine the actual wait time based on clock source and divider
|
||||||
|
udelay(50 * clocks);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs low-level initialization for SDMMC4, used for the eMMC.
|
* Performs low-level initialization for SDMMC4, used for the eMMC.
|
||||||
*/
|
*/
|
||||||
|
@ -804,23 +836,22 @@ static int sdmmc4_set_up_clock_and_io(struct mmc *mmc)
|
||||||
{
|
{
|
||||||
volatile struct tegra_car *car = car_get_regs();
|
volatile struct tegra_car *car = car_get_regs();
|
||||||
volatile struct tegra_padctl *padctl = padctl_get_regs();
|
volatile struct tegra_padctl *padctl = padctl_get_regs();
|
||||||
(void)mmc;
|
|
||||||
|
|
||||||
// Put SDMMC4 in reset
|
// Put SDMMC4 in reset
|
||||||
car->rst_dev_l_set |= 0x8000;
|
car->rst_dev_l_set |= 0x8000;
|
||||||
|
|
||||||
// Configure the clock to place the device into the initial mode.
|
// Configure the clock to place the device into the initial mode.
|
||||||
car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
|
car->clk_src[CLK_SOURCE_SDMMC4] = MMC_CLOCK_SOURCE_SDR12 | MMC_CLOCK_DIVIDER_SDR12;
|
||||||
|
|
||||||
// Set the legacy divier used for detecting timeouts.
|
// Set the legacy divier used for detecting timeouts.
|
||||||
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
|
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = MMC_CLOCK_SOURCE_LEGACY | MMC_CLOCK_DIVIDER_LEGACY;
|
||||||
|
|
||||||
// Set SDMMC4 clock enable
|
// Set SDMMC4 clock enable
|
||||||
car->clk_enb_l_set |= 0x8000;
|
car->clk_enb_l_set |= 0x8000;
|
||||||
car->clk_enb_y_set |= CAR_CONTROL_SDMMC_LEGACY;
|
car->clk_enb_y_set |= CAR_CONTROL_SDMMC_LEGACY;
|
||||||
|
|
||||||
// host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles
|
// Delay 100 host clock cycles
|
||||||
udelay(5000);
|
sdmmc_host_clock_delay(mmc, 100);
|
||||||
|
|
||||||
// Take SDMMC4 out of reset
|
// Take SDMMC4 out of reset
|
||||||
car->rst_dev_l_clr |= 0x8000;
|
car->rst_dev_l_clr |= 0x8000;
|
||||||
|
@ -1103,11 +1134,11 @@ static int sdmmc_always_fail(struct mmc *mmc)
|
||||||
* a divider of N results in a clock that's (N/2) + 1 slower.
|
* a divider of N results in a clock that's (N/2) + 1 slower.
|
||||||
* @param sdmmc_divisor An additional divisor applied in the SDMMC controller.
|
* @param sdmmc_divisor An additional divisor applied in the SDMMC controller.
|
||||||
*/
|
*/
|
||||||
static void sdmmc4_configure_clock(struct mmc *mmc, int source, int car_divisor, int sdmmc_divisor)
|
static void sdmmc4_configure_clock(struct mmc *mmc, uint32_t source, int car_divisor, int sdmmc_divisor)
|
||||||
{
|
{
|
||||||
volatile struct tegra_car *car = car_get_regs();
|
volatile struct tegra_car *car = car_get_regs();
|
||||||
|
|
||||||
// Set up the CAR aspect of the clock, and wait 2uS per change per the TRM.
|
// Set up the CAR aspect of the clock, and wait 2us per change per the TRM.
|
||||||
car->clk_enb_l_clr = CAR_CONTROL_SDMMC4;
|
car->clk_enb_l_clr = CAR_CONTROL_SDMMC4;
|
||||||
car->clk_src[CLK_SOURCE_SDMMC4] = source | car_divisor;
|
car->clk_src[CLK_SOURCE_SDMMC4] = source | car_divisor;
|
||||||
udelay(2);
|
udelay(2);
|
||||||
|
@ -1129,11 +1160,11 @@ static void sdmmc4_configure_clock(struct mmc *mmc, int source, int car_divisor,
|
||||||
* a divider of N results in a clock that's (N/2) + 1 slower.
|
* a divider of N results in a clock that's (N/2) + 1 slower.
|
||||||
* @param sdmmc_divisor An additional divisor applied in the SDMMC controller.
|
* @param sdmmc_divisor An additional divisor applied in the SDMMC controller.
|
||||||
*/
|
*/
|
||||||
static void sdmmc1_configure_clock(struct mmc *mmc, int source, int car_divisor, int sdmmc_divisor)
|
static void sdmmc1_configure_clock(struct mmc *mmc, uint32_t source, int car_divisor, int sdmmc_divisor)
|
||||||
{
|
{
|
||||||
volatile struct tegra_car *car = car_get_regs();
|
volatile struct tegra_car *car = car_get_regs();
|
||||||
|
|
||||||
// Set up the CAR aspect of the clock, and wait 2uS per change per the TRM.
|
// Set up the CAR aspect of the clock, and wait 2us per change per the TRM.
|
||||||
car->clk_enb_l_clr = CAR_CONTROL_SDMMC1;
|
car->clk_enb_l_clr = CAR_CONTROL_SDMMC1;
|
||||||
car->clk_src[CLK_SOURCE_SDMMC1] = source | car_divisor;
|
car->clk_src[CLK_SOURCE_SDMMC1] = source | car_divisor;
|
||||||
udelay(2);
|
udelay(2);
|
||||||
|
@ -1470,7 +1501,6 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc)
|
||||||
volatile struct tegra_car *car = car_get_regs();
|
volatile struct tegra_car *car = car_get_regs();
|
||||||
volatile struct tegra_pinmux *pinmux = pinmux_get_regs();
|
volatile struct tegra_pinmux *pinmux = pinmux_get_regs();
|
||||||
volatile struct tegra_padctl *padctl = padctl_get_regs();
|
volatile struct tegra_padctl *padctl = padctl_get_regs();
|
||||||
(void)mmc;
|
|
||||||
|
|
||||||
// Set up each of the relevant pins to be connected to output drivers,
|
// Set up each of the relevant pins to be connected to output drivers,
|
||||||
// and selected for SDMMC use.
|
// and selected for SDMMC use.
|
||||||
|
@ -1494,19 +1524,19 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc)
|
||||||
car->rst_dev_l_set = CAR_CONTROL_SDMMC1;
|
car->rst_dev_l_set = CAR_CONTROL_SDMMC1;
|
||||||
|
|
||||||
// Configure the clock to place the device into the initial mode.
|
// Configure the clock to place the device into the initial mode.
|
||||||
car->clk_src[CLK_SOURCE_SDMMC1] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
|
car->clk_src[CLK_SOURCE_SDMMC1] = MMC_CLOCK_SOURCE_SDR12 | MMC_CLOCK_DIVIDER_SDR12;
|
||||||
|
|
||||||
// Set the legacy divier used for detecting timeouts.
|
// Set the legacy divier used for detecting timeouts.
|
||||||
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
|
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = MMC_CLOCK_SOURCE_LEGACY | MMC_CLOCK_DIVIDER_LEGACY;
|
||||||
|
|
||||||
// Set SDMMC1 clock enable
|
// Set SDMMC1 clock enable
|
||||||
car->clk_enb_l_set = CAR_CONTROL_SDMMC1;
|
car->clk_enb_l_set = CAR_CONTROL_SDMMC1;
|
||||||
car->clk_enb_y_set = CAR_CONTROL_SDMMC_LEGACY;
|
car->clk_enb_y_set = CAR_CONTROL_SDMMC_LEGACY;
|
||||||
|
|
||||||
// host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles
|
// Delay 100 host clock cycles
|
||||||
udelay(5000);
|
sdmmc_host_clock_delay(mmc, 100);
|
||||||
|
|
||||||
// Take SDMMC4 out of reset
|
// Take SDMMC1 out of reset
|
||||||
car->rst_dev_l_clr |= CAR_CONTROL_SDMMC1;
|
car->rst_dev_l_clr |= CAR_CONTROL_SDMMC1;
|
||||||
|
|
||||||
// Enable clock loopback.
|
// Enable clock loopback.
|
||||||
|
@ -3444,7 +3474,7 @@ int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller, bool allow_vol
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to a timeout of 1S.
|
// Default to a timeout of 1s.
|
||||||
mmc->timeout = 1000000;
|
mmc->timeout = 1000000;
|
||||||
mmc->partition_switch_time = 1000;
|
mmc->partition_switch_time = 1000;
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ struct mmc {
|
||||||
|
|
||||||
/* Per-controller operations. */
|
/* Per-controller operations. */
|
||||||
int (*set_up_clock_and_io)(struct mmc *mmc);
|
int (*set_up_clock_and_io)(struct mmc *mmc);
|
||||||
void (*configure_clock)(struct mmc *mmc, int source, int car_divisor, int sdmmc_divisor);
|
void (*configure_clock)(struct mmc *mmc, uint32_t source, int car_divisor, int sdmmc_divisor);
|
||||||
int (*enable_supplies)(struct mmc *mmc);
|
int (*enable_supplies)(struct mmc *mmc);
|
||||||
int (*switch_to_low_voltage)(struct mmc *mmc);
|
int (*switch_to_low_voltage)(struct mmc *mmc);
|
||||||
bool (*card_present)(struct mmc *mmc);
|
bool (*card_present)(struct mmc *mmc);
|
||||||
|
|
Loading…
Reference in a new issue