Hi, Alim. Tested-by: Jaehoon Chung <jh80.chung@xxxxxxxxxxx> Acked-by: Jaehoon Chung <jh80.chung@xxxxxxxxxxx> I will include this patch into my tree, and i will request pull to Ulf. Thanks! Best Regards, Jaehoon Chung On 01/29/2015 11:41 AM, Alim Akhtar wrote: > From: Seungwon Jeon <tgih.jun@xxxxxxxxxxx> > > Implements HS400 mode support for exynos host driver. > This also include some updates as new mode is added. > > Signed-off-by: Seungwon Jeon <tgih.jun@xxxxxxxxxxx> > Signed-off-by: Alim Akhtar <alim.akhtar@xxxxxxxxxxx> > [Alim: addressed review comments] > --- > .../devicetree/bindings/mmc/exynos-dw-mshc.txt | 7 + > drivers/mmc/host/dw_mmc-exynos.c | 185 ++++++++++++++++---- > drivers/mmc/host/dw_mmc-exynos.h | 19 +- > drivers/mmc/host/dw_mmc.c | 16 +- > drivers/mmc/host/dw_mmc.h | 2 + > 5 files changed, 195 insertions(+), 34 deletions(-) > > diff --git a/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt b/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt > index ee4fc05..aad9844 100644 > --- a/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt > +++ b/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt > @@ -36,6 +36,8 @@ Required Properties: > in transmit mode and CIU clock phase shift value in receive mode for double > data rate mode operation. Refer notes below for the order of the cells and the > valid values. > +* samsung,dw-mshc-hs400-timing: Specifies the value of CIU TX and RX clock phase > + shift value for hs400 mode operation. > > Notes for the sdr-timing and ddr-timing values: > > @@ -50,6 +52,9 @@ Required Properties: > - if CIU clock divider value is 0 (that is divide by 1), both tx and rx > phase shift clocks should be 0. > > +* samsung,read-strobe-delay: RCLK (Data strobe) delay to control HS400 mode > + (Latency value for delay line in Read path) > + > Required properties for a slot (Deprecated - Recommend to use one slot per host): > > * gpios: specifies a list of gpios used for command, clock and data bus. The > @@ -82,5 +87,7 @@ Example: > samsung,dw-mshc-ciu-div = <3>; > samsung,dw-mshc-sdr-timing = <2 3>; > samsung,dw-mshc-ddr-timing = <1 2>; > + samsung,dw-mshc-hs400-timing = <0 2>; > + samsung,read-strobe-delay = <90>; > bus-width = <8>; > }; > diff --git a/drivers/mmc/host/dw_mmc-exynos.c b/drivers/mmc/host/dw_mmc-exynos.c > index fe32948..0a56d76 100644 > --- a/drivers/mmc/host/dw_mmc-exynos.c > +++ b/drivers/mmc/host/dw_mmc-exynos.c > @@ -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[] = { > diff --git a/drivers/mmc/host/dw_mmc-exynos.h b/drivers/mmc/host/dw_mmc-exynos.h > index 7872ce5..595c934 100644 > --- a/drivers/mmc/host/dw_mmc-exynos.h > +++ b/drivers/mmc/host/dw_mmc-exynos.h > @@ -12,20 +12,36 @@ > #ifndef _DW_MMC_EXYNOS_H_ > #define _DW_MMC_EXYNOS_H_ > > -/* Extended Register's Offset */ > #define SDMMC_CLKSEL 0x09C > #define SDMMC_CLKSEL64 0x0A8 > > +/* Extended Register's Offset */ > +#define SDMMC_HS400_DQS_EN 0x180 > +#define SDMMC_HS400_ASYNC_FIFO_CTRL 0x184 > +#define SDMMC_HS400_DLINE_CTRL 0x188 > + > /* CLKSEL register defines */ > #define SDMMC_CLKSEL_CCLK_SAMPLE(x) (((x) & 7) << 0) > #define SDMMC_CLKSEL_CCLK_DRIVE(x) (((x) & 7) << 16) > #define SDMMC_CLKSEL_CCLK_DIVIDER(x) (((x) & 7) << 24) > #define SDMMC_CLKSEL_GET_DRV_WD3(x) (((x) >> 16) & 0x7) > +#define SDMMC_CLKSEL_GET_DIV(x) (((x) >> 24) & 0x7) > +#define SDMMC_CLKSEL_UP_SAMPLE(x, y) (((x) & ~SDMMC_CLKSEL_CCLK_SAMPLE(7)) |\ > + SDMMC_CLKSEL_CCLK_SAMPLE(y)) > #define SDMMC_CLKSEL_TIMING(x, y, z) (SDMMC_CLKSEL_CCLK_SAMPLE(x) | \ > SDMMC_CLKSEL_CCLK_DRIVE(y) | \ > SDMMC_CLKSEL_CCLK_DIVIDER(z)) > +#define SDMMC_CLKSEL_TIMING_MASK SDMMC_CLKSEL_TIMING(0x7, 0x7, 0x7) > #define SDMMC_CLKSEL_WAKEUP_INT BIT(11) > > +/* RCLK_EN register defines */ > +#define DATA_STROBE_EN BIT(0) > +#define AXI_NON_BLOCKING_WR BIT(7) > + > +/* DLINE_CTRL register defines */ > +#define DQS_CTRL_RD_DELAY(x, y) (((x) & ~0x3FF) | ((y) & 0x3FF)) > +#define DQS_CTRL_GET_RD_DELAY(x) ((x) & 0x3FF) > + > /* Protector Register */ > #define SDMMC_EMMCP_BASE 0x1000 > #define SDMMC_MPSECURITY (SDMMC_EMMCP_BASE + 0x0010) > @@ -49,6 +65,7 @@ > /* Fixed clock divider */ > #define EXYNOS4210_FIXED_CIU_CLK_DIV 2 > #define EXYNOS4412_FIXED_CIU_CLK_DIV 4 > +#define HS400_FIXED_CIU_CLK_DIV 1 > > /* Minimal required clock frequency for cclkin, unit: HZ */ > #define EXYNOS_CCLKIN_MIN 50000000 > diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c > index 4d2e3c2..f30ef69 100644 > --- a/drivers/mmc/host/dw_mmc.c > +++ b/drivers/mmc/host/dw_mmc.c > @@ -1084,7 +1084,8 @@ static void dw_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) > regs = mci_readl(slot->host, UHS_REG); > > /* DDR mode set */ > - if (ios->timing == MMC_TIMING_MMC_DDR52) > + if (ios->timing == MMC_TIMING_MMC_DDR52 || > + ios->timing == MMC_TIMING_MMC_HS400) > regs |= ((0x1 << slot->id) << 16); > else > regs &= ~((0x1 << slot->id) << 16); > @@ -1323,6 +1324,18 @@ static int dw_mci_execute_tuning(struct mmc_host *mmc, u32 opcode) > return err; > } > > +int dw_mci_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios) > +{ > + struct dw_mci_slot *slot = mmc_priv(mmc); > + struct dw_mci *host = slot->host; > + const struct dw_mci_drv_data *drv_data = host->drv_data; > + > + if (drv_data && drv_data->prepare_hs400_tuning) > + return drv_data->prepare_hs400_tuning(host, ios); > + > + return 0; > +} > + > static const struct mmc_host_ops dw_mci_ops = { > .request = dw_mci_request, > .pre_req = dw_mci_pre_req, > @@ -1335,6 +1348,7 @@ static const struct mmc_host_ops dw_mci_ops = { > .card_busy = dw_mci_card_busy, > .start_signal_voltage_switch = dw_mci_switch_voltage, > .init_card = dw_mci_init_card, > + .prepare_hs400_tuning = dw_mci_prepare_hs400_tuning, > }; > > static void dw_mci_request_end(struct dw_mci *host, struct mmc_request *mrq) > diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h > index 18c4afe..d239867 100644 > --- a/drivers/mmc/host/dw_mmc.h > +++ b/drivers/mmc/host/dw_mmc.h > @@ -271,5 +271,7 @@ struct dw_mci_drv_data { > void (*set_ios)(struct dw_mci *host, struct mmc_ios *ios); > int (*parse_dt)(struct dw_mci *host); > int (*execute_tuning)(struct dw_mci_slot *slot); > + int (*prepare_hs400_tuning)(struct dw_mci *host, > + struct mmc_ios *ios); > }; > #endif /* _DW_MMC_H_ */ > -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html