Merge tag 'trace-printf-v6.13' of git://git.kernel.org/pub/scm/linux/kernel/git/trace...
[drm/drm-misc.git] / drivers / thermal / thermal_thresholds.c
blobd9b2a0bb44fca51f46325674bb0e1c2ca148a0db
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright 2024 Linaro Limited
5 * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
7 * Thermal thresholds
8 */
9 #include <linux/list.h>
10 #include <linux/list_sort.h>
11 #include <linux/slab.h>
13 #include "thermal_core.h"
14 #include "thermal_thresholds.h"
16 int thermal_thresholds_init(struct thermal_zone_device *tz)
18 INIT_LIST_HEAD(&tz->user_thresholds);
20 return 0;
23 static void __thermal_thresholds_flush(struct thermal_zone_device *tz)
25 struct list_head *thresholds = &tz->user_thresholds;
26 struct user_threshold *entry, *tmp;
28 list_for_each_entry_safe(entry, tmp, thresholds, list_node) {
29 list_del(&entry->list_node);
30 kfree(entry);
34 void thermal_thresholds_flush(struct thermal_zone_device *tz)
36 lockdep_assert_held(&tz->lock);
38 __thermal_thresholds_flush(tz);
40 thermal_notify_threshold_flush(tz);
42 __thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS);
45 void thermal_thresholds_exit(struct thermal_zone_device *tz)
47 __thermal_thresholds_flush(tz);
50 static int __thermal_thresholds_cmp(void *data,
51 const struct list_head *l1,
52 const struct list_head *l2)
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);
57 return t1->temperature - t2->temperature;
60 static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds,
61 int temperature)
63 struct user_threshold *t;
65 list_for_each_entry(t, thresholds, list_node)
66 if (t->temperature == temperature)
67 return t;
69 return NULL;
72 static bool __thermal_threshold_is_crossed(struct user_threshold *threshold, int temperature,
73 int last_temperature, int direction,
74 int *low, int *high)
77 if (temperature >= threshold->temperature) {
78 if (threshold->temperature > *low &&
79 THERMAL_THRESHOLD_WAY_DOWN & threshold->direction)
80 *low = threshold->temperature;
82 if (last_temperature < threshold->temperature &&
83 threshold->direction & direction)
84 return true;
85 } else {
86 if (threshold->temperature < *high && THERMAL_THRESHOLD_WAY_UP
87 & threshold->direction)
88 *high = threshold->temperature;
90 if (last_temperature >= threshold->temperature &&
91 threshold->direction & direction)
92 return true;
95 return false;
98 static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
99 int last_temperature, int *low, int *high)
101 struct user_threshold *t;
103 list_for_each_entry(t, thresholds, list_node) {
104 if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
105 THERMAL_THRESHOLD_WAY_UP, low, high))
106 return true;
109 return false;
112 static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
113 int last_temperature, int *low, int *high)
115 struct user_threshold *t;
117 list_for_each_entry_reverse(t, thresholds, list_node) {
118 if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
119 THERMAL_THRESHOLD_WAY_DOWN, low, high))
120 return true;
123 return false;
126 void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
128 struct list_head *thresholds = &tz->user_thresholds;
130 int temperature = tz->temperature;
131 int last_temperature = tz->last_temperature;
133 lockdep_assert_held(&tz->lock);
136 * We need a second update in order to detect a threshold being crossed
138 if (last_temperature == THERMAL_TEMP_INVALID)
139 return;
142 * The temperature is stable, so obviously we can not have
143 * crossed a threshold.
145 if (last_temperature == temperature)
146 return;
149 * Since last update the temperature:
150 * - increased : thresholds are crossed the way up
151 * - decreased : thresholds are crossed the way down
153 if (temperature > last_temperature) {
154 if (thermal_thresholds_handle_raising(thresholds, temperature,
155 last_temperature, low, high))
156 thermal_notify_threshold_up(tz);
157 } else {
158 if (thermal_thresholds_handle_dropping(thresholds, temperature,
159 last_temperature, low, high))
160 thermal_notify_threshold_down(tz);
164 int thermal_thresholds_add(struct thermal_zone_device *tz,
165 int temperature, int direction)
167 struct list_head *thresholds = &tz->user_thresholds;
168 struct user_threshold *t;
170 lockdep_assert_held(&tz->lock);
172 t = __thermal_thresholds_find(thresholds, temperature);
173 if (t) {
174 if (t->direction == direction)
175 return -EEXIST;
177 t->direction |= direction;
178 } else {
180 t = kmalloc(sizeof(*t), GFP_KERNEL);
181 if (!t)
182 return -ENOMEM;
184 INIT_LIST_HEAD(&t->list_node);
185 t->temperature = temperature;
186 t->direction = direction;
187 list_add(&t->list_node, thresholds);
188 list_sort(NULL, thresholds, __thermal_thresholds_cmp);
191 thermal_notify_threshold_add(tz, temperature, direction);
193 __thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
195 return 0;
198 int thermal_thresholds_delete(struct thermal_zone_device *tz,
199 int temperature, int direction)
201 struct list_head *thresholds = &tz->user_thresholds;
202 struct user_threshold *t;
204 lockdep_assert_held(&tz->lock);
206 t = __thermal_thresholds_find(thresholds, temperature);
207 if (!t)
208 return -ENOENT;
210 if (t->direction == direction) {
211 list_del(&t->list_node);
212 kfree(t);
213 } else {
214 t->direction &= ~direction;
217 thermal_notify_threshold_delete(tz, temperature, direction);
219 __thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
221 return 0;
224 int thermal_thresholds_for_each(struct thermal_zone_device *tz,
225 int (*cb)(struct user_threshold *, void *arg), void *arg)
227 struct list_head *thresholds = &tz->user_thresholds;
228 struct user_threshold *entry;
229 int ret;
231 guard(thermal_zone)(tz);
233 list_for_each_entry(entry, thresholds, list_node) {
234 ret = cb(entry, arg);
235 if (ret)
236 return ret;
239 return 0;