arm: tegra: power: Device agnostic thermal driver
[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
33 #include "clock.h"
34 #include "cpu-tegra.h"
35 #include "dvfs.h"
36
37 #define MAX_ZONES (16)
38
39 /* Thermal sysfs handles hysteresis */
40 #ifndef CONFIG_TEGRA_THERMAL_SYSFS
41 #define ALERT_HYSTERESIS_THROTTLE       1
42 #endif
43
44 #define ALERT_HYSTERESIS_EDP    3
45
46 #define THROTTLING_LIMIT        (85000)
47 #define MAX_LIMIT               (90000)
48
49 u8 thermal_zones[MAX_ZONES];
50 int thermal_zones_sz;
51 static int edp_thermal_zone_val = -1;
52
53 #ifndef CONFIG_TEGRA_THERMAL_SYSFS
54 static bool throttle_enb;
55 struct mutex mutex;
56 #endif
57
58
59 int __init tegra_thermal_init()
60 {
61         const struct tegra_edp_limits *z;
62         int zones_sz;
63         int i;
64
65 #ifndef CONFIG_TEGRA_THERMAL_SYSFS
66         mutex_init(&mutex);
67 #endif
68         tegra_get_cpu_edp_limits(&z, &zones_sz);
69         zones_sz = min(zones_sz, MAX_ZONES);
70
71         for (i = 0; i < zones_sz; i++)
72                 thermal_zones[i] = z[i].temperature;
73
74         thermal_zones_sz = zones_sz;
75
76         return 0;
77 }
78
79
80 #ifdef CONFIG_TEGRA_THERMAL_SYSFS
81
82 static int tegra_thermal_zone_bind(struct thermal_zone_device *thermal,
83                                 struct thermal_cooling_device *cdevice) {
84         /* Support only Thermal Throttling (1 trip) for now */
85         return thermal_zone_bind_cooling_device(thermal, 0, cdevice);
86 }
87
88 static int tegra_thermal_zone_unbind(struct thermal_zone_device *thermal,
89                                 struct thermal_cooling_device *cdevice) {
90         /* Support only Thermal Throttling (1 trip) for now */
91         return thermal_zone_unbind_cooling_device(thermal, 0, cdevice);
92 }
93
94 static int tegra_thermal_zone_get_temp(struct thermal_zone_device *thz,
95                                                 long *temp)
96 {
97         struct tegra_thermal *thermal = thz->devdata;
98         thermal->ops->get_temp(thermal->data, temp);
99
100         return 0;
101 }
102
103 static int tegra_thermal_zone_get_trip_type(
104                         struct thermal_zone_device *thermal,
105                         int trip,
106                         enum thermal_trip_type *type) {
107
108         /* Support only Thermal Throttling (1 trip) for now */
109         if (trip != 0)
110                 return -EINVAL;
111
112         *type = THERMAL_TRIP_PASSIVE;
113
114         return 0;
115 }
116
117 static int tegra_thermal_zone_get_trip_temp(struct thermal_zone_device *thermal,
118                                                 int trip,
119                                                 long *temp) {
120         /* Support only Thermal Throttling (1 trip) for now */
121         if (trip != 0)
122                 return -EINVAL;
123
124         *temp = THROTTLING_LIMIT;
125
126         return 0;
127 }
128
129 static struct thermal_zone_device_ops tegra_thermal_zone_ops = {
130         .bind = tegra_thermal_zone_bind,
131         .unbind = tegra_thermal_zone_unbind,
132         .get_temp = tegra_thermal_zone_get_temp,
133         .get_trip_type = tegra_thermal_zone_get_trip_type,
134         .get_trip_temp = tegra_thermal_zone_get_trip_temp,
135 };
136 #endif
137
138
139
140 struct tegra_thermal
141 *tegra_thermal_register(void *data, struct tegra_thermal_ops *thermal_ops)
142 {
143         long temp_milli;
144         struct tegra_thermal *thermal;
145 #ifdef CONFIG_THERMAL_SYSFS
146         struct thermal_zone_device *thz;
147 #endif
148
149         thermal = kzalloc(sizeof(struct tegra_thermal), GFP_KERNEL);
150         if (!thermal)
151                 return ERR_PTR(-ENOMEM);
152
153         thermal->ops = thermal_ops;
154         thermal->data = data;
155
156 #ifdef CONFIG_TEGRA_THERMAL_SYSFS
157         thz = thermal_zone_device_register("nct1008",
158                                         1, /* trips */
159                                         thermal,
160                                         &tegra_thermal_zone_ops,
161                                         2, /* tc1 */
162                                         1, /* tc2 */
163                                         2000, /* passive delay */
164                                         0); /* polling delay */
165
166         if (IS_ERR(thz)) {
167                 thz = NULL;
168                 kfree(thermal);
169                 return ERR_PTR(-ENODEV);
170         }
171
172         thermal->thz = thz;
173 #endif
174
175         thermal->ops->get_temp(thermal->data, &temp_milli);
176         tegra_edp_update_thermal_zone(MILLICELSIUS_TO_CELSIUS(temp_milli));
177
178         return thermal;
179 }
180
181 int tegra_thermal_unregister(struct tegra_thermal *thermal)
182 {
183 #ifdef CONFIG_TEGRA_THERMAL_SYSFS
184         if (thermal->thz)
185                 thermal_zone_device_unregister(thermal->thz);
186 #endif
187
188         kfree(thermal);
189
190         return 0;
191 }
192
193 /* The thermal sysfs handles notifying the throttling
194  * cooling device */
195 #ifndef CONFIG_TEGRA_THERMAL_SYSFS
196 static void tegra_therm_throttle(bool enable)
197 {
198         if (throttle_enb != enable) {
199                 mutex_lock(&mutex);
200                 tegra_throttling_enable(enable);
201                 throttle_enb = enable;
202                 mutex_unlock(&mutex);
203         }
204 }
205 #endif
206
207 int tegra_thermal_alert(struct tegra_thermal *thermal)
208 {
209         int err;
210         int hysteresis;
211         long temp, tzone1, tzone2;
212         int lo_limit = 0, hi_limit = 0;
213         int nentries = thermal_zones_sz;
214         int i;
215
216         err = thermal->ops->get_temp(thermal->data, &temp);
217         if (err) {
218                 pr_err("%s: get temp fail(%d)", __func__, err);
219                 return err;
220         }
221
222         hysteresis = ALERT_HYSTERESIS_EDP;
223
224 #ifndef CONFIG_TEGRA_THERMAL_SYSFS
225         if (temp >= THROTTLING_LIMIT) {
226                 /* start throttling */
227                 tegra_therm_throttle(true);
228                 hysteresis = ALERT_HYSTERESIS_THROTTLE;
229         } else if (temp <=
230                         (THROTTLING_LIMIT -
231                         ALERT_HYSTERESIS_THROTTLE)) {
232                 /* switch off throttling */
233                 tegra_therm_throttle(false);
234         }
235 #endif
236
237         if (temp < CELSIUS_TO_MILLICELSIUS(thermal_zones[0])) {
238                 lo_limit = 0;
239                 hi_limit = thermal_zones[0];
240         } else if (temp >=
241                         CELSIUS_TO_MILLICELSIUS(thermal_zones[nentries-1])) {
242                 lo_limit = thermal_zones[nentries-1] - hysteresis;
243                 hi_limit = MILLICELSIUS_TO_CELSIUS(MAX_LIMIT);
244         } else {
245                 for (i = 0; (i + 1) < nentries; i++) {
246                         tzone1 = thermal_zones[i];
247                         tzone2 = thermal_zones[i + 1];
248
249                         if (temp >= CELSIUS_TO_MILLICELSIUS(tzone1) &&
250                             temp < CELSIUS_TO_MILLICELSIUS(tzone2)) {
251                                 lo_limit = tzone1 - hysteresis;
252                                 hi_limit = tzone2;
253                                 break;
254                         }
255                 }
256         }
257
258         err = thermal->ops->set_limits(thermal->data, lo_limit, hi_limit);
259
260         if (err)
261                 return err;
262
263         /* inform edp governor */
264         if (edp_thermal_zone_val != temp)
265                 /*
266                  * FIXME: Move this direct tegra_ function call to be called
267                  * via a pointer in 'struct nct1008_data' (like 'alarm_fn')
268                  */
269                 tegra_edp_update_thermal_zone(MILLICELSIUS_TO_CELSIUS(temp));
270
271         edp_thermal_zone_val = temp;
272
273 #ifdef CONFIG_TEGRA_THERMAL_SYSFS
274         if (thermal->thz) {
275                 if (!thermal->thz->passive)
276                         thermal_zone_device_update(thermal->thz);
277         }
278 #endif
279
280         return 0;
281 }
282
283 int tegra_thermal_exit(void)
284 {
285         return 0;
286 }