Search
SailfishOS Open Build Service
>
Projects
>
home:dcthang:branches:nemo:devel:hw:ti:omap3:n900
>
kernel-adaptation-n900
> linux-2.6-OMAP2-3-clock-implement-clock-rate-parent-change-not.patch
Log In
Username
Password
Cancel
Overview
Repositories
Revisions
Requests
Users
Advanced
Attributes
Meta
File linux-2.6-OMAP2-3-clock-implement-clock-rate-parent-change-not.patch of Package kernel-adaptation-n900
From 1a5d80052fb55efd073b8fa86a7e97517a27b1fa Mon Sep 17 00:00:00 2001 From: Paul Walmsley <paul@pwsan.com> Date: Mon, 19 Oct 2009 19:27:59 +0300 Subject: [PATCH 06/42] OMAP2/3 clock: implement clock rate/parent change notifiers Patch-Mainline: Not sure This patch allows core code and driver code to register for notification when a clock's rate or parent changes. These are useful because drivers don't have exclusive control over a clock's rate: power management code (e.g., CPUFreq) may cause rate changes across large parts of the clock tree. There are three notifier messages: 1. a pre-change notifier, called before the change; 2. a post-change notifier, called after the change; and 3. an abort notifier, called if the change fails for any reason after the pre-change notifier callbacks have run. Since the implementation uses a blocking notifier, notifier code may block waiting for devices to quiesce; but long delays here will reduce the effectiveness of DVFS. Since notifier callbacks are called with clocks_mutex held, callback code must not re-enter the clock framework. Pre-change notifiers are passed the current clock rate and the post-change notifiers the new clock rate. The notifiers are called even if the clock rate is the same before and after the change. This is because reprogramming a clock's parent or rate may briefly disrupt the clock. 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. This patch is a collaboration between Tero Kristo <tero.kristo@nokia.com> and Paul Walmsley <paul@pwsan.com> and several others. Hiroshi Doyu <Hiroshi.DOYU@nokia.com> tracked down and fixed a bug where blocking_notifier_chain_*() were called while interrupts were disabled. Nishanth Menon <nm@ti.com> found and fixed a bug in the clk_notifier_unregister() path, where a list_del() was missing. And thanks to Jouni Hogander <jouni.hogander@nokia.com> for comments and review during the evolution of these patches. Signed-off-by: Paul Walmsley <paul@pwsan.com> Signed-off-by: Tero Kristo <tero.kristo@nokia.com> --- arch/arm/plat-omap/clock.c | 206 +++++++++++++++++++++++++++++++ arch/arm/plat-omap/include/plat/clock.h | 62 +++++++++ 2 files changed, 268 insertions(+), 0 deletions(-) diff --git a/arch/arm/plat-omap/clock.c b/arch/arm/plat-omap/clock.c index fc62fb5..89dd5f9 100644 --- a/arch/arm/plat-omap/clock.c +++ b/arch/arm/plat-omap/clock.c @@ -21,6 +21,7 @@ #include <linux/cpufreq.h> #include <linux/debugfs.h> #include <linux/io.h> +#include <linux/slab.h> #include <plat/clock.h> @@ -29,6 +30,78 @@ static DEFINE_MUTEX(clocks_mutex); static DEFINE_SPINLOCK(clockfw_lock); static struct clk_functions *arch_clock; +static LIST_HEAD(clk_notifier_list); + +/* + * _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_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) +{ + struct clk_notifier *cn; + struct clk_notifier_data cnd; + + cnd.clk = clk; + cnd.rate = clk->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 + * + * 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) +{ + struct clk *child; + int ret; + + if (!clk->notifier_count) + return NOTIFY_DONE; + + omap_clk_notify(clk, msg); + + if (list_empty(&clk->children)) + return NOTIFY_DONE; + + list_for_each_entry(child, &clk->children, sibling) { + ret = omap_clk_notify_downstream(child, msg); + if (ret) + break; + } + return ret; +} /* * Standard clock functions defined in include/linux/clk.h @@ -115,10 +188,16 @@ 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) + omap_clk_notify_downstream(clk, CLK_PRE_RATE_CHANGE); + spin_lock_irqsave(&clockfw_lock, flags); if (arch_clock->clk_set_rate) ret = arch_clock->clk_set_rate(clk, rate); @@ -129,6 +208,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); + + mutex_unlock(&clocks_mutex); + return ret; } EXPORT_SYMBOL(clk_set_rate); @@ -137,10 +222,16 @@ int clk_set_parent(struct clk *clk, struct clk *parent) { unsigned long flags; int ret = -EINVAL; + int msg; if (clk == NULL || IS_ERR(clk) || parent == NULL || IS_ERR(parent)) return ret; + mutex_lock(&clocks_mutex); + + if (clk->notifier_count) + omap_clk_notify_downstream(clk, CLK_PRE_RATE_CHANGE); + spin_lock_irqsave(&clockfw_lock, flags); if (clk->usecount == 0) { if (arch_clock->clk_set_parent) @@ -154,6 +245,12 @@ int clk_set_parent(struct clk *clk, struct clk *parent) ret = -EBUSY; spin_unlock_irqrestore(&clockfw_lock, flags); + msg = (ret) ? CLK_ABORT_RATE_CHANGE : CLK_POST_RATE_CHANGE; + + omap_clk_notify_downstream(clk, msg); + + mutex_unlock(&clocks_mutex); + return ret; } EXPORT_SYMBOL(clk_set_parent); @@ -384,9 +481,118 @@ void clk_exit_cpufreq_table(struct cpufreq_frequency_table **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 /* diff --git a/arch/arm/plat-omap/include/plat/clock.h b/arch/arm/plat-omap/include/plat/clock.h index fef4696..b5409f8 100644 --- a/arch/arm/plat-omap/include/plat/clock.h +++ b/arch/arm/plat-omap/include/plat/clock.h @@ -15,6 +15,8 @@ #include <linux/list.h> +#include <linux/notifier.h> + struct module; struct clk; struct clockdomain; @@ -165,6 +167,37 @@ struct dpll_data { #endif +/* + * 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 + * @rate: current rate of this clock + * + * This struct is passed as parameter to the clock notifier callbacks when + * a clock is changed. Current rate of the clock is passed along with the + * call in pre-notifier, and the new rate in post-notifier. + */ +struct clk_notifier_data { + struct clk *clk; + unsigned long rate; +}; + /* struct clk.flags possibilities */ #define ENABLE_REG_32BIT (1 << 0) /* Use 32-bit access */ #define CLOCK_IDLE_CONTROL (1 << 1) @@ -232,6 +265,7 @@ struct clk { int (*set_rate)(struct clk *, unsigned long); long (*round_rate)(struct clk *, unsigned long); void (*init)(struct clk *); + u16 notifier_count; u8 enable_bit; s8 usecount; u8 fixed_div; @@ -281,6 +315,8 @@ extern void recalculate_root_clocks(void); extern unsigned long followparent_recalc(struct clk *clk); extern void clk_enable_init_clocks(void); unsigned long omap_fixed_divisor_recalc(struct clk *clk); +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); extern void clk_exit_cpufreq_table(struct cpufreq_frequency_table **table); @@ -291,4 +327,30 @@ extern const struct clkops clkops_null; extern struct clk dummy_ck; +/* + * 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 immediately before the clock rate is + * changed. 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 -- 1.7.0.4