From: Paul Walmsley <p...@pwsan.com>

This patch implements notifier registration and unregistration functions
for clock rate changes.

The implementation is via a blocking notifier, called with the clockfw
spinlock held.  Callback functions must not re-enter the clock
framework.

There are likely to be few users of these notifiers, compared to the
total number of clocks.  So, rather than storing one notifier per
struct clk, notifiers are stored in a separate, dynamically allocated
list, effectively trading execution speed (in terms of a sequential
scan of the notifier list) for memory savings.  The implementation is
completely hidden from the callbacks and can be easily changed.

Until prototypes for these functions are made available in
include/linux/clk.h, drivers should pass function pointers to
clk_notifier_register() and clk_notifier_unregister() via their
platform_data struct.

Signed-off-by: Paul Walmsley <p...@pwsan.com>
---
 arch/arm/plat-omap/clock.c              |  118 +++++++++++++++++++++++++++++++
 arch/arm/plat-omap/include/mach/clock.h |   66 +++++++++++++++++
 2 files changed, 184 insertions(+), 0 deletions(-)

diff --git a/arch/arm/plat-omap/clock.c b/arch/arm/plat-omap/clock.c
index bdf2cd4..d76964a 100644
--- a/arch/arm/plat-omap/clock.c
+++ b/arch/arm/plat-omap/clock.c
@@ -21,6 +21,7 @@
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
 #include <linux/cpufreq.h>
+#include <linux/notifier.h>
 #include <linux/debugfs.h>
 #include <linux/io.h>
 #include <linux/bootmem.h>
@@ -34,6 +35,8 @@ static DEFINE_SPINLOCK(clockfw_lock);
 
 static struct clk_functions *arch_clock;
 
+static LIST_HEAD(clk_notifier_list);
+
 /**
  * omap_clk_for_each_child - call callback on each child clock of clk
  * @clk: struct clk * to use as the "parent"
@@ -535,6 +538,121 @@ void clk_init_cpufreq_table(struct 
cpufreq_frequency_table **table)
 EXPORT_SYMBOL(clk_init_cpufreq_table);
 #endif
 
+/**
+ * clk_notifier_register - add a clock parameter change notifier
+ * @clk: struct clk * to watch
+ * @nb: struct notifier_block * with callback info
+ *
+ * Request notification for changes to the clock 'clk'.  This uses a
+ * blocking notifier.  Callback code must not call back into the clock
+ * framework.  Pre-notifier callbacks will be passed the previous and
+ * new rate of the clock.
+ *
+ * clk_notifier_register() must be called from process
+ * context.  Returns -EINVAL if called with null arguments, -ENOMEM
+ * upon allocation failure; otherwise, passes along the return value
+ * of blocking_notifier_chain_register().
+ */
+int clk_notifier_register(struct clk *clk, struct notifier_block *nb)
+{
+       struct clk_notifier *cn = NULL, *cn_new = NULL;
+       int r;
+       unsigned long flags;
+       struct clk *clkp;
+
+       if (!clk || !nb)
+               return -EINVAL;
+
+       /* Allocate this here speculatively to avoid GFP_ATOMIC */
+       cn_new = kzalloc(sizeof(struct clk_notifier), GFP_KERNEL);
+       if (!cn_new)
+               return -ENOMEM;
+
+       spin_lock_irqsave(&clockfw_lock, flags);
+
+       list_for_each_entry(cn, &clk_notifier_list, node)
+               if (cn->clk == clk)
+                       break;
+
+       if (cn->clk != clk) {
+               cn_new->clk = clk;
+               BLOCKING_INIT_NOTIFIER_HEAD(&cn_new->notifier_head);
+
+               list_add(&cn_new->node, &clk_notifier_list);
+               cn = cn_new;
+       } else {
+               kfree(cn_new); /* didn't need it after all */
+       }
+
+       r = blocking_notifier_chain_register(&cn->notifier_head, nb);
+       if (!r) {
+               clkp = clk;
+               do {
+                       clkp->notifier_count++;
+               } while ((clkp = clkp->parent));
+       }
+
+       spin_unlock_irqrestore(&clockfw_lock, flags);
+
+       return r;
+}
+
+/**
+ * clk_notifier_unregister - remove a clock change notifier
+ * @clk: struct clk *
+ * @nb: struct notifier_block * with callback info
+ *
+ * Request no further notification for changes to clock 'clk'.  This
+ * function presently does not release memory allocated by its
+ * corresponding _register function; see inline comments for more
+ * information.  Returns -EINVAL if called with null arguments;
+ * otherwise, passes along the return value of
+ * blocking_notifier_chain_unregister().
+ */
+int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb)
+{
+       struct clk_notifier *cn = NULL;
+       struct clk *clkp;
+       int r = -EINVAL;
+       unsigned long flags;
+
+       if (!clk || !nb)
+               return -EINVAL;
+
+       spin_lock_irqsave(&clockfw_lock, flags);
+
+       list_for_each_entry(cn, &clk_notifier_list, node)
+               if (cn->clk == clk)
+                       break;
+
+       if (cn->clk == clk) {
+               r = blocking_notifier_chain_unregister(&cn->notifier_head, nb);
+
+               if (!r) {
+                       clkp = clk;
+                       do {
+                               clkp->notifier_count--;
+                       } while ((clkp = clkp->parent));
+               }
+
+               /*
+                * XXX ugh, layering violation.  there should be some
+                * support in the notifier code for this.
+                */
+               if (!cn->notifier_head.head)
+                       kfree(cn);
+
+       } else {
+               r = -ENOENT;
+       }
+
+       spin_unlock_irqrestore(&clockfw_lock, flags);
+
+       return r;
+}
+
+
+
 /*-------------------------------------------------------------------------*/
 
 #ifdef CONFIG_OMAP_RESET_CLOCKS
diff --git a/arch/arm/plat-omap/include/mach/clock.h 
b/arch/arm/plat-omap/include/mach/clock.h
index db57b71..41faba8 100644
--- a/arch/arm/plat-omap/include/mach/clock.h
+++ b/arch/arm/plat-omap/include/mach/clock.h
@@ -10,6 +10,8 @@
  * published by the Free Software Foundation.
  */
 
+#include <linux/notifier.h>
+
 #ifndef __ARCH_ARM_OMAP_CLOCK_H
 #define __ARCH_ARM_OMAP_CLOCK_H
 
@@ -75,6 +77,40 @@ struct clk_child {
        u8                      flags;
 };
 
+/**
+ * struct clk_notifier - associate a clk with a notifier
+ * @clk: struct clk * to associate the notifier with
+ * @notifier_head: a blocking_notifier_head for this clk
+ * @node: linked list pointers
+ *
+ * A list of struct clk_notifier is maintained by the notifier code.
+ * An entry is created whenever code registers the first notifier on a
+ * particular @clk.  Future notifiers on that @clk are added to the
+ * @notifier_head.
+ */
+struct clk_notifier {
+       struct clk                      *clk;
+       struct blocking_notifier_head   notifier_head;
+       struct list_head                node;
+};
+
+/**
+ * struct clk_notifier_data - rate data to pass to the notifier callback
+ * @clk: struct clk * being changed
+ * @old_rate: previous rate of this clock
+ * @new_rate: new rate of this clock
+ *
+ * For a pre-notifier, old_rate is the clock's rate before this rate
+ * change, and new_rate is what the rate will be in the future.  For a
+ * post-notifier, old_rate and new_rate are both set to the clock's
+ * current rate (this was done to optimize the implementation).
+ */
+struct clk_notifier_data {
+       struct clk              *clk;
+       unsigned long           old_rate;
+       unsigned long           new_rate;
+};
+
 struct clk {
        struct list_head        node;
        const char              *name;
@@ -91,6 +127,7 @@ struct clk {
        void                    (*init)(struct clk *);
        int                     (*enable)(struct clk *);
        void                    (*disable)(struct clk *);
+       u16                     notifier_count;
        __u8                    enable_bit;
        __s8                    usecount;
        u8                      idlest_bit;
@@ -144,6 +181,8 @@ extern void followparent_recalc(struct clk *clk, unsigned 
long parent_rate,
 extern void clk_allow_idle(struct clk *clk);
 extern void clk_deny_idle(struct clk *clk);
 extern void clk_enable_init_clocks(void);
+extern int clk_notifier_register(struct clk *clk, struct notifier_block *nb);
+extern int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);
 #ifdef CONFIG_CPU_FREQ
 extern void clk_init_cpufreq_table(struct cpufreq_frequency_table **table);
 #endif
@@ -201,4 +240,31 @@ void omap_clk_del_child(struct clk *clk, struct clk *clk2);
 #define CLK_REG_IN_PRM         (1 << 0)
 #define CLK_REG_IN_SCM         (1 << 1)
 
+/*
+ * Clk notifier callback types
+ *
+ * Since the notifier is called with interrupts disabled, any actions
+ * taken by callbacks must be extremely fast and lightweight.
+ *
+ * CLK_PRE_RATE_CHANGE - called after all callbacks have approved the
+ *     rate change, immediately before the clock rate is changed, to
+ *     indicate that the rate change will proceed.  Drivers must
+ *     immediately terminate any operations that will be affected by
+ *     the rate change.  Callbacks must always return NOTIFY_DONE.
+ *
+ * CLK_ABORT_RATE_CHANGE: called if the rate change failed for some
+ *     reason after CLK_PRE_RATE_CHANGE.  In this case, all registered
+ *     notifiers on the clock will be called with
+ *     CLK_ABORT_RATE_CHANGE. Callbacks must always return
+ *     NOTIFY_DONE.
+ *
+ * CLK_POST_RATE_CHANGE - called after the clock rate change has
+ *     successfully completed.  Callbacks must always return
+ *     NOTIFY_DONE.
+ *
+ */
+#define CLK_ABORT_RATE_CHANGE          2
+#define CLK_PRE_RATE_CHANGE            3
+#define CLK_POST_RATE_CHANGE           4
+
 #endif
-- 
1.5.4.3
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to