mmc: sdhci-msm: Add calibration tuning for CDCLP533 circuit
authorRitesh Harjani <riteshh@codeaurora.org>
Mon, 21 Nov 2016 06:37:25 +0000 (12:07 +0530)
committerUlf Hansson <ulf.hansson@linaro.org>
Tue, 29 Nov 2016 08:05:19 +0000 (09:05 +0100)
In HS400 mode a new RCLK is introduced on the interface for read data
transfers. The eMMC5.0 device transmits the read data to the host with
respect to rising and falling edges of RCLK. In order to ensure correct
operation of read data transfers in HS400 mode, the incoming RX data
needs to be sampled by delayed version of RCLK.

The CDCLP533 delay circuit shifts the RCLK by T/4. It needs to be
initialized, configured and enabled once during HS400 mode switch and
when operational voltage/clock is changed.

Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
Signed-off-by: Ritesh Harjani <riteshh@codeaurora.org>
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/mmc/host/sdhci-msm.c

index f4f277b..5c73f7d 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/mmc/mmc.h>
 #include <linux/pm_runtime.h>
 #include <linux/slab.h>
+#include <linux/iopoll.h>
 
 #include "sdhci-pltfm.h"
 
@@ -58,6 +59,7 @@
 #define CORE_DLL_PDN           BIT(29)
 #define CORE_DLL_RST           BIT(30)
 #define CORE_DLL_CONFIG                0x100
+#define CORE_CMD_DAT_TRACK_SEL BIT(0)
 #define CORE_DLL_STATUS                0x108
 
 #define CORE_DLL_CONFIG_2      0x1b4
 #define CORE_HC_SELECT_IN_HS400        (6 << 19)
 #define CORE_HC_SELECT_IN_MASK (7 << 19)
 
+#define CORE_CSR_CDC_CTLR_CFG0         0x130
+#define CORE_SW_TRIG_FULL_CALIB                BIT(16)
+#define CORE_HW_AUTOCAL_ENA            BIT(17)
+
+#define CORE_CSR_CDC_CTLR_CFG1         0x134
+#define CORE_CSR_CDC_CAL_TIMER_CFG0    0x138
+#define CORE_TIMER_ENA                 BIT(16)
+
+#define CORE_CSR_CDC_CAL_TIMER_CFG1    0x13C
+#define CORE_CSR_CDC_REFCOUNT_CFG      0x140
+#define CORE_CSR_CDC_COARSE_CAL_CFG    0x144
+#define CORE_CDC_OFFSET_CFG            0x14C
+#define CORE_CSR_CDC_DELAY_CFG         0x150
+#define CORE_CDC_SLAVE_DDA_CFG         0x160
+#define CORE_CSR_CDC_STATUS0           0x164
+#define CORE_CALIBRATION_DONE          BIT(0)
+
+#define CORE_CDC_ERROR_CODE_MASK       0x7000000
+
+#define CORE_CSR_CDC_GEN_CFG           0x178
+#define CORE_CDC_SWITCH_BYPASS_OFF     BIT(0)
+#define CORE_CDC_SWITCH_RC_EN          BIT(1)
+
+#define CORE_DDR_200_CFG               0x184
+#define CORE_CDC_T4_DLY_SEL            BIT(0)
+#define CORE_START_CDC_TRAFFIC         BIT(6)
+
 #define CORE_VENDOR_SPEC_CAPABILITIES0 0x11c
 
 #define INVALID_TUNING_PHASE   -1
@@ -427,6 +456,119 @@ static int msm_init_cm_dll(struct sdhci_host *host)
        return 0;
 }
 
