diff options
author | Venkat Gopalakrishnan <venkatg@codeaurora.org> | 2013-06-23 17:36:46 -0700 |
---|---|---|
committer | Subhash Jadavani <subhashj@codeaurora.org> | 2016-05-27 10:28:50 -0700 |
commit | 405700959cf6729be2c474052b5060a404ed90b7 (patch) | |
tree | 4b9271e76f0aedb93b613320092a21614737d837 /drivers | |
parent | 2858ae8aceb39b24d51eec05975d93b73ca271d2 (diff) |
mmc: sdhci-msm: Add HS400 platform support
The following msm platform specific changes are added to support HS400.
- Enable CDC calibration fixed feedback and sleep clock.
- Allow tuning for HS400 mode.
- Add capability to parse and configure pull settings for RCLK pin.
- Configure HS400 timing mode using the VENDOR_SPECIFIC_FUNC register.
Change-Id: I1304fe0f01df493ead48bf9ff3c7baee5ab040d4
Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mmc/host/sdhci-msm.c | 201 |
1 files changed, 181 insertions, 20 deletions
diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 9ec0ec0baf84..936326fe411b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -42,6 +42,7 @@ #define SDHCI_VER_100 0x2B #define CORE_HC_MODE 0x78 #define HC_MODE_EN 0x1 +#define FF_CLK_SW_RST_DIS (1 << 13) #define CORE_GENERICS 0x70 #define SWITCHABLE_SIGNALLING_VOL (1 << 29) @@ -80,7 +81,13 @@ #define CORE_VENDOR_SPEC 0x10C #define CORE_CLK_PWRSAVE (1 << 1) +#define CORE_HC_MCLK_SEL_DFLT (2 << 8) +#define CORE_HC_MCLK_SEL_HS400 (3 << 8) +#define CORE_HC_MCLK_SEL_MASK (3 << 8) #define CORE_IO_PAD_PWR_SWITCH (1 << 16) +#define CORE_HC_SELECT_IN_EN (1 << 18) +#define CORE_HC_SELECT_IN_HS400 (6 << 19) +#define CORE_HC_SELECT_IN_MASK (7 << 19) #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 @@ -96,6 +103,8 @@ #define SDHCI_MSM_MAX_SEGMENTS (1 << 13) #define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ +#define CORE_FREQ_100MHZ (100 * 1000 * 1000) + static const u32 tuning_block_64[] = { 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777, @@ -241,6 +250,8 @@ struct sdhci_msm_host { struct clk *clk; /* main SD/MMC bus clock */ struct clk *pclk; /* SDHC peripheral bus clock */ struct clk *bus_clk; /* SDHC bus voter clock */ + struct clk *ff_clk; /* CDC calibration fixed feedback clock */ + struct clk *sleep_clk; /* CDC calibration sleep clock */ atomic_t clks_on; /* Set if clocks are enabled */ struct sdhci_msm_pltfm_data *pdata; struct mmc_host *mmc; @@ -251,6 +262,8 @@ struct sdhci_msm_host { struct sdhci_msm_bus_vote msm_bus_vote; struct device_attribute polling; u32 clk_rate; /* Keeps track of current clock rate that is set */ + bool tuning_done; + bool calibration_done; }; enum vdd_io_level { @@ -354,8 +367,8 @@ out: * Find out the greatest range of consecuitive selected * DLL clock output phases that can be used as sampling * setting for SD3.0 UHS-I card read operation (in SDR104 - * timing mode) or for eMMC4.5 card read operation (in HS200 - * timing mode). + * timing mode) or for eMMC4.5 card read operation (in + * HS400/HS200 timing mode). * Select the 3/4 of the range and configure the DLL with the * selected DLL clock output phase. */ @@ -588,12 +601,13 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) struct mmc_ios ios = host->mmc->ios; /* - * Tuning is required for SDR104 and HS200 cards and if clock frequency - * is greater than 100MHz in these modes. + * Tuning is required for SDR104, HS200 and HS400 cards and + * if clock frequency is greater than 100MHz in these modes. */ - if (host->clock <= (100 * 1000 * 1000) || - !(ios.timing == MMC_TIMING_MMC_HS200 || - ios.timing == MMC_TIMING_UHS_SDR104)) + if (host->clock <= CORE_FREQ_100MHZ || + !((ios.timing == MMC_TIMING_MMC_HS400) || + (ios.timing == MMC_TIMING_MMC_HS200) || + (ios.timing == MMC_TIMING_UHS_SDR104))) return 0; pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); @@ -784,7 +798,7 @@ static int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name, goto out; } sz = *len = *len / sizeof(*arr); - if (sz <= 0 || (size > 0 && (sz != size))) { + if (sz <= 0 || (size > 0 && (sz > size))) { dev_err(dev, "%s invalid size\n", prop_name); ret = -EINVAL; goto out; @@ -910,9 +924,9 @@ static int sdhci_msm_dt_get_pad_pull_info(struct device *dev, int id, ret = -ENOMEM; goto out; } - pull_data->size = 3; /* array size for clk, cmd, data */ + pull_data->size = 4; /* array size for clk, cmd, data and rclk */ - /* Allocate on, off configs for clk, cmd, data */ + /* Allocate on, off configs for clk, cmd, data and rclk */ pull = devm_kzalloc(dev, 2 * pull_data->size *\ sizeof(struct sdhci_msm_pad_pull), GFP_KERNEL); if (!pull) { @@ -1195,7 +1209,11 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) if (!name) continue; - if (!strncmp(name, "HS200_1p8v", sizeof("HS200_1p8v"))) + if (!strncmp(name, "HS400_1p8v", sizeof("HS400_1p8v"))) + pdata->caps2 |= MMC_CAP2_HS400_1_8V; + else if (!strncmp(name, "HS400_1p2v", sizeof("HS400_1p2v"))) + pdata->caps2 |= MMC_CAP2_HS400_1_2V; + else if (!strncmp(name, "HS200_1p8v", sizeof("HS200_1p8v"))) pdata->caps2 |= MMC_CAP2_HS200_1_8V_SDR; else if (!strncmp(name, "HS200_1p2v", sizeof("HS200_1p2v"))) pdata->caps2 |= MMC_CAP2_HS200_1_2V_SDR; @@ -2039,6 +2057,22 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) mmc_hostname(host->mmc), __func__, rc); goto disable_pclk; } + if (!IS_ERR(msm_host->ff_clk)) { + rc = clk_prepare_enable(msm_host->ff_clk); + if (rc) { + pr_err("%s: %s: failed to enable the ff_clk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_clk; + } + } + if (!IS_ERR(msm_host->sleep_clk)) { + rc = clk_prepare_enable(msm_host->sleep_clk); + if (rc) { + pr_err("%s: %s: failed to enable the sleep_clk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_ff_clk; + } + } mb(); } else if (!enable && atomic_read(&msm_host->clks_on)) { @@ -2046,6 +2080,10 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) mmc_hostname(host->mmc)); sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); mb(); + if (!IS_ERR_OR_NULL(msm_host->sleep_clk)) + clk_disable_unprepare(msm_host->sleep_clk); + if (!IS_ERR_OR_NULL(msm_host->ff_clk)) + clk_disable_unprepare(msm_host->ff_clk); clk_disable_unprepare(msm_host->clk); if (!IS_ERR(msm_host->pclk)) clk_disable_unprepare(msm_host->pclk); @@ -2056,6 +2094,12 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) } atomic_set(&msm_host->clks_on, enable); goto out; +disable_ff_clk: + if (!IS_ERR_OR_NULL(msm_host->ff_clk)) + clk_disable_unprepare(msm_host->ff_clk); +disable_clk: + if (!IS_ERR_OR_NULL(msm_host->clk)) + clk_disable_unprepare(msm_host->clk); disable_pclk: if (!IS_ERR_OR_NULL(msm_host->pclk)) clk_disable_unprepare(msm_host->pclk); @@ -2088,17 +2132,79 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) goto out; sup_clock = sdhci_msm_get_sup_clk_rate(host, clock); - if (curr_ios.timing == MMC_TIMING_UHS_DDR50) { + if ((curr_ios.timing == MMC_TIMING_UHS_DDR50) || + (curr_ios.timing == MMC_TIMING_MMC_HS400)) { /* * The SDHC requires internal clock frequency to be double the * actual clock that will be set for DDR mode. The controller - * uses the faster clock(100MHz) for some of its parts and send - * the actual required clock (50MHz) to the card. + * uses the faster clock(100/400MHz) for some of its parts and + * send the actual required clock (50/200MHz) to the card. */ ddr_clock = clock * 2; sup_clock = sdhci_msm_get_sup_clk_rate(host, ddr_clock); } + + /* + * In general all timing modes are controlled via UHS mode select in + * Host Control2 register. eMMC specific HS200/HS400 doesn't have + * their respective modes defined here, hence we use these values. + * + * HS200 - SDR104 (Since they both are equivalent in functionality) + * HS400 - This involves multiple configurations + * Initially SDR104 - when tuning is required as HS200 + * Then when switching to DDR @ 400MHz (HS400) we use + * the vendor specific HC_SELECT_IN to control the mode. + * + * In addition to controlling the modes we also need to select the + * correct input clock for DLL depending on the mode. + * + * HS400 - divided clock (free running MCLK/2) + * All other modes - default (free running MCLK) + */ + if (curr_ios.timing == MMC_TIMING_MMC_HS400) { + /* Select the divided clock (free running MCLK/2) */ + writel_relaxed(((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_HC_MCLK_SEL_MASK) + | CORE_HC_MCLK_SEL_HS400), + host->ioaddr + CORE_VENDOR_SPEC); + /* + * Select HS400 mode using the HC_SELECT_IN from VENDOR SPEC + * register + */ + if (msm_host->tuning_done && !msm_host->calibration_done) { + /* + * Write 0x6 to HC_SELECT_IN and 1 to HC_SELECT_IN_EN + * field in VENDOR_SPEC_FUNC + */ + writel_relaxed((readl_relaxed(host->ioaddr + \ + CORE_VENDOR_SPEC) + | CORE_HC_SELECT_IN_HS400 + | CORE_HC_SELECT_IN_EN), + host->ioaddr + CORE_VENDOR_SPEC); + } + } else { + /* Select the default clock (free running MCLK) */ + writel_relaxed(((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_HC_MCLK_SEL_MASK) + | CORE_HC_MCLK_SEL_DFLT), + host->ioaddr + CORE_VENDOR_SPEC); + + /* + * Disable HC_SELECT_IN to be able to use the UHS mode select + * configuration from Host Control2 register for all other + * modes. + * + * Write 0 to HC_SELECT_IN and HC_SELECT_IN_EN field + * in VENDOR_SPEC_FUNC + */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_HC_SELECT_IN_EN + & ~CORE_HC_SELECT_IN_MASK), + host->ioaddr + CORE_VENDOR_SPEC); + } + mb(); + if (sup_clock != msm_host->clk_rate) { pr_debug("%s: %s: setting clk rate to %u\n", mmc_hostname(host->mmc), __func__, sup_clock); @@ -2124,12 +2230,16 @@ out: static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; u16 ctrl_2; ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); /* Select Bus Speed Mode for host */ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; - if (uhs == MMC_TIMING_MMC_HS200) + if (uhs == MMC_TIMING_MMC_HS400) + ctrl_2 |= SDHCI_CTRL_UHS_SDR104; + else if (uhs == MMC_TIMING_MMC_HS200) ctrl_2 |= SDHCI_CTRL_UHS_SDR104; else if (uhs == MMC_TIMING_UHS_SDR12) ctrl_2 |= SDHCI_CTRL_UHS_SDR12; @@ -2147,11 +2257,36 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, * provide feedback clock, the mode selection can be any value less * than 3'b011 in bits [2:0] of HOST CONTROL2 register. */ - if (host->clock <= (100 * 1000 * 1000) && - (uhs == MMC_TIMING_MMC_HS200 || - uhs == MMC_TIMING_UHS_SDR104)) - ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + if (host->clock <= CORE_FREQ_100MHZ) { + if ((uhs == MMC_TIMING_MMC_HS400) || + (uhs == MMC_TIMING_MMC_HS200) || + (uhs == MMC_TIMING_UHS_SDR104)) + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + + /* + * Make sure DLL is disabled when not required + * + * Write 1 to DLL_RST bit of DLL_CONFIG register + */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_RST), + host->ioaddr + CORE_DLL_CONFIG); + + /* Write 1 to DLL_PDN bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_PDN), + host->ioaddr + CORE_DLL_CONFIG); + mb(); + + /* + * The DLL needs to be restored and CDCLP533 recalibrated + * when the clock frequency is set back to 400MHz. + */ + msm_host->calibration_done = false; + } + pr_debug("%s: %s-clock:%u uhs mode:%u ctrl_2:0x%x\n", + mmc_hostname(host->mmc), __func__, host->clock, uhs, ctrl_2); sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); } @@ -2320,9 +2455,25 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->clk_rate = sdhci_msm_get_min_clock(host); atomic_set(&msm_host->clks_on, 1); + /* Setup CDC calibration fixed feedback clock */ + msm_host->ff_clk = devm_clk_get(&pdev->dev, "cal_clk"); + if (!IS_ERR(msm_host->ff_clk)) { + ret = clk_prepare_enable(msm_host->ff_clk); + if (ret) + goto clk_disable; + } + + /* Setup CDC calibration sleep clock */ + msm_host->sleep_clk = devm_clk_get(&pdev->dev, "sleep_clk"); + if (!IS_ERR(msm_host->sleep_clk)) { + ret = clk_prepare_enable(msm_host->sleep_clk); + if (ret) + goto ff_clk_disable; + } + ret = sdhci_msm_bus_register(msm_host, pdev); if (ret) - goto clk_disable; + goto sleep_clk_disable; if (msm_host->msm_bus_vote.client_handle) INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work, @@ -2367,6 +2518,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Set HC_MODE_EN bit in HC_MODE register */ writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); + /* Set FF_CLK_SW_RST_DIS bit in HC_MODE register */ + writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_HC_MODE) | + FF_CLK_SW_RST_DIS, msm_host->core_mem + CORE_HC_MODE); + /* * CORE_SW_RST above may trigger power irq if previous status of PWRCTL * was either BUS_ON or IO_HIGH_V. So before we enable the power irq @@ -2529,6 +2684,12 @@ bus_unregister: if (msm_host->msm_bus_vote.client_handle) sdhci_msm_bus_cancel_work_and_set_vote(host, 0); sdhci_msm_bus_unregister(msm_host); +sleep_clk_disable: + if (!IS_ERR(msm_host->sleep_clk)) + clk_disable_unprepare(msm_host->sleep_clk); +ff_clk_disable: + if (!IS_ERR(msm_host->ff_clk)) + clk_disable_unprepare(msm_host->ff_clk); clk_disable: if (!IS_ERR(msm_host->clk)) clk_disable_unprepare(msm_host->clk); |