All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] MMC Agressive clocking framework v7
@ 2010-10-31 16:06 Linus Walleij
  2010-11-03 10:03 ` Ohad Ben-Cohen
  0 siblings, 1 reply; 6+ messages in thread
From: Linus Walleij @ 2010-10-31 16:06 UTC (permalink / raw)
  To: linux-mmc
  Cc: Ghorai Sukumar, Chris Ball, Nicolas Pitre, Adrian Hunter,
	jh80.chung, Kyungmin Park, arm-kernel, Linus Walleij

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.

Signed-off-by: Linus Walleij <linus.walleij@stericsson.com>
---
Changes v6->v7: found a bug when "gearing up" cards to higher
frequencies while the clock is gated. Regard this set_ios()
event as an "ungate".
---
 drivers/mmc/core/Kconfig   |   11 +++
 drivers/mmc/core/core.c    |   45 +++++++++++-
 drivers/mmc/core/core.h    |    2 +
 drivers/mmc/core/debugfs.c |    5 +
 drivers/mmc/core/host.c    |  182 +++++++++++++++++++++++++++++++++++++++++++-
 drivers/mmc/core/host.h    |   21 +++++
 include/linux/mmc/host.h   |   10 +++
 7 files changed, 274 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..1be5d65 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)
 			/*
@@ -614,6 +617,12 @@ static inline void mmc_set_ios(struct mmc_host *host)
 		 ios->power_mode, ios->chip_select, ios->vdd,
 		 ios->bus_width, ios->timing);
 
+	/*
+	 * We get a new frequency while the clock is gated,
+	 * so make sure we regard this as ungating it.
+	 */
+	if (ios->clock > 0 && host->clk_gated)
+		host->clk_gated = false;
 	host->ops->set_ios(host, ios);
 }
 
@@ -641,6 +650,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..f1834fb 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,182 @@ 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 n bus cycles (at least 8 from MMC spec) before attempting
+	 * to disable the MCI 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: gated 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 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 clock to control
+ */
+static inline void mmc_host_clk_exit(struct mmc_host *host)
+{
+	/*
+	 * Wait for any outstanding gate and then make sure we're
+	 * ungated before exiting.
+	 */
+	if (cancel_work_sync(&host->clk_disable_work))
+		mmc_host_clk_gate_delayed(host);
+	if (host->clk_gated)
+		mmc_host_clk_ungate(host);
+	BUG_ON(host->clk_requests > 0);
+}
+
+#else
+
+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 +259,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 +342,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 +364,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..de199f9 100644
--- a/drivers/mmc/core/host.h
+++ b/drivers/mmc/core/host.h
@@ -10,10 +10,31 @@
  */
 #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);
 
+#ifdef CONFIG_MMC_CLKGATE
+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);
+
+#else
+static inline void mmc_host_clk_ungate(struct mmc_host *host)
+{
+}
+
+static inline void mmc_host_clk_gate(struct mmc_host *host)
+{
+}
+
+static inline unsigned int mmc_host_clk_rate(struct mmc_host *host)
+{
+	return host->ios.clock;
+}
+#endif
+
 void mmc_host_deeper_disable(struct work_struct *work);
 
 #endif
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


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] MMC Agressive clocking framework v7
  2010-10-31 16:06 [PATCH 1/2] MMC Agressive clocking framework v7 Linus Walleij
@ 2010-11-03 10:03 ` Ohad Ben-Cohen
  2010-11-03 12:25   ` Nicolas Pitre
  2010-11-03 13:18   ` Linus Walleij
  0 siblings, 2 replies; 6+ messages in thread
From: Ohad Ben-Cohen @ 2010-11-03 10:03 UTC (permalink / raw)
  To: Linus Walleij
  Cc: linux-mmc, Ghorai Sukumar, Chris Ball, Nicolas Pitre,
	Adrian Hunter, jh80.chung, Kyungmin Park, arm-kernel

On Sun, Oct 31, 2010 at 6:06 PM, Linus Walleij
<linus.walleij@stericsson.com> 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

Have you considered migrating this to runtime PM API ?

Runtime PM core already provides most of the necessary plumbing:
referred work, reference counting, tunable delay, active/suspended
status, etc..

It will also allow you to avoid overloading the set_ios() operation:
once suspended, host controllers will receive an idle notification
from runtime PM core. also CONFIG_MMC_CLKGATE will probably not be
needed anymore (no API changes so everything should just work as
before. only difference is that now hosts can start supporting  these
runtime PM notifications).

