1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright 2024 Linaro Limited
5 * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
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
);
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
);
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
,
63 struct user_threshold
*t
;
65 list_for_each_entry(t
, thresholds
, list_node
)
66 if (t
->temperature
== temperature
)
72 static bool __thermal_threshold_is_crossed(struct user_threshold
*threshold
, int temperature
,
73 int last_temperature
, int direction
,
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
)
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
)
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
))
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
))
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
)
142 * The temperature is stable, so obviously we can not have
143 * crossed a threshold.
145 if (last_temperature
== temperature
)
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
);
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
);
174 if (t
->direction
== direction
)
177 t
->direction
|= direction
;
180 t
= kmalloc(sizeof(*t
), GFP_KERNEL
);
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
);
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
);
210 if (t
->direction
== direction
) {
211 list_del(&t
->list_node
);
214 t
->direction
&= ~direction
;
217 thermal_notify_threshold_delete(tz
, temperature
, direction
);
219 __thermal_zone_device_update(tz
, THERMAL_TZ_DEL_THRESHOLD
);
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
;
231 guard(thermal_zone
)(tz
);
233 list_for_each_entry(entry
, thresholds
, list_node
) {
234 ret
= cb(entry
, arg
);