diff options
author | Tony Lin <tony.lin@freescale.com> | 2011-08-24 13:00:56 +0800 |
---|---|---|
committer | Tony Lin <tony.lin@freescale.com> | 2011-08-26 15:49:08 +0800 |
commit | 9904463d0690bab44940c2590d4a161a186f01d1 (patch) | |
tree | 4fd45cb4770c296841abfaf3763b63742113238e /drivers | |
parent | 8981ab5203e20368d8aeffe70e0e866159c35aa1 (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')
-rw-r--r-- | drivers/mmc/host/sdhci-esdhc-imx.c | 26 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 99 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 2 |
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 1ddfa470bdde..63c96aa2a1f9 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 5b29d62e0cbf..72b9476ff012 100644 --- 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) @@ -46,6 +48,48 @@ static void sdhci_finish_data(struct sdhci_host *); static void sdhci_send_command(struct sdhci_host *, struct mmc_command *); static void sdhci_finish_command(struct sdhci_host *); +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", @@ -235,12 +279,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 @@ -1139,6 +1185,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) @@ -1166,6 +1213,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; @@ -1241,6 +1289,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); } @@ -1253,6 +1304,7 @@ static int sdhci_get_ro(struct mmc_host *mmc) host = mmc_priv(mmc); spin_lock_irqsave(&host->lock, flags); + sdhci_enable_clk(host); if (host->flags & SDHCI_DEVICE_DEAD) is_readonly = 0; @@ -1277,6 +1329,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; @@ -1313,6 +1366,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", @@ -1327,7 +1383,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)); @@ -1382,6 +1438,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); @@ -1662,17 +1719,24 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state) { int ret; + sdhci_enable_clk(host); sdhci_disable_card_detection(host); 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; } @@ -1688,7 +1752,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); @@ -1697,14 +1761,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); + return ret; } @@ -1713,9 +1784,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); @@ -1763,6 +1837,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); @@ -1853,6 +1931,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); @@ -1868,6 +1947,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; } } @@ -1929,6 +2009,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; } @@ -2043,7 +2124,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 @@ -2054,7 +2135,7 @@ reset: untasklet: tasklet_kill(&host->card_tasklet); tasklet_kill(&host->finish_tasklet); - + sdhci_disable_clk(host, 0); return ret; } @@ -2074,12 +2155,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); @@ -2088,8 +2171,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 d8cd9b8e94ad..5ac31324dd59 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -226,6 +226,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 |