diff options
-rw-r--r-- | arch/arm/mach-tegra/Kconfig | 2 | ||||
-rw-r--r-- | arch/arm/mach-tegra/cpu-tegra3.c | 145 |
2 files changed, 137 insertions, 10 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig index d477116db830..9a105cdc35c0 100644 --- a/arch/arm/mach-tegra/Kconfig +++ b/arch/arm/mach-tegra/Kconfig @@ -226,7 +226,7 @@ config TEGRA_CLOCK_DEBUG_WRITE config TEGRA_AUTO_HOTPLUG bool "Enable automatic CPU hot-plugging" - depends on HOTPLUG_CPU && CPU_FREQ + depends on HOTPLUG_CPU && CPU_FREQ && !ARCH_CPU_PROBE_RELEASE default y help This option enables turning CPUs off/on and switching tegra diff --git a/arch/arm/mach-tegra/cpu-tegra3.c b/arch/arm/mach-tegra/cpu-tegra3.c index 6208e5fa9241..ac5699599270 100644 --- a/arch/arm/mach-tegra/cpu-tegra3.c +++ b/arch/arm/mach-tegra/cpu-tegra3.c @@ -29,6 +29,8 @@ #include <linux/err.h> #include <linux/io.h> #include <linux/cpu.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> #include "pm.h" @@ -54,6 +56,12 @@ module_param(idle_bottom_freq, uint, 0644); static unsigned int lpcpu_max_freq; +static struct { + cputime64_t time_up_total; + u64 last_update; + unsigned int up_down_count; +} hp_stats[CONFIG_NR_CPUS + 1]; /* Append LP CPU entry at the end */ + enum { TEGRA_HP_DISABLED = 0, TEGRA_HP_IDLE, @@ -109,6 +117,29 @@ static struct kernel_param_ops tegra_hp_state_ops = { module_param_cb(auto_hotplug, &tegra_hp_state_ops, &hp_state, 0644); +static void hp_stats_update(unsigned int cpu, bool up) +{ + u64 cur_jiffies = get_jiffies_64(); + bool was_up = hp_stats[cpu].up_down_count & 0x1; + + if (was_up) + hp_stats[cpu].time_up_total = cputime64_add( + hp_stats[cpu].time_up_total, cputime64_sub( + cur_jiffies, hp_stats[cpu].last_update)); + + if (was_up != up) { + hp_stats[cpu].up_down_count++; + if ((hp_stats[cpu].up_down_count & 0x1) != up) { + /* FIXME: sysfs user space CPU control breaks stats */ + pr_err("tegra hotplug stats out of sync with %s CPU%d", + (cpu < CONFIG_NR_CPUS) ? "G" : "LP", + (cpu < CONFIG_NR_CPUS) ? cpu : 0); + hp_stats[cpu].up_down_count ^= 0x1; + } + } + hp_stats[cpu].last_update = cur_jiffies; +} + static void tegra_auto_hotplug_work_func(struct work_struct *work) { bool up = false; @@ -131,10 +162,13 @@ static void tegra_auto_hotplug_work_func(struct work_struct *work) up = false; queue_delayed_work( hotplug_wq, &hotplug_work, down_delay); - } - else + hp_stats_update(cpu, false); + } else { tegra_cluster_control(0, TEGRA_POWER_CLUSTER_LP | - TEGRA_POWER_CLUSTER_IMMEDIATE); + TEGRA_POWER_CLUSTER_IMMEDIATE); + hp_stats_update(CONFIG_NR_CPUS, true); + hp_stats_update(0, false); + } break; case TEGRA_HP_UP: cpu = cpumask_next_zero(0, cpu_online_mask); @@ -142,6 +176,7 @@ static void tegra_auto_hotplug_work_func(struct work_struct *work) up = true; queue_delayed_work( hotplug_wq, &hotplug_work, up_delay); + hp_stats_update(cpu, true); } break; default: @@ -165,6 +200,8 @@ void tegra_auto_hotplug_governor(unsigned int cpu_freq) if (is_lp_cluster() && (cpu_freq > lpcpu_max_freq)) { tegra_cluster_control(0, TEGRA_POWER_CLUSTER_G | TEGRA_POWER_CLUSTER_IMMEDIATE); + hp_stats_update(CONFIG_NR_CPUS, false); + hp_stats_update(0, true); } switch (hp_state) { @@ -175,8 +212,7 @@ void tegra_auto_hotplug_governor(unsigned int cpu_freq) hp_state = TEGRA_HP_UP; queue_delayed_work( hotplug_wq, &hotplug_work, up_delay); - } - else if (cpu_freq <= idle_bottom_freq) { + } else if (cpu_freq <= idle_bottom_freq) { hp_state = TEGRA_HP_DOWN; queue_delayed_work( hotplug_wq, &hotplug_work, down_delay); @@ -187,8 +223,7 @@ void tegra_auto_hotplug_governor(unsigned int cpu_freq) hp_state = TEGRA_HP_UP; queue_delayed_work( hotplug_wq, &hotplug_work, up_delay); - } - else if (cpu_freq > idle_bottom_freq) { + } else if (cpu_freq > idle_bottom_freq) { hp_state = TEGRA_HP_IDLE; } break; @@ -197,8 +232,7 @@ void tegra_auto_hotplug_governor(unsigned int cpu_freq) hp_state = TEGRA_HP_DOWN; queue_delayed_work( hotplug_wq, &hotplug_work, down_delay); - } - else if (cpu_freq <= idle_top_freq) { + } else if (cpu_freq <= idle_top_freq) { hp_state = TEGRA_HP_IDLE; } break; @@ -212,6 +246,9 @@ void tegra_auto_hotplug_governor(unsigned int cpu_freq) int tegra_auto_hotplug_init(void) { + int i; + u64 cur_jiffies = get_jiffies_64(); + /* * Not bound to the issuer CPU (=> high-priority), has rescue worker * task, single-threaded, frrezeable. @@ -233,10 +270,100 @@ int tegra_auto_hotplug_init(void) pr_info("Tegra auto-hotplug initialized: %s\n", (hp_state == TEGRA_HP_DISABLED) ? "disabled" : "enabled"); + for (i = 0; i < CONFIG_NR_CPUS; i++) { + hp_stats[i].time_up_total = 0; + hp_stats[i].last_update = cur_jiffies; + + hp_stats[i].up_down_count = 0; + if (is_lp_cluster()) { + if (i == CONFIG_NR_CPUS) + hp_stats[i].up_down_count = 1; + } else { + if ((i < nr_cpu_ids) && cpu_online(i)) + hp_stats[i].up_down_count = 1; + } + } + return 0; } +#ifdef CONFIG_DEBUG_FS + +static struct dentry *hp_debugfs_root; + +static int hp_stats_show(struct seq_file *s, void *data) +{ + int i; + u64 cur_jiffies = get_jiffies_64(); + + mutex_lock(&tegra_hp_lock); + for (i = 0; i < CONFIG_NR_CPUS; i++) { + bool was_up = (hp_stats[i].up_down_count & 0x1); + hp_stats_update(i, was_up); + } + mutex_unlock(&tegra_hp_lock); + + seq_printf(s, "%-15s ", "cpu:"); + for (i = 0; i < CONFIG_NR_CPUS; i++) { + seq_printf(s, "G%-9d ", i); + } + seq_printf(s, "LP\n"); + + seq_printf(s, "%-15s ", "transitions:"); + for (i = 0; i <= CONFIG_NR_CPUS; i++) { + seq_printf(s, "%-10u ", hp_stats[i].up_down_count); + } + seq_printf(s, "\n"); + + seq_printf(s, "%-15s ", "time plugged:"); + for (i = 0; i <= CONFIG_NR_CPUS; i++) { + seq_printf(s, "%-10llu ", + cputime64_to_clock_t(hp_stats[i].time_up_total)); + } + seq_printf(s, "\n"); + + seq_printf(s, "%-15s %llu\n", "time-stamp:", + cputime64_to_clock_t(cur_jiffies)); + + return 0; +} + +static int hp_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, hp_stats_show, inode->i_private); +} + +static const struct file_operations hp_stats_fops = { + .open = hp_stats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init tegra_auto_hotplug_debug_init(void) +{ + hp_debugfs_root = debugfs_create_dir("tegra_hotplug", NULL); + if (!hp_debugfs_root) + return -ENOMEM; + + if (!debugfs_create_file( + "stats", S_IRUGO, hp_debugfs_root, NULL, &hp_stats_fops)) + goto err_out; + + return 0; + +err_out: + debugfs_remove_recursive(hp_debugfs_root); + return -ENOMEM; +} + +late_initcall(tegra_auto_hotplug_debug_init); +#endif + void tegra_auto_hotplug_exit(void) { destroy_workqueue(hotplug_wq); +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(hp_debugfs_root); +#endif } |