Merge branches 'pm-cpuidle' and 'pm-qos'
[linux-2.6-block.git] / drivers / mmc / host / sdhci-omap.c
index 628bfe9a3d175e2e46e7fb4fccb99c18c1d852fd..1456abd5eeb9563aa6a5cec10d066a019a02201f 100644 (file)
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
 #include <linux/regulator/consumer.h>
+#include <linux/pinctrl/consumer.h>
 
 #include "sdhci-pltfm.h"
 
 #define SDHCI_OMAP_CON         0x12c
 #define CON_DW8                        BIT(5)
 #define CON_DMA_MASTER         BIT(20)
+#define CON_DDR                        BIT(19)
+#define CON_CLKEXTFREE         BIT(16)
+#define CON_PADEN              BIT(15)
 #define CON_INIT               BIT(1)
 #define CON_OD                 BIT(0)
 
+#define SDHCI_OMAP_DLL         0x0134
+#define DLL_SWT                        BIT(20)
+#define DLL_FORCE_SR_C_SHIFT   13
+#define DLL_FORCE_SR_C_MASK    (0x7f << DLL_FORCE_SR_C_SHIFT)
+#define DLL_FORCE_VALUE                BIT(12)
+#define DLL_CALIB              BIT(1)
+
 #define SDHCI_OMAP_CMD         0x20c
 
+#define SDHCI_OMAP_PSTATE      0x0224
+#define PSTATE_DLEV_DAT0       BIT(20)
+#define PSTATE_DATI            BIT(1)
+
 #define SDHCI_OMAP_HCTL                0x228
 #define HCTL_SDBP              BIT(8)
 #define HCTL_SDVS_SHIFT                9
 
 #define SDHCI_OMAP_AC12                0x23c
 #define AC12_V1V8_SIGEN                BIT(19)
+#define AC12_SCLK_SEL          BIT(23)
 
 #define SDHCI_OMAP_CAPA                0x240
 #define CAPA_VS33              BIT(24)
 #define CAPA_VS30              BIT(25)
 #define CAPA_VS18              BIT(26)
 
+#define SDHCI_OMAP_CAPA2       0x0244
+#define CAPA2_TSDR50           BIT(13)
+
 #define SDHCI_OMAP_TIMEOUT     1               /* 1 msec */
 
 #define SYSCTL_CLKD_MAX                0x3FF
 #define IOV_3V0                        3000000         /* 300000 uV */
 #define IOV_3V3                        3300000         /* 330000 uV */
 
+#define MAX_PHASE_DELAY                0x7C
+
+/* sdhci-omap controller flags */
+#define SDHCI_OMAP_REQUIRE_IODELAY     BIT(0)
+
 struct sdhci_omap_data {
        u32 offset;
+       u8 flags;
 };
 
 struct sdhci_omap_host {
@@ -82,8 +107,16 @@ struct sdhci_omap_host {
        struct sdhci_host       *host;
        u8                      bus_mode;
        u8                      power_mode;
+       u8                      timing;
+       u8                      flags;
+
+       struct pinctrl          *pinctrl;
+       struct pinctrl_state    **pinctrl_state;
 };
 
+static void sdhci_omap_start_clock(struct sdhci_omap_host *omap_host);
+static void sdhci_omap_stop_clock(struct sdhci_omap_host *omap_host);
+
 static inline u32 sdhci_omap_readl(struct sdhci_omap_host *host,
                                   unsigned int offset)
 {
@@ -191,6 +224,178 @@ static void sdhci_omap_conf_bus_power(struct sdhci_omap_host *omap_host,
        }
 }
 
+static inline void sdhci_omap_set_dll(struct sdhci_omap_host *omap_host,
+                                     int count)
+{
+       int i;
+       u32 reg;
+
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_DLL);
+       reg |= DLL_FORCE_VALUE;
+       reg &= ~DLL_FORCE_SR_C_MASK;
+       reg |= (count << DLL_FORCE_SR_C_SHIFT);
+       sdhci_omap_writel(omap_host, SDHCI_OMAP_DLL, reg);
+
+       reg |= DLL_CALIB;
+       sdhci_omap_writel(omap_host, SDHCI_OMAP_DLL, reg);
+       for (i = 0; i < 1000; i++) {
+               reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_DLL);
+               if (reg & DLL_CALIB)
+                       break;
+       }
+       reg &= ~DLL_CALIB;
+       sdhci_omap_writel(omap_host, SDHCI_OMAP_DLL, reg);
+}
+
+static void sdhci_omap_disable_tuning(struct sdhci_omap_host *omap_host)
+{
+       u32 reg;
+
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_AC12);
+       reg &= ~AC12_SCLK_SEL;
+       sdhci_omap_writel(omap_host, SDHCI_OMAP_AC12, reg);
+
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_DLL);
+       reg &= ~(DLL_FORCE_VALUE | DLL_SWT);
+       sdhci_omap_writel(omap_host, SDHCI_OMAP_DLL, reg);
+}
+
+static int sdhci_omap_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+       struct sdhci_host *host = mmc_priv(mmc);
+       struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+       struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host);
+       struct device *dev = omap_host->dev;
+       struct mmc_ios *ios = &mmc->ios;
+       u32 start_window = 0, max_window = 0;
+       u8 cur_match, prev_match = 0;
+       u32 length = 0, max_len = 0;
+       u32 ier = host->ier;
+       u32 phase_delay = 0;
+       int ret = 0;
+       u32 reg;
+
+       pltfm_host = sdhci_priv(host);
+       omap_host = sdhci_pltfm_priv(pltfm_host);
+       dev = omap_host->dev;
+
+       /* clock tuning is not needed for upto 52MHz */
+       if (ios->clock <= 52000000)
+               return 0;
+
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_CAPA2);
+       if (ios->timing == MMC_TIMING_UHS_SDR50 && !(reg & CAPA2_TSDR50))
+               return 0;
+
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_DLL);
+       reg |= DLL_SWT;
+       sdhci_omap_writel(omap_host, SDHCI_OMAP_DLL, reg);
+
+       /*
+        * OMAP5/DRA74X/DRA72x Errata i802:
+        * DCRC error interrupts (MMCHS_STAT[21] DCRC=0x1) can occur
+        * during the tuning procedure. So disable it during the
+        * tuning procedure.
+        */
+       ier &= ~SDHCI_INT_DATA_CRC;
+       sdhci_writel(host, ier, SDHCI_INT_ENABLE);
+       sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+
+       while (phase_delay <= MAX_PHASE_DELAY) {
+               sdhci_omap_set_dll(omap_host, phase_delay);
+
+               cur_match = !mmc_send_tuning(mmc, opcode, NULL);
+               if (cur_match) {
+                       if (prev_match) {
+                               length++;
+                       } else {
+                               start_window = phase_delay;
+                               length = 1;
+                       }
+               }
+
+               if (length > max_len) {
+                       max_window = start_window;
+                       max_len = length;
+               }
+
+               prev_match = cur_match;
+               phase_delay += 4;
+       }
+
+       if (!max_len) {
+               dev_err(dev, "Unable to find match\n");
+               ret = -EIO;
+               goto tuning_error;
+       }
+
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_AC12);
+       if (!(reg & AC12_SCLK_SEL)) {
+               ret = -EIO;
+               goto tuning_error;
+       }
+
+       phase_delay = max_window + 4 * (max_len >> 1);
+       sdhci_omap_set_dll(omap_host, phase_delay);
+
+       goto ret;
+
+tuning_error:
+       dev_err(dev, "Tuning failed\n");
+       sdhci_omap_disable_tuning(omap_host);
+
+ret:
+       sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+       sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+       sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+       return ret;
+}
+
+static int sdhci_omap_card_busy(struct mmc_host *mmc)
+{
+       u32 reg, ac12;
+       int ret = false;
+       struct sdhci_host *host = mmc_priv(mmc);
+       struct sdhci_pltfm_host *pltfm_host;
+       struct sdhci_omap_host *omap_host;
+       u32 ier = host->ier;
+
+       pltfm_host = sdhci_priv(host);
+       omap_host = sdhci_pltfm_priv(pltfm_host);
+
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_CON);
+       ac12 = sdhci_omap_readl(omap_host, SDHCI_OMAP_AC12);
+       reg &= ~CON_CLKEXTFREE;
+       if (ac12 & AC12_V1V8_SIGEN)
+               reg |= CON_CLKEXTFREE;
+       reg |= CON_PADEN;
+       sdhci_omap_writel(omap_host, SDHCI_OMAP_CON, reg);
+
+       disable_irq(host->irq);
+       ier |= SDHCI_INT_CARD_INT;
+       sdhci_writel(host, ier, SDHCI_INT_ENABLE);
+       sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+
+       /*
+        * Delay is required for PSTATE to correctly reflect
+        * DLEV/CLEV values after PADEN is set.
+        */
+       usleep_range(50, 100);
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_PSTATE);
+       if ((reg & PSTATE_DATI) || !(reg & PSTATE_DLEV_DAT0))
+               ret = true;
+
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_CON);
+       reg &= ~(CON_CLKEXTFREE | CON_PADEN);
+       sdhci_omap_writel(omap_host, SDHCI_OMAP_CON, reg);
+
+       sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+       sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+       enable_irq(host->irq);
+
+       return ret;
+}
+
 static int sdhci_omap_start_signal_voltage_switch(struct mmc_host *mmc,
                                                  struct mmc_ios *ios)
 {
@@ -244,6 +449,39 @@ static int sdhci_omap_start_signal_voltage_switch(struct mmc_host *mmc,
        return 0;
 }
 
+static void sdhci_omap_set_timing(struct sdhci_omap_host *omap_host, u8 timing)
+{
+       int ret;
+       struct pinctrl_state *pinctrl_state;
+       struct device *dev = omap_host->dev;
+
+       if (!(omap_host->flags & SDHCI_OMAP_REQUIRE_IODELAY))
+               return;
+
+       if (omap_host->timing == timing)
+               return;
+
+       sdhci_omap_stop_clock(omap_host);
+
+       pinctrl_state = omap_host->pinctrl_state[timing];
+       ret = pinctrl_select_state(omap_host->pinctrl, pinctrl_state);
+       if (ret) {
+               dev_err(dev, "failed to select pinctrl state\n");
+               return;
+       }
+
+       sdhci_omap_start_clock(omap_host);
+       omap_host->timing = timing;
+}
+
+static void sdhci_omap_set_power_mode(struct sdhci_omap_host *omap_host,
+                                     u8 power_mode)
+{
+       if (omap_host->bus_mode == MMC_POWER_OFF)
+               sdhci_omap_disable_tuning(omap_host);
+       omap_host->power_mode = power_mode;
+}
+
 static void sdhci_omap_set_bus_mode(struct sdhci_omap_host *omap_host,
                                    unsigned int mode)
 {
@@ -272,7 +510,9 @@ static void sdhci_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        omap_host = sdhci_pltfm_priv(pltfm_host);
 
        sdhci_omap_set_bus_mode(omap_host, ios->bus_mode);
+       sdhci_omap_set_timing(omap_host, ios->timing);
        sdhci_set_ios(mmc, ios);
+       sdhci_omap_set_power_mode(omap_host, ios->power_mode);
 }
 
 static u16 sdhci_omap_calc_divisor(struct sdhci_pltfm_host *host,
@@ -401,8 +641,26 @@ static void sdhci_omap_init_74_clocks(struct sdhci_host *host, u8 power_mode)
        sdhci_omap_writel(omap_host, SDHCI_OMAP_STAT, INT_CC_EN);
 
        enable_irq(host->irq);
+}
 
-       omap_host->power_mode = power_mode;
+static void sdhci_omap_set_uhs_signaling(struct sdhci_host *host,
+                                        unsigned int timing)
+{
+       u32 reg;
+       struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+       struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host);
+
+       sdhci_omap_stop_clock(omap_host);
+
+       reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_CON);
+       if (timing == MMC_TIMING_UHS_DDR50 || timing == MMC_TIMING_MMC_DDR52)
+               reg |= CON_DDR;
+       else
+               reg &= ~CON_DDR;
+       sdhci_omap_writel(omap_host, SDHCI_OMAP_CON, reg);
+
+       sdhci_set_uhs_signaling(host, timing);
+       sdhci_omap_start_clock(omap_host);
 }
 
 static struct sdhci_ops sdhci_omap_ops = {
@@ -414,7 +672,7 @@ static struct sdhci_ops sdhci_omap_ops = {
        .set_bus_width = sdhci_omap_set_bus_width,
        .platform_send_init_74_clocks = sdhci_omap_init_74_clocks,
        .reset = sdhci_reset,
-       .set_uhs_signaling = sdhci_set_uhs_signaling,
+       .set_uhs_signaling = sdhci_omap_set_uhs_signaling,
 };
 
 static int sdhci_omap_set_capabilities(struct sdhci_omap_host *omap_host)
@@ -453,14 +711,15 @@ static const struct sdhci_pltfm_data sdhci_omap_pdata = {
                  SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
                  SDHCI_QUIRK_NO_HISPD_BIT |
                  SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC,
-       .quirks2 = SDHCI_QUIRK2_NO_1_8_V |
-                  SDHCI_QUIRK2_ACMD23_BROKEN |
+       .quirks2 = SDHCI_QUIRK2_ACMD23_BROKEN |
+                  SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
                   SDHCI_QUIRK2_RSP_136_HAS_CRC,
        .ops = &sdhci_omap_ops,
 };
 
 static const struct sdhci_omap_data dra7_data = {
        .offset = 0x200,
+       .flags  = SDHCI_OMAP_REQUIRE_IODELAY,
 };
 
 static const struct of_device_id omap_sdhci_match[] = {
@@ -469,6 +728,108 @@ static const struct of_device_id omap_sdhci_match[] = {
 };
 MODULE_DEVICE_TABLE(of, omap_sdhci_match);
 
+static struct pinctrl_state
+*sdhci_omap_iodelay_pinctrl_state(struct sdhci_omap_host *omap_host, char *mode,
+                                 u32 *caps, u32 capmask)
+{
+       struct device *dev = omap_host->dev;
+       struct pinctrl_state *pinctrl_state = ERR_PTR(-ENODEV);
+
+       if (!(*caps & capmask))
+               goto ret;
+
+       pinctrl_state = pinctrl_lookup_state(omap_host->pinctrl, mode);
+       if (IS_ERR(pinctrl_state)) {
+               dev_err(dev, "no pinctrl state for %s mode", mode);
+               *caps &= ~capmask;
+       }
+
+ret:
+       return pinctrl_state;
+}
+
+static int sdhci_omap_config_iodelay_pinctrl_state(struct sdhci_omap_host
+                                                  *omap_host)
+{
+       struct device *dev = omap_host->dev;
+       struct sdhci_host *host = omap_host->host;
+       struct mmc_host *mmc = host->mmc;
+       u32 *caps = &mmc->caps;
+       u32 *caps2 = &mmc->caps2;
+       struct pinctrl_state *state;
+       struct pinctrl_state **pinctrl_state;
+
+       if (!(omap_host->flags & SDHCI_OMAP_REQUIRE_IODELAY))
+               return 0;
+
+       pinctrl_state = devm_kzalloc(dev, sizeof(*pinctrl_state) *
+                                    (MMC_TIMING_MMC_HS200 + 1), GFP_KERNEL);
+       if (!pinctrl_state)
+               return -ENOMEM;
+
+       omap_host->pinctrl = devm_pinctrl_get(omap_host->dev);
+       if (IS_ERR(omap_host->pinctrl)) {
+               dev_err(dev, "Cannot get pinctrl\n");
+               return PTR_ERR(omap_host->pinctrl);
+       }
+
+       state = pinctrl_lookup_state(omap_host->pinctrl, "default");
+       if (IS_ERR(state)) {
+               dev_err(dev, "no pinctrl state for default mode\n");
+               return PTR_ERR(state);
+       }
+       pinctrl_state[MMC_TIMING_LEGACY] = state;
+
+       state = sdhci_omap_iodelay_pinctrl_state(omap_host, "sdr104", caps,
+                                                MMC_CAP_UHS_SDR104);
+       if (!IS_ERR(state))
+               pinctrl_state[MMC_TIMING_UHS_SDR104] = state;
+
+       state = sdhci_omap_iodelay_pinctrl_state(omap_host, "ddr50", caps,
+                                                MMC_CAP_UHS_DDR50);
+       if (!IS_ERR(state))
+               pinctrl_state[MMC_TIMING_UHS_DDR50] = state;
+
+       state = sdhci_omap_iodelay_pinctrl_state(omap_host, "sdr50", caps,
+                                                MMC_CAP_UHS_SDR50);
+       if (!IS_ERR(state))
+               pinctrl_state[MMC_TIMING_UHS_SDR50] = state;
+
+       state = sdhci_omap_iodelay_pinctrl_state(omap_host, "sdr25", caps,
+                                                MMC_CAP_UHS_SDR25);
+       if (!IS_ERR(state))
+               pinctrl_state[MMC_TIMING_UHS_SDR25] = state;
+
+       state = sdhci_omap_iodelay_pinctrl_state(omap_host, "sdr12", caps,
+                                                MMC_CAP_UHS_SDR12);
+       if (!IS_ERR(state))
+               pinctrl_state[MMC_TIMING_UHS_SDR12] = state;
+
+       state = sdhci_omap_iodelay_pinctrl_state(omap_host, "ddr_1_8v", caps,
+                                                MMC_CAP_1_8V_DDR);
+       if (!IS_ERR(state))
+               pinctrl_state[MMC_TIMING_MMC_DDR52] = state;
+
+       state = sdhci_omap_iodelay_pinctrl_state(omap_host, "hs", caps,
+                                                MMC_CAP_SD_HIGHSPEED);
+       if (!IS_ERR(state))
+               pinctrl_state[MMC_TIMING_SD_HS] = state;
+
+       state = sdhci_omap_iodelay_pinctrl_state(omap_host, "hs", caps,
+                                                MMC_CAP_MMC_HIGHSPEED);
+       if (!IS_ERR(state))
+               pinctrl_state[MMC_TIMING_MMC_HS] = state;
+
+       state = sdhci_omap_iodelay_pinctrl_state(omap_host, "hs200_1_8v", caps2,
+                                                MMC_CAP2_HS200_1_8V_SDR);
+       if (!IS_ERR(state))
+               pinctrl_state[MMC_TIMING_MMC_HS200] = state;
+
+       omap_host->pinctrl_state = pinctrl_state;
+
+       return 0;
+}
+
 static int sdhci_omap_probe(struct platform_device *pdev)
 {
        int ret;
@@ -504,6 +865,9 @@ static int sdhci_omap_probe(struct platform_device *pdev)
        omap_host->host = host;
        omap_host->base = host->ioaddr;
        omap_host->dev = dev;
+       omap_host->power_mode = MMC_POWER_UNDEFINED;
+       omap_host->timing = MMC_TIMING_LEGACY;
+       omap_host->flags = data->flags;
        host->ioaddr += offset;
 
        mmc = host->mmc;
@@ -552,10 +916,16 @@ static int sdhci_omap_probe(struct platform_device *pdev)
                goto err_put_sync;
        }
 
+       ret = sdhci_omap_config_iodelay_pinctrl_state(omap_host);
+       if (ret)
+               goto err_put_sync;
+
        host->mmc_host_ops.get_ro = mmc_gpio_get_ro;
        host->mmc_host_ops.start_signal_voltage_switch =
                                        sdhci_omap_start_signal_voltage_switch;
        host->mmc_host_ops.set_ios = sdhci_omap_set_ios;
+       host->mmc_host_ops.card_busy = sdhci_omap_card_busy;
+       host->mmc_host_ops.execute_tuning = sdhci_omap_execute_tuning;
 
        sdhci_read_caps(host);
        host->caps |= SDHCI_CAN_DO_ADMA2;