diff options
author | Vincent Guittot <vincent.guittot@stericsson.com> | 2010-12-14 10:32:28 +0100 |
---|---|---|
committer | Ulf Hansson <ulf.hansson@stericsson.com> | 2011-09-19 15:14:52 +0200 |
commit | 7b9732215e0fb7f0361bff1aa2ffcf2b529c449b (patch) | |
tree | c43932ffb212388cd164fceaaac52930d1173395 /arch | |
parent | 4febd388795eec55092154ffa4701696be7196dc (diff) |
add debugfs support for powerdebug
Change-Id: I1a2992f01cf10236c25d33feda2b289c2dbbbeb9
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/8549
Reviewed-by: QATOOLS
Reviewed-by: Vincent GUITTOT <vincent.guittot@stericsson.com>
Tested-by: Vincent GUITTOT <vincent.guittot@stericsson.com>
Reviewed-by: Linus WALLEIJ <linus.walleij@stericsson.com>
Signed-off-by: Vincent Guittot <vincent.guittot@stericsson.com>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/mach-ux500/clock.c | 323 | ||||
-rw-r--r-- | arch/arm/mach-ux500/clock.h | 4 |
2 files changed, 326 insertions, 1 deletions
diff --git a/arch/arm/mach-ux500/clock.c b/arch/arm/mach-ux500/clock.c index 925937332d7..a4c73fef82e 100644 --- a/arch/arm/mach-ux500/clock.c +++ b/arch/arm/mach-ux500/clock.c @@ -11,10 +11,22 @@ #include <linux/io.h> #include <linux/spinlock.h> #include <linux/mfd/ab8500/sysctrl.h> +#ifdef CONFIG_DEBUG_FS +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> /* for copy_from_user */ +#include <linux/kernel.h> +#endif + #include <mach/prcmu-fw-api.h> #include "clock.h" +#ifdef CONFIG_DEBUG_FS +static LIST_HEAD(clk_list); +#endif + #define PRCC_PCKEN 0x0 #define PRCC_PCKDIS 0x4 #define PRCC_KCKEN 0x8 @@ -111,6 +123,16 @@ bus_parent_error: return err; } +static struct clk *__clk_get_parent(struct clk *clk) +{ + if (clk->parent != NULL) + return clk->parent; + else if ((clk->bus_parent != NULL)) + return clk->bus_parent; + + return NULL; +} + unsigned long __clk_get_rate(struct clk *clk, void *current_lock) { unsigned long rate; @@ -169,6 +191,15 @@ void clk_disable(struct clk *clk) } EXPORT_SYMBOL(clk_disable); +struct clk *clk_get_parent(struct clk *clk) +{ + if (clk == NULL) + return ERR_PTR(-EINVAL); + + return __clk_get_parent(clk); +} +EXPORT_SYMBOL(clk_get_parent); + unsigned long clk_get_rate(struct clk *clk) { if (clk == NULL) @@ -357,8 +388,14 @@ void clks_register(struct clk_lookup *clks, size_t num) { unsigned int i; - for (i = 0; i < num; i++) + for (i = 0; i < num; i++) { clkdev_add(&clks[i]); +#ifdef CONFIG_DEBUG_FS + /* Check that the clock has not been already registered */ + if (!(clks[i].clk->list.prev != clks[i].clk->list.next)) + list_add_tail(&clks[i].clk->list, &clk_list); +#endif + } } int __init clk_init(void) @@ -378,3 +415,287 @@ int __init clk_init(void) return 0; } + +#ifdef CONFIG_DEBUG_FS +/* + * debugfs support to trace clock tree hierarchy and attributes with + * powerdebug + */ +static struct dentry *clk_debugfs_root; + +#ifdef CONFIG_DEBUG_FS_WRITE +static ssize_t enable_dbg_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct clk *clk = file->f_dentry->d_inode->i_private; + + clk_enable(clk); + return size; +} + +static ssize_t disable_dbg_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct clk *clk = file->f_dentry->d_inode->i_private; + + clk_disable(clk); + return size; +} + +static ssize_t rate_dbg_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct clk *clk = file->f_dentry->d_inode->i_private; + char crate[128]; + unsigned long rate; + + if (size < (sizeof(crate)-1)) { + if (copy_from_user(crate, buf, size)) + return -EFAULT; + crate[size] = 0; + if (!strict_strtoul(crate, 10, &rate)) + clk_set_rate(clk, rate); + return size; + } + return -EINVAL; +} + +static ssize_t parent_dbg_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct clk *clk = file->f_dentry->d_inode->i_private; + char cname[128]; + struct clk *pclk; + + if (size < (sizeof(cname) - 1)) { + if (copy_from_user(cname, buf, size)) + return -EFAULT; + cname[size] = 0; + pclk = clk_get_sys(cname, cname); + clk_set_parent(clk, pclk); + return size; + } + return -EINVAL; +} +#endif + +static ssize_t usecount_dbg_read(struct file *file, char __user *buf, + size_t size, loff_t *off) +{ + struct clk *clk = file->f_dentry->d_inode->i_private; + char cusecount[128]; + unsigned int len; + + len = sprintf(cusecount, "%u\n", clk->enabled); + return simple_read_from_buffer(buf, size, off, cusecount, len); +} + +static ssize_t rate_dbg_read(struct file *file, char __user *buf, + size_t size, loff_t *off) +{ + struct clk *clk = file->f_dentry->d_inode->i_private; + char crate[128]; + unsigned int rate; + unsigned int len; + + rate = clk_get_rate(clk); + len = sprintf(crate, "%u\n", rate); + return simple_read_from_buffer(buf, size, off, crate, len); +} + +static ssize_t parent_dbg_read(struct file *file, char __user *buf, + size_t size, loff_t *off) +{ + struct clk *clk = file->f_dentry->d_inode->i_private; + char cname[128]; + struct clk *pclk; + unsigned int len; + + pclk = clk_get_parent(clk); + if (pclk) + len = sprintf(cname, "%s\n", pclk->name); + else + len = sprintf(cname, "No parent\n"); + return simple_read_from_buffer(buf, size, off, cname, len); +} + +#ifdef CONFIG_DEBUG_FS_WRITE +static const struct file_operations enable_fops = { + .write = enable_dbg_write, +}; + +static const struct file_operations disable_fops = { + .write = disable_dbg_write, +}; +#endif + +static const struct file_operations usecount_fops = { + .read = usecount_dbg_read, +}; + +static const struct file_operations set_rate_fops = { +#ifdef CONFIG_DEBUG_FS_WRITE + .write = rate_dbg_write, +#endif + .read = rate_dbg_read, +}; + +static const struct file_operations set_parent_fops = { +#ifdef CONFIG_DEBUG_FS_WRITE + .write = parent_dbg_write, +#endif + .read = parent_dbg_read, +}; + +static struct dentry *clk_debugfs_register_dir(struct clk *c, + struct dentry *p_dentry) +{ + struct dentry *d, *clk_d, *child, *child_tmp; + char s[255]; + char *p = s; + + if (c->name == NULL) + p += sprintf(p, "BUG"); + else + p += sprintf(p, "%s", c->name); + + clk_d = debugfs_create_dir(s, p_dentry); + if (!clk_d) + return NULL; + + d = debugfs_create_file("usecount", S_IRUGO, + clk_d, c, &usecount_fops); + if (!d) + goto err_out; +#ifdef CONFIG_DEBUG_FS_WRITE + d = debugfs_create_file("enable", S_IWUGO, + clk_d, c, &enable_fops); + if (!d) + goto err_out; + d = debugfs_create_file("disable", S_IWUGO, + clk_d, c, &disable_fops); + if (!d) + goto err_out; + d = debugfs_create_file("rate", S_IRUGO | S_IWUGO, + clk_d, c, &set_rate_fops); + if (!d) + goto err_out; + d = debugfs_create_file("parent", S_IRUGO | S_IWUGO, + clk_d, c, &set_parent_fops); + if (!d) + goto err_out; +#else + d = debugfs_create_file("rate", S_IRUGO, + clk_d, c, &set_rate_fops); + if (!d) + goto err_out; + d = debugfs_create_file("parent", S_IRUGO, + clk_d, c, &set_parent_fops); + if (!d) + goto err_out; +#endif + /* + * TODO : not currently available in ux500 + * d = debugfs_create_x32("flags", S_IRUGO, clk_d, (u32 *)&c->flags); + * if (!d) + * goto err_out; + */ + + return clk_d; + +err_out: + d = clk_d; + list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) + debugfs_remove(child); + debugfs_remove(clk_d); + return NULL; +} + +static void clk_debugfs_remove_dir(struct dentry *cdentry) +{ + struct dentry *d, *child, *child_tmp; + + d = cdentry; + list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) + debugfs_remove(child); + debugfs_remove(cdentry); + return ; +} + + +static int clk_debugfs_register_one(struct clk *c) +{ + struct clk *pa = c->parent; + struct clk *bpa = c->bus_parent; + + if (!(bpa && !pa)) { + c->dent = clk_debugfs_register_dir(c, + pa ? pa->dent : clk_debugfs_root); + if (!c->dent) + return -ENOMEM; + } + + if (bpa) { + c->dent_bus = clk_debugfs_register_dir(c, + bpa->dent_bus ? bpa->dent_bus : bpa->dent); + if ((!c->dent_bus) && (c->dent)) { + clk_debugfs_remove_dir(c->dent); + c->dent = NULL; + return -ENOMEM; + } + } + return 0; +} + +static int clk_debugfs_register(struct clk *c) +{ + int err; + struct clk *pa = c->parent; + struct clk *bpa = c->bus_parent; + + if (pa && (!pa->dent && !pa->dent_bus)) { + err = clk_debugfs_register(pa); + if (err) + return err; + } + + if (bpa && (!bpa->dent && !bpa->dent_bus)) { + err = clk_debugfs_register(bpa); + if (err) + return err; + } + + if ((!c->dent) && (!c->dent_bus)) { + err = clk_debugfs_register_one(c); + if (err) + return err; + } + return 0; +} + +static int __init clk_debugfs_init(void) +{ + struct clk *c; + struct dentry *d; + int err; + + d = debugfs_create_dir("clock", NULL); + if (!d) + return -ENOMEM; + clk_debugfs_root = d; + + list_for_each_entry(c, &clk_list, list) { + err = clk_debugfs_register(c); + 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_DEBUG_FS) */ + + diff --git a/arch/arm/mach-ux500/clock.h b/arch/arm/mach-ux500/clock.h index e37e401b883..61d68b50007 100644 --- a/arch/arm/mach-ux500/clock.h +++ b/arch/arm/mach-ux500/clock.h @@ -47,6 +47,10 @@ struct clk { struct clk **parents; struct regulator *regulator; struct list_head list; +#if defined(CONFIG_DEBUG_FS) + struct dentry *dent; /* For visible tree hierarchy */ + struct dentry *dent_bus; /* For visible tree hierarchy */ +#endif }; /** |