An additional benefit of using runtime PM is that we will then support
new debug and statistics features of powertop, that will present
useful runtime pm stats about the various devices in the system
(http://www.mail-archive.com/linaro-dev@lists.linaro.org/msg00506.html).

Thanks,
Ohad.

>
> Signed-off-by: Linus Walleij <linus.walleij@stericsson.com>
> ---
> Changes v6->v7: found a bug when "gearing up" cards to higher
> frequencies while the clock is gated. Regard this set_ios()
> event as an "ungate".
> ---
>  drivers/mmc/core/Kconfig   |   11 +++
>  drivers/mmc/core/core.c    |   45 +++++++++++-
>  drivers/mmc/core/core.h    |    2 +
>  drivers/mmc/core/debugfs.c |    5 +
>  drivers/mmc/core/host.c    |  182 +++++++++++++++++++++++++++++++++++++++++++-
>  drivers/mmc/core/host.h    |   21 +++++
>  include/linux/mmc/host.h   |   10 +++
>  7 files changed, 274 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..1be5d65 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)
>                        /*
> @@ -614,6 +617,12 @@ static inline void mmc_set_ios(struct mmc_host *host)
>                 ios->power_mode, ios->chip_select, ios->vdd,
>                 ios->bus_width, ios->timing);
>
> +       /*
> +        * We get a new frequency while the clock is gated,
> +        * so make sure we regard this as ungating it.
> +        */
> +       if (ios->clock > 0 && host->clk_gated)
> +               host->clk_gated = false;
>        host->ops->set_ios(host, ios);
>  }
>
> @@ -641,6 +650,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..f1834fb 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,182 @@ 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 n bus cycles (at least 8 from MMC spec) before attempting
> +        * to disable the MCI 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: gated 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 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 clock to control
> + */
> +static inline void mmc_host_clk_exit(struct mmc_host *host)
> +{
> +       /*
> +        * Wait for any outstanding gate and then make sure we're
> +        * ungated before exiting.
> +        */
> +       if (cancel_work_sync(&host->clk_disable_work))
> +               mmc_host_clk_gate_delayed(host);
> +       if (host->clk_gated)
> +               mmc_host_clk_ungate(host);
> +       BUG_ON(host->clk_requests > 0);
> +}
> +
> +#else
> +
> +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 +259,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 +342,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 +364,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..de199f9 100644
> --- a/drivers/mmc/core/host.h
> +++ b/drivers/mmc/core/host.h
> @@ -10,10 +10,31 @@
>  */
>  #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);
>
> +#ifdef CONFIG_MMC_CLKGATE
> +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);
> +
> +#else
> +static inline void mmc_host_clk_ungate(struct mmc_host *host)
> +{
> +}
> +
> +static inline void mmc_host_clk_gate(struct mmc_host *host)
> +{
> +}
> +
> +static inline unsigned int mmc_host_clk_rate(struct mmc_host *host)
> +{
> +       return host->ios.clock;
> +}
> +#endif
> +
>  void mmc_host_deeper_disable(struct work_struct *work);
>
>  #endif
> 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@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] MMC Agressive clocking framework v7
  2010-11-03 10:03 ` Ohad Ben-Cohen
@ 2010-11-03 12:25   ` Nicolas Pitre
  2010-11-03 13:18   ` Linus Walleij
  1 sibling, 0 replies; 6+ messages in thread
From: Nicolas Pitre @ 2010-11-03 12:25 UTC (permalink / raw)
  To: Ohad Ben-Cohen
  Cc: Linus Walleij, linux-mmc, Ghorai Sukumar, Chris Ball,
	Adrian Hunter, jh80.chung, Kyungmin Park, arm-kernel

On Wed, 3 Nov 2010, Ohad Ben-Cohen wrote:

> On Sun, Oct 31, 2010 at 6:06 PM, Linus Walleij
> <linus.walleij@stericsson.com> 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
> 
> Have you considered migrating this to runtime PM API ?
> 
> Runtime PM core already provides most of the necessary plumbing:
> referred work, reference counting, tunable delay, active/suspended
> status, etc..
> 
> It will also allow you to avoid overloading the set_ios() operation:
> once suspended, host controllers will receive an idle notification
> from runtime PM core. also CONFIG_MMC_CLKGATE will probably not be
> needed anymore (no API changes so everything should just work as
> before. only difference is that now hosts can start supporting  these
> runtime PM notifications).

While runtime PM might be appropriate, I think that passing a clock rate 
of 0 to set_ios() is a clear and fully defined state worth supporting.  
Nothing prevents the later from being used by the former then.


Nicolas

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] MMC Agressive clocking framework v7
  2010-11-03 10:03 ` Ohad Ben-Cohen
  2010-11-03 12:25   ` Nicolas Pitre
@ 2010-11-03 13:18   ` Linus Walleij
  2010-11-03 13:44     ` Alan Cox
  1 sibling, 1 reply; 6+ messages in thread
From: Linus Walleij @ 2010-11-03 13:18 UTC (permalink / raw)
  To: Ohad Ben-Cohen
  Cc: linux-mmc, Ghorai Sukumar, Chris Ball, Nicolas Pitre,
	Adrian Hunter, jh80.chung, Kyungmin Park, arm-kernel

Ohad Ben-Cohen wrote:

> Have you considered migrating this to runtime PM API ?
>
> Runtime PM core already provides most of the necessary plumbing:
> referred work, reference counting, tunable delay, active/suspended
> status, etc..

Doesn't it require a struct device* to be used?

I cannot use the host controller struct because that may be using
dev_pm_ops for some other scheme.

> It will also allow you to avoid overloading the set_ios() operation:
> once suspended, host controllers will receive an idle notification
> from runtime PM core. also CONFIG_MMC_CLKGATE will probably not be
> needed anymore (no API changes so everything should just work as
> before. only difference is that now hosts can start supporting  these
> runtime PM notifications).

Then you must mean that it should be implemented in each and every
driver instead, like it is today. We had a long discussion about
that already.

This means replicating the knowledge of the last card operation,
the 8 MCI cycles required for the timeout and the check
that the card is not sdio into every single driver instead of
moving it up in the core (as was Pierre Ossmans original
suggestion some two years ago or so).

Right now the way I see it the host driver can support
dev_pm_ops and simply call runtime_pm_put() when it recieves an
ios with clock frequency 0, so it's not like it contradicts
runtime PM.

The plumbing used in runtime PM is however very applicable to the
task, so if it could be made to work in a subsystem without any
struct device* anchor, it would be very reusable. Is this
feasible?

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] MMC Agressive clocking framework v7
  2010-11-03 13:18   ` Linus Walleij
