diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index c44247d0b83e..fcada610b3b1 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -23,6 +23,15 @@ config COMMON_CLK menu "Common Clock Framework" depends on COMMON_CLK +config COMMON_CLK_QCOM_DEBUG + bool "QCOM clock debug features" + depends on DEBUG_FS && QGKI + help + Support for QCOM debug features. These features include + modifications to existing debugfs nodes to make them writable (e.g. + clk_enable_count, clk_rate), as well as new debugfs nodes (e.g. + clk_enabled_list, debug_suspend, trace_clocks). + config COMMON_CLK_WM831X tristate "Clock driver for WM831x/2x PMICs" depends on MFD_WM831X diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 98cb6f6daf8e..bde91b2284e6 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -3127,6 +3127,76 @@ static int clk_max_rate_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(clk_max_rate); +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 = clk_get_rate(core->hw->clk); + + return 0; +} + +DEFINE_DEBUGFS_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_DEBUGFS_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get, + clock_debug_enable_set, "%lld\n"); + static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) { struct dentry *root; @@ -3137,14 +3207,15 @@ static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) root = debugfs_create_dir(core->name, pdentry); core->dentry = root; - debugfs_create_ulong("clk_rate", 0444, root, &core->rate); + debugfs_create_file("clk_rate", 0444, root, core, &clock_rate_fops); debugfs_create_file("clk_min_rate", 0444, root, core, &clk_min_rate_fops); debugfs_create_file("clk_max_rate", 0444, root, core, &clk_max_rate_fops); debugfs_create_ulong("clk_accuracy", 0444, root, &core->accuracy); debugfs_create_u32("clk_phase", 0444, root, &core->phase); debugfs_create_file("clk_flags", 0444, root, core, &clk_flags_fops); debugfs_create_u32("clk_prepare_count", 0444, root, &core->prepare_count); - debugfs_create_u32("clk_enable_count", 0444, root, &core->enable_count); + debugfs_create_file("clk_enable_count", 0444, root, core, + &clock_enable_fops); debugfs_create_u32("clk_protect_count", 0444, root, &core->protect_count); debugfs_create_u32("clk_notifier_count", 0444, root, &core->notifier_count); debugfs_create_file("clk_duty_cycle", 0444, root, core, @@ -3196,6 +3267,156 @@ static void clk_debug_unregister(struct clk_core *core) mutex_unlock(&clk_debug_lock); } +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG +#define clock_debug_output(m, fmt, ...) \ +do { \ + if (m) \ + seq_printf(m, fmt, ##__VA_ARGS__); \ + else \ + pr_info(fmt, ##__VA_ARGS__); \ +} while (0) + +static int clock_debug_print_clock(struct clk_core *c, struct seq_file *s) +{ + char *start = "\t"; + struct clk *clk; + + if (!c || !c->prepare_count) + return 0; + + clk = c->hw->clk; + + do { + c = clk->core; + if (c->ops->list_rate_vdd_level) + clock_debug_output(s, "%s%s:%u:%u [%ld, %d]", start, + c->name, + c->prepare_count, + c->enable_count, + c->rate, + c->ops->list_rate_vdd_level(c->hw, c->rate)); + else + clock_debug_output(s, "%s%s:%u:%u [%ld]", start, + c->name, + c->prepare_count, + c->enable_count, + c->rate); + start = " -> "; + } while (s && (clk = clk_get_parent(clk))); + + if (s) + clock_debug_output(s, "\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, "Enabled clocks:\n"); + + hlist_for_each_entry(core, &clk_debug_list, debug_node) + cnt += clock_debug_print_clock(core, s); + + if (cnt) + clock_debug_output(s, "Enabled clock count: %d\n", cnt); + else + clock_debug_output(s, "No clocks enabled.\n"); +} + +static int enabled_clocks_show(struct seq_file *s, void *unused) +{ + mutex_lock(&clk_debug_lock); + clock_debug_print_enabled_clocks(s); + mutex_unlock(&clk_debug_lock); + + 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 u32 debug_suspend; + +/* + * 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; + + if (!mutex_trylock(&clk_debug_lock)) + return; + + clock_debug_print_enabled_clocks(NULL); + mutex_unlock(&clk_debug_lock); +} +EXPORT_SYMBOL(clock_debug_print_enabled); + +static void clk_state_subtree(struct clk_core *c) +{ + int vdd_level = 0; + struct clk_core *child; + + if (!c) + return; + + if (c->ops->list_rate_vdd_level) + vdd_level = c->ops->list_rate_vdd_level(c->hw, c->rate); + + trace_clk_state(c->name, c->prepare_count, c->enable_count, + c->rate, vdd_level); + + hlist_for_each_entry(child, &c->children, child_node) + clk_state_subtree(child); +} + +static int clk_state_show(struct seq_file *s, void *data) +{ + struct clk_core *c; + struct hlist_head **lists = (struct hlist_head **)s->private; + + clk_prepare_lock(); + + for (; *lists; lists++) + hlist_for_each_entry(c, *lists, child_node) + clk_state_subtree(c); + + clk_prepare_unlock(); + + return 0; +} + + +static int clk_state_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_state_show, inode->i_private); +} + +static const struct file_operations clk_state_fops = { + .open = clk_state_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + /** * clk_debug_init - lazily populate the debugfs clk directory * @@ -3220,6 +3441,14 @@ static int __init clk_debug_init(void) debugfs_create_file("clk_orphan_dump", 0444, rootdir, &orphan_list, &clk_dump_fops); +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + debugfs_create_file("clk_enabled_list", 0444, rootdir, + &clk_debug_list, &clk_enabled_list_fops); + debugfs_create_u32("debug_suspend", 0644, rootdir, &debug_suspend); + debugfs_create_file("trace_clocks", 0444, rootdir, &all_lists, + &clk_state_fops); +#endif + 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 2d801900cad5..99a02ba422fd 100644 --- a/drivers/clk/clk.h +++ b/drivers/clk/clk.h @@ -25,6 +25,10 @@ struct clk_hw *clk_find_hw(const char *dev_id, const char *con_id); struct clk *clk_hw_create_clk(struct device *dev, struct clk_hw *hw, const char *dev_id, const char *con_id); void __clk_put(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 * diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 0cbcba1940f4..2846eeda3e92 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -208,6 +208,10 @@ struct clk_duty { * requirements that were needed while the clock and its tree * was changing states. Returns 0 on success, -EERROR otherwise. * + * @list_rate_vdd_level: Queries the required voltage level for the given rate. + * The return value may not represent an exact voltage and instead + * may be an abstract index or voltage "corner". + * * 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, @@ -259,6 +263,10 @@ struct clk_ops { int (*post_rate_change)(struct clk_hw *hw, unsigned long old_rate, unsigned long rate); +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + int (*list_rate_vdd_level)(struct clk_hw *hw, + unsigned int rate); +#endif }; /** diff --git a/include/trace/events/clk.h b/include/trace/events/clk.h index cb1aea25c199..bb74d3bba8c4 100644 --- a/include/trace/events/clk.h +++ b/include/trace/events/clk.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. */ #undef TRACE_SYSTEM #define TRACE_SYSTEM clk @@ -220,6 +220,42 @@ DEFINE_EVENT(clk_duty_cycle, clk_set_duty_cycle_complete, TP_ARGS(core, duty) ); +DECLARE_EVENT_CLASS(clk_state_dump, + + TP_PROTO(const char *name, unsigned int prepare_count, + unsigned int enable_count, unsigned long rate, unsigned int vdd_level), + + TP_ARGS(name, prepare_count, enable_count, rate, vdd_level), + + TP_STRUCT__entry( + __string(name, name) + __field(unsigned int, prepare_count) + __field(unsigned int, enable_count) + __field(unsigned long, rate) + __field(unsigned int, vdd_level) + ), + + TP_fast_assign( + __assign_str(name, name); + __entry->prepare_count = prepare_count; + __entry->enable_count = enable_count; + __entry->rate = rate; + __entry->vdd_level = vdd_level; + ), + + TP_printk("%s\tprepare:enable cnt [%u:%u]\trate: vdd_level [%lu:%u]", + __get_str(name), __entry->prepare_count, __entry->enable_count, + __entry->rate, __entry->vdd_level) +); + +DEFINE_EVENT(clk_state_dump, clk_state, + + TP_PROTO(const char *name, unsigned int prepare_count, + unsigned int enable_count, unsigned long rate, unsigned int vdd_level), + + TP_ARGS(name, prepare_count, enable_count, rate, vdd_level) +); + #endif /* _TRACE_CLK_H */ /* This part must be outside protection */