Loading...
Note: File does not exist in v3.15.
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
16int thermal_thresholds_init(struct thermal_zone_device *tz)
17{
18 INIT_LIST_HEAD(&tz->user_thresholds);
19
20 return 0;
21}
22
23static 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
34void 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
45void thermal_thresholds_exit(struct thermal_zone_device *tz)
46{
47 __thermal_thresholds_flush(tz);
48}
49
50static 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
60static 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
72static 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
90static 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
108static 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
128void 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
168int 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
202int 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
228int 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}