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