From: Andy Shevchenko ext-andriy.shevche...@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.shevche...@nokia.com
Signed-off-by: Adrian Hunter adrian.hun...@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 4800
+
#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;
+
+