summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavan Kunapuli <pkunapuli@nvidia.com>2013-10-24 19:03:45 +0530
committerHarry Hong <hhong@nvidia.com>2013-12-08 16:37:13 -0800
commit1753207a0133dd0839d73ec96a45d6a7c46a6003 (patch)
tree8b35f8282cbe76b5b62a86b9272236dc7c2ec8ec
parentcb2ae3ffb426802a46ce928024a52ab0ec3a6392 (diff)
mmc: host: sdhci: delayed clock gate support
Aggressive clock gate degrades sdhci performance. Hence, sdhci clock gate is delayed. - sdhci clock gate is done if no further transaction starts within 20msec interval - delayed clock gate only supported for EMMC in this patch - switch is set as delayed clock gate off bug 1372006 Change-Id: I9672cbd643dfb45192062dc827275daacc813cc5 Signed-off-by: Pavan Kunapuli <pkunapuli@nvidia.com> Signed-off-by: Bitan Biswas <bbiswas@nvidia.com> Reviewed-on: http://git-master/r/335081 (cherry picked from commit 378d42be0224ed9f57c77ee57997786c5e8e218f) Reviewed-on: http://git-master/r/339098 Reviewed-by: Automatic_Commit_Validation_User GVS: Gerrit_Virtual_Submit Reviewed-by: Harry Hong <hhong@nvidia.com> Tested-by: Harry Hong <hhong@nvidia.com>
-rw-r--r--drivers/mmc/host/sdhci-tegra.c6
-rw-r--r--drivers/mmc/host/sdhci.c51
-rw-r--r--include/linux/mmc/sdhci.h9
3 files changed, 61 insertions, 5 deletions
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c
index 85eab572adad..8055817bd00b 100644
--- a/drivers/mmc/host/sdhci-tegra.c
+++ b/drivers/mmc/host/sdhci-tegra.c
@@ -1172,6 +1172,7 @@ static void tegra_sdhci_set_clock(struct sdhci_host *sdhci, unsigned int clock)
pm_runtime_put_sync(&pdev->dev);
tegra_host->clk_enabled = false;
}
+ sdhci->is_clk_on = tegra_host->clk_enabled;
}
static void tegra_sdhci_do_calibration(struct sdhci_host *sdhci)
{
@@ -2809,6 +2810,7 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev)
}
host = sdhci_pltfm_init(pdev, soc_data->pdata);
+
if (IS_ERR(host))
return PTR_ERR(host);
@@ -3031,6 +3033,7 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev)
pltfm_host->priv = tegra_host;
tegra_host->clk_enabled = true;
+ host->is_clk_on = tegra_host->clk_enabled;
tegra_host->max_clk_limit = plat->max_clk_limit;
tegra_host->ddr_clk_limit = plat->ddr_clk_limit;
tegra_host->sd_detect_in_suspend = plat->sd_detect_in_suspend;
@@ -3098,6 +3101,9 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev)
rc = sdhci_add_host(host);
device_create_file(&pdev->dev, &dev_attr_cmd_state);
+
+ INIT_DELAYED_WORK(&host->delayed_clk_gate_wrk, delayed_clk_gate_cb);
+
sdhci_tegra_error_stats_debugfs(host);
if (rc)
goto err_add_host;
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index ee0602f8af4c..cafcad2a894c 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -47,6 +47,14 @@
#define MAX_TUNING_LOOP 40
+#define DELAYED_CLK_GATING_TICK_TMOUT (HZ / 50)
+
+#define IS_DELAYED_CLK_GATE(host) \
+ ((host->quirks2 & SDHCI_QUIRK2_DELAYED_CLK_GATE) && \
+ (host->mmc->card && ( \
+ (host->mmc->card->type == MMC_TYPE_MMC) /* EMMC */ \
+ )))
+
static unsigned int debug_quirks = 0;
static unsigned int debug_quirks2;
@@ -2040,6 +2048,11 @@ int sdhci_enable(struct mmc_host *mmc)
if (!mmc->card || mmc->card->type == MMC_TYPE_SDIO)
return 0;
+ if (IS_DELAYED_CLK_GATE(host)) {
+ /* cancel delayed clk gate work */
+ cancel_delayed_work_sync(&host->delayed_clk_gate_wrk);
+ }
+
if (mmc->ios.clock) {
if (host->ops->set_clock)
host->ops->set_clock(host, mmc->ios.clock);
@@ -2056,14 +2069,11 @@ int sdhci_enable(struct mmc_host *mmc)
return 0;
}
-int sdhci_disable(struct mmc_host *mmc)
+static void mmc_host_clk_gate(struct sdhci_host *host)
{
- struct sdhci_host *host = mmc_priv(mmc);
int ret;
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
-
- if (!mmc->card || mmc->card->type == MMC_TYPE_SDIO)
- return 0;
+ struct mmc_host *mmc = host->mmc;
sdhci_set_clock(host, 0);
if (host->ops->set_clock)
@@ -2075,6 +2085,37 @@ int sdhci_disable(struct mmc_host *mmc)
if (ret)
dev_err(&pdev->dev, "Unable to set SD_EDP_LOW state\n");
}
+ return;
+}
+
+void delayed_clk_gate_cb(struct work_struct *work)
+{
+ struct sdhci_host *host = container_of(work, struct sdhci_host,
+ delayed_clk_gate_wrk.work);
+ /* power off check */
+ if (host->mmc->ios.power_mode == MMC_POWER_OFF)
+ goto end;
+
+ mmc_host_clk_gate(host);
+end:
+ return;
+}
+EXPORT_SYMBOL_GPL(delayed_clk_gate_cb);
+
+int sdhci_disable(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ if (!mmc->card || mmc->card->type == MMC_TYPE_SDIO)
+ return 0;
+
+ if (IS_DELAYED_CLK_GATE(host)) {
+ schedule_delayed_work(&host->delayed_clk_gate_wrk,
+ DELAYED_CLK_GATING_TICK_TMOUT);
+ return 0;
+ }
+
+ mmc_host_clk_gate(host);
return 0;
}
diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h
index 148ec670ea3c..689ceb041554 100644
--- a/include/linux/mmc/sdhci.h
+++ b/include/linux/mmc/sdhci.h
@@ -102,6 +102,8 @@ struct sdhci_host {
#define SDHCI_QUIRK2_NO_CALC_MAX_DISCARD_TO (1<<4)
/* Controller needs a dummy write after INT_CLK_EN for clock to be stable */
#define SDHCI_QUIRK2_INT_CLK_STABLE_REQ_DUMMY_REG_WRITE (1<<5)
+/* sdio delayed clock gate */
+#define SDHCI_QUIRK2_DELAYED_CLK_GATE (1<<6)
int irq; /* Device IRQ */
void __iomem *ioaddr; /* Mapped address */
@@ -187,7 +189,14 @@ struct sdhci_host {
unsigned int edp_states[SD_EDP_NUM_STATES];
bool edp_support;
+ struct delayed_work delayed_clk_gate_wrk;
+ bool is_clk_on;
+
unsigned long private[0] ____cacheline_aligned;
};
+
+/* callback is registered during init */
+void delayed_clk_gate_cb(struct work_struct *work);
+
#endif /* LINUX_MMC_SDHCI_H */