diff options
Diffstat (limited to 'drivers/clk/clk.c')
-rw-r--r-- | drivers/clk/clk.c | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 7d74830e2ced..d2f0a565f75f 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -114,6 +114,134 @@ static struct hlist_head *orphan_list[] = { NULL, }; +#ifdef CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING + +#ifdef CONFIG_COMMON_CLK_BEGIN_ACCOUNTING_FROM_BOOT +static bool freq_stats_on = true; +#else +static bool freq_stats_on; +#endif /*CONFIG_COMMON_CLK_BEGIN_ACCOUNTING_FROM_BOOT*/ + +static void free_tree(struct rb_node *node) +{ + struct freq_stats *this; + + if (!node) + return; + + free_tree(node->rb_left); + free_tree(node->rb_right); + + this = rb_entry(node, struct freq_stats, node); + kfree(this); +} + +static struct freq_stats *freq_stats_insert(struct rb_root *freq_stats_table, + unsigned long rate) +{ + struct rb_node **new = &(freq_stats_table->rb_node), *parent = NULL; + struct freq_stats *this; + + /* Figure out where to put new node */ + while (*new) { + this = rb_entry(*new, struct freq_stats, node); + parent = *new; + + if (rate < this->rate) + new = &((*new)->rb_left); + else if (rate > this->rate) + new = &((*new)->rb_right); + else + return this; + } + + this = kzalloc(sizeof(*this), GFP_ATOMIC); + this->rate = rate; + + /* Add new node and rebalance tree. */ + rb_link_node(&this->node, parent, new); + rb_insert_color(&this->node, freq_stats_table); + + return this; +} + +static void generic_print_freq_stats_table(struct seq_file *m, + struct clk *clk, + bool indent, int level) +{ + struct rb_node *pos; + struct freq_stats *cur; + + if (indent) + seq_printf(m, "%*s*%s%20s", level * 3 + 1, "", + !clk->current_freq_stats ? "[" : "", + "default_freq"); + else + seq_printf(m, "%2s%20s", !clk->current_freq_stats ? "[" : "", + "default_freq"); + + if (!clk->current_freq_stats && !ktime_equal(clk->start_time, + ktime_set(0, 0))) + seq_printf(m, "%40llu", + ktime_to_ms(ktime_add(clk->default_freq_time, + ktime_sub(ktime_get(), clk->start_time)))); + else + seq_printf(m, "%40llu", ktime_to_ms(clk->default_freq_time)); + + if (!clk->current_freq_stats) + seq_puts(m, "]"); + + seq_puts(m, "\n"); + + for (pos = rb_first(&clk->freq_stats_table); pos; pos = rb_next(pos)) { + cur = rb_entry(pos, typeof(*cur), node); + + if (indent) + seq_printf(m, "%*s*%s%20lu", level * 3 + 1, "", + cur->rate == clk->rate ? "[" : "", cur->rate); + else + seq_printf(m, "%2s%20lu", cur->rate == clk->rate ? + "[" : "", cur->rate); + + if (cur->rate == clk->rate && !ktime_equal(clk->start_time, + ktime_set(0, 0))) + seq_printf(m, "%40llu", + ktime_to_ms(ktime_add(cur->time_spent, + ktime_sub(ktime_get(), clk->start_time)))); + else + seq_printf(m, "%40llu", ktime_to_ms(cur->time_spent)); + + if (cur->rate == clk->rate) + seq_puts(m, "]"); + seq_puts(m, "\n"); + } +} + +static int clock_print_freq_stats_table(struct seq_file *m, void *unused) +{ + struct clk *clk = m->private; + + if (!(clk->flags & CLK_GET_RATE_NOCACHE)) + generic_print_freq_stats_table(m, clk, false, 0); + + return 0; +} + +static int freq_stats_table_open(struct inode *inode, struct file *file) +{ + return single_open(file, clock_print_freq_stats_table, + inode->i_private); +} + +static const struct file_operations freq_stats_table_fops = { + .open = freq_stats_table_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif /*CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING*/ + + static void clk_summary_show_one(struct seq_file *s, struct clk *c, int level) { if (!c) @@ -124,6 +252,12 @@ static void clk_summary_show_one(struct seq_file *s, struct clk *c, int level) 30 - level * 3, c->name, c->enable_count, c->prepare_count, clk_get_rate(c), clk_get_accuracy(c), clk_get_phase(c)); + +#ifdef CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING + if (!(c->flags & CLK_GET_RATE_NOCACHE)) + generic_print_freq_stats_table(s, c, true, level); +#endif /*CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING*/ + } static void clk_summary_show_subtree(struct seq_file *s, struct clk *c, @@ -240,6 +374,78 @@ static const struct file_operations clk_dump_fops = { .release = single_release, }; +#ifdef CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING +static int freq_stats_get(void *unused, u64 *val) +{ + *val = freq_stats_on; + return 0; +} + +static void clk_traverse_subtree(struct clk *clk, int freq_stats_on) +{ + struct clk *child; + struct rb_node *node; + + if (!clk) + return; + + if (freq_stats_on) { + for (node = rb_first(&clk->freq_stats_table); + node; node = rb_next(node)) + rb_entry(node, struct freq_stats, node)->time_spent = + ktime_set(0, 0); + + clk->current_freq_stats = freq_stats_insert( + &clk->freq_stats_table, + clk_get_rate(clk)); + + if (clk->enable_count > 0) + clk->start_time = ktime_get(); + } else { + if (clk->enable_count > 0) { + if (!clk->current_freq_stats) + clk->default_freq_time = + ktime_add(clk->default_freq_time, + ktime_sub(ktime_get(), clk->start_time)); + else + clk->current_freq_stats->time_spent = + ktime_add(clk->current_freq_stats->time_spent, + ktime_sub(ktime_get(), clk->start_time)); + + clk->start_time = ktime_set(0, 0); + } + } + hlist_for_each_entry(child, &clk->children, child_node) + clk_traverse_subtree(child, freq_stats_on); +} + +static int freq_stats_set(void *data, u64 val) +{ + struct clk *c; + unsigned long flags; + struct hlist_head **lists = (struct hlist_head **)data; + + clk_prepare_lock(); + flags = clk_enable_lock(); + + if (val == 0) + freq_stats_on = 0; + else + freq_stats_on = 1; + + for (; *lists; lists++) + hlist_for_each_entry(c, *lists, child_node) + clk_traverse_subtree(c, freq_stats_on); + + clk_enable_unlock(flags); + clk_prepare_unlock(); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(freq_stats_fops, freq_stats_get, + freq_stats_set, "%llu\n"); +#endif /*CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING*/ + static int clk_debug_create_one(struct clk *clk, struct dentry *pdentry) { struct dentry *d; @@ -291,6 +497,14 @@ static int clk_debug_create_one(struct clk *clk, struct dentry *pdentry) if (!d) goto err_out; +#ifdef CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING + d = debugfs_create_file("frequency_stats_table", S_IRUGO, clk->dentry, + clk, &freq_stats_table_fops); + + if (!d) + goto err_out; +#endif /*CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING*/ + if (clk->ops->debug_init) { ret = clk->ops->debug_init(clk->hw, clk->dentry); if (ret) @@ -403,6 +617,13 @@ static int __init clk_debug_init(void) if (!d) return -ENOMEM; +#ifdef CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING + d = debugfs_create_file("freq_stats_on", S_IRUGO|S_IWUSR, + rootdir, &all_lists, &freq_stats_fops); + if (!d) + return -ENOMEM; +#endif /*CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING*/ + mutex_lock(&clk_debug_lock); hlist_for_each_entry(clk, &clk_debug_list, debug_node) clk_debug_create_one(clk, rootdir); @@ -852,6 +1073,22 @@ static void __clk_disable(struct clk *clk) if (clk->ops->disable) clk->ops->disable(clk->hw); +#ifdef CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING + + if (freq_stats_on) { + if (!clk->current_freq_stats) + clk->default_freq_time = + ktime_add(clk->default_freq_time, + ktime_sub(ktime_get(), clk->start_time)); + else + clk->current_freq_stats->time_spent = + ktime_add(clk->current_freq_stats->time_spent, + ktime_sub(ktime_get(), clk->start_time)); + + clk->start_time = ktime_set(0, 0); + } +#endif /*CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING*/ + __clk_disable(clk->parent); } @@ -903,6 +1140,11 @@ static int __clk_enable(struct clk *clk) return ret; } } + +#ifdef CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING + if (freq_stats_on) + clk->start_time = ktime_get(); +#endif /*CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING*/ } clk->enable_count++; @@ -1483,6 +1725,32 @@ static void clk_change_rate(struct clk *clk) clk->rate = clk_recalc(clk, best_parent_rate); +#ifdef CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING + if (freq_stats_on) { + if (!ktime_equal(clk->start_time, ktime_set(0, 0))) { + if (!clk->current_freq_stats) + clk->default_freq_time = + ktime_add(clk->default_freq_time, + ktime_sub(ktime_get(), + clk->start_time)); + else + clk->current_freq_stats->time_spent = + ktime_add( + clk->current_freq_stats->time_spent, + ktime_sub(ktime_get(), + clk->start_time)); + } + + clk->current_freq_stats = freq_stats_insert( + &clk->freq_stats_table, + clk->rate); + + if (clk->enable_count > 0) + clk->start_time = ktime_get(); + } +#endif /*CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING*/ + + if (clk->notifier_count && old_rate != clk->rate) __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate); @@ -2112,6 +2380,11 @@ static void __clk_release(struct kref *ref) kfree(clk->parent_names); kfree(clk->name); + +#ifdef CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING + free_tree(clk->freq_stats_table.rb_node); +#endif/*CONFIG_COMMON_CLK_FREQ_STATS_ACCOUNTING*/ + kfree(clk); } |