+static int sdhci_msm_cdclp533_calibration(struct sdhci_host *host)
+{
+       struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+       struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
+       u32 config, calib_done;
+       int ret;
+
+       pr_debug("%s: %s: Enter\n", mmc_hostname(host->mmc), __func__);
+
+       /*
+        * Retuning in HS400 (DDR mode) will fail, just reset the
+        * tuning block and restore the saved tuning phase.
+        */
+       ret = msm_init_cm_dll(host);
+       if (ret)
+               goto out;
+
+       /* Set the selected phase in delay line hw block */
+       ret = msm_config_cm_dll_phase(host, msm_host->saved_tuning_phase);
+       if (ret)
+               goto out;
+
+       config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
+       config |= CORE_CMD_DAT_TRACK_SEL;
+       writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
+
+       config = readl_relaxed(host->ioaddr + CORE_DDR_200_CFG);
+       config &= ~CORE_CDC_T4_DLY_SEL;
+       writel_relaxed(config, host->ioaddr + CORE_DDR_200_CFG);
+
+       config = readl_relaxed(host->ioaddr + CORE_CSR_CDC_GEN_CFG);
+       config &= ~CORE_CDC_SWITCH_BYPASS_OFF;
+       writel_relaxed(config, host->ioaddr + CORE_CSR_CDC_GEN_CFG);
+
+       config = readl_relaxed(host->ioaddr + CORE_CSR_CDC_GEN_CFG);
+       config |= CORE_CDC_SWITCH_RC_EN;
+       writel_relaxed(config, host->ioaddr + CORE_CSR_CDC_GEN_CFG);
+
+       config = readl_relaxed(host->ioaddr + CORE_DDR_200_CFG);
+       config &= ~CORE_START_CDC_TRAFFIC;
+       writel_relaxed(config, host->ioaddr + CORE_DDR_200_CFG);
+
+       /*
+        * Perform CDC Register Initialization Sequence
+        *
+        * CORE_CSR_CDC_CTLR_CFG0       0x11800EC
+        * CORE_CSR_CDC_CTLR_CFG1       0x3011111
+        * CORE_CSR_CDC_CAL_TIMER_CFG0  0x1201000
+        * CORE_CSR_CDC_CAL_TIMER_CFG1  0x4
+        * CORE_CSR_CDC_REFCOUNT_CFG    0xCB732020
+        * CORE_CSR_CDC_COARSE_CAL_CFG  0xB19
+        * CORE_CSR_CDC_DELAY_CFG       0x3AC
+        * CORE_CDC_OFFSET_CFG          0x0
+        * CORE_CDC_SLAVE_DDA_CFG       0x16334
+        */
+
+       writel_relaxed(0x11800EC, host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
+       writel_relaxed(0x3011111, host->ioaddr + CORE_CSR_CDC_CTLR_CFG1);
+       writel_relaxed(0x1201000, host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG0);
+       writel_relaxed(0x4, host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG1);
+       writel_relaxed(0xCB732020, host->ioaddr + CORE_CSR_CDC_REFCOUNT_CFG);
+       writel_relaxed(0xB19, host->ioaddr + CORE_CSR_CDC_COARSE_CAL_CFG);
+       writel_relaxed(0x3AC, host->ioaddr + CORE_CSR_CDC_DELAY_CFG);
+       writel_relaxed(0x0, host->ioaddr + CORE_CDC_OFFSET_CFG);
+       writel_relaxed(0x16334, host->ioaddr + CORE_CDC_SLAVE_DDA_CFG);
+
+       /* CDC HW Calibration */
+
+       config = readl_relaxed(host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
+       config |= CORE_SW_TRIG_FULL_CALIB;
+       writel_relaxed(config, host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
+
+       config = readl_relaxed(host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
+       config &= ~CORE_SW_TRIG_FULL_CALIB;
+       writel_relaxed(config, host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
+
+       config = readl_relaxed(host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
+       config |= CORE_HW_AUTOCAL_ENA;
+       writel_relaxed(config, host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
+
+       config = readl_relaxed(host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG0);
+       config |= CORE_TIMER_ENA;
+       writel_relaxed(config, host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG0);
+
+       ret = readl_relaxed_poll_timeout(host->ioaddr + CORE_CSR_CDC_STATUS0,
+                                        calib_done,
+                                        (calib_done & CORE_CALIBRATION_DONE),
+                                        1, 50);
+
+       if (ret == -ETIMEDOUT) {
+               pr_err("%s: %s: CDC calibration was not completed\n",
+                      mmc_hostname(host->mmc), __func__);
+               goto out;
+       }
+
+       ret = readl_relaxed(host->ioaddr + CORE_CSR_CDC_STATUS0)
+                       & CORE_CDC_ERROR_CODE_MASK;
+       if (ret) {
+               pr_err("%s: %s: CDC error code %d\n",
+                      mmc_hostname(host->mmc), __func__, ret);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       config = readl_relaxed(host->ioaddr + CORE_DDR_200_CFG);
+       config |= CORE_START_CDC_TRAFFIC;
+       writel_relaxed(config, host->ioaddr + CORE_DDR_200_CFG);
+out:
+       pr_debug("%s: %s: Exit, ret %d\n", mmc_hostname(host->mmc),
+                __func__, ret);
+       return ret;
+}
+
 static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode)
 {
        int tuning_seq_cnt = 3;
@@ -567,6 +709,15 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host,
        dev_dbg(mmc_dev(mmc), "%s: clock=%u uhs=%u ctrl_2=0x%x\n",
                mmc_hostname(host->mmc), host->clock, uhs, ctrl_2);
        sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+
+       spin_unlock_irq(&host->lock);
+       /* CDCLP533 HW calibration is only required for HS400 mode*/
+       if (host->clock > CORE_FREQ_100MHZ &&
+           msm_host->tuning_done && !msm_host->calibration_done &&
+           mmc->ios.timing == MMC_TIMING_MMC_HS400)
+               if (!sdhci_msm_cdclp533_calibration(host))
+                       msm_host->calibration_done = true;
+       spin_lock_irq(&host->lock);
 }
 
 static void sdhci_msm_voltage_switch(struct sdhci_host *host)