aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-tegra/tegra3_throttle.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-tegra/tegra3_throttle.c')
-rw-r--r--arch/arm/mach-tegra/tegra3_throttle.c367
1 files changed, 367 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/tegra3_throttle.c b/arch/arm/mach-tegra/tegra3_throttle.c
new file mode 100644
index 00000000000..f927be7800d
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra3_throttle.c
@@ -0,0 +1,367 @@
1/*
2 * arch/arm/mach-tegra/tegra3_throttle.c
3 *
4 * Copyright (c) 2011, NVIDIA Corporation.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#include <linux/kernel.h>
21#include <linux/cpufreq.h>
22#include <linux/delay.h>
23#include <linux/init.h>
24#include <linux/err.h>
25#include <linux/clk.h>
26#include <linux/debugfs.h>
27#include <linux/seq_file.h>
28#include <linux/uaccess.h>
29#include <linux/thermal.h>
30
31#include "clock.h"
32#include "cpu-tegra.h"
33#include "dvfs.h"
34
35/* tegra throttling require frequencies in the table to be in ascending order */
36static struct cpufreq_frequency_table *cpu_freq_table;
37static struct mutex *cpu_throttle_lock;
38
39static struct {
40 unsigned int cpu_freq;
41 int core_cap_level;
42 int ms;
43} throttle_table[] = {
44 { 0, 1000, 2000 }, /* placeholder for cpu floor rate */
45 { 640000, 1000, 2000 },
46 { 640000, 1000, 2000 },
47 { 640000, 1000, 2000 },
48 { 640000, 1000, 2000 },
49 { 640000, 1000, 2000 },
50 { 760000, 1000, 2000 },
51 { 760000, 1050, 2000 },
52 {1000000, 1050, 2000 },
53 {1000000, 1100, 2000 },
54};
55
56static int is_throttling;
57static int throttle_index;
58static struct delayed_work throttle_work;
59static struct workqueue_struct *workqueue;
60static DEFINE_MUTEX(tegra_throttle_lock);
61#ifdef CONFIG_TEGRA_THERMAL_SYSFS
62static struct thermal_cooling_device *cdev;
63#endif
64
65static unsigned int clip_to_table(unsigned int cpu_freq)
66{
67 int i;
68
69 for (i = 0; cpu_freq_table[i].frequency != CPUFREQ_TABLE_END; i++) {
70 if (cpu_freq_table[i].frequency > cpu_freq)
71 break;
72 }
73 i = (i == 0) ? 0 : i-1;
74 return cpu_freq_table[i].frequency;
75}
76
77static void tegra_throttle_work_func(struct work_struct *work)
78{
79 unsigned int cpu_freq;
80 int core_level;
81
82 mutex_lock(cpu_throttle_lock);
83 if (!is_throttling) {
84 mutex_unlock(cpu_throttle_lock);
85 return;
86 }
87
88 cpu_freq = tegra_getspeed(0);
89 throttle_index -= throttle_index ? 1 : 0;
90
91 core_level = throttle_table[throttle_index].core_cap_level;
92 if (throttle_table[throttle_index].cpu_freq < cpu_freq)
93 tegra_cpu_set_speed_cap(NULL);
94
95 if (throttle_index || (throttle_table[0].cpu_freq < cpu_freq))
96 queue_delayed_work(workqueue, &throttle_work,
97 msecs_to_jiffies(throttle_table[throttle_index].ms));
98
99 mutex_unlock(cpu_throttle_lock);
100
101 tegra_dvfs_core_cap_level_set(core_level);
102}
103
104/*
105 * tegra_throttling_enable
106 * This function may sleep
107 */
108void tegra_throttling_enable(bool enable)
109{
110 mutex_lock(&tegra_throttle_lock);
111 mutex_lock(cpu_throttle_lock);
112
113 if (enable && !(is_throttling++)) {
114 int core_level;
115 unsigned int cpu_freq = tegra_getspeed(0);
116 throttle_index = ARRAY_SIZE(throttle_table) - 1;
117
118 core_level = throttle_table[throttle_index].core_cap_level;
119 if (throttle_table[throttle_index].cpu_freq < cpu_freq)
120 tegra_cpu_set_speed_cap(NULL);
121
122 queue_delayed_work(workqueue, &throttle_work,
123 msecs_to_jiffies(throttle_table[throttle_index].ms));
124
125 mutex_unlock(cpu_throttle_lock);
126
127 tegra_dvfs_core_cap_level_set(core_level);
128 tegra_dvfs_core_cap_enable(true);
129
130 mutex_unlock(&tegra_throttle_lock);
131 return;
132 }
133
134 if (!enable && is_throttling) {
135 if (!(--is_throttling)) {
136 /* restore speed requested by governor */
137 tegra_cpu_set_speed_cap(NULL);
138 mutex_unlock(cpu_throttle_lock);
139
140 tegra_dvfs_core_cap_enable(false);
141 cancel_delayed_work_sync(&throttle_work);
142 mutex_unlock(&tegra_throttle_lock);
143 return;
144 }
145 }
146
147 mutex_unlock(cpu_throttle_lock);
148 mutex_unlock(&tegra_throttle_lock);
149}
150EXPORT_SYMBOL_GPL(tegra_throttling_enable);
151
152unsigned int tegra_throttle_governor_speed(unsigned int requested_speed)
153{
154 return is_throttling ?
155 min(requested_speed, throttle_table[throttle_index].cpu_freq) :
156 requested_speed;
157}
158
159bool tegra_is_throttling(void)
160{
161 return is_throttling;
162}
163
164#ifdef CONFIG_TEGRA_THERMAL_SYSFS
165
166static int
167tegra_throttle_get_max_state(struct thermal_cooling_device *cdev,
168 unsigned long *max_state)
169{
170 *max_state = ARRAY_SIZE(throttle_table);
171 return 0;
172}
173
174static int
175tegra_throttle_get_cur_state(struct thermal_cooling_device *cdev,
176 unsigned long *cur_state)
177{
178 mutex_lock(cpu_throttle_lock);
179 *cur_state = is_throttling ?
180 (ARRAY_SIZE(throttle_table) - throttle_index) :
181 0;
182 mutex_unlock(cpu_throttle_lock);
183
184 return 0;
185}
186
187static int
188tegra_throttle_set_cur_state(struct thermal_cooling_device *cdev,
189 unsigned long cur_state)
190{
191 int core_level;
192
193 mutex_lock(cpu_throttle_lock);
194 if (cur_state == 0) {
195 /* restore speed requested by governor */
196 if (is_throttling) {
197 tegra_dvfs_core_cap_enable(false);
198 is_throttling = false;
199 }
200
201 tegra_cpu_set_speed_cap(NULL);
202 } else {
203 if (!is_throttling) {
204 tegra_dvfs_core_cap_enable(true);
205 is_throttling = true;
206 }
207
208 throttle_index = ARRAY_SIZE(throttle_table) - cur_state;
209 core_level = throttle_table[throttle_index].core_cap_level;
210 tegra_dvfs_core_cap_level_set(core_level);
211
212 tegra_cpu_set_speed_cap(NULL);
213 }
214
215 mutex_unlock(cpu_throttle_lock);
216
217 return 0;
218}
219
220struct thermal_cooling_device_ops tegra_throttle_cooling_ops = {
221 .get_max_state = tegra_throttle_get_max_state,
222 .get_cur_state = tegra_throttle_get_cur_state,
223 .set_cur_state = tegra_throttle_set_cur_state,
224};
225#endif
226
227int __init tegra_throttle_init(struct mutex *cpu_lock)
228{
229 int i;
230 struct tegra_cpufreq_table_data *table_data =
231 tegra_cpufreq_table_get();
232 if (IS_ERR_OR_NULL(table_data))
233 return -EINVAL;
234
235 /*
236 * High-priority, others flags default: not bound to a specific
237 * CPU, has rescue worker task (in case of allocation deadlock,
238 * etc.). Single-threaded.
239 */
240 workqueue = alloc_workqueue("cpu-tegra",
241 WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
242 if (!workqueue)
243 return -ENOMEM;
244 INIT_DELAYED_WORK(&throttle_work, tegra_throttle_work_func);
245
246 cpu_throttle_lock = cpu_lock;
247 cpu_freq_table = table_data->freq_table;
248 throttle_table[0].cpu_freq =
249 cpu_freq_table[table_data->throttle_lowest_index].frequency;
250
251 for (i = 0; i < ARRAY_SIZE(throttle_table); i++) {
252 unsigned int cpu_freq = throttle_table[i].cpu_freq;
253 throttle_table[i].cpu_freq = clip_to_table(cpu_freq);
254 }
255
256#ifdef CONFIG_TEGRA_THERMAL_SYSFS
257 cdev = thermal_cooling_device_register("Throttle", NULL,
258 &tegra_throttle_cooling_ops);
259
260 if (IS_ERR(cdev)) {
261 cdev = NULL;
262 return -ENODEV;
263 }
264#endif
265
266 return 0;
267}
268
269void tegra_throttle_exit(void)
270{
271#ifdef CONFIG_TEGRA_THERMAL_SYSFS
272 if (cdev) {
273 thermal_cooling_device_unregister(cdev);
274 cdev = NULL;
275 }
276#endif
277 destroy_workqueue(workqueue);
278}
279
280#ifdef CONFIG_DEBUG_FS
281
282static int throttle_debug_set(void *data, u64 val)
283{
284 tegra_throttling_enable(val);
285 return 0;
286}
287static int throttle_debug_get(void *data, u64 *val)
288{
289 *val = (u64) is_throttling;
290 return 0;
291}
292DEFINE_SIMPLE_ATTRIBUTE(throttle_fops, throttle_debug_get, throttle_debug_set,
293 "%llu\n");
294static int table_show(struct seq_file *s, void *data)
295{
296 int i;
297
298 for (i = 0; i < ARRAY_SIZE(throttle_table); i++)
299 seq_printf(s, "[%d] = %7u %4d %5d\n",
300 i, throttle_table[i].cpu_freq,
301 throttle_table[i].core_cap_level, throttle_table[i].ms);
302 return 0;
303}
304
305static int table_open(struct inode *inode, struct file *file)
306{
307 return single_open(file, table_show, inode->i_private);
308}
309
310static ssize_t table_write(struct file *file,
311 const char __user *userbuf, size_t count, loff_t *ppos)
312{
313 char buf[80];
314 int table_idx;
315 unsigned int cpu_freq;
316 int core_cap_level;
317 int ms;
318
319 if (sizeof(buf) <= count)
320 return -EINVAL;
321
322 if (copy_from_user(buf, userbuf, count))
323 return -EFAULT;
324
325 /* terminate buffer and trim - white spaces may be appended
326 * at the end when invoked from shell command line */
327 buf[count] = '\0';
328 strim(buf);
329
330 if (sscanf(buf, "[%d] = %u %d %d",
331 &table_idx, &cpu_freq, &core_cap_level, &ms) != 4)
332 return -1;
333
334 if ((table_idx < 0) || (table_idx >= ARRAY_SIZE(throttle_table)))
335 return -EINVAL;
336
337 /* round new settings before updating table */
338 throttle_table[table_idx].cpu_freq = clip_to_table(cpu_freq);
339 throttle_table[table_idx].core_cap_level = (core_cap_level / 50) * 50;
340 throttle_table[table_idx].ms = jiffies_to_msecs(msecs_to_jiffies(ms));
341
342 return count;
343}
344
345static const struct file_operations table_fops = {
346 .open = table_open,
347 .read = seq_read,
348 .write = table_write,
349 .llseek = seq_lseek,
350 .release = single_release,
351};
352
353
354int __init tegra_throttle_debug_init(struct dentry *cpu_tegra_debugfs_root)
355{
356 if (!debugfs_create_file("throttle", 0644, cpu_tegra_debugfs_root,
357 NULL, &throttle_fops))
358 return -ENOMEM;
359
360 if (!debugfs_create_file("throttle_table", 0644, cpu_tegra_debugfs_root,
361 NULL, &table_fops))
362 return -ENOMEM;
363
364 return 0;
365}
366#endif /* CONFIG_DEBUG_FS */
367