thermal: exynos: Add support for instance based register/unregister
[deliverable/linux.git] / drivers / thermal / samsung / exynos_thermal_common.c
1 /*
2 * exynos_thermal_common.c - Samsung EXYNOS common thermal file
3 *
4 * Copyright (C) 2013 Samsung Electronics
5 * Amit Daniel Kachhap <amit.daniel@samsung.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
22
23 #include <linux/cpu_cooling.h>
24 #include <linux/err.h>
25 #include <linux/slab.h>
26 #include <linux/thermal.h>
27
28 #include "exynos_thermal_common.h"
29
30 struct exynos_thermal_zone {
31 enum thermal_device_mode mode;
32 struct thermal_zone_device *therm_dev;
33 struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE];
34 unsigned int cool_dev_size;
35 struct platform_device *exynos4_dev;
36 struct thermal_sensor_conf *sensor_conf;
37 bool bind;
38 };
39
40 /* Get mode callback functions for thermal zone */
41 static int exynos_get_mode(struct thermal_zone_device *thermal,
42 enum thermal_device_mode *mode)
43 {
44 struct exynos_thermal_zone *th_zone = thermal->devdata;
45 if (th_zone)
46 *mode = th_zone->mode;
47 return 0;
48 }
49
50 /* Set mode callback functions for thermal zone */
51 static int exynos_set_mode(struct thermal_zone_device *thermal,
52 enum thermal_device_mode mode)
53 {
54 struct exynos_thermal_zone *th_zone = thermal->devdata;
55 if (!th_zone) {
56 pr_notice("thermal zone not registered\n");
57 return 0;
58 }
59
60 mutex_lock(&thermal->lock);
61
62 if (mode == THERMAL_DEVICE_ENABLED &&
63 !th_zone->sensor_conf->trip_data.trigger_falling)
64 thermal->polling_delay = IDLE_INTERVAL;
65 else
66 thermal->polling_delay = 0;
67
68 mutex_unlock(&thermal->lock);
69
70 th_zone->mode = mode;
71 thermal_zone_device_update(thermal);
72 pr_info("thermal polling set for duration=%d msec\n",
73 thermal->polling_delay);
74 return 0;
75 }
76
77
78 /* Get trip type callback functions for thermal zone */
79 static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip,
80 enum thermal_trip_type *type)
81 {
82 switch (GET_ZONE(trip)) {
83 case MONITOR_ZONE:
84 case WARN_ZONE:
85 *type = THERMAL_TRIP_ACTIVE;
86 break;
87 case PANIC_ZONE:
88 *type = THERMAL_TRIP_CRITICAL;
89 break;
90 default:
91 return -EINVAL;
92 }
93 return 0;
94 }
95
96 /* Get trip temperature callback functions for thermal zone */
97 static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip,
98 unsigned long *temp)
99 {
100 struct exynos_thermal_zone *th_zone = thermal->devdata;
101
102 if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE))
103 return -EINVAL;
104
105 *temp = th_zone->sensor_conf->trip_data.trip_val[trip];
106 /* convert the temperature into millicelsius */
107 *temp = *temp * MCELSIUS;
108
109 return 0;
110 }
111
112 /* Get critical temperature callback functions for thermal zone */
113 static int exynos_get_crit_temp(struct thermal_zone_device *thermal,
114 unsigned long *temp)
115 {
116 int ret;
117 /* Panic zone */
118 ret = exynos_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp);
119 return ret;
120 }
121
122 /* Bind callback functions for thermal zone */
123 static int exynos_bind(struct thermal_zone_device *thermal,
124 struct thermal_cooling_device *cdev)
125 {
126 int ret = 0, i, tab_size, level;
127 struct freq_clip_table *tab_ptr, *clip_data;
128 struct exynos_thermal_zone *th_zone = thermal->devdata;
129 struct thermal_sensor_conf *data = th_zone->sensor_conf;
130
131 tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data;
132 tab_size = data->cooling_data.freq_clip_count;
133
134 if (tab_ptr == NULL || tab_size == 0)
135 return -EINVAL;
136
137 /* find the cooling device registered*/
138 for (i = 0; i < th_zone->cool_dev_size; i++)
139 if (cdev == th_zone->cool_dev[i])
140 break;
141
142 /* No matching cooling device */
143 if (i == th_zone->cool_dev_size)
144 return 0;
145
146 /* Bind the thermal zone to the cpufreq cooling device */
147 for (i = 0; i < tab_size; i++) {
148 clip_data = (struct freq_clip_table *)&(tab_ptr[i]);
149 level = cpufreq_cooling_get_level(0, clip_data->freq_clip_max);
150 if (level == THERMAL_CSTATE_INVALID)
151 return 0;
152 switch (GET_ZONE(i)) {
153 case MONITOR_ZONE:
154 case WARN_ZONE:
155 if (thermal_zone_bind_cooling_device(thermal, i, cdev,
156 level, 0)) {
157 pr_err("error binding cdev inst %d\n", i);
158 ret = -EINVAL;
159 }
160 th_zone->bind = true;
161 break;
162 default:
163 ret = -EINVAL;
164 }
165 }
166
167 return ret;
168 }
169
170 /* Unbind callback functions for thermal zone */
171 static int exynos_unbind(struct thermal_zone_device *thermal,
172 struct thermal_cooling_device *cdev)
173 {
174 int ret = 0, i, tab_size;
175 struct exynos_thermal_zone *th_zone = thermal->devdata;
176 struct thermal_sensor_conf *data = th_zone->sensor_conf;
177
178 if (th_zone->bind == false)
179 return 0;
180
181 tab_size = data->cooling_data.freq_clip_count;
182
183 if (tab_size == 0)
184 return -EINVAL;
185
186 /* find the cooling device registered*/
187 for (i = 0; i < th_zone->cool_dev_size; i++)
188 if (cdev == th_zone->cool_dev[i])
189 break;
190
191 /* No matching cooling device */
192 if (i == th_zone->cool_dev_size)
193 return 0;
194
195 /* Bind the thermal zone to the cpufreq cooling device */
196 for (i = 0; i < tab_size; i++) {
197 switch (GET_ZONE(i)) {
198 case MONITOR_ZONE:
199 case WARN_ZONE:
200 if (thermal_zone_unbind_cooling_device(thermal, i,
201 cdev)) {
202 pr_err("error unbinding cdev inst=%d\n", i);
203 ret = -EINVAL;
204 }
205 th_zone->bind = false;
206 break;
207 default:
208 ret = -EINVAL;
209 }
210 }
211 return ret;
212 }
213
214 /* Get temperature callback functions for thermal zone */
215 static int exynos_get_temp(struct thermal_zone_device *thermal,
216 unsigned long *temp)
217 {
218 struct exynos_thermal_zone *th_zone = thermal->devdata;
219 void *data;
220
221 if (!th_zone->sensor_conf) {
222 pr_info("Temperature sensor not initialised\n");
223 return -EINVAL;
224 }
225 data = th_zone->sensor_conf->private_data;
226 *temp = th_zone->sensor_conf->read_temperature(data);
227 /* convert the temperature into millicelsius */
228 *temp = *temp * MCELSIUS;
229 return 0;
230 }
231
232 /* Get temperature callback functions for thermal zone */
233 static int exynos_set_emul_temp(struct thermal_zone_device *thermal,
234 unsigned long temp)
235 {
236 void *data;
237 int ret = -EINVAL;
238 struct exynos_thermal_zone *th_zone = thermal->devdata;
239
240 if (!th_zone->sensor_conf) {
241 pr_info("Temperature sensor not initialised\n");
242 return -EINVAL;
243 }
244 data = th_zone->sensor_conf->private_data;
245 if (th_zone->sensor_conf->write_emul_temp)
246 ret = th_zone->sensor_conf->write_emul_temp(data, temp);
247 return ret;
248 }
249
250 /* Get the temperature trend */
251 static int exynos_get_trend(struct thermal_zone_device *thermal,
252 int trip, enum thermal_trend *trend)
253 {
254 int ret;
255 unsigned long trip_temp;
256
257 ret = exynos_get_trip_temp(thermal, trip, &trip_temp);
258 if (ret < 0)
259 return ret;
260
261 if (thermal->temperature >= trip_temp)
262 *trend = THERMAL_TREND_RAISE_FULL;
263 else
264 *trend = THERMAL_TREND_DROP_FULL;
265
266 return 0;
267 }
268 /* Operation callback functions for thermal zone */
269 static struct thermal_zone_device_ops const exynos_dev_ops = {
270 .bind = exynos_bind,
271 .unbind = exynos_unbind,
272 .get_temp = exynos_get_temp,
273 .set_emul_temp = exynos_set_emul_temp,
274 .get_trend = exynos_get_trend,
275 .get_mode = exynos_get_mode,
276 .set_mode = exynos_set_mode,
277 .get_trip_type = exynos_get_trip_type,
278 .get_trip_temp = exynos_get_trip_temp,
279 .get_crit_temp = exynos_get_crit_temp,
280 };
281
282 /*
283 * This function may be called from interrupt based temperature sensor
284 * when threshold is changed.
285 */
286 void exynos_report_trigger(struct thermal_sensor_conf *conf)
287 {
288 unsigned int i;
289 char data[10];
290 char *envp[] = { data, NULL };
291 struct exynos_thermal_zone *th_zone;
292
293 if (!conf || !conf->pzone_data) {
294 pr_err("Invalid temperature sensor configuration data\n");
295 return;
296 }
297
298 th_zone = conf->pzone_data;
299 if (th_zone->therm_dev)
300 return;
301
302 if (th_zone->bind == false) {
303 for (i = 0; i < th_zone->cool_dev_size; i++) {
304 if (!th_zone->cool_dev[i])
305 continue;
306 exynos_bind(th_zone->therm_dev,
307 th_zone->cool_dev[i]);
308 }
309 }
310
311 thermal_zone_device_update(th_zone->therm_dev);
312
313 mutex_lock(&th_zone->therm_dev->lock);
314 /* Find the level for which trip happened */
315 for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) {
316 if (th_zone->therm_dev->last_temperature <
317 th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS)
318 break;
319 }
320
321 if (th_zone->mode == THERMAL_DEVICE_ENABLED &&
322 !th_zone->sensor_conf->trip_data.trigger_falling) {
323 if (i > 0)
324 th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
325 else
326 th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
327 }
328
329 snprintf(data, sizeof(data), "%u", i);
330 kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp);
331 mutex_unlock(&th_zone->therm_dev->lock);
332 }
333
334 /* Register with the in-kernel thermal management */
335 int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
336 {
337 int ret;
338 struct cpumask mask_val;
339 struct exynos_thermal_zone *th_zone;
340
341 if (!sensor_conf || !sensor_conf->read_temperature) {
342 pr_err("Temperature sensor not initialised\n");
343 return -EINVAL;
344 }
345
346 th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL);
347 if (!th_zone)
348 return -ENOMEM;
349
350 th_zone->sensor_conf = sensor_conf;
351 cpumask_set_cpu(0, &mask_val);
352 th_zone->cool_dev[0] = cpufreq_cooling_register(&mask_val);
353 if (IS_ERR(th_zone->cool_dev[0])) {
354 pr_err("Failed to register cpufreq cooling device\n");
355 ret = -EINVAL;
356 goto err_unregister;
357 }
358 th_zone->cool_dev_size++;
359
360 th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name,
361 EXYNOS_ZONE_COUNT, 0, th_zone, &exynos_dev_ops, NULL, 0,
362 sensor_conf->trip_data.trigger_falling ?
363 0 : IDLE_INTERVAL);
364
365 if (IS_ERR(th_zone->therm_dev)) {
366 pr_err("Failed to register thermal zone device\n");
367 ret = PTR_ERR(th_zone->therm_dev);
368 goto err_unregister;
369 }
370 th_zone->mode = THERMAL_DEVICE_ENABLED;
371 sensor_conf->pzone_data = th_zone;
372
373 pr_info("Exynos: Kernel Thermal management registered\n");
374
375 return 0;
376
377 err_unregister:
378 exynos_unregister_thermal(sensor_conf);
379 return ret;
380 }
381
382 /* Un-Register with the in-kernel thermal management */
383 void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf)
384 {
385 int i;
386 struct exynos_thermal_zone *th_zone;
387
388 if (!sensor_conf || !sensor_conf->pzone_data) {
389 pr_err("Invalid temperature sensor configuration data\n");
390 return;
391 }
392
393 th_zone = sensor_conf->pzone_data;
394
395 if (th_zone->therm_dev)
396 thermal_zone_device_unregister(th_zone->therm_dev);
397
398 for (i = 0; i < th_zone->cool_dev_size; i++) {
399 if (th_zone->cool_dev[i])
400 cpufreq_cooling_unregister(th_zone->cool_dev[i]);
401 }
402
403 kfree(th_zone);
404 pr_info("Exynos: Kernel Thermal management unregistered\n");
405 }
This page took 0.039638 seconds and 6 git commands to generate.