[19/22] OMAP: hsmmc: implement clock switcher
diff mbox

Message ID 1304596282-4095-20-git-send-email-adrian.hunter@nokia.com
State New, archived
Headers show

Commit Message

Adrian Hunter May 5, 2011, 11:51 a.m. UTC
From: Andy Shevchenko <ext-andriy.shevchenko@nokia.com>

There are 3 new platform data methods which should help us to do a clock
switching when notification is happened or request is started.

The purpose of the patch is to avoid high frequency of MMC controller on low
OPPs due to an HW bug in OMAP 3630.

The algorithm:
 - the PM governor switches the clock of L3 (and therefore L4) bus on demand
 - the MMC controller clock should follow the change

We have considered two OPPs for L3/L4 bus. Thus, we have corresponding flow:
 - OPP1 -> OPP2 (in case of abort - OPP1)
 - OPP2 -> OPP1 (in case of abort - OPP2)

During idle the MMC gates functional clock and we don't care about the
frequency. Most important to get proper solution when notification comes during
request. Then:
 - in case of OPP1 -> OPP2 we update frequency limit and it will be used for
   new coming requests (it assumes the current frequency of the controller is
   lower then new one)
 - otherwise we wait until no device has used higher frequency then upcoming
   one

Known issues and limitations:
 - originally a clock notifier was used for the core L4 iclk but for upstream
   Adrian changed to a cpufreq notifier where we assume OPP1 below 400MHz and
   OPP2 above 400MHz

Signed-off-by: Andy Shevchenko <ext-andriy.shevchenko@nokia.com>
Signed-off-by: Adrian Hunter <adrian.hunter@nokia.com>
---
 arch/arm/mach-omap2/hsmmc.c           |  180 ++++++++++++++++++++++++++++++++-
 arch/arm/plat-omap/include/plat/mmc.h |    8 ++
 2 files changed, 187 insertions(+), 1 deletions(-)

Comments

Grazvydas Ignotas May 5, 2011, 1:34 p.m. UTC | #1
On Thu, May 5, 2011 at 2:51 PM, Adrian Hunter <adrian.hunter@nokia.com> wrote:
> From: Andy Shevchenko <ext-andriy.shevchenko@nokia.com>
>
> There are 3 new platform data methods which should help us to do a clock
> switching when notification is happened or request is started.
>
> The purpose of the patch is to avoid high frequency of MMC controller on low
> OPPs due to an HW bug in OMAP 3630.
>
> The algorithm:
>  - the PM governor switches the clock of L3 (and therefore L4) bus on demand
>  - the MMC controller clock should follow the change
>
> We have considered two OPPs for L3/L4 bus. Thus, we have corresponding flow:
>  - OPP1 -> OPP2 (in case of abort - OPP1)
>  - OPP2 -> OPP1 (in case of abort - OPP2)
>
> During idle the MMC gates functional clock and we don't care about the
> frequency. Most important to get proper solution when notification comes during
> request. Then:
>  - in case of OPP1 -> OPP2 we update frequency limit and it will be used for
>   new coming requests (it assumes the current frequency of the controller is
>   lower then new one)
>  - otherwise we wait until no device has used higher frequency then upcoming
>   one
>
> Known issues and limitations:
>  - originally a clock notifier was used for the core L4 iclk but for upstream
>   Adrian changed to a cpufreq notifier where we assume OPP1 below 400MHz and
>   OPP2 above 400MHz
>
> Signed-off-by: Andy Shevchenko <ext-andriy.shevchenko@nokia.com>
> Signed-off-by: Adrian Hunter <adrian.hunter@nokia.com>
> ---
>  arch/arm/mach-omap2/hsmmc.c           |  180 ++++++++++++++++++++++++++++++++-
>  arch/arm/plat-omap/include/plat/mmc.h |    8 ++
>  2 files changed, 187 insertions(+), 1 deletions(-)
>
> diff --git a/arch/arm/mach-omap2/hsmmc.c b/arch/arm/mach-omap2/hsmmc.c
> index 6b97fae..34cba89 100644
> --- a/arch/arm/mach-omap2/hsmmc.c
> +++ b/arch/arm/mach-omap2/hsmmc.c

<snip>

