/* * arch/arm/mach-tegra/tegra2_throttle.c * * Copyright (C) 2010 Google, Inc. * * Author: * Colin Cross * Based on arch/arm/plat-omap/cpu-omap.c, (C) 2005 Nokia Corporation * * Copyright (C) 2010-2011 NVIDIA Corporation * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include "clock.h" #include "cpu-tegra.h" /* tegra throttling require frequencies in the table to be in ascending order */ static struct cpufreq_frequency_table *throttle_table; static struct mutex *cpu_throttle_lock; /* CPU frequency is gradually lowered when throttling is enabled */ #define THROTTLE_DELAY msecs_to_jiffies(2000) static int is_throttling; static int throttle_lowest_index; static int throttle_highest_index; static int throttle_index; static int throttle_next_index; static struct delayed_work throttle_work; static struct workqueue_struct *workqueue; static DEFINE_MUTEX(tegra_throttle_lock); static void tegra_throttle_work_func(struct work_struct *work) { unsigned int current_freq; mutex_lock(cpu_throttle_lock); if (!is_throttling) goto out; current_freq = tegra_getspeed(0); throttle_index = throttle_next_index; if (throttle_table[throttle_index].frequency < current_freq) tegra_cpu_set_speed_cap(NULL); if (throttle_index > throttle_lowest_index) { throttle_next_index = throttle_index - 1; queue_delayed_work(workqueue, &throttle_work, THROTTLE_DELAY); } out: mutex_unlock(cpu_throttle_lock); } /* * tegra_throttling_enable * This function may sleep */ void tegra_throttling_enable(bool enable) { mutex_lock(&tegra_throttle_lock); mutex_lock(cpu_throttle_lock); if (enable && !(is_throttling++)) { unsigned int current_freq = tegra_getspeed(0); for (throttle_index = throttle_highest_index; throttle_index >= throttle_lowest_index; throttle_index--) if (throttle_table[throttle_index].frequency < current_freq) break; throttle_index = max(throttle_index, throttle_lowest_index); throttle_next_index = throttle_index; queue_delayed_work(workqueue, &throttle_work, 0); } else if (!enable && is_throttling) { if (!(--is_throttling)) { /* restore speed requested by governor */ tegra_cpu_set_speed_cap(NULL); mutex_unlock(cpu_throttle_lock); cancel_delayed_work_sync(&throttle_work); mutex_unlock(&tegra_throttle_lock); return; } } mutex_unlock(cpu_throttle_lock); mutex_unlock(&tegra_throttle_lock); } EXPORT_SYMBOL_GPL(tegra_throttling_enable); unsigned int tegra_throttle_governor_speed(unsigned int requested_speed) { return is_throttling ? min(requested_speed, throttle_table[throttle_index].frequency) : requested_speed; } bool tegra_is_throttling(void) { return is_throttling; } int __init tegra_throttle_init(struct mutex *cpu_lock) { struct tegra_cpufreq_table_data *table_data = tegra_cpufreq_table_get(); if (IS_ERR_OR_NULL(table_data)) return -EINVAL; /* * High-priority, others flags default: not bound to a specific * CPU, has rescue worker task (in case of allocation deadlock, * etc.). Single-threaded. */ workqueue = alloc_workqueue("cpu-tegra", WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1); if (!workqueue) return -ENOMEM; INIT_DELAYED_WORK(&throttle_work, tegra_throttle_work_func); throttle_lowest_index = table_data->throttle_lowest_index; throttle_highest_index = table_data->throttle_highest_index; throttle_table = table_data->freq_table; cpu_throttle_lock = cpu_lock; return 0; } void tegra_throttle_exit(void) { destroy_workqueue(workqueue); } #ifdef CONFIG_DEBUG_FS static int throttle_debug_set(void *data, u64 val) { tegra_throttling_enable(val); 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"); int __init tegra_throttle_debug_init(struct dentry *cpu_tegra_debugfs_root) { if (!debugfs_create_file("throttle", 0644, cpu_tegra_debugfs_root, NULL, &throttle_fops)) return -ENOMEM; return 0; } #endif /* CONFIG_DEBUG_FS */