diff options
Diffstat (limited to 'arch/arm/mach-tegra/tegra3_throttle.c')
-rw-r--r-- | arch/arm/mach-tegra/tegra3_throttle.c | 367 |
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 */ | ||
36 | static struct cpufreq_frequency_table *cpu_freq_table; | ||
37 | static struct mutex *cpu_throttle_lock; | ||
38 | |||
39 | static 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 | |||
56 | static int is_throttling; | ||
57 | static int throttle_index; | ||
58 | static struct delayed_work throttle_work; | ||
59 | static struct workqueue_struct *workqueue; | ||
60 | static DEFINE_MUTEX(tegra_throttle_lock); | ||
61 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
62 | static struct thermal_cooling_device *cdev; | ||
63 | #endif | ||
64 | |||
65 | static 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 | |||
77 | static 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 | */ | ||
108 | void 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 | } | ||
150 | EXPORT_SYMBOL_GPL(tegra_throttling_enable); | ||
151 | |||
152 | unsigned 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 | |||
159 | bool tegra_is_throttling(void) | ||
160 | { | ||
161 | return is_throttling; | ||
162 | } | ||
163 | |||
164 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
165 | |||
166 | static int | ||
167 | tegra_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 | |||
174 | static int | ||
175 | tegra_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 | |||
187 | static int | ||
188 | tegra_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 | |||
220 | struct 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 | |||
227 | int __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 | |||
269 | void 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 | |||
282 | static int throttle_debug_set(void *data, u64 val) | ||
283 | { | ||
284 | tegra_throttling_enable(val); | ||
285 | return 0; | ||
286 | } | ||
287 | static int throttle_debug_get(void *data, u64 *val) | ||
288 | { | ||
289 | *val = (u64) is_throttling; | ||
290 | return 0; | ||
291 | } | ||
292 | DEFINE_SIMPLE_ATTRIBUTE(throttle_fops, throttle_debug_get, throttle_debug_set, | ||
293 | "%llu\n"); | ||
294 | static 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 | |||
305 | static int table_open(struct inode *inode, struct file *file) | ||
306 | { | ||
307 | return single_open(file, table_show, inode->i_private); | ||
308 | } | ||
309 | |||
310 | static 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 | |||
345 | static 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 | |||
354 | int __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 | |||