diff options
author | Pavan Kunapuli <pkunapuli@nvidia.com> | 2011-09-21 21:06:19 +0530 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:48:56 -0800 |
commit | 72c7f042aa49f5672f7df4066e4400ab44439516 (patch) | |
tree | 52bff060ddfa8639355e5c8ad2eaac32c6251ccf /drivers/mmc/host/sdhci-tegra.c | |
parent | 9e0e071ed1f511016fae774eb8ca707fc9c42878 (diff) |
Sdhci: tegra: Tegra3 sdmmc uses non-std clk configuration
Tegra3 sdmmc controllers need to follow a non-standard
clock configuration sequence for the internal clock to
stabilize.
Enable SDHCI_CONFIG_NONSTANDARD_CLOCK.
Implemented chip specific HW ops.
Bug 871369
Change-Id: I954f93ce579c9e8b4889b27f51fa5d54a0a8e434
Reviewed-on: http://git-master/r/53416
Reviewed-by: Varun Colbert <vcolbert@nvidia.com>
Tested-by: Varun Colbert <vcolbert@nvidia.com>
Rebase-Id: R4b68a67d19a299608da6969f4d403c958f55b3b4
Diffstat (limited to 'drivers/mmc/host/sdhci-tegra.c')
-rw-r--r-- | drivers/mmc/host/sdhci-tegra.c | 130 |
1 files changed, 125 insertions, 5 deletions
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index d6fae9fb881f..525e8bf59df1 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -22,6 +22,7 @@ #include <linux/mmc/card.h> #include <linux/mmc/host.h> #include <linux/regulator/consumer.h> +#include <linux/delay.h> #include <mach/gpio.h> #include <mach/sdhci.h> @@ -29,11 +30,28 @@ #include "sdhci-pltfm.h" #define SDHCI_VENDOR_CLOCK_CNTRL 0x100 +#define SDHCI_VENDOR_CLOCK_CNTRL_PADPIPE_CLKEN_OVERRIDE 0x8 + +static void tegra_3x_sdhci_set_card_clock(struct sdhci_host *sdhci, unsigned int clock); + +struct tegra_sdhci_hw_ops{ + /* Set the internal clk and card clk.*/ + void (*set_card_clock)(struct sdhci_host *sdhci, unsigned int clock); +}; + +static struct tegra_sdhci_hw_ops tegra_2x_sdhci_ops = { +}; + +static struct tegra_sdhci_hw_ops tegra_3x_sdhci_ops = { + .set_card_clock = tegra_3x_sdhci_set_card_clock, +}; struct tegra_sdhci_host { bool clk_enabled; struct regulator *vdd_io_reg; struct regulator *vdd_slot_reg; + /* Pointer to the chip specific HW ops */ + struct tegra_sdhci_hw_ops *hw_ops; }; static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) @@ -157,6 +175,92 @@ static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) return 0; } +static void tegra_3x_sdhci_set_card_clock(struct sdhci_host *sdhci, unsigned int clock) +{ + int div; + u16 clk; + unsigned long timeout; + u8 ctrl; + + if (clock && clock == sdhci->clock) + return; + + sdhci_writew(sdhci, 0, SDHCI_CLOCK_CONTROL); + + if (clock == 0) + goto out; + + if (sdhci->version >= SDHCI_SPEC_300) { + /* Version 3.00 divisors must be a multiple of 2. */ + if (sdhci->max_clk <= clock) { + div = 1; + } else { + for (div = 2; div < SDHCI_MAX_DIV_SPEC_300; div += 2) { + if ((sdhci->max_clk / div) <= clock) + break; + } + } + } else { + /* Version 2.00 divisors must be a power of 2. */ + for (div = 1; div < SDHCI_MAX_DIV_SPEC_200; div *= 2) { + if ((sdhci->max_clk / div) <= clock) + break; + } + } + div >>= 1; + + /* + * Tegra3 sdmmc controller internal clock will not be stabilized when + * we use a clock divider value greater than 4. The WAR is as follows. + * - Enable PADPIPE_CLK_OVERRIDE in the vendr clk cntrl register. + * - Enable internal clock. + * - Wait for 5 usec and do a dummy write. + * - Poll for clk stable and disable PADPIPE_CLK_OVERRIDE. + */ + + /* Enable PADPIPE clk override */ + ctrl = sdhci_readb(sdhci, SDHCI_VENDOR_CLOCK_CNTRL); + ctrl |= SDHCI_VENDOR_CLOCK_CNTRL_PADPIPE_CLKEN_OVERRIDE; + sdhci_writeb(sdhci, ctrl, SDHCI_VENDOR_CLOCK_CNTRL); + + clk = (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT; + clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN) + << SDHCI_DIVIDER_HI_SHIFT; + clk |= SDHCI_CLOCK_INT_EN; + sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL); + + /* Wait for 5 usec */ + udelay(5); + + /* Do a dummy write */ + ctrl = sdhci_readb(sdhci, SDHCI_CAPABILITIES); + ctrl |= 1; + sdhci_writeb(sdhci, ctrl, SDHCI_CAPABILITIES); + + /* Wait max 20 ms */ + timeout = 20; + while (!((clk = sdhci_readw(sdhci, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + dev_err(mmc_dev(sdhci->mmc), "Internal clock never stabilised\n"); + return; + } + timeout--; + mdelay(1); + } + + /* Disable PADPIPE clk override */ + ctrl = sdhci_readb(sdhci, SDHCI_VENDOR_CLOCK_CNTRL); + ctrl &= ~SDHCI_VENDOR_CLOCK_CNTRL_PADPIPE_CLKEN_OVERRIDE; + sdhci_writeb(sdhci, ctrl, SDHCI_VENDOR_CLOCK_CNTRL); + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL); + +out: + sdhci->clock = clock; +} + static void tegra_sdhci_set_clock(struct sdhci_host *sdhci, unsigned int clock) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); @@ -165,11 +269,17 @@ static void tegra_sdhci_set_clock(struct sdhci_host *sdhci, unsigned int clock) pr_debug("%s %s %u enabled=%u\n", __func__, mmc_hostname(sdhci->mmc), clock, tegra_host->clk_enabled); - if (clock && !tegra_host->clk_enabled) { - clk_enable(pltfm_host->clk); - sdhci_writeb(sdhci, 1, SDHCI_VENDOR_CLOCK_CNTRL); - tegra_host->clk_enabled = true; + if (clock) { + if (!tegra_host->clk_enabled) { + clk_enable(pltfm_host->clk); + sdhci_writeb(sdhci, 1, SDHCI_VENDOR_CLOCK_CNTRL); + tegra_host->clk_enabled = true; + } + if (tegra_host->hw_ops->set_card_clock) + tegra_host->hw_ops->set_card_clock(sdhci, clock); } else if (!clock && tegra_host->clk_enabled) { + if (tegra_host->hw_ops->set_card_clock) + tegra_host->hw_ops->set_card_clock(sdhci, clock); sdhci_writeb(sdhci, 0, SDHCI_VENDOR_CLOCK_CNTRL); clk_disable(pltfm_host->clk); tegra_host->clk_enabled = false; @@ -185,7 +295,8 @@ static int tegra_sdhci_suspend(struct sdhci_host *sdhci, pm_message_t state) static int tegra_sdhci_resume(struct sdhci_host *sdhci) { - tegra_sdhci_set_clock(sdhci, 1); + /* Setting the min identification clock of freq 400KHz */ + tegra_sdhci_set_clock(sdhci, 400000); return 0; } @@ -206,6 +317,9 @@ static struct sdhci_pltfm_data sdhci_tegra_pdata = { #ifndef CONFIG_ARCH_TEGRA_2x_SOC SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | #endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + SDHCI_QUIRK_NONSTANDARD_CLOCK | +#endif SDHCI_QUIRK_SINGLE_POWER_WRITE | SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, @@ -356,6 +470,12 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev) host->mmc->caps |= MMC_CAP_NONREMOVABLE; } +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + tegra_host->hw_ops = &tegra_2x_sdhci_ops; +#else + tegra_host->hw_ops = &tegra_3x_sdhci_ops; +#endif + rc = sdhci_add_host(host); if (rc) goto err_add_host; |