From 770f204fbd1ded8c48a8441e55e86d77ad99d367 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 14 Nov 2016 11:26:02 +0530 Subject: [PATCH 1/3] clk: add/modify debugfs support for clocks Update clock debugfs to support the below functionalities. - Allow enable/disable a clock. - Allow set_rate on a clock. - Display the list of enabled_clocks along with prepare_count, enable_count and rate. - Display the register contents of all the clocks which support this clock operation. Change-Id: Ib67b3a3409c9e7d8adb710bb524f54f543abf712 Signed-off-by: Taniya Das --- drivers/clk/clk.c | 207 ++++++++++++++++++++++++++++++++++- include/linux/clk-provider.h | 6 + 2 files changed, 209 insertions(+), 4 deletions(-) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 1eb6e32e0d51..620b1c16ab21 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -2409,6 +2409,190 @@ static const struct file_operations clk_dump_fops = { .release = single_release, }; +static int clock_debug_rate_set(void *data, u64 val) +{ + struct clk_core *core = data; + int ret; + + ret = clk_set_rate(core->hw->clk, val); + if (ret) + pr_err("clk_set_rate(%lu) failed (%d)\n", + (unsigned long)val, ret); + + return ret; +} + +static int clock_debug_rate_get(void *data, u64 *val) +{ + struct clk_core *core = data; + + *val = core->hw->core->rate; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_rate_fops, clock_debug_rate_get, + clock_debug_rate_set, "%llu\n"); + +static ssize_t clock_parent_read(struct file *filp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + char name[256] = {0}; + struct clk_core *core = filp->private_data; + struct clk_core *p = core->hw->core->parent; + + snprintf(name, sizeof(name), "%s\n", p ? p->name : "None\n"); + + return simple_read_from_buffer(ubuf, cnt, ppos, name, strlen(name)); +} + +static const struct file_operations clock_parent_fops = { + .open = simple_open, + .read = clock_parent_read, +}; + +static int clock_debug_enable_set(void *data, u64 val) +{ + struct clk_core *core = data; + int rc = 0; + + if (val) + rc = clk_prepare_enable(core->hw->clk); + else + clk_disable_unprepare(core->hw->clk); + + return rc; +} + +static int clock_debug_enable_get(void *data, u64 *val) +{ + struct clk_core *core = data; + int enabled = 0; + + enabled = core->enable_count; + + *val = enabled; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get, + clock_debug_enable_set, "%lld\n"); + +#define clock_debug_output(m, c, fmt, ...) \ +do { \ + if (m) \ + seq_printf(m, fmt, ##__VA_ARGS__); \ + else if (c) \ + pr_cont(fmt, ##__VA_ARGS__); \ + else \ + pr_info(fmt, ##__VA_ARGS__); \ +} while (0) + +int clock_debug_print_clock(struct clk_core *c, struct seq_file *s) +{ + char *start = ""; + struct clk *clk; + + if (!c || !c->prepare_count) + return 0; + + clk = c->hw->clk; + + clock_debug_output(s, 0, "\t"); + + do { + clock_debug_output(s, 1, "%s%s:%u:%u [%ld]", start, + clk->core->name, + clk->core->prepare_count, + clk->core->enable_count, + clk->core->rate); + start = " -> "; + } while ((clk = clk_get_parent(clk))); + + clock_debug_output(s, 1, "\n"); + + return 1; +} + +/* + * clock_debug_print_enabled_clocks() - Print names of enabled clocks + */ +static void clock_debug_print_enabled_clocks(struct seq_file *s) +{ + struct clk_core *core; + int cnt = 0; + + clock_debug_output(s, 0, "Enabled clocks:\n"); + + mutex_lock(&clk_debug_lock); + + hlist_for_each_entry(core, &clk_debug_list, debug_node) + cnt += clock_debug_print_clock(core, s); + + mutex_unlock(&clk_debug_lock); + + if (cnt) + clock_debug_output(s, 0, "Enabled clock count: %d\n", cnt); + else + clock_debug_output(s, 0, "No clocks enabled.\n"); +} + +static int enabled_clocks_show(struct seq_file *s, void *unused) +{ + clock_debug_print_enabled_clocks(s); + + return 0; +} + +static int enabled_clocks_open(struct inode *inode, struct file *file) +{ + return single_open(file, enabled_clocks_show, inode->i_private); +} + +static const struct file_operations clk_enabled_list_fops = { + .open = enabled_clocks_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void clk_debug_print_hw(struct clk_core *clk, struct seq_file *f) +{ + if (IS_ERR_OR_NULL(clk)) + return; + + clk_debug_print_hw(clk->parent, f); + + clock_debug_output(f, false, "%s\n", clk->name); + + if (!clk->ops->list_registers) + return; + + clk->ops->list_registers(f, clk->hw); +} + +static int print_hw_show(struct seq_file *m, void *unused) +{ + struct clk_core *c = m->private; + + clk_debug_print_hw(c, m); + + return 0; +} + +static int print_hw_open(struct inode *inode, struct file *file) +{ + return single_open(file, print_hw_show, inode->i_private); +} + +static const struct file_operations clock_print_hw_fops = { + .open = print_hw_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) { struct dentry *d; @@ -2425,8 +2609,8 @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) core->dentry = d; - d = debugfs_create_u32("clk_rate", S_IRUGO, core->dentry, - (u32 *)&core->rate); + d = debugfs_create_file("clk_rate", S_IRUGO, core->dentry, core, + &clock_rate_fops); if (!d) goto err_out; @@ -2450,8 +2634,8 @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) if (!d) goto err_out; - d = debugfs_create_u32("clk_enable_count", S_IRUGO, core->dentry, - (u32 *)&core->enable_count); + d = debugfs_create_file("clk_enable_count", S_IRUGO, core->dentry, + core, &clock_enable_fops); if (!d) goto err_out; @@ -2460,6 +2644,16 @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) if (!d) goto err_out; + d = debugfs_create_file("clk_parent", S_IRUGO, core->dentry, core, + &clock_parent_fops); + if (!d) + goto err_out; + + d = debugfs_create_file("clk_print_regs", S_IRUGO, core->dentry, + core, &clock_print_hw_fops); + if (!d) + goto err_out; + if (core->ops->debug_init) { ret = core->ops->debug_init(core->hw, core->dentry); if (ret) @@ -2570,6 +2764,11 @@ static int __init clk_debug_init(void) if (!d) return -ENOMEM; + d = debugfs_create_file("clk_enabled_list", S_IRUGO, rootdir, + &clk_debug_list, &clk_enabled_list_fops); + if (!d) + return -ENOMEM; + mutex_lock(&clk_debug_lock); hlist_for_each_entry(core, &clk_debug_list, debug_node) clk_debug_create_one(core, rootdir); diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 2a5acbdc6327..2a4047c049a8 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -177,6 +177,10 @@ struct clk_rate_request { * @set_flags: Set custom flags which deals with hardware specifics. Returns 0 * on success, -EEROR otherwise. * + * @list_registers: Queries the hardware to get the current register contents. + * This callback is optional and required clocks could + * add this callback. + * * The clk_enable/clk_disable and clk_prepare/clk_unprepare pairs allow * implementations to split any work between atomic (enable) and sleepable * (prepare) contexts. If enabling a clock requires code that might sleep, @@ -217,6 +221,8 @@ struct clk_ops { void (*init)(struct clk_hw *hw); int (*debug_init)(struct clk_hw *hw, struct dentry *dentry); int (*set_flags)(struct clk_hw *hw, unsigned flags); + void (*list_registers)(struct seq_file *f, + struct clk_hw *hw); }; /** From dba9af81b8765aa65fed6488b29eb4bbfe90267d Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 14 Nov 2016 11:54:02 +0530 Subject: [PATCH 2/3] clk: Add support for list_rates ops for clocks Add support for clocks debugfs to be able to display - rates_max which indicates the frequency to voltage mapping of a clock. - list_rates, the list of clock frequencies supported by root clocks. - Also display the rate_max associated with enabled clocks list. Change-Id: I0a202af6f46c7cf164036d65487db5c40aab4063 Signed-off-by: Taniya Das --- drivers/clk/clk.c | 131 ++++++++++++++++++++++++++++++++++- include/linux/clk-provider.h | 6 ++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 620b1c16ab21..deb935ce6a11 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -2502,7 +2502,15 @@ int clock_debug_print_clock(struct clk_core *c, struct seq_file *s) clock_debug_output(s, 0, "\t"); do { - clock_debug_output(s, 1, "%s%s:%u:%u [%ld]", start, + if (clk->core->vdd_class) + clock_debug_output(s, 1, "%s%s:%u:%u [%ld, %d]", start, + clk->core->name, + clk->core->prepare_count, + clk->core->enable_count, + clk->core->rate, + clk_find_vdd_level(clk->core, clk->core->rate)); + else + clock_debug_output(s, 1, "%s%s:%u:%u [%ld]", start, clk->core->name, clk->core->prepare_count, clk->core->enable_count, @@ -2593,6 +2601,117 @@ static const struct file_operations clock_print_hw_fops = { .release = seq_release, }; +static int list_rates_show(struct seq_file *s, void *unused) +{ + struct clk_core *core = s->private; + int level = 0, i = 0; + unsigned long rate, rate_max = 0; + + /* Find max frequency supported within voltage constraints. */ + if (!core->vdd_class) { + rate_max = ULONG_MAX; + } else { + for (level = 0; level < core->num_rate_max; level++) + if (core->rate_max[level]) + rate_max = core->rate_max[level]; + } + + /* + * List supported frequencies <= rate_max. Higher frequencies may + * appear in the frequency table, but are not valid and should not + * be listed. + */ + while (!IS_ERR_VALUE(rate = + core->ops->list_rate(core->hw, i++, rate_max))) { + if (rate <= 0) + break; + if (rate <= rate_max) + seq_printf(s, "%lu\n", rate); + } + + return 0; +} + +static int list_rates_open(struct inode *inode, struct file *file) +{ + return single_open(file, list_rates_show, inode->i_private); +} + +static const struct file_operations list_rates_fops = { + .open = list_rates_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void clock_print_rate_max_by_level(struct seq_file *s, int level) +{ + struct clk_core *core = s->private; + struct clk_vdd_class *vdd_class = core->vdd_class; + int off, i, vdd_level, nregs = vdd_class->num_regulators; + + vdd_level = clk_find_vdd_level(core, core->rate); + + seq_printf(s, "%2s%10lu", vdd_level == level ? "[" : "", + core->rate_max[level]); + + for (i = 0; i < nregs; i++) { + off = nregs*level + i; + if (vdd_class->vdd_uv) + seq_printf(s, "%10u", vdd_class->vdd_uv[off]); + } + + if (vdd_level == level) + seq_puts(s, "]"); + + seq_puts(s, "\n"); +} + +static int rate_max_show(struct seq_file *s, void *unused) +{ + struct clk_core *core = s->private; + struct clk_vdd_class *vdd_class = core->vdd_class; + int level = 0, i, nregs = vdd_class->num_regulators; + char reg_name[10]; + + int vdd_level = clk_find_vdd_level(core, core->rate); + + if (vdd_level < 0) { + seq_printf(s, "could not find_vdd_level for %s, %ld\n", + core->name, core->rate); + return 0; + } + + seq_printf(s, "%12s", ""); + for (i = 0; i < nregs; i++) { + snprintf(reg_name, ARRAY_SIZE(reg_name), "reg %d", i); + seq_printf(s, "%10s", reg_name); + } + + seq_printf(s, "\n%12s", "freq"); + for (i = 0; i < nregs; i++) + seq_printf(s, "%10s", "uV"); + + seq_puts(s, "\n"); + + for (level = 0; level < core->num_rate_max; level++) + clock_print_rate_max_by_level(s, level); + + return 0; +} + +static int rate_max_open(struct inode *inode, struct file *file) +{ + return single_open(file, rate_max_show, inode->i_private); +} + +static const struct file_operations rate_max_fops = { + .open = rate_max_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) { struct dentry *d; @@ -2614,6 +2733,16 @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) if (!d) goto err_out; + if (core->ops->list_rate) { + if (!debugfs_create_file("clk_list_rates", + S_IRUGO, core->dentry, core, &list_rates_fops)) + goto err_out; + } + + if (core->vdd_class && !debugfs_create_file("clk_rate_max", + S_IRUGO, core->dentry, core, &rate_max_fops)) + goto err_out; + d = debugfs_create_u32("clk_accuracy", S_IRUGO, core->dentry, (u32 *)&core->accuracy); if (!d) diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 2a4047c049a8..fd2eb059b991 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -181,6 +181,10 @@ struct clk_rate_request { * This callback is optional and required clocks could * add this callback. * + * @list_rate: Return the nth supported frequency for a given clock which is + * below rate_max on success and -ENXIO in case of no frequency + * table. + * * The clk_enable/clk_disable and clk_prepare/clk_unprepare pairs allow * implementations to split any work between atomic (enable) and sleepable * (prepare) contexts. If enabling a clock requires code that might sleep, @@ -223,6 +227,8 @@ struct clk_ops { int (*set_flags)(struct clk_hw *hw, unsigned flags); void (*list_registers)(struct seq_file *f, struct clk_hw *hw); + long (*list_rate)(struct clk_hw *hw, unsigned n, + unsigned long rate_max); }; /** From b9b1975fe3f2d0ea767be06a494a8e5983cab911 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 7 Nov 2016 10:08:30 +0530 Subject: [PATCH 3/3] clk: Add support to allow client to print all enabled clocks The clock api clock_debug_print_enabled would allow any client like low power driver to print all the enabled clocks which include the prepare_count/enable_count/rate/rate max vote. Change-Id: I936496e553bc958c10e743fd8a225ffc7fbc0f79 Signed-off-by: Taniya Das --- drivers/clk/clk.c | 20 ++++++++++++++++++++ drivers/clk/clk.h | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index deb935ce6a11..25ab30063072 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -2267,6 +2267,7 @@ EXPORT_SYMBOL_GPL(clk_set_flags); static struct dentry *rootdir; static int inited = 0; +static u32 debug_suspend; static DEFINE_MUTEX(clk_debug_lock); static HLIST_HEAD(clk_debug_list); @@ -2854,6 +2855,19 @@ struct dentry *clk_debugfs_add_file(struct clk_hw *hw, char *name, umode_t mode, } EXPORT_SYMBOL_GPL(clk_debugfs_add_file); +/* + * Print the names of all enabled clocks and their parents if + * debug_suspend is set from debugfs. + */ +void clock_debug_print_enabled(void) +{ + if (likely(!debug_suspend)) + return; + + clock_debug_print_enabled_clocks(NULL); +} +EXPORT_SYMBOL_GPL(clock_debug_print_enabled); + /** * clk_debug_init - lazily populate the debugfs clk directory * @@ -2898,6 +2912,12 @@ static int __init clk_debug_init(void) if (!d) return -ENOMEM; + + d = debugfs_create_u32("debug_suspend", S_IRUGO | S_IWUSR, + rootdir, &debug_suspend); + if (!d) + return -ENOMEM; + mutex_lock(&clk_debug_lock); hlist_for_each_entry(core, &clk_debug_list, debug_node) clk_debug_create_one(core, rootdir); diff --git a/drivers/clk/clk.h b/drivers/clk/clk.h index 97941b0f8f32..179b27c08022 100644 --- a/drivers/clk/clk.h +++ b/drivers/clk/clk.h @@ -20,6 +20,10 @@ struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec, struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id, const char *con_id); void __clk_free_clk(struct clk *clk); + +/* Debugfs API to print the enabled clocks */ +void clock_debug_print_enabled(void); + #else /* All these casts to avoid ifdefs in clkdev... */ static inline struct clk *