|
@@ -53,6 +53,7 @@ static void sdhci_send_command(struct sdhci_host *, struct mmc_command *);
|
|
|
static void sdhci_finish_command(struct sdhci_host *);
|
|
|
static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
|
|
|
static void sdhci_tuning_timer(unsigned long data);
|
|
|
+static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
|
|
|
|
|
|
#ifdef CONFIG_PM_RUNTIME
|
|
|
static int sdhci_runtime_pm_get(struct sdhci_host *host);
|
|
@@ -1082,6 +1083,37 @@ static void sdhci_finish_command(struct sdhci_host *host)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static u16 sdhci_get_preset_value(struct sdhci_host *host)
|
|
|
+{
|
|
|
+ u16 ctrl, preset = 0;
|
|
|
+
|
|
|
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
|
+
|
|
|
+ switch (ctrl & SDHCI_CTRL_UHS_MASK) {
|
|
|
+ case SDHCI_CTRL_UHS_SDR12:
|
|
|
+ preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR12);
|
|
|
+ break;
|
|
|
+ case SDHCI_CTRL_UHS_SDR25:
|
|
|
+ preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR25);
|
|
|
+ break;
|
|
|
+ case SDHCI_CTRL_UHS_SDR50:
|
|
|
+ preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR50);
|
|
|
+ break;
|
|
|
+ case SDHCI_CTRL_UHS_SDR104:
|
|
|
+ preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR104);
|
|
|
+ break;
|
|
|
+ case SDHCI_CTRL_UHS_DDR50:
|
|
|
+ preset = sdhci_readw(host, SDHCI_PRESET_FOR_DDR50);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ pr_warn("%s: Invalid UHS-I mode selected\n",
|
|
|
+ mmc_hostname(host->mmc));
|
|
|
+ preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR12);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return preset;
|
|
|
+}
|
|
|
+
|
|
|
static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
|
|
|
{
|
|
|
int div = 0; /* Initialized for compiler warning */
|
|
@@ -1106,35 +1138,43 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
|
|
|
goto out;
|
|
|
|
|
|
if (host->version >= SDHCI_SPEC_300) {
|
|
|
+ if (sdhci_readw(host, SDHCI_HOST_CONTROL2) &
|
|
|
+ SDHCI_CTRL_PRESET_VAL_ENABLE) {
|
|
|
+ u16 pre_val;
|
|
|
+
|
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
|
+ pre_val = sdhci_get_preset_value(host);
|
|
|
+ div = (pre_val & SDHCI_PRESET_SDCLK_FREQ_MASK)
|
|
|
+ >> SDHCI_PRESET_SDCLK_FREQ_SHIFT;
|
|
|
+ if (host->clk_mul &&
|
|
|
+ (pre_val & SDHCI_PRESET_CLKGEN_SEL_MASK)) {
|
|
|
+ clk = SDHCI_PROG_CLOCK_MODE;
|
|
|
+ real_div = div + 1;
|
|
|
+ clk_mul = host->clk_mul;
|
|
|
+ } else {
|
|
|
+ real_div = max_t(int, 1, div << 1);
|
|
|
+ }
|
|
|
+ goto clock_set;
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* Check if the Host Controller supports Programmable Clock
|
|
|
* Mode.
|
|
|
*/
|
|
|
if (host->clk_mul) {
|
|
|
- u16 ctrl;
|
|
|
-
|
|
|
+ for (div = 1; div <= 1024; div++) {
|
|
|
+ if ((host->max_clk * host->clk_mul / div)
|
|
|
+ <= clock)
|
|
|
+ break;
|
|
|
+ }
|
|
|
/*
|
|
|
- * We need to figure out whether the Host Driver needs
|
|
|
- * to select Programmable Clock Mode, or the value can
|
|
|
- * be set automatically by the Host Controller based on
|
|
|
- * the Preset Value registers.
|
|
|
+ * Set Programmable Clock Mode in the Clock
|
|
|
+ * Control register.
|
|
|
*/
|
|
|
- ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
|
- if (!(ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
|
|
|
- for (div = 1; div <= 1024; div++) {
|
|
|
- if (((host->max_clk * host->clk_mul) /
|
|
|
- div) <= clock)
|
|
|
- break;
|
|
|
- }
|
|
|
- /*
|
|
|
- * Set Programmable Clock Mode in the Clock
|
|
|
- * Control register.
|
|
|
- */
|
|
|
- clk = SDHCI_PROG_CLOCK_MODE;
|
|
|
- real_div = div;
|
|
|
- clk_mul = host->clk_mul;
|
|
|
- div--;
|
|
|
- }
|
|
|
+ clk = SDHCI_PROG_CLOCK_MODE;
|
|
|
+ real_div = div;
|
|
|
+ clk_mul = host->clk_mul;
|
|
|
+ div--;
|
|
|
} else {
|
|
|
/* Version 3.00 divisors must be a multiple of 2. */
|
|
|
if (host->max_clk <= clock)
|
|
@@ -1159,6 +1199,7 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
|
|
|
div >>= 1;
|
|
|
}
|
|
|
|
|
|
+clock_set:
|
|
|
if (real_div)
|
|
|
host->mmc->actual_clock = (host->max_clk * clk_mul) / real_div;
|
|
|
|
|
@@ -1376,6 +1417,10 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
|
|
|
sdhci_reinit(host);
|
|
|
}
|
|
|
|
|
|
+ if (host->version >= SDHCI_SPEC_300 &&
|
|
|
+ (ios->power_mode == MMC_POWER_UP))
|
|
|
+ sdhci_enable_preset_value(host, false);
|
|
|
+
|
|
|
sdhci_set_clock(host, ios->clock);
|
|
|
|
|
|
if (ios->power_mode == MMC_POWER_OFF)
|
|
@@ -1496,6 +1541,20 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
|
|
|
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
|
|
}
|
|
|
|
|
|
+ if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN) &&
|
|
|
+ ((ios->timing == MMC_TIMING_UHS_SDR12) ||
|
|
|
+ (ios->timing == MMC_TIMING_UHS_SDR25) ||
|
|
|
+ (ios->timing == MMC_TIMING_UHS_SDR50) ||
|
|
|
+ (ios->timing == MMC_TIMING_UHS_SDR104) ||
|
|
|
+ (ios->timing == MMC_TIMING_UHS_DDR50))) {
|
|
|
+ u16 preset;
|
|
|
+
|
|
|
+ sdhci_enable_preset_value(host, true);
|
|
|
+ preset = sdhci_get_preset_value(host);
|
|
|
+ ios->drv_type = (preset & SDHCI_PRESET_DRV_MASK)
|
|
|
+ >> SDHCI_PRESET_DRV_SHIFT;
|
|
|
+ }
|
|
|
+
|
|
|
/* Re-enable SD Clock */
|
|
|
sdhci_update_clock(host);
|
|
|
} else
|
|
@@ -1925,17 +1984,15 @@ out:
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
-static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable)
|
|
|
+
|
|
|
+static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
|
|
|
{
|
|
|
u16 ctrl;
|
|
|
- unsigned long flags;
|
|
|
|
|
|
/* Host Controller v3.00 defines preset value registers */
|
|
|
if (host->version < SDHCI_SPEC_300)
|
|
|
return;
|
|
|
|
|
|
- spin_lock_irqsave(&host->lock, flags);
|
|
|
-
|
|
|
ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
|
|
|
|
/*
|
|
@@ -1951,17 +2008,6 @@ static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable)
|
|
|
sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
|
|
|
host->flags &= ~SDHCI_PV_ENABLED;
|
|
|
}
|
|
|
-
|
|
|
- spin_unlock_irqrestore(&host->lock, flags);
|
|
|
-}
|
|
|
-
|
|
|
-static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable)
|
|
|
-{
|
|
|
- struct sdhci_host *host = mmc_priv(mmc);
|
|
|
-
|
|
|
- sdhci_runtime_pm_get(host);
|
|
|
- sdhci_do_enable_preset_value(host, enable);
|
|
|
- sdhci_runtime_pm_put(host);
|
|
|
}
|
|
|
|
|
|
static void sdhci_card_event(struct mmc_host *mmc)
|
|
@@ -1997,7 +2043,6 @@ static const struct mmc_host_ops sdhci_ops = {
|
|
|
.enable_sdio_irq = sdhci_enable_sdio_irq,
|
|
|
.start_signal_voltage_switch = sdhci_start_signal_voltage_switch,
|
|
|
.execute_tuning = sdhci_execute_tuning,
|
|
|
- .enable_preset_value = sdhci_enable_preset_value,
|
|
|
.card_event = sdhci_card_event,
|
|
|
.card_busy = sdhci_card_busy,
|
|
|
};
|
|
@@ -2591,8 +2636,12 @@ int sdhci_runtime_resume_host(struct sdhci_host *host)
|
|
|
sdhci_do_set_ios(host, &host->mmc->ios);
|
|
|
|
|
|
sdhci_do_start_signal_voltage_switch(host, &host->mmc->ios);
|
|
|
- if (host_flags & SDHCI_PV_ENABLED)
|
|
|
- sdhci_do_enable_preset_value(host, true);
|
|
|
+ if ((host_flags & SDHCI_PV_ENABLED) &&
|
|
|
+ !(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN)) {
|
|
|
+ spin_lock_irqsave(&host->lock, flags);
|
|
|
+ sdhci_enable_preset_value(host, true);
|
|
|
+ spin_unlock_irqrestore(&host->lock, flags);
|
|
|
+ }
|
|
|
|
|
|
/* Set the re-tuning expiration flag */
|
|
|
if (host->flags & SDHCI_USING_RETUNING_TIMER)
|