summaryrefslogtreecommitdiff
path: root/drivers/mmc
diff options
context:
space:
mode:
authorTony Lin <tony.lin@freescale.com>2011-08-24 13:00:56 +0800
committerJason Liu <r64343@freescale.com>2012-01-09 20:23:23 +0800
commitaf79633fa7c729c552c77d05e32d0f071ee1dbea (patch)
tree2e4e15c26c79a4407622270ee13346659b9400d4 /drivers/mmc
parent6ee31b615667db63dd0a86945463dc366bba2ac3 (diff)
ENGR00153895 [MX6Q]SD: SD3 clock is not off, when no SD card is in use
the patch brings in clock management, not only card removal will gate off corresponding SD clock, but also a timeout after last request will gate off the SD clock. Signed-off-by: Tony Lin <tony.lin@freescale.com>
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/host/sdhci-esdhc-imx.c26
-rwxr-xr-x[-rw-r--r--]drivers/mmc/host/sdhci.c99
-rw-r--r--drivers/mmc/host/sdhci.h2
3 files changed, 118 insertions, 9 deletions
diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c
index 16c0a3c14164..1ce981cbb578 100644
--- a/drivers/mmc/host/sdhci-esdhc-imx.c
+++ b/drivers/mmc/host/sdhci-esdhc-imx.c
@@ -358,6 +358,20 @@ static int plt_8bit_width(struct sdhci_host *host, int width)
return 0;
}
+
+static void plt_clk_ctrl(struct sdhci_host *host, bool enable)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+ if (enable) {
+ clk_enable(pltfm_host->clk);
+ host->clk_status = true;
+ } else {
+ clk_disable(pltfm_host->clk);
+ host->clk_status = false;
+ }
+}
+
static struct sdhci_ops sdhci_esdhc_ops = {
.read_l = esdhc_readl_le,
.read_w = esdhc_readw_le,
@@ -370,6 +384,7 @@ static struct sdhci_ops sdhci_esdhc_ops = {
.pre_tuning = esdhc_prepare_tuning,
.platform_8bit_width = plt_8bit_width,
.platform_ddr_mode = plt_ddr_mode,
+ .platform_clk_ctrl = plt_clk_ctrl,
};
static irqreturn_t cd_irq(int irq, void *data)
@@ -439,6 +454,7 @@ static int esdhc_pltfm_init(struct sdhci_host *host, struct sdhci_pltfm_data *pd
host->tuning_min = SDHCI_TUNE_CTRL_MIN;
host->tuning_max = SDHCI_TUNE_CTRL_MAX;
host->tuning_step = SDHCI_TUNE_CTRL_STEP;
+ host->clk_mgr_en = true;
}
/* disable card interrupt enable bit, and clear status bit
@@ -460,6 +476,8 @@ static int esdhc_pltfm_init(struct sdhci_host *host, struct sdhci_pltfm_data *pd
if (boarddata->always_present) {
imx_data->flags |= ESDHC_FLAG_GPIO_FOR_CD_WP;
host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+ if (host->clk_mgr_en)
+ clk_disable(pltfm_host->clk);
return 0;
}
@@ -489,7 +507,8 @@ static int esdhc_pltfm_init(struct sdhci_host *host, struct sdhci_pltfm_data *pd
/* Now we have a working card_detect again */
host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
}
-
+ if (host->clk_mgr_en)
+ clk_disable(pltfm_host->clk);
return 0;
no_card_detect_irq:
@@ -497,6 +516,8 @@ static int esdhc_pltfm_init(struct sdhci_host *host, struct sdhci_pltfm_data *pd
no_card_detect_pin:
boarddata->cd_gpio = err;
kfree(imx_data);
+ if (host->clk_mgr_en)
+ clk_disable(pltfm_host->clk);
return 0;
}
@@ -516,7 +537,8 @@ static void esdhc_pltfm_exit(struct sdhci_host *host)
free_irq(gpio_to_irq(boarddata->cd_gpio), host);
}
- clk_disable(pltfm_host->clk);
+ if (!host->clk_mgr_en)
+ clk_disable(pltfm_host->clk);
clk_put(pltfm_host->clk);
kfree(imx_data);
}
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 777bbac27f83..fb3faa9f9d5e 100644..100755
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1,7 +1,7 @@
/*
* linux/drivers/mmc/host/sdhci.c - Secure Digital Host Controller Interface driver
*
- * Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved.
+ * Copyright (C) 2005-2011 Pierre Ossman, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,10 +25,12 @@
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
#include "sdhci.h"
#define DRIVER_NAME "sdhci"
+#define CLK_TIMEOUT (10 * HZ)
#define DBG(f, x...) \
pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x)
@@ -49,6 +51,48 @@ static void sdhci_finish_command(struct sdhci_host *);
static int sdhci_execute_tuning(struct mmc_host *mmc);
static void sdhci_tuning_timer(unsigned long data);
+static void sdhci_clk_worker(struct work_struct *work)
+{
+ unsigned long flags;
+ struct sdhci_host *host =
+ container_of(work, struct sdhci_host, clk_worker.work);
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->ops->platform_clk_ctrl && host->clk_status)
+ host->ops->platform_clk_ctrl(host, false);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static inline bool sdhci_is_sdio_attached(struct sdhci_host *host)
+{
+ struct mmc_card *card = host->mmc->card;
+
+ if (card && card->sdio_func[0])
+ return true;
+ return false;
+}
+
+static void sdhci_enable_clk(struct sdhci_host *host)
+{
+ if (host->clk_mgr_en) {
+ if (!in_interrupt())
+ cancel_delayed_work(&host->clk_worker);
+ if (!host->clk_status && host->ops->platform_clk_ctrl)
+ host->ops->platform_clk_ctrl(host, true);
+ }
+}
+
+static void sdhci_disable_clk(struct sdhci_host *host, int delay)
+{
+ if (host->clk_mgr_en && !sdhci_is_sdio_attached(host)) {
+ if (delay == 0 && !in_interrupt()) {
+ if (host->ops->platform_clk_ctrl && host->clk_status)
+ host->ops->platform_clk_ctrl(host, false);
+ } else
+ schedule_delayed_work(&host->clk_worker, delay);
+ }
+}
+
static void sdhci_dumpregs(struct sdhci_host *host)
{
printk(KERN_DEBUG DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n",
@@ -246,12 +290,14 @@ static void sdhci_led_control(struct led_classdev *led,
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
+ sdhci_enable_clk(host);
if (brightness == LED_OFF)
sdhci_deactivate_led(host);
else
sdhci_activate_led(host);
+ sdhci_disable_clk(host, CLK_TIMEOUT);
spin_unlock_irqrestore(&host->lock, flags);
}
#endif
@@ -1227,6 +1273,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
}
host->mrq = mrq;
+ sdhci_enable_clk(host);
/* If polling, assume that the card is always present. */
if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
@@ -1276,6 +1323,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
host = mmc_priv(mmc);
spin_lock_irqsave(&host->lock, flags);
+ sdhci_enable_clk(host);
if (host->flags & SDHCI_DEVICE_DEAD)
goto out;
@@ -1428,6 +1476,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
out:
mmiowb();
+ if (ios->power_mode == MMC_POWER_OFF)
+ sdhci_disable_clk(host, 0);
+
spin_unlock_irqrestore(&host->lock, flags);
}
@@ -1437,6 +1488,7 @@ static int check_ro(struct sdhci_host *host)
int is_readonly;
spin_lock_irqsave(&host->lock, flags);
+ sdhci_enable_clk(host);
if (host->flags & SDHCI_DEVICE_DEAD)
is_readonly = 0;
@@ -1484,6 +1536,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
host = mmc_priv(mmc);
spin_lock_irqsave(&host->lock, flags);
+ sdhci_enable_clk(host);
if (host->flags & SDHCI_DEVICE_DEAD)
goto out;
@@ -1831,6 +1884,9 @@ static void sdhci_tasklet_card(unsigned long param)
spin_lock_irqsave(&host->lock, flags);
+ if (host->clk_mgr_en)
+ goto out;
+
if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
if (host->mrq) {
printk(KERN_ERR "%s: Card removed during transfer!\n",
@@ -1845,7 +1901,7 @@ static void sdhci_tasklet_card(unsigned long param)
tasklet_schedule(&host->finish_tasklet);
}
}
-
+out:
spin_unlock_irqrestore(&host->lock, flags);
mmc_detect_change(host->mmc, msecs_to_jiffies(200));
@@ -1907,6 +1963,7 @@ static void sdhci_tasklet_finish(unsigned long param)
#endif
mmiowb();
+ sdhci_disable_clk(host, CLK_TIMEOUT);
spin_unlock_irqrestore(&host->lock, flags);
mmc_request_done(host->mmc, mrq);
@@ -2229,6 +2286,7 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state)
{
int ret;
+ sdhci_enable_clk(host);
sdhci_disable_card_detection(host);
/* Disable tuning since we are suspending */
@@ -2241,13 +2299,19 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state)
ret = mmc_suspend_host(host->mmc);
if (ret)
- return ret;
+ goto out;
free_irq(host->irq, host);
if (host->vmmc)
ret = regulator_disable(host->vmmc);
+out:
+ /* sync worker
+ * mmc_suspend_host may disable the clk
+ */
+ sdhci_enable_clk(host);
+ sdhci_disable_clk(host, 0);
return ret;
}
@@ -2263,7 +2327,7 @@ int sdhci_resume_host(struct sdhci_host *host)
return ret;
}
-
+ sdhci_enable_clk(host);
if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
if (host->ops->enable_dma)
host->ops->enable_dma(host);
@@ -2272,14 +2336,21 @@ int sdhci_resume_host(struct sdhci_host *host)
ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
mmc_hostname(host->mmc), host);
if (ret)
- return ret;
+ goto out;
sdhci_init(host, (host->mmc->pm_flags & MMC_PM_KEEP_POWER));
mmiowb();
ret = mmc_resume_host(host->mmc);
+
+ /* mmc_resume_host may disable the clk */
+ sdhci_enable_clk(host);
sdhci_enable_card_detection(host);
+out:
+ /* sync worker */
+ sdhci_disable_clk(host, 0);
+
/* Set the re-tuning expiration flag */
if ((host->version >= SDHCI_SPEC_300) && host->tuning_count &&
(host->tuning_mode == SDHCI_TUNING_MODE_1))
@@ -2293,9 +2364,12 @@ EXPORT_SYMBOL_GPL(sdhci_resume_host);
void sdhci_enable_irq_wakeups(struct sdhci_host *host)
{
u8 val;
+
+ sdhci_enable_clk(host);
val = sdhci_readb(host, SDHCI_WAKE_UP_CONTROL);
val |= SDHCI_WAKE_ON_INT;
sdhci_writeb(host, val, SDHCI_WAKE_UP_CONTROL);
+ sdhci_disable_clk(host, CLK_TIMEOUT);
}
EXPORT_SYMBOL_GPL(sdhci_enable_irq_wakeups);
@@ -2345,6 +2419,10 @@ int sdhci_add_host(struct sdhci_host *host)
if (debug_quirks)
host->quirks = debug_quirks;
+ if (host->clk_mgr_en)
+ INIT_DELAYED_WORK(&host->clk_worker, sdhci_clk_worker);
+
+ sdhci_enable_clk(host);
sdhci_reset(host, SDHCI_RESET_ALL);
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
@@ -2439,6 +2517,7 @@ int sdhci_add_host(struct sdhci_host *host)
printk(KERN_ERR
"%s: Hardware doesn't specify base clock "
"frequency.\n", mmc_hostname(mmc));
+ sdhci_disable_clk(host, 0);
return -ENODEV;
}
host->max_clk = host->ops->get_max_clock(host);
@@ -2454,6 +2533,7 @@ int sdhci_add_host(struct sdhci_host *host)
printk(KERN_ERR
"%s: Hardware doesn't specify timeout clock "
"frequency.\n", mmc_hostname(mmc));
+ sdhci_disable_clk(host, 0);
return -ENODEV;
}
}
@@ -2640,6 +2720,7 @@ int sdhci_add_host(struct sdhci_host *host)
if (mmc->ocr_avail == 0) {
printk(KERN_ERR "%s: Hardware doesn't report any "
"support voltages.\n", mmc_hostname(mmc));
+ sdhci_disable_clk(host, 0);
return -ENODEV;
}
@@ -2763,7 +2844,7 @@ int sdhci_add_host(struct sdhci_host *host)
(host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO");
sdhci_enable_card_detection(host);
-
+ sdhci_disable_clk(host, CLK_TIMEOUT);
return 0;
#ifdef SDHCI_USE_LEDS_CLASS
@@ -2774,7 +2855,7 @@ reset:
untasklet:
tasklet_kill(&host->card_tasklet);
tasklet_kill(&host->finish_tasklet);
-
+ sdhci_disable_clk(host, 0);
return ret;
}
@@ -2794,12 +2875,14 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)
" transfer!\n", mmc_hostname(host->mmc));
host->mrq->cmd->error = -ENOMEDIUM;
+ sdhci_enable_clk(host);
tasklet_schedule(&host->finish_tasklet);
}
spin_unlock_irqrestore(&host->lock, flags);
}
+ sdhci_enable_clk(host);
sdhci_disable_card_detection(host);
mmc_remove_host(host->mmc);
@@ -2808,8 +2891,10 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)
led_classdev_unregister(&host->led);
#endif
+ sdhci_enable_clk(host);
if (!dead)
sdhci_reset(host, SDHCI_RESET_ALL);
+ sdhci_disable_clk(host, 0);
free_irq(host->irq, host);
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 3c8cf951fa88..f565c5cf1ab8 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -277,6 +277,8 @@ struct sdhci_ops {
void (*pre_tuning)(struct sdhci_host *host, u32 val);
int (*platform_ddr_mode)(struct sdhci_host *host,
int mode);
+ void (*platform_clk_ctrl)(struct sdhci_host *host,
+ bool enable);
};
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS