Hi Jeremy, Thanks a lot. Yes, it's also nice to have a file containing all the clock information which you have implemented in the email. Since we expect more features like enable/disable clocks in the debugfs, we also like to have tree-like debugfs for clock information. Below is my draft implementation. Original implementation is from omap platform clock code, and I adopted to common clock device.
diff --git a/arch/arm/common/clkdev.c b/arch/arm/common/clkdev.c index 9e4c4d9..e7a629a 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> @@ -186,3 +187,128 @@ void clkdev_drop(struct clk_lookup *cl) kfree(cl); } EXPORT_SYMBOL(clkdev_drop); + +#if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS) +/* + * debugfs support to trace clock tree hierarchy and attributes + */ + +static int open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct clk *clk = file->private_data; + unsigned long rate; + char buf[32]; + ssize_t ret; + + rate = clk_get_rate(clk); + + ret = snprintf(buf, 32, "%d\n", rate); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return ret; +} + +static const struct file_operations rate_fops = { + .open = open_file, + .read = read_file, +}; + +static struct dentry *clk_debugfs_root; + +static int clk_debugfs_register_one(struct clk *clk) +{ + int err; + struct dentry *d, *child, *child_tmp; + struct clk *pa = clk_get_parent(clk); + + if ((pa != ERR_PTR(-ENOSYS)) && pa) + d = debugfs_create_dir(clk ? clk->name : "null", pa->dentry); + else + d = debugfs_create_dir(clk ? clk->name : "null", clk_debugfs_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, &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; +} + +static int clk_debugfs_register(struct clk *clk) +{ + int err; + struct clk *pa; + + if (clk == ERR_PTR(-ENOSYS)) + return -1; + + if (!clk) + return -1; + + pa = clk_get_parent(clk); + + if ((pa != ERR_PTR(-ENOSYS)) && pa && !pa->dentry) { + err = clk_debugfs_register(pa); + if (err) + return err; + } + + if (!clk->dentry) { + err = clk_debugfs_register_one(clk); + if (err) + return err; + } + return 0; +} + +static int __init clk_debugfs_init(void) +{ + struct clk_lookup *cl; + struct dentry *d; + int err; + + d = debugfs_create_dir("clock", NULL); + if (!d) + return -ENOMEM; + clk_debugfs_root = d; + + list_for_each_entry(cl, &clocks, node) { + err = clk_debugfs_register(cl->clk); + if (err) + goto err_out; + } + return 0; +err_out: + debugfs_remove_recursive(clk_debugfs_root); + return err; +} +late_initcall(clk_debugfs_init); + +#endif /* defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS) */ diff --git a/arch/arm/plat-mxc/clock-common.c b/arch/arm/plat-mxc/clock-common.c index 8911813..96e19a4 100644 --- a/arch/arm/plat-mxc/clock-common.c +++ b/arch/arm/plat-mxc/clock-common.c @@ -93,11 +93,14 @@ void clk_mxc_disable(struct clk *_clk) unsigned long clk_mxc_get_rate(struct clk *_clk) { struct clk_mxc *clk = to_clk_mxc(_clk); - + if (clk->get_rate) return clk->get_rate(clk); - return clk_get_rate(clk->parent); + if (clk->parent) + return clk_get_rate(clk->parent); + + return 0; } /* Round the requested clock rate to the nearest supported diff --git a/include/linux/clk.h b/include/linux/clk.h index 56416b7..0dc3443 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -44,16 +44,22 @@ struct device; * registered with the arch-specific clock management code; the clock driver * code does not need to handle these. */ +#define CLK_NAME_LEN 16 struct clk { const struct clk_ops *ops; unsigned int enable_count; struct mutex mutex; + char name[CLK_NAME_LEN]; +#if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS) + struct dentry *dentry; +#endif }; -#define INIT_CLK(name, o) { \ +#define INIT_CLK(clk_name, o) { \ .ops = &o, \ .enable_count = 0, \ - .mutex = __MUTEX_INITIALIZER(name.mutex), \ + .mutex = __MUTEX_INITIALIZER(clk_name.mutex), \ + .name = #clk_name, \ } struct clk_ops { Yong On Mon, Nov 15, 2010 at 1:41 PM, Jeremy Kerr <jeremy.k...@canonical.com>wrote: > Add a debugfs file to expose system clocks. > > Signed-off-by: Jeremy Kerr <jeremy.k...@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 */ >
_______________________________________________ linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev