summaryrefslogtreecommitdiff
path: root/arch/arm
diff options
context:
space:
mode:
authorVincent Guittot <vincent.guittot@stericsson.com>2010-12-14 10:32:28 +0100
committerUlf Hansson <ulf.hansson@stericsson.com>2011-09-19 15:14:52 +0200
commit7b9732215e0fb7f0361bff1aa2ffcf2b529c449b (patch)
treec43932ffb212388cd164fceaaac52930d1173395 /arch/arm
parent4febd388795eec55092154ffa4701696be7196dc (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/arm')
-rw-r--r--arch/arm/mach-ux500/clock.c323
-rw-r--r--arch/arm/mach-ux500/clock.h4
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
};
/**