mmc: sdhci-of-esdhc: fix clock setting for different controller versions

This patch is to fix clock setting code for different controller
versions. Two of HW changes after vendor version 2.2 are removing
PEREN/HCKEN/IPGEN bits in system control register, and adding SD
clock stable bit in present state register. This patch cleans up
related code too.

Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
Link: https://lore.kernel.org/r/20200108040713.38888-2-yangbo.lu@nxp.com
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
Yangbo Lu 2020-01-08 12:07:13 +08:00 committed by Ulf Hansson
parent 2aa3d826ad
commit 1b21a701ae

View File

@ -562,32 +562,46 @@ static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host)
static void esdhc_clock_enable(struct sdhci_host *host, bool enable)
{
u32 val;
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
ktime_t timeout;
u32 val, clk_en;
clk_en = ESDHC_CLOCK_SDCLKEN;
/*
* IPGEN/HCKEN/PEREN bits exist on eSDHC whose vendor version
* is 2.2 or lower.
*/
if (esdhc->vendor_ver <= VENDOR_V_22)
clk_en |= (ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN |
ESDHC_CLOCK_PEREN);
val = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
if (enable)
val |= ESDHC_CLOCK_SDCLKEN;
val |= clk_en;
else
val &= ~ESDHC_CLOCK_SDCLKEN;
val &= ~clk_en;
sdhci_writel(host, val, ESDHC_SYSTEM_CONTROL);
/* Wait max 20 ms */
/*
* Wait max 20 ms. If vendor version is 2.2 or lower, do not
* wait clock stable bit which does not exist.
*/
timeout = ktime_add_ms(ktime_get(), 20);
val = ESDHC_CLOCK_STABLE;
while (1) {
while (esdhc->vendor_ver > VENDOR_V_22) {
bool timedout = ktime_after(ktime_get(), timeout);
if (sdhci_readl(host, ESDHC_PRSSTAT) & val)
if (sdhci_readl(host, ESDHC_PRSSTAT) & ESDHC_CLOCK_STABLE)
break;
if (timedout) {
pr_err("%s: Internal clock never stabilised.\n",
mmc_hostname(host->mmc));
break;
}
udelay(10);
usleep_range(10, 20);
}
}
@ -621,77 +635,97 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
int pre_div = 1;
int div = 1;
int division;
unsigned int pre_div = 1, div = 1;
unsigned int clock_fixup = 0;
ktime_t timeout;
long fixup = 0;
u32 temp;
host->mmc->actual_clock = 0;
if (clock == 0) {
host->mmc->actual_clock = 0;
esdhc_clock_enable(host, false);
return;
}
/* Workaround to start pre_div at 2 for VNN < VENDOR_V_23 */
/* Start pre_div at 2 for vendor version < 2.3. */
if (esdhc->vendor_ver < VENDOR_V_23)
pre_div = 2;
/* Fix clock value. */
if (host->mmc->card && mmc_card_sd(host->mmc->card) &&
esdhc->clk_fixup && host->mmc->ios.timing == MMC_TIMING_LEGACY)
fixup = esdhc->clk_fixup->sd_dflt_max_clk;
esdhc->clk_fixup && host->mmc->ios.timing == MMC_TIMING_LEGACY)
clock_fixup = esdhc->clk_fixup->sd_dflt_max_clk;
else if (esdhc->clk_fixup)
fixup = esdhc->clk_fixup->max_clk[host->mmc->ios.timing];
clock_fixup = esdhc->clk_fixup->max_clk[host->mmc->ios.timing];
if (fixup && clock > fixup)
clock = fixup;
if (clock_fixup == 0 || clock < clock_fixup)
clock_fixup = clock;
temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
temp &= ~(ESDHC_CLOCK_SDCLKEN | ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN |
ESDHC_CLOCK_PEREN | ESDHC_CLOCK_MASK);
sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
while (host->max_clk / pre_div / 16 > clock && pre_div < 256)
/* Calculate pre_div and div. */
while (host->max_clk / pre_div / 16 > clock_fixup && pre_div < 256)
pre_div *= 2;
while (host->max_clk / pre_div / div > clock && div < 16)
while (host->max_clk / pre_div / div > clock_fixup && div < 16)
div++;
esdhc->div_ratio = pre_div * div;
/* Limit clock division for HS400 200MHz clock for quirk. */
if (esdhc->quirk_limited_clk_division &&
clock == MMC_HS200_MAX_DTR &&
(host->mmc->ios.timing == MMC_TIMING_MMC_HS400 ||
host->flags & SDHCI_HS400_TUNING)) {
division = pre_div * div;
if (division <= 4) {
if (esdhc->div_ratio <= 4) {
pre_div = 4;
div = 1;
} else if (division <= 8) {
} else if (esdhc->div_ratio <= 8) {
pre_div = 4;
div = 2;
} else if (division <= 12) {
} else if (esdhc->div_ratio <= 12) {
pre_div = 4;
div = 3;
} else {
pr_warn("%s: using unsupported clock division.\n",
mmc_hostname(host->mmc));
}
esdhc->div_ratio = pre_div * div;
}
host->mmc->actual_clock = host->max_clk / esdhc->div_ratio;
dev_dbg(mmc_dev(host->mmc), "desired SD clock: %d, actual: %d\n",
clock, host->max_clk / pre_div / div);
host->mmc->actual_clock = host->max_clk / pre_div / div;
esdhc->div_ratio = pre_div * div;
clock, host->mmc->actual_clock);
/* Set clock division into register. */
pre_div >>= 1;
div--;
esdhc_clock_enable(host, false);
temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
temp |= (ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN
| (div << ESDHC_DIVIDER_SHIFT)
| (pre_div << ESDHC_PREDIV_SHIFT));
temp &= ~ESDHC_CLOCK_MASK;
temp |= ((div << ESDHC_DIVIDER_SHIFT) |
(pre_div << ESDHC_PREDIV_SHIFT));
sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
/*
* Wait max 20 ms. If vendor version is 2.2 or lower, do not
* wait clock stable bit which does not exist.
*/
timeout = ktime_add_ms(ktime_get(), 20);
while (esdhc->vendor_ver > VENDOR_V_22) {
bool timedout = ktime_after(ktime_get(), timeout);
if (sdhci_readl(host, ESDHC_PRSSTAT) & ESDHC_CLOCK_STABLE)
break;
if (timedout) {
pr_err("%s: Internal clock never stabilised.\n",
mmc_hostname(host->mmc));
break;
}
usleep_range(10, 20);
}
/* Additional setting for HS400. */
if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400 &&
clock == MMC_HS200_MAX_DTR) {
temp = sdhci_readl(host, ESDHC_TBCTL);
@ -711,25 +745,7 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock)
esdhc_clock_enable(host, false);
esdhc_flush_async_fifo(host);
}
/* Wait max 20 ms */
timeout = ktime_add_ms(ktime_get(), 20);
while (1) {
bool timedout = ktime_after(ktime_get(), timeout);
if (sdhci_readl(host, ESDHC_PRSSTAT) & ESDHC_CLOCK_STABLE)
break;
if (timedout) {
pr_err("%s: Internal clock never stabilised.\n",
mmc_hostname(host->mmc));
return;
}
udelay(10);
}
temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
temp |= ESDHC_CLOCK_SDCLKEN;
sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
esdhc_clock_enable(host, false);
}
static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width)