diff options
author | Tony Lin <tony.lin@freescale.com> | 2011-08-24 13:00:56 +0800 |
---|---|---|
committer | Jason Liu <r64343@freescale.com> | 2012-01-09 20:23:23 +0800 |
commit | af79633fa7c729c552c77d05e32d0f071ee1dbea (patch) | |
tree | 2e4e15c26c79a4407622270ee13346659b9400d4 /drivers/mmc | |
parent | 6ee31b615667db63dd0a86945463dc366bba2ac3 (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.c | 26 | ||||
-rwxr-xr-x[-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 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 |