rk29: clock: support notify

This commit is contained in:
黄涛
2011-08-26 20:20:42 +08:00
parent 97475ecdcc
commit da9a553f07
2 changed files with 281 additions and 9 deletions

View File

@@ -33,6 +33,7 @@
#include <mach/pmu.h>
#include <mach/sram.h>
#include <mach/board.h>
#include <mach/clock.h>
/* Clock flags */
@@ -61,7 +62,8 @@ struct clk {
long (*round_rate)(struct clk *, unsigned long);
struct clk* (*get_parent)(struct clk *); /* get clk's parent from the hardware. default is clksel_get_parent if parents present */
int (*set_parent)(struct clk *, struct clk *); /* default is clksel_set_parent if parents present */
s32 usecount;
s16 usecount;
u16 notifier_count;
u8 gate_idx;
u8 pll_idx;
u8 clksel_con;
@@ -73,6 +75,8 @@ struct clk {
struct clk **parents;
};
static void clk_notify(struct clk *clk, unsigned long msg,
unsigned long old_rate, unsigned long new_rate);
static int clk_enable_nolock(struct clk *clk);
static void clk_disable_nolock(struct clk *clk);
static int clk_set_rate_nolock(struct clk *clk, unsigned long rate);
@@ -2220,13 +2224,16 @@ static int clk_enable_nolock(struct clk *clk)
return ret;
}
if (clk->mode) {
if (clk->notifier_count)
clk_notify(clk, CLK_PRE_ENABLE, clk->rate, clk->rate);
if (clk->mode)
ret = clk->mode(clk, 1);
if (ret) {
if (clk->parent)
clk_disable_nolock(clk->parent);
return ret;
}
if (clk->notifier_count)
clk_notify(clk, ret ? CLK_ABORT_ENABLE : CLK_POST_ENABLE, clk->rate, clk->rate);
if (ret) {
if (clk->parent)
clk_disable_nolock(clk->parent);
return ret;
}
pr_debug("%s enabled\n", clk->name);
}
@@ -2259,10 +2266,15 @@ static void clk_disable_nolock(struct clk *clk)
}
if (--clk->usecount == 0) {
int ret = 0;
if (clk->notifier_count)
clk_notify(clk, CLK_PRE_DISABLE, clk->rate, clk->rate);
if (clk->mode)
clk->mode(clk, 0);
ret = clk->mode(clk, 0);
if (clk->notifier_count)
clk_notify(clk, ret ? CLK_ABORT_DISABLE : CLK_POST_DISABLE, clk->rate, clk->rate);
pr_debug("%s disabled\n", clk->name);
if (clk->parent)
if (ret == 0 && clk->parent)
clk_disable_nolock(clk->parent);
}
}
@@ -2332,6 +2344,7 @@ static void __clk_recalc(struct clk *clk)
static int clk_set_rate_nolock(struct clk *clk, unsigned long rate)
{
int ret;
unsigned long old_rate;
if (rate == clk->rate)
return 0;
@@ -2344,6 +2357,10 @@ static int clk_set_rate_nolock(struct clk *clk, unsigned long rate)
if (!clk->set_rate)
return -EINVAL;
old_rate = clk->rate;
if (clk->notifier_count)
clk_notify(clk, CLK_PRE_RATE_CHANGE, old_rate, rate);
ret = clk->set_rate(clk, rate);
if (ret == 0) {
@@ -2351,6 +2368,9 @@ static int clk_set_rate_nolock(struct clk *clk, unsigned long rate)
__propagate_rate(clk);
}
if (clk->notifier_count)
clk_notify(clk, ret ? CLK_ABORT_RATE_CHANGE : CLK_POST_RATE_CHANGE, old_rate, clk->rate);
return ret;
}
@@ -2877,3 +2897,181 @@ static int __init clk_proc_init(void)
late_initcall(clk_proc_init);
#endif /* CONFIG_PROC_FS */
/* Clk notifier implementation */
/**
* struct clk_notifier - associate a clk with a notifier
* @clk: struct clk * to associate the notifier with
* @notifier_head: a atomic_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 atomic_notifier_head notifier_head;
struct list_head node;
};
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);
}
/**
* 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 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) {
pr_debug("%s msg %lu rate %lu -> %lu\n", clk->name, msg, old_rate, new_rate);
atomic_notifier_call_chain(&cn->notifier_head, msg, &cnd);
break;
}
}
}
/**
* 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;
ATOMIC_INIT_NOTIFIER_HEAD(&cn_new->notifier_head);
list_add(&cn_new->node, &clk_notifier_list);
cn = cn_new;
}
r = atomic_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;
}
EXPORT_SYMBOL(clk_notifier_register);
/**
* 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 = atomic_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;
}
EXPORT_SYMBOL(clk_notifier_unregister);

View File

@@ -0,0 +1,74 @@
/* arch/arm/mach-rk29/include/mach/clock.h
*
* Copyright (C) 2011 ROCKCHIP, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __ASM_ARCH_RK29_CLOCK_H
#define __ASM_ARCH_RK29_CLOCK_H
/**
* 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;
};
/*
* 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_POST_RATE_CHANGE 2
#define CLK_ABORT_RATE_CHANGE 3
#define CLK_PRE_ENABLE 4
#define CLK_POST_ENABLE 5
#define CLK_ABORT_ENABLE 6
#define CLK_PRE_DISABLE 7
#define CLK_POST_DISABLE 8
#define CLK_ABORT_DISABLE 9
extern int clk_notifier_register(struct clk *clk, struct notifier_block *nb);
extern int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);
#endif