mmc: tegra: Non std freq tuning support
Shridhar Rasal [Thu, 9 Feb 2012 09:18:47 +0000 (14:18 +0530)]
Tegra sdmmc controllers support fixed sampling
frequency tuning. Also, frequency tuning is used
to find the ideal tap delay value which ensures
reliable high speed data transfers. Added support
for the same. In SDR50 mode, setting controller clk
rate to 208MHz as lower clk rates result in CRC
errors.

Bug 919232

Originally reviewed on: http://git-master/r/72596

Change-Id: I8825b4bdbc8533005bc76c54f5d1660f18304e4d
Signed-off-by: Pavan Kunapuli <pkunapuli@nvidia.com>
Signed-off-by: Shridhar Rasal <srasal@nvidia.com>
Reviewed-on: http://git-master/r/77798
Reviewed-by: Simone Willett <swillett@nvidia.com>
Tested-by: Simone Willett <swillett@nvidia.com>

drivers/mmc/host/sdhci-tegra.c

index 4cc5b2c..54ddc09 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/slab.h>
 #include <linux/mmc/card.h>
 #include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
 #include <linux/regulator/consumer.h>
 #include <linux/delay.h>
 
 #define SDHCI_VENDOR_CLOCK_CNTRL_PADPIPE_CLKEN_OVERRIDE        0x8
 #define SDHCI_VENDOR_CLOCK_CNTRL_BASE_CLK_FREQ_SHIFT   8
 #define SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT       16
+#define SDHCI_VENDOR_CLOCK_CNTRL_SDR50_TUNING          0x20
 
 #define SDHCI_VENDOR_MISC_CNTRL                0x120
 #define SDHCI_VENDOR_MISC_CNTRL_ENABLE_SDR104_SUPPORT  0x8
 #define SDHCI_VENDOR_MISC_CNTRL_ENABLE_SDR50_SUPPORT   0x10
 #define SDHCI_VENDOR_MISC_CNTRL_ENABLE_SD_3_0  0x20
 
+#define SDMMC_SDMEMCOMPPADCTRL 0x1E0
+#define SDMMC_SDMEMCOMPPADCTRL_VREF_SEL_MASK   0xF
+
 #define SDMMC_AUTO_CAL_CONFIG  0x1E4
 #define SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_ENABLE  0x20000000
 #define SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET_SHIFT 0x8
@@ -59,6 +64,9 @@
 #define TEGRA2_SDHOST_STD_FREQ 50000000
 #define TEGRA3_SDHOST_STD_FREQ 104000000
 
+#define SD_SEND_TUNING_PATTERN 19
+#define MAX_TAP_VALUES 256
+
 static unsigned int tegra_sdhost_min_freq;
 static unsigned int tegra_sdhost_std_freq;
 static void tegra_3x_sdhci_set_card_clock(struct sdhci_host *sdhci, unsigned int clock);
@@ -189,6 +197,8 @@ static void tegra3_sdhci_post_reset_init(struct sdhci_host *sdhci)
                vendor_ctrl |= (plat->tap_delay <<
                        SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT);
        }
+       /* Enable frequency tuning for SDR50 mode */
+       vendor_ctrl |= SDHCI_VENDOR_CLOCK_CNTRL_SDR50_TUNING;
        sdhci_writel(sdhci, vendor_ctrl, SDHCI_VENDOR_CLOCK_CNTRL);
 
        /* Enable SDHOST v3.0 support */
