Hi Jeremy, In power management group of linaro, we want to export debug information of clock to user space and make a common interface to all the SOC platform. Currently, each SOC platform has their own way to export clock information, like freesale and TI, which is not ideal.
To do so, I need to make somethings done, and some of them are related to common clock code, for which I am more than happy to hear your comments. 1. Create clock information based on common clock device, more specific, based on struct clk_lookup. Since platform drivers are supposed to register their clock using 'void clkdev_add(struct clk_lookup *cl)', clk_lookup should contain enough information for clock display, like clock name and platform clk definition 'struck clk'. An extra field need to be added into clk_lookup is a 'struct dentry *' to store a pointer of the dentry node which will be exported in debug fs, like below: struct clk_lookup { struct list_head node; const char *dev_id; const char *con_id; struct clk *clk; + +#if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS) + struct dentry *dent; +#endif }; 2. clock names is a little bit fuzzy, cause there are two names in the clk_lookup, they are dev_id and con_id. And there is no name checking in clkdev_add, which means that two clock with the same name could be register into system and it is not reasonable. Also, it is impossible to create clock information with two clocks having the same name, since user will be confused about that. So, I plan to add name checking to avoid name confilt in clkdev_add. To avoid confusion, I am going to use the combination of dev name and connection name togather to display clock. It goes like dev-id_con-id. 3. Currently, 'struct clk' is defined in platform related code, common clk interface did not impose any assumption on how each platform define their own 'struct clk', therefore, clock debug interface can not make any assumption on how much information the platform defined clock contains as well. So far, only clk_get_rate is there as a common defined interface which I can use to export clock rate information, and I plan to add some optional interface like clk_get_count, clk_get_flag, so more useful information can be displayed. I also attached a draft patch and the clock information displayed on the console is a tree pattern like below(generated by powerdebug, a user space application which can display clock information based on the debug interface I made in the kernel). |-- NULL-osc <flags=0x0:rate=24000000:usecount=0> | |-- NULL-spdif_xtal_clk <flags=0x0:rate=24000000:usecount=0> | | |-- mxc_alsa_spdif.0-NULL <flags=0x0:rate=24000000:usecount=0> | |-- NULL-usb_phy1_clk <flags=0x0:rate=24000000:usecount=0> | |-- NULL-lp_apm <flags=0x0:rate=24000000:usecount=0> | | |-- NULL-ssi_lp_apm_clk <flags=0x0:rate=24000000:usecount=0> | | | |-- mxc_ssi.1-NULL <flags=0x0:rate=12000000:usecount=0> | | | | |-- NULL-ssi_ext2_clk <flags=0x0:rate=12000000:usecount=0> | | | |-- mxc_ssi.0-NULL <flags=0x0:rate=12000000:usecount=0> | | | | |-- NULL-ssi_ext1_clk <flags=0x0:rate=12000000:usecount=0> Yong
diff --git a/arch/arm/common/clkdev.c b/arch/arm/common/clkdev.c index e2b2bb6..ad747b2 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> #include <mach/clkdev.h> @@ -95,6 +96,21 @@ EXPORT_SYMBOL(clk_put); void clkdev_add(struct clk_lookup *cl) { + struct clk_lookup *p; + struct clk *clk = NULL; + + list_for_each_entry(p, &clocks, node) { + if (p->dev_id) { + if (!cl->dev_id || strcmp(p->dev_id, cl->dev_id)) + continue; + } + if (p->con_id) { + if (!cl->con_id || strcmp(p->con_id, cl->con_id)) + continue; + } + + return; + } mutex_lock(&clocks_mutex); list_add_tail(&cl->node, &clocks); mutex_unlock(&clocks_mutex); @@ -177,3 +193,139 @@ 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 struct clk_lookup *clk_lookup_get_parent(struct clk *clk) +{ + struct clk_lookup *cl; + + list_for_each_entry(cl, &clocks, node) { + if (cl->clk == clk_get_parent(clk)) + return cl; + } + + return NULL; +} +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_lookup *cl) +{ + int err; + struct dentry *d, *child, *child_tmp; + struct clk_lookup *pa = clk_lookup_get_parent(cl->clk); + char s[255]; + char *p = s; + + if (cl->dev_id && cl->con_id) + p += sprintf(p, "%s-%s", cl->dev_id, cl->con_id); + else if (cl->dev_id) + p += sprintf(p, "%s-NULL", cl->dev_id); + else if (cl->con_id) + p += sprintf(p, "NULL-%s", cl->con_id); + else + p += sprintf(p, "%s", "unknown"); + + printk("create dir %s, %s....\n", s, pa? "nonroot" : "root"); + d = debugfs_create_dir(s, pa ? pa->dent : clk_debugfs_root); + if (!d) { + printk("failed to create dir....\n"); + return -ENOMEM; + } + + cl->dent = d; + + d = debugfs_create_file("rate", S_IRUGO, cl->dent, (void *)cl->clk, &rate_fops); + if (!d) { + err = -ENOMEM; + goto err_out; + } + + return 0; + +err_out: + printk("err out....\n"); + return -ENOMEM; + d = cl->dent; + list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) + debugfs_remove(child); + debugfs_remove(cl->dent); + return err; +} + +static int clk_debugfs_register(struct clk_lookup *cl) +{ + int err; + struct clk_lookup *pa = clk_lookup_get_parent(cl->clk); + + if (pa && !pa->dent) { + err = clk_debugfs_register(pa); + if (err) + return err; + } + + if (!cl->dent) { + err = clk_debugfs_register_one(cl); + 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) { + printk("dev name %s, con name %s\n",\ + cl->dev_id ? cl->dev_id : NULL,\ + cl->con_id ? cl->con_id : NULL); + err = clk_debugfs_register(cl); + 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/include/asm/clkdev.h b/arch/arm/include/asm/clkdev.h index b56c138..81bc118 100644 --- a/arch/arm/include/asm/clkdev.h +++ b/arch/arm/include/asm/clkdev.h @@ -20,6 +20,10 @@ struct clk_lookup { const char *dev_id; const char *con_id; struct clk *clk; + +#if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS) + struct dentry *dent; +#endif }; struct clk_lookup *clkdev_alloc(struct clk *clk, const char *con_id,
_______________________________________________ linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev