summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/meson-gx-mmc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/host/meson-gx-mmc.c')
-rw-r--r--drivers/mmc/host/meson-gx-mmc.c74
1 files changed, 68 insertions, 6 deletions
diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c
index 341e5a1b32cc..43aabb793121 100644
--- a/drivers/mmc/host/meson-gx-mmc.c
+++ b/drivers/mmc/host/meson-gx-mmc.c
@@ -137,6 +137,10 @@ struct meson_host {
struct clk *mmc_clk;
unsigned long req_rate;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pins_default;
+ struct pinctrl_state *pins_clk_gate;
+
unsigned int bounce_buf_size;
void *bounce_buf;
dma_addr_t bounce_dma_addr;
@@ -272,6 +276,42 @@ static bool meson_mmc_timing_is_ddr(struct mmc_ios *ios)
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)
{
struct mmc_host *mmc = host->mmc;
@@ -288,9 +328,7 @@ static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios)
return 0;
/* stop clock */
- cfg = readl(host->regs + SD_EMMC_CFG);
- cfg |= CFG_STOP_CLOCK;
- writel(cfg, host->regs + SD_EMMC_CFG);
+ meson_mmc_clk_gate(host);
host->req_rate = 0;
if (!rate) {
@@ -299,6 +337,11 @@ static int meson_mmc_clk_set(struct meson_host *host, struct mmc_ios *ios)
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);
if (ret) {
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)
dev_dbg(host->dev, "requested rate was %u\n", ios->clock);
/* (re)start clock */
- cfg = readl(host->regs + SD_EMMC_CFG);
- cfg &= ~CFG_STOP_CLOCK;
- writel(cfg, host->regs + SD_EMMC_CFG);
+ meson_mmc_clk_ungate(host);
return 0;
}
@@ -931,6 +972,27 @@ static int meson_mmc_probe(struct platform_device *pdev)
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");
if (IS_ERR(host->core_clk)) {
ret = PTR_ERR(host->core_clk);