@@ -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
@@ -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 */