diff options
author | Alex Frid <afrid@nvidia.com> | 2011-11-19 17:28:31 -0800 |
---|---|---|
committer | Varun Wadekar <vwadekar@nvidia.com> | 2011-12-15 12:06:15 +0530 |
commit | 051a0b2e40779eee1125308bac32e1957570ccd6 (patch) | |
tree | 543f5ff68118d020092e3b6c88d178810027a5b3 | |
parent | 7b6ad0c9924f567c8bd4dd7c2fbadf6ee8a960a8 (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.c | 10 | ||||
-rw-r--r-- | arch/arm/mach-tegra/dvfs.c | 127 | ||||
-rw-r--r-- | arch/arm/mach-tegra/dvfs.h | 21 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pm-t3.c | 31 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pm.c | 21 |
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; |