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