summaryrefslogtreecommitdiff
path: root/drivers/mmc/host
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/host')
-rw-r--r--drivers/mmc/host/sdhci-tegra.c284
1 files changed, 263 insertions, 21 deletions
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c
index 4cc5b2ce4409..54ddc09897cd 100644
--- a/drivers/mmc/host/sdhci-tegra.c
+++ b/drivers/mmc/host/sdhci-tegra.c
@@ -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>
@@ -37,12 +38,16 @@
#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 |