summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/sdhci-tegra.c
diff options
context:
space:
mode:
authorPavan Kunapuli <pkunapuli@nvidia.com>2011-09-21 21:06:19 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:48:56 -0800
commit72c7f042aa49f5672f7df4066e4400ab44439516 (patch)
tree52bff060ddfa8639355e5c8ad2eaac32c6251ccf /drivers/mmc/host/sdhci-tegra.c
parent9e0e071ed1f511016fae774eb8ca707fc9c42878 (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.c130
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;