1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright 2024 Linaro Limited
4 *
5 * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
6 *
7 * Thermal thresholds
8 */
9 #include <linux/list.h>
10 #include <linux/list_sort.h>
11 #include <linux/slab.h>
12
13 #include "thermal_core.h"
14 #include "thermal_thresholds.h"
15
thermal_thresholds_init(struct thermal_zone_device * tz)16 int thermal_thresholds_init(struct thermal_zone_device *tz)
17 {
18 INIT_LIST_HEAD(&tz->user_thresholds);
19
20 return 0;
21 }
22
__thermal_thresholds_flush(struct thermal_zone_device * tz)23 static void __thermal_thresholds_flush(struct thermal_zone_device *tz)
24 {
25 struct list_head *thresholds = &tz->user_thresholds;
26 struct user_threshold *entry, *tmp;
27
28 list_for_each_entry_safe(entry, tmp, thresholds, list_node) {
29 list_del(&entry->list_node);
30 kfree(entry);
31 }
32 }
33
thermal_thresholds_flush(struct thermal_zone_device * tz)34 void thermal_thresholds_flush(struct thermal_zone_device *tz)
35 {
36 lockdep_assert_held(&tz->lock);
37
38 __thermal_thresholds_flush(tz);
39
40 thermal_notify_threshold_flush(tz);
41
42 __thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS);
43 }
44
thermal_thresholds_exit(struct thermal_zone_device * tz)45 void thermal_thresholds_exit(struct thermal_zone_device *tz)
46 {
47 __thermal_thresholds_flush(tz);
48 }
49
__thermal_thresholds_cmp(void * data,const struct list_head * l1,const struct list_head * l2)50 static int __thermal_thresholds_cmp(void *data,
51 const struct list_head *l1,
52 const struct list_head *l2)
53 {
54 struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node);
55 struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node);
56
57 return t1->temperature - t2->temperature;
58 }
59
__thermal_thresholds_find(const struct list_head * thresholds,int temperature)60 static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds,
61 int temperature)
62 {
63 struct user_threshold *t;
64
65 list_for_each_entry(t, thresholds, list_node)
66 if (t->temperature == temperature)
67 return t;
68
69 return NULL;
70 }
71
thermal_thresholds_handle_raising(struct list_head * thresholds,int temperature,int last_temperature)72 static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
73 int last_temperature)
74 {
75 struct user_threshold *t;
76
77 list_for_each_entry(t, thresholds, list_node) {
78
79 if (!(t->direction & THERMAL_THRESHOLD_WAY_UP))
80 continue;
81
82 if (temperature >= t->temperature &&
83 last_temperature < t->temperature)
84 return true;
85 }
86
87 return false;
88 }
89
thermal_thresholds_handle_dropping(struct list_head * thresholds,int temperature,int last_temperature)90 static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
91 int last_temperature)
92 {
93 struct user_threshold *t;
94
95 list_for_each_entry_reverse(t, thresholds, list_node) {
96
97 if (!(t->direction & THERMAL_THRESHOLD_WAY_DOWN))
98 continue;
99
100 if (temperature <= t->temperature &&
101 last_temperature > t->temperature)
102 return true;
103 }
104
105 return false;
106 }
107
thermal_threshold_find_boundaries(struct list_head * thresholds,int temperature,int * low,int * high)108 static void thermal_threshold_find_boundaries(struct list_head *thresholds, int temperature,
109 int *low, int *high)
110 {
111 struct user_threshold *t;
112
113 list_for_each_entry(t, thresholds, list_node) {
114 if (temperature < t->temperature &&
115 (t->direction & THERMAL_THRESHOLD_WAY_UP) &&
116 *high > t->temperature)
117 *high = t->temperature;
118 }
119
120 list_for_each_entry_reverse(t, thresholds, list_node) {
121 if (temperature > t->temperature &&
122 (t->direction & THERMAL_THRESHOLD_WAY_DOWN) &&
123 *low < t->temperature)
124 *low = t->temperature;
125 }
126 }
127
thermal_thresholds_handle(struct thermal_zone_device * tz,int * low,int * high)128 void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
129 {
130 struct list_head *thresholds = &tz->user_thresholds;
131
132 int temperature = tz->temperature;
133 int last_temperature = tz->last_temperature;
134
135 lockdep_assert_held(&tz->lock);
136
137 thermal_threshold_find_boundaries(thresholds, temperature, low, high);
138
139 /*
140 * We need a second update in order to detect a threshold being crossed
141 */
142 if (last_temperature == THERMAL_TEMP_INVALID)
143 return;
144
145 /*
146 * The temperature is stable, so obviously we can not have
147 * crossed a threshold.
148 */
149 if (last_temperature == temperature)
150 return;
151
152 /*
153 * Since last update the temperature:
154 * - increased : thresholds are crossed the way up
155 * - decreased : thresholds are crossed the way down
156 */
157 if (temperature > last_temperature) {
158 if (thermal_thresholds_handle_raising(thresholds,
159 temperature, last_temperature))
160 thermal_notify_threshold_up(tz);
161 } else {
162 if (thermal_thresholds_handle_dropping(thresholds,
163 temperature, last_temperature))
164 thermal_notify_threshold_down(tz);
165 }
166 }
167
thermal_thresholds_add(struct thermal_zone_device * tz,int temperature,int direction)168 int thermal_thresholds_add(struct thermal_zone_device *tz,
169 int temperature, int direction)
170 {
171 struct list_head *thresholds = &tz->user_thresholds;
172 struct user_threshold *t;
173
174 lockdep_assert_held(&tz->lock);
175
176 t = __thermal_thresholds_find(thresholds, temperature);
177 if (t) {
178 if (t->direction == direction)
179 return -EEXIST;
180
181 t->direction |= direction;
182 } else {
183
184 t = kmalloc(sizeof(*t), GFP_KERNEL);
185 if (!t)
186 return -ENOMEM;
187
188 INIT_LIST_HEAD(&t->list_node);
189 t->temperature = temperature;
190 t->direction = direction;
191 list_add(&t->list_node, thresholds);
192 list_sort(NULL, thresholds, __thermal_thresholds_cmp);
193 }
194
195 thermal_notify_threshold_add(tz, temperature, direction);
196
197 __thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
198
199 return 0;
200 }
201
thermal_thresholds_delete(struct thermal_zone_device * tz,int temperature,int direction)202 int thermal_thresholds_delete(struct thermal_zone_device *tz,
203 int temperature, int direction)
204 {
205 struct list_head *thresholds = &tz->user_thresholds;
206 struct user_threshold *t;
207
208 lockdep_assert_held(&tz->lock);
209
210 t = __thermal_thresholds_find(thresholds, temperature);
211 if (!t)
212 return -ENOENT;
213
214 if (t->direction == direction) {
215 list_del(&t->list_node);
216 kfree(t);
217 } else {
218 t->direction &= ~direction;
219 }
220
221 thermal_notify_threshold_delete(tz, temperature, direction);
222
223 __thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
224
225 return 0;
226 }
227
thermal_thresholds_for_each(struct thermal_zone_device * tz,int (* cb)(struct user_threshold *,void * arg),void * arg)228 int thermal_thresholds_for_each(struct thermal_zone_device *tz,
229 int (*cb)(struct user_threshold *, void *arg), void *arg)
230 {
231 struct list_head *thresholds = &tz->user_thresholds;
232 struct user_threshold *entry;
233 int ret;
234
235 guard(thermal_zone)(tz);
236
237 list_for_each_entry(entry, thresholds, list_node) {
238 ret = cb(entry, arg);
239 if (ret)
240 return ret;
241 }
242
243 return 0;
244 }
245