| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- // 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;
- }
|