summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavan Kunapuli <pkunapuli@nvidia.com>2011-02-21 18:39:36 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2011-04-26 15:52:00 -0700
commita891c1fa00368ea53a9dc5949eae1881fd97769a (patch)
tree30b5cd931e431d9c6e3be6aa1974722763d256ec
parenta5371fd54e8fdc79558d0478c3147a3b514fe20e (diff)
sd: tegra:Add frequency tuning for SD 3.0 cards
Implemented the frequency tuning by configuring tap delays. This is required for SD 3.0 cards to work at 208 MHz on tegra. Bug 661035 Original-Change-Id: Ie86b084473da090b329a0220d58a6753d7fb335b Reviewed-on: http://git-master/r/20044 Tested-by: Pavan Kunapuli <pkunapuli@nvidia.com> Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com> Change-Id: Ifb812ee2d807f35bff78f440f6da7f7f5673c2ce
-rw-r--r--drivers/mmc/core/core.c11
-rw-r--r--drivers/mmc/core/core.h3
-rw-r--r--drivers/mmc/core/sd.c170
-rw-r--r--drivers/mmc/host/Kconfig8
-rw-r--r--drivers/mmc/host/sdhci.c29
-rw-r--r--drivers/mmc/host/sdhci.h8
-rw-r--r--include/linux/mmc/host.h4
7 files changed, 204 insertions, 29 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 0d0c0e1d4738..9edac828924e 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -715,6 +715,17 @@ void mmc_reset_tuning_circuit(struct mmc_host *host)
host->ios.tuning_arg = 0;
}
+#ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+/*
+ * Set the tap value.
+ */
+void mmc_set_tap_value(struct mmc_host *host, unsigned char tap_value)
+{
+ host->ios.tap_value = tap_value;
+ mmc_set_ios(host);
+}
+#endif
+
/**
* mmc_vdd_to_ocrbitnum - Convert a voltage to the OCR bit number
* @vdd: voltage (mV)
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index d341dc51b196..1451aae20cfe 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -42,6 +42,9 @@ void mmc_switch_signalling_voltage(struct mmc_host *host, unsigned int signallin
void mmc_start_tuning(struct mmc_host *host);
void mmc_get_tuning_status(struct mmc_host *host, int tuning_arg);
void mmc_reset_tuning_circuit(struct mmc_host *host);
+#ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+void mmc_set_tap_value(struct mmc_host *host, unsigned char tap_value);
+#endif
static inline void mmc_delay(unsigned int ms)
{
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
index 53735815e95e..a30e5108d123 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -264,43 +264,157 @@ static int mmc_execute_tuning(struct mmc_card *card)
mmc_get_tuning_status(card->host, MMC_QUERY_TUNING_STATUS);
if (card->host->tuning_status != MMC_SD_TUNING_COMPLETED)
err = -EAGAIN;
+
+ /* If the sampling clock select is set, tuning is successful */
+ mmc_get_tuning_status(card->host, MMC_SAMPLING_CLOCK_SELECT);
+ if (card->host->tuning_status != MMC_SD_SAMPLING_CLOCK_SELECT_SET) {
+ printk(KERN_ERR "%s: frequency tuning failed\n",
+ mmc_hostname(card->host));
+ err = -EAGAIN;
+ }
out:
card->host->tuning_status = 0;
return err;
}
+#ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+#define FIND_PASSING_TAP 0
+#define FIND_MAX_TAP 1
+#define FIND_MIN_TAP 2
+#define SET_MAX_TAP 3
+#define SET_MIN_TAP 4
+#define SET_PASSING_TAP 5
+
+static int set_tap_delay(struct mmc_card *card, bool *is_tap_working, unsigned char* tuning_done)
+{
+ struct mmc_host *host = card->host;
+ unsigned int err = 0;
+ static unsigned int tap_delay = 10;
+ static unsigned int tap_delta = 10;
+ static int min_tap;
+ static int max_tap;
+ static unsigned int tap_config_arg = 0;
+ static bool best_tap_found = false;
+
+ if (tap_delay > 0xFF)
+ return -EINVAL;
+
+ if (!tap_config_arg && *is_tap_working)
+ tap_config_arg = SET_PASSING_TAP;
+
+ switch(tap_config_arg) {
+ case FIND_PASSING_TAP:
+ mmc_set_tap_value(host, tap_delay);
+ tap_delay += tap_delta;
+ break;
+ case SET_PASSING_TAP:
+ tap_delay -= tap_delta;
+ min_tap = tap_delay;
+ tap_delta = 1;
+ tap_delay += tap_delta;
+ mmc_set_tap_value(host, tap_delay);
+ tap_config_arg = FIND_MAX_TAP;
+ break;
+ case FIND_MAX_TAP:
+ if (*is_tap_working) {
+ tap_delay += tap_delta;
+ mmc_set_tap_value(host, tap_delay);
+ } else {
+ max_tap = tap_delay - 1;
+ tap_config_arg = FIND_MIN_TAP;
+ tap_delay = min_tap;
+ mmc_set_tap_value(host, tap_delay);
+ }
+ break;
+ case FIND_MIN_TAP:
+ if (*is_tap_working) {
+ if (tap_delay) {
+ tap_delay -= tap_delta;
+ mmc_set_tap_value(host, tap_delay);
+ } else {
+ min_tap = tap_delay;
+ best_tap_found = true;
+ }
+ } else {
+ min_tap = tap_delay + 1;
+ best_tap_found = true;
+ }
+ break;
+ default:
+ pr_err("Incorrect tap configuration argument\n");
+ break;
+ }
+
+ if (best_tap_found) {
+ /*
+ * Both the min and max tap values should not be less
+ * 10. This tap range is prone to errors and is not
+ * recommended.
+ */
+ if ((min_tap == 0) && (max_tap < 10)) {
+ tap_delta = 10;
+ tap_delay = max_tap + tap_delta;
+ pr_err("Passing tap window is too small. Do frequency "
+ "tuning again from %d\n", tap_delay);
+ tap_config_arg = FIND_PASSING_TAP;
+ best_tap_found = false;
+ *is_tap_working = false;
+ mmc_set_tap_value(host, tap_delay);
+ tap_delay += tap_delta;
+ } else {
+ tap_delay = (min_tap + (((max_tap - min_tap) * 3) >> 2));
+ mmc_set_tap_value(host, tap_delay);
+ err = mmc_execute_tuning(card);
+ *tuning_done = 1;
+ }
+ }
+
+ return err;
+}
+#endif
+
static int mmc_frequency_tuning(struct mmc_card *card)
{
int err;
unsigned int count = 0;
unsigned char tuning_done = 0;
+#ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+ bool is_tap_working = false;
+#endif
- while (!tuning_done) {
+ do {
+ #ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+ err = set_tap_delay(card, &is_tap_working, &tuning_done);
+ if (err)
+ return err;
+ #endif
err = mmc_execute_tuning(card);
if (err) {
+ #ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+ is_tap_working = false;
+ #endif
count++;
continue;
} else {
if (count >= 40) {
- count = 0;
mmc_reset_tuning_circuit(card->host);
+ #ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+ is_tap_working = false;
+ #endif
+ count = 0;
continue;
- } else
- tuning_done = 1;
+ } else {
+ #ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+ is_tap_working = true;
+ count = 0;
+ #else
+ tuning_done = 1;
+ #endif
+ }
}
- }
-
- /* If the sampling clock select is set, tuning is successful */
- mmc_get_tuning_status(card->host, MMC_SAMPLING_CLOCK_SELECT);
- if (card->host->tuning_status != MMC_SD_SAMPLING_CLOCK_SELECT_SET) {
- printk(KERN_ERR "%s: frequency tuning failed.Reset tuning circuit\n",
- mmc_hostname(card->host));
-
- mmc_reset_tuning_circuit(card->host);
- return -EIO;
- }
+ } while (!tuning_done);
- return 0;
+ return err;
}
/*
@@ -717,6 +831,18 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
goto free_card;
/*
+ * Switch to wider bus (if supported).
+ */
+ if ((host->caps & MMC_CAP_4_BIT_DATA) &&
+ (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
+ err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
+ if (err)
+ goto free_card;
+
+ mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
+ }
+
+ /*
* If voltage switching is supported, attempt to switch to a supported
* UHS mode. If voltage switching is not supported, attempt to switch
* to high speed mode.
@@ -757,18 +883,6 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
goto free_card;
}
- /*
- * Switch to wider bus (if supported).
- */
- if ((host->caps & MMC_CAP_4_BIT_DATA) &&
- (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
- err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
- if (err)
- goto free_card;
-
- mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
- }
-
host->card = card;
return 0;
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 44a476d3f24e..5cfb0ef7f89a 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -121,6 +121,14 @@ config MMC_SDHCI_PLTFM
If unsure, say N.
+config MMC_TEGRA_TAP_DELAY
+ bool "TEGRA SDHCI tap delay configuration support"
+ depends on ARCH_TEGRA
+ depends on ARCH_TEGRA_3x_SOC
+ help
+ This selects the tap delay configuration for tegra
+ sdhci driver.
+
config MMC_SDHCI_CNS3XXX
bool "SDHCI support on the Cavium Networks CNS3xxx SoC"
depends on ARCH_CNS3XXX
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index d01ca11291d0..ef2b521a7301 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1264,6 +1264,12 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
if (ios->tuning_arg) {
switch(ios->tuning_arg) {
case MMC_EXECUTE_TUNING:
+ /* Clear the sampling clock select bit */
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL_2);
+ ctrl &= ~SDHCI_CTRL_2_SAMPLING_CLOCK_SELECT;
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL_2);
+
+ /* Set the execute tuning bit */
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL_2);
if (!(ctrl & SDHCI_CTRL_2_EXECUTE_TUNING)) {
ctrl |= SDHCI_CTRL_2_EXECUTE_TUNING;
@@ -1281,6 +1287,12 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
if (ctrl & SDHCI_CTRL_2_SAMPLING_CLOCK_SELECT)
mmc->tuning_status = MMC_SD_SAMPLING_CLOCK_SELECT_SET;
break;
+ case MMC_RESET_TUNING_CIRCUIT:
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL_2);
+ ctrl &= ~(SDHCI_CTRL_2_SAMPLING_CLOCK_SELECT);
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL_2);
+ mmc->tuning_status = MMC_SD_RESET_TUNING_CIRCUIT;
+ break;
default:
printk(KERN_ERR "%s: Undefined tuning operation\n",
mmc_hostname(host->mmc));
@@ -1318,6 +1330,22 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
host->signalling_voltage = ios->signalling_voltage;
}
+
+#ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+ if (ios->tap_value != host->tap_value) {
+ /* Switch OFF SD clock */
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ clk &= (~SDHCI_CLOCK_CARD_EN);
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ host->ops->configure_tap_value(host, ios->tap_value);
+ host->tap_value = ios->tap_value;
+
+ /* Switch ON sd clock */
+ clk |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+ }
+#endif
out:
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
@@ -2008,6 +2036,7 @@ int sdhci_add_host(struct sdhci_host *host)
mmc->f_min = host->ops->get_min_clock(host);
else
mmc->f_min = host->max_clk / 256;
+
mmc->f_max = host->max_clk;
mmc->caps = 0;
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 2626f21a6514..b72fd0b263e7 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -337,7 +337,9 @@ struct sdhci_host {
struct timer_list timer; /* Timer for timeouts */
unsigned int caps; /* Alternative capabilities */
-
+#ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+ unsigned int tap_value; /* Tap delay value */
+#endif
unsigned long private[0] ____cacheline_aligned;
};
@@ -356,6 +358,10 @@ struct sdhci_ops {
void (*configure_capabilities)(struct sdhci_host *host);
void (*set_signalling_voltage)(struct sdhci_host *host,
unsigned int signalling_voltage);
+#ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+ void (*configure_tap_value)(struct sdhci_host *sdhci,
+ unsigned int tap_value);
+#endif
int (*enable_dma)(struct sdhci_host *host);
int (*get_cd)(struct sdhci_host *host);
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 198057bd3789..62ffcb05fb78 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -68,6 +68,10 @@ struct mmc_ios {
#define MMC_QUERY_TUNING_STATUS 2
#define MMC_SAMPLING_CLOCK_SELECT 3
#define MMC_RESET_TUNING_CIRCUIT 4
+
+#ifdef CONFIG_MMC_TEGRA_TAP_DELAY
+ unsigned int tap_value; /* Tap delay value */
+#endif
};
struct mmc_host_ops {