> +
> +static int hsmmc_clk_notifier(struct notifier_block *nb, unsigned long event,
> +                             void *data)
> +{
> +       struct cpufreq_freqs *freqs = data;
> +       unsigned int threshold = 400000; /* between opp1 and opp2 */
> +
> +       switch (event) {
> +       case CPUFREQ_PRECHANGE:
> +               if (freqs->new < threshold && freqs->old > threshold) {

What if we go from 400000 to, say, 200000?

> +                       /* opp2 -> opp1 */
> +                       hsmmc_max_freq = HSMMC_MAX_FREQ >> 1;
> +
> +                       /* Timeout is 1 sec */
> +                       if (!wait_event_timeout(hsmmc_max_freq_wq,
> +                                               hsmmc_max_freq_ok(),
> +                                               msecs_to_jiffies(1000)))
> +                               pr_err("MMC violates maximum frequency "
> +                                      "constraint\n");
> +               }
> +               break;
> +       case CPUFREQ_POSTCHANGE:
> +               if (freqs->old < threshold && freqs->new > threshold) {

Same here, you could go from 200000 to 400000 and then 600000 and code
would never notice it.

> +                       /* opp1 -> opp2 */
> +                       hsmmc_max_freq = HSMMC_MAX_FREQ;
> +               }
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch
diff mbox

diff --git a/arch/arm/mach-omap2/hsmmc.c b/arch/arm/mach-omap2/hsmmc.c
index 6b97fae..34cba89 100644
--- a/arch/arm/mach-omap2/hsmmc.c
+++ b/arch/arm/mach-omap2/hsmmc.c
@@ -10,10 +10,15 @@ 
  * published by the Free Software Foundation.
  */
 #include <linux/kernel.h>
+#include <linux/err.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/mmc/card.h>
 #include <mach/hardware.h>
+#include <plat/clock.h>
 #include <plat/mmc.h>
 #include <plat/omap-pm.h>
 #include <plat/mux.h>
@@ -23,6 +28,8 @@ 
 #include "hsmmc.h"
 #include "control.h"
 
+#define HSMMC_MAX_FREQ	48000000
+
 #if defined(CONFIG_MMC_OMAP_HS) || defined(CONFIG_MMC_OMAP_HS_MODULE)
 
 static u16 control_pbias_offset;
@@ -203,6 +210,155 @@  static int nop_mmc_set_power(struct device *dev, int slot, int power_on,
 	return 0;
 }
 
+#ifdef CONFIG_ARCH_OMAP3
+static struct hsmmc_max_freq_info {
+	struct device *dev;
+	int freq;
+	int high_speed;
+} hsmmc_max_freq_info[OMAP34XX_NR_MMC];
+
+static unsigned int hsmmc_max_freq = HSMMC_MAX_FREQ;
+static DEFINE_SPINLOCK(hsmmc_max_freq_lock);
+
+static DECLARE_WAIT_QUEUE_HEAD(hsmmc_max_freq_wq);
+
+static int hsmmc_high_speed(struct device *dev)
+{
+	void *drvdata = platform_get_drvdata(to_platform_device(dev));
+	struct mmc_host *mmc = container_of(drvdata, struct mmc_host, private);
+
+	return mmc->card ? mmc_card_highspeed(mmc->card) : 0;
+}
+
+static unsigned int hsmmc_get_max_freq_hs(struct device *dev, int high_speed)
+{
+	return high_speed ? hsmmc_max_freq : hsmmc_max_freq >> 1;
+}
+
+static unsigned int hsmmc_get_max_freq(struct device *dev)
+{
+	return hsmmc_get_max_freq_hs(dev, hsmmc_high_speed(dev));
+}
+
+static unsigned int hsmmc_active(struct device *dev, unsigned int target_freq)
+{
+	int high_speed = hsmmc_high_speed(dev);
+	int i;
+	unsigned int max_freq, freq;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hsmmc_max_freq_lock, flags);
+	max_freq = hsmmc_get_max_freq_hs(dev, high_speed);
+	freq = min(target_freq, max_freq);
+	for (i = 0; i < ARRAY_SIZE(hsmmc_max_freq_info); i++) {
+		if (!hsmmc_max_freq_info[i].dev) {
+			hsmmc_max_freq_info[i].dev = dev;
+			hsmmc_max_freq_info[i].freq = freq;
+			hsmmc_max_freq_info[i].high_speed = high_speed;
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&hsmmc_max_freq_lock, flags);
+	return freq;
+}
+
+static void hsmmc_inactive(struct device *dev)
+{
+	int i;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hsmmc_max_freq_lock, flags);
+	for (i = 0; i < ARRAY_SIZE(hsmmc_max_freq_info); i++) {
+		if (hsmmc_max_freq_info[i].dev == dev) {
+			hsmmc_max_freq_info[i].dev = NULL;
+			spin_unlock_irqrestore(&hsmmc_max_freq_lock, flags);
+			/*
+			 * Wake up the queue only in case we deactivated a
+			 * device.
+			 */
+			wake_up(&hsmmc_max_freq_wq);
+			return;
+		}
+	}
+	spin_unlock_irqrestore(&hsmmc_max_freq_lock, flags);
+}
+
+static bool hsmmc_max_freq_ok(void)
+{
+	int i;
+	bool ret = true;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hsmmc_max_freq_lock, flags);
+	for (i = 0; i < ARRAY_SIZE(hsmmc_max_freq_info); i++) {
+		if (hsmmc_max_freq_info[i].dev) {
+			unsigned int max_freq;
+
+			if (hsmmc_max_freq_info[i].high_speed)
+				max_freq = HSMMC_MAX_FREQ >> 1;
+			else
+				max_freq = HSMMC_MAX_FREQ >> 2;
+
+			if (hsmmc_max_freq_info[i].freq > max_freq) {
+				ret = false;
+				break;
+			}
+		}
+	}
+	spin_unlock_irqrestore(&hsmmc_max_freq_lock, flags);
+	return ret;
+}
+
+static int hsmmc_clk_notifier(struct notifier_block *nb, unsigned long event,
+			      void *data)
+{
+	struct cpufreq_freqs *freqs = data;
+	unsigned int threshold = 400000; /* between opp1 and opp2 */
+
+	switch (event) {
+	case CPUFREQ_PRECHANGE:
+		if (freqs->new < threshold && freqs->old > threshold) {
+			/* opp2 -> opp1 */
+			hsmmc_max_freq = HSMMC_MAX_FREQ >> 1;
+
+			/* Timeout is 1 sec */
+			if (!wait_event_timeout(hsmmc_max_freq_wq,
+						hsmmc_max_freq_ok(),
+						msecs_to_jiffies(1000)))
+				pr_err("MMC violates maximum frequency "
+				       "constraint\n");
+		}
+		break;
+	case CPUFREQ_POSTCHANGE:
+		if (freqs->old < threshold && freqs->new > threshold) {
+			/* opp1 -> opp2 */
+			hsmmc_max_freq = HSMMC_MAX_FREQ;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block hsmmc_notifier_block = {
+	.notifier_call	= hsmmc_clk_notifier,
+	.priority	= INT_MAX,
+};
+
+static int __init hsmmc_init_notifier(void)
+{
+	return cpufreq_register_notifier(&hsmmc_notifier_block,
+					 CPUFREQ_TRANSITION_NOTIFIER);
+}
+#else
+static inline int hsmmc_init_notifier(void)
+{
+	return 0;
+}
+#endif
+
 static inline void omap_hsmmc_mux(struct omap_mmc_platform_data *mmc_controller,
 			int controller_nr)
 {
@@ -401,6 +557,17 @@  static int __init omap_hsmmc_pdata_init(struct omap2_hsmmc_info *c,
 		kfree(hc_name);
 		return -ENODEV;
 	}
+
+	/*
+	 * The 3630's host controller cannot guarantee setup times at all
+	 * frequencies, so notification and control of frequency changes
+	 * is necessary.
+	 */
+	if (cpu_is_omap3630()) {
+		mmc->get_max_freq = hsmmc_get_max_freq;
+		mmc->active = hsmmc_active;
+		mmc->inactive = hsmmc_inactive;
+	}
 	return 0;
 }
 
@@ -507,9 +674,20 @@  void __init omap2_hsmmc_init(struct omap2_hsmmc_info *controllers)
 		omap4_ctrl_pad_writel(reg, control_mmc1);
 	}
 
+	/*
+	 * The 3630's host controller cannot guarantee setup times at all
+	 * frequencies, so notification and control of frequency changes
+	 * is necessary.
+	 */
+	if (cpu_is_omap3630()) {
+		if (hsmmc_init_notifier()) {
+			pr_err("Can't setup clock notifier for mmc driver!\n");
+			return;
+		}
+	}
+
 	for (; controllers->mmc; controllers++)
 		omap_init_hsmmc(controllers, controllers->mmc);
-
 }
 
 #endif
diff --git a/arch/arm/plat-omap/include/plat/mmc.h b/arch/arm/plat-omap/include/plat/mmc.h
index f38fef9..e3c9b20 100644
--- a/arch/arm/plat-omap/include/plat/mmc.h
+++ b/arch/arm/plat-omap/include/plat/mmc.h
@@ -27,6 +27,8 @@ 
 #define OMAP2420_MMC_SIZE	OMAP1_MMC_SIZE
 #define OMAP2_MMC1_BASE		0x4809c000
 
+#define OMAP34XX_NR_MMC		3
+
 #define OMAP4_MMC_REG_OFFSET	0x100
 
 #define OMAP_MMC_MAX_SLOTS	2
@@ -63,6 +65,12 @@  struct omap_mmc_platform_data {
 	/* Return context loss count due to PM states changing */
 	int (*get_context_loss_count)(struct device *dev);
 
+	/* Return max controller frequency for current OPP */
+	unsigned int (*get_max_freq)(struct device *dev);
+
+	unsigned int (*active)(struct device *dev, unsigned int target_freq);
+	void (*inactive)(struct device *dev);
+
 	u64 dma_mask;
 
 	/* Integrating attributes from the omap_hwmod layer */