Change log: 1. rebased on the latest code of git://kernel.ubuntu.com/jk/dt/linux-2.6.git 2. changes according last round of comments
From: Yong Shen yong.shen@linaro.org
create a tree-like directory structure in debugfs so user space tools like powerdebug can generate readable clock information. more functions tend to be add in, like individual clock enable/disable by writing to this debug interface.
Signed-off-by: Yong Shen yong.shen@linaro.org --- arch/arm/common/Kconfig | 6 ++ arch/arm/common/clkdev.c | 3 + include/linux/clk.h | 18 +++++++ kernel/clk.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 0 deletions(-)
diff --git a/arch/arm/common/Kconfig b/arch/arm/common/Kconfig index 0a34c81..13d7cf9 100644 --- a/arch/arm/common/Kconfig +++ b/arch/arm/common/Kconfig @@ -41,3 +41,9 @@ config SHARP_SCOOP config COMMON_CLKDEV bool select HAVE_CLK + +config CLK_DEBUG + bool "clock debug information export to user space" + depends on USE_COMMON_STRUCT_CLK && PM_DEBUG && DEBUG_FS + help + export clk debug information to user space diff --git a/arch/arm/common/clkdev.c b/arch/arm/common/clkdev.c index 9e4c4d9..1d08fb3 100644 --- a/arch/arm/common/clkdev.c +++ b/arch/arm/common/clkdev.c @@ -19,6 +19,7 @@ #include <linux/mutex.h> #include <linux/clk.h> #include <linux/slab.h> +#include <linux/debugfs.h>
#include <asm/clkdev.h>
@@ -104,6 +105,7 @@ EXPORT_SYMBOL(clk_put);
void clkdev_add(struct clk_lookup *cl) { + clk_debug_register(cl->clk); mutex_lock(&clocks_mutex); list_add_tail(&cl->node, &clocks); mutex_unlock(&clocks_mutex); @@ -114,6 +116,7 @@ void __init clkdev_add_table(struct clk_lookup *cl, size_t num) { mutex_lock(&clocks_mutex); while (num--) { + clk_debug_register(cl->clk); list_add_tail(&cl->node, &clocks); cl++; } diff --git a/include/linux/clk.h b/include/linux/clk.h index ae7e4ed..bf9396a 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -64,14 +64,26 @@ struct clk { struct mutex mutex; spinlock_t spinlock; } lock; +#ifdef CONFIG_CLK_DEBUG +#define CLK_NAME_LEN 32 + char name[CLK_NAME_LEN]; + struct dentry *dentry; +#endif };
+#ifdef CONFIG_CLK_DEBUG +#define __INIT_CLK_DEBUG(n) .name = #n, +#else +#define __INIT_CLK_DEBUG(n) +#endif + /* static initialiser for non-atomic clocks */ #define INIT_CLK(name, o) { \ .ops = &o, \ .enable_count = 0, \ .flags = 0, \ .lock.mutex = __MUTEX_INITIALIZER(name.lock.mutex), \ + __INIT_CLK_DEBUG(name) \ }
/* static initialiser for atomic clocks */ @@ -308,4 +320,10 @@ struct clk *clk_get_sys(const char *dev_id, const char *con_id); int clk_add_alias(const char *alias, const char *alias_dev_name, char *id, struct device *dev);
+#ifdef CONFIG_CLK_DEBUG +void clk_debug_register(struct clk *clk); +#else +static inline void clk_debug_register(struct clk *clk) {} +#endif + #endif diff --git a/kernel/clk.c b/kernel/clk.c index 2779abb..89996f3 100644 --- a/kernel/clk.c +++ b/kernel/clk.c @@ -10,6 +10,8 @@
#include <linux/clk.h> #include <linux/module.h> +#include <linux/slab.h> +#include <linux/debugfs.h>
int clk_enable(struct clk *clk) { @@ -112,3 +114,125 @@ struct clk_ops clk_fixed_ops = { .get_rate = clk_fixed_get_rate, }; EXPORT_SYMBOL_GPL(clk_fixed_ops); + +#ifdef CONFIG_CLK_DEBUG +/* + * debugfs support to trace clock tree hierarchy and attributes + */ +static int clk_debug_rate_get(void *data, u64 *val) +{ + struct clk *clk = data; + + *val = (u64)clk_get_rate(clk); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(clk_debug_rate_fops, clk_debug_rate_get, NULL, + "%llu\n"); + + +static struct dentry *clk_root; +static int clk_debug_register_one(struct clk *clk) +{ + int err; + struct dentry *d, *child, *child_tmp; + struct clk *pa = clk_get_parent(clk); + + if (pa && !IS_ERR(pa)) + d = debugfs_create_dir(clk->name, pa->dentry); + else { + if (!clk_root) + clk_root = debugfs_create_dir("clocks", NULL); + if (!clk_root) + return -ENOMEM; + d = debugfs_create_dir(clk->name, clk_root); + } + + if (!d) + return -ENOMEM; + + clk->dentry = d; + + d = debugfs_create_u32("enable_count", S_IRUGO, clk->dentry, + (u32 *)&clk->enable_count); + if (!d) { + err = -ENOMEM; + goto err_out; + } + + d = debugfs_create_file("rate", S_IRUGO, clk->dentry, (void *)clk, + &clk_debug_rate_fops); + if (!d) { + err = -ENOMEM; + goto err_out; + } + + return 0; + +err_out: + d = clk->dentry; + list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) + debugfs_remove(child); + debugfs_remove(clk->dentry); + return err; +} + +struct preinit_clk { + struct list_head list; + struct clk *clk; +}; +static LIST_HEAD(preinit_clks); +static DEFINE_MUTEX(preinit_lock); +static int init_done; + +void clk_debug_register(struct clk *clk) +{ + int err; + struct clk *pa; + + if (init_done) { + pa = clk_get_parent(clk); + + if (pa && !IS_ERR(pa) && !pa->dentry) + clk_debug_register(pa); + + if (!clk->dentry) { + err = clk_debug_register_one(clk); + if (err) + return; + } + } else { + struct preinit_clk *p; + mutex_lock(&preinit_lock); + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p) { + p->clk = clk; + list_add(&p->list, &preinit_clks); + } + mutex_unlock(&preinit_lock); + } +} +EXPORT_SYMBOL_GPL(clk_debug_register); + +static int __init clk_debugfs_init(void) +{ + struct preinit_clk *pclk, *tmp; + + /* Here, debugfs is supposed to be initialised */ + init_done = 1; + + mutex_lock(&preinit_lock); + list_for_each_entry(pclk, &preinit_clks, list) { + clk_debug_register(pclk->clk); + } + + list_for_each_entry_safe(pclk, tmp, &preinit_clks, list) { + list_del(&pclk->list); + kfree(pclk); + } + + mutex_unlock(&preinit_lock); + + return 0; +} +late_initcall(clk_debugfs_init); +#endif