Commit 1e03331d authored by Jerome Brunet's avatar Jerome Brunet Committed by Ulf Hansson

mmc: meson-gx: work around clk-stop issue

It seems that the mmc clock is also used and required, somehow, by
the controller itself.

It is shown during init, when writing to CFG while the divider is set
to 0 will crash the SoC. During a voltage switch, the controller may
crash and the card may then fail to exit busy state if the clock is
stopped.

To avoid this, it is best to keep the clock running for the controller,
except during rate change. However, we still need to be able to gate
the clock out of the SoC. Let's use the pinmux for this, and fallback
to gpio mode (pulled-down) when we need to gate the clock
Reviewed-by: default avatarKevin Hilman <khilman@baylibre.com>
Signed-off-by: default avatarJerome Brunet <jbrunet@baylibre.com>
Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent 844c8a75
...@@ -137,6 +137,10 @@ struct meson_host { ...@@ -137,6 +137,10 @@ struct meson_host {
struct clk *mmc_clk; struct clk *mmc_clk;
unsigned long req_rate; unsigned long req_rate;
struct pinctrl *pinctrl;
struct pinctrl_state *pins_default;
struct pinctrl_state *pins_clk_gate;
unsigned int bounce_buf_size; unsigned int bounce_buf_size;
void *bounce_buf; void *bounce_buf;
dma_addr_t bounce_dma_addr; dma_addr_t bounce_dma_addr;
...@@ -272,6 +276,42 @@ static bool meson_mmc_timing_is_ddr(struct mmc_ios *ios) ...@@ -272,6 +276,42 @@ static bool meson_mmc_timing_is_ddr(struct mmc_ios *ios)
return false; 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
* clock is stopped. The safest thing to do, whenever possible, is to keep
* clock running at stop it at the pad using the pinmux.
*/
static void meson_mmc_clk_gate(struct meson_host *host)
{
u32 cfg;
if (host->pins_clk_gate) {
pinctrl_select_state(host->pinctrl, host->pins_clk_gate);
} else {
/*
* If the pinmux is not provided - default to the classic and
* unsafe method
*/
cfg = readl(host->regs + SD_EMMC_CFG);
cfg |= CFG_STOP_CLOCK;
writel(cfg, host->regs + SD_EMMC_CFG);
}
}
static void meson_mmc_clk_ungate(struct meson_host *host)
{
u32 cfg;
if (host->pins_clk_gate)
pinctrl_select_state(host->pinctrl, host->pins_default);
/* Make sure the clock is not stopped in the controller */
cfg = readl(host->regs + SD_EMMC_CFG);
cfg &= ~CFG_STOP_CLOCK;
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, struct mmc_ios *ios)
{ {
struct mmc_host *mmc = host->mmc; struct mmc_host *mmc = host->mmc;
...@@ -288,9 +328,7 @@ static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios) ...@@ -288,9 +328,7 @@ static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios)
return 0; return 0;
/* stop clock */ /* stop clock */
cfg = readl(host->regs + SD_EMMC_CFG); meson_mmc_clk_gate(host);
cfg |= CFG_STOP_CLOCK;
writel(cfg, host->regs + SD_EMMC_CFG);
host->req_rate = 0; host->req_rate = 0;
if (!rate) { if (!rate) {
...@@ -299,6 +337,11 @@ static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios) ...@@ -299,6 +337,11 @@ static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios)
return 0; 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);
ret = clk_set_rate(host->mmc_clk, rate); ret = clk_set_rate(host->mmc_clk, rate);
if (ret) { if (ret) {
dev_err(host->dev, "Unable to set cfg_div_clk to %lu. ret=%d\n", dev_err(host->dev, "Unable to set cfg_div_clk to %lu. ret=%d\n",
...@@ -318,9 +361,7 @@ static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios) ...@@ -318,9 +361,7 @@ static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios)
dev_dbg(host->dev, "requested rate was %u\n", ios->clock); dev_dbg(host->dev, "requested rate was %u\n", ios->clock);
/* (re)start clock */ /* (re)start clock */
cfg = readl(host->regs + SD_EMMC_CFG); meson_mmc_clk_ungate(host);
cfg &= ~CFG_STOP_CLOCK;
writel(cfg, host->regs + SD_EMMC_CFG);
return 0; return 0;
} }
...@@ -931,6 +972,27 @@ static int meson_mmc_probe(struct platform_device *pdev) ...@@ -931,6 +972,27 @@ static int meson_mmc_probe(struct platform_device *pdev)
goto free_host; goto free_host;
} }
host->pinctrl = devm_pinctrl_get(&pdev->dev);
if (IS_ERR(host->pinctrl)) {
ret = PTR_ERR(host->pinctrl);
goto free_host;
}
host->pins_default = pinctrl_lookup_state(host->pinctrl,
PINCTRL_STATE_DEFAULT);
if (IS_ERR(host->pins_default)) {
ret = PTR_ERR(host->pins_default);
goto free_host;
}
host->pins_clk_gate = pinctrl_lookup_state(host->pinctrl,
"clk-gate");
if (IS_ERR(host->pins_clk_gate)) {
dev_warn(&pdev->dev,
"can't get clk-gate pinctrl, using clk_stop bit\n");
host->pins_clk_gate = NULL;
}
host->core_clk = devm_clk_get(&pdev->dev, "core"); host->core_clk = devm_clk_get(&pdev->dev, "core");
if (IS_ERR(host->core_clk)) { if (IS_ERR(host->core_clk)) {
ret = PTR_ERR(host->core_clk); ret = PTR_ERR(host->core_clk);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment