@@ -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"
@@ -95,6 +98,19 @@ static int _do_propagate_rate(struct clk *clk, unsigned long parent_rate,
}
/**
+ * _clk_free_notifier_chain - safely remove struct clk_notifier
+ * @cn: struct clk_notifier *
+ *
+ * Removes the struct clk_notifier @cn from the clk_notifier_list and
+ * frees it.
+ */
+static void _clk_free_notifier_chain(struct clk_notifier *cn)
+{
+ list_del(&cn->node);
+ kfree(cn);
+}
+
+/**
* omap_clk_add_child - add a child clock @clk2 to @clk
* @clk: parent struct clk *
* @clk2: new child struct clk *
@@ -170,6 +186,101 @@ void omap_clk_del_child(struct clk *clk, struct clk *clk2)
}
}
+/**
+ * omap_clk_notify - call clk notifier chain
+ * @clk: struct clk * that is changing rate
+ * @msg: clk notifier type (i.e., CLK_POST_RATE_CHANGE; see mach/clock.h)
+ * @old_rate: old rate
+ * @new_rate: new rate
+ *
+ * Triggers a notifier call chain on the post-clk-rate-change notifier
+ * for clock 'clk'. Passes a pointer to the struct clk and the
+ * previous and current rates to the notifier callback. Intended to be
+ * called by internal clock code only. No return value.
+ */
+static void omap_clk_notify(struct clk *clk, unsigned long msg,
+ unsigned long old_rate, unsigned long new_rate)
+{
+ struct clk_notifier *cn;
+ struct clk_notifier_data cnd;
+
+ cnd.clk = clk;
+ cnd.old_rate = old_rate;
+ cnd.new_rate = new_rate;
+
+ list_for_each_entry(cn, &clk_notifier_list, node) {
+ if (cn->clk == clk) {
+ blocking_notifier_call_chain(&cn->notifier_head, msg,
+ &cnd);
+ break;
+ }
+ }
+}
+
+/**
+ * omap_clk_notify_downstream - trigger clock change notifications
+ * @clk: struct clk * to start the notifications with
+ * @msg: notifier msg - see "Clk notifier callback types" in mach/clock.h
+ * @param2: (not used - any u8 will do)
+ *
+ * Call clock change notifiers on clocks starting with @clk and including
+ * all of @clk's downstream children clocks. Returns NOTIFY_DONE.
+ */
+static int omap_clk_notify_downstream(struct clk *clk, unsigned long msg,
+ u8 param2)
+{
+ if (!clk->notifier_count)
+ return NOTIFY_DONE;
+
+ omap_clk_notify(clk, msg, clk->rate, clk->temp_rate);
+
+ if (!omap_clk_has_children(clk))
+ return NOTIFY_DONE;
+
+ return omap_clk_for_each_child(clk, msg, 0, omap_clk_notify_downstream);
+}
+
+
+/**
+ * _clk_pre_notify_set_parent - handle pre-notification for clk_set_parent()
+ * @clk: struct clk * changing parent
+ *
+ * When @clk is ready to change its parent, handle pre-notification.
+ * If the architecture does not have an
+ * arch_clock->clk_round_rate_parent() defined, this code will be unable
+ * to verify that the selected parent is valid, and also unable to pass the
+ * post-parent-change clock rate to the notifier. Returns any error from
+ * clk_round_rate_parent() or 0 upon success.
+ */
+static int _clk_pre_notify_set_parent(struct clk *clk, struct clk *parent)
+{
+ long rate;
+
+ if (!clk->notifier_count)
+ return 0;
+
+ if (!arch_clock->clk_round_rate_parent) {
+ pr_warning("clock: clk_set_parent(): WARNING: "
+ "clk_round_rate_parent() undefined: pre-notifiers "
+ "will get bogus rate\n");
+
+ rate = 0;
+ } else {
+ rate = arch_clock->clk_round_rate_parent(clk, parent);
+ };
+
+ if (IS_ERR_VALUE(rate))
+ return rate;
+
+ clk->temp_rate = rate;
+ propagate_rate(clk, TEMP_RATE);
+
+ omap_clk_notify_downstream(clk, CLK_PRE_RATE_CHANGE, 0);
+
+ return 0;
+}
+
+
/*-------------------------------------------------------------------------
* Standard clock functions defined in include/linux/clk.h
*-------------------------------------------------------------------------*/
@@ -306,10 +417,20 @@ int clk_set_rate(struct clk *clk, unsigned long rate)
{
unsigned long flags;
int ret = -EINVAL;
+ int msg;
if (clk == NULL || IS_ERR(clk))
return ret;
+ mutex_lock(&clocks_mutex);
+
+ if (clk->notifier_count) {
+ clk->temp_rate = rate;
+ propagate_rate(clk, TEMP_RATE);
+
+ omap_clk_notify_downstream(clk, CLK_PRE_RATE_CHANGE, 0);
+ }
+
spin_lock_irqsave(&clockfw_lock, flags);
if (arch_clock->clk_set_rate) {
@@ -321,6 +442,12 @@ int clk_set_rate(struct clk *clk, unsigned long rate)
spin_unlock_irqrestore(&clockfw_lock, flags);
+ msg = (ret) ? CLK_ABORT_RATE_CHANGE : CLK_POST_RATE_CHANGE;
+
+ omap_clk_notify_downstream(clk, msg, 0);
+
+ mutex_unlock(&clocks_mutex);
+
return ret;
}
EXPORT_SYMBOL(clk_set_rate);
@@ -330,10 +457,17 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
unsigned long flags;
struct clk *prev_parent;
int ret = -EINVAL;
+ int msg;
if (clk == NULL || IS_ERR(clk) || parent == NULL || IS_ERR(parent))
return ret;
+ mutex_lock(&clocks_mutex);
+
+ ret = _clk_pre_notify_set_parent(clk, parent);
+ if (IS_ERR_VALUE(ret))
+ goto csp_out;
+
spin_lock_irqsave(&clockfw_lock, flags);
if (arch_clock->clk_set_parent) {
@@ -349,6 +483,13 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
spin_unlock_irqrestore(&clockfw_lock, flags);
+ msg = (ret) ? CLK_ABORT_RATE_CHANGE : CLK_POST_RATE_CHANGE;
+
+ omap_clk_notify_downstream(clk, msg, 0);
+
+csp_out:
+ mutex_unlock(&clocks_mutex);
+
return ret;
}
EXPORT_SYMBOL(clk_set_parent);
@@ -535,6 +676,121 @@ void clk_init_cpufreq_table(struct cpufreq_frequency_table **table)
EXPORT_SYMBOL(clk_init_cpufreq_table);
#endif
+/* Clk notifier implementation */
+
+/**
+ * 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 into the clock
+ * framework, as clocks_mutex is held. 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;
+ struct clk *clkp;
+
+ if (!clk || !nb)
+ return -EINVAL;
+
+ mutex_lock(&clocks_mutex);
+
+ list_for_each_entry(cn, &clk_notifier_list, node)
+ if (cn->clk == clk)
+ break;
+
+ if (cn->clk != clk) {
+ cn_new = kzalloc(sizeof(struct clk_notifier), GFP_KERNEL);
+ if (!cn_new) {
+ r = -ENOMEM;
+ goto cnr_out;
+ };
+
+ cn_new->clk = clk;
+ BLOCKING_INIT_NOTIFIER_HEAD(&cn_new->notifier_head);
+
+ list_add(&cn_new->node, &clk_notifier_list);
+ cn = cn_new;
+ }
+
+ r = blocking_notifier_chain_register(&cn->notifier_head, nb);
+ if (!IS_ERR_VALUE(r)) {
+ clkp = clk;
+ do {
+ clkp->notifier_count++;
+ } while ((clkp = clkp->parent));
+ } else {
+ if (cn_new)
+ _clk_free_notifier_chain(cn);
+ }
+
+cnr_out:
+ mutex_unlock(&clocks_mutex);
+
+ 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'.
+ * 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;
+
+ if (!clk || !nb)
+ return -EINVAL;
+
+ mutex_lock(&clocks_mutex);
+
+ list_for_each_entry(cn, &clk_notifier_list, node)
+ if (cn->clk == clk)
+ break;
+
+ if (cn->clk != clk) {
+ r = -ENOENT;
+ goto cnu_out;
+ };
+
+ r = blocking_notifier_chain_unregister(&cn->notifier_head, nb);
+ if (!IS_ERR_VALUE(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)
+ _clk_free_notifier_chain(cn);
+
+cnu_out:
+ mutex_unlock(&clocks_mutex);
+
+ 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;
@@ -146,6 +183,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
@@ -203,4 +242,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_PRE_RATE_CHANGE 1
+#define CLK_ABORT_RATE_CHANGE 2
+#define CLK_POST_RATE_CHANGE 3
+
#endif