@ 2010-11-03 13:44     ` Alan Cox
  2010-11-03 14:30       ` Linus Walleij
  0 siblings, 1 reply; 6+ messages in thread
From: Alan Cox @ 2010-11-03 13:44 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Ohad Ben-Cohen, linux-mmc, Ghorai Sukumar, Chris Ball,
	Nicolas Pitre, Adrian Hunter, jh80.chung, Kyungmin Park,
	arm-kernel

> Doesn't it require a struct device* to be used?

Yes but the sdhci port can be a device even if its wired via a controller
which is its parent. The device layer is designed that way
> 
> I cannot use the host controller struct because that may be using
> dev_pm_ops for some other scheme.

Such as ?

> Then you must mean that it should be implemented in each and every
> driver instead, like it is today. We had a long discussion about
> that already.

Why - it simply means you need the core to contain a library routine
which is the default behaviour. This is how scsi works, how ATA works,
etc for just about everything it does. Standardising something in the
core code shouldn't mean hardwiring it to work that way or having a
million copies of it.

> Right now the way I see it the host driver can support
> dev_pm_ops and simply call runtime_pm_put() when it recieves an
> ios with clock frequency 0, so it's not like it contradicts
> runtime PM.

This makes sense - for one it means that devices with more complex rules
- eg not being able to do card insertion detects with power off can
  handle it themselves.
> 
> The plumbing used in runtime PM is however very applicable to the
> task, so if it could be made to work in a subsystem without any
> struct device* anchor, it would be very reusable. Is this
> feasible?

I think its the wrong question. The right question is why can't the
device be used, and if not why can't it have subdevices as the device
tree is meant to work (and runtime pm understands)

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] MMC Agressive clocking framework v7
  2010-11-03 13:44     ` Alan Cox
@ 2010-11-03 14:30       ` Linus Walleij
  0 siblings, 0 replies; 6+ messages in thread
From: Linus Walleij @ 2010-11-03 14:30 UTC (permalink / raw)
  To: Alan Cox
  Cc: Ohad Ben-Cohen, linux-mmc, Ghorai Sukumar, Chris Ball,
	Nicolas Pitre, Adrian Hunter, jh80.chung, Kyungmin Park,
	arm-kernel

2010/11/3 Alan Cox <alan@lxorguk.ukuu.org.uk>:

>> Doesn't it require a struct device* to be used?
>
> Yes but the sdhci port can be a device even if its wired via a controller
> which is its parent. The device layer is designed that way

Yeah wait I got it all backwards, of course I should be able to hook
some dev_pm_ops to the struct device * in MMC's host->class_dev
that mmc_add_host() creates, can't be too hard.

I'll give it a spin, this will probably hook nicely into runtime PM.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2010-11-03 14:30 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-10-31 16:06 [PATCH 1/2] MMC Agressive clocking framework v7 Linus Walleij
2010-11-03 10:03 ` Ohad Ben-Cohen
2010-11-03 12:25   ` Nicolas Pitre
2010-11-03 13:18   ` Linus Walleij
2010-11-03 13:44     ` Alan Cox
2010-11-03 14:30       ` Linus Walleij

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.