]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/mmc/core/host.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / mmc / core / host.c
index 10b8af27e03ad535e8d4bd1a6ce3c25794a58ccd..b3ac6c5bc5c6289b39299e715d0feb01170b22bb 100644 (file)
@@ -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
@@ -20,6 +21,7 @@
 #include <linux/suspend.h>
 
 #include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
 
 #include "core.h"
 #include "host.h"
@@ -50,6 +52,205 @@ 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;
+
+       if (!freq) {
+               pr_debug("%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);
+
+       /*
+        * 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 (!host->clk_requests) {
+               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 */
+               spin_unlock_irqrestore(&host->clk_lock, flags);
+               return;
+       }
+       mutex_lock(&host->clk_gate_mutex);
+       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));
+       }
+       spin_unlock_irqrestore(&host->clk_lock, flags);
+       mutex_unlock(&host->clk_gate_mutex);
+}
+
+/*
+ * 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_gate_work);
+
+       mmc_host_clk_gate_delayed(host);
+}
+
+/**
+ *     mmc_host_clk_ungate - ungate hardware MCI clocks
+ *     @host: host to ungate.
+ *
+ *     Makes sure the host ios.clock is restored to a non-zero value
+ *     past this call. Increase clock reference count and ungate clock
+ *     if we're the first user.
+ */
+void mmc_host_clk_ungate(struct mmc_host *host)
+{
+       unsigned long flags;
+
+       mutex_lock(&host->clk_gate_mutex);
+       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);
+       mutex_unlock(&host->clk_gate_mutex);
+}
+
+/**
+ *     mmc_host_may_gate_card - check if this card may be gated
+ *     @card: card to check.
+ */
+static bool mmc_host_may_gate_card(struct mmc_card *card)
+{
+       /* If there is no card we may gate it */
+       if (!card)
+               return true;
+       /*
+        * Don't gate SDIO cards! These need to be clocked at all times
+        * since they may be independent systems generating interrupts
+        * and other events. The clock requests counter from the core will
+        * go down to zero since the core does not need it, but we will not
+        * gate the clock, because there is somebody out there that may still
+        * be using it.
+        */
+       if (mmc_card_sdio(card))
+               return false;
+
+       return true;
+}
+
+/**
+ *     mmc_host_clk_gate - gate off hardware MCI clocks
+ *     @host: host to gate.
+ *
+ *     Calls the host driver with ios.clock set to zero as often as possible
+ *     in order to gate off hardware MCI clocks. Decrease clock reference
+ *     count and schedule disabling 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 (mmc_host_may_gate_card(host->card) &&
+           !host->clk_requests)
+               schedule_work(&host->clk_gate_work);
+       spin_unlock_irqrestore(&host->clk_lock, flags);
+}
+
+/**
+ *     mmc_host_clk_rate - get current clock frequency setting
+ *     @host: host to get the clock frequency for.
+ *
+ *     Returns current clock frequency regardless of gating.
+ */
+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;
+       /* Hold MCI clock for 8 cycles by default */
+       host->clk_delay = 8;
+       host->clk_gated = false;
+       INIT_WORK(&host->clk_gate_work, mmc_host_clk_gate_work);
+       spin_lock_init(&host->clk_lock);
+       mutex_init(&host->clk_gate_mutex);
+}
+
+/**
+ *     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_gate_work))
+               mmc_host_clk_gate_delayed(host);
+       if (host->clk_gated)
+               mmc_host_clk_ungate(host);
+       /* There should be only one user now */
+       WARN_ON(host->clk_requests > 1);
+}
+
+#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 +283,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 +366,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 +388,3 @@ void mmc_free_host(struct mmc_host *host)
 }
 
 EXPORT_SYMBOL(mmc_free_host);
-