diff options
author | Alex Frid <afrid@nvidia.com> | 2011-03-08 10:35:31 -0800 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:42:24 -0800 |
commit | dc930f7e452c147783ed974a198be129e94445f7 (patch) | |
tree | 755fc5c9cce5764500ba3a8e73dd97a399d7f445 /arch | |
parent | 96b81d1d96e516da6305a8e9f0687c8a0d3723d1 (diff) |
ARM: tegra: dvfs: Add emc dfs statistic
Original-Change-Id: I191ce07b461c9283d61000ca81746b282502f786
Reviewed-on: http://git-master/r/22530
Tested-by: Aleksandr Frid <afrid@nvidia.com>
Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>
Original-Change-Id: I81d6caa3c8f6a0c267d171f38156657ef8c52688
Rebase-Id: R9d9ce785a3e65a0851b3f70159395ed6753bdf87
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/mach-tegra/tegra3_emc.c | 109 |
1 files changed, 106 insertions, 3 deletions
diff --git a/arch/arm/mach-tegra/tegra3_emc.c b/arch/arm/mach-tegra/tegra3_emc.c index 8323f8721bee..044db65e28d1 100644 --- a/arch/arm/mach-tegra/tegra3_emc.c +++ b/arch/arm/mach-tegra/tegra3_emc.c @@ -25,6 +25,10 @@ #include <linux/io.h> #include <linux/module.h> #include <linux/delay.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include <asm/cputime.h> #include <mach/iomap.h> @@ -175,7 +179,7 @@ enum { #undef DEFINE_REG static struct clk_mux_sel tegra_emc_clk_sel[TEGRA_EMC_TABLE_MAX_SIZE]; -static int emc_last_sel = TEGRA_EMC_TABLE_MAX_SIZE; +static int emc_last_sel; static struct tegra_emc_table start_timing; static const struct tegra_emc_table *tegra_emc_table; @@ -186,6 +190,13 @@ static u32 dram_dev_num; static struct clk *emc; +static struct { + cputime64_t time_at_clock[TEGRA_EMC_TABLE_MAX_SIZE]; + u64 last_update; + u64 clkchange_count; + spinlock_t spinlock; +} emc_stats; + static void __iomem *emc_base = IO_ADDRESS(TEGRA_EMC_BASE); static void __iomem *mc_base = IO_ADDRESS(TEGRA_MC_BASE); static void __iomem *clk_base = IO_ADDRESS(TEGRA_CLK_RESET_BASE); @@ -209,6 +220,26 @@ static inline u32 mc_readl(unsigned long addr) return readl((u32)mc_base + addr); } +static void emc_last_stats_update(int last_sel) +{ + unsigned long flags; + u64 cur_jiffies = get_jiffies_64(); + + spin_lock_irqsave(&emc_stats.spinlock, flags); + + emc_stats.time_at_clock[emc_last_sel] = cputime64_add( + emc_stats.time_at_clock[emc_last_sel], cputime64_sub( + cur_jiffies, emc_stats.last_update)); + + emc_stats.last_update = cur_jiffies; + + if (last_sel < TEGRA_EMC_TABLE_MAX_SIZE) { + emc_stats.clkchange_count++; + emc_last_sel = last_sel; + } + spin_unlock_irqrestore(&emc_stats.spinlock, flags); +} + static int wait_for_update(u32 status_reg, u32 bit_mask, bool updated_state) { int i; @@ -507,7 +538,9 @@ int tegra_emc_set_rate(unsigned long rate) if (i >= tegra_emc_table_size) return -EINVAL; - if (emc_last_sel >= tegra_emc_table_size) { + if (!emc_stats.clkchange_count) { + /* can not assume that boot timing matches dfs table even + if boot frequency matches one of the table nodes */ emc_get_timing(&start_timing); last_timing = &start_timing; } @@ -516,7 +549,7 @@ int tegra_emc_set_rate(unsigned long rate) clk_setting = tegra_emc_clk_sel[i].value; emc_set_clock(&tegra_emc_table[i], last_timing, clk_setting); - emc_last_sel = i; + emc_last_stats_update(i); pr_debug("%s: rate %lu setting 0x%x\n", __func__, rate, clk_setting); @@ -610,10 +643,16 @@ void tegra_init_emc(const struct tegra_emc_table *table, int table_size) int i; u32 reg, div_value; bool pllm_entry = false; + unsigned long boot_rate; const struct clk_mux_sel *sel; + emc_stats.clkchange_count = 0; + spin_lock_init(&emc_stats.spinlock); + emc_stats.last_update = get_jiffies_64(); + emc = tegra_get_clock_by_name("emc"); BUG_ON(!emc); + boot_rate = clk_get_rate(emc) / 1000; if (emc->parent != tegra_get_clock_by_name("pll_m")) { pr_warn("tegra: boot parent %s is not supported by EMC DFS\n", @@ -631,6 +670,9 @@ void tegra_init_emc(const struct tegra_emc_table *table, int table_size) if (!sel) continue; + if (table_rate == boot_rate) + emc_last_sel = i; + tegra_emc_clk_sel[i] = *sel; BUG_ON(div_value > (EMC_CLK_DIV_MASK >> EMC_CLK_DIV_SHIFT)); @@ -668,3 +710,64 @@ void tegra_init_emc(const struct tegra_emc_table *table, int table_size) pr_info("tegra: validated EMC DFS table\n"); tegra_emc_table = table; } + +#ifdef CONFIG_DEBUG_FS + +static struct dentry *emc_debugfs_root; + +static int emc_stats_show(struct seq_file *s, void *data) +{ + int i; + + emc_last_stats_update(TEGRA_EMC_TABLE_MAX_SIZE); + + seq_printf(s, "%-10s %-10s \n", "rate kHz", "time"); + for (i = 0; i < tegra_emc_table_size; i++) { + if (tegra_emc_clk_sel[i].input == NULL) + continue; /* invalid entry */ + + seq_printf(s, "%-10lu %-10llu \n", tegra_emc_table[i].rate, + cputime64_to_clock_t(emc_stats.time_at_clock[i])); + } + seq_printf(s, "%-15s %llu\n", "transitions:", + emc_stats.clkchange_count); + seq_printf(s, "%-15s %llu\n", "time-stamp:", + cputime64_to_clock_t(emc_stats.last_update)); + + return 0; +} + +static int emc_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, emc_stats_show, inode->i_private); +} + +static const struct file_operations emc_stats_fops = { + .open = emc_stats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init tegra_emc_debug_init(void) +{ + if (!tegra_emc_table) + return 0; + + emc_debugfs_root = debugfs_create_dir("tegra_emc", NULL); + if (!emc_debugfs_root) + return -ENOMEM; + + if (!debugfs_create_file( + "stats", S_IRUGO, emc_debugfs_root, NULL, &emc_stats_fops)) + goto err_out; + + return 0; + +err_out: + debugfs_remove_recursive(emc_debugfs_root); + return -ENOMEM; +} + +late_initcall(tegra_emc_debug_init); +#endif |