@@ -355,6 +365,15 @@ static void tegra_sdhci_set_clk_rate(struct sdhci_host *sdhci,
                        clk_rate = tegra_sdhost_std_freq;
                else
                        clk_rate = clock;
+
+               /*
+                * In SDR50 mode, run the sdmmc controller at 208MHz to ensure
+                * the core voltage is at 1.2V. If the core voltage is below 1.2V, CRC
+                * errors would occur during data transfers.
+                */
+               if ((sdhci->mmc->ios.timing == MMC_TIMING_UHS_SDR50) &&
+                       (clk_rate == tegra_sdhost_std_freq))
+                       clk_rate <<= 1;
        }
 
        if (tegra_host->max_clk_limit &&
@@ -486,7 +505,7 @@ static int tegra_sdhci_signal_voltage_switch(struct sdhci_host *sdhci,
        struct tegra_sdhci_host *tegra_host = pltfm_host->priv;
        unsigned int min_uV = SDHOST_HIGH_VOLT_MIN;
        unsigned int max_uV = SDHOST_HIGH_VOLT_MAX;
-       unsigned int rc;
+       unsigned int rc = 0;
        u16 clk, ctrl;
        unsigned int val;
 
@@ -516,7 +535,7 @@ static int tegra_sdhci_signal_voltage_switch(struct sdhci_host *sdhci,
                        regulator_set_voltage(tegra_host->vdd_io_reg,
                                SDHOST_HIGH_VOLT_MIN,
                                SDHOST_HIGH_VOLT_MAX);
-                       return rc;
+                       goto out;
                }
        }
 
@@ -543,9 +562,247 @@ static int tegra_sdhci_signal_voltage_switch(struct sdhci_host *sdhci,
                val &= ~0x7F;
                val |= SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET;
                sdhci_writel(sdhci, val, SDMMC_AUTO_CAL_CONFIG);
+
+               val = sdhci_readl(sdhci, SDMMC_SDMEMCOMPPADCTRL);
+               val &= ~SDMMC_SDMEMCOMPPADCTRL_VREF_SEL_MASK;
+               val |= 0x7;
+               sdhci_writel(sdhci, val, SDMMC_SDMEMCOMPPADCTRL);
        }
 
-       return 0;
+       return rc;
+out:
+       /* Enable the card clock */
+       clk |= SDHCI_CLOCK_CARD_EN;
+       sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL);
+
+       /* Wait for 1 msec for the clock to stabilize */
+       mdelay(1);
+
+       return rc;
+}
+
+static void tegra_sdhci_reset(struct sdhci_host *sdhci, u8 mask)
+{
+       unsigned long timeout;
+
+       sdhci_writeb(sdhci, mask, SDHCI_SOFTWARE_RESET);
+
+       /* Wait max 100 ms */
+       timeout = 100;
+
+       /* hw clears the bit when it's done */
+       while (sdhci_readb(sdhci, SDHCI_SOFTWARE_RESET) & mask) {
+               if (timeout == 0) {
+                       dev_err(mmc_dev(sdhci->mmc), "Reset 0x%x never"
+                               "completed.\n", (int)mask);
+                       return;
+               }
+               timeout--;
+               mdelay(1);
+       }
+}
+
+static void sdhci_tegra_set_tap_delay(struct sdhci_host *sdhci,
+       unsigned int tap_delay)
+{
+       u32 vendor_ctrl;
+
+       /* Max tap delay value is 255 */
+       BUG_ON(tap_delay > MAX_TAP_VALUES);
+
+       vendor_ctrl = sdhci_readl(sdhci, SDHCI_VENDOR_CLOCK_CNTRL);
+       vendor_ctrl &= ~(0xFF << SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT);
+       vendor_ctrl |= (tap_delay << SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT);
+       sdhci_writel(sdhci, vendor_ctrl, SDHCI_VENDOR_CLOCK_CNTRL);
+}
+
+static void sdhci_tegra_clear_set_irqs(struct sdhci_host *host,
+       u32 clear, u32 set)
+{
+       u32 ier;
+
+       ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+       ier &= ~clear;
+       ier |= set;
+       sdhci_writel(host, ier, SDHCI_INT_ENABLE);
+       sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+}
+
+static int sdhci_tegra_run_frequency_tuning(struct sdhci_host *sdhci)
+{
+       int err = 0;
+       u8 ctrl;
+       u32 ier;
+       u32 mask;
+       unsigned int timeout = 10;
+       int flags;
+       u32 intstatus;
+
+       /*
+        * As per the Host Controller spec v3.00, tuning command
+        * generates Buffer Read Ready interrupt only, so enable that.
+        */
+       ier = sdhci_readl(sdhci, SDHCI_INT_ENABLE);
+       sdhci_tegra_clear_set_irqs(sdhci, ier, SDHCI_INT_DATA_AVAIL |
+               SDHCI_INT_DATA_CRC);
+
+       mask = SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT;
+       while (sdhci_readl(sdhci, SDHCI_PRESENT_STATE) & mask) {
+               if (timeout == 0) {
+                       dev_err(mmc_dev(sdhci->mmc), "Controller never"
+                               "released inhibit bit(s).\n");
+                       err = -ETIMEDOUT;
+                       goto out;
+               }
+               timeout--;
+               mdelay(1);
+       }
+
+       ctrl = sdhci_readb(sdhci, SDHCI_HOST_CONTROL2);
+       ctrl &= ~SDHCI_CTRL_TUNED_CLK;
+       sdhci_writeb(sdhci, ctrl, SDHCI_HOST_CONTROL2);
+
+       ctrl = sdhci_readb(sdhci, SDHCI_HOST_CONTROL2);
+       ctrl |= SDHCI_CTRL_EXEC_TUNING;
+       sdhci_writeb(sdhci, ctrl, SDHCI_HOST_CONTROL2);
+
+       /*
+        * In response to CMD19, the card sends 64 bytes of tuning
+        * block to the Host Controller. So we set the block size
+        * to 64 here.
+        */
+       sdhci_writew(sdhci, SDHCI_MAKE_BLKSZ(7, 64), SDHCI_BLOCK_SIZE);
+
+       sdhci_writeb(sdhci, 0xE, SDHCI_TIMEOUT_CONTROL);
+
+       sdhci_writeb(sdhci, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE);
+
+       sdhci_writel(sdhci, 0x0, SDHCI_ARGUMENT);
+
+       /* Set the cmd flags */
+       flags = SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | SDHCI_CMD_DATA;
+       /* Issue the command */
+       sdhci_writew(sdhci, SDHCI_MAKE_CMD(
+               SD_SEND_TUNING_PATTERN, flags), SDHCI_COMMAND);
+
+       timeout = 5;
+       do {
+               timeout--;
+               mdelay(1);
+               intstatus = sdhci_readl(sdhci, SDHCI_INT_STATUS);
+               if (intstatus) {
+                       sdhci_writel(sdhci, intstatus, SDHCI_INT_STATUS);
+                       break;
+               }
+       } while(timeout);
+
+       if ((intstatus & SDHCI_INT_DATA_AVAIL) &&
+               !(intstatus & SDHCI_INT_DATA_CRC)) {
+               err = 0;
+               sdhci->tuning_done = 1;
+       } else {
+               tegra_sdhci_reset(sdhci, SDHCI_RESET_CMD);
+               tegra_sdhci_reset(sdhci, SDHCI_RESET_DATA);
+               err = -EIO;
+       }
+
+       if (sdhci->tuning_done) {
+               sdhci->tuning_done = 0;
+               ctrl = sdhci_readb(sdhci, SDHCI_HOST_CONTROL2);
+               if (!(ctrl & SDHCI_CTRL_EXEC_TUNING) &&
+                       (ctrl & SDHCI_CTRL_TUNED_CLK))
+                       err = 0;
+               else
+                       err = -EIO;
+       }
+       mdelay(1);
+out:
+       sdhci_tegra_clear_set_irqs(sdhci, SDHCI_INT_DATA_AVAIL, ier);
+       return err;
+}
+
+static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci)
+{
+       int err;
+       u16 ctrl_2;
+       u8 *tap_delay_status;
+       unsigned int i = 0;
+       unsigned int temp_low_pass_tap = 0;
+       unsigned int temp_pass_window = 0;
+       unsigned int best_low_pass_tap = 0;
+       unsigned int best_pass_window = 0;
+
+       /* Tuning is valid only in SDR104 and SDR50 modes */
+       ctrl_2 = sdhci_readw(sdhci, SDHCI_HOST_CONTROL2);
+       if (!(((ctrl_2 & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) ||
+               (((ctrl_2 & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) &&
+               (sdhci->flags & SDHCI_SDR50_NEEDS_TUNING))))
+                       return 0;
+
+       tap_delay_status = kzalloc(MAX_TAP_VALUES, GFP_KERNEL);
+       if (tap_delay_status == NULL) {
+               dev_err(mmc_dev(sdhci->mmc), "failed to allocate memory"
+                       "for storing tap_delay_status\n");
+               err = -ENOMEM;
+               goto out;
+       }
+
+       /*
+        * Set each tap delay value and run frequency tuning. After each
+        * run, update the tap delay status as working or not working.
+        */
+       do {
+               /* Set the tap delay */
+               sdhci_tegra_set_tap_delay(sdhci, i);
+
+               /* Run frequency tuning */
+               err = sdhci_tegra_run_frequency_tuning(sdhci);
+
+               /* Update whether the tap delay worked or not */
+               tap_delay_status[i] = (err) ? 0: 1;
+               i++;
+       } while (i < 0xFF);
+
+       /* Find the best possible tap range */
+       for (i = 0; i < 0xFF; i++) {
+               temp_pass_window = 0;
+
+               /* Find the first passing tap in the current window */
+               if (tap_delay_status[i]) {
+                       temp_low_pass_tap = i;
+
+                       /* Find the pass window */
+                       do {
+                               temp_pass_window++;
+                               i++;
+                               if (i > 0xFF)
+                                       break;
+                       } while (tap_delay_status[i]);
+
+                       if ((temp_pass_window > best_pass_window) && (temp_pass_window > 1)){
+                               best_low_pass_tap = temp_low_pass_tap;
+                               best_pass_window = temp_pass_window;
+                       }
+               }
+       }
+
+
+       pr_debug("%s: best pass tap window: start %d, end %d\n",
+               mmc_hostname(sdhci->mmc), best_low_pass_tap,
+               (best_low_pass_tap + best_pass_window));
+
+       /* Set the best tap */
+       sdhci_tegra_set_tap_delay(sdhci,
+               (best_low_pass_tap + ((best_pass_window * 3) / 4)));
+
+       /* Run frequency tuning */
+       err = sdhci_tegra_run_frequency_tuning(sdhci);
+
+out:
+       if (tap_delay_status)
+               kfree(tap_delay_status);
+
+       return err;
 }
 
 static int tegra_sdhci_suspend(struct sdhci_host *sdhci, pm_message_t state)
@@ -573,7 +830,6 @@ static int tegra_sdhci_resume(struct sdhci_host *sdhci)
 {
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
        struct tegra_sdhci_host *tegra_host = pltfm_host->priv;
-       unsigned long timeout;
 
        /* Enable the power rails if any */
        if (tegra_host->card_present) {
@@ -587,28 +843,12 @@ static int tegra_sdhci_resume(struct sdhci_host *sdhci)
                        tegra_host->is_rail_enabled = 1;
                }
        }
-
        /* Setting the min identification clock of freq 400KHz */
        tegra_sdhci_set_clock(sdhci, 400000);
 
        /* Reset the controller and power on if MMC_KEEP_POWER flag is set*/
        if (sdhci->mmc->pm_flags & MMC_PM_KEEP_POWER) {
-               sdhci_writeb(sdhci, SDHCI_RESET_ALL, SDHCI_SOFTWARE_RESET);
-
-               /* Wait max 100 ms */
-               timeout = 100;
-
-               /* hw clears the bit when it's done */
-               while (sdhci_readb(sdhci, SDHCI_SOFTWARE_RESET) & SDHCI_RESET_ALL) {
-                       if (timeout == 0) {
-                               printk(KERN_ERR "%s: Reset 0x%x never completed.\n",
-                                       mmc_hostname(sdhci->mmc), (int)SDHCI_RESET_ALL);
-                               return -ETIMEDOUT;
-                       }
-                       timeout--;
-                       mdelay(1);
-               }
-
+               tegra_sdhci_reset(sdhci, SDHCI_RESET_ALL);
                sdhci_writeb(sdhci, SDHCI_POWER_ON, SDHCI_POWER_CONTROL);
                sdhci->pwr = 0;
        }
@@ -628,6 +868,7 @@ static struct sdhci_ops tegra_sdhci_ops = {
        .platform_reset_exit = tegra_sdhci_reset_exit,
        .set_uhs_signaling = tegra_sdhci_set_uhs_signaling,
        .switch_signal_voltage = tegra_sdhci_signal_voltage_switch,
+       .execute_freq_tuning = sdhci_tegra_execute_tuning,
 };
 
 static struct sdhci_pltfm_data sdhci_tegra_pdata = {
@@ -638,6 +879,7 @@ static struct sdhci_pltfm_data sdhci_tegra_pdata = {
 #endif
 #ifdef CONFIG_ARCH_TEGRA_3x_SOC
                  SDHCI_QUIRK_NONSTANDARD_CLOCK |
+                 SDHCI_QUIRK_NON_STANDARD_TUNING |
 #endif
                  SDHCI_QUIRK_SINGLE_POWER_WRITE |
                  SDHCI_QUIRK_NO_HISPD_BIT |