| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2024 Linaro Limited | |
| * | |
| * Author: Daniel Lezcano <daniel.lezcano@linaro.org> | |
| * | |
| * Thermal thresholds | |
| */ | |
| #include <linux/list.h> | |
| #include <linux/list_sort.h> | |
| #include <linux/slab.h> | |
| #include "thermal_core.h" | |
| #include "thermal_thresholds.h" | |
| int thermal_thresholds_init(struct thermal_zone_device *tz) | |
| { | |
| INIT_LIST_HEAD(&tz->user_thresholds); | |
| return 0; | |
| } | |
| static void __thermal_thresholds_flush(struct thermal_zone_device *tz) | |
| { | |
| struct list_head *thresholds = &tz->user_thresholds; | |
| struct user_threshold *entry, *tmp; | |
| list_for_each_entry_safe(entry, tmp, thresholds, list_node) { | |
| list_del(&entry->list_node); | |
| kfree(entry); | |
| } | |
| } | |
| void thermal_thresholds_flush(struct thermal_zone_device *tz) | |
| { | |
| lockdep_assert_held(&tz->lock); | |
| __thermal_thresholds_flush(tz); | |
| thermal_notify_threshold_flush(tz); | |
| __thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS); | |
| } | |
| void thermal_thresholds_exit(struct thermal_zone_device *tz) | |
| { | |
| __thermal_thresholds_flush(tz); | |
| } | |
| static int __thermal_thresholds_cmp(void *data, | |
| const struct list_head *l1, | |
| const struct list_head *l2) | |
| { | |
| struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node); | |
| struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node); | |
| return t1->temperature - t2->temperature; | |
| } | |
| static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds, | |
| int temperature) | |
| { | |
| struct user_threshold *t; | |
| list_for_each_entry(t, thresholds, list_node) | |
| if (t->temperature == temperature) | |
| return t; | |
| return NULL; | |
| } | |
| static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature, | |
| int last_temperature) | |
| { | |
| struct user_threshold *t; | |
| list_for_each_entry(t, thresholds, list_node) { | |
| if (!(t->direction & THERMAL_THRESHOLD_WAY_UP)) | |
| continue; | |
| if (temperature >= t->temperature && | |
| last_temperature < t->temperature) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature, | |
| int last_temperature) | |
| { | |
| struct user_threshold *t; | |
| list_for_each_entry_reverse(t, thresholds, list_node) { | |
| if (!(t->direction & THERMAL_THRESHOLD_WAY_DOWN)) | |
| continue; | |
| if (temperature <= t->temperature && | |
| last_temperature > t->temperature) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static void thermal_threshold_find_boundaries(struct list_head *thresholds, int temperature, | |
| int *low, int *high) | |
| { | |
| struct user_threshold *t; | |
| list_for_each_entry(t, thresholds, list_node) { | |
| if (temperature < t->temperature && | |
| (t->direction & THERMAL_THRESHOLD_WAY_UP) && | |
| *high > t->temperature) | |
| *high = t->temperature; | |
| } | |
| list_for_each_entry_reverse(t, thresholds, list_node) { | |
| if (temperature > t->temperature && | |
| (t->direction & THERMAL_THRESHOLD_WAY_DOWN) && | |
| *low < t->temperature) | |
| *low = t->temperature; | |
| } | |
| } | |
| void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high) | |
| { | |
| struct list_head *thresholds = &tz->user_thresholds; | |
| int temperature = tz->temperature; | |
| int last_temperature = tz->last_temperature; | |
| lockdep_assert_held(&tz->lock); | |
| thermal_threshold_find_boundaries(thresholds, temperature, low, high); | |
| /* | |
| * We need a second update in order to detect a threshold being crossed | |
| */ | |
| if (last_temperature == THERMAL_TEMP_INVALID) | |
| return; | |
| /* | |
| * The temperature is stable, so obviously we can not have | |
| * crossed a threshold. | |
| */ | |
| if (last_temperature == temperature) | |
| return; | |
| /* | |
| * Since last update the temperature: | |
| * - increased : thresholds are crossed the way up | |
| * - decreased : thresholds are crossed the way down | |
| */ | |
| if (temperature > last_temperature) { | |
| if (thermal_thresholds_handle_raising(thresholds, | |
| temperature, last_temperature)) | |
| thermal_notify_threshold_up(tz); | |
| } else { | |
| if (thermal_thresholds_handle_dropping(thresholds, | |
| temperature, last_temperature)) | |
| thermal_notify_threshold_down(tz); | |
| } | |
| } | |
| int thermal_thresholds_add(struct thermal_zone_device *tz, | |
| int temperature, int direction) | |
| { | |
| struct list_head *thresholds = &tz->user_thresholds; | |
| struct user_threshold *t; | |
| lockdep_assert_held(&tz->lock); | |
| t = __thermal_thresholds_find(thresholds, temperature); | |
| if (t) { | |
| if (t->direction == direction) | |
| return -EEXIST; | |
| t->direction |= direction; | |
| } else { | |
| t = kmalloc_obj(*t); | |
| if (!t) | |
| return -ENOMEM; | |
| INIT_LIST_HEAD(&t->list_node); | |
| t->temperature = temperature; | |
| t->direction = direction; | |
| list_add(&t->list_node, thresholds); | |
| list_sort(NULL, thresholds, __thermal_thresholds_cmp); | |
| } | |
| thermal_notify_threshold_add(tz, temperature, direction); | |
| __thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD); | |
| return 0; | |
| } | |
| int thermal_thresholds_delete(struct thermal_zone_device *tz, | |
| int temperature, int direction) | |
| { | |
| struct list_head *thresholds = &tz->user_thresholds; | |
| struct user_threshold *t; | |
| lockdep_assert_held(&tz->lock); | |
| t = __thermal_thresholds_find(thresholds, temperature); | |
| if (!t) | |
| return -ENOENT; | |
| if (t->direction == direction) { | |
| list_del(&t->list_node); | |
| kfree(t); | |
| } else { | |
| t->direction &= ~direction; | |
| } | |
| thermal_notify_threshold_delete(tz, temperature, direction); | |
| __thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD); | |
| return 0; | |
| } | |
| int thermal_thresholds_for_each(struct thermal_zone_device *tz, | |
| int (*cb)(struct user_threshold *, void *arg), void *arg) | |
| { | |
| struct list_head *thresholds = &tz->user_thresholds; | |
| struct user_threshold *entry; | |
| int ret; | |
| guard(thermal_zone)(tz); | |
| list_for_each_entry(entry, thresholds, list_node) { | |
| ret = cb(entry, arg); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } |