1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * thermal_helpers.c - helper functions to handle thermal devices |
4 | * |
5 | * Copyright (C) 2016 Eduardo Valentin <edubezval@gmail.com> |
6 | * |
7 | * Highly based on original thermal_core.c |
8 | * Copyright (C) 2008 Intel Corp |
9 | * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> |
10 | * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> |
11 | */ |
12 | |
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
14 | |
15 | #include <linux/device.h> |
16 | #include <linux/err.h> |
17 | #include <linux/export.h> |
18 | #include <linux/slab.h> |
19 | #include <linux/string.h> |
20 | #include <linux/sysfs.h> |
21 | |
22 | #include "thermal_core.h" |
23 | #include "thermal_trace.h" |
24 | |
25 | int get_tz_trend(struct thermal_zone_device *tz, const struct thermal_trip *trip) |
26 | { |
27 | enum thermal_trend trend; |
28 | |
29 | if (tz->emul_temperature || !tz->ops.get_trend || |
30 | tz->ops.get_trend(tz, trip, &trend)) { |
31 | if (tz->temperature > tz->last_temperature) |
32 | trend = THERMAL_TREND_RAISING; |
33 | else if (tz->temperature < tz->last_temperature) |
34 | trend = THERMAL_TREND_DROPPING; |
35 | else |
36 | trend = THERMAL_TREND_STABLE; |
37 | } |
38 | |
39 | return trend; |
40 | } |
41 | |
42 | struct thermal_instance * |
43 | get_thermal_instance(struct thermal_zone_device *tz, |
44 | struct thermal_cooling_device *cdev, int trip_index) |
45 | { |
46 | struct thermal_instance *pos = NULL; |
47 | struct thermal_instance *target_instance = NULL; |
48 | const struct thermal_trip *trip; |
49 | |
50 | mutex_lock(&tz->lock); |
51 | mutex_lock(&cdev->lock); |
52 | |
53 | trip = &tz->trips[trip_index]; |
54 | |
55 | list_for_each_entry(pos, &tz->thermal_instances, tz_node) { |
56 | if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { |
57 | target_instance = pos; |
58 | break; |
59 | } |
60 | } |
61 | |
62 | mutex_unlock(lock: &cdev->lock); |
63 | mutex_unlock(lock: &tz->lock); |
64 | |
65 | return target_instance; |
66 | } |
67 | EXPORT_SYMBOL(get_thermal_instance); |
68 | |
69 | /** |
70 | * __thermal_zone_get_temp() - returns the temperature of a thermal zone |
71 | * @tz: a valid pointer to a struct thermal_zone_device |
72 | * @temp: a valid pointer to where to store the resulting temperature. |
73 | * |
74 | * When a valid thermal zone reference is passed, it will fetch its |
75 | * temperature and fill @temp. |
76 | * |
77 | * Both tz and tz->ops must be valid pointers when calling this function, |
78 | * and the tz->ops.get_temp callback must be provided. |
79 | * The function must be called under tz->lock. |
80 | * |
81 | * Return: On success returns 0, an error code otherwise |
82 | */ |
83 | int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp) |
84 | { |
85 | const struct thermal_trip *trip; |
86 | int crit_temp = INT_MAX; |
87 | int ret = -EINVAL; |
88 | |
89 | lockdep_assert_held(&tz->lock); |
90 | |
91 | ret = tz->ops.get_temp(tz, temp); |
92 | |
93 | if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) { |
94 | for_each_trip(tz, trip) { |
95 | if (trip->type == THERMAL_TRIP_CRITICAL) { |
96 | crit_temp = trip->temperature; |
97 | break; |
98 | } |
99 | } |
100 | |
101 | /* |
102 | * Only allow emulating a temperature when the real temperature |
103 | * is below the critical temperature so that the emulation code |
104 | * cannot hide critical conditions. |
105 | */ |
106 | if (!ret && *temp < crit_temp) |
107 | *temp = tz->emul_temperature; |
108 | } |
109 | |
110 | if (ret) |
111 | dev_dbg(&tz->device, "Failed to get temperature: %d\n" , ret); |
112 | |
113 | return ret; |
114 | } |
115 | |
116 | /** |
117 | * thermal_zone_get_temp() - returns the temperature of a thermal zone |
118 | * @tz: a valid pointer to a struct thermal_zone_device |
119 | * @temp: a valid pointer to where to store the resulting temperature. |
120 | * |
121 | * When a valid thermal zone reference is passed, it will fetch its |
122 | * temperature and fill @temp. |
123 | * |
124 | * Return: On success returns 0, an error code otherwise |
125 | */ |
126 | int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp) |
127 | { |
128 | int ret; |
129 | |
130 | if (IS_ERR_OR_NULL(ptr: tz)) |
131 | return -EINVAL; |
132 | |
133 | mutex_lock(&tz->lock); |
134 | |
135 | if (!tz->ops.get_temp) { |
136 | ret = -EINVAL; |
137 | goto unlock; |
138 | } |
139 | |
140 | ret = __thermal_zone_get_temp(tz, temp); |
141 | |
142 | unlock: |
143 | mutex_unlock(lock: &tz->lock); |
144 | |
145 | return ret; |
146 | } |
147 | EXPORT_SYMBOL_GPL(thermal_zone_get_temp); |
148 | |
149 | static int thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev, int state) |
150 | { |
151 | int ret; |
152 | |
153 | /* |
154 | * No check is needed for the ops->set_cur_state as the |
155 | * registering function checked the ops are correctly set |
156 | */ |
157 | ret = cdev->ops->set_cur_state(cdev, state); |
158 | if (ret) |
159 | return ret; |
160 | |
161 | thermal_notify_cdev_state_update(cdev, state); |
162 | thermal_cooling_device_stats_update(cdev, new_state: state); |
163 | thermal_debug_cdev_state_update(cdev, state); |
164 | |
165 | return 0; |
166 | } |
167 | |
168 | void __thermal_cdev_update(struct thermal_cooling_device *cdev) |
169 | { |
170 | struct thermal_instance *instance; |
171 | unsigned long target = 0; |
172 | |
173 | /* Make sure cdev enters the deepest cooling state */ |
174 | list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) { |
175 | dev_dbg(&cdev->device, "zone%d->target=%lu\n" , |
176 | instance->tz->id, instance->target); |
177 | if (instance->target == THERMAL_NO_TARGET) |
178 | continue; |
179 | if (instance->target > target) |
180 | target = instance->target; |
181 | } |
182 | |
183 | thermal_cdev_set_cur_state(cdev, state: target); |
184 | |
185 | trace_cdev_update(cdev, target); |
186 | dev_dbg(&cdev->device, "set to state %lu\n" , target); |
187 | } |
188 | |
189 | /** |
190 | * thermal_cdev_update - update cooling device state if needed |
191 | * @cdev: pointer to struct thermal_cooling_device |
192 | * |
193 | * Update the cooling device state if there is a need. |
194 | */ |
195 | void thermal_cdev_update(struct thermal_cooling_device *cdev) |
196 | { |
197 | mutex_lock(&cdev->lock); |
198 | if (!cdev->updated) { |
199 | __thermal_cdev_update(cdev); |
200 | cdev->updated = true; |
201 | } |
202 | mutex_unlock(lock: &cdev->lock); |
203 | } |
204 | |
205 | /** |
206 | * thermal_zone_get_slope - return the slope attribute of the thermal zone |
207 | * @tz: thermal zone device with the slope attribute |
208 | * |
209 | * Return: If the thermal zone device has a slope attribute, return it, else |
210 | * return 1. |
211 | */ |
212 | int thermal_zone_get_slope(struct thermal_zone_device *tz) |
213 | { |
214 | if (tz && tz->tzp) |
215 | return tz->tzp->slope; |
216 | return 1; |
217 | } |
218 | EXPORT_SYMBOL_GPL(thermal_zone_get_slope); |
219 | |
220 | /** |
221 | * thermal_zone_get_offset - return the offset attribute of the thermal zone |
222 | * @tz: thermal zone device with the offset attribute |
223 | * |
224 | * Return: If the thermal zone device has a offset attribute, return it, else |
225 | * return 0. |
226 | */ |
227 | int thermal_zone_get_offset(struct thermal_zone_device *tz) |
228 | { |
229 | if (tz && tz->tzp) |
230 | return tz->tzp->offset; |
231 | return 0; |
232 | } |
233 | EXPORT_SYMBOL_GPL(thermal_zone_get_offset); |
234 | |