xref: /linux/drivers/thermal/thermal_thresholds.c (revision 364eeb79a213fcf9164208b53764223ad522d6b3)
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 
16 int thermal_thresholds_init(struct thermal_zone_device *tz)
17 {
18 	INIT_LIST_HEAD(&tz->user_thresholds);
19 
20 	return 0;
21 }
22 
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 
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 
45 void thermal_thresholds_exit(struct thermal_zone_device *tz)
46 {
47 	__thermal_thresholds_flush(tz);
48 }
49 
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 
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 
72 static bool __thermal_threshold_is_crossed(struct user_threshold *threshold, int temperature,
73 					   int last_temperature, int direction,
74 					   int *low, int *high)
75 {
76 
77 	if (temperature >= threshold->temperature) {
78 		if (threshold->temperature > *low &&
79 		    THERMAL_THRESHOLD_WAY_DOWN & threshold->direction)
80 			*low = threshold->temperature;
81 
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;
89 
90 		if (last_temperature >= threshold->temperature &&
91 		    threshold->direction & direction)
92 			return true;
93 	}
94 
95 	return false;
96 }
97 
98 static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
99 					      int last_temperature, int *low, int *high)
100 {
101 	struct user_threshold *t;
102 
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;
107 	}
108 
109 	return false;
110 }
111 
112 static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
113 					       int last_temperature, int *low, int *high)
114 {
115 	struct user_threshold *t;
116 
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;
121 	}
122 
123 	return false;
124 }
125 
126 void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
127 {
128 	struct list_head *thresholds = &tz->user_thresholds;
129 
130 	int temperature = tz->temperature;
131 	int last_temperature = tz->last_temperature;
132 
133 	lockdep_assert_held(&tz->lock);
134 
135 	/*
136 	 * We need a second update in order to detect a threshold being crossed
137 	 */
138 	if (last_temperature == THERMAL_TEMP_INVALID)
139 		return;
140 
141 	/*
142 	 * The temperature is stable, so obviously we can not have
143 	 * crossed a threshold.
144 	 */
145 	if (last_temperature == temperature)
146 		return;
147 
148 	/*
149 	 * Since last update the temperature:
150 	 * - increased : thresholds are crossed the way up
151 	 * - decreased : thresholds are crossed the way down
152 	 */
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);
161 	}
162 }
163 
164 int thermal_thresholds_add(struct thermal_zone_device *tz,
165 			   int temperature, int direction)
166 {
167 	struct list_head *thresholds = &tz->user_thresholds;
168 	struct user_threshold *t;
169 
170 	lockdep_assert_held(&tz->lock);
171 
172 	t = __thermal_thresholds_find(thresholds, temperature);
173 	if (t) {
174 		if (t->direction == direction)
175 			return -EEXIST;
176 
177 		t->direction |= direction;
178 	} else {
179 
180 		t = kmalloc(sizeof(*t), GFP_KERNEL);
181 		if (!t)
182 			return -ENOMEM;
183 
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);
189 	}
190 
191 	thermal_notify_threshold_add(tz, temperature, direction);
192 
193 	__thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
194 
195 	return 0;
196 }
197 
198 int thermal_thresholds_delete(struct thermal_zone_device *tz,
199 			      int temperature, int direction)
200 {
201 	struct list_head *thresholds = &tz->user_thresholds;
202 	struct user_threshold *t;
203 
204 	lockdep_assert_held(&tz->lock);
205 
206 	t = __thermal_thresholds_find(thresholds, temperature);
207 	if (!t)
208 		return -ENOENT;
209 
210 	if (t->direction == direction) {
211 		list_del(&t->list_node);
212 		kfree(t);
213 	} else {
214 		t->direction &= ~direction;
215 	}
216 
217 	thermal_notify_threshold_delete(tz, temperature, direction);
218 
219 	__thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
220 
221 	return 0;
222 }
223 
224 int thermal_thresholds_for_each(struct thermal_zone_device *tz,
225 				int (*cb)(struct user_threshold *, void *arg), void *arg)
226 {
227 	struct list_head *thresholds = &tz->user_thresholds;
228 	struct user_threshold *entry;
229 	int ret;
230 
231 	guard(thermal_zone)(tz);
232 
233 	list_for_each_entry(entry, thresholds, list_node) {
234 		ret = cb(entry, arg);
235 		if (ret)
236 			return ret;
237 	}
238 
239 	return 0;
240 }
241