aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEduardo Valentin <eduardo.valentin@ti.com>2014-01-06 08:04:18 -0500
committerEduardo Valentin <edubezval@gmail.com>2014-11-20 09:43:16 -0500
commitf9df89d897ee0928aa4e03b30250e87f5d1e788a (patch)
tree8f18e403fc6ef4225b0b40c3abfcb32d7ecef888
parentfc14f9c1272f62c3e8d01300f52467c0d9af50f9 (diff)
thermal: introduce clock cooling device
This patch introduces a new thermal cooling device based on common clock framework. The original motivation to write this cooling device is to be able to cool down thermal zones using clocks that feed co-processors, such as GPUs, DSPs, Image Processing Co-processors, etc. But it is written in a way that it can be used on top of any clock. The implementation is pretty straight forward. The code creates a thermal cooling device based on a pair of a struct device and a clock name. The struct device is assumed to be usable by the OPP layer. The OPP layer is used as source of the list of possible frequencies. The (cpufreq) frequency table is then used as a map from frequencies to cooling states. Cooling states are indexes to the frequency table. The logic sits on top of common clock framework, specifically on clock pre notifications. Any PRE_RATE_CHANGE is hijacked, and the transition is only allowed when the new rate is within the thermal limit (cooling state -> freq). When a thermal cooling device state transition is requested, the clock is also checked to verify if the current clock rate is within the new thermal limit. Cc: Zhang Rui <rui.zhang@intel.com> Cc: Mike Turquette <mturquette@linaro.org> Cc: Nishanth Menon <nm@ti.com> Cc: Pavel Machek <pavel@ucw.cz> Cc: "Rafael J. Wysocki" <rjw@sisk.pl> Cc: Len Brown <len.brown@intel.com> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: linux-pm@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Eduardo Valentin <eduardo.valentin@ti.com> Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
-rw-r--r--drivers/thermal/Kconfig12
-rw-r--r--drivers/thermal/Makefile3
-rw-r--r--drivers/thermal/clock_cooling.c485
-rw-r--r--include/linux/clock_cooling.h65
4 files changed, 565 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index f554d25b4399..efbdb8c0bb1d 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -112,6 +112,18 @@ config CPU_THERMAL
112 112
113 If you want this support, you should say Y here. 113 If you want this support, you should say Y here.
114 114
115config CLOCK_THERMAL
116 bool "Generic clock cooling support"
117 depends on COMMON_CLK
118 depends on PM_OPP
119 help
120 This entry implements the generic clock cooling mechanism through
121 frequency clipping. Typically used to cool off co-processors. The
122 device that is configured to use this cooling mechanism will be
123 controlled to reduce clock frequency whenever temperature is high.
124
125 If you want this support, you should say Y here.
126
115config THERMAL_EMULATION 127config THERMAL_EMULATION
116 bool "Thermal emulation mode support" 128 bool "Thermal emulation mode support"
117 help 129 help
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 39c4fe87da2f..9d33532cae9f 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -18,6 +18,9 @@ thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o
18# cpufreq cooling 18# cpufreq cooling
19thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o 19thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
20 20
21# clock cooling
22thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o
23
21# platform thermal drivers 24# platform thermal drivers
22obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o 25obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
23obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o 26obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
diff --git a/drivers/thermal/clock_cooling.c b/drivers/thermal/clock_cooling.c
new file mode 100644
index 000000000000..1b4ff0f4c716
--- /dev/null
+++ b/drivers/thermal/clock_cooling.c
@@ -0,0 +1,485 @@
1/*
2 * drivers/thermal/clock_cooling.c
3 *
4 * Copyright (C) 2014 Eduardo Valentin <edubezval@gmail.com>
5 *
6 * Copyright (C) 2013 Texas Instruments Inc.
7 * Contact: Eduardo Valentin <eduardo.valentin@ti.com>
8 *
9 * Highly based on cpu_cooling.c.
10 * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
11 * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; version 2 of the License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 */
22#include <linux/clk.h>
23#include <linux/cpufreq.h>
24#include <linux/device.h>
25#include <linux/err.h>
26#include <linux/idr.h>
27#include <linux/mutex.h>
28#include <linux/pm_opp.h>
29#include <linux/slab.h>
30#include <linux/thermal.h>
31#include <linux/clock_cooling.h>
32
33/**
34 * struct clock_cooling_device - data for cooling device with clock
35 * @id: unique integer value corresponding to each clock_cooling_device
36 * registered.
37 * @dev: struct device pointer to the device being used to cool off using
38 * clock frequencies.
39 * @cdev: thermal_cooling_device pointer to keep track of the
40 * registered cooling device.
41 * @clk_rate_change_nb: reference to notifier block used to receive clock
42 * rate changes.
43 * @freq_table: frequency table used to keep track of available frequencies.
44 * @clock_state: integer value representing the current state of clock
45 * cooling devices.
46 * @clock_val: integer value representing the absolute value of the clipped
47 * frequency.
48 * @clk: struct clk reference used to enforce clock limits.
49 * @lock: mutex lock to protect this struct.
50 *
51 * This structure is required for keeping information of each
52 * clock_cooling_device registered. In order to prevent corruption of this a
53 * mutex @lock is used.
54 */
55struct clock_cooling_device {
56 int id;
57 struct device *dev;
58 struct thermal_cooling_device *cdev;
59 struct notifier_block clk_rate_change_nb;
60 struct cpufreq_frequency_table *freq_table;
61 unsigned long clock_state;
62 unsigned long clock_val;
63 struct clk *clk;
64 struct mutex lock; /* lock to protect the content of this struct */
65};
66#define to_clock_cooling_device(x) \
67 container_of(x, struct clock_cooling_device, clk_rate_change_nb)
68static DEFINE_IDR(clock_idr);
69static DEFINE_MUTEX(cooling_clock_lock);
70
71/**
72 * clock_cooling_get_idr - function to get an unique id.
73 * @id: int * value generated by this function.
74 *
75 * This function will populate @id with an unique
76 * id, using the idr API.
77 *
78 * Return: 0 on success, an error code on failure.
79 */
80static int clock_cooling_get_idr(int *id)
81{
82 int ret;
83
84 mutex_lock(&cooling_clock_lock);
85 ret = idr_alloc(&clock_idr, NULL, 0, 0, GFP_KERNEL);
86 mutex_unlock(&cooling_clock_lock);
87 if (unlikely(ret < 0))
88 return ret;
89 *id = ret;
90
91 return 0;
92}
93
94/**
95 * release_idr - function to free the unique id.
96 * @id: int value representing the unique id.
97 */
98static void release_idr(int id)
99{
100 mutex_lock(&cooling_clock_lock);
101 idr_remove(&clock_idr, id);
102 mutex_unlock(&cooling_clock_lock);
103}
104
105/* Below code defines functions to be used for clock as cooling device */
106
107enum clock_cooling_property {
108 GET_LEVEL,
109 GET_FREQ,
110 GET_MAXL,
111};
112
113/**
114 * clock_cooling_get_property - fetch a property of interest for a give cpu.
115 * @ccdev: clock cooling device reference
116 * @input: query parameter
117 * @output: query return
118 * @property: type of query (frequency, level, max level)
119 *
120 * This is the common function to
121 * 1. get maximum clock cooling states
122 * 2. translate frequency to cooling state
123 * 3. translate cooling state to frequency
124 * Note that the code may be not in good shape
125 * but it is written in this way in order to:
126 * a) reduce duplicate code as most of the code can be shared.
127 * b) make sure the logic is consistent when translating between
128 * cooling states and frequencies.
129 *
130 * Return: 0 on success, -EINVAL when invalid parameters are passed.
131 */
132static int clock_cooling_get_property(struct clock_cooling_device *ccdev,
133 unsigned long input,
134 unsigned long *output,
135 enum clock_cooling_property property)
136{
137 int i;
138 unsigned long max_level = 0, level = 0;
139 unsigned int freq = CPUFREQ_ENTRY_INVALID;
140 int descend = -1;
141 struct cpufreq_frequency_table *pos, *table = ccdev->freq_table;
142
143 if (!output)
144 return -EINVAL;
145
146 if (!table)
147 return -EINVAL;
148
149 cpufreq_for_each_valid_entry(pos, table) {
150 /* ignore duplicate entry */
151 if (freq == pos->frequency)
152 continue;
153
154 /* get the frequency order */
155 if (freq != CPUFREQ_ENTRY_INVALID && descend == -1)
156 descend = freq > pos->frequency;
157
158 freq = pos->frequency;
159 max_level++;
160 }
161
162 /* No valid cpu frequency entry */
163 if (max_level == 0)
164 return -EINVAL;
165
166 /* max_level is an index, not a counter */
167 max_level--;
168
169 /* get max level */
170 if (property == GET_MAXL) {
171 *output = max_level;
172 return 0;
173 }
174
175 if (property == GET_FREQ)
176 level = descend ? input : (max_level - input);
177
178 i = 0;
179 cpufreq_for_each_valid_entry(pos, table) {
180 /* ignore duplicate entry */
181 if (freq == pos->frequency)
182 continue;
183
184 /* now we have a valid frequency entry */
185 freq = pos->frequency;
186
187 if (property == GET_LEVEL && (unsigned int)input == freq) {
188 /* get level by frequency */
189 *output = descend ? i : (max_level - i);
190 return 0;
191 }
192 if (property == GET_FREQ && level == i) {
193 /* get frequency by level */
194 *output = freq;
195 return 0;
196 }
197 i++;
198 }
199
200 return -EINVAL;
201}
202
203/**
204 * clock_cooling_get_level - return the cooling level of given clock cooling.
205 * @cdev: reference of a thermal cooling device of used as clock cooling device
206 * @freq: the frequency of interest
207 *
208 * This function will match the cooling level corresponding to the
209 * requested @freq and return it.
210 *
211 * Return: The matched cooling level on success or THERMAL_CSTATE_INVALID
212 * otherwise.
213 */
214unsigned long clock_cooling_get_level(struct thermal_cooling_device *cdev,
215 unsigned long freq)
216{
217 struct clock_cooling_device *ccdev = cdev->devdata;
218 unsigned long val;
219
220 if (clock_cooling_get_property(ccdev, (unsigned long)freq, &val,
221 GET_LEVEL))
222 return THERMAL_CSTATE_INVALID;
223
224 return val;
225}
226EXPORT_SYMBOL_GPL(clock_cooling_get_level);
227
228/**
229 * clock_cooling_get_frequency - get the absolute value of frequency from level.
230 * @ccdev: clock cooling device reference
231 * @level: cooling level
232 *
233 * This function matches cooling level with frequency. Based on a cooling level
234 * of frequency, equals cooling state of cpu cooling device, it will return
235 * the corresponding frequency.
236 * e.g level=0 --> 1st MAX FREQ, level=1 ---> 2nd MAX FREQ, .... etc
237 *
238 * Return: 0 on error, the corresponding frequency otherwise.
239 */
240static unsigned long
241clock_cooling_get_frequency(struct clock_cooling_device *ccdev,
242 unsigned long level)
243{
244 int ret = 0;
245 unsigned long freq;
246
247 ret = clock_cooling_get_property(ccdev, level, &freq, GET_FREQ);
248 if (ret)
249 return 0;
250
251 return freq;
252}
253
254/**
255 * clock_cooling_apply - function to apply frequency clipping.
256 * @ccdev: clock_cooling_device pointer containing frequency clipping data.
257 * @cooling_state: value of the cooling state.
258 *
259 * Function used to make sure the clock layer is aware of current thermal
260 * limits. The limits are applied by updating the clock rate in case it is
261 * higher than the corresponding frequency based on the requested cooling_state.
262 *
263 * Return: 0 on success, an error code otherwise (-EINVAL in case wrong
264 * cooling state).
265 */
266static int clock_cooling_apply(struct clock_cooling_device *ccdev,
267 unsigned long cooling_state)
268{
269 unsigned long clip_freq, cur_freq;
270 int ret = 0;
271
272 /* Here we write the clipping */
273 /* Check if the old cooling action is same as new cooling action */
274 if (ccdev->clock_state == cooling_state)
275 return 0;
276
277 clip_freq = clock_cooling_get_frequency(ccdev, cooling_state);
278 if (!clip_freq)
279 return -EINVAL;
280
281 cur_freq = clk_get_rate(ccdev->clk);
282
283 mutex_lock(&ccdev->lock);
284 ccdev->clock_state = cooling_state;
285 ccdev->clock_val = clip_freq;
286 /* enforce clock level */
287 if (cur_freq > clip_freq)
288 ret = clk_set_rate(ccdev->clk, clip_freq);
289 mutex_unlock(&ccdev->lock);
290
291 return ret;
292}
293
294/**
295 * clock_cooling_clock_notifier - notifier callback on clock rate changes.
296 * @nb: struct notifier_block * with callback info.
297 * @event: value showing clock event for which this function invoked.
298 * @data: callback-specific data
299 *
300 * Callback to hijack the notification on clock transition.
301 * Every time there is a clock change, we intercept all pre change events
302 * and block the transition in case the new rate infringes thermal limits.
303 *
304 * Return: NOTIFY_DONE (success) or NOTIFY_BAD (new_rate > thermal limit).
305 */
306static int clock_cooling_clock_notifier(struct notifier_block *nb,
307 unsigned long event, void *data)
308{
309 struct clk_notifier_data *ndata = data;
310 struct clock_cooling_device *ccdev = to_clock_cooling_device(nb);
311
312 switch (event) {
313 case PRE_RATE_CHANGE:
314 /*
315 * checks on current state
316 * TODO: current method is not best we can find as it
317 * allows possibly voltage transitions, in case DVFS
318 * layer is also hijacking clock pre notifications.
319 */
320 if (ndata->new_rate > ccdev->clock_val)
321 return NOTIFY_BAD;
322 /* fall through */
323 case POST_RATE_CHANGE:
324 case ABORT_RATE_CHANGE:
325 default:
326 return NOTIFY_DONE;
327 }
328}
329
330/* clock cooling device thermal callback functions are defined below */
331
332/**
333 * clock_cooling_get_max_state - callback function to get the max cooling state.
334 * @cdev: thermal cooling device pointer.
335 * @state: fill this variable with the max cooling state.
336 *
337 * Callback for the thermal cooling device to return the clock
338 * max cooling state.
339 *
340 * Return: 0 on success, an error code otherwise.
341 */
342static int clock_cooling_get_max_state(struct thermal_cooling_device *cdev,
343 unsigned long *state)
344{
345 struct clock_cooling_device *ccdev = cdev->devdata;
346 unsigned long count = 0;
347 int ret;
348
349 ret = clock_cooling_get_property(ccdev, 0, &count, GET_MAXL);
350 if (!ret)
351 *state = count;
352
353 return ret;
354}
355
356/**
357 * clock_cooling_get_cur_state - function to get the current cooling state.
358 * @cdev: thermal cooling device pointer.
359 * @state: fill this variable with the current cooling state.
360 *
361 * Callback for the thermal cooling device to return the clock
362 * current cooling state.
363 *
364 * Return: 0 (success)
365 */
366static int clock_cooling_get_cur_state(struct thermal_cooling_device *cdev,
367 unsigned long *state)
368{
369 struct clock_cooling_device *ccdev = cdev->devdata;
370
371 *state = ccdev->clock_state;
372
373 return 0;
374}
375
376/**
377 * clock_cooling_set_cur_state - function to set the current cooling state.
378 * @cdev: thermal cooling device pointer.
379 * @state: set this variable to the current cooling state.
380 *
381 * Callback for the thermal cooling device to change the clock cooling
382 * current cooling state.
383 *
384 * Return: 0 on success, an error code otherwise.
385 */
386static int clock_cooling_set_cur_state(struct thermal_cooling_device *cdev,
387 unsigned long state)
388{
389 struct clock_cooling_device *clock_device = cdev->devdata;
390
391 return clock_cooling_apply(clock_device, state);
392}
393
394/* Bind clock callbacks to thermal cooling device ops */
395static struct thermal_cooling_device_ops const clock_cooling_ops = {
396 .get_max_state = clock_cooling_get_max_state,
397 .get_cur_state = clock_cooling_get_cur_state,
398 .set_cur_state = clock_cooling_set_cur_state,
399};
400
401/**
402 * clock_cooling_register - function to create clock cooling device.
403 * @dev: struct device pointer to the device used as clock cooling device.
404 * @clock_name: string containing the clock used as cooling mechanism.
405 *
406 * This interface function registers the clock cooling device with the name
407 * "thermal-clock-%x". The cooling device is based on clock frequencies.
408 * The struct device is assumed to be capable of DVFS transitions.
409 * The OPP layer is used to fetch and fill the available frequencies for
410 * the referred device. The ordered frequency table is used to control
411 * the clock cooling device cooling states and to limit clock transitions
412 * based on the cooling state requested by the thermal framework.
413 *
414 * Return: a valid struct thermal_cooling_device pointer on success,
415 * on failure, it returns a corresponding ERR_PTR().
416 */
417struct thermal_cooling_device *
418clock_cooling_register(struct device *dev, const char *clock_name)
419{
420 struct thermal_cooling_device *cdev;
421 struct clock_cooling_device *ccdev = NULL;
422 char dev_name[THERMAL_NAME_LENGTH];
423 int ret = 0;
424
425 ccdev = devm_kzalloc(dev, sizeof(*ccdev), GFP_KERNEL);
426 if (!ccdev)
427 return ERR_PTR(-ENOMEM);
428
429 ccdev->dev = dev;
430 ccdev->clk = devm_clk_get(dev, clock_name);
431 if (IS_ERR(ccdev->clk))
432 return ERR_CAST(ccdev->clk);
433
434 ret = clock_cooling_get_idr(&ccdev->id);
435 if (ret)
436 return ERR_PTR(-EINVAL);
437
438 snprintf(dev_name, sizeof(dev_name), "thermal-clock-%d", ccdev->id);
439
440 cdev = thermal_cooling_device_register(dev_name, ccdev,
441 &clock_cooling_ops);
442 if (IS_ERR(cdev)) {
443 release_idr(ccdev->id);
444 return ERR_PTR(-EINVAL);
445 }
446 ccdev->cdev = cdev;
447 ccdev->clk_rate_change_nb.notifier_call = clock_cooling_clock_notifier;
448
449 /* Assuming someone has already filled the opp table for this device */
450 ret = dev_pm_opp_init_cpufreq_table(dev, &ccdev->freq_table);
451 if (ret) {
452 release_idr(ccdev->id);
453 return ERR_PTR(ret);
454 }
455 ccdev->clock_state = 0;
456 ccdev->clock_val = clock_cooling_get_frequency(ccdev, 0);
457
458 clk_notifier_register(ccdev->clk, &ccdev->clk_rate_change_nb);
459
460 return cdev;
461}
462EXPORT_SYMBOL_GPL(clock_cooling_register);
463
464/**
465 * clock_cooling_unregister - function to remove clock cooling device.
466 * @cdev: thermal cooling device pointer.
467 *
468 * This interface function unregisters the "thermal-clock-%x" cooling device.
469 */
470void clock_cooling_unregister(struct thermal_cooling_device *cdev)
471{
472 struct clock_cooling_device *ccdev;
473
474 if (!cdev)
475 return;
476
477 ccdev = cdev->devdata;
478
479 clk_notifier_unregister(ccdev->clk, &ccdev->clk_rate_change_nb);
480 dev_pm_opp_free_cpufreq_table(ccdev->dev, &ccdev->freq_table);
481
482 thermal_cooling_device_unregister(ccdev->cdev);
483 release_idr(ccdev->id);
484}
485EXPORT_SYMBOL_GPL(clock_cooling_unregister);
diff --git a/include/linux/clock_cooling.h b/include/linux/clock_cooling.h
new file mode 100644
index 000000000000..4d1019d56f7f
--- /dev/null
+++ b/include/linux/clock_cooling.h
@@ -0,0 +1,65 @@
1/*
2 * linux/include/linux/clock_cooling.h
3 *
4 * Copyright (C) 2014 Eduardo Valentin <edubezval@gmail.com>
5 *
6 * Copyright (C) 2013 Texas Instruments Inc.
7 * Contact: Eduardo Valentin <eduardo.valentin@ti.com>
8 *
9 * Highly based on cpu_cooling.c.
10 * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
11 * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; version 2 of the License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 */
22
23#ifndef __CPU_COOLING_H__
24#define __CPU_COOLING_H__
25
26#include <linux/of.h>
27#include <linux/thermal.h>
28#include <linux/cpumask.h>
29
30#ifdef CONFIG_CLOCK_THERMAL
31/**
32 * clock_cooling_register - function to create clock cooling device.
33 * @dev: struct device pointer to the device used as clock cooling device.
34 * @clock_name: string containing the clock used as cooling mechanism.
35 */
36struct thermal_cooling_device *
37clock_cooling_register(struct device *dev, const char *clock_name);
38
39/**
40 * clock_cooling_unregister - function to remove clock cooling device.
41 * @cdev: thermal cooling device pointer.
42 */
43void clock_cooling_unregister(struct thermal_cooling_device *cdev);
44
45unsigned long clock_cooling_get_level(struct thermal_cooling_device *cdev,
46 unsigned long freq);
47#else /* !CONFIG_CLOCK_THERMAL */
48static inline struct thermal_cooling_device *
49clock_cooling_register(struct device *dev, const char *clock_name)
50{
51 return NULL;
52}
53static inline
54void clock_cooling_unregister(struct thermal_cooling_device *cdev)
55{
56}
57static inline
58unsigned long clock_cooling_get_level(struct thermal_cooling_device *cdev,
59 unsigned long freq)
60{
61 return THERMAL_CSTATE_INVALID;
62}
63#endif /* CONFIG_CLOCK_THERMAL */
64
65#endif /* __CPU_COOLING_H__ */