diff options
author | Pavan Kunapuli <pkunapuli@nvidia.com> | 2011-02-21 18:39:36 +0530 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-04-26 15:52:00 -0700 |
commit | a891c1fa00368ea53a9dc5949eae1881fd97769a (patch) | |
tree | 30b5cd931e431d9c6e3be6aa1974722763d256ec | |
parent | a5371fd54e8fdc79558d0478c3147a3b514fe20e (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.c | 11 | ||||
-rw-r--r-- | drivers/mmc/core/core.h | 3 | ||||
-rw-r--r-- | drivers/mmc/core/sd.c | 170 | ||||
-rw-r--r-- | drivers/mmc/host/Kconfig | 8 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 29 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 8 | ||||
-rw-r--r-- | include/linux/mmc/host.h | 4 |
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 { |