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.kerr@canonical.comwrote:
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 */