summaryrefslogtreecommitdiff
path: root/drivers/thermal/devfreq_cooling.c
blob: fb2c0654fa7de3d633ec27a0e67935c57c1c5c40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/*
 *  linux/drivers/thermal/devfreq_cooling.c
 *
 *  Copyright (C) 2015 Samsung Electronics Co., Ltd
 *  Author: Chanwoo Choi <cw00.choi@samsung.com>
 *
 *  This driver is based on drivers/thermal/cpu_cooling.c.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */
#include <linux/module.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include <linux/devfreq.h>
#include <linux/devfreq_cooling.h>

/*
 * Cooling state <-> devfreq frequency
 *
 * Cooling states are translated to frequencies throughout this driver and this
 * is the relation between them.
 *
 * Highest cooling state corresponds to lowest possible frequency.
 *
 * i.e.
 *	level 0 --> 1st Max Freq
 *	level 1 --> 2nd Max Freq
 *	...
 */

/**
 * struct devfreq_cooling_device - data for cooling device with devfreq
 * @cool_dev:	thermal_cooling_device pointer to keep track of the
 *		registered cooling device.
 * @devfreq:	the devfreq instance.
 * @cur_state:	integer value representing the current state of devfreq
 *		cooling	devices.
 * @max_state:	interger value representing the maximum state of devfreq
 *		cooling devices.
 *
 * This structure is required for keeping information of each registered
 * devfreq_cooling_device.
 */
struct devfreq_cooling_device {
	struct thermal_cooling_device *cool_dev;
	struct devfreq *devfreq;

	unsigned int cur_state;
	unsigned int max_state;

	unsigned int *freq_table;	/* In descending order */
};

/* devfreq cooling device callback functions are defined below */

/**
 * devfreq_get_max_state - callback function to get the max cooling state.
 * @cdev:	thermal cooling device pointer.
 * @state:	fill this variable with the max cooling state.
 *
 * Callback for the thermal cooling device to return the devfreq
 * max cooling state.
 *
 * Return: 0 on success, an error code otherwise.
 */
static int devfreq_get_max_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct devfreq_cooling_device *devfreq_device = cdev->devdata;

	*state = devfreq_device->max_state;

	return 0;
}

/**
 * devfreq_get_cur_state - callback function to get the current cooling state.
 * @cdev:	thermal cooling device pointer.
 * @state:	fill this variable with the current cooling state.
 *
 * Callback for the thermal cooling device to return the devfreq
 * current cooling state.
 *
 * Return: 0 on success, an error code otherwise.
 */
static int devfreq_get_cur_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct devfreq_cooling_device *devfreq_device = cdev->devdata;

	*state = devfreq_device->cur_state;

	return 0;
}

/**
 * devfreq_set_cur_state - callback function to set the current cooling state.
 * @cdev:	thermal cooling device pointer.
 * @state:	set this variable to the current cooling state.
 *
 * Callback for the thermal cooling device to change the devfreq
 * current cooling state.
 *
 * Return: 0 on success, an error code otherwise.
 */
static int devfreq_set_cur_state(struct thermal_cooling_device *cdev,
				 unsigned long state)
{
	struct devfreq_cooling_device *devfreq_dev = cdev->devdata;
	unsigned int limited_freq;

	/* Request state should be less than max_level */
	if (WARN_ON(state > devfreq_dev->max_state))
		return -EINVAL;

	/* Check if the old cooling action is same as new cooling action */
	if (devfreq_dev->cur_state == state)
		return 0;

	limited_freq = devfreq_dev->freq_table[state];

	devfreq_dev->cur_state = state;

	/* Set the limited frequency to maximum frequency of devfreq */
	devfreq_dev->devfreq->max_freq = limited_freq;
	update_devfreq(devfreq_dev->devfreq);

	return 0;
}

/* Bind devfreq callbacks to thermal cooling device ops */
static struct thermal_cooling_device_ops const devfreq_cooling_ops = {
	.get_max_state = devfreq_get_max_state,
	.get_cur_state = devfreq_get_cur_state,
	.set_cur_state = devfreq_set_cur_state,
};

/**
 * __devfreq_cooling_register - helper function to create devfreq cooling device
 * @np:		a valid struct device_node to the cooling device tree node
 * @devfreq:	the devfreq instance.
 *
 * This interface function registers the devfreq cooling device with the name
 * "thermal-devfreq-%x". This api can support multiple instances of devfreq
 * cooling devices. It also gives the opportunity to link the cooling device
 * with a device tree node, in order to bind it via the thermal DT code.
 *
 * Return: a valid struct thermal_cooling_device pointer on success,
 * on failure, it returns a corresponding ERR_PTR().
 */
