summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-11-19 17:28:31 -0800
committerVarun Wadekar <vwadekar@nvidia.com>2011-12-15 12:06:15 +0530
commit051a0b2e40779eee1125308bac32e1957570ccd6 (patch)
tree543f5ff68118d020092e3b6c88d178810027a5b3
parent7b6ad0c9924f567c8bd4dd7c2fbadf6ee8a960a8 (diff)
ARM: tegra: dvfs: Add DVFS rails statistic
On Tegra3: complete account of in- and out-of-bound rails control. On Tegra2: out-of-bound vdd_cpu control in LP2 state is not accounted. Change-Id: Ib68cbbfe3e4f965e758aca17a0ba30277d530347 Signed-off-by: Alex Frid <afrid@nvidia.com> Reviewed-on: http://git-master/r/67340 Reviewed-by: Automatic_Commit_Validation_User Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>
-rw-r--r--arch/arm/mach-tegra/cpuidle-t3.c10
-rw-r--r--arch/arm/mach-tegra/dvfs.c127
-rw-r--r--arch/arm/mach-tegra/dvfs.h21
-rw-r--r--arch/arm/mach-tegra/pm-t3.c31
-rw-r--r--arch/arm/mach-tegra/pm.c21
5 files changed, 196 insertions, 14 deletions
diff --git a/arch/arm/mach-tegra/cpuidle-t3.c b/arch/arm/mach-tegra/cpuidle-t3.c
index e326a9b99875..2dedf2c2248a 100644
--- a/arch/arm/mach-tegra/cpuidle-t3.c
+++ b/arch/arm/mach-tegra/cpuidle-t3.c
@@ -233,6 +233,8 @@ static void tegra3_idle_enter_lp2_cpu_0(struct cpuidle_device *dev,
trace_power_start(POWER_CSTATE, 2, dev->cpu);
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
+ if (!is_lp_cluster())
+ tegra_dvfs_rail_off(tegra_cpu_rail, entry_time);
if (tegra_idle_lp2_last(sleep_time, 0) == 0)
sleep_completed = true;
@@ -242,7 +244,12 @@ static void tegra3_idle_enter_lp2_cpu_0(struct cpuidle_device *dev,
}
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
- }
+ exit_time = ktime_get();
+ if (!is_lp_cluster())
+ tegra_dvfs_rail_on(tegra_cpu_rail, exit_time);
+ } else
+ exit_time = ktime_get();
+
#ifdef CONFIG_SMP
if (!is_lp_cluster() && (num_online_cpus() > 1)) {
@@ -258,7 +265,6 @@ static void tegra3_idle_enter_lp2_cpu_0(struct cpuidle_device *dev,
}
#endif
- exit_time = ktime_get();
if (sleep_completed) {
/*
* Stayed in LP2 for the full time until the next tick,
diff --git a/arch/arm/mach-tegra/dvfs.c b/arch/arm/mach-tegra/dvfs.c
index c39a10a9e46b..22c666081c90 100644
--- a/arch/arm/mach-tegra/dvfs.c
+++ b/arch/arm/mach-tegra/dvfs.c
@@ -39,6 +39,11 @@
#include "clock.h"
#include "dvfs.h"
+#define DVFS_RAIL_STATS_BIN 25
+#define DVFS_RAIL_STATS_SCALE 2
+#define DVFS_RAIL_STATS_RANGE ((DVFS_RAIL_STATS_TOP_BIN - 1) * \
+ DVFS_RAIL_STATS_BIN / DVFS_RAIL_STATS_SCALE)
+
static LIST_HEAD(dvfs_rail_list);
static DEFINE_MUTEX(dvfs_lock);
static DEFINE_MUTEX(rail_disable_lock);
@@ -89,6 +94,74 @@ static int dvfs_solve_relationship(struct dvfs_relationship *rel)
return rel->solve(rel->from, rel->to);
}
+/* rail statistic - called during rail init, or under dfs_lock, or with
+ CPU0 only on-line, and interrupts disabled */
+static void dvfs_rail_stats_init(struct dvfs_rail *rail, int millivolts)
+{
+ rail->stats.last_update = ktime_get();
+ if (millivolts >= rail->min_millivolts) {
+ int i = 1 + (2 * (millivolts - rail->min_millivolts) *
+ DVFS_RAIL_STATS_SCALE + DVFS_RAIL_STATS_BIN) /
+ (2 * DVFS_RAIL_STATS_BIN);
+ rail->stats.last_index = min(i, DVFS_RAIL_STATS_TOP_BIN);
+ }
+
+ if (rail->max_millivolts >
+ rail->min_millivolts + DVFS_RAIL_STATS_RANGE)
+ pr_warn("tegra_dvfs: %s: stats above %d mV will be squashed\n",
+ rail->reg_id,
+ rail->min_millivolts + DVFS_RAIL_STATS_RANGE);
+}
+
+static void dvfs_rail_stats_update(
+ struct dvfs_rail *rail, int millivolts, ktime_t now)
+{
+ rail->stats.time_at_mv[rail->stats.last_index] = ktime_add(
+ rail->stats.time_at_mv[rail->stats.last_index], ktime_sub(
+ now, rail->stats.last_update));
+ rail->stats.last_update = now;
+
+ if (rail->stats.off)
+ return;
+
+ if (millivolts >= rail->min_millivolts) {
+ int i = 1 + (2 * (millivolts - rail->min_millivolts) *
+ DVFS_RAIL_STATS_SCALE + DVFS_RAIL_STATS_BIN) /
+ (2 * DVFS_RAIL_STATS_BIN);
+ rail->stats.last_index = min(i, DVFS_RAIL_STATS_TOP_BIN);
+ } else if (millivolts == 0)
+ rail->stats.last_index = 0;
+}
+
+static void dvfs_rail_stats_pause(struct dvfs_rail *rail,
+ ktime_t delta, bool on)
+{
+ int i = on ? rail->stats.last_index : 0;
+ rail->stats.time_at_mv[i] = ktime_add(rail->stats.time_at_mv[i], delta);
+}
+
+void tegra_dvfs_rail_off(struct dvfs_rail *rail, ktime_t now)
+{
+ if (rail) {
+ dvfs_rail_stats_update(rail, 0, now);
+ rail->stats.off = true;
+ }
+}
+
+void tegra_dvfs_rail_on(struct dvfs_rail *rail, ktime_t now)
+{
+ if (rail) {
+ rail->stats.off = false;
+ dvfs_rail_stats_update(rail, rail->millivolts, now);
+ }
+}
+
+void tegra_dvfs_rail_pause(struct dvfs_rail *rail, ktime_t delta, bool on)
+{
+ if (rail)
+ dvfs_rail_stats_pause(rail, delta, on);
+}
+
/* Sets the voltage on a dvfs rail to a specific value, and updates any
* rails that depend on this rail. */
static int dvfs_rail_set_voltage(struct dvfs_rail *rail, int millivolts)
@@ -148,6 +221,7 @@ static int dvfs_rail_set_voltage(struct dvfs_rail *rail, int millivolts)
}
rail->millivolts = rail->new_millivolts;
+ dvfs_rail_stats_update(rail, rail->millivolts, ktime_get());
/* After changing the voltage, tell each rail that depends
* on this rail that the voltage has changed.
@@ -242,6 +316,7 @@ static int dvfs_rail_connect_to_regulator(struct dvfs_rail *rail)
}
rail->millivolts = v / 1000;
rail->new_millivolts = rail->millivolts;
+ dvfs_rail_stats_init(rail, rail->millivolts);
return 0;
}
@@ -664,6 +739,53 @@ static const struct file_operations dvfs_tree_fops = {
.release = single_release,
};
+static int rail_stats_show(struct seq_file *s, void *data)
+{
+ int i;
+ struct dvfs_rail *rail;
+
+ seq_printf(s, "%-12s %-10s (bin: %d.%dmV)\n", "millivolts", "time",
+ DVFS_RAIL_STATS_BIN / DVFS_RAIL_STATS_SCALE,
+ ((DVFS_RAIL_STATS_BIN * 100) / DVFS_RAIL_STATS_SCALE) % 100);
+
+ mutex_lock(&dvfs_lock);
+
+ list_for_each_entry(rail, &dvfs_rail_list, node) {
+ seq_printf(s, "%s\n", rail->reg_id);
+ dvfs_rail_stats_update(rail, -1, ktime_get());
+
+ seq_printf(s, "%-12d %-10llu\n", 0,
+ cputime64_to_clock_t(msecs_to_jiffies(
+ ktime_to_ms(rail->stats.time_at_mv[0]))));
+
+ for (i = 1; i <= DVFS_RAIL_STATS_TOP_BIN; i++) {
+ ktime_t ktime_zero = ktime_set(0, 0);
+ if (ktime_equal(rail->stats.time_at_mv[i], ktime_zero))
+ continue;
+ seq_printf(s, "%-12d %-10llu\n",
+ rail->min_millivolts + (i - 1) *
+ DVFS_RAIL_STATS_BIN / DVFS_RAIL_STATS_SCALE,
+ cputime64_to_clock_t(msecs_to_jiffies(
+ ktime_to_ms(rail->stats.time_at_mv[i])))
+ );
+ }
+ }
+ mutex_unlock(&dvfs_lock);
+ return 0;
+}
+
+static int rail_stats_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, rail_stats_show, inode->i_private);
+}
+
+static const struct file_operations rail_stats_fops = {
+ .open = rail_stats_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
int __init dvfs_debugfs_init(struct dentry *clk_debugfs_root)
{
struct dentry *d;
@@ -673,6 +795,11 @@ int __init dvfs_debugfs_init(struct dentry *clk_debugfs_root)
if (!d)
return -ENOMEM;
+ d = debugfs_create_file("rails", S_IRUGO, clk_debugfs_root, NULL,
+ &rail_stats_fops);
+ if (!d)
+ return -ENOMEM;
+
return 0;
}
diff --git a/arch/arm/mach-tegra/dvfs.h b/arch/arm/mach-tegra/dvfs.h
index 60f8d51c6f1a..462eef645a4f 100644
--- a/arch/arm/mach-tegra/dvfs.h
+++ b/arch/arm/mach-tegra/dvfs.h
@@ -22,6 +22,7 @@
#define _TEGRA_DVFS_H_
#define MAX_DVFS_FREQS 18
+#define DVFS_RAIL_STATS_TOP_BIN 40
struct clk;
struct dvfs_rail;
@@ -43,6 +44,13 @@ struct dvfs_relationship {
bool solved_at_nominal;
};
+struct rail_stats {
+ ktime_t time_at_mv[DVFS_RAIL_STATS_TOP_BIN + 1];
+ ktime_t last_update;
+ int last_index;
+ bool off;
+};
+
struct dvfs_rail {
const char *reg_id;
int min_millivolts;
@@ -62,6 +70,7 @@ struct dvfs_rail {
int millivolts;
int new_millivolts;
bool suspended;
+ struct rail_stats stats;
};
struct dvfs {
@@ -88,6 +97,8 @@ struct dvfs {
struct list_head reg_node;
};
+extern struct dvfs_rail *tegra_cpu_rail;
+
#ifdef CONFIG_TEGRA_SILICON_PLATFORM
void tegra_soc_init_dvfs(void);
int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d);
@@ -98,6 +109,9 @@ void tegra_dvfs_add_relationships(struct dvfs_relationship *rels, int n);
void tegra_dvfs_rail_enable(struct dvfs_rail *rail);
void tegra_dvfs_rail_disable(struct dvfs_rail *rail);
bool tegra_dvfs_rail_updating(struct clk *clk);
+void tegra_dvfs_rail_off(struct dvfs_rail *rail, ktime_t now);
+void tegra_dvfs_rail_on(struct dvfs_rail *rail, ktime_t now);
+void tegra_dvfs_rail_pause(struct dvfs_rail *rail, ktime_t delta, bool on);
struct dvfs_rail *tegra_dvfs_get_rail_by_name(const char *reg_id);
int tegra_dvfs_predict_millivolts(struct clk *c, unsigned long rate);
void tegra_dvfs_core_cap_enable(bool enable);
@@ -121,6 +135,13 @@ static inline void tegra_dvfs_rail_disable(struct dvfs_rail *rail)
{}
static inline bool tegra_dvfs_rail_updating(struct clk *clk)
{ return false; }
+static inline void tegra_dvfs_rail_off(struct dvfs_rail *rail, ktime_t now)
+{}
+static inline void tegra_dvfs_rail_on(struct dvfs_rail *rail, ktime_t now)
+{}
+static inline void tegra_dvfs_rail_pause(
+ struct dvfs_rail *rail, ktime_t delta, bool on)
+{}
static inline struct dvfs_rail *tegra_dvfs_get_rail_by_name(const char *reg_id)
{ return NULL; }
static inline int tegra_dvfs_predict_millivolts(struct clk *c, unsigned long rate)
diff --git a/arch/arm/mach-tegra/pm-t3.c b/arch/arm/mach-tegra/pm-t3.c
index df20340ed508..23ff0fe4c97c 100644
--- a/arch/arm/mach-tegra/pm-t3.c
+++ b/arch/arm/mach-tegra/pm-t3.c
@@ -38,6 +38,7 @@
#include "pm.h"
#include "sleep.h"
#include "tegra3_emc.h"
+#include "dvfs.h"
#ifdef CONFIG_TEGRA_CLUSTER_CONTROL
#define CAR_CCLK_BURST_POLICY \
@@ -322,26 +323,32 @@ int tegra_cluster_control(unsigned int us, unsigned int flags)
if (flags & TEGRA_POWER_CLUSTER_IMMEDIATE)
us = 0;
- if (current_cluster != target_cluster && !timekeeping_suspended) {
- if (target_cluster == TEGRA_POWER_CLUSTER_G) {
- s64 t = ktime_to_us(ktime_sub(ktime_get(), last_g2lp));
- s64 t_off = tegra_cpu_power_off_time();
- if (t_off > t)
- udelay((unsigned int)(t_off - t));
- }
- else
- last_g2lp = ktime_get();
- }
-
DEBUG_CLUSTER(("%s(LP%d): %s->%s %s %s %d\r\n", __func__,
(flags & TEGRA_POWER_SDRAM_SELFREFRESH) ? 1 : 2,
is_lp_cluster() ? "LP" : "G",
(target_cluster == TEGRA_POWER_CLUSTER_G) ? "G" : "LP",
(flags & TEGRA_POWER_CLUSTER_IMMEDIATE) ? "immediate" : "",
(flags & TEGRA_POWER_CLUSTER_FORCE) ? "force" : "",
- us));
+ us));
local_irq_save(irq_flags);
+
+ if (current_cluster != target_cluster && !timekeeping_suspended) {
+ ktime_t now = ktime_get();
+ if (target_cluster == TEGRA_POWER_CLUSTER_G) {
+ s64 t = ktime_to_us(ktime_sub(now, last_g2lp));
+ s64 t_off = tegra_cpu_power_off_time();
+ if (t_off > t)
+ udelay((unsigned int)(t_off - t));
+
+ tegra_dvfs_rail_on(tegra_cpu_rail, now);
+
+ } else {
+ last_g2lp = now;
+ tegra_dvfs_rail_off(tegra_cpu_rail, now);
+ }
+ }
+
if (flags & TEGRA_POWER_SDRAM_SELFREFRESH) {
if (us)
tegra_lp2_set_trigger(us);
diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c
index ca3baada0aab..3fe9e2fd41c8 100644
--- a/arch/arm/mach-tegra/pm.c
+++ b/arch/arm/mach-tegra/pm.c
@@ -66,6 +66,7 @@
#include "reset.h"
#include "sleep.h"
#include "timer.h"
+#include "dvfs.h"
struct suspend_context {
/*
@@ -166,6 +167,8 @@ struct suspend_context tegra_sctx;
#define MC_SECURITY_SIZE 0x70
#define MC_SECURITY_CFG2 0x7c
+struct dvfs_rail *tegra_cpu_rail;
+static struct dvfs_rail *tegra_core_rail;
static struct clk *tegra_pclk;
static const struct tegra_suspend_platform_data *pdata;
static enum tegra_suspend_mode current_suspend_mode = TEGRA_SUSPEND_NONE;
@@ -751,12 +754,28 @@ static const char *lp_state[TEGRA_MAX_SUSPEND_MODE] = {
static int tegra_suspend_enter(suspend_state_t state)
{
int ret;
+ ktime_t delta;
+ struct timespec ts_entry, ts_exit;
if (pdata && pdata->board_suspend)
pdata->board_suspend(current_suspend_mode, TEGRA_SUSPEND_BEFORE_PERIPHERAL);
+ read_persistent_clock(&ts_entry);
+
ret = tegra_suspend_dram(current_suspend_mode, 0);
+ read_persistent_clock(&ts_exit);
+
+ if (timespec_compare(&ts_exit, &ts_entry) > 0) {
+ delta = timespec_to_ktime(timespec_sub(ts_exit, ts_entry));
+
+ tegra_dvfs_rail_pause(tegra_cpu_rail, delta, false);
+ if (current_suspend_mode == TEGRA_SUSPEND_LP0)
+ tegra_dvfs_rail_pause(tegra_core_rail, delta, false);
+ else
+ tegra_dvfs_rail_pause(tegra_core_rail, delta, true);
+ }
+
if (pdata && pdata->board_resume)
pdata->board_resume(current_suspend_mode, TEGRA_RESUME_AFTER_PERIPHERAL);
@@ -984,6 +1003,8 @@ void __init tegra_init_suspend(struct tegra_suspend_platform_data *plat)
u32 reg;
u32 mode;
+ tegra_cpu_rail = tegra_dvfs_get_rail_by_name("vdd_cpu");
+ tegra_core_rail = tegra_dvfs_get_rail_by_name("vdd_core");
tegra_pclk = clk_get_sys(NULL, "pclk");
BUG_ON(IS_ERR(tegra_pclk));
pdata = plat;