From 899162f40e04883893aca49b90db7aafaf69fad0 Mon Sep 17 00:00:00 2001 From: Dmitriy Gruzman Date: Mon, 29 Nov 2010 15:06:26 -0600 Subject: [ARM] tegra: cpufreq: Support for tegra cpu throttling Change-Id: I28d69d22437b6ba2d22e4ce12746630786006071 Signed-off-by: Dmitriy Gruzman --- arch/arm/mach-tegra/cpu-tegra.c | 152 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 3 deletions(-) (limited to 'arch/arm/mach-tegra/cpu-tegra.c') diff --git a/arch/arm/mach-tegra/cpu-tegra.c b/arch/arm/mach-tegra/cpu-tegra.c index 0e0fd4d889bd..fcf54ede8542 100644 --- a/arch/arm/mach-tegra/cpu-tegra.c +++ b/arch/arm/mach-tegra/cpu-tegra.c @@ -29,12 +29,13 @@ #include #include #include +#include #include #include -/* Frequency table index must be sequential starting at 0 */ +/* Frequency table index must be sequential starting at 0 and frequencies must be ascending*/ static struct cpufreq_frequency_table freq_table[] = { { 0, 216000 }, { 1, 312000 }, @@ -47,15 +48,28 @@ static struct cpufreq_frequency_table freq_table[] = { { 8, CPUFREQ_TABLE_END }, }; +/* CPU frequency is gradually lowered when throttling is enabled */ +#define THROTTLE_START_INDEX 2 +#define THROTTLE_END_INDEX 6 +#define THROTTLE_DELAY msecs_to_jiffies(2000) +#define NO_DELAY msecs_to_jiffies(0) + #define NUM_CPUS 2 static struct clk *cpu_clk; static struct clk *emc_clk; +static struct workqueue_struct *workqueue; + static unsigned long target_cpu_speed[NUM_CPUS]; static DEFINE_MUTEX(tegra_cpu_lock); static bool is_suspended; +static DEFINE_MUTEX(throttling_lock); +static bool is_throttling; +static struct delayed_work throttle_work; + + int tegra_verify_speed(struct cpufreq_policy *policy) { return cpufreq_frequency_table_verify(policy, freq_table); @@ -131,6 +145,8 @@ static int tegra_target(struct cpufreq_policy *policy, { int idx; unsigned int freq; + unsigned int highest_speed; + unsigned int limit_when_throttling; int ret = 0; mutex_lock(&tegra_cpu_lock); @@ -147,13 +163,137 @@ static int tegra_target(struct cpufreq_policy *policy, target_cpu_speed[policy->cpu] = freq; - ret = tegra_update_cpu_speed(tegra_cpu_highest_speed()); + highest_speed = tegra_cpu_highest_speed(); + /* Do not go above this frequency when throttling */ + limit_when_throttling = freq_table[THROTTLE_START_INDEX].frequency; + + if (is_throttling && highest_speed > limit_when_throttling) { + if (tegra_getspeed(0) < limit_when_throttling) { + ret = tegra_update_cpu_speed(limit_when_throttling); + goto out; + } else { + ret = -EBUSY; + goto out; + } + } + + ret = tegra_update_cpu_speed(highest_speed); out: mutex_unlock(&tegra_cpu_lock); return ret; } +static bool tegra_throttling_needed(unsigned long *rate) +{ + unsigned int current_freq = tegra_getspeed(0); + int i; + + for (i = THROTTLE_END_INDEX; i >= THROTTLE_START_INDEX; i--) { + if (freq_table[i].frequency < current_freq) { + *rate = freq_table[i].frequency; + return true; + } + } + + return false; +} + +static void tegra_throttle_work_func(struct work_struct *work) +{ + unsigned long rate; + + mutex_lock(&tegra_cpu_lock); + + if (tegra_throttling_needed(&rate) && tegra_update_cpu_speed(rate) == 0) { + queue_delayed_work(workqueue, &throttle_work, THROTTLE_DELAY); + } + + mutex_unlock(&tegra_cpu_lock); +} + +/** + * tegra_throttling_enable + * This functions may sleep + */ +void tegra_throttling_enable(void) +{ + mutex_lock(&throttling_lock); + + if (!is_throttling) { + is_throttling = true; + queue_delayed_work(workqueue, &throttle_work, NO_DELAY); + } + + mutex_unlock(&throttling_lock); +} +EXPORT_SYMBOL_GPL(tegra_throttling_enable); + +/** + * tegra_throttling_disable + * This functions may sleep + */ +void tegra_throttling_disable(void) +{ + mutex_lock(&throttling_lock); + + if (is_throttling) { + cancel_delayed_work_sync(&throttle_work); + is_throttling = false; + } + + mutex_unlock(&throttling_lock); +} +EXPORT_SYMBOL_GPL(tegra_throttling_disable); + +#ifdef CONFIG_DEBUG_FS +static int throttle_debug_set(void *data, u64 val) +{ + if (val) { + tegra_throttling_enable(); + } else { + tegra_throttling_disable(); + } + + return 0; +} +static int throttle_debug_get(void *data, u64 *val) +{ + *val = (u64) is_throttling; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(throttle_fops, throttle_debug_get, throttle_debug_set, "%llu\n"); + +static struct dentry *cpu_tegra_debugfs_root; + +static int __init tegra_cpu_debug_init(void) +{ + cpu_tegra_debugfs_root = debugfs_create_dir("cpu-tegra", 0); + + if (!cpu_tegra_debugfs_root) + return -ENOMEM; + + if (!debugfs_create_file("throttle", 0644, cpu_tegra_debugfs_root, NULL, &throttle_fops)) + goto err_out; + + return 0; + +err_out: + debugfs_remove_recursive(cpu_tegra_debugfs_root); + return -ENOMEM; + +} + +static void __exit tegra_cpu_debug_exit(void) +{ + debugfs_remove_recursive(cpu_tegra_debugfs_root); +} + +late_initcall(tegra_cpu_debug_init); +module_exit(tegra_cpu_debug_exit); +#endif + static int tegra_pm_notify(struct notifier_block *nb, unsigned long event, void *dummy) { @@ -204,8 +344,10 @@ static int tegra_cpu_init(struct cpufreq_policy *policy) policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; cpumask_copy(policy->related_cpus, cpu_possible_mask); - if (policy->cpu == 0) + if (policy->cpu == 0) { + INIT_DELAYED_WORK(&throttle_work, tegra_throttle_work_func); register_pm_notifier(&tegra_cpu_pm_notifier); + } return 0; } @@ -236,11 +378,15 @@ static struct cpufreq_driver tegra_cpufreq_driver = { static int __init tegra_cpufreq_init(void) { + workqueue = create_singlethread_workqueue("cpu-tegra"); + if (!workqueue) + return -ENOMEM; return cpufreq_register_driver(&tegra_cpufreq_driver); } static void __exit tegra_cpufreq_exit(void) { + destroy_workqueue(workqueue); cpufreq_unregister_driver(&tegra_cpufreq_driver); } -- cgit v1.2.3