linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
From: adrian.hunter@nokia.com (Adrian Hunter)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH V2 13/16] OMAP: hsmmc: implement clock switcher
Date: Fri,  6 May 2011 12:14:12 +0300	[thread overview]
Message-ID: <1304673255-31634-14-git-send-email-adrian.hunter@nokia.com> (raw)
In-Reply-To: <1304673255-31634-1-git-send-email-adrian.hunter@nokia.com>

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..c37ba4f 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 */
-- 
1.7.0.4

  parent reply	other threads:[~2011-05-06  9:14 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-05-06  9:13 [PATCH V2 00/16] omap_hsmmc patches Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 01/16] mmc: omap_hsmmc: fix missing mmc_release_host() in no_off case Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 02/16] mmc: omap_hsmmc: correct debug report error status mnemonics Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 03/16] mmc: omap_hsmmc: move hardcoded frequency constants to definition block Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 04/16] mmc: omap_hsmmc: reduce a bit the error handlers in probe() Adrian Hunter
2011-05-06 11:34   ` Varadarajan, Charulatha
2011-05-06 11:38     ` Andy Shevchenko
2011-05-06  9:14 ` [PATCH V2 05/16] mmc: omap_hsmmc: split duplicate code to calc_divisor() function Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 06/16] mmc: omap_hsmmc: introduce start_clock and re-use stop_clock Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 07/16] mmc: omap_hsmmc: fix few bugs when set the clock divisor Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 08/16] mmc: omap_hsmmc: split same pieces of code to separate functions Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 09/16] OMAP: hsmmc: Do not mux the slot if non default muxing is already done Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 10/16] OMAP: board-rm680: set MMC nomux flag Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 11/16] mmc: omap_hsmmc: ensure pbias configuration is always done Adrian Hunter
2011-09-29 13:40   ` T Krishnamoorthy, Balaji
2011-10-14 14:01     ` Chris Ball
2011-05-06  9:14 ` [PATCH V2 12/16] mmc: omap_hsmmc: fix oops in omap_hsmmc_dma_cb Adrian Hunter
2011-05-06  9:14 ` Adrian Hunter [this message]
2011-05-12 10:37   ` [PATCH V2 13/16] OMAP: hsmmc: implement clock switcher Tony Lindgren
2011-05-06  9:14 ` [PATCH V2 14/16] mmc: omap_hsmmc: adjust host controller clock in regard to current OPP Adrian Hunter
2011-05-06  9:14 ` [PATCH V2 15/16] OMAP: hsmmc: add platform data for eMMC hardware reset gpio Adrian Hunter
2011-05-12 10:38   ` Tony Lindgren
2011-05-06  9:14 ` [PATCH V2 16/16] mmc: omap_hsmmc: add a hardware reset before initialization Adrian Hunter
2011-05-06 13:26   ` Varadarajan, Charulatha
2011-07-13 10:48 ` [PATCH V2 00/16] omap_hsmmc patches Grazvydas Ignotas
2011-07-13 15:36   ` Chris Ball
2011-07-15  9:32     ` Grazvydas Ignotas
2011-09-02 10:53       ` Tony Lindgren

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1304673255-31634-14-git-send-email-adrian.hunter@nokia.com \
    --to=adrian.hunter@nokia.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).