summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorVenkat Gopalakrishnan <venkatg@codeaurora.org>2013-06-23 17:36:46 -0700
committerSubhash Jadavani <subhashj@codeaurora.org>2016-05-27 10:28:50 -0700
commit405700959cf6729be2c474052b5060a404ed90b7 (patch)
tree4b9271e76f0aedb93b613320092a21614737d837 /drivers
parent2858ae8aceb39b24d51eec05975d93b73ca271d2 (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.c201
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);