diff options
author | Srikanth Nori <srikanthn@nvidia.com> | 2012-07-02 17:35:38 -0700 |
---|---|---|
committer | Simone Willett <swillett@nvidia.com> | 2012-08-16 13:37:52 -0700 |
commit | 43ecb3724fa90222dd6cd502aee422364b0c65cb (patch) | |
tree | 39a1c589e7ec31c0124f1dbab151ae968802a91c /arch/arm/mach-tegra/clocks_stats.c | |
parent | be3e9def273181d1b44c1891de8602faebd719a8 (diff) |
ARM: tegra: clocks: Frequency stats for SCLK/CBUS
This adds a frequency histogram of the frequencies that SCLK
and CBUS clocks go to over time. Stats are presented in the
debugfs at /d/clock_stats/cbus and /d/clock_stats/sclk only if
debugfs is enabled in config
Change-Id: Icae83329612958d8ed4318b2e10c487683d9d734
Signed-off-by: Dan Willemsen <dwillemsen@nvidia.com>
Reviewed-on: http://git-master/r/118380
Reviewed-by: Wen Yi <wyi@nvidia.com>
Reviewed-by: Automatic_Commit_Validation_User
GVS: Gerrit_Virtual_Submit
Reviewed-by: Thomas Cherry <tcherry@nvidia.com>
Diffstat (limited to 'arch/arm/mach-tegra/clocks_stats.c')
-rw-r--r-- | arch/arm/mach-tegra/clocks_stats.c | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/clocks_stats.c b/arch/arm/mach-tegra/clocks_stats.c new file mode 100644 index 000000000000..1018a24ddbf9 --- /dev/null +++ b/arch/arm/mach-tegra/clocks_stats.c @@ -0,0 +1,259 @@ +/* + * arch/arm/mach-tegra/clocks_stats.c + * + * Copyright (C) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/list.h> + +#include "clock.h" + +#define STATS_TABLE_MAX_SIZE 64 + +/* + * Generic stats tracking structures and functions + */ +struct stats_entry { + int rate; + cputime64_t time_at_rate; +}; + +struct stats_table { + struct stats_entry *entry; + int last_rate; + cputime64_t last_updated; + spinlock_t spinlock; + unsigned int num_entries; +}; + +struct clock_data { + struct dentry *dentry; + struct list_head node; + struct stats_table table; + struct notifier_block rate_change_nb; +}; + +static LIST_HEAD(clock_stats); +static struct dentry *clock_debugfs_root; + +/* + * Initialize a stats table to zeros + */ +static void init_stats_table(struct stats_table *table) +{ + table->last_rate = -1; + spin_lock_init(&(table->spinlock)); + table->num_entries = 0; + table->last_updated = get_jiffies_64(); +} + +/* + * Populate table with possible rates + */ +static int populate_rates(struct stats_table *table, struct clk *c) +{ + unsigned long rate = 0, rounded_rate = 0; + unsigned int num_rates = 0; + int i = 0; + + /* Calculate number of rates */ + while (rate <= c->max_rate) { + rounded_rate = c->ops->round_rate(c, rate); + if (IS_ERR_VALUE(rounded_rate) || (rounded_rate <= rate)) + break; + + num_rates++; + rate = rounded_rate + 2000; /* 2kHz resolution */ + } + + /* Allocate space for a table of that size */ + table->entry = kmalloc(num_rates * sizeof(struct stats_entry), + GFP_KERNEL); + if (!table->entry) + return -ENOMEM; + rate = 0; + i = 0; + + /* Populate table with possible rates */ + while (rate <= c->max_rate) { + rounded_rate = c->ops->round_rate(c, rate); + if (IS_ERR_VALUE(rounded_rate) || (rounded_rate <= rate)) + break; + + table->entry[i].rate = rounded_rate; + table->entry[i].time_at_rate = 0; + i++; + rate = rounded_rate + 2000; /* 2kHz resolution */ + } + + table->num_entries = num_rates; + + return 0; +} + +/* + * Function is called whenever a rate changes. The time spent + * in the 'old rate' is finalized and the new rate is tracked. + * Entries are tracked in increasing order of rate + */ +static void update_stats_table(struct stats_table *table, int new_rate) +{ + int i = 0; + unsigned long flags; + u64 cur_jiffies = get_jiffies_64(); + + spin_lock_irqsave(&table->spinlock, flags); + + if (new_rate == -1) + new_rate = table->last_rate; + + /* update time spent on old clock */ + for (i = 0; i < table->num_entries; i++) { + if (table->entry[i].rate == table->last_rate) { + table->entry[i].time_at_rate = cputime64_add( + table->entry[i].time_at_rate, + cputime64_sub(cur_jiffies, + table->last_updated)); + } + } + + table->last_updated = cur_jiffies; + table->last_rate = new_rate; + + spin_unlock_irqrestore(&table->spinlock, flags); + +} + +/* + * Print stats table to seq_file + */ +static void dump_stats_table(struct seq_file *s, struct stats_table *table) +{ + int i = 0; + update_stats_table(table, -1); + + seq_printf(s, "%-10s %-10s\n", "rate kHz", "time"); + for (i = 0; i < table->num_entries; i++) { + seq_printf(s, "%-10lu %-10llu\n", + (long unsigned int)(table->entry[i].rate/1000), + cputime64_to_clock_t(table->entry[i].time_at_rate)); + } +} + +static int stats_show(struct seq_file *s, void *data) +{ + struct clock_data *d = (struct clock_data *)(s->private); + dump_stats_table(s, &d->table); + return 0; +} + +static int stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, stats_show, inode->i_private); +} + +static const struct file_operations clock_stats_fops = { + .open = stats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * Clock rate change notification callback + */ +static int rate_notify_cb(struct notifier_block *nb, unsigned long rate, + void *v) +{ + struct clock_data *c = + container_of(nb, struct clock_data, rate_change_nb); + update_stats_table(&c->table, rate); + return NOTIFY_OK; +} + +/* + * Call once for each clock to track + */ +static int track_clock(char *clk_name) +{ + int ret = 0; + struct clock_data *d; + struct clk *c = clk_get(NULL, clk_name); + if (IS_ERR(c)) + return PTR_ERR(c); + + d = kmalloc(sizeof(struct clock_data), GFP_KERNEL); + if (d == NULL) + goto err_clk; + + d->rate_change_nb.notifier_call = rate_notify_cb; + + if (!clock_debugfs_root) + goto err_clk; + + d->dentry = debugfs_create_file( + clk_name, S_IRUGO, clock_debugfs_root, d, &clock_stats_fops); + if (!d->dentry) + goto err_clk; + + init_stats_table(&d->table); + ret = populate_rates(&d->table, c); + if (ret) + goto err_out; + + ret = tegra_register_clk_rate_notifier(c, &d->rate_change_nb); + if (ret) + goto err_out; + + list_add(&d->node, &clock_stats); + + clk_put(c); + return 0; + +err_out: + kfree(d->table.entry); + debugfs_remove(d->dentry); +err_clk: + kfree(d); + clk_put(c); + return -ENOMEM; +} + +static int __init tegra_clocks_debug_init(void) +{ + int ret = 0; + + clock_debugfs_root = debugfs_create_dir("clock_stats", NULL); + if (!clock_debugfs_root) + return -ENOMEM; + + /* Start tracking individual clocks */ + ret = track_clock("sbus"); + if (0 != ret) + goto err_out; + + ret = track_clock("cbus"); + if (0 != ret) + goto err_out; + + return 0; + +err_out: + return ret; + +} +late_initcall(tegra_clocks_debug_init); |