summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/tegra3_emc.c
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-03-08 10:35:31 -0800
committerDan Willemsen <dwillemsen@nvidia.com>2013-09-14 00:52:48 -0700
commit06fe5533a7b39b7ac594bfc451c20f9ba2c5a0ba (patch)
tree42d044298cea720612a4c209bb35a0a48f4f61ec /arch/arm/mach-tegra/tegra3_emc.c
parent6a080042c1e6d9c9e9d51245bbe0775e98bdf7d9 (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/arm/mach-tegra/tegra3_emc.c')
-rw-r--r--arch/arm/mach-tegra/tegra3_emc.c109
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 fa016cceda49..4673b00f1602 100644
--- a/arch/arm/mach-tegra/tegra3_emc.c
+++ b/arch/arm/mach-tegra/tegra3_emc.c
@@ -27,6 +27,10 @@
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/platform_data/tegra30_emc.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include <asm/cputime.h>
#include <mach/iomap.h>
@@ -180,7 +184,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 u32 dram_type;
@@ -188,6 +192,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 *clk_base = IO_ADDRESS(TEGRA_CLK_RESET_BASE);
static inline void emc_writel(int bank, u32 val, unsigned long addr)
@@ -200,6 +211,26 @@ static inline u32 emc_readl(int bank, unsigned long addr)
return readl(emc_regbases[bank] + 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] =
+ emc_stats.time_at_clock[emc_last_sel] +
+ (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;
@@ -498,7 +529,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;
}
@@ -507,7 +540,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);
@@ -603,10 +636,16 @@ static int tegra_emc_probe(struct platform_device *pdev)
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",
@@ -645,6 +684,9 @@ static int tegra_emc_probe(struct platform_device *pdev)
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));
@@ -697,3 +739,64 @@ static int __init tegra_init_emc(void)
return platform_driver_register(&tegra_emc_driver);
}
device_initcall(tegra_emc_init);
+
+#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