On Sat, Oct 30, 2010 at 5:18 AM, Linus Walleij <linus.walleij@xxxxxxxxxxxxxx> wrote: > This patch modifies the MMC core code to optionally call the > set_ios() operation on the driver with the clock frequency set > to 0 (gate) after a grace period of at least 8 MCLK cycles, then > restore it (ungate) before any new request. This gives > the driver the option to shut down the MCI clock to the MMC/SD > card when the clock frequency is 0, i.e. the core has stated > that the MCI clock does not need to be generated. > > It is inspired by existing clock gating code found in the OMAP > and Atmel drivers and brings this up to the host abstraction. > Gating is performed before and after any MMC request. > > It exemplifies by implementing this for the MMCI/PL180 MMC/SD > host controller, but it should be simple to switch OMAP and > Atmel over to using this instead. Jaehoon will be test it at samsung SoCs. To Jaehoon I wonder, if we apply this patch at MMC, then what's happening with SDHCI clock gating (from jaehoon patch) I mean SDHCI already did the clock gating and mmc framework also did the same things. Please share the results. Thank you, Kyungmin Park > > Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxxxxxx> > --- > It turned out to be easy to port this code forward to the > current kernel. I'll be hacking some MMCI support code on my > flight and test it when I get back to Sweden. > > In the meantime, Sukumar can you test if this can be made > to work for you on the OMAP? > --- > drivers/mmc/core/Kconfig | 11 +++ > drivers/mmc/core/core.c | 39 +++++++++- > drivers/mmc/core/core.h | 2 + > drivers/mmc/core/debugfs.c | 5 + > drivers/mmc/core/host.c | 187 +++++++++++++++++++++++++++++++++++++++++++- > drivers/mmc/core/host.h | 4 + > include/linux/mmc/host.h | 10 +++ > 7 files changed, 256 insertions(+), 2 deletions(-) > > diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig > index bb22ffd..1018ccc1 100644 > --- a/drivers/mmc/core/Kconfig > +++ b/drivers/mmc/core/Kconfig > @@ -16,3 +16,14 @@ config MMC_UNSAFE_RESUME > > This option sets a default which can be overridden by the > module parameter "removable=0" or "removable=1". > + > +config MMC_CLKGATE > + bool "MMC host clock gating (EXPERIMENTAL)" > + depends on EXPERIMENTAL > + help > + This will attempt to agressively gate the clock to the MMC card. > + This is done to save power due to gating off the logic and bus > + noise when the MMC card is not in use. Your host driver has to > + support handling this in order for it to be of any use. > + > + If unsure, say N. > diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c > index 8f86d70..e74e767 100644 > --- a/drivers/mmc/core/core.c > +++ b/drivers/mmc/core/core.c > @@ -130,6 +130,8 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) > > if (mrq->done) > mrq->done(mrq); > + > + mmc_host_clk_gate(host); > } > } > > @@ -190,6 +192,7 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) > mrq->stop->mrq = mrq; > } > } > + mmc_host_clk_ungate(host); > host->ops->request(host, mrq); > } > > @@ -296,7 +299,7 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) > > timeout_us = data->timeout_ns / 1000; > timeout_us += data->timeout_clks * 1000 / > - (card->host->ios.clock / 1000); > + (mmc_host_clk_rate(card->host) / 1000); > > if (data->flags & MMC_DATA_WRITE) > /* > @@ -641,6 +644,40 @@ void mmc_set_clock(struct mmc_host *host, unsigned int hz) > mmc_set_ios(host); > } > > +#ifdef CONFIG_MMC_CLKGATE > +/* > + * This gates the clock by setting it to 0 Hz. > + */ > +void mmc_gate_clock(struct mmc_host *host) > +{ > + host->clk_old = host->ios.clock; > + host->ios.clock = 0; > + host->clk_gated = true; > + mmc_set_ios(host); > +} > + > +/* > + * This restores the clock from gating by using the cached > + * clock value. > + */ > +void mmc_ungate_clock(struct mmc_host *host) > +{ > + /* > + * We should previously have gated the clock, so the clock > + * shall be 0 here! > + * The clock may however be 0 during intialization, > + * when some request operations are performed before setting > + * the frequency. When ungate is requested in that situation > + * we just ignore the call. > + */ > + if (host->clk_old) { > + BUG_ON(host->ios.clock); > + mmc_set_clock(host, host->clk_old); > + } > + host->clk_gated = false; > +} > +#endif > + > /* > * Change the bus mode (open drain/push-pull) of a host. > */ > diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h > index 77240cd..9972808 100644 > --- a/drivers/mmc/core/core.h > +++ b/drivers/mmc/core/core.h > @@ -33,6 +33,8 @@ void mmc_init_erase(struct mmc_card *card); > > void mmc_set_chip_select(struct mmc_host *host, int mode); > void mmc_set_clock(struct mmc_host *host, unsigned int hz); > +void mmc_gate_clock(struct mmc_host *host); > +void mmc_ungate_clock(struct mmc_host *host); > void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode); > void mmc_set_bus_width(struct mmc_host *host, unsigned int width); > void mmc_set_bus_width_ddr(struct mmc_host *host, unsigned int width, > diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c > index eed1405..998797e 100644 > --- a/drivers/mmc/core/debugfs.c > +++ b/drivers/mmc/core/debugfs.c > @@ -183,6 +183,11 @@ void mmc_add_host_debugfs(struct mmc_host *host) > &mmc_clock_fops)) > goto err_node; > > +#ifdef CONFIG_MMC_CLKGATE > + if (!debugfs_create_u32("clk_delay", (S_IRUSR | S_IWUSR), > + root, &host->clk_delay)) > + goto err_node; > +#endif > return; > > err_node: > diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c > index 10b8af2..dc39605 100644 > --- a/drivers/mmc/core/host.c > +++ b/drivers/mmc/core/host.c > @@ -3,6 +3,7 @@ > * > * Copyright (C) 2003 Russell King, All Rights Reserved. > * Copyright (C) 2007-2008 Pierre Ossman > + * Copyright (C) 2010 Linus Walleij > * > * This program is free software; you can redistribute it and/or modify > * it under the terms of the GNU General Public License version 2 as > @@ -50,6 +51,187 @@ void mmc_unregister_host_class(void) > static DEFINE_IDR(mmc_host_idr); > static DEFINE_SPINLOCK(mmc_host_lock); > > +#ifdef CONFIG_MMC_CLKGATE > + > +/* > + * Enabling clock gating will make the core call out to the host > + * once up and once down when it performs a request or card operation > + * intermingled in any fashion. The driver will see this through > + * set_ios() operations with ios.clock field set to 0 to gate > + * (disable) the block clock, and to the old frequency to enable > + * it again. > + */ > +static void mmc_host_clk_gate_delayed(struct mmc_host *host) > +{ > + unsigned long tick_ns; > + unsigned long freq = host->ios.clock; > + unsigned long flags; > + int users; > + > + if (!freq) { > + pr_err("%s: frequency set to 0 in disable function, " > + "this means the clock is already disabled.\n", > + mmc_hostname(host)); > + return; > + } > + /* > + * New requests may have appeared while we were scheduling, > + * then there is no reason to delay the check before > + * clk_disable(). > + */ > + spin_lock_irqsave(&host->clk_lock, flags); > + users = host->clk_requests; > + /* > + * Delay 8 bus cycles (from MMC spec) before attempting > + * to disable the MMCI block clock. The reference count > + * may have gone up again after this delay due to > + * rescheduling! > + */ > + if (!users) { > + spin_unlock_irqrestore(&host->clk_lock, flags); > + tick_ns = DIV_ROUND_UP(1000000000, freq); > + ndelay(host->clk_delay * tick_ns); > + } else { > + /* New users appeared while waiting for this work */ > + host->clk_pending_gate = false; > + spin_unlock_irqrestore(&host->clk_lock, flags); > + return; > + } > + spin_lock_irqsave(&host->clk_lock, flags); > + if (!host->clk_requests) { > + spin_unlock_irqrestore(&host->clk_lock, flags); > + /* this will set host->ios.clock to 0 */ > + mmc_gate_clock(host); > + spin_lock_irqsave(&host->clk_lock, flags); > + pr_debug("%s: disabled MCI clock\n", > + mmc_hostname(host)); > + } > + host->clk_pending_gate = false; > + spin_unlock_irqrestore(&host->clk_lock, flags); > +} > + > +/* > + * Internal work. Work to disable the clock at some later point. > + */ > +static void mmc_host_clk_gate_work(struct work_struct *work) > +{ > + struct mmc_host *host = container_of(work, struct mmc_host, > + clk_disable_work); > + > + mmc_host_clk_gate_delayed(host); > +} > + > +/* > + * mmc_host_clk_ungate - make sure the host ios.clock is > + * restored to some non-zero value past this call. > + * @host: host to ungate. > + * > + * Increase clock reference count and ungate clock if first user. > + */ > +void mmc_host_clk_ungate(struct mmc_host *host) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&host->clk_lock, flags); > + if (host->clk_gated) { > + spin_unlock_irqrestore(&host->clk_lock, flags); > + mmc_ungate_clock(host); > + spin_lock_irqsave(&host->clk_lock, flags); > + pr_debug("%s: ungated MCI clock\n", > + mmc_hostname(host)); > + } > + host->clk_requests++; > + spin_unlock_irqrestore(&host->clk_lock, flags); > +} > + > +/* > + * mmc_host_clk_gate - call the host driver with ios.clock > + * set to zero as often as possible so as to make it > + * possible to gate off hardware MCI clocks. > + * @host: host to gate. > + * > + * Decrease clock reference count and schedule disablement of clock. > + */ > +void mmc_host_clk_gate(struct mmc_host *host) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&host->clk_lock, flags); > + host->clk_requests--; > + if (!host->clk_requests) { > + host->clk_pending_gate = true; > + schedule_work(&host->clk_disable_work); > + } > + spin_unlock_irqrestore(&host->clk_lock, flags); > +} > + > +/* > + * mmc_host_clk_rate - get current clock frequency setting no matter > + * whether it's gated or not. > + * @host: host to get the clock frequency for. > + */ > +unsigned int mmc_host_clk_rate(struct mmc_host *host) > +{ > + unsigned long freq; > + unsigned long flags; > + > + spin_lock_irqsave(&host->clk_lock, flags); > + if (host->clk_gated) > + freq = host->clk_old; > + else > + freq = host->ios.clock; > + spin_unlock_irqrestore(&host->clk_lock, flags); > + return freq; > +} > + > +/* > + * mmc_host_clk_init - set up clock gating code > + * @host: host with potential hardware clock to control > + */ > +static inline void mmc_host_clk_init(struct mmc_host *host) > +{ > + host->clk_requests = 0; > + host->clk_delay = 8; /* hold MCI clock in 8 cycles by default */ > + host->clk_gated = false; > + host->clk_pending_gate = false; > + INIT_WORK(&host->clk_disable_work, mmc_host_clk_gate_work); > + spin_lock_init(&host->clk_lock); > +} > + > +/* > + * mmc_host_clk_exit - shut down clock gating code > + * @host: host with potential hardware clock to control > + */ > +static inline void mmc_host_clk_exit(struct mmc_host *host) > +{ > + if (cancel_work_sync(&host->clk_disable_work)) > + mmc_host_clk_gate_delayed(host); > + BUG_ON(host->clk_requests > 0); > +} > + > +#else > +inline void mmc_host_clk_ungate(struct mmc_host *host) > +{ > +} > + > +inline void mmc_host_clk_gate(struct mmc_host *host) > +{ > +} > + > +inline unsigned int mmc_host_clk_rate(struct mmc_host *host) > +{ > + return host->ios.clock; > +} > + > +static inline void mmc_host_clk_init(struct mmc_host *host) > +{ > +} > + > +static inline void mmc_host_clk_exit(struct mmc_host *host) > +{ > +} > +#endif > + > /** > * mmc_alloc_host - initialise the per-host structure. > * @extra: sizeof private data structure > @@ -82,6 +264,8 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) > host->class_dev.class = &mmc_host_class; > device_initialize(&host->class_dev); > > + mmc_host_clk_init(host); > + > spin_lock_init(&host->lock); > init_waitqueue_head(&host->wq); > INIT_DELAYED_WORK(&host->detect, mmc_rescan); > @@ -163,6 +347,8 @@ void mmc_remove_host(struct mmc_host *host) > device_del(&host->class_dev); > > led_trigger_unregister_simple(host->led); > + > + mmc_host_clk_exit(host); > } > > EXPORT_SYMBOL(mmc_remove_host); > @@ -183,4 +369,3 @@ void mmc_free_host(struct mmc_host *host) > } > > EXPORT_SYMBOL(mmc_free_host); > - > diff --git a/drivers/mmc/core/host.h b/drivers/mmc/core/host.h > index 8c87e11..0529b1f 100644 > --- a/drivers/mmc/core/host.h > +++ b/drivers/mmc/core/host.h > @@ -10,9 +10,13 @@ > */ > #ifndef _MMC_CORE_HOST_H > #define _MMC_CORE_HOST_H > +#include <linux/mmc/host.h> > > int mmc_register_host_class(void); > void mmc_unregister_host_class(void); > +void mmc_host_clk_ungate(struct mmc_host *host); > +void mmc_host_clk_gate(struct mmc_host *host); > +unsigned int mmc_host_clk_rate(struct mmc_host *host); > > void mmc_host_deeper_disable(struct work_struct *work); > > diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h > index 6d87f68..c38c400 100644 > --- a/include/linux/mmc/host.h > +++ b/include/linux/mmc/host.h > @@ -171,6 +171,16 @@ struct mmc_host { > > mmc_pm_flag_t pm_caps; /* supported pm features */ > > +#ifdef CONFIG_MMC_CLKGATE > + int clk_requests; /* internal reference counter */ > + unsigned int clk_delay; /* number of MCI clk hold cycles */ > + bool clk_gated; /* clock gated */ > + bool clk_pending_gate; /* pending clock gating */ > + struct work_struct clk_disable_work; /* delayed clock disablement */ > + unsigned int clk_old; /* old clock value cache */ > + spinlock_t clk_lock; /* lock for clk fields */ > +#endif > + > /* host specific block data */ > unsigned int max_seg_size; /* see blk_queue_max_segment_size */ > unsigned short max_segs; /* see blk_queue_max_segments */ > -- > 1.7.2.3 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-mmc" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html > -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html