arm: tegra: power: Tegra Thermal refactoring
[linux-2.6.git] / arch / arm / mach-tegra / tegra3_thermal.c
1 /*
2  * arch/arm/mach-tegra/tegra3_thermal.c
3  *
4  * Copyright (C) 2010-2011 NVIDIA Corporation.
5  *
6  * This software is licensed under the terms of the GNU General Public
7  * License version 2, as published by the Free Software Foundation, and
8  * may be copied, distributed, and modified under those terms.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  */
16
17 #include <linux/kernel.h>
18 #include <linux/cpufreq.h>
19 #include <linux/delay.h>
20 #include <linux/mutex.h>
21 #include <linux/init.h>
22 #include <linux/err.h>
23 #include <linux/clk.h>
24 #include <linux/debugfs.h>
25 #include <linux/seq_file.h>
26 #include <linux/uaccess.h>
27 #include <linux/thermal.h>
28 #include <mach/thermal.h>
29 #include <mach/edp.h>
30 #include <linux/slab.h>
31
32 #include "clock.h"
33 #include "cpu-tegra.h"
34 #include "dvfs.h"
35
36 #define MAX_ZONES (16)
37
38 struct tegra_thermal {
39         struct tegra_thermal_data data;
40         struct tegra_thermal_device *device;
41 #ifdef CONFIG_TEGRA_THERMAL_SYSFS
42         struct thermal_zone_device *thz;
43 #endif
44 #ifdef CONFIG_TEGRA_EDP_LIMITS
45         int edp_thermal_zone_val;
46 #endif
47 };
48
49 static struct tegra_thermal thermal_state = {
50         .device = NULL,
51 #ifdef CONFIG_TEGRA_EDP_LIMITS
52         .edp_thermal_zone_val = -1,
53 #endif
54 };
55
56 #ifndef CONFIG_TEGRA_THERMAL_SYSFS
57 static bool throttle_enb;
58 struct mutex mutex;
59 #endif
60
61 #ifdef CONFIG_TEGRA_THERMAL_SYSFS
62
63 static int tegra_thermal_zone_bind(struct thermal_zone_device *thermal,
64                                 struct thermal_cooling_device *cdevice) {
65         /* Support only Thermal Throttling (1 trip) for now */
66         return thermal_zone_bind_cooling_device(thermal, 0, cdevice);
67 }
68
69 static int tegra_thermal_zone_unbind(struct thermal_zone_device *thermal,
70                                 struct thermal_cooling_device *cdevice) {
71         /* Support only Thermal Throttling (1 trip) for now */
72         return thermal_zone_unbind_cooling_device(thermal, 0, cdevice);
73 }
74
75 static int tegra_thermal_zone_get_temp(struct thermal_zone_device *thz,
76                                                 long *temp)
77 {
78         struct tegra_thermal *thermal = thz->devdata;
79         thermal->device->get_temp(thermal->device_client, temp);
80
81         return 0;
82 }
83
84 static int tegra_thermal_zone_get_trip_type(
85                         struct thermal_zone_device *thermal,
86                         int trip,
87                         enum thermal_trip_type *type) {
88
89         /* Support only Thermal Throttling (1 trip) for now */
90         if (trip != 0)
91                 return -EINVAL;
92
93         *type = THERMAL_TRIP_PASSIVE;
94
95         return 0;
96 }
97
98 static int tegra_thermal_zone_get_trip_temp(struct thermal_zone_device *thermal,
99                                                 int trip,
100                                                 long *temp) {
101         /* Support only Thermal Throttling (1 trip) for now */
102         if (trip != 0)
103                 return -EINVAL;
104
105         *temp = thermal->data.temp_throttle +
106                 thermal->data.temp_offset -
107                 thermal->device->offset;
108
109         return 0;
110 }
111
112 static struct thermal_zone_device_ops tegra_thermal_zone_ops = {
113         .bind = tegra_thermal_zone_bind,
114         .unbind = tegra_thermal_zone_unbind,
115         .get_temp = tegra_thermal_zone_get_temp,
116         .get_trip_type = tegra_thermal_zone_get_trip_type,
117         .get_trip_temp = tegra_thermal_zone_get_trip_temp,
118 };
119 #endif
120
121 /* The thermal sysfs handles notifying the throttling
122  * cooling device */
123 #ifndef CONFIG_TEGRA_THERMAL_SYSFS
124 static void tegra_therm_throttle(bool enable)
125 {
126         if (throttle_enb != enable) {
127                 mutex_lock(&mutex);
128                 tegra_throttling_enable(enable);
129                 throttle_enb = enable;
130                 mutex_unlock(&mutex);
131         }
132 }
133 #endif
134
135 void tegra_thermal_alert(void *data)
136 {
137         struct tegra_thermal *thermal = data;
138         int err;
139         long temp;
140         long lo_limit_throttle, hi_limit_throttle;
141         long lo_limit_edp = 0, hi_limit_edp = 0;
142         long tj_temp, tj_throttle_temp, tj_shutdown_temp;
143         int lo_limit_tj = 0, hi_limit_tj = 0;
144         int lo_limit = 0, hi_limit = 0;
145         const struct tegra_edp_limits *z;
146         int zones_sz;
147         int i;
148
149
150         if (thermal != &thermal_state)
151                 BUG();
152
153 #ifdef CONFIG_TEGRA_THERMAL_SYSFS
154         if (thermal->thz) {
155                 if (!thermal->thz->passive)
156                         thermal_zone_device_update(thermal->thz);
157         }
158 #endif
159
160         err = thermal->device->get_temp(thermal->device->data, &temp);
161         if (err) {
162                 pr_err("%s: get temp fail(%d)", __func__, err);
163                 return;
164         }
165
166         tj_temp = temp + thermal->device->offset;
167         tj_throttle_temp = thermal->data.temp_throttle
168                                 + thermal->data.temp_offset;
169         tj_shutdown_temp = thermal->data.temp_shutdown
170                                 + thermal->data.temp_offset;
171
172         if ((tegra_is_throttling() &&
173                 (tj_temp >
174                         (tj_throttle_temp - thermal->data.hysteresis_throttle)))
175                 || (tj_temp >= tj_throttle_temp)) {
176                 lo_limit_throttle = tj_throttle_temp -
177                                         thermal->data.hysteresis_throttle;
178                 hi_limit_throttle = tj_shutdown_temp;
179         } else {
180                 lo_limit_throttle = 0;
181                 hi_limit_throttle = tj_throttle_temp;
182         }
183
184 #ifdef CONFIG_TEGRA_EDP_LIMITS
185         tegra_get_cpu_edp_limits(&z, &zones_sz);
186
187 /* edp table based off of tdiode measurements */
188 #define EDP_TEMP(_index)        ((z[_index].temperature * 1000)\
189                                 + thermal->data.edp_offset)
190         if (tj_temp < EDP_TEMP(0)) {
191                 lo_limit_edp = 0;
192                 hi_limit_edp = EDP_TEMP(0);
193         } else if (tj_temp >= EDP_TEMP(zones_sz-1)) {
194                 lo_limit_edp = EDP_TEMP(zones_sz-1) -
195                                 thermal->data.hysteresis_edp;
196                 hi_limit_edp = tj_shutdown_temp;
197         } else {
198                 for (i = 0; (i + 1) < zones_sz; i++) {
199                         if ((tj_temp >= EDP_TEMP(i)) &&
200                                 (tj_temp < EDP_TEMP(i+1))) {
201                                 lo_limit_edp = EDP_TEMP(i) -
202                                                 thermal->data.hysteresis_edp;
203                                 hi_limit_edp = EDP_TEMP(i+1);
204                                 break;
205                         }
206                 }
207         }
208 #undef EDP_TEMP
209 #else
210         lo_limit_edp = 0;
211         hi_limit_edp = tj_shutdown_temp;
212 #endif
213
214         /* Get smallest window size */
215         lo_limit_tj = max(lo_limit_throttle, lo_limit_edp);
216         hi_limit_tj = min(hi_limit_throttle, hi_limit_edp);
217
218         /* Get corresponding device temps */
219         lo_limit = lo_limit_tj ? (lo_limit_tj - thermal->device->offset) : 0;
220         hi_limit = hi_limit_tj ? (hi_limit_tj - thermal->device->offset) : 0;
221
222         thermal->device->set_limits(thermal->device->data, lo_limit, hi_limit);
223
224 #ifndef CONFIG_TEGRA_THERMAL_SYSFS
225         if (tj_temp >= tj_throttle_temp) {
226                 /* start throttling */
227                 if (!tegra_is_throttling())
228                         tegra_therm_throttle(true);
229         } else if (tj_temp <=
230                         (tj_throttle_temp -
231                         thermal->data.hysteresis_throttle)) {
232                 /* switch off throttling */
233                 if (tegra_is_throttling())
234                         tegra_therm_throttle(false);
235         }
236 #endif
237
238 #ifdef CONFIG_TEGRA_EDP_LIMITS
239         /* inform edp governor */
240         if (thermal->edp_thermal_zone_val != tj_temp)
241                 tegra_edp_update_thermal_zone(
242                         (tj_temp - thermal->data.edp_offset)/1000);
243
244         thermal->edp_thermal_zone_val = tj_temp;
245 #endif
246 }
247
248 int tegra_thermal_set_device(struct tegra_thermal_device *device)
249 {
250 #ifdef CONFIG_THERMAL_SYSFS
251         struct thermal_zone_device *thz;
252 #endif
253
254         /* only support one device */
255         if (thermal_state.device)
256                 return -EINVAL;
257
258         thermal_state.device = device;
259
260 #ifdef CONFIG_TEGRA_THERMAL_SYSFS
261         thz = thermal_zone_device_register("thermal",
262                                         1, /* trips */
263                                         &thermal_state,
264                                         &tegra_thermal_zone_ops,
265                                         2, /* tc1 */
266                                         1, /* tc2 */
267                                         2000, /* passive delay */
268                                         0); /* polling delay */
269
270         if (IS_ERR(thz)) {
271                 thz = NULL;
272                 kfree(thermal);
273                 return -ENODEV;
274         }
275
276         thermal_state.thz = thz;
277 #endif
278
279         thermal_state.device->set_alert(thermal_state.device->data,
280                                         tegra_thermal_alert,
281                                         &thermal_state);
282         thermal_state.device->set_shutdown_temp(thermal_state.device->data,
283                                         thermal_state.data.temp_shutdown +
284                                         thermal_state.data.temp_offset -
285                                         thermal_state.device->offset);
286         /* initialize limits */
287         tegra_thermal_alert(&thermal_state);
288
289         return 0;
290 }
291
292 int __init tegra_thermal_init(struct tegra_thermal_data *data)
293 {
294 #ifndef CONFIG_TEGRA_THERMAL_SYSFS
295         mutex_init(&mutex);
296 #endif
297
298         memcpy(&thermal_state.data, data, sizeof(struct tegra_thermal_data));
299
300         return 0;
301 }
302
303 int tegra_thermal_exit(void)
304 {
305 #ifdef CONFIG_TEGRA_THERMAL_SYSFS
306         if (thermal->thz)
307                 thermal_zone_device_unregister(thermal->thz);
308 #endif
309
310         return 0;
311 }