]>
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 | ||
ce0dbf04 | 33 | #include "../thermal_core.h" |
65b6d57c WN |
34 | #include "soctherm.h" |
35 | ||
36 | #define SENSOR_CONFIG0 0 | |
37 | #define SENSOR_CONFIG0_STOP BIT(0) | |
65b6d57c | 38 | #define SENSOR_CONFIG0_CPTR_OVER BIT(2) |
d753b22d WN |
39 | #define SENSOR_CONFIG0_OVER BIT(3) |
40 | #define SENSOR_CONFIG0_TCALC_OVER BIT(4) | |
41 | #define SENSOR_CONFIG0_TALL_MASK (0xfffff << 8) | |
42 | #define SENSOR_CONFIG0_TALL_SHIFT 8 | |
65b6d57c WN |
43 | |
44 | #define SENSOR_CONFIG1 4 | |
d753b22d | 45 | #define SENSOR_CONFIG1_TSAMPLE_MASK 0x3ff |
65b6d57c | 46 | #define SENSOR_CONFIG1_TSAMPLE_SHIFT 0 |
d753b22d | 47 | #define SENSOR_CONFIG1_TIDDQ_EN_MASK (0x3f << 15) |
65b6d57c | 48 | #define SENSOR_CONFIG1_TIDDQ_EN_SHIFT 15 |
d753b22d | 49 | #define SENSOR_CONFIG1_TEN_COUNT_MASK (0x3f << 24) |
65b6d57c WN |
50 | #define SENSOR_CONFIG1_TEN_COUNT_SHIFT 24 |
51 | #define SENSOR_CONFIG1_TEMP_ENABLE BIT(31) | |
52 | ||
53 | /* | |
54 | * SENSOR_CONFIG2 is defined in soctherm.h | |
55 | * because, it will be used by tegra_soctherm_fuse.c | |
56 | */ | |
57 | ||
d753b22d WN |
58 | #define SENSOR_STATUS0 0xc |
59 | #define SENSOR_STATUS0_VALID_MASK BIT(31) | |
60 | #define SENSOR_STATUS0_CAPTURE_MASK 0xffff | |
61 | ||
62 | #define SENSOR_STATUS1 0x10 | |
63 | #define SENSOR_STATUS1_TEMP_VALID_MASK BIT(31) | |
64 | #define SENSOR_STATUS1_TEMP_MASK 0xffff | |
65 | ||
65b6d57c WN |
66 | #define READBACK_VALUE_MASK 0xff00 |
67 | #define READBACK_VALUE_SHIFT 8 | |
68 | #define READBACK_ADD_HALF BIT(7) | |
69 | #define READBACK_NEGATE BIT(0) | |
70 | ||
ce0dbf04 WN |
71 | /* |
72 | * THERMCTL_LEVEL0_GROUP_CPU is defined in soctherm.h | |
73 | * because it will be used by tegraxxx_soctherm.c | |
74 | */ | |
75 | #define THERMCTL_LVL0_CPU0_EN_MASK BIT(8) | |
76 | #define THERMCTL_LVL0_CPU0_CPU_THROT_MASK (0x3 << 5) | |
77 | #define THERMCTL_LVL0_CPU0_CPU_THROT_LIGHT 0x1 | |
78 | #define THERMCTL_LVL0_CPU0_CPU_THROT_HEAVY 0x2 | |
79 | #define THERMCTL_LVL0_CPU0_GPU_THROT_MASK (0x3 << 3) | |
80 | #define THERMCTL_LVL0_CPU0_GPU_THROT_LIGHT 0x1 | |
81 | #define THERMCTL_LVL0_CPU0_GPU_THROT_HEAVY 0x2 | |
82 | #define THERMCTL_LVL0_CPU0_MEM_THROT_MASK BIT(2) | |
83 | #define THERMCTL_LVL0_CPU0_STATUS_MASK 0x3 | |
84 | ||
85 | #define THERMCTL_LVL0_UP_STATS 0x10 | |
86 | #define THERMCTL_LVL0_DN_STATS 0x14 | |
87 | ||
88 | #define THERMCTL_STATS_CTL 0x94 | |
89 | #define STATS_CTL_CLR_DN 0x8 | |
90 | #define STATS_CTL_EN_DN 0x4 | |
91 | #define STATS_CTL_CLR_UP 0x2 | |
92 | #define STATS_CTL_EN_UP 0x1 | |
93 | ||
94 | #define THROT_GLOBAL_CFG 0x400 | |
95 | #define THROT_GLOBAL_ENB_MASK BIT(0) | |
96 | ||
97 | #define CPU_PSKIP_STATUS 0x418 | |
98 | #define XPU_PSKIP_STATUS_M_MASK (0xff << 12) | |
99 | #define XPU_PSKIP_STATUS_N_MASK (0xff << 4) | |
100 | #define XPU_PSKIP_STATUS_SW_OVERRIDE_MASK BIT(1) | |
101 | #define XPU_PSKIP_STATUS_ENABLED_MASK BIT(0) | |
102 | ||
103 | #define THROT_PRIORITY_LOCK 0x424 | |
104 | #define THROT_PRIORITY_LOCK_PRIORITY_MASK 0xff | |
105 | ||
106 | #define THROT_STATUS 0x428 | |
107 | #define THROT_STATUS_BREACH_MASK BIT(12) | |
108 | #define THROT_STATUS_STATE_MASK (0xff << 4) | |
109 | #define THROT_STATUS_ENABLED_MASK BIT(0) | |
110 | ||
111 | #define THROT_PSKIP_CTRL_LITE_CPU 0x430 | |
112 | #define THROT_PSKIP_CTRL_ENABLE_MASK BIT(31) | |
113 | #define THROT_PSKIP_CTRL_DIVIDEND_MASK (0xff << 8) | |
114 | #define THROT_PSKIP_CTRL_DIVISOR_MASK 0xff | |
115 | #define THROT_PSKIP_CTRL_VECT_GPU_MASK (0x7 << 16) | |
116 | #define THROT_PSKIP_CTRL_VECT_CPU_MASK (0x7 << 8) | |
117 | #define THROT_PSKIP_CTRL_VECT2_CPU_MASK 0x7 | |
118 | ||
119 | #define THROT_VECT_NONE 0x0 /* 3'b000 */ | |
120 | #define THROT_VECT_LOW 0x1 /* 3'b001 */ | |
121 | #define THROT_VECT_MED 0x3 /* 3'b011 */ | |
122 | #define THROT_VECT_HIGH 0x7 /* 3'b111 */ | |
123 | ||
124 | #define THROT_PSKIP_RAMP_LITE_CPU 0x434 | |
125 | #define THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK BIT(31) | |
126 | #define THROT_PSKIP_RAMP_DURATION_MASK (0xffff << 8) | |
127 | #define THROT_PSKIP_RAMP_STEP_MASK 0xff | |
128 | ||
129 | #define THROT_PRIORITY_LITE 0x444 | |
130 | #define THROT_PRIORITY_LITE_PRIO_MASK 0xff | |
131 | ||
132 | #define THROT_DELAY_LITE 0x448 | |
133 | #define THROT_DELAY_LITE_DELAY_MASK 0xff | |
134 | ||
135 | /* car register offsets needed for enabling HW throttling */ | |
136 | #define CAR_SUPER_CCLKG_DIVIDER 0x36c | |
137 | #define CDIVG_USE_THERM_CONTROLS_MASK BIT(30) | |
138 | ||
6c7c3245 WN |
139 | /* ccroc register offsets needed for enabling HW throttling for Tegra132 */ |
140 | #define CCROC_SUPER_CCLKG_DIVIDER 0x024 | |
141 | ||
142 | #define CCROC_GLOBAL_CFG 0x148 | |
143 | ||
144 | #define CCROC_THROT_PSKIP_RAMP_CPU 0x150 | |
145 | #define CCROC_THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK BIT(31) | |
146 | #define CCROC_THROT_PSKIP_RAMP_DURATION_MASK (0xffff << 8) | |
147 | #define CCROC_THROT_PSKIP_RAMP_STEP_MASK 0xff | |
148 | ||
149 | #define CCROC_THROT_PSKIP_CTRL_CPU 0x154 | |
150 | #define CCROC_THROT_PSKIP_CTRL_ENB_MASK BIT(31) | |
151 | #define CCROC_THROT_PSKIP_CTRL_DIVIDEND_MASK (0xff << 8) | |
152 | #define CCROC_THROT_PSKIP_CTRL_DIVISOR_MASK 0xff | |
153 | ||
65b6d57c WN |
154 | /* get val from register(r) mask bits(m) */ |
155 | #define REG_GET_MASK(r, m) (((r) & (m)) >> (ffs(m) - 1)) | |
156 | /* set val(v) to mask bits(m) of register(r) */ | |
157 | #define REG_SET_MASK(r, m, v) (((r) & ~(m)) | \ | |
158 | (((v) & (m >> (ffs(m) - 1))) << (ffs(m) - 1))) | |
159 | ||
ce0dbf04 WN |
160 | /* get dividend from the depth */ |
161 | #define THROT_DEPTH_DIVIDEND(depth) ((256 * (100 - (depth)) / 100) - 1) | |
162 | ||
163 | /* get THROT_PSKIP_xxx offset per LIGHT/HEAVY throt and CPU/GPU dev */ | |
164 | #define THROT_OFFSET 0x30 | |
165 | #define THROT_PSKIP_CTRL(throt, dev) (THROT_PSKIP_CTRL_LITE_CPU + \ | |
166 | (THROT_OFFSET * throt) + (8 * dev)) | |
167 | #define THROT_PSKIP_RAMP(throt, dev) (THROT_PSKIP_RAMP_LITE_CPU + \ | |
168 | (THROT_OFFSET * throt) + (8 * dev)) | |
169 | ||
170 | /* get THROT_xxx_CTRL offset per LIGHT/HEAVY throt */ | |
171 | #define THROT_PRIORITY_CTRL(throt) (THROT_PRIORITY_LITE + \ | |
172 | (THROT_OFFSET * throt)) | |
173 | #define THROT_DELAY_CTRL(throt) (THROT_DELAY_LITE + \ | |
174 | (THROT_OFFSET * throt)) | |
175 | ||
6c7c3245 WN |
176 | /* get CCROC_THROT_PSKIP_xxx offset per HIGH/MED/LOW vect*/ |
177 | #define CCROC_THROT_OFFSET 0x0c | |
178 | #define CCROC_THROT_PSKIP_CTRL_CPU_REG(vect) (CCROC_THROT_PSKIP_CTRL_CPU + \ | |
179 | (CCROC_THROT_OFFSET * vect)) | |
180 | #define CCROC_THROT_PSKIP_RAMP_CPU_REG(vect) (CCROC_THROT_PSKIP_RAMP_CPU + \ | |
181 | (CCROC_THROT_OFFSET * vect)) | |
182 | ||
ce0dbf04 WN |
183 | /* get THERMCTL_LEVELx offset per CPU/GPU/MEM/TSENSE rg and LEVEL0~3 lv */ |
184 | #define THERMCTL_LVL_REGS_SIZE 0x20 | |
185 | #define THERMCTL_LVL_REG(rg, lv) ((rg) + ((lv) * THERMCTL_LVL_REGS_SIZE)) | |
186 | ||
2a895871 WN |
187 | static const int min_low_temp = -127000; |
188 | static const int max_high_temp = 127000; | |
189 | ||
ce0dbf04 WN |
190 | enum soctherm_throttle_id { |
191 | THROTTLE_LIGHT = 0, | |
192 | THROTTLE_HEAVY, | |
193 | THROTTLE_SIZE, | |
194 | }; | |
195 | ||
196 | enum soctherm_throttle_dev_id { | |
197 | THROTTLE_DEV_CPU = 0, | |
198 | THROTTLE_DEV_GPU, | |
199 | THROTTLE_DEV_SIZE, | |
200 | }; | |
201 | ||
202 | static const char *const throt_names[] = { | |
203 | [THROTTLE_LIGHT] = "light", | |
204 | [THROTTLE_HEAVY] = "heavy", | |
205 | }; | |
206 | ||
207 | struct tegra_soctherm; | |
65b6d57c WN |
208 | struct tegra_thermctl_zone { |
209 | void __iomem *reg; | |
2a895871 | 210 | struct device *dev; |
ce0dbf04 | 211 | struct tegra_soctherm *ts; |
2a895871 WN |
212 | struct thermal_zone_device *tz; |
213 | const struct tegra_tsensor_group *sg; | |
65b6d57c WN |
214 | }; |
215 | ||
ce0dbf04 WN |
216 | struct soctherm_throt_cfg { |
217 | const char *name; | |
218 | unsigned int id; | |
219 | u8 priority; | |
6c7c3245 | 220 | u8 cpu_throt_level; |
ce0dbf04 WN |
221 | u32 cpu_throt_depth; |
222 | struct thermal_cooling_device *cdev; | |
223 | bool init; | |
224 | }; | |
225 | ||
65b6d57c WN |
226 | struct tegra_soctherm { |
227 | struct reset_control *reset; | |
228 | struct clk *clock_tsensor; | |
229 | struct clk *clock_soctherm; | |
230 | void __iomem *regs; | |
ce0dbf04 | 231 | void __iomem *clk_regs; |
6c7c3245 | 232 | void __iomem *ccroc_regs; |
65b6d57c WN |
233 | |
234 | u32 *calib; | |
ce0dbf04 | 235 | struct thermal_zone_device **thermctl_tzs; |
65b6d57c | 236 | struct tegra_soctherm_soc *soc; |
d753b22d | 237 | |
ce0dbf04 WN |
238 | struct soctherm_throt_cfg throt_cfgs[THROTTLE_SIZE]; |
239 | ||
d753b22d | 240 | struct dentry *debugfs_dir; |
65b6d57c WN |
241 | }; |
242 | ||
ce0dbf04 WN |
243 | /** |
244 | * clk_writel() - writes a value to a CAR register | |
245 | * @ts: pointer to a struct tegra_soctherm | |
246 | * @v: the value to write | |
247 | * @reg: the register offset | |
248 | * | |
249 | * Writes @v to @reg. No return value. | |
250 | */ | |
251 | static inline void clk_writel(struct tegra_soctherm *ts, u32 value, u32 reg) | |
252 | { | |
253 | writel(value, (ts->clk_regs + reg)); | |
254 | } | |
255 | ||
256 | /** | |
257 | * clk_readl() - reads specified register from CAR IP block | |
258 | * @ts: pointer to a struct tegra_soctherm | |
259 | * @reg: register address to be read | |
260 | * | |
261 | * Return: the value of the register | |
262 | */ | |
263 | static inline u32 clk_readl(struct tegra_soctherm *ts, u32 reg) | |
264 | { | |
265 | return readl(ts->clk_regs + reg); | |
266 | } | |
267 | ||
6c7c3245 WN |
268 | /** |
269 | * ccroc_writel() - writes a value to a CCROC register | |
270 | * @ts: pointer to a struct tegra_soctherm | |
271 | * @v: the value to write | |
272 | * @reg: the register offset | |
273 | * | |
274 | * Writes @v to @reg. No return value. | |
275 | */ | |
276 | static inline void ccroc_writel(struct tegra_soctherm *ts, u32 value, u32 reg) | |
277 | { | |
278 | writel(value, (ts->ccroc_regs + reg)); | |
279 | } | |
280 | ||
281 | /** | |
282 | * ccroc_readl() - reads specified register from CCROC IP block | |
283 | * @ts: pointer to a struct tegra_soctherm | |
284 | * @reg: register address to be read | |
285 | * | |
286 | * Return: the value of the register | |
287 | */ | |
288 | static inline u32 ccroc_readl(struct tegra_soctherm *ts, u32 reg) | |
289 | { | |
290 | return readl(ts->ccroc_regs + reg); | |
291 | } | |
292 | ||
1ed895c2 | 293 | static void enable_tsensor(struct tegra_soctherm *tegra, unsigned int i) |
65b6d57c WN |
294 | { |
295 | const struct tegra_tsensor *sensor = &tegra->soc->tsensors[i]; | |
296 | void __iomem *base = tegra->regs + sensor->base; | |
65b6d57c | 297 | unsigned int val; |
65b6d57c WN |
298 | |
299 | val = sensor->config->tall << SENSOR_CONFIG0_TALL_SHIFT; | |
300 | writel(val, base + SENSOR_CONFIG0); | |
301 | ||
302 | val = (sensor->config->tsample - 1) << SENSOR_CONFIG1_TSAMPLE_SHIFT; | |
303 | val |= sensor->config->tiddq_en << SENSOR_CONFIG1_TIDDQ_EN_SHIFT; | |
304 | val |= sensor->config->ten_count << SENSOR_CONFIG1_TEN_COUNT_SHIFT; | |
305 | val |= SENSOR_CONFIG1_TEMP_ENABLE; | |
306 | writel(val, base + SENSOR_CONFIG1); | |
307 | ||
1ed895c2 | 308 | writel(tegra->calib[i], base + SENSOR_CONFIG2); |
65b6d57c WN |
309 | } |
310 | ||
311 | /* | |
312 | * Translate from soctherm readback format to millicelsius. | |
313 | * The soctherm readback format in bits is as follows: | |
314 | * TTTTTTTT H______N | |
315 | * where T's contain the temperature in Celsius, | |
316 | * H denotes an addition of 0.5 Celsius and N denotes negation | |
317 | * of the final value. | |
318 | */ | |
319 | static int translate_temp(u16 val) | |
320 | { | |
321 | int t; | |
322 | ||
323 | t = ((val & READBACK_VALUE_MASK) >> READBACK_VALUE_SHIFT) * 1000; | |
324 | if (val & READBACK_ADD_HALF) | |
325 | t += 500; | |
326 | if (val & READBACK_NEGATE) | |
327 | t *= -1; | |
328 | ||
329 | return t; | |
330 | } | |
331 | ||
332 | static int tegra_thermctl_get_temp(void *data, int *out_temp) | |
333 | { | |
334 | struct tegra_thermctl_zone *zone = data; | |
335 | u32 val; | |
336 | ||
337 | val = readl(zone->reg); | |
2a895871 | 338 | val = REG_GET_MASK(val, zone->sg->sensor_temp_mask); |
65b6d57c WN |
339 | *out_temp = translate_temp(val); |
340 | ||
341 | return 0; | |
342 | } | |
343 | ||
2a895871 WN |
344 | static int |
345 | thermtrip_program(struct device *dev, const struct tegra_tsensor_group *sg, | |
346 | int trip_temp); | |
ce0dbf04 WN |
347 | static int |
348 | throttrip_program(struct device *dev, const struct tegra_tsensor_group *sg, | |
349 | struct soctherm_throt_cfg *stc, int trip_temp); | |
350 | static struct soctherm_throt_cfg * | |
351 | find_throttle_cfg_by_name(struct tegra_soctherm *ts, const char *name); | |
2a895871 WN |
352 | |
353 | static int tegra_thermctl_set_trip_temp(void *data, int trip, int temp) | |
354 | { | |
355 | struct tegra_thermctl_zone *zone = data; | |
356 | struct thermal_zone_device *tz = zone->tz; | |
ce0dbf04 | 357 | struct tegra_soctherm *ts = zone->ts; |
2a895871 WN |
358 | const struct tegra_tsensor_group *sg = zone->sg; |
359 | struct device *dev = zone->dev; | |
360 | enum thermal_trip_type type; | |
361 | int ret; | |
362 | ||
363 | if (!tz) | |
364 | return -EINVAL; | |
365 | ||
366 | ret = tz->ops->get_trip_type(tz, trip, &type); | |
367 | if (ret) | |
368 | return ret; | |
369 | ||
ce0dbf04 WN |
370 | if (type == THERMAL_TRIP_CRITICAL) { |
371 | return thermtrip_program(dev, sg, temp); | |
372 | } else if (type == THERMAL_TRIP_HOT) { | |
373 | int i; | |
374 | ||
375 | for (i = 0; i < THROTTLE_SIZE; i++) { | |
376 | struct thermal_cooling_device *cdev; | |
377 | struct soctherm_throt_cfg *stc; | |
2a895871 | 378 | |
ce0dbf04 WN |
379 | if (!ts->throt_cfgs[i].init) |
380 | continue; | |
381 | ||
382 | cdev = ts->throt_cfgs[i].cdev; | |
383 | if (get_thermal_instance(tz, cdev, trip)) | |
384 | stc = find_throttle_cfg_by_name(ts, cdev->type); | |
385 | else | |
386 | continue; | |
387 | ||
388 | return throttrip_program(dev, sg, stc, temp); | |
389 | } | |
390 | } | |
391 | ||
392 | return 0; | |
2a895871 WN |
393 | } |
394 | ||
65b6d57c WN |
395 | static const struct thermal_zone_of_device_ops tegra_of_thermal_ops = { |
396 | .get_temp = tegra_thermctl_get_temp, | |
2a895871 | 397 | .set_trip_temp = tegra_thermctl_set_trip_temp, |
65b6d57c WN |
398 | }; |
399 | ||
2a895871 WN |
400 | /** |
401 | * enforce_temp_range() - check and enforce temperature range [min, max] | |
402 | * @trip_temp: the trip temperature to check | |
403 | * | |
404 | * Checks and enforces the permitted temperature range that SOC_THERM | |
405 | * HW can support This is | |
406 | * done while taking care of precision. | |
407 | * | |
408 | * Return: The precision adjusted capped temperature in millicelsius. | |
409 | */ | |
410 | static int enforce_temp_range(struct device *dev, int trip_temp) | |
411 | { | |
412 | int temp; | |
413 | ||
414 | temp = clamp_val(trip_temp, min_low_temp, max_high_temp); | |
415 | if (temp != trip_temp) | |
416 | dev_info(dev, "soctherm: trip temperature %d forced to %d\n", | |
417 | trip_temp, temp); | |
418 | return temp; | |
419 | } | |
420 | ||
421 | /** | |
422 | * thermtrip_program() - Configures the hardware to shut down the | |
423 | * system if a given sensor group reaches a given temperature | |
424 | * @dev: ptr to the struct device for the SOC_THERM IP block | |
425 | * @sg: pointer to the sensor group to set the thermtrip temperature for | |
426 | * @trip_temp: the temperature in millicelsius to trigger the thermal trip at | |
427 | * | |
428 | * Sets the thermal trip threshold of the given sensor group to be the | |
429 | * @trip_temp. If this threshold is crossed, the hardware will shut | |
430 | * down. | |
431 | * | |
432 | * Note that, although @trip_temp is specified in millicelsius, the | |
433 | * hardware is programmed in degrees Celsius. | |
434 | * | |
435 | * Return: 0 upon success, or %-EINVAL upon failure. | |
436 | */ | |
437 | static int thermtrip_program(struct device *dev, | |
438 | const struct tegra_tsensor_group *sg, | |
439 | int trip_temp) | |
440 | { | |
441 | struct tegra_soctherm *ts = dev_get_drvdata(dev); | |
442 | int temp; | |
443 | u32 r; | |
444 | ||
f3500980 | 445 | if (!sg || !sg->thermtrip_threshold_mask) |
2a895871 WN |
446 | return -EINVAL; |
447 | ||
448 | temp = enforce_temp_range(dev, trip_temp) / ts->soc->thresh_grain; | |
449 | ||
450 | r = readl(ts->regs + THERMCTL_THERMTRIP_CTL); | |
451 | r = REG_SET_MASK(r, sg->thermtrip_threshold_mask, temp); | |
452 | r = REG_SET_MASK(r, sg->thermtrip_enable_mask, 1); | |
453 | r = REG_SET_MASK(r, sg->thermtrip_any_en_mask, 0); | |
454 | writel(r, ts->regs + THERMCTL_THERMTRIP_CTL); | |
455 | ||
456 | return 0; | |
457 | } | |
458 | ||
ce0dbf04 WN |
459 | /** |
460 | * throttrip_program() - Configures the hardware to throttle the | |
461 | * pulse if a given sensor group reaches a given temperature | |
462 | * @dev: ptr to the struct device for the SOC_THERM IP block | |
463 | * @sg: pointer to the sensor group to set the thermtrip temperature for | |
464 | * @stc: pointer to the throttle need to be triggered | |
465 | * @trip_temp: the temperature in millicelsius to trigger the thermal trip at | |
466 | * | |
467 | * Sets the thermal trip threshold and throttle event of the given sensor | |
468 | * group. If this threshold is crossed, the hardware will trigger the | |
469 | * throttle. | |
470 | * | |
471 | * Note that, although @trip_temp is specified in millicelsius, the | |
472 | * hardware is programmed in degrees Celsius. | |
473 | * | |
474 | * Return: 0 upon success, or %-EINVAL upon failure. | |
475 | */ | |
476 | static int throttrip_program(struct device *dev, | |
477 | const struct tegra_tsensor_group *sg, | |
478 | struct soctherm_throt_cfg *stc, | |
479 | int trip_temp) | |
480 | { | |
481 | struct tegra_soctherm *ts = dev_get_drvdata(dev); | |
482 | int temp, cpu_throt, gpu_throt; | |
483 | unsigned int throt; | |
484 | u32 r, reg_off; | |
485 | ||
486 | if (!dev || !sg || !stc || !stc->init) | |
487 | return -EINVAL; | |
488 | ||
489 | temp = enforce_temp_range(dev, trip_temp) / ts->soc->thresh_grain; | |
490 | ||
491 | /* Hardcode LIGHT on LEVEL1 and HEAVY on LEVEL2 */ | |
492 | throt = stc->id; | |
493 | reg_off = THERMCTL_LVL_REG(sg->thermctl_lvl0_offset, throt + 1); | |
494 | ||
495 | if (throt == THROTTLE_LIGHT) { | |
496 | cpu_throt = THERMCTL_LVL0_CPU0_CPU_THROT_LIGHT; | |
497 | gpu_throt = THERMCTL_LVL0_CPU0_GPU_THROT_LIGHT; | |
498 | } else { | |
499 | cpu_throt = THERMCTL_LVL0_CPU0_CPU_THROT_HEAVY; | |
500 | gpu_throt = THERMCTL_LVL0_CPU0_GPU_THROT_HEAVY; | |
501 | if (throt != THROTTLE_HEAVY) | |
502 | dev_warn(dev, | |
503 | "invalid throt id %d - assuming HEAVY", | |
504 | throt); | |
505 | } | |
506 | ||
507 | r = readl(ts->regs + reg_off); | |
508 | r = REG_SET_MASK(r, sg->thermctl_lvl0_up_thresh_mask, temp); | |
509 | r = REG_SET_MASK(r, sg->thermctl_lvl0_dn_thresh_mask, temp); | |
510 | r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_CPU_THROT_MASK, cpu_throt); | |
511 | r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_GPU_THROT_MASK, gpu_throt); | |
512 | r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_EN_MASK, 1); | |
513 | writel(r, ts->regs + reg_off); | |
514 | ||
515 | return 0; | |
516 | } | |
517 | ||
518 | static struct soctherm_throt_cfg * | |
519 | find_throttle_cfg_by_name(struct tegra_soctherm *ts, const char *name) | |
520 | { | |
521 | unsigned int i; | |
522 | ||
523 | for (i = 0; ts->throt_cfgs[i].name; i++) | |
524 | if (!strcmp(ts->throt_cfgs[i].name, name)) | |
525 | return &ts->throt_cfgs[i]; | |
526 | ||
527 | return NULL; | |
528 | } | |
529 | ||
530 | static int get_hot_temp(struct thermal_zone_device *tz, int *trip, int *temp) | |
531 | { | |
532 | int ntrips, i, ret; | |
533 | enum thermal_trip_type type; | |
534 | ||
535 | ntrips = of_thermal_get_ntrips(tz); | |
536 | if (ntrips <= 0) | |
537 | return -EINVAL; | |
538 | ||
539 | for (i = 0; i < ntrips; i++) { | |
540 | ret = tz->ops->get_trip_type(tz, i, &type); | |
541 | if (ret) | |
542 | return -EINVAL; | |
543 | if (type == THERMAL_TRIP_HOT) { | |
544 | ret = tz->ops->get_trip_temp(tz, i, temp); | |
545 | if (!ret) | |
546 | *trip = i; | |
547 | ||
548 | return ret; | |
549 | } | |
550 | } | |
551 | ||
552 | return -EINVAL; | |
553 | } | |
554 | ||
2a895871 WN |
555 | /** |
556 | * tegra_soctherm_set_hwtrips() - set HW trip point from DT data | |
557 | * @dev: struct device * of the SOC_THERM instance | |
558 | * | |
559 | * Configure the SOC_THERM HW trip points, setting "THERMTRIP" | |
ce0dbf04 WN |
560 | * "THROTTLE" trip points , using "critical" or "hot" type trip_temp |
561 | * from thermal zone. | |
562 | * After they have been configured, THERMTRIP or THROTTLE will take | |
563 | * action when the configured SoC thermal sensor group reaches a | |
2a895871 WN |
564 | * certain temperature. |
565 | * | |
566 | * Return: 0 upon success, or a negative error code on failure. | |
567 | * "Success" does not mean that trips was enabled; it could also | |
568 | * mean that no node was found in DT. | |
569 | * THERMTRIP has been enabled successfully when a message similar to | |
570 | * this one appears on the serial console: | |
571 | * "thermtrip: will shut down when sensor group XXX reaches YYYYYY mC" | |
ce0dbf04 WN |
572 | * THROTTLE has been enabled successfully when a message similar to |
573 | * this one appears on the serial console: | |
574 | * ""throttrip: will throttle when sensor group XXX reaches YYYYYY mC" | |
2a895871 WN |
575 | */ |
576 | static int tegra_soctherm_set_hwtrips(struct device *dev, | |
577 | const struct tegra_tsensor_group *sg, | |
578 | struct thermal_zone_device *tz) | |
579 | { | |
ce0dbf04 WN |
580 | struct tegra_soctherm *ts = dev_get_drvdata(dev); |
581 | struct soctherm_throt_cfg *stc; | |
582 | int i, trip, temperature; | |
2a895871 WN |
583 | int ret; |
584 | ||
585 | ret = tz->ops->get_crit_temp(tz, &temperature); | |
586 | if (ret) { | |
587 | dev_warn(dev, "thermtrip: %s: missing critical temperature\n", | |
588 | sg->name); | |
ce0dbf04 | 589 | goto set_throttle; |
2a895871 WN |
590 | } |
591 | ||
592 | ret = thermtrip_program(dev, sg, temperature); | |
593 | if (ret) { | |
594 | dev_err(dev, "thermtrip: %s: error during enable\n", | |
595 | sg->name); | |
596 | return ret; | |
597 | } | |
598 | ||
599 | dev_info(dev, | |
600 | "thermtrip: will shut down when %s reaches %d mC\n", | |
601 | sg->name, temperature); | |
602 | ||
ce0dbf04 | 603 | set_throttle: |
ce0dbf04 WN |
604 | ret = get_hot_temp(tz, &trip, &temperature); |
605 | if (ret) { | |
606 | dev_warn(dev, "throttrip: %s: missing hot temperature\n", | |
607 | sg->name); | |
608 | return 0; | |
609 | } | |
610 | ||
611 | for (i = 0; i < THROTTLE_SIZE; i++) { | |
612 | struct thermal_cooling_device *cdev; | |
613 | ||
614 | if (!ts->throt_cfgs[i].init) | |
615 | continue; | |
616 | ||
617 | cdev = ts->throt_cfgs[i].cdev; | |
618 | if (get_thermal_instance(tz, cdev, trip)) | |
619 | stc = find_throttle_cfg_by_name(ts, cdev->type); | |
620 | else | |
621 | continue; | |
622 | ||
623 | ret = throttrip_program(dev, sg, stc, temperature); | |
624 | if (ret) { | |
625 | dev_err(dev, "throttrip: %s: error during enable\n", | |
626 | sg->name); | |
627 | return ret; | |
628 | } | |
629 | ||
630 | dev_info(dev, | |
631 | "throttrip: will throttle when %s reaches %d mC\n", | |
632 | sg->name, temperature); | |
633 | break; | |
634 | } | |
635 | ||
636 | if (i == THROTTLE_SIZE) | |
637 | dev_warn(dev, "throttrip: %s: missing throttle cdev\n", | |
638 | sg->name); | |
639 | ||
2a895871 WN |
640 | return 0; |
641 | } | |
642 | ||
d753b22d WN |
643 | #ifdef CONFIG_DEBUG_FS |
644 | static int regs_show(struct seq_file *s, void *data) | |
645 | { | |
646 | struct platform_device *pdev = s->private; | |
647 | struct tegra_soctherm *ts = platform_get_drvdata(pdev); | |
648 | const struct tegra_tsensor *tsensors = ts->soc->tsensors; | |
2a895871 | 649 | const struct tegra_tsensor_group **ttgs = ts->soc->ttgs; |
d753b22d | 650 | u32 r, state; |
ce0dbf04 | 651 | int i, level; |
d753b22d WN |
652 | |
653 | seq_puts(s, "-----TSENSE (convert HW)-----\n"); | |
654 | ||
655 | for (i = 0; i < ts->soc->num_tsensors; i++) { | |
656 | r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG1); | |
657 | state = REG_GET_MASK(r, SENSOR_CONFIG1_TEMP_ENABLE); | |
658 | ||
659 | seq_printf(s, "%s: ", tsensors[i].name); | |
660 | seq_printf(s, "En(%d) ", state); | |
661 | ||
662 | if (!state) { | |
663 | seq_puts(s, "\n"); | |
664 | continue; | |
665 | } | |
666 | ||
667 | state = REG_GET_MASK(r, SENSOR_CONFIG1_TIDDQ_EN_MASK); | |
668 | seq_printf(s, "tiddq(%d) ", state); | |
669 | state = REG_GET_MASK(r, SENSOR_CONFIG1_TEN_COUNT_MASK); | |
670 | seq_printf(s, "ten_count(%d) ", state); | |
671 | state = REG_GET_MASK(r, SENSOR_CONFIG1_TSAMPLE_MASK); | |
672 | seq_printf(s, "tsample(%d) ", state + 1); | |
673 | ||
674 | r = readl(ts->regs + tsensors[i].base + SENSOR_STATUS1); | |
675 | state = REG_GET_MASK(r, SENSOR_STATUS1_TEMP_VALID_MASK); | |
676 | seq_printf(s, "Temp(%d/", state); | |
677 | state = REG_GET_MASK(r, SENSOR_STATUS1_TEMP_MASK); | |
678 | seq_printf(s, "%d) ", translate_temp(state)); | |
679 | ||
680 | r = readl(ts->regs + tsensors[i].base + SENSOR_STATUS0); | |
681 | state = REG_GET_MASK(r, SENSOR_STATUS0_VALID_MASK); | |
682 | seq_printf(s, "Capture(%d/", state); | |
683 | state = REG_GET_MASK(r, SENSOR_STATUS0_CAPTURE_MASK); | |
684 | seq_printf(s, "%d) ", state); | |
685 | ||
686 | r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG0); | |
687 | state = REG_GET_MASK(r, SENSOR_CONFIG0_STOP); | |
688 | seq_printf(s, "Stop(%d) ", state); | |
689 | state = REG_GET_MASK(r, SENSOR_CONFIG0_TALL_MASK); | |
690 | seq_printf(s, "Tall(%d) ", state); | |
691 | state = REG_GET_MASK(r, SENSOR_CONFIG0_TCALC_OVER); | |
692 | seq_printf(s, "Over(%d/", state); | |
693 | state = REG_GET_MASK(r, SENSOR_CONFIG0_OVER); | |
694 | seq_printf(s, "%d/", state); | |
695 | state = REG_GET_MASK(r, SENSOR_CONFIG0_CPTR_OVER); | |
696 | seq_printf(s, "%d) ", state); | |
697 | ||
698 | r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG2); | |
699 | state = REG_GET_MASK(r, SENSOR_CONFIG2_THERMA_MASK); | |
700 | seq_printf(s, "Therm_A/B(%d/", state); | |
701 | state = REG_GET_MASK(r, SENSOR_CONFIG2_THERMB_MASK); | |
702 | seq_printf(s, "%d)\n", (s16)state); | |
703 | } | |
704 | ||
705 | r = readl(ts->regs + SENSOR_PDIV); | |
706 | seq_printf(s, "PDIV: 0x%x\n", r); | |
707 | ||
708 | r = readl(ts->regs + SENSOR_HOTSPOT_OFF); | |
709 | seq_printf(s, "HOTSPOT: 0x%x\n", r); | |
710 | ||
711 | seq_puts(s, "\n"); | |
712 | seq_puts(s, "-----SOC_THERM-----\n"); | |
713 | ||
714 | r = readl(ts->regs + SENSOR_TEMP1); | |
715 | state = REG_GET_MASK(r, SENSOR_TEMP1_CPU_TEMP_MASK); | |
716 | seq_printf(s, "Temperatures: CPU(%d) ", translate_temp(state)); | |
717 | state = REG_GET_MASK(r, SENSOR_TEMP1_GPU_TEMP_MASK); | |
718 | seq_printf(s, " GPU(%d) ", translate_temp(state)); | |
719 | r = readl(ts->regs + SENSOR_TEMP2); | |
720 | state = REG_GET_MASK(r, SENSOR_TEMP2_PLLX_TEMP_MASK); | |
721 | seq_printf(s, " PLLX(%d) ", translate_temp(state)); | |
722 | state = REG_GET_MASK(r, SENSOR_TEMP2_MEM_TEMP_MASK); | |
723 | seq_printf(s, " MEM(%d)\n", translate_temp(state)); | |
724 | ||
ce0dbf04 WN |
725 | for (i = 0; i < ts->soc->num_ttgs; i++) { |
726 | seq_printf(s, "%s:\n", ttgs[i]->name); | |
727 | for (level = 0; level < 4; level++) { | |
728 | s32 v; | |
729 | u32 mask; | |
730 | u16 off = ttgs[i]->thermctl_lvl0_offset; | |
731 | ||
732 | r = readl(ts->regs + THERMCTL_LVL_REG(off, level)); | |
733 | ||
734 | mask = ttgs[i]->thermctl_lvl0_up_thresh_mask; | |
735 | state = REG_GET_MASK(r, mask); | |
736 | v = sign_extend32(state, ts->soc->bptt - 1); | |
737 | v *= ts->soc->thresh_grain; | |
738 | seq_printf(s, " %d: Up/Dn(%d /", level, v); | |
739 | ||
740 | mask = ttgs[i]->thermctl_lvl0_dn_thresh_mask; | |
741 | state = REG_GET_MASK(r, mask); | |
742 | v = sign_extend32(state, ts->soc->bptt - 1); | |
743 | v *= ts->soc->thresh_grain; | |
744 | seq_printf(s, "%d ) ", v); | |
745 | ||
746 | mask = THERMCTL_LVL0_CPU0_EN_MASK; | |
747 | state = REG_GET_MASK(r, mask); | |
748 | seq_printf(s, "En(%d) ", state); | |
749 | ||
750 | mask = THERMCTL_LVL0_CPU0_CPU_THROT_MASK; | |
751 | state = REG_GET_MASK(r, mask); | |
752 | seq_puts(s, "CPU Throt"); | |
753 | if (!state) | |
754 | seq_printf(s, "(%s) ", "none"); | |
755 | else if (state == THERMCTL_LVL0_CPU0_CPU_THROT_LIGHT) | |
756 | seq_printf(s, "(%s) ", "L"); | |
757 | else if (state == THERMCTL_LVL0_CPU0_CPU_THROT_HEAVY) | |
758 | seq_printf(s, "(%s) ", "H"); | |
759 | else | |
760 | seq_printf(s, "(%s) ", "H+L"); | |
761 | ||
762 | mask = THERMCTL_LVL0_CPU0_GPU_THROT_MASK; | |
763 | state = REG_GET_MASK(r, mask); | |
764 | seq_puts(s, "GPU Throt"); | |
765 | if (!state) | |
766 | seq_printf(s, "(%s) ", "none"); | |
767 | else if (state == THERMCTL_LVL0_CPU0_GPU_THROT_LIGHT) | |
768 | seq_printf(s, "(%s) ", "L"); | |
769 | else if (state == THERMCTL_LVL0_CPU0_GPU_THROT_HEAVY) | |
770 | seq_printf(s, "(%s) ", "H"); | |
771 | else | |
772 | seq_printf(s, "(%s) ", "H+L"); | |
773 | ||
774 | mask = THERMCTL_LVL0_CPU0_STATUS_MASK; | |
775 | state = REG_GET_MASK(r, mask); | |
776 | seq_printf(s, "Status(%s)\n", | |
777 | state == 0 ? "LO" : | |
778 | state == 1 ? "In" : | |
779 | state == 2 ? "Res" : "HI"); | |
780 | } | |
781 | } | |
782 | ||
783 | r = readl(ts->regs + THERMCTL_STATS_CTL); | |
784 | seq_printf(s, "STATS: Up(%s) Dn(%s)\n", | |
785 | r & STATS_CTL_EN_UP ? "En" : "--", | |
786 | r & STATS_CTL_EN_DN ? "En" : "--"); | |
787 | ||
788 | for (level = 0; level < 4; level++) { | |
789 | u16 off; | |
790 | ||
791 | off = THERMCTL_LVL0_UP_STATS; | |
792 | r = readl(ts->regs + THERMCTL_LVL_REG(off, level)); | |
793 | seq_printf(s, " Level_%d Up(%d) ", level, r); | |
794 | ||
795 | off = THERMCTL_LVL0_DN_STATS; | |
796 | r = readl(ts->regs + THERMCTL_LVL_REG(off, level)); | |
797 | seq_printf(s, "Dn(%d)\n", r); | |
798 | } | |
799 | ||
2a895871 WN |
800 | r = readl(ts->regs + THERMCTL_THERMTRIP_CTL); |
801 | state = REG_GET_MASK(r, ttgs[0]->thermtrip_any_en_mask); | |
802 | seq_printf(s, "Thermtrip Any En(%d)\n", state); | |
803 | for (i = 0; i < ts->soc->num_ttgs; i++) { | |
804 | state = REG_GET_MASK(r, ttgs[i]->thermtrip_enable_mask); | |
805 | seq_printf(s, " %s En(%d) ", ttgs[i]->name, state); | |
806 | state = REG_GET_MASK(r, ttgs[i]->thermtrip_threshold_mask); | |
807 | state *= ts->soc->thresh_grain; | |
808 | seq_printf(s, "Thresh(%d)\n", state); | |
809 | } | |
810 | ||
ce0dbf04 WN |
811 | r = readl(ts->regs + THROT_GLOBAL_CFG); |
812 | seq_puts(s, "\n"); | |
813 | seq_printf(s, "GLOBAL THROTTLE CONFIG: 0x%08x\n", r); | |
814 | ||
815 | seq_puts(s, "---------------------------------------------------\n"); | |
816 | r = readl(ts->regs + THROT_STATUS); | |
817 | state = REG_GET_MASK(r, THROT_STATUS_BREACH_MASK); | |
818 | seq_printf(s, "THROT STATUS: breach(%d) ", state); | |
819 | state = REG_GET_MASK(r, THROT_STATUS_STATE_MASK); | |
820 | seq_printf(s, "state(%d) ", state); | |
821 | state = REG_GET_MASK(r, THROT_STATUS_ENABLED_MASK); | |
822 | seq_printf(s, "enabled(%d)\n", state); | |
823 | ||
824 | r = readl(ts->regs + CPU_PSKIP_STATUS); | |
6c7c3245 WN |
825 | if (ts->soc->use_ccroc) { |
826 | state = REG_GET_MASK(r, XPU_PSKIP_STATUS_ENABLED_MASK); | |
827 | seq_printf(s, "CPU PSKIP STATUS: enabled(%d)\n", state); | |
828 | } else { | |
829 | state = REG_GET_MASK(r, XPU_PSKIP_STATUS_M_MASK); | |
830 | seq_printf(s, "CPU PSKIP STATUS: M(%d) ", state); | |
831 | state = REG_GET_MASK(r, XPU_PSKIP_STATUS_N_MASK); | |
832 | seq_printf(s, "N(%d) ", state); | |
833 | state = REG_GET_MASK(r, XPU_PSKIP_STATUS_ENABLED_MASK); | |
834 | seq_printf(s, "enabled(%d)\n", state); | |
835 | } | |
ce0dbf04 | 836 | |
d753b22d WN |
837 | return 0; |
838 | } | |
839 | ||
840 | static int regs_open(struct inode *inode, struct file *file) | |
841 | { | |
842 | return single_open(file, regs_show, inode->i_private); | |
843 | } | |
844 | ||
845 | static const struct file_operations regs_fops = { | |
846 | .open = regs_open, | |
847 | .read = seq_read, | |
848 | .llseek = seq_lseek, | |
849 | .release = single_release, | |
850 | }; | |
851 | ||
852 | static void soctherm_debug_init(struct platform_device *pdev) | |
853 | { | |
854 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
855 | struct dentry *root, *file; | |
856 | ||
857 | root = debugfs_create_dir("soctherm", NULL); | |
858 | if (!root) { | |
859 | dev_err(&pdev->dev, "failed to create debugfs directory\n"); | |
860 | return; | |
861 | } | |
862 | ||
863 | tegra->debugfs_dir = root; | |
864 | ||
865 | file = debugfs_create_file("reg_contents", 0644, root, | |
866 | pdev, ®s_fops); | |
867 | if (!file) { | |
868 | dev_err(&pdev->dev, "failed to create debugfs file\n"); | |
869 | debugfs_remove_recursive(tegra->debugfs_dir); | |
870 | tegra->debugfs_dir = NULL; | |
871 | } | |
872 | } | |
873 | #else | |
874 | static inline void soctherm_debug_init(struct platform_device *pdev) {} | |
875 | #endif | |
876 | ||
8de2ab02 WN |
877 | static int soctherm_clk_enable(struct platform_device *pdev, bool enable) |
878 | { | |
879 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
880 | int err; | |
881 | ||
882 | if (!tegra->clock_soctherm || !tegra->clock_tsensor) | |
883 | return -EINVAL; | |
884 | ||
885 | reset_control_assert(tegra->reset); | |
886 | ||
887 | if (enable) { | |
888 | err = clk_prepare_enable(tegra->clock_soctherm); | |
889 | if (err) { | |
890 | reset_control_deassert(tegra->reset); | |
891 | return err; | |
892 | } | |
893 | ||
894 | err = clk_prepare_enable(tegra->clock_tsensor); | |
895 | if (err) { | |
896 | clk_disable_unprepare(tegra->clock_soctherm); | |
897 | reset_control_deassert(tegra->reset); | |
898 | return err; | |
899 | } | |
900 | } else { | |
901 | clk_disable_unprepare(tegra->clock_tsensor); | |
902 | clk_disable_unprepare(tegra->clock_soctherm); | |
903 | } | |
904 | ||
905 | reset_control_deassert(tegra->reset); | |
906 | ||
907 | return 0; | |
908 | } | |
909 | ||
ce0dbf04 WN |
910 | static int throt_get_cdev_max_state(struct thermal_cooling_device *cdev, |
911 | unsigned long *max_state) | |
912 | { | |
913 | *max_state = 1; | |
914 | return 0; | |
915 | } | |
916 | ||
917 | static int throt_get_cdev_cur_state(struct thermal_cooling_device *cdev, | |
918 | unsigned long *cur_state) | |
919 | { | |
920 | struct tegra_soctherm *ts = cdev->devdata; | |
921 | u32 r; | |
922 | ||
923 | r = readl(ts->regs + THROT_STATUS); | |
924 | if (REG_GET_MASK(r, THROT_STATUS_STATE_MASK)) | |
925 | *cur_state = 1; | |
926 | else | |
927 | *cur_state = 0; | |
928 | ||
929 | return 0; | |
930 | } | |
931 | ||
932 | static int throt_set_cdev_state(struct thermal_cooling_device *cdev, | |
933 | unsigned long cur_state) | |
934 | { | |
935 | return 0; | |
936 | } | |
937 | ||
938 | static struct thermal_cooling_device_ops throt_cooling_ops = { | |
939 | .get_max_state = throt_get_cdev_max_state, | |
940 | .get_cur_state = throt_get_cdev_cur_state, | |
941 | .set_cur_state = throt_set_cdev_state, | |
942 | }; | |
943 | ||
944 | /** | |
945 | * soctherm_init_hw_throt_cdev() - Parse the HW throttle configurations | |
946 | * and register them as cooling devices. | |
947 | */ | |
948 | static void soctherm_init_hw_throt_cdev(struct platform_device *pdev) | |
949 | { | |
950 | struct device *dev = &pdev->dev; | |
951 | struct tegra_soctherm *ts = dev_get_drvdata(dev); | |
952 | struct device_node *np_stc, *np_stcc; | |
953 | const char *name; | |
954 | u32 val; | |
955 | int i, r; | |
956 | ||
957 | for (i = 0; i < THROTTLE_SIZE; i++) { | |
958 | ts->throt_cfgs[i].name = throt_names[i]; | |
959 | ts->throt_cfgs[i].id = i; | |
960 | ts->throt_cfgs[i].init = false; | |
961 | } | |
962 | ||
963 | np_stc = of_get_child_by_name(dev->of_node, "throttle-cfgs"); | |
964 | if (!np_stc) { | |
965 | dev_info(dev, | |
966 | "throttle-cfg: no throttle-cfgs - not enabling\n"); | |
967 | return; | |
968 | } | |
969 | ||
970 | for_each_child_of_node(np_stc, np_stcc) { | |
971 | struct soctherm_throt_cfg *stc; | |
972 | struct thermal_cooling_device *tcd; | |
973 | ||
974 | name = np_stcc->name; | |
975 | stc = find_throttle_cfg_by_name(ts, name); | |
976 | if (!stc) { | |
977 | dev_err(dev, | |
978 | "throttle-cfg: could not find %s\n", name); | |
979 | continue; | |
980 | } | |
981 | ||
982 | r = of_property_read_u32(np_stcc, "nvidia,priority", &val); | |
983 | if (r) { | |
984 | dev_info(dev, | |
985 | "throttle-cfg: %s: missing priority\n", name); | |
986 | continue; | |
987 | } | |
988 | stc->priority = val; | |
989 | ||
6c7c3245 WN |
990 | if (ts->soc->use_ccroc) { |
991 | r = of_property_read_u32(np_stcc, | |
992 | "nvidia,cpu-throt-level", | |
993 | &val); | |
994 | if (r) { | |
995 | dev_info(dev, | |
996 | "throttle-cfg: %s: missing cpu-throt-level\n", | |
997 | name); | |
998 | continue; | |
999 | } | |
1000 | stc->cpu_throt_level = val; | |
1001 | } else { | |
1002 | r = of_property_read_u32(np_stcc, | |
1003 | "nvidia,cpu-throt-percent", | |
1004 | &val); | |
1005 | if (r) { | |
1006 | dev_info(dev, | |
1007 | "throttle-cfg: %s: missing cpu-throt-percent\n", | |
1008 | name); | |
1009 | continue; | |
1010 | } | |
1011 | stc->cpu_throt_depth = val; | |
ce0dbf04 | 1012 | } |
ce0dbf04 WN |
1013 | |
1014 | tcd = thermal_of_cooling_device_register(np_stcc, | |
1015 | (char *)name, ts, | |
1016 | &throt_cooling_ops); | |
1017 | of_node_put(np_stcc); | |
1018 | if (IS_ERR_OR_NULL(tcd)) { | |
1019 | dev_err(dev, | |
1020 | "throttle-cfg: %s: failed to register cooling device\n", | |
1021 | name); | |
1022 | continue; | |
1023 | } | |
1024 | ||
1025 | stc->cdev = tcd; | |
1026 | stc->init = true; | |
1027 | } | |
1028 | ||
1029 | of_node_put(np_stc); | |
1030 | } | |
1031 | ||
6c7c3245 WN |
1032 | /** |
1033 | * throttlectl_cpu_level_cfg() - programs CCROC NV_THERM level config | |
1034 | * @level: describing the level LOW/MED/HIGH of throttling | |
1035 | * | |
1036 | * It's necessary to set up the CPU-local CCROC NV_THERM instance with | |
1037 | * the M/N values desired for each level. This function does this. | |
1038 | * | |
1039 | * This function pre-programs the CCROC NV_THERM levels in terms of | |
1040 | * pre-configured "Low", "Medium" or "Heavy" throttle levels which are | |
1041 | * mapped to THROT_LEVEL_LOW, THROT_LEVEL_MED and THROT_LEVEL_HVY. | |
1042 | */ | |
1043 | static void throttlectl_cpu_level_cfg(struct tegra_soctherm *ts, int level) | |
1044 | { | |
1045 | u8 depth, dividend; | |
1046 | u32 r; | |
1047 | ||
1048 | switch (level) { | |
1049 | case TEGRA_SOCTHERM_THROT_LEVEL_LOW: | |
1050 | depth = 50; | |
1051 | break; | |
1052 | case TEGRA_SOCTHERM_THROT_LEVEL_MED: | |
1053 | depth = 75; | |
1054 | break; | |
1055 | case TEGRA_SOCTHERM_THROT_LEVEL_HIGH: | |
1056 | depth = 80; | |
1057 | break; | |
1058 | case TEGRA_SOCTHERM_THROT_LEVEL_NONE: | |
1059 | return; | |
1060 | default: | |
1061 | return; | |
1062 | } | |
1063 | ||
1064 | dividend = THROT_DEPTH_DIVIDEND(depth); | |
1065 | ||
1066 | /* setup PSKIP in ccroc nv_therm registers */ | |
1067 | r = ccroc_readl(ts, CCROC_THROT_PSKIP_RAMP_CPU_REG(level)); | |
1068 | r = REG_SET_MASK(r, CCROC_THROT_PSKIP_RAMP_DURATION_MASK, 0xff); | |
1069 | r = REG_SET_MASK(r, CCROC_THROT_PSKIP_RAMP_STEP_MASK, 0xf); | |
1070 | ccroc_writel(ts, r, CCROC_THROT_PSKIP_RAMP_CPU_REG(level)); | |
1071 | ||
1072 | r = ccroc_readl(ts, CCROC_THROT_PSKIP_CTRL_CPU_REG(level)); | |
1073 | r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_ENB_MASK, 1); | |
1074 | r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_DIVIDEND_MASK, dividend); | |
1075 | r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_DIVISOR_MASK, 0xff); | |
1076 | ccroc_writel(ts, r, CCROC_THROT_PSKIP_CTRL_CPU_REG(level)); | |
1077 | } | |
1078 | ||
1079 | /** | |
1080 | * throttlectl_cpu_level_select() - program CPU pulse skipper config | |
1081 | * @throt: the LIGHT/HEAVY of throttle event id | |
1082 | * | |
1083 | * Pulse skippers are used to throttle clock frequencies. This | |
1084 | * function programs the pulse skippers based on @throt and platform | |
1085 | * data. This function is used on SoCs which have CPU-local pulse | |
1086 | * skipper control, such as T13x. It programs soctherm's interface to | |
1087 | * Denver:CCROC NV_THERM in terms of Low, Medium and HIGH throttling | |
1088 | * vectors. PSKIP_BYPASS mode is set as required per HW spec. | |
1089 | */ | |
1090 | static void throttlectl_cpu_level_select(struct tegra_soctherm *ts, | |
1091 | enum soctherm_throttle_id throt) | |
1092 | { | |
1093 | u32 r, throt_vect; | |
1094 | ||
1095 | /* Denver:CCROC NV_THERM interface N:3 Mapping */ | |
1096 | switch (ts->throt_cfgs[throt].cpu_throt_level) { | |
1097 | case TEGRA_SOCTHERM_THROT_LEVEL_LOW: | |
1098 | throt_vect = THROT_VECT_LOW; | |
1099 | break; | |
1100 | case TEGRA_SOCTHERM_THROT_LEVEL_MED: | |
1101 | throt_vect = THROT_VECT_MED; | |
1102 | break; | |
1103 | case TEGRA_SOCTHERM_THROT_LEVEL_HIGH: | |
1104 | throt_vect = THROT_VECT_HIGH; | |
1105 | break; | |
1106 | default: | |
1107 | throt_vect = THROT_VECT_NONE; | |
1108 | break; | |
1109 | } | |
1110 | ||
1111 | r = readl(ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); | |
1112 | r = REG_SET_MASK(r, THROT_PSKIP_CTRL_ENABLE_MASK, 1); | |
1113 | r = REG_SET_MASK(r, THROT_PSKIP_CTRL_VECT_CPU_MASK, throt_vect); | |
1114 | r = REG_SET_MASK(r, THROT_PSKIP_CTRL_VECT2_CPU_MASK, throt_vect); | |
1115 | writel(r, ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); | |
1116 | ||
1117 | /* bypass sequencer in soc_therm as it is programmed in ccroc */ | |
1118 | r = REG_SET_MASK(0, THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK, 1); | |
1119 | writel(r, ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); | |
1120 | } | |
1121 | ||
ce0dbf04 WN |
1122 | /** |
1123 | * throttlectl_cpu_mn() - program CPU pulse skipper configuration | |
1124 | * @throt: the LIGHT/HEAVY of throttle event id | |
1125 | * | |
1126 | * Pulse skippers are used to throttle clock frequencies. This | |
1127 | * function programs the pulse skippers based on @throt and platform | |
1128 | * data. This function is used for CPUs that have "remote" pulse | |
1129 | * skipper control, e.g., the CPU pulse skipper is controlled by the | |
1130 | * SOC_THERM IP block. (SOC_THERM is located outside the CPU | |
1131 | * complex.) | |
1132 | */ | |
1133 | static void throttlectl_cpu_mn(struct tegra_soctherm *ts, | |
1134 | enum soctherm_throttle_id throt) | |
1135 | { | |
1136 | u32 r; | |
1137 | int depth; | |
1138 | u8 dividend; | |
1139 | ||
1140 | depth = ts->throt_cfgs[throt].cpu_throt_depth; | |
1141 | dividend = THROT_DEPTH_DIVIDEND(depth); | |
1142 | ||
1143 | r = readl(ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); | |
1144 | r = REG_SET_MASK(r, THROT_PSKIP_CTRL_ENABLE_MASK, 1); | |
1145 | r = REG_SET_MASK(r, THROT_PSKIP_CTRL_DIVIDEND_MASK, dividend); | |
1146 | r = REG_SET_MASK(r, THROT_PSKIP_CTRL_DIVISOR_MASK, 0xff); | |
1147 | writel(r, ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); | |
1148 | ||
1149 | r = readl(ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); | |
1150 | r = REG_SET_MASK(r, THROT_PSKIP_RAMP_DURATION_MASK, 0xff); | |
1151 | r = REG_SET_MASK(r, THROT_PSKIP_RAMP_STEP_MASK, 0xf); | |
1152 | writel(r, ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); | |
1153 | } | |
1154 | ||
1155 | /** | |
1156 | * soctherm_throttle_program() - programs pulse skippers' configuration | |
1157 | * @throt: the LIGHT/HEAVY of the throttle event id. | |
1158 | * | |
1159 | * Pulse skippers are used to throttle clock frequencies. | |
1160 | * This function programs the pulse skippers. | |
1161 | */ | |
1162 | static void soctherm_throttle_program(struct tegra_soctherm *ts, | |
1163 | enum soctherm_throttle_id throt) | |
1164 | { | |
1165 | u32 r; | |
1166 | struct soctherm_throt_cfg stc = ts->throt_cfgs[throt]; | |
1167 | ||
1168 | if (!stc.init) | |
1169 | return; | |
1170 | ||
1171 | /* Setup PSKIP parameters */ | |
6c7c3245 WN |
1172 | if (ts->soc->use_ccroc) |
1173 | throttlectl_cpu_level_select(ts, throt); | |
1174 | else | |
1175 | throttlectl_cpu_mn(ts, throt); | |
ce0dbf04 WN |
1176 | |
1177 | r = REG_SET_MASK(0, THROT_PRIORITY_LITE_PRIO_MASK, stc.priority); | |
1178 | writel(r, ts->regs + THROT_PRIORITY_CTRL(throt)); | |
1179 | ||
1180 | r = REG_SET_MASK(0, THROT_DELAY_LITE_DELAY_MASK, 0); | |
1181 | writel(r, ts->regs + THROT_DELAY_CTRL(throt)); | |
1182 | ||
1183 | r = readl(ts->regs + THROT_PRIORITY_LOCK); | |
1184 | r = REG_GET_MASK(r, THROT_PRIORITY_LOCK_PRIORITY_MASK); | |
1185 | if (r >= stc.priority) | |
1186 | return; | |
1187 | r = REG_SET_MASK(0, THROT_PRIORITY_LOCK_PRIORITY_MASK, | |
1188 | stc.priority); | |
1189 | writel(r, ts->regs + THROT_PRIORITY_LOCK); | |
1190 | } | |
1191 | ||
1192 | static void tegra_soctherm_throttle(struct device *dev) | |
1193 | { | |
1194 | struct tegra_soctherm *ts = dev_get_drvdata(dev); | |
1195 | u32 v; | |
1196 | int i; | |
1197 | ||
6c7c3245 WN |
1198 | /* configure LOW, MED and HIGH levels for CCROC NV_THERM */ |
1199 | if (ts->soc->use_ccroc) { | |
1200 | throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_LOW); | |
1201 | throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_MED); | |
1202 | throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_HIGH); | |
1203 | } | |
1204 | ||
ce0dbf04 WN |
1205 | /* Thermal HW throttle programming */ |
1206 | for (i = 0; i < THROTTLE_SIZE; i++) | |
1207 | soctherm_throttle_program(ts, i); | |
1208 | ||
1209 | v = REG_SET_MASK(0, THROT_GLOBAL_ENB_MASK, 1); | |
6c7c3245 WN |
1210 | if (ts->soc->use_ccroc) { |
1211 | ccroc_writel(ts, v, CCROC_GLOBAL_CFG); | |
ce0dbf04 | 1212 | |
6c7c3245 WN |
1213 | v = ccroc_readl(ts, CCROC_SUPER_CCLKG_DIVIDER); |
1214 | v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1); | |
1215 | ccroc_writel(ts, v, CCROC_SUPER_CCLKG_DIVIDER); | |
1216 | } else { | |
1217 | writel(v, ts->regs + THROT_GLOBAL_CFG); | |
1218 | ||
1219 | v = clk_readl(ts, CAR_SUPER_CCLKG_DIVIDER); | |
1220 | v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1); | |
1221 | clk_writel(ts, v, CAR_SUPER_CCLKG_DIVIDER); | |
1222 | } | |
ce0dbf04 WN |
1223 | |
1224 | /* initialize stats collection */ | |
1225 | v = STATS_CTL_CLR_DN | STATS_CTL_EN_DN | | |
1226 | STATS_CTL_CLR_UP | STATS_CTL_EN_UP; | |
1227 | writel(v, ts->regs + THERMCTL_STATS_CTL); | |
1228 | } | |
1229 | ||
1ed895c2 WN |
1230 | static void soctherm_init(struct platform_device *pdev) |
1231 | { | |
1232 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
1233 | const struct tegra_tsensor_group **ttgs = tegra->soc->ttgs; | |
1234 | int i; | |
1235 | u32 pdiv, hotspot; | |
1236 | ||
1237 | /* Initialize raw sensors */ | |
1238 | for (i = 0; i < tegra->soc->num_tsensors; ++i) | |
1239 | enable_tsensor(tegra, i); | |
1240 | ||
1241 | /* program pdiv and hotspot offsets per THERM */ | |
1242 | pdiv = readl(tegra->regs + SENSOR_PDIV); | |
1243 | hotspot = readl(tegra->regs + SENSOR_HOTSPOT_OFF); | |
1244 | for (i = 0; i < tegra->soc->num_ttgs; ++i) { | |
1245 | pdiv = REG_SET_MASK(pdiv, ttgs[i]->pdiv_mask, | |
1246 | ttgs[i]->pdiv); | |
1247 | /* hotspot offset from PLLX, doesn't need to configure PLLX */ | |
1248 | if (ttgs[i]->id == TEGRA124_SOCTHERM_SENSOR_PLLX) | |
1249 | continue; | |
1250 | hotspot = REG_SET_MASK(hotspot, | |
1251 | ttgs[i]->pllx_hotspot_mask, | |
1252 | ttgs[i]->pllx_hotspot_diff); | |
1253 | } | |
1254 | writel(pdiv, tegra->regs + SENSOR_PDIV); | |
1255 | writel(hotspot, tegra->regs + SENSOR_HOTSPOT_OFF); | |
ce0dbf04 | 1256 | |
ce0dbf04 WN |
1257 | /* Configure hw throttle */ |
1258 | tegra_soctherm_throttle(&pdev->dev); | |
1ed895c2 WN |
1259 | } |
1260 | ||
65b6d57c WN |
1261 | static const struct of_device_id tegra_soctherm_of_match[] = { |
1262 | #ifdef CONFIG_ARCH_TEGRA_124_SOC | |
1263 | { | |
1264 | .compatible = "nvidia,tegra124-soctherm", | |
1265 | .data = &tegra124_soctherm, | |
1266 | }, | |
8204104f | 1267 | #endif |
44cb6a7d WN |
1268 | #ifdef CONFIG_ARCH_TEGRA_132_SOC |
1269 | { | |
1270 | .compatible = "nvidia,tegra132-soctherm", | |
1271 | .data = &tegra132_soctherm, | |
1272 | }, | |
1273 | #endif | |
8204104f WN |
1274 | #ifdef CONFIG_ARCH_TEGRA_210_SOC |
1275 | { | |
1276 | .compatible = "nvidia,tegra210-soctherm", | |
1277 | .data = &tegra210_soctherm, | |
1278 | }, | |
65b6d57c WN |
1279 | #endif |
1280 | { }, | |
1281 | }; | |
1282 | MODULE_DEVICE_TABLE(of, tegra_soctherm_of_match); | |
1283 | ||
1284 | static int tegra_soctherm_probe(struct platform_device *pdev) | |
1285 | { | |
1286 | const struct of_device_id *match; | |
1287 | struct tegra_soctherm *tegra; | |
1288 | struct thermal_zone_device *z; | |
1289 | struct tsensor_shared_calib shared_calib; | |
1290 | struct resource *res; | |
1291 | struct tegra_soctherm_soc *soc; | |
1292 | unsigned int i; | |
1293 | int err; | |
65b6d57c WN |
1294 | |
1295 | match = of_match_node(tegra_soctherm_of_match, pdev->dev.of_node); | |
1296 | if (!match) | |
1297 | return -ENODEV; | |
1298 | ||
1299 | soc = (struct tegra_soctherm_soc *)match->data; | |
1300 | if (soc->num_ttgs > TEGRA124_SOCTHERM_SENSOR_NUM) | |
1301 | return -EINVAL; | |
1302 | ||
1303 | tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); | |
1304 | if (!tegra) | |
1305 | return -ENOMEM; | |
1306 | ||
1307 | dev_set_drvdata(&pdev->dev, tegra); | |
1308 | ||
1309 | tegra->soc = soc; | |
1310 | ||
ce0dbf04 WN |
1311 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
1312 | "soctherm-reg"); | |
65b6d57c | 1313 | tegra->regs = devm_ioremap_resource(&pdev->dev, res); |
ce0dbf04 WN |
1314 | if (IS_ERR(tegra->regs)) { |
1315 | dev_err(&pdev->dev, "can't get soctherm registers"); | |
65b6d57c | 1316 | return PTR_ERR(tegra->regs); |
ce0dbf04 WN |
1317 | } |
1318 | ||
1319 | if (!tegra->soc->use_ccroc) { | |
1320 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, | |
1321 | "car-reg"); | |
1322 | tegra->clk_regs = devm_ioremap_resource(&pdev->dev, res); | |
1323 | if (IS_ERR(tegra->clk_regs)) { | |
1324 | dev_err(&pdev->dev, "can't get car clk registers"); | |
1325 | return PTR_ERR(tegra->clk_regs); | |
1326 | } | |
6c7c3245 WN |
1327 | } else { |
1328 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, | |
1329 | "ccroc-reg"); | |
1330 | tegra->ccroc_regs = devm_ioremap_resource(&pdev->dev, res); | |
1331 | if (IS_ERR(tegra->ccroc_regs)) { | |
1332 | dev_err(&pdev->dev, "can't get ccroc registers"); | |
1333 | return PTR_ERR(tegra->ccroc_regs); | |
1334 | } | |
ce0dbf04 | 1335 | } |
65b6d57c WN |
1336 | |
1337 | tegra->reset = devm_reset_control_get(&pdev->dev, "soctherm"); | |
1338 | if (IS_ERR(tegra->reset)) { | |
1339 | dev_err(&pdev->dev, "can't get soctherm reset\n"); | |
1340 | return PTR_ERR(tegra->reset); | |
1341 | } | |
1342 | ||
1343 | tegra->clock_tsensor = devm_clk_get(&pdev->dev, "tsensor"); | |
1344 | if (IS_ERR(tegra->clock_tsensor)) { | |
1345 | dev_err(&pdev->dev, "can't get tsensor clock\n"); | |
1346 | return PTR_ERR(tegra->clock_tsensor); | |
1347 | } | |
1348 | ||
1349 | tegra->clock_soctherm = devm_clk_get(&pdev->dev, "soctherm"); | |
1350 | if (IS_ERR(tegra->clock_soctherm)) { | |
1351 | dev_err(&pdev->dev, "can't get soctherm clock\n"); | |
1352 | return PTR_ERR(tegra->clock_soctherm); | |
1353 | } | |
1354 | ||
65b6d57c WN |
1355 | tegra->calib = devm_kzalloc(&pdev->dev, |
1356 | sizeof(u32) * soc->num_tsensors, | |
1357 | GFP_KERNEL); | |
1ed895c2 WN |
1358 | if (!tegra->calib) |
1359 | return -ENOMEM; | |
65b6d57c | 1360 | |
1ed895c2 | 1361 | /* calculate shared calibration data */ |
65b6d57c WN |
1362 | err = tegra_calc_shared_calib(soc->tfuse, &shared_calib); |
1363 | if (err) | |
1ed895c2 | 1364 | return err; |
65b6d57c | 1365 | |
1ed895c2 | 1366 | /* calculate tsensor calibaration data */ |
65b6d57c | 1367 | for (i = 0; i < soc->num_tsensors; ++i) { |
1ed895c2 WN |
1368 | err = tegra_calc_tsensor_calib(&soc->tsensors[i], |
1369 | &shared_calib, | |
1370 | &tegra->calib[i]); | |
65b6d57c | 1371 | if (err) |
1ed895c2 | 1372 | return err; |
65b6d57c WN |
1373 | } |
1374 | ||
f09d6984 WN |
1375 | tegra->thermctl_tzs = devm_kzalloc(&pdev->dev, |
1376 | sizeof(*z) * soc->num_ttgs, | |
1377 | GFP_KERNEL); | |
1378 | if (!tegra->thermctl_tzs) | |
1379 | return -ENOMEM; | |
1380 | ||
1ed895c2 WN |
1381 | err = soctherm_clk_enable(pdev, true); |
1382 | if (err) | |
1383 | return err; | |
1384 | ||
6c7c3245 | 1385 | soctherm_init_hw_throt_cdev(pdev); |
ce0dbf04 | 1386 | |
1ed895c2 | 1387 | soctherm_init(pdev); |
65b6d57c | 1388 | |
65b6d57c WN |
1389 | for (i = 0; i < soc->num_ttgs; ++i) { |
1390 | struct tegra_thermctl_zone *zone = | |
1391 | devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL); | |
1392 | if (!zone) { | |
1393 | err = -ENOMEM; | |
1394 | goto disable_clocks; | |
1395 | } | |
1396 | ||
1397 | zone->reg = tegra->regs + soc->ttgs[i]->sensor_temp_offset; | |
2a895871 WN |
1398 | zone->dev = &pdev->dev; |
1399 | zone->sg = soc->ttgs[i]; | |
ce0dbf04 | 1400 | zone->ts = tegra; |
65b6d57c WN |
1401 | |
1402 | z = devm_thermal_zone_of_sensor_register(&pdev->dev, | |
1403 | soc->ttgs[i]->id, zone, | |
1404 | &tegra_of_thermal_ops); | |
1405 | if (IS_ERR(z)) { | |
1406 | err = PTR_ERR(z); | |
1407 | dev_err(&pdev->dev, "failed to register sensor: %d\n", | |
1408 | err); | |
1409 | goto disable_clocks; | |
1410 | } | |
2a895871 WN |
1411 | |
1412 | zone->tz = z; | |
f09d6984 | 1413 | tegra->thermctl_tzs[soc->ttgs[i]->id] = z; |
2a895871 WN |
1414 | |
1415 | /* Configure hw trip points */ | |
ce0dbf04 WN |
1416 | err = tegra_soctherm_set_hwtrips(&pdev->dev, soc->ttgs[i], z); |
1417 | if (err) | |
1418 | goto disable_clocks; | |
65b6d57c WN |
1419 | } |
1420 | ||
d753b22d WN |
1421 | soctherm_debug_init(pdev); |
1422 | ||
65b6d57c WN |
1423 | return 0; |
1424 | ||
1425 | disable_clocks: | |
8de2ab02 | 1426 | soctherm_clk_enable(pdev, false); |
65b6d57c WN |
1427 | |
1428 | return err; | |
1429 | } | |
1430 | ||
1431 | static int tegra_soctherm_remove(struct platform_device *pdev) | |
1432 | { | |
1433 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
1434 | ||
d753b22d WN |
1435 | debugfs_remove_recursive(tegra->debugfs_dir); |
1436 | ||
8de2ab02 | 1437 | soctherm_clk_enable(pdev, false); |
65b6d57c WN |
1438 | |
1439 | return 0; | |
1440 | } | |
1441 | ||
a977c41e | 1442 | static int __maybe_unused soctherm_suspend(struct device *dev) |
f09d6984 WN |
1443 | { |
1444 | struct platform_device *pdev = to_platform_device(dev); | |
1445 | ||
1446 | soctherm_clk_enable(pdev, false); | |
1447 | ||
1448 | return 0; | |
1449 | } | |
1450 | ||
a977c41e | 1451 | static int __maybe_unused soctherm_resume(struct device *dev) |
f09d6984 WN |
1452 | { |
1453 | struct platform_device *pdev = to_platform_device(dev); | |
1454 | struct tegra_soctherm *tegra = platform_get_drvdata(pdev); | |
1455 | struct tegra_soctherm_soc *soc = tegra->soc; | |
1456 | int err, i; | |
1457 | ||
1458 | err = soctherm_clk_enable(pdev, true); | |
1459 | if (err) { | |
1460 | dev_err(&pdev->dev, | |
1461 | "Resume failed: enable clocks failed\n"); | |
1462 | return err; | |
1463 | } | |
1464 | ||
1465 | soctherm_init(pdev); | |
1466 | ||
1467 | for (i = 0; i < soc->num_ttgs; ++i) { | |
1468 | struct thermal_zone_device *tz; | |
1469 | ||
1470 | tz = tegra->thermctl_tzs[soc->ttgs[i]->id]; | |
ce0dbf04 WN |
1471 | err = tegra_soctherm_set_hwtrips(dev, soc->ttgs[i], tz); |
1472 | if (err) { | |
1473 | dev_err(&pdev->dev, | |
1474 | "Resume failed: set hwtrips failed\n"); | |
1475 | return err; | |
1476 | } | |
f09d6984 WN |
1477 | } |
1478 | ||
1479 | return 0; | |
1480 | } | |
1481 | ||
1482 | static SIMPLE_DEV_PM_OPS(tegra_soctherm_pm, soctherm_suspend, soctherm_resume); | |
1483 | ||
65b6d57c WN |
1484 | static struct platform_driver tegra_soctherm_driver = { |
1485 | .probe = tegra_soctherm_probe, | |
1486 | .remove = tegra_soctherm_remove, | |
1487 | .driver = { | |
1488 | .name = "tegra_soctherm", | |
f09d6984 | 1489 | .pm = &tegra_soctherm_pm, |
65b6d57c WN |
1490 | .of_match_table = tegra_soctherm_of_match, |
1491 | }, | |
1492 | }; | |
1493 | module_platform_driver(tegra_soctherm_driver); | |
1494 | ||
1495 | MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>"); | |
1496 | MODULE_DESCRIPTION("NVIDIA Tegra SOCTHERM thermal management driver"); | |
1497 | MODULE_LICENSE("GPL v2"); |