|
@@ -40,7 +40,12 @@ struct dw_mci_exynos_priv_data {
|
|
|
u8 ciu_div;
|
|
|
u32 sdr_timing;
|
|
|
u32 ddr_timing;
|
|
|
+ u32 hs400_timing;
|
|
|
+ u32 tuned_sample;
|
|
|
u32 cur_speed;
|
|
|
+ u32 dqs_delay;
|
|
|
+ u32 saved_dqs_en;
|
|
|
+ u32 saved_strobe_ctrl;
|
|
|
};
|
|
|
|
|
|
static struct dw_mci_exynos_compatible {
|
|
@@ -71,6 +76,21 @@ static struct dw_mci_exynos_compatible {
|
|
|
},
|
|
|
};
|
|
|
|
|
|
+static inline u8 dw_mci_exynos_get_ciu_div(struct dw_mci *host)
|
|
|
+{
|
|
|
+ struct dw_mci_exynos_priv_data *priv = host->priv;
|
|
|
+
|
|
|
+ if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
|
|
|
+ return EXYNOS4412_FIXED_CIU_CLK_DIV;
|
|
|
+ else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
|
|
|
+ return EXYNOS4210_FIXED_CIU_CLK_DIV;
|
|
|
+ else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
|
|
|
+ priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
|
|
|
+ return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL64)) + 1;
|
|
|
+ else
|
|
|
+ return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL)) + 1;
|
|
|
+}
|
|
|
+
|
|
|
static int dw_mci_exynos_priv_init(struct dw_mci *host)
|
|
|
{
|
|
|
struct dw_mci_exynos_priv_data *priv = host->priv;
|
|
@@ -85,6 +105,16 @@ static int dw_mci_exynos_priv_init(struct dw_mci *host)
|
|
|
SDMMC_MPSCTRL_NON_SECURE_WRITE_BIT);
|
|
|
}
|
|
|
|
|
|
+ if (priv->ctrl_type >= DW_MCI_TYPE_EXYNOS5420) {
|
|
|
+ priv->saved_strobe_ctrl = mci_readl(host, HS400_DLINE_CTRL);
|
|
|
+ priv->saved_dqs_en = mci_readl(host, HS400_DQS_EN);
|
|
|
+ priv->saved_dqs_en |= AXI_NON_BLOCKING_WR;
|
|
|
+ mci_writel(host, HS400_DQS_EN, priv->saved_dqs_en);
|
|
|
+ if (!priv->dqs_delay)
|
|
|
+ priv->dqs_delay =
|
|
|
+ DQS_CTRL_GET_RD_DELAY(priv->saved_strobe_ctrl);
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -97,6 +127,26 @@ static int dw_mci_exynos_setup_clock(struct dw_mci *host)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static void dw_mci_exynos_set_clksel_timing(struct dw_mci *host, u32 timing)
|
|
|
+{
|
|
|
+ struct dw_mci_exynos_priv_data *priv = host->priv;
|
|
|
+ u32 clksel;
|
|
|
+
|
|
|
+ if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
|
|
|
+ priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
|
|
|
+ clksel = mci_readl(host, CLKSEL64);
|
|
|
+ else
|
|
|
+ clksel = mci_readl(host, CLKSEL);
|
|
|
+
|
|
|
+ clksel = (clksel & ~SDMMC_CLKSEL_TIMING_MASK) | timing;
|
|
|
+
|
|
|
+ if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
|
|
|
+ priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
|
|
|
+ mci_writel(host, CLKSEL64, clksel);
|
|
|
+ else
|
|
|
+ mci_writel(host, CLKSEL, clksel);
|
|
|
+}
|
|
|
+
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
static int dw_mci_exynos_suspend(struct device *dev)
|
|
|
{
|
|
@@ -172,30 +222,38 @@ static void dw_mci_exynos_prepare_command(struct dw_mci *host, u32 *cmdr)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
|
|
|
+static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing)
|
|
|
{
|
|
|
struct dw_mci_exynos_priv_data *priv = host->priv;
|
|
|
- unsigned int wanted = ios->clock;
|
|
|
- unsigned long actual;
|
|
|
- u8 div = priv->ciu_div + 1;
|
|
|
+ u32 dqs, strobe;
|
|
|
|
|
|
- if (ios->timing == MMC_TIMING_MMC_DDR52) {
|
|
|
- if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
|
|
|
- priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
|
|
|
- mci_writel(host, CLKSEL64, priv->ddr_timing);
|
|
|
- else
|
|
|
- mci_writel(host, CLKSEL, priv->ddr_timing);
|
|
|
- /* Should be double rate for DDR mode */
|
|
|
- if (ios->bus_width == MMC_BUS_WIDTH_8)
|
|
|
- wanted <<= 1;
|
|
|
+ /*
|
|
|
+ * Not supported to configure register
|
|
|
+ * related to HS400
|
|
|
+ */
|
|
|
+ if (priv->ctrl_type < DW_MCI_TYPE_EXYNOS5420)
|
|
|
+ return;
|
|
|
+
|
|
|
+ dqs = priv->saved_dqs_en;
|
|
|
+ strobe = priv->saved_strobe_ctrl;
|
|
|
+
|
|
|
+ if (timing == MMC_TIMING_MMC_HS400) {
|
|
|
+ dqs |= DATA_STROBE_EN;
|
|
|
+ strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay);
|
|
|
} else {
|
|
|
- if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
|
|
|
- priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
|
|
|
- mci_writel(host, CLKSEL64, priv->sdr_timing);
|
|
|
- else
|
|
|
- mci_writel(host, CLKSEL, priv->sdr_timing);
|
|
|
+ dqs &= ~DATA_STROBE_EN;
|
|
|
}
|
|
|
|
|
|
+ mci_writel(host, HS400_DQS_EN, dqs);
|
|
|
+ mci_writel(host, HS400_DLINE_CTRL, strobe);
|
|
|
+}
|
|
|
+
|
|
|
+static void dw_mci_exynos_adjust_clock(struct dw_mci *host, unsigned int wanted)
|
|
|
+{
|
|
|
+ struct dw_mci_exynos_priv_data *priv = host->priv;
|
|
|
+ unsigned long actual;
|
|
|
+ u8 div;
|
|
|
+ int ret;
|
|
|
/*
|
|
|
* Don't care if wanted clock is zero or
|
|
|
* ciu clock is unavailable
|
|
@@ -207,17 +265,52 @@ static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
|
|
|
if (wanted < EXYNOS_CCLKIN_MIN)
|
|
|
wanted = EXYNOS_CCLKIN_MIN;
|
|
|
|
|
|
- if (wanted != priv->cur_speed) {
|
|
|
- int ret = clk_set_rate(host->ciu_clk, wanted * div);
|
|
|
- if (ret)
|
|
|
- dev_warn(host->dev,
|
|
|
- "failed to set clk-rate %u error: %d\n",
|
|
|
- wanted * div, ret);
|
|
|
- actual = clk_get_rate(host->ciu_clk);
|
|
|
- host->bus_hz = actual / div;
|
|
|
- priv->cur_speed = wanted;
|
|
|
- host->current_speed = 0;
|
|
|
+ if (wanted == priv->cur_speed)
|
|
|
+ return;
|
|
|
+
|
|
|
+ div = dw_mci_exynos_get_ciu_div(host);
|
|
|
+ ret = clk_set_rate(host->ciu_clk, wanted * div);
|
|
|
+ if (ret)
|
|
|
+ dev_warn(host->dev,
|
|
|
+ "failed to set clk-rate %u error: %d\n",
|
|
|
+ wanted * div, ret);
|
|
|
+ actual = clk_get_rate(host->ciu_clk);
|
|
|
+ host->bus_hz = actual / div;
|
|
|
+ priv->cur_speed = wanted;
|
|
|
+ host->current_speed = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
|
|
|
+{
|
|
|
+ struct dw_mci_exynos_priv_data *priv = host->priv;
|
|
|
+ unsigned int wanted = ios->clock;
|
|
|
+ u32 timing = ios->timing, clksel;
|
|
|
+
|
|
|
+ switch (timing) {
|
|
|
+ case MMC_TIMING_MMC_HS400:
|
|
|
+ /* Update tuned sample timing */
|
|
|
+ clksel = SDMMC_CLKSEL_UP_SAMPLE(
|
|
|
+ priv->hs400_timing, priv->tuned_sample);
|
|
|
+ wanted <<= 1;
|
|
|
+ break;
|
|
|
+ case MMC_TIMING_MMC_DDR52:
|
|
|
+ clksel = priv->ddr_timing;
|
|
|
+ /* Should be double rate for DDR mode */
|
|
|
+ if (ios->bus_width == MMC_BUS_WIDTH_8)
|
|
|
+ wanted <<= 1;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ clksel = priv->sdr_timing;
|
|
|
}
|
|
|
+
|
|
|
+ /* Set clock timing for the requested speed mode*/
|
|
|
+ dw_mci_exynos_set_clksel_timing(host, clksel);
|
|
|
+
|
|
|
+ /* Configure setting for HS400 */
|
|
|
+ dw_mci_exynos_config_hs400(host, timing);
|
|
|
+
|
|
|
+ /* Configure clock rate */
|
|
|
+ dw_mci_exynos_adjust_clock(host, wanted);
|
|
|
}
|
|
|
|
|
|
static int dw_mci_exynos_parse_dt(struct dw_mci *host)
|
|
@@ -260,6 +353,16 @@ static int dw_mci_exynos_parse_dt(struct dw_mci *host)
|
|
|
return ret;
|
|
|
|
|
|
priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
|
|
|
+
|
|
|
+ ret = of_property_read_u32_array(np,
|
|
|
+ "samsung,dw-mshc-hs400-timing", timing, 2);
|
|
|
+ if (!ret && of_property_read_u32(np,
|
|
|
+ "samsung,read-strobe-delay", &priv->dqs_delay))
|
|
|
+ dev_dbg(host->dev,
|
|
|
+ "read-strobe-delay is not found, assuming usage of default value\n");
|
|
|
+
|
|
|
+ priv->hs400_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1],
|
|
|
+ HS400_FIXED_CIU_CLK_DIV);
|
|
|
host->priv = priv;
|
|
|
return 0;
|
|
|
}
|
|
@@ -285,7 +388,7 @@ static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample)
|
|
|
clksel = mci_readl(host, CLKSEL64);
|
|
|
else
|
|
|
clksel = mci_readl(host, CLKSEL);
|
|
|
- clksel = (clksel & ~0x7) | SDMMC_CLKSEL_CCLK_SAMPLE(sample);
|
|
|
+ clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
|
|
|
if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
|
|
|
priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
|
|
|
mci_writel(host, CLKSEL64, clksel);
|
|
@@ -304,13 +407,16 @@ static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host)
|
|
|
clksel = mci_readl(host, CLKSEL64);
|
|
|
else
|
|
|
clksel = mci_readl(host, CLKSEL);
|
|
|
+
|
|
|
sample = (clksel + 1) & 0x7;
|
|
|
- clksel = (clksel & ~0x7) | sample;
|
|
|
+ clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
|
|
|
+
|
|
|
if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
|
|
|
priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
|
|
|
mci_writel(host, CLKSEL64, clksel);
|
|
|
else
|
|
|
mci_writel(host, CLKSEL, clksel);
|
|
|
+
|
|
|
return sample;
|
|
|
}
|
|
|
|
|
@@ -343,6 +449,7 @@ out:
|
|
|
static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot)
|
|
|
{
|
|
|
struct dw_mci *host = slot->host;
|
|
|
+ struct dw_mci_exynos_priv_data *priv = host->priv;
|
|
|
struct mmc_host *mmc = slot->mmc;
|
|
|
u8 start_smpl, smpl, candiates = 0;
|
|
|
s8 found = -1;
|
|
@@ -360,14 +467,27 @@ static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot)
|
|
|
} while (start_smpl != smpl);
|
|
|
|
|
|
found = dw_mci_exynos_get_best_clksmpl(candiates);
|
|
|
- if (found >= 0)
|
|
|
+ if (found >= 0) {
|
|
|
dw_mci_exynos_set_clksmpl(host, found);
|
|
|
- else
|
|
|
+ priv->tuned_sample = found;
|
|
|
+ } else {
|
|
|
ret = -EIO;
|
|
|
+ }
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+int dw_mci_exynos_prepare_hs400_tuning(struct dw_mci *host,
|
|
|
+ struct mmc_ios *ios)
|
|
|
+{
|
|
|
+ struct dw_mci_exynos_priv_data *priv = host->priv;
|
|
|
+
|
|
|
+ dw_mci_exynos_set_clksel_timing(host, priv->hs400_timing);
|
|
|
+ dw_mci_exynos_adjust_clock(host, (ios->clock) << 1);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/* Common capabilities of Exynos4/Exynos5 SoC */
|
|
|
static unsigned long exynos_dwmmc_caps[4] = {
|
|
|
MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
|
|
@@ -384,6 +504,7 @@ static const struct dw_mci_drv_data exynos_drv_data = {
|
|
|
.set_ios = dw_mci_exynos_set_ios,
|
|
|
.parse_dt = dw_mci_exynos_parse_dt,
|
|
|
.execute_tuning = dw_mci_exynos_execute_tuning,
|
|
|
+ .prepare_hs400_tuning = dw_mci_exynos_prepare_hs400_tuning,
|
|
|
};
|
|
|
|
|
|
static const struct of_device_id dw_mci_exynos_match[] = {
|