245 lines
5.6 KiB
C
245 lines
5.6 KiB
C
|
// 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(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;
|
||
|
}
|