static struct thermal_cooling_device *
__devfreq_cooling_register(struct device_node *np, struct devfreq *devfreq)
{
	struct thermal_cooling_device *cool_dev;
	struct devfreq_cooling_device *devfreq_dev;
	struct devfreq_dev_profile *devfreq_profile = devfreq->profile;
	struct dev_pm_opp *opp;
	static atomic_t devfreq_cooling_no = ATOMIC_INIT(-1);
	char dev_name[THERMAL_NAME_LENGTH];
	unsigned long freq;
	int i;

	devfreq_dev = kzalloc(sizeof(*devfreq_dev), GFP_KERNEL);
	if (!devfreq_dev)
		return ERR_PTR(-ENOMEM);

	rcu_read_lock();
	devfreq_dev->max_state = dev_pm_opp_get_opp_count(devfreq->dev.parent);
	if (devfreq_dev->max_state <= 0) {
		rcu_read_unlock();
		cool_dev = ERR_PTR(-EINVAL);
		goto free_cdev;
	}
	rcu_read_unlock();

	devfreq_dev->max_state -= 1;

	/*
	 * Use the freq_table of devfreq_dev_profile structure
	 * if the devfreq_dev_profile includes already filled frequency table.
	 */
	if (devfreq_profile->freq_table) {
		devfreq_dev->freq_table = devfreq_profile->freq_table;
		goto register_cooling_dev;
	}

	/* Allocate the frequency table and fill it */
	rcu_read_lock();
	devfreq_dev->freq_table = kzalloc(sizeof(*devfreq_dev->freq_table) *
					devfreq_dev->max_state + 1, GFP_KERNEL);
	if (!devfreq_dev->freq_table) {
		rcu_read_unlock();
		cool_dev = ERR_PTR(-ENOMEM);
		goto free_cdev;
	}

	freq = ULONG_MAX;
	for (i = 0; i <= devfreq_dev->max_state; i++, freq--) {
		opp = dev_pm_opp_find_freq_floor(devfreq->dev.parent,
						&freq);
		if (IS_ERR(opp)) {
			rcu_read_unlock();
			cool_dev = ERR_PTR(-EINVAL);
			goto free_table;
		}
		devfreq_dev->freq_table[i] = freq;
	}
	rcu_read_unlock();

register_cooling_dev:
	/* Register cooling device with devfreq device */
	snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d",
			atomic_inc_return(&devfreq_cooling_no));

	cool_dev = thermal_of_cooling_device_register(np, dev_name, devfreq_dev,
						      &devfreq_cooling_ops);
	if (IS_ERR(cool_dev))
		goto free_table;

	devfreq_dev->devfreq = devfreq;
	devfreq_dev->cool_dev = cool_dev;

	return cool_dev;

free_table:
	kfree(devfreq_dev->freq_table);
free_cdev:
	kfree(devfreq_dev);

	return cool_dev;
}

/**
 * of_devfreq_cooling_register - function to create devfreq cooling device.
 * @np:		a valid struct device_node to the cooling device tree node
 * @devfreq:	the devfreq instance.
 *
 * This interface function registers the devfreq cooling device with the name
 * "thermal-devfreq-%x". This api can support multiple instances of devfreq
 * cooling devices. Using this API, the devfreq cooling device will be
 * linked to the device tree node provided.
 *
 * Return: a valid struct thermal_cooling_device pointer on success,
 * on failure, it returns a corresponding ERR_PTR().
 */
struct thermal_cooling_device *
of_devfreq_cooling_register(struct device_node *np, struct devfreq *devfreq)
{
	if (!np || !devfreq)
		return ERR_PTR(-EINVAL);

	return __devfreq_cooling_register(np, devfreq);
}
EXPORT_SYMBOL_GPL(of_devfreq_cooling_register);

/**
 * devfreq_cooling_register - function to create devfreq cooling device.
 * @devfreq:	the devfreq instance.
 *
 * This interface function registers the devfreq cooling device with the name
 * "thermal-devfreq-%x". This api can support multiple instances of devfreq
 * cooling devices.
 *
 * Return: a valid struct thermal_cooling_device pointer on success,
 * on failure, it returns a corresponding ERR_PTR().
 */
struct thermal_cooling_device *
devfreq_cooling_register(struct devfreq *devfreq)
{
	if (!devfreq)
		return ERR_PTR(-EINVAL);

	return __devfreq_cooling_register(NULL, devfreq);
}
EXPORT_SYMBOL_GPL(devfreq_cooling_register);

/**
 * devfreq_cooling_unregister - function to remove devfreq cooling device.
 * @cdev: thermal cooling device pointer.
 *
 * This interface function unregisters the "thermal-devfreq-%x" cooling device.
 */
void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
{
	struct devfreq_cooling_device *devfreq_dev;

	if (!cdev)
		return;

	devfreq_dev = cdev->devdata;

	thermal_cooling_device_unregister(devfreq_dev->cool_dev);
	kfree(devfreq_dev->freq_table);
	kfree(devfreq_dev);
}
EXPORT_SYMBOL_GPL(devfreq_cooling_unregister);