// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2024 Linaro Limited * * Author: Daniel Lezcano * * Thermal thresholds */ #include #include #include #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(sizeof(*t), GFP_KERNEL); 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; }