Add a debugfs file to expose system clocks.
Signed-off-by: Jeremy Kerr jeremy.kerr@canonical.com
--- Yong: as promised, here's the sample debug code for the common struck clk
--- arch/Kconfig | 4 + arch/arm/common/clkdev.c | 2 include/linux/clk.h | 20 ++++++++ kernel/clk.c | 92 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+)
diff --git a/arch/Kconfig b/arch/Kconfig index 212bd3c..8c2e329 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -168,6 +168,10 @@ config HAVE_USER_RETURN_NOTIFIER config USE_COMMON_STRUCT_CLK bool
+config CLK_DEBUG + bool "Expose clock status information in debugfs" + depends on USE_COMMON_STRUCT_CLK && DEBUG_FS + config HAVE_PERF_EVENTS_NMI bool help diff --git a/arch/arm/common/clkdev.c b/arch/arm/common/clkdev.c index e2b2bb6..7524445 100644 --- a/arch/arm/common/clkdev.c +++ b/arch/arm/common/clkdev.c @@ -97,6 +97,7 @@ void clkdev_add(struct clk_lookup *cl) { mutex_lock(&clocks_mutex); list_add_tail(&cl->node, &clocks); + clk_register(cl->clk); mutex_unlock(&clocks_mutex); } EXPORT_SYMBOL(clkdev_add); @@ -106,6 +107,7 @@ void __init clkdev_add_table(struct clk_lookup *cl, size_t num) mutex_lock(&clocks_mutex); while (num--) { list_add_tail(&cl->node, &clocks); + clk_register(cl->clk); cl++; } mutex_unlock(&clocks_mutex); diff --git a/include/linux/clk.h b/include/linux/clk.h index ae7e4ed..d6e64bf 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -20,6 +20,8 @@ struct device;
#ifdef CONFIG_USE_COMMON_STRUCT_CLK
+#define CLK_NAME_LEN 32 + #define CLK_ATOMIC 0x1
/* If we're using the common struct clk, we define the base clk object here */ @@ -64,14 +66,25 @@ struct clk { struct mutex mutex; spinlock_t spinlock; } lock; +#ifdef CONFIG_CLK_DEBUG + const char name[CLK_NAME_LEN]; + struct list_head list; +#endif /* CONFIG_CLK_DEBUG */ };
+#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 */ @@ -80,6 +93,7 @@ struct clk { .enable_count = 0, \ .flags = CLK_ATOMIC, \ .lock.spinlock = __SPIN_LOCK_UNLOCKED(name.lock.spinlock), \ + __INIT_CLK_DEBUG(name) \ }
/** @@ -308,4 +322,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 +extern void clk_register(struct clk *clk); +#else +static inline void clk_register(struct clk *clk) { } +#endif + #endif diff --git a/kernel/clk.c b/kernel/clk.c index 2779abb..1969b0c 100644 --- a/kernel/clk.c +++ b/kernel/clk.c @@ -9,7 +9,11 @@ */
#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/fs.h> #include <linux/module.h> +#include <linux/mutex.h> +#include <linux/seq_file.h>
int clk_enable(struct clk *clk) { @@ -112,3 +116,91 @@ struct clk_ops clk_fixed_ops = { .get_rate = clk_fixed_get_rate, }; EXPORT_SYMBOL_GPL(clk_fixed_ops); + +#ifdef CONFIG_CLK_DEBUG +static LIST_HEAD(clocks); +static DEFINE_MUTEX(clocks_lock); + +void clk_register(struct clk *clk) +{ + struct clk *c; + + mutex_lock(&clocks_lock); + + /* Look for duplicates. Since we're being called from clkdev_add, + * we may see multiple clk_lookups for one clock, so seeing the same + * clock is fine. However, warn if we see different clocks with the + * same name */ + list_for_each_entry(c, &clocks, list) { + if (c == clk) + goto out_unlock; + + if (!strcmp(clk->name, c->name)) + pr_warn("clock %s: duplicate name\n", clk->name); + } + + list_add(&clk->list, &clocks); +out_unlock: + mutex_unlock(&clocks_lock); +} + +static void *clk_debug_seq_start(struct seq_file *m, loff_t *pos) +{ + mutex_lock(&clocks_lock); + return seq_list_start(&clocks, *pos); +} + +static void *clk_debug_seq_next(struct seq_file *m, void *v, loff_t *pos) +{ + return seq_list_next(v, &clocks, pos); +} + +static void clk_debug_seq_stop(struct seq_file *m, void *v) +{ + mutex_unlock(&clocks_lock); +} + +static int clk_debug_seq_show(struct seq_file *m, void *v) +{ + struct clk *parent, *clk = list_entry(v, struct clk, list); + const char *parent_name = "root"; + + parent = clk_get_parent(clk); + if (parent && !IS_ERR(parent)) + parent_name = parent->name; + + seq_printf(m, "%s [parent %s] usecount %d rate %lu\n", + clk->name, parent_name, + clk->enable_count, clk_get_rate(clk)); + return 0; +} + +static const struct seq_operations clk_debug_seq_ops = { + .start = clk_debug_seq_start, + .next = clk_debug_seq_next, + .stop = clk_debug_seq_stop, + .show = clk_debug_seq_show, +}; + +static int clk_debug_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &clk_debug_seq_ops); +} + +static const struct file_operations clk_debug_file_ops = { + .open = clk_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +int clk_debug_init(void) +{ + debugfs_create_file("clocks", 0444, NULL, NULL, + &clk_debug_file_ops); + return 0; +} + +late_initcall(clk_debug_init); + +#endif /* CONFIG_CLK_DEBUG */