clk: add/modify debugfs support for clocks

Update clock debugfs to support the below functionalities.
 - Allow enabling/disabling a clock.
 - Allow calling set_rate on a clock.
 - Display the list of enabled_clocks along with prepare_count,
   enable_count and rate.
 - Add a trace_clocks node to dump the current state of all clocks
   in the ftrace logs.

Change-Id: Ib67b3a3409c9e7d8adb710bb524f54f543abf712
Signed-off-by: Taniya Das <tdas@codeaurora.org>
Signed-off-by: David Dai <daidavid1@codeaurora.org>
Signed-off-by: Mike Tipton <mdtipton@codeaurora.org>
This commit is contained in:
Taniya Das 2016-11-14 11:26:02 +05:30 • committed by Mike Tipton
commit fd4adb4c8c
5 changed files with 289 additions and 3 deletions

View file

@ -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

View file

@ -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);

View file

@ -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 *

View file

@ -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
};
/**

View file

@ -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 */