mmc: meson-gx: avoid clock glitch when switching to DDR modes

Activating DDR in the Amlogic mmc controller, among other things, will
divide the output clock by 2. So by activating it with clock on, we are
creating a glitch on the output.

Instead, let's deal with DDR when the clock output is off, when setting
the clock.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Tested-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Reviewed-by: Kevin Hilman <khilman@baylibre.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
Jerome Brunet 2019-04-23 11:02:33 +02:00 committed by Ulf Hansson
parent d5f758f2df
commit dc38ac8141
1 changed files with 43 additions and 30 deletions

View File

@ -169,6 +169,7 @@ struct meson_host {
struct clk *rx_clk;
struct clk *tx_clk;
unsigned long req_rate;
bool ddr;
struct pinctrl *pinctrl;
struct pinctrl_state *pins_default;
@ -384,16 +385,6 @@ static void meson_mmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
mmc_get_dma_dir(data));
}
static bool meson_mmc_timing_is_ddr(struct mmc_ios *ios)
{
if (ios->timing == MMC_TIMING_MMC_DDR52 ||
ios->timing == MMC_TIMING_UHS_DDR50 ||
ios->timing == MMC_TIMING_MMC_HS400)
return true;
return false;
}
/*
* Gating the clock on this controller is tricky. It seems the mmc clock
* is also used by the controller. It may crash during some operation if the
@ -430,36 +421,41 @@ static void meson_mmc_clk_ungate(struct meson_host *host)
writel(cfg, host->regs + SD_EMMC_CFG);
}
static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios)
static int meson_mmc_clk_set(struct meson_host *host, unsigned long rate,
bool ddr)
{
struct mmc_host *mmc = host->mmc;
unsigned long rate = ios->clock;
int ret;
u32 cfg;
/* DDR modes require higher module clock */
if (meson_mmc_timing_is_ddr(ios))
rate <<= 1;
/* Same request - bail-out */
if (host->req_rate == rate)
if (host->ddr == ddr && host->req_rate == rate)
return 0;
/* stop clock */
meson_mmc_clk_gate(host);
host->req_rate = 0;
mmc->actual_clock = 0;
if (!rate) {
mmc->actual_clock = 0;
/* return with clock being stopped */
/* return with clock being stopped */
if (!rate)
return 0;
}
/* Stop the clock during rate change to avoid glitches */
cfg = readl(host->regs + SD_EMMC_CFG);
cfg |= CFG_STOP_CLOCK;
writel(cfg, host->regs + SD_EMMC_CFG);
if (ddr) {
/* DDR modes require higher module clock */
rate <<= 1;
cfg |= CFG_DDR;
} else {
cfg &= ~CFG_DDR;
}
writel(cfg, host->regs + SD_EMMC_CFG);
host->ddr = ddr;
ret = clk_set_rate(host->mmc_clk, rate);
if (ret) {
dev_err(host->dev, "Unable to set cfg_div_clk to %lu. ret=%d\n",
@ -471,12 +467,14 @@ static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios)
mmc->actual_clock = clk_get_rate(host->mmc_clk);
/* We should report the real output frequency of the controller */
if (meson_mmc_timing_is_ddr(ios))
if (ddr) {
host->req_rate >>= 1;
mmc->actual_clock >>= 1;
}
dev_dbg(host->dev, "clk rate: %u Hz\n", mmc->actual_clock);
if (ios->clock != mmc->actual_clock)
dev_dbg(host->dev, "requested rate was %u\n", ios->clock);
if (rate != mmc->actual_clock)
dev_dbg(host->dev, "requested rate was %lu\n", rate);
/* (re)start clock */
meson_mmc_clk_ungate(host);
@ -750,6 +748,25 @@ static int meson_mmc_execute_tuning(struct mmc_host *mmc, u32 opcode)
return meson_mmc_clk_phase_tuning(mmc, opcode, host->rx_clk);
}
static int meson_mmc_prepare_ios_clock(struct meson_host *host,
struct mmc_ios *ios)
{
bool ddr;
switch (ios->timing) {
case MMC_TIMING_MMC_DDR52:
case MMC_TIMING_UHS_DDR50:
ddr = true;
break;
default:
ddr = false;
break;
}
return meson_mmc_clk_set(host, ios->clock, ddr);
}
static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct meson_host *host = mmc_priv(mmc);
@ -818,16 +835,12 @@ static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
val = readl(host->regs + SD_EMMC_CFG);
val &= ~CFG_BUS_WIDTH_MASK;
val |= FIELD_PREP(CFG_BUS_WIDTH_MASK, bus_width);
writel(val, host->regs + SD_EMMC_CFG);
val &= ~CFG_DDR;
if (meson_mmc_timing_is_ddr(ios))
val |= CFG_DDR;
err = meson_mmc_clk_set(host, ios);
err = meson_mmc_prepare_ios_clock(host, ios);
if (err)
dev_err(host->dev, "Failed to set clock: %d\n,", err);
writel(val, host->regs + SD_EMMC_CFG);
dev_dbg(host->dev, "SD_EMMC_CFG: 0x%08x\n", val);
}