Commit | Line | Data |
---|---|---|
65b6d57c WN |
1 | /* |
2 | * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. | |
3 | * | |
4 | * Author: | |
5 | * Mikko Perttunen <mperttunen@nvidia.com> | |
6 | * | |
7 | * This software is licensed under the terms of the GNU General Public | |
8 | * License version 2, as published by the Free Software Foundation, and | |
9 | * may be copied, distributed, and modified under those terms. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | */ | |
17 | ||
d753b22d | 18 | #include <linux/debugfs.h> |
65b6d57c WN |
19 | #include <linux/bitops.h> |
20 | #include <linux/clk.h> | |
21 | #include <linux/delay.h> | |
22 | #include <linux/err.h> | |
23 | #include <linux/interrupt.h> | |
24 | #include <linux/io.h> | |
25 | #include <linux/module.h> | |
26 | #include <linux/of.h> | |
27 | #include <linux/platform_device.h> | |
28 | #include <linux/reset.h> | |
29 | #include <linux/thermal.h> | |
30 | ||
31 | #include <dt-bindings/thermal/tegra124-soctherm.h> | |
32 | ||
33 | #include "soctherm.h" | |
34 | ||
35 | #define SENSOR_CONFIG0 0 | |
36 | #define SENSOR_CONFIG0_STOP BIT(0) | |
65b6d57c | 37 | #define SENSOR_CONFIG0_CPTR_OVER BIT(2) |
d753b22d WN |
38 | #define SENSOR_CONFIG0_OVER BIT(3) |
39 | #define SENSOR_CONFIG0_TCALC_OVER BIT(4) | |
40 | #define SENSOR_CONFIG0_TALL_MASK (0xfffff << 8) | |
41 | #define SENSOR_CONFIG0_TALL_SHIFT 8 | |
65b6d57c WN |
42 | |
43 | #define SENSOR_CONFIG1 4 | |
d753b22d | 44 | #define SENSOR_CONFIG1_TSAMPLE_MASK 0x3ff |
65b6d57c | 45 | #define SENSOR_CONFIG1_TSAMPLE_SHIFT 0 |
d753b22d | 46 | #define SENSOR_CONFIG1_TIDDQ_EN_MASK (0x3f << 15) |
65b6d57c | 47 | #define SENSOR_CONFIG1_TIDDQ_EN_SHIFT 15 |
d753b22d | 48 | #define SENSOR_CONFIG1_TEN_COUNT_MASK (0x3f << 24) |
65b6d57c WN |
49 | #define SENSOR_CONFIG1_TEN_COUNT_SHIFT 24 |
50 | #define SENSOR_CONFIG1_TEMP_ENABLE BIT(31) | |
51 | ||
52 | /* | |
53 | * SENSOR_CONFIG2 is defined in soctherm.h | |
54 | * because, it will be used by tegra_soctherm_fuse.c | |
55 | */ | |
56 | ||
d753b22d WN |
57 | #define SENSOR_STATUS0 0xc |
58 | #define SENSOR_STATUS0_VALID_MASK BIT(31) | |
59 | #define SENSOR_STATUS0_CAPTURE_MASK 0xffff | |
60 | ||
61 | #define SENSOR_STATUS1 0x10 | |
62 | #define SENSOR_STATUS1_TEMP_VALID_MASK BIT(31) | |
63 | #define SENSOR_STATUS1_TEMP_MASK 0xffff | |
64 | ||
65b6d57c WN |
65 | #define READBACK_VALUE_MASK 0xff00 |
66 | #define READBACK_VALUE_SHIFT 8 | |
67 | #define READBACK_ADD_HALF BIT(7) | |
68 | #define READBACK_NEGATE BIT(0) | |
69 | ||
70 | /* get val from register(r) mask bits(m) */ | |
71 | #define REG_GET_MASK(r, m) (((r) & (m)) >> (ffs(m) - 1)) | |
72 | /* set val(v) to mask bits(m) of register(r) */ | |
73 | #define REG_SET_MASK(r, m, v) (((r) & ~(m)) | \ | |
74 | (((v) & (m >> (ffs(m) - 1))) << (ffs(m) - 1))) | |
75 | ||
2a895871 WN |
76 | static const int min_low_temp = -127000; |
77 | static const int max_high_temp = 127000; | |
78 | ||
65b6d57c WN |
79 | struct tegra_thermctl_zone { |
80 | void __iomem *reg; | |
2a895871 WN |
81 | struct device *dev; |
82 | struct thermal_zone_device *tz; | |
83 | const struct tegra_tsensor_group *sg; | |
65b6d57c WN |
84 | }; |
85 | ||
86 | struct tegra_soctherm { | |
87 | struct reset_control *reset; | |
88 | struct clk *clock_tsensor; | |
89 | struct clk *clock_soctherm; | |
90 | void __iomem *regs; | |
f09d6984 | 91 | struct thermal_zone_device **thermctl_tzs; |
65b6d57c WN |
92 | |
93 | u32 *calib; | |
94 | struct tegra_soctherm_soc *soc; | |
d753b22d WN |
95 | |
96 | struct dentry *debugfs_dir; | |
65b6d57c WN |
97 | }; |
98 | ||
1ed895c2 | 99 | static void enable_tsensor(struct tegra_soctherm *tegra, unsigned int i) |
65b6d57c WN |
100 | { |
101 | const struct tegra_tsensor *sensor = &tegra->soc->tsensors[i]; | |
102 | void __iomem *base = tegra->regs + sensor->base; | |
65b6d57c | 103 | unsigned int val; |
65b6d57c WN |
104 | |
105 | val = sensor->config->tall << SENSOR_CONFIG0_TALL_SHIFT; | |
106 | writel(val, base + SENSOR_CONFIG0); | |
107 | ||
108 | val = (sensor->config->tsample - 1) << SENSOR_CONFIG1_TSAMPLE_SHIFT; | |
109 | val |= sensor->config->tiddq_en << SENSOR_CONFIG1_TIDDQ_EN_SHIFT; | |
110 | val |= sensor->config->ten_count << SENSOR_CONFIG1_TEN_COUNT_SHIFT; | |
111 | val |= SENSOR_CONFIG1_TEMP_ENABLE; | |
112 | writel(val, base + SENSOR_CONFIG1); | |
113 | ||
1ed895c2 | 114 | writel(tegra->calib[i], base + SENSOR_CONFIG2); |
65b6d57c WN |
115 | } |
116 | ||
117 | /* | |
118 | * Translate from soctherm readback format to millicelsius. | |
119 | * The soctherm readback format in bits is as follows: | |
120 | * TTTTTTTT H______N | |
121 | * where T's contain the temperature in Celsius, | |
122 | * H denotes an addition of 0.5 Celsius and N denotes negation | |
123 | * of the final value. | |
124 | */ | |
125 | static int translate_temp(u16 val) | |
126 | { | |
127 | int t; | |
128 | ||
129 | t = ((val & READBACK_VALUE_MASK) >> READBACK_VALUE_SHIFT) * 1000; | |
130 | if (val & READBACK_ADD_HALF) | |
131 | t += 500; | |
132 | if (val & READBACK_NEGATE) | |
133 | t *= -1; | |
134 | ||
135 | return t; | |
136 | } | |
137 | ||
138 | static int tegra_thermctl_get_temp(void *data, int *out_temp) | |
139 | { | |
140 | struct tegra_thermctl_zone *zone = data; | |
141 | u32 val; | |
142 | ||
143 | val = readl(zone->reg); | |
2a895871 | 144 | val = REG_GET_MASK(val, zone->sg->sensor_temp_mask); |
65b6d57c WN |
145 | *out_temp = translate_temp(val); |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
2a895871 WN |
150 | static int |
151 | thermtrip_program(struct device *dev, const struct tegra_tsensor_group *sg, | |
152 | int trip_temp); | |
153 | ||
154 | static int tegra_thermctl_set_trip_temp(void *data, int trip, int temp) | |
155 | { | |
156 | struct tegra_thermctl_zone *zone = data; | |
157 | struct thermal_zone_device *tz = zone->tz; | |
158 | const struct tegra_tsensor_group *sg = zone->sg; | |
159 | struct device *dev = zone->dev; | |
160 | enum thermal_trip_type type; | |
161 | int ret; | |
162 | ||
163 | if (!tz) | |
164 | return -EINVAL; | |
165 | ||
166 | ret = tz->ops->get_trip_type(tz, trip, &type); | |
167 | if (ret) | |
168 | return ret; | |
169 | ||
170 | if (type != THERMAL_TRIP_CRITICAL) | |
171 | return 0; | |
172 | ||
173 | return thermtrip_program(dev, sg, temp); | |
174 | } | |
175 | ||
65b6d57c WN |
176 | static const struct thermal_zone_of_device_ops tegra_of_thermal_ops = { |
177 | .get_temp = tegra_thermctl_get_temp, | |
2a895871 | 178 | .set_trip_temp = tegra_thermctl_set_trip_temp, |
65b6d57c WN |
179 | }; |
180 | ||
2a895871 WN |
181 | /** |
182 | * enforce_temp_range() - check and enforce temperature range [min, max] | |
183 | * @trip_temp: the trip temperature to check | |
184 | * | |
185 | * Checks and enforces the permitted temperature range that SOC_THERM | |
186 | * HW can support This is | |
187 | * done while taking care of precision. | |
188 | * | |
189 | * Return: The precision adjusted capped temperature in millicelsius. | |
190 | */ | |
191 | static int enforce_temp_range(struct device *dev, int trip_temp) | |
192 | { | |
193 | int temp; | |
194 | ||
195 | temp = clamp_val(trip_temp, min_low_temp, max_high_temp); | |
196 | if (temp != trip_temp) | |
197 | dev_info(dev, "soctherm: trip temperature %d forced to %d\n", | |
198 | trip_temp, temp); | |
199 | return temp; | |
200 | } | |
201 | ||
202 | /** | |
203 | * thermtrip_program() - Configures the hardware to shut down the | |
204 | * system if a given sensor group reaches a given temperature | |
205 | * @dev: ptr to the struct device for the SOC_THERM IP block | |
206 | * @sg: pointer to the sensor group to set the thermtrip temperature for | |
207 | * @trip_temp: the temperature in millicelsius to trigger the thermal trip at | |
208 | * | |
209 | * Sets the thermal trip threshold of the given sensor group to be the | |
210 | * @trip_temp. If this threshold is crossed, the hardware will shut | |
211 | * down. | |
212 | * | |
213 | * Note that, although @trip_temp is specified in millicelsius, the | |
214 | * hardware is programmed in degrees Celsius. | |
215 | * | |
216 | * Return: 0 upon success, or %-EINVAL upon failure. | |
217 | */ | |
218 | static int thermtrip_program(struct device *dev, | |
219 | const struct tegra_tsensor_group *sg, | |
220 | int trip_temp) | |
221 | { | |
222 | struct tegra_soctherm *ts = dev_get_drvdata(dev); | |
223 | int temp; | |
224 | u32 r; | |
225 | ||
f3500980 | 226 | if (!sg || !sg->thermtrip_threshold_mask) |
2a895871 WN |
227 | return -EINVAL; |
228 | ||
229 | temp = enforce_temp_range(dev, trip_temp) / ts->soc->thresh_grain; | |
230 | ||
231 | r = readl(ts->regs + THERMCTL_THERMTRIP_CTL); | |
232 | r = REG_SET_MASK(r, sg->thermtrip_threshold_mask, temp); | |
233 | r = REG_SET_MASK(r, sg->thermtrip_enable_mask, 1); | |
234 | r = REG_SET_MASK(r, sg->thermtrip_any_en_mask, 0); | |
235 | writel(r, ts->regs + THERMCTL_THERMTRIP_CTL); | |
236 | ||
237 | return 0; | |
238 | } | |
239 | ||
240 | /** | |
241 | * tegra_soctherm_set_hwtrips() - set HW trip point from DT data | |
242 | * @dev: struct device * of the SOC_THERM instance | |
243 | * | |
244 | * Configure the SOC_THERM HW trip points, setting "THERMTRIP" | |
245 | * trip points , using "critical" type trip_temp from thermal | |
246 | * zone. | |
247 | * After they have been configured, THERMTRIP will take action | |
248 | * when the configured SoC thermal sensor group reaches a | |
249 | * certain temperature. | |
250 | * | |
251 | * Return: 0 upon success, or a negative error code on failure. | |
252 | * "Success" does not mean that trips was enabled; it could also | |
253 | * mean that no node was found in DT. | |
254 | * THERMTRIP has been enabled successfully when a message similar to | |
255 | * this one appears on the serial console: | |
256 | * "thermtrip: will shut down when sensor group XXX reaches YYYYYY mC" | |
257 | */ | |
258 | static int tegra_soctherm_set_hwtrips(struct device *dev, | |
259 | const struct tegra_tsensor_group *sg, | |
260 | struct thermal_zone_device *tz) | |
261 | { | |
262 | int temperature; | |
263 | int ret; | |
264 | ||
265 | ret = tz->ops->get_crit_temp(tz, &temperature); | |
266 | if (ret) { | |
267 | dev_warn(dev, "thermtrip: %s: missing critical temperature\n", | |
268 | sg->name); | |
269 | return ret; | |
270 | } | |
271 | ||
272 | ret = thermtrip_program(dev, sg, temperature); | |
273 | if (ret) { | |
274 | dev_err(dev, "thermtrip: %s: error during enable\n", | |
275 | sg->name); | |
276 | return ret; | |
277 | } | |
278 | ||
279 | dev_info(dev, | |
280 | "thermtrip: will shut down when %s reaches %d mC\n", | |
281 | sg->name, temperature); | |
282 | ||
283 | return 0; | |
284 | } | |
285 | ||
d753b22d WN |
286 | #ifdef CONFIG_DEBUG_FS |
287 | static int regs_show(struct seq_file *s, void *data) | |
288 | { | |
289 | struct platform_device *pdev = s->private; | |
290 | struct tegra_soctherm *ts = platform_get_drvdata(pdev); | |
291 | const struct tegra_tsensor *tsensors = ts->soc->tsensors; | |
2a895871 | 292 | const struct tegra_tsensor_group **ttgs = ts->soc->ttgs; |
d753b22d WN |
293 | u32 r, state; |
294 | int i; | |
295 | ||
296 | seq_puts(s, "-----TSENSE (convert HW)-----\n"); | |
297 | ||
298 | for (i = 0; i < ts->soc->num_tsensors; i++) { | |
299 | r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG1); | |
300 | state = REG_GET_MASK(r, SENSOR_CONFIG1_TEMP_ENABLE); | |
301 | ||
302 | seq_printf(s, "%s: ", tsensors[i].name); | |
303 | seq_printf(s, "En(%d) ", state); | |
304 | ||
305 | if (!state) { | |
306 | seq_puts(s, "\n"); | |
307 | continue; | |
308 | } | |
309 | ||
310 | state = REG_GET_MASK(r, SENSOR_CONFIG1_TIDDQ_EN_MASK); | |
311 | seq_printf(s, "tiddq(%d) ", state); | |
312 | state = REG_GET_MASK(r, SENSOR_CONFIG1_TEN_COUNT_MASK); | |
313 | seq_printf(s, "ten_count(%d) ", state); | |
314 | state = REG_GET_MASK(r, SENSOR_CONFIG1_TSAMPLE_MASK); | |
315 | seq_printf(s, "tsample(%d) ", state + 1); | |
316 | ||
317 | r = readl(ts->regs + tsensors[i].base + SENSOR_STATUS1); | |
318 | state = REG_GET_MASK(r, SENSOR_STATUS1_TEMP_VALID_MASK); | |
319 | seq_printf(s, "Temp(%d/", state); | |
320 | state = REG_GET_MASK(r, SENSOR_STATUS1_TEMP_MASK); | |
321 | seq_printf(s, "%d) ", translate_temp(state)); | |
322 | ||
323 | r = readl(ts->regs + tsensors[i].base + SENSOR_STATUS0); | |
324 | state = REG_GET_MASK(r, SENSOR_STATUS0_VALID_MASK); | |
325 | seq_printf(s, "Capture(%d/", state); | |
326 | state = REG_GET_MASK(r, SENSOR_STATUS0_CAPTURE_MASK); | |
327 | seq_printf(s, "%d) ", state); | |
328 | ||
329 | r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG0); | |
330 | state = REG_GET_MASK(r, SENSOR_CONFIG0_STOP); | |
331 | seq_printf(s, "Stop(%d) ", state); | |
332 | state = REG_GET_MASK(r, SENSOR_CONFIG0_TALL_MASK); | |
333 | seq_printf(s, "Tall(%d) ", state); | |
334 | state = REG_GET_MASK(r, SENSOR_CONFIG0_TCALC_OVER); | |
335 | seq_printf(s, "Over(%d/", state); | |
336 | state = REG_GET_MASK(r, SENSOR_CONFIG0_OVER); | |
337 | seq_printf(s, "%d/", state); | |
338 | state = REG_GET_MASK(r, SENSOR_CONFIG0_CPTR_OVER); | |
339 | seq_printf(s, "%d) ", state); | |
340 | ||
341 | r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG2); | |
342 | state = REG_GET_MASK(r, SENSOR_CONFIG2_THERMA_MASK); | |
343 | seq_printf(s, "Therm_A/B(%d/", state); | |
344 | state = REG_GET_MASK(r, SENSOR_CONFIG2_THERMB_MASK); | |
345 | seq_printf(s, "%d)\n", (s16)state); | |
346 | } | |
347 | ||
348 | r = readl(ts->regs + SENSOR_PDIV); | |
349 | seq_printf(s, "PDIV: 0x%x\n", r); | |
350 | ||
351 | r = readl(ts->regs + SENSOR_HOTSPOT_OFF); | |
352 | seq_printf(s, "HOTSPOT: 0x%x\n", r); | |
353 | ||
354 | seq_puts(s, "\n"); | |
355 | seq_puts(s, "-----SOC_THERM-----\n"); | |
356 | ||
357 | r = readl(ts->regs + SENSOR_TEMP1); | |
358 | state = REG_GET_MASK(r, SENSOR_TEMP1_CPU_TEMP_MASK); | |
359 | seq_printf(s, "Temperatures: CPU(%d) ", translate_temp(state)); | |
360 | state = REG_GET_MASK(r, SENSOR_TEMP1_GPU_TEMP_MASK); | |
361 | seq_printf(s, " GPU(%d) ", translate_temp(state)); | |
362 | r = readl(ts->regs + SENSOR_TEMP2); | |
363 | state = REG_GET_MASK(r, SENSOR_TEMP2_PLLX_TEMP_MASK); | |
364 | seq_printf(s, " PLLX(%d) ", translate_temp(state)); | |
365 | state = REG_GET_MASK(r, SENSOR_TEMP2_MEM_TEMP_MASK); | |
366 | seq_printf(s, " MEM(%d)\n", translate_temp(state)); | |
367 | ||
2a895871 WN |
368 | r = readl(ts->regs + THERMCTL_THERMTRIP_CTL); |
369 | state = REG_GET_MASK(r, ttgs[0]->thermtrip_any_en_mask); | |
370 | seq_printf(s, "Thermtrip Any En(%d)\n", state); | |
371 | for (i = 0; i < ts->soc->num_ttgs; i++) { | |
372 | state = REG_GET_MASK(r, ttgs[i]->thermtrip_enable_mask); | |
373 | seq_printf(s, " %s En(%d) ", ttgs[i]->name, state); | |
374 | state = REG_GET_MASK(r, ttgs[i]->thermtrip_threshold_mask); | |
375 | state *= ts->soc->thresh_grain; | |
376 | seq_printf(s, "Thresh(%d)\n", state); | |
377 | } | |
378 | ||
d753b22d WN |
379 | return 0; |
380 | } | |
381 | ||
382 | static int regs_open(struct inode *inode, struct file *file) | |
383 | { | |
384 | return single_open(file, regs_show, inode->i_private); | |
385 | } | |
386 | ||
387 | static const struct file_operations regs_fops = { | |
388 | .open = regs_open, | |
389 | .read = seq_read, | |
390 | .llseek = seq_lseek, | |
391 | .release = single_release, | |
392 | }; | |
393 | ||
394 | static void soctherm_debug_init(struct platform_device *pdev) | |
395 | { | |
396 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
397 | struct dentry *root, *file; | |
398 | ||
399 | root = debugfs_create_dir("soctherm", NULL); | |
400 | if (!root) { | |
401 | dev_err(&pdev->dev, "failed to create debugfs directory\n"); | |
402 | return; | |
403 | } | |
404 | ||
405 | tegra->debugfs_dir = root; | |
406 | ||
407 | file = debugfs_create_file("reg_contents", 0644, root, | |
408 | pdev, ®s_fops); | |
409 | if (!file) { | |
410 | dev_err(&pdev->dev, "failed to create debugfs file\n"); | |
411 | debugfs_remove_recursive(tegra->debugfs_dir); | |
412 | tegra->debugfs_dir = NULL; | |
413 | } | |
414 | } | |
415 | #else | |
416 | static inline void soctherm_debug_init(struct platform_device *pdev) {} | |
417 | #endif | |
418 | ||
8de2ab02 WN |
419 | static int soctherm_clk_enable(struct platform_device *pdev, bool enable) |
420 | { | |
421 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
422 | int err; | |
423 | ||
424 | if (!tegra->clock_soctherm || !tegra->clock_tsensor) | |
425 | return -EINVAL; | |
426 | ||
427 | reset_control_assert(tegra->reset); | |
428 | ||
429 | if (enable) { | |
430 | err = clk_prepare_enable(tegra->clock_soctherm); | |
431 | if (err) { | |
432 | reset_control_deassert(tegra->reset); | |
433 | return err; | |
434 | } | |
435 | ||
436 | err = clk_prepare_enable(tegra->clock_tsensor); | |
437 | if (err) { | |
438 | clk_disable_unprepare(tegra->clock_soctherm); | |
439 | reset_control_deassert(tegra->reset); | |
440 | return err; | |
441 | } | |
442 | } else { | |
443 | clk_disable_unprepare(tegra->clock_tsensor); | |
444 | clk_disable_unprepare(tegra->clock_soctherm); | |
445 | } | |
446 | ||
447 | reset_control_deassert(tegra->reset); | |
448 | ||
449 | return 0; | |
450 | } | |
451 | ||
1ed895c2 WN |
452 | static void soctherm_init(struct platform_device *pdev) |
453 | { | |
454 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
455 | const struct tegra_tsensor_group **ttgs = tegra->soc->ttgs; | |
456 | int i; | |
457 | u32 pdiv, hotspot; | |
458 | ||
459 | /* Initialize raw sensors */ | |
460 | for (i = 0; i < tegra->soc->num_tsensors; ++i) | |
461 | enable_tsensor(tegra, i); | |
462 | ||
463 | /* program pdiv and hotspot offsets per THERM */ | |
464 | pdiv = readl(tegra->regs + SENSOR_PDIV); | |
465 | hotspot = readl(tegra->regs + SENSOR_HOTSPOT_OFF); | |
466 | for (i = 0; i < tegra->soc->num_ttgs; ++i) { | |
467 | pdiv = REG_SET_MASK(pdiv, ttgs[i]->pdiv_mask, | |
468 | ttgs[i]->pdiv); | |
469 | /* hotspot offset from PLLX, doesn't need to configure PLLX */ | |
470 | if (ttgs[i]->id == TEGRA124_SOCTHERM_SENSOR_PLLX) | |
471 | continue; | |
472 | hotspot = REG_SET_MASK(hotspot, | |
473 | ttgs[i]->pllx_hotspot_mask, | |
474 | ttgs[i]->pllx_hotspot_diff); | |
475 | } | |
476 | writel(pdiv, tegra->regs + SENSOR_PDIV); | |
477 | writel(hotspot, tegra->regs + SENSOR_HOTSPOT_OFF); | |
478 | } | |
479 | ||
65b6d57c WN |
480 | static const struct of_device_id tegra_soctherm_of_match[] = { |
481 | #ifdef CONFIG_ARCH_TEGRA_124_SOC | |
482 | { | |
483 | .compatible = "nvidia,tegra124-soctherm", | |
484 | .data = &tegra124_soctherm, | |
485 | }, | |
8204104f | 486 | #endif |
44cb6a7d WN |
487 | #ifdef CONFIG_ARCH_TEGRA_132_SOC |
488 | { | |
489 | .compatible = "nvidia,tegra132-soctherm", | |
490 | .data = &tegra132_soctherm, | |
491 | }, | |
492 | #endif | |
8204104f WN |
493 | #ifdef CONFIG_ARCH_TEGRA_210_SOC |
494 | { | |
495 | .compatible = "nvidia,tegra210-soctherm", | |
496 | .data = &tegra210_soctherm, | |
497 | }, | |
65b6d57c WN |
498 | #endif |
499 | { }, | |
500 | }; | |
501 | MODULE_DEVICE_TABLE(of, tegra_soctherm_of_match); | |
502 | ||
503 | static int tegra_soctherm_probe(struct platform_device *pdev) | |
504 | { | |
505 | const struct of_device_id *match; | |
506 | struct tegra_soctherm *tegra; | |
507 | struct thermal_zone_device *z; | |
508 | struct tsensor_shared_calib shared_calib; | |
509 | struct resource *res; | |
510 | struct tegra_soctherm_soc *soc; | |
511 | unsigned int i; | |
512 | int err; | |
65b6d57c WN |
513 | |
514 | match = of_match_node(tegra_soctherm_of_match, pdev->dev.of_node); | |
515 | if (!match) | |
516 | return -ENODEV; | |
517 | ||
518 | soc = (struct tegra_soctherm_soc *)match->data; | |
519 | if (soc->num_ttgs > TEGRA124_SOCTHERM_SENSOR_NUM) | |
520 | return -EINVAL; | |
521 | ||
522 | tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); | |
523 | if (!tegra) | |
524 | return -ENOMEM; | |
525 | ||
526 | dev_set_drvdata(&pdev->dev, tegra); | |
527 | ||
528 | tegra->soc = soc; | |
529 | ||
530 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
531 | tegra->regs = devm_ioremap_resource(&pdev->dev, res); | |
532 | if (IS_ERR(tegra->regs)) | |
533 | return PTR_ERR(tegra->regs); | |
534 | ||
535 | tegra->reset = devm_reset_control_get(&pdev->dev, "soctherm"); | |
536 | if (IS_ERR(tegra->reset)) { | |
537 | dev_err(&pdev->dev, "can't get soctherm reset\n"); | |
538 | return PTR_ERR(tegra->reset); | |
539 | } | |
540 | ||
541 | tegra->clock_tsensor = devm_clk_get(&pdev->dev, "tsensor"); | |
542 | if (IS_ERR(tegra->clock_tsensor)) { | |
543 | dev_err(&pdev->dev, "can't get tsensor clock\n"); | |
544 | return PTR_ERR(tegra->clock_tsensor); | |
545 | } | |
546 | ||
547 | tegra->clock_soctherm = devm_clk_get(&pdev->dev, "soctherm"); | |
548 | if (IS_ERR(tegra->clock_soctherm)) { | |
549 | dev_err(&pdev->dev, "can't get soctherm clock\n"); | |
550 | return PTR_ERR(tegra->clock_soctherm); | |
551 | } | |
552 | ||
65b6d57c WN |
553 | tegra->calib = devm_kzalloc(&pdev->dev, |
554 | sizeof(u32) * soc->num_tsensors, | |
555 | GFP_KERNEL); | |
1ed895c2 WN |
556 | if (!tegra->calib) |
557 | return -ENOMEM; | |
65b6d57c | 558 | |
1ed895c2 | 559 | /* calculate shared calibration data */ |
65b6d57c WN |
560 | err = tegra_calc_shared_calib(soc->tfuse, &shared_calib); |
561 | if (err) | |
1ed895c2 | 562 | return err; |
65b6d57c | 563 | |
1ed895c2 | 564 | /* calculate tsensor calibaration data */ |
65b6d57c | 565 | for (i = 0; i < soc->num_tsensors; ++i) { |
1ed895c2 WN |
566 | err = tegra_calc_tsensor_calib(&soc->tsensors[i], |
567 | &shared_calib, | |
568 | &tegra->calib[i]); | |
65b6d57c | 569 | if (err) |
1ed895c2 | 570 | return err; |
65b6d57c WN |
571 | } |
572 | ||
f09d6984 WN |
573 | tegra->thermctl_tzs = devm_kzalloc(&pdev->dev, |
574 | sizeof(*z) * soc->num_ttgs, | |
575 | GFP_KERNEL); | |
576 | if (!tegra->thermctl_tzs) | |
577 | return -ENOMEM; | |
578 | ||
1ed895c2 WN |
579 | err = soctherm_clk_enable(pdev, true); |
580 | if (err) | |
581 | return err; | |
582 | ||
583 | soctherm_init(pdev); | |
65b6d57c | 584 | |
65b6d57c WN |
585 | for (i = 0; i < soc->num_ttgs; ++i) { |
586 | struct tegra_thermctl_zone *zone = | |
587 | devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL); | |
588 | if (!zone) { | |
589 | err = -ENOMEM; | |
590 | goto disable_clocks; | |
591 | } | |
592 | ||
593 | zone->reg = tegra->regs + soc->ttgs[i]->sensor_temp_offset; | |
2a895871 WN |
594 | zone->dev = &pdev->dev; |
595 | zone->sg = soc->ttgs[i]; | |
65b6d57c WN |
596 | |
597 | z = devm_thermal_zone_of_sensor_register(&pdev->dev, | |
598 | soc->ttgs[i]->id, zone, | |
599 | &tegra_of_thermal_ops); | |
600 | if (IS_ERR(z)) { | |
601 | err = PTR_ERR(z); | |
602 | dev_err(&pdev->dev, "failed to register sensor: %d\n", | |
603 | err); | |
604 | goto disable_clocks; | |
605 | } | |
2a895871 WN |
606 | |
607 | zone->tz = z; | |
f09d6984 | 608 | tegra->thermctl_tzs[soc->ttgs[i]->id] = z; |
2a895871 WN |
609 | |
610 | /* Configure hw trip points */ | |
611 | tegra_soctherm_set_hwtrips(&pdev->dev, soc->ttgs[i], z); | |
65b6d57c WN |
612 | } |
613 | ||
d753b22d WN |
614 | soctherm_debug_init(pdev); |
615 | ||
65b6d57c WN |
616 | return 0; |
617 | ||
618 | disable_clocks: | |
8de2ab02 | 619 | soctherm_clk_enable(pdev, false); |
65b6d57c WN |
620 | |
621 | return err; | |
622 | } | |
623 | ||
624 | static int tegra_soctherm_remove(struct platform_device *pdev) | |
625 | { | |
626 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
627 | ||
d753b22d WN |
628 | debugfs_remove_recursive(tegra->debugfs_dir); |
629 | ||
8de2ab02 | 630 | soctherm_clk_enable(pdev, false); |
65b6d57c WN |
631 | |
632 | return 0; | |
633 | } | |
634 | ||
a977c41e | 635 | static int __maybe_unused soctherm_suspend(struct device *dev) |
f09d6984 WN |
636 | { |
637 | struct platform_device *pdev = to_platform_device(dev); | |
638 | ||
639 | soctherm_clk_enable(pdev, false); | |
640 | ||
641 | return 0; | |
642 | } | |
643 | ||
a977c41e | 644 | static int __maybe_unused soctherm_resume(struct device *dev) |
f09d6984 WN |
645 | { |
646 | struct platform_device *pdev = to_platform_device(dev); | |
647 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
648 | struct tegra_soctherm_soc *soc = tegra->soc; | |
649 | int err, i; | |
650 | ||
651 | err = soctherm_clk_enable(pdev, true); | |
652 | if (err) { | |
653 | dev_err(&pdev->dev, | |
654 | "Resume failed: enable clocks failed\n"); | |
655 | return err; | |
656 | } | |
657 | ||
658 | soctherm_init(pdev); | |
659 | ||
660 | for (i = 0; i < soc->num_ttgs; ++i) { | |
661 | struct thermal_zone_device *tz; | |
662 | ||
663 | tz = tegra->thermctl_tzs[soc->ttgs[i]->id]; | |
664 | tegra_soctherm_set_hwtrips(dev, soc->ttgs[i], tz); | |
665 | } | |
666 | ||
667 | return 0; | |
668 | } | |
669 | ||
670 | static SIMPLE_DEV_PM_OPS(tegra_soctherm_pm, soctherm_suspend, soctherm_resume); | |
671 | ||
65b6d57c WN |
672 | static struct platform_driver tegra_soctherm_driver = { |
673 | .probe = tegra_soctherm_probe, | |
674 | .remove = tegra_soctherm_remove, | |
675 | .driver = { | |
676 | .name = "tegra_soctherm", | |
f09d6984 | 677 | .pm = &tegra_soctherm_pm, |
65b6d57c WN |
678 | .of_match_table = tegra_soctherm_of_match, |
679 | }, | |
680 | }; | |
681 | module_platform_driver(tegra_soctherm_driver); | |
682 | ||
683 | MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>"); | |
684 | MODULE_DESCRIPTION("NVIDIA Tegra SOCTHERM thermal management driver"); | |
685 | MODULE_LICENSE("GPL v2"); |