diff options
Diffstat (limited to 'arch/arm/mach-tegra/tegra3_thermal.c')
-rw-r--r-- | arch/arm/mach-tegra/tegra3_thermal.c | 544 |
1 files changed, 544 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/tegra3_thermal.c b/arch/arm/mach-tegra/tegra3_thermal.c new file mode 100644 index 00000000000..8ad7bd5b670 --- /dev/null +++ b/arch/arm/mach-tegra/tegra3_thermal.c | |||
@@ -0,0 +1,544 @@ | |||
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_device *device; | ||
40 | long temp_throttle_tj; | ||
41 | long temp_shutdown_tj; | ||
42 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
43 | struct thermal_zone_device *thz; | ||
44 | int tc1; | ||
45 | int tc2; | ||
46 | long passive_delay; | ||
47 | #else | ||
48 | long temp_throttle_low_tj; | ||
49 | #endif | ||
50 | #ifdef CONFIG_TEGRA_EDP_LIMITS | ||
51 | int edp_thermal_zone_val; | ||
52 | long edp_offset; | ||
53 | long hysteresis_edp; | ||
54 | #endif | ||
55 | struct mutex mutex; | ||
56 | }; | ||
57 | |||
58 | static struct tegra_thermal thermal_state = { | ||
59 | .device = NULL, | ||
60 | #ifdef CONFIG_TEGRA_EDP_LIMITS | ||
61 | .edp_thermal_zone_val = -1, | ||
62 | #endif | ||
63 | }; | ||
64 | |||
65 | #ifndef CONFIG_TEGRA_THERMAL_SYSFS | ||
66 | static bool throttle_enb; | ||
67 | #endif | ||
68 | |||
69 | #ifdef CONFIG_TEGRA_EDP_LIMITS | ||
70 | static inline long edp2tj(struct tegra_thermal *thermal, | ||
71 | long edp_temp) | ||
72 | { | ||
73 | return edp_temp + thermal->edp_offset; | ||
74 | } | ||
75 | |||
76 | static inline long tj2edp(struct tegra_thermal *thermal, | ||
77 | long temp_tj) | ||
78 | { | ||
79 | return temp_tj - thermal->edp_offset; | ||
80 | } | ||
81 | #endif | ||
82 | |||
83 | static inline long dev2tj(struct tegra_thermal_device *dev, | ||
84 | long dev_temp) | ||
85 | { | ||
86 | return dev_temp + dev->offset; | ||
87 | } | ||
88 | |||
89 | static inline long tj2dev(struct tegra_thermal_device *dev, | ||
90 | long tj_temp) | ||
91 | { | ||
92 | return tj_temp - dev->offset; | ||
93 | } | ||
94 | |||
95 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
96 | |||
97 | static int tegra_thermal_zone_bind(struct thermal_zone_device *thermal, | ||
98 | struct thermal_cooling_device *cdevice) { | ||
99 | /* Support only Thermal Throttling (1 trip) for now */ | ||
100 | return thermal_zone_bind_cooling_device(thermal, 0, cdevice); | ||
101 | } | ||
102 | |||
103 | static int tegra_thermal_zone_unbind(struct thermal_zone_device *thermal, | ||
104 | struct thermal_cooling_device *cdevice) { | ||
105 | /* Support only Thermal Throttling (1 trip) for now */ | ||
106 | return thermal_zone_unbind_cooling_device(thermal, 0, cdevice); | ||
107 | } | ||
108 | |||
109 | static int tegra_thermal_zone_get_temp(struct thermal_zone_device *thz, | ||
110 | long *temp) | ||
111 | { | ||
112 | struct tegra_thermal *thermal = thz->devdata; | ||
113 | thermal->device->get_temp(thermal->device->data, temp); | ||
114 | |||
115 | return 0; | ||
116 | } | ||
117 | |||
118 | static int tegra_thermal_zone_get_trip_type( | ||
119 | struct thermal_zone_device *thermal, | ||
120 | int trip, | ||
121 | enum thermal_trip_type *type) { | ||
122 | |||
123 | /* Support only Thermal Throttling (1 trip) for now */ | ||
124 | if (trip != 0) | ||
125 | return -EINVAL; | ||
126 | |||
127 | *type = THERMAL_TRIP_PASSIVE; | ||
128 | |||
129 | return 0; | ||
130 | } | ||
131 | |||
132 | static int tegra_thermal_zone_get_trip_temp(struct thermal_zone_device *thz, | ||
133 | int trip, | ||
134 | long *temp) { | ||
135 | struct tegra_thermal *thermal = thz->devdata; | ||
136 | |||
137 | /* Support only Thermal Throttling (1 trip) for now */ | ||
138 | if (trip != 0) | ||
139 | return -EINVAL; | ||
140 | |||
141 | *temp = tj2dev(thermal->device, thermal->temp_throttle_tj); | ||
142 | |||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | static struct thermal_zone_device_ops tegra_thermal_zone_ops = { | ||
147 | .bind = tegra_thermal_zone_bind, | ||
148 | .unbind = tegra_thermal_zone_unbind, | ||
149 | .get_temp = tegra_thermal_zone_get_temp, | ||
150 | .get_trip_type = tegra_thermal_zone_get_trip_type, | ||
151 | .get_trip_temp = tegra_thermal_zone_get_trip_temp, | ||
152 | }; | ||
153 | #endif | ||
154 | |||
155 | /* The thermal sysfs handles notifying the throttling | ||
156 | * cooling device */ | ||
157 | #ifndef CONFIG_TEGRA_THERMAL_SYSFS | ||
158 | static void tegra_therm_throttle(bool enable) | ||
159 | { | ||
160 | if (throttle_enb != enable) { | ||
161 | mutex_lock(&thermal_state.mutex); | ||
162 | tegra_throttling_enable(enable); | ||
163 | throttle_enb = enable; | ||
164 | mutex_unlock(&thermal_state.mutex); | ||
165 | } | ||
166 | } | ||
167 | #endif | ||
168 | |||
169 | /* Make sure this function remains stateless */ | ||
170 | void tegra_thermal_alert(void *data) | ||
171 | { | ||
172 | struct tegra_thermal *thermal = data; | ||
173 | int err; | ||
174 | long temp_dev, temp_tj; | ||
175 | long lo_limit_throttle_tj, hi_limit_throttle_tj; | ||
176 | long lo_limit_edp_tj = 0, hi_limit_edp_tj = 0; | ||
177 | long temp_low_dev, temp_low_tj; | ||
178 | int lo_limit_tj = 0, hi_limit_tj = 0; | ||
179 | #ifdef CONFIG_TEGRA_EDP_LIMITS | ||
180 | const struct tegra_edp_limits *z; | ||
181 | int zones_sz; | ||
182 | int i; | ||
183 | #endif | ||
184 | |||
185 | if (thermal != &thermal_state) | ||
186 | BUG(); | ||
187 | |||
188 | mutex_lock(&thermal_state.mutex); | ||
189 | |||
190 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
191 | if (thermal->thz) { | ||
192 | if (!thermal->thz->passive) | ||
193 | thermal_zone_device_update(thermal->thz); | ||
194 | } | ||
195 | #endif | ||
196 | |||
197 | err = thermal->device->get_temp(thermal->device->data, &temp_dev); | ||
198 | if (err) { | ||
199 | pr_err("%s: get temp fail(%d)", __func__, err); | ||
200 | goto done; | ||
201 | } | ||
202 | |||
203 | /* Convert all temps to tj and then do all work/logic in terms of | ||
204 | tj in order to avoid confusion */ | ||
205 | temp_tj = dev2tj(thermal->device, temp_dev); | ||
206 | thermal->device->get_temp_low(thermal->device, &temp_low_dev); | ||
207 | temp_low_tj = dev2tj(thermal->device, temp_low_dev); | ||
208 | |||
209 | lo_limit_throttle_tj = temp_low_tj; | ||
210 | hi_limit_throttle_tj = thermal->temp_throttle_tj; | ||
211 | |||
212 | #ifndef CONFIG_TEGRA_THERMAL_SYSFS | ||
213 | /* Check to see if we are currently throttling */ | ||
214 | if ((tegra_is_throttling() && | ||
215 | (temp_tj > thermal->temp_throttle_low_tj)) | ||
216 | || (temp_tj >= thermal->temp_throttle_tj)) { | ||
217 | lo_limit_throttle_tj = thermal->temp_throttle_low_tj; | ||
218 | hi_limit_throttle_tj = thermal->temp_shutdown_tj; | ||
219 | } | ||
220 | #else | ||
221 | if (temp_tj > thermal->temp_throttle_tj) { | ||
222 | lo_limit_throttle_tj = thermal->temp_throttle_tj; | ||
223 | hi_limit_throttle_tj = thermal->temp_shutdown_tj; | ||
224 | } | ||
225 | #endif | ||
226 | |||
227 | #ifdef CONFIG_TEGRA_EDP_LIMITS | ||
228 | tegra_get_cpu_edp_limits(&z, &zones_sz); | ||
229 | |||
230 | /* edp table based off of tdiode measurements */ | ||
231 | #define EDP_TEMP_TJ(_index) edp2tj(thermal, z[_index].temperature * 1000) | ||
232 | |||
233 | if (temp_tj < EDP_TEMP_TJ(0)) { | ||
234 | lo_limit_edp_tj = temp_low_tj; | ||
235 | hi_limit_edp_tj = EDP_TEMP_TJ(0); | ||
236 | } else if (temp_tj >= EDP_TEMP_TJ(zones_sz-1)) { | ||
237 | lo_limit_edp_tj = EDP_TEMP_TJ(zones_sz-1) - | ||
238 | thermal->hysteresis_edp; | ||
239 | hi_limit_edp_tj = thermal->temp_shutdown_tj; | ||
240 | } else { | ||
241 | for (i = 0; (i + 1) < zones_sz; i++) { | ||
242 | if ((temp_tj >= EDP_TEMP_TJ(i)) && | ||
243 | (temp_tj < EDP_TEMP_TJ(i+1))) { | ||
244 | lo_limit_edp_tj = EDP_TEMP_TJ(i) - | ||
245 | thermal->hysteresis_edp; | ||
246 | hi_limit_edp_tj = EDP_TEMP_TJ(i+1); | ||
247 | break; | ||
248 | } | ||
249 | } | ||
250 | } | ||
251 | #undef EDP_TEMP_TJ | ||
252 | #else | ||
253 | lo_limit_edp_tj = temp_low_tj; | ||
254 | hi_limit_edp_tj = thermal->temp_shutdown_tj; | ||
255 | #endif | ||
256 | |||
257 | /* Get smallest window size */ | ||
258 | lo_limit_tj = max(lo_limit_throttle_tj, lo_limit_edp_tj); | ||
259 | hi_limit_tj = min(hi_limit_throttle_tj, hi_limit_edp_tj); | ||
260 | |||
261 | thermal->device->set_limits(thermal->device->data, | ||
262 | tj2dev(thermal->device, lo_limit_tj), | ||
263 | tj2dev(thermal->device, hi_limit_tj)); | ||
264 | |||
265 | #ifndef CONFIG_TEGRA_THERMAL_SYSFS | ||
266 | if (temp_tj >= thermal->temp_throttle_tj) { | ||
267 | /* start throttling */ | ||
268 | if (!tegra_is_throttling()) | ||
269 | tegra_therm_throttle(true); | ||
270 | } else if (temp_tj <= thermal->temp_throttle_low_tj) { | ||
271 | /* switch off throttling */ | ||
272 | if (tegra_is_throttling()) | ||
273 | tegra_therm_throttle(false); | ||
274 | } | ||
275 | #endif | ||
276 | |||
277 | #ifdef CONFIG_TEGRA_EDP_LIMITS | ||
278 | /* inform edp governor */ | ||
279 | if (thermal->edp_thermal_zone_val != temp_tj) | ||
280 | tegra_edp_update_thermal_zone(tj2edp(thermal, temp_tj)/1000); | ||
281 | |||
282 | thermal->edp_thermal_zone_val = temp_tj; | ||
283 | #endif | ||
284 | |||
285 | done: | ||
286 | mutex_unlock(&thermal_state.mutex); | ||
287 | } | ||
288 | |||
289 | int tegra_thermal_set_device(struct tegra_thermal_device *device) | ||
290 | { | ||
291 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
292 | struct thermal_zone_device *thz; | ||
293 | #endif | ||
294 | |||
295 | /* only support one device */ | ||
296 | if (thermal_state.device) | ||
297 | return -EINVAL; | ||
298 | |||
299 | thermal_state.device = device; | ||
300 | |||
301 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
302 | thz = thermal_zone_device_register(thermal_state.device->name, | ||
303 | 1, /* trips */ | ||
304 | &thermal_state, | ||
305 | &tegra_thermal_zone_ops, | ||
306 | thermal_state.tc1, /* dT/dt */ | ||
307 | thermal_state.tc2, /* throttle */ | ||
308 | thermal_state.passive_delay, | ||
309 | 0); /* polling delay */ | ||
310 | |||
311 | if (IS_ERR(thz)) { | ||
312 | thz = NULL; | ||
313 | return -ENODEV; | ||
314 | } | ||
315 | |||
316 | thermal_state.thz = thz; | ||
317 | #endif | ||
318 | thermal_state.device->set_alert(thermal_state.device->data, | ||
319 | tegra_thermal_alert, | ||
320 | &thermal_state); | ||
321 | |||
322 | thermal_state.device->set_shutdown_temp(thermal_state.device->data, | ||
323 | tj2dev(device, thermal_state.temp_shutdown_tj)); | ||
324 | |||
325 | /* initialize limits */ | ||
326 | tegra_thermal_alert(&thermal_state); | ||
327 | |||
328 | return 0; | ||
329 | } | ||
330 | |||
331 | int __init tegra_thermal_init(struct tegra_thermal_data *data) | ||
332 | { | ||
333 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
334 | thermal_state.tc1 = data->tc1; | ||
335 | thermal_state.tc2 = data->tc2; | ||
336 | thermal_state.passive_delay = data->passive_delay; | ||
337 | #else | ||
338 | thermal_state.temp_throttle_low_tj = data->temp_throttle + | ||
339 | data->temp_offset - | ||
340 | data->hysteresis_throttle; | ||
341 | #endif | ||
342 | mutex_init(&thermal_state.mutex); | ||
343 | #ifdef CONFIG_TEGRA_EDP_LIMITS | ||
344 | thermal_state.edp_offset = data->edp_offset; | ||
345 | thermal_state.hysteresis_edp = data->hysteresis_edp; | ||
346 | #endif | ||
347 | thermal_state.temp_throttle_tj = data->temp_throttle + | ||
348 | data->temp_offset; | ||
349 | thermal_state.temp_shutdown_tj = data->temp_shutdown + | ||
350 | data->temp_offset; | ||
351 | |||
352 | return 0; | ||
353 | } | ||
354 | |||
355 | int tegra_thermal_exit(void) | ||
356 | { | ||
357 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
358 | if (thermal_state.thz) | ||
359 | thermal_zone_device_unregister(thermal_state.thz); | ||
360 | #endif | ||
361 | |||
362 | return 0; | ||
363 | } | ||
364 | |||
365 | #ifdef CONFIG_DEBUG_FS | ||
366 | |||
367 | static int tegra_thermal_throttle_temp_tj_set(void *data, u64 val) | ||
368 | { | ||
369 | #ifndef CONFIG_TEGRA_THERMAL_SYSFS | ||
370 | long throttle_hysteresis = thermal_state.temp_throttle_tj - | ||
371 | thermal_state.temp_throttle_low_tj; | ||
372 | #endif | ||
373 | |||
374 | mutex_lock(&thermal_state.mutex); | ||
375 | thermal_state.temp_throttle_tj = val; | ||
376 | #ifndef CONFIG_TEGRA_THERMAL_SYSFS | ||
377 | thermal_state.temp_throttle_low_tj = thermal_state.temp_throttle_tj - | ||
378 | throttle_hysteresis; | ||
379 | #endif | ||
380 | mutex_unlock(&thermal_state.mutex); | ||
381 | |||
382 | tegra_thermal_alert(&thermal_state); | ||
383 | |||
384 | return 0; | ||
385 | } | ||
386 | |||
387 | static int tegra_thermal_throttle_temp_tj_get(void *data, u64 *val) | ||
388 | { | ||
389 | *val = (u64)thermal_state.temp_throttle_tj; | ||
390 | return 0; | ||
391 | } | ||
392 | |||
393 | DEFINE_SIMPLE_ATTRIBUTE(throttle_temp_tj_fops, | ||
394 | tegra_thermal_throttle_temp_tj_get, | ||
395 | tegra_thermal_throttle_temp_tj_set, | ||
396 | "%llu\n"); | ||
397 | |||
398 | static int tegra_thermal_shutdown_temp_tj_set(void *data, u64 val) | ||
399 | { | ||
400 | thermal_state.temp_shutdown_tj = val; | ||
401 | |||
402 | if (thermal_state.device) | ||
403 | thermal_state.device->set_shutdown_temp( | ||
404 | thermal_state.device->data, | ||
405 | tj2dev(thermal_state.device, | ||
406 | thermal_state.temp_shutdown_tj)); | ||
407 | |||
408 | tegra_thermal_alert(&thermal_state); | ||
409 | |||
410 | return 0; | ||
411 | } | ||
412 | |||
413 | static int tegra_thermal_shutdown_temp_tj_get(void *data, u64 *val) | ||
414 | { | ||
415 | *val = (u64)thermal_state.temp_shutdown_tj; | ||
416 | return 0; | ||
417 | } | ||
418 | |||
419 | DEFINE_SIMPLE_ATTRIBUTE(shutdown_temp_tj_fops, | ||
420 | tegra_thermal_shutdown_temp_tj_get, | ||
421 | tegra_thermal_shutdown_temp_tj_set, | ||
422 | "%llu\n"); | ||
423 | |||
424 | |||
425 | static int tegra_thermal_temp_tj_get(void *data, u64 *val) | ||
426 | { | ||
427 | long temp_tj, temp_dev; | ||
428 | |||
429 | if (thermal_state.device) { | ||
430 | thermal_state.device->get_temp(thermal_state.device->data, | ||
431 | &temp_dev); | ||
432 | |||
433 | /* Convert all temps to tj and then do all work/logic in | ||
434 | terms of tj in order to avoid confusion */ | ||
435 | temp_tj = dev2tj(thermal_state.device, temp_dev); | ||
436 | } else { | ||
437 | temp_tj = -1; | ||
438 | } | ||
439 | |||
440 | *val = (u64)temp_tj; | ||
441 | |||
442 | return 0; | ||
443 | } | ||
444 | |||
445 | DEFINE_SIMPLE_ATTRIBUTE(temp_tj_fops, | ||
446 | tegra_thermal_temp_tj_get, | ||
447 | NULL, | ||
448 | "%llu\n"); | ||
449 | |||
450 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
451 | static int tegra_thermal_tc1_set(void *data, u64 val) | ||
452 | { | ||
453 | thermal_state.thz->tc1 = val; | ||
454 | return 0; | ||
455 | } | ||
456 | |||
457 | static int tegra_thermal_tc1_get(void *data, u64 *val) | ||
458 | { | ||
459 | *val = (u64)thermal_state.thz->tc1; | ||
460 | return 0; | ||
461 | } | ||
462 | |||
463 | DEFINE_SIMPLE_ATTRIBUTE(tc1_fops, | ||
464 | tegra_thermal_tc1_get, | ||
465 | tegra_thermal_tc1_set, | ||
466 | "%llu\n"); | ||
467 | |||
468 | static int tegra_thermal_tc2_set(void *data, u64 val) | ||
469 | { | ||
470 | thermal_state.thz->tc2 = val; | ||
471 | return 0; | ||
472 | } | ||
473 | |||
474 | static int tegra_thermal_tc2_get(void *data, u64 *val) | ||
475 | { | ||
476 | *val = (u64)thermal_state.thz->tc2; | ||
477 | return 0; | ||
478 | } | ||
479 | |||
480 | DEFINE_SIMPLE_ATTRIBUTE(tc2_fops, | ||
481 | tegra_thermal_tc2_get, | ||
482 | tegra_thermal_tc2_set, | ||
483 | "%llu\n"); | ||
484 | |||
485 | static int tegra_thermal_passive_delay_set(void *data, u64 val) | ||
486 | { | ||
487 | thermal_state.thz->passive_delay = val; | ||
488 | return 0; | ||
489 | } | ||
490 | |||
491 | static int tegra_thermal_passive_delay_get(void *data, u64 *val) | ||
492 | { | ||
493 | *val = (u64)thermal_state.thz->passive_delay; | ||
494 | return 0; | ||
495 | } | ||
496 | |||
497 | DEFINE_SIMPLE_ATTRIBUTE(passive_delay_fops, | ||
498 | tegra_thermal_passive_delay_get, | ||
499 | tegra_thermal_passive_delay_set, | ||
500 | "%llu\n"); | ||
501 | #endif | ||
502 | |||
503 | |||
504 | static struct dentry *thermal_debugfs_root; | ||
505 | |||
506 | static int __init tegra_thermal_debug_init(void) | ||
507 | { | ||
508 | thermal_debugfs_root = debugfs_create_dir("tegra_thermal", 0); | ||
509 | |||
510 | if (!debugfs_create_file("throttle_temp_tj", 0644, thermal_debugfs_root, | ||
511 | NULL, &throttle_temp_tj_fops)) | ||
512 | goto err_out; | ||
513 | |||
514 | if (!debugfs_create_file("shutdown_temp_tj", 0644, thermal_debugfs_root, | ||
515 | NULL, &shutdown_temp_tj_fops)) | ||
516 | goto err_out; | ||
517 | |||
518 | if (!debugfs_create_file("temp_tj", 0644, thermal_debugfs_root, | ||
519 | NULL, &temp_tj_fops)) | ||
520 | goto err_out; | ||
521 | |||
522 | #ifdef CONFIG_TEGRA_THERMAL_SYSFS | ||
523 | if (!debugfs_create_file("tc1", 0644, thermal_debugfs_root, | ||
524 | NULL, &tc1_fops)) | ||
525 | goto err_out; | ||
526 | |||
527 | if (!debugfs_create_file("tc2", 0644, thermal_debugfs_root, | ||
528 | NULL, &tc2_fops)) | ||
529 | goto err_out; | ||
530 | |||
531 | if (!debugfs_create_file("passive_delay", 0644, thermal_debugfs_root, | ||
532 | NULL, &passive_delay_fops)) | ||
533 | goto err_out; | ||
534 | #endif | ||
535 | |||
536 | return 0; | ||
537 | |||
538 | err_out: | ||
539 | debugfs_remove_recursive(thermal_debugfs_root); | ||
540 | return -ENOMEM; | ||
541 | } | ||
542 | |||
543 | late_initcall(tegra_thermal_debug_init); | ||
544 | #endif | ||