[DM/FEATURE] Support thermal

Thermal drivers offer a generic mechanism for thermal management.
Usually it's made up of one or more thermal zones and cooling devices.
Each thermal zone contains its own temperature, trip points, and cooling devices.
All platforms with ACPI or OFW thermal support can use this driver.

Signed-off-by: GuEe-GUI <2991707448@qq.com>
This commit is contained in:
GuEe-GUI 2024-11-27 13:30:12 +08:00
parent d025072837
commit b8a89c225d
9 changed files with 1266 additions and 0 deletions

View File

@ -28,6 +28,7 @@ rsource "block/Kconfig"
rsource "nvme/Kconfig"
rsource "scsi/Kconfig"
rsource "reset/Kconfig"
rsource "thermal/Kconfig"
rsource "virtio/Kconfig"
rsource "dma/Kconfig"
rsource "mfd/Kconfig"

View File

@ -0,0 +1,205 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-3-08 GuEe-GUI the first version
*/
#ifndef __THERMAL_H__
#define __THERMAL_H__
#include <rtdef.h>
#include <dt-bindings/thermal/thermal.h>
/* No upper/lower limit requirement */
#define RT_THERMAL_NO_LIMIT ((rt_uint32_t)THERMAL_NO_LIMIT)
#define RT_THERMAL_TEMP_INVALID (-274000)
struct rt_thermal_zone_ops;
struct rt_thermal_cooling_device;
struct rt_thermal_cooling_device_ops;
struct rt_thermal_cooling_governor;
enum rt_thermal_trip_type
{
RT_THERMAL_TRIP_ACTIVE = 0,
RT_THERMAL_TRIP_PASSIVE,
RT_THERMAL_TRIP_HOT,
RT_THERMAL_TRIP_CRITICAL,
RT_THERMAL_TRIP_TYPE_MAX,
};
struct rt_thermal_trip
{
/* Temperature value in millidegree celsius */
int temperature;
/* Relative hysteresis in millidegree celsius */
int hysteresis;
enum rt_thermal_trip_type type;
void *priv;
};
struct rt_thermal_zone_params
{
/* Sustainable power (heat) that this thermal zone can dissipate in mW */
int sustainable_power;
/* Slope of a linear temperature adjustment curve */
int slope;
/* Offset of a linear temperature adjustment curve */
int offset;
};
struct rt_thermal_cooling_cell
{
struct rt_thermal_cooling_device *cooling_devices;
rt_uint32_t level_range[2];
};
struct rt_thermal_cooling_map
{
rt_uint32_t contribution;
rt_size_t cells_nr;
struct rt_thermal_cooling_cell *cells;
struct rt_thermal_trip *trips;
};
struct rt_thermal_zone_device
{
struct rt_device parent;
int zone_id;
const struct rt_thermal_zone_ops *ops;
rt_bool_t trips_free;
rt_size_t trips_nr;
struct rt_thermal_trip *trips;
struct rt_thermal_zone_params params;
rt_bool_t enabled;
rt_bool_t cooling;
int temperature;
int last_temperature;
int prev_low_trip;
int prev_high_trip;
rt_list_t notifier_nodes;
struct rt_spinlock nodes_lock;
rt_size_t cooling_maps_nr;
struct rt_thermal_cooling_map *cooling_maps;
rt_tick_t passive_delay, polling_delay;
struct rt_work poller;
struct rt_mutex mutex;
void *priv;
};
struct rt_thermal_zone_ops
{
rt_err_t (*get_temp)(struct rt_thermal_zone_device *zdev, int *out_temp);
rt_err_t (*set_trips)(struct rt_thermal_zone_device *zdev, int low_temp, int high_temp);
rt_err_t (*set_trip_temp)(struct rt_thermal_zone_device *zdev, int trip_id, int temp);
rt_err_t (*set_trip_hyst)(struct rt_thermal_zone_device *zdev, int trip_id, int hyst);
void (*hot)(struct rt_thermal_zone_device *zdev);
void (*critical)(struct rt_thermal_zone_device *zdev);
};
/*
* We don't want to make a temperature control system
* that is finer than an air conditioner's temperature control,
* just ensure get a reliable heat dissipation under high-load task
* or when the SoC temperature is too high.
*/
struct rt_thermal_cooling_device
{
struct rt_device parent;
const struct rt_thermal_cooling_device_ops *ops;
/* The cooling capacity indicator */
rt_ubase_t max_level;
rt_list_t governor_node;
struct rt_thermal_cooling_governor *gov;
void *priv;
};
struct rt_thermal_cooling_device_ops
{
rt_err_t (*bind)(struct rt_thermal_cooling_device *cdev, struct rt_thermal_zone_device *zdev);
rt_err_t (*unbind)(struct rt_thermal_cooling_device *cdev, struct rt_thermal_zone_device *zdev);
rt_err_t (*get_max_level)(struct rt_thermal_cooling_device *cdev, rt_ubase_t *out_level);
rt_err_t (*get_cur_level)(struct rt_thermal_cooling_device *cdev, rt_ubase_t *out_level);
rt_err_t (*set_cur_level)(struct rt_thermal_cooling_device *cdev, rt_ubase_t level);
};
struct rt_thermal_cooling_governor
{
rt_list_t list;
const char *name;
rt_list_t cdev_nodes;
void (*tuning)(struct rt_thermal_zone_device *zdev,
int map_idx, int cell_idx, rt_ubase_t *level);
};
struct rt_thermal_notifier;
#define RT_THERMAL_MSG_EVENT_UNSPECIFIED RT_BIT(0) /* Unspecified event */
#define RT_THERMAL_MSG_EVENT_TEMP_SAMPLE RT_BIT(1) /* New Temperature sample */
#define RT_THERMAL_MSG_TRIP_VIOLATED RT_BIT(2) /* TRIP Point violation */
#define RT_THERMAL_MSG_TRIP_CHANGED RT_BIT(3) /* TRIP Point temperature changed */
#define RT_THERMAL_MSG_DEVICE_DOWN RT_BIT(4) /* Thermal device is down */
#define RT_THERMAL_MSG_DEVICE_UP RT_BIT(5) /* Thermal device is up after a down event */
#define RT_THERMAL_MSG_DEVICE_POWER_CAPABILITY_CHANGED RT_BIT(6) /* Power capability changed */
#define RT_THERMAL_MSG_TABLE_CHANGED RT_BIT(7) /* Thermal table(s) changed */
#define RT_THERMAL_MSG_EVENT_KEEP_ALIVE RT_BIT(8) /* Request for user space handler to respond */
typedef rt_err_t (*rt_thermal_notifier_callback)(struct rt_thermal_notifier *notifier,
rt_ubase_t msg);
struct rt_thermal_notifier
{
rt_list_t list;
struct rt_thermal_zone_device *zdev;
rt_thermal_notifier_callback callback;
void *priv;
};
rt_err_t rt_thermal_zone_device_register(struct rt_thermal_zone_device *zdev);
rt_err_t rt_thermal_zone_device_unregister(struct rt_thermal_zone_device *zdev);
rt_err_t rt_thermal_cooling_device_register(struct rt_thermal_cooling_device *cdev);
rt_err_t rt_thermal_cooling_device_unregister(struct rt_thermal_cooling_device *cdev);
rt_err_t rt_thermal_cooling_governor_register(struct rt_thermal_cooling_governor *gov);
rt_err_t rt_thermal_cooling_governor_unregister(struct rt_thermal_cooling_governor *gov);
rt_err_t rt_thermal_cooling_device_change_governor(struct rt_thermal_cooling_device *cdev,
const char *name);
rt_err_t rt_thermal_zone_notifier_register(struct rt_thermal_zone_device *zdev,
struct rt_thermal_notifier *notifier);
rt_err_t rt_thermal_zone_notifier_unregister(struct rt_thermal_zone_device *zdev,
struct rt_thermal_notifier *notifier);
void rt_thermal_zone_device_update(struct rt_thermal_zone_device *zdev, rt_ubase_t msg);
void rt_thermal_cooling_device_kick(struct rt_thermal_zone_device *zdev);
rt_err_t rt_thermal_zone_set_trip(struct rt_thermal_zone_device *zdev, int trip_id,
const struct rt_thermal_trip *trip);
rt_err_t rt_thermal_zone_get_trip(struct rt_thermal_zone_device *zdev, int trip_id,
struct rt_thermal_trip *out_trip);
#endif /* __THERMAL_H__ */

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __DT_BINDINGS_THERMAL_THERMAL_H__
#define __DT_BINDINGS_THERMAL_THERMAL_H__
/* On cooling devices upper and lower limits */
#define THERMAL_NO_LIMIT (~0)
#endif /* __DT_BINDINGS_THERMAL_THERMAL_H__ */

View File

@ -96,6 +96,10 @@ extern "C" {
#ifdef RT_MFD_SYSCON
#include "drivers/syscon.h"
#endif /* RT_MFD_SYSCON */
#ifdef RT_USING_THERMAL
#include "drivers/thermal.h"
#endif /* RT_USING_THERMAL */
#endif /* RT_USING_DM */
#ifdef RT_USING_RTC

View File

@ -0,0 +1,20 @@
menuconfig RT_USING_THERMAL
bool "Using Thermal Management device drivers"
depends on RT_USING_DM
default n
if RT_USING_THERMAL
comment "Thermal Sensors Drivers"
endif
if RT_USING_THERMAL
osource "$(SOC_DM_THERMAL_DIR)/Kconfig"
endif
if RT_USING_THERMAL
comment "Thermal Cool Drivers"
endif
if RT_USING_THERMAL
osource "$(SOC_DM_THERMAL_COOL_DIR)/Kconfig"
endif

View File

@ -0,0 +1,15 @@
from building import *
group = []
if not GetDepend(['RT_USING_THERMAL']):
Return('group')
cwd = GetCurrentDir()
CPPPATH = [cwd + '/../include']
src = ['thermal.c', 'thermal_dm.c']
group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
Return('group')

View File

@ -0,0 +1,917 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-3-08 GuEe-GUI the first version
*/
#include <drivers/platform.h>
#define DBG_TAG "rtdm.thermal"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#include "thermal_dm.h"
#ifndef INT_MAX
#define INT_MAX (RT_UINT32_MAX >> 1)
#endif
#define device_list(dev) (dev)->parent.parent.list
#define device_foreach(dev, nodes) rt_list_for_each_entry(dev, nodes, parent.parent.list)
static struct rt_spinlock nodes_lock = {};
static rt_list_t thermal_zone_device_nodes = RT_LIST_OBJECT_INIT(thermal_zone_device_nodes);
static rt_list_t thermal_cooling_device_nodes = RT_LIST_OBJECT_INIT(thermal_cooling_device_nodes);
static rt_list_t thermal_cooling_governor_nodes = RT_LIST_OBJECT_INIT(thermal_cooling_governor_nodes);
#ifdef RT_USING_OFW
static void thermal_ofw_params_parse(struct rt_ofw_node *np,
struct rt_thermal_zone_params *tz_params)
{
rt_uint32_t coef[2], prop;
if (!np)
{
return;
}
if (!rt_ofw_prop_read_u32(np, "sustainable-power", &prop))
{
tz_params->sustainable_power = prop;
}
/*
* For now, the thermal framework supports only one sensor per thermal zone.
* Thus, we are considering only the first two values as slope and offset.
*/
if (rt_ofw_prop_read_u32_array_index(np, "coefficients", 0, 1, coef) < 0)
{
coef[0] = 1;
coef[1] = 0;
}
tz_params->slope = coef[0];
tz_params->offset = coef[1];
}
static void thermal_ofw_setup(struct rt_ofw_node *np, struct rt_thermal_zone_device *zdev)
{
int i = 0;
rt_uint32_t delay, pdelay;
struct rt_ofw_cell_args args;
struct rt_ofw_node *tmp_np, *tz_np, *trip_np, *cm_np, *cdev_np;
if (!np || !zdev)
{
return;
}
tmp_np = rt_ofw_find_node_by_path("/thermal-zones");
if (!tmp_np)
{
return;
}
rt_ofw_foreach_child_node(tmp_np, tz_np)
{
if (!rt_ofw_parse_phandle_cells(tz_np, "thermal-sensors", "#thermal-sensor-cells", 0, &args))
{
if (args.data == np && (!args.args_count || args.args[0] == zdev->zone_id))
{
rt_ofw_node_put(args.data);
goto _found;
}
rt_ofw_node_put(args.data);
}
}
return;
_found:
rt_ofw_prop_read_u32(tz_np, "polling-delay-passive", &pdelay);
rt_ofw_prop_read_u32(tz_np, "polling-delay", &delay);
zdev->passive_delay = rt_tick_from_millisecond(pdelay);
zdev->polling_delay = rt_tick_from_millisecond(delay);
thermal_ofw_params_parse(tz_np, &zdev->params);
if (zdev->trips_nr)
{
goto _scan_cooling;
}
tmp_np = rt_ofw_get_child_by_tag(tz_np, "trips");
if (!tmp_np)
{
goto _scan_cooling;
}
zdev->trips_nr = rt_ofw_get_child_count(tmp_np);
if (!zdev->trips_nr)
{
goto _scan_cooling;
}
zdev->trips = rt_calloc(zdev->trips_nr, sizeof(*zdev->trips));
zdev->trips_free = RT_TRUE;
if (!zdev->trips)
{
LOG_E("%s: No memory to create %s", rt_ofw_node_full_name(np), "trips");
RT_ASSERT(0);
}
rt_ofw_foreach_child_node(tmp_np, trip_np)
{
const char *type;
rt_ofw_prop_read_u32(trip_np, "temperature", (rt_uint32_t *)&zdev->trips[i].temperature);
rt_ofw_prop_read_u32(trip_np, "hysteresis", (rt_uint32_t *)&zdev->trips[i].hysteresis);
rt_ofw_prop_read_string(trip_np, "type", &type);
zdev->trips[i].type = thermal_type(type);
rt_ofw_data(trip_np) = &zdev->trips[i];
++i;
}
_scan_cooling:
i = 0;
tmp_np = rt_ofw_get_child_by_tag(tz_np, "cooling-maps");
if (!tmp_np)
{
goto _end;
}
zdev->cooling_maps_nr = rt_ofw_get_child_count(tmp_np);
if (!zdev->cooling_maps_nr)
{
goto _end;
}
zdev->cooling_maps = rt_calloc(zdev->cooling_maps_nr, sizeof(*zdev->cooling_maps));
if (!zdev->cooling_maps)
{
LOG_E("%s: No memory to create %s", rt_ofw_node_full_name(np), "cooling_maps");
RT_ASSERT(0);
}
rt_ofw_foreach_child_node(tmp_np, cm_np)
{
struct rt_thermal_cooling_device *cdev;
struct rt_thermal_cooling_map *map = &zdev->cooling_maps[i++];
map->cells_nr = rt_ofw_count_phandle_cells(cm_np, "cooling-device", "#cooling-cells");
map->cells = rt_calloc(sizeof(*map->cells), map->cells_nr);
if (!map->cells)
{
LOG_E("%s: No memory to create %s", rt_ofw_node_full_name(np), "cells");
RT_ASSERT(0);
}
trip_np = rt_ofw_parse_phandle(cm_np, "trip", 0);
map->trips = rt_ofw_data(trip_np);
rt_ofw_node_put(trip_np);
if (!map->trips)
{
LOG_E("%s: trips(%s) not found", rt_ofw_node_full_name(np),
rt_ofw_node_full_name(trip_np));
RT_ASSERT(0);
}
rt_ofw_prop_read_u32(cm_np, "contribution", &map->contribution);
for (int c = 0; c < map->cells_nr; ++c)
{
struct rt_thermal_cooling_cell *cell = &map->cells[c];
if (rt_ofw_parse_phandle_cells(cm_np, "cooling-device", "#cooling-cells", c, &args))
{
continue;
}
cdev_np = args.data;
rt_spin_lock(&nodes_lock);
device_foreach(cdev, &thermal_cooling_device_nodes)
{
if (cdev->parent.ofw_node == cdev_np)
{
cell->cooling_devices = cdev;
break;
}
}
rt_spin_unlock(&nodes_lock);
cell->level_range[0] = args.args[0];
cell->level_range[1] = args.args[1];
if (cell->cooling_devices)
{
thermal_bind(cell->cooling_devices, zdev);
}
rt_ofw_node_put(cdev_np);
}
}
_end:
}
#else
rt_inline void thermal_ofw_setup(struct rt_ofw_node *np, struct rt_thermal_zone_device *zdev)
{
}
#endif /* RT_USING_OFW */
static void thermal_zone_poll(struct rt_work *work, void *work_data)
{
struct rt_thermal_zone_device *zdev = work_data;
rt_thermal_zone_device_update(zdev, RT_THERMAL_MSG_EVENT_UNSPECIFIED);
}
rt_err_t rt_thermal_zone_device_register(struct rt_thermal_zone_device *zdev)
{
if (!zdev || !zdev->ops || !zdev->ops->get_temp)
{
return -RT_EINVAL;
}
zdev->ops->get_temp(zdev, &zdev->temperature);
zdev->last_temperature = zdev->temperature;
if (!zdev->trips)
{
zdev->trips_nr = 0;
}
rt_spin_lock_init(&zdev->nodes_lock);
rt_list_init(&zdev->notifier_nodes);
rt_list_init(&device_list(zdev));
rt_mutex_init(&zdev->mutex, rt_dm_dev_get_name(&zdev->parent), RT_IPC_FLAG_PRIO);
zdev->temperature = RT_THERMAL_TEMP_INVALID;
zdev->prev_low_trip = -INT_MAX;
zdev->prev_high_trip = INT_MAX;
rt_spin_lock(&nodes_lock);
rt_list_insert_before(&thermal_zone_device_nodes, &device_list(zdev));
rt_spin_unlock(&nodes_lock);
thermal_ofw_setup(zdev->parent.ofw_node, zdev);
rt_work_init(&zdev->poller, thermal_zone_poll, zdev);
zdev->enabled = RT_TRUE;
/* Start to poll */
rt_work_submit(&zdev->poller, zdev->polling_delay);
return RT_EOK;
}
rt_err_t rt_thermal_zone_device_unregister(struct rt_thermal_zone_device *zdev)
{
if (!zdev)
{
return -RT_EINVAL;
}
rt_spin_lock(&zdev->nodes_lock);
if (rt_list_isempty(&zdev->notifier_nodes))
{
LOG_E("%s: there is %u user", rt_dm_dev_get_name(&zdev->parent),
rt_list_len(&zdev->notifier_nodes));
rt_spin_unlock(&zdev->nodes_lock);
return -RT_EBUSY;
}
rt_spin_unlock(&zdev->nodes_lock);
rt_work_cancel(&zdev->poller);
rt_spin_lock(&nodes_lock);
rt_list_remove(&device_list(zdev));
rt_spin_unlock(&nodes_lock);
if (zdev->trips_free && zdev->trips)
{
rt_free(zdev->trips);
}
if (zdev->cooling_maps_nr && zdev->cooling_maps_nr)
{
for (int i = 0; i < zdev->cooling_maps_nr; ++i)
{
struct rt_thermal_cooling_device *cdev;
struct rt_thermal_cooling_map *map = &zdev->cooling_maps[i];
for (int c = 0; c < map->cells_nr; ++c)
{
cdev = map->cells[i].cooling_devices;
if (cdev)
{
thermal_unbind(cdev, zdev);
}
}
rt_free(map->cells);
}
rt_free(zdev->cooling_maps);
}
rt_mutex_detach(&zdev->mutex);
return RT_EOK;
}
rt_err_t rt_thermal_cooling_device_register(struct rt_thermal_cooling_device *cdev)
{
rt_err_t err;
if (!cdev || !cdev->ops ||
!cdev->ops->get_max_level || !cdev->ops->get_cur_level || !cdev->ops->set_cur_level)
{
return -RT_EINVAL;
}
if ((err = cdev->ops->get_max_level(cdev, &cdev->max_level)))
{
return err;
}
rt_list_init(&device_list(cdev));
rt_list_init(&cdev->governor_node);
rt_spin_lock(&nodes_lock);
rt_list_insert_before(&thermal_cooling_device_nodes, &device_list(cdev));
rt_spin_unlock(&nodes_lock);
err = rt_thermal_cooling_device_change_governor(cdev, RT_NULL);
return err;
}
rt_err_t rt_thermal_cooling_device_unregister(struct rt_thermal_cooling_device *cdev)
{
if (!cdev)
{
return -RT_EINVAL;
}
if (cdev->parent.ref_count)
{
LOG_E("%s: there is %u user",
rt_dm_dev_get_name(&cdev->parent), cdev->parent.ref_count);
return -RT_EINVAL;
}
rt_spin_lock(&nodes_lock);
rt_list_remove(&device_list(cdev));
rt_spin_unlock(&nodes_lock);
return RT_EOK;
}
static void dumb_governor_tuning(struct rt_thermal_zone_device *zdev,
int map_idx, int cell_idx, rt_ubase_t *level)
{
struct rt_thermal_cooling_map *map = &zdev->cooling_maps[map_idx];
if (zdev->cooling && zdev->temperature > map->trips->temperature)
{
if (zdev->temperature - zdev->last_temperature > map->trips->hysteresis)
{
++*level;
}
else if (zdev->last_temperature - zdev->temperature > map->trips->hysteresis)
{
--*level;
}
}
else
{
*level = 0;
}
}
static struct rt_thermal_cooling_governor dumb_governor =
{
.name = "dumb",
.tuning = dumb_governor_tuning,
};
static int system_thermal_cooling_governor_init(void)
{
rt_thermal_cooling_governor_register(&dumb_governor);
return 0;
}
INIT_CORE_EXPORT(system_thermal_cooling_governor_init);
rt_err_t rt_thermal_cooling_governor_register(struct rt_thermal_cooling_governor *gov)
{
rt_err_t err = RT_EOK;
struct rt_thermal_cooling_governor *gov_tmp;
if (!gov || !gov->name || !gov->tuning)
{
return -RT_EINVAL;
}
rt_list_init(&gov->list);
rt_list_init(&gov->cdev_nodes);
rt_spin_lock(&nodes_lock);
rt_list_for_each_entry(gov_tmp, &thermal_cooling_governor_nodes, list)
{
if (!rt_strcmp(gov_tmp->name, gov->name))
{
err = -RT_ERROR;
goto _out_unlock;
}
}
rt_list_insert_before(&thermal_cooling_governor_nodes, &gov->list);
_out_unlock:
rt_spin_unlock(&nodes_lock);
return err;
}
rt_err_t rt_thermal_cooling_governor_unregister(struct rt_thermal_cooling_governor *gov)
{
if (!gov)
{
return -RT_EINVAL;
}
if (gov == &dumb_governor)
{
return -RT_EINVAL;
}
rt_spin_lock(&nodes_lock);
if (!rt_list_isempty(&gov->cdev_nodes))
{
goto _out_unlock;
}
rt_list_remove(&gov->list);
_out_unlock:
rt_spin_unlock(&nodes_lock);
return RT_EOK;
}
rt_err_t rt_thermal_cooling_device_change_governor(struct rt_thermal_cooling_device *cdev,
const char *name)
{
rt_err_t err;
struct rt_thermal_cooling_governor *gov;
if (!cdev)
{
return -RT_EINVAL;
}
name = name ? : dumb_governor.name;
err = -RT_ENOSYS;
rt_spin_lock(&nodes_lock);
rt_list_for_each_entry(gov, &thermal_cooling_governor_nodes, list)
{
if (!rt_strcmp(gov->name, name))
{
if (cdev->gov)
{
rt_list_remove(&cdev->governor_node);
}
cdev->gov = gov;
rt_list_insert_before(&cdev->governor_node, &gov->cdev_nodes);
err = RT_EOK;
break;
}
}
rt_spin_unlock(&nodes_lock);
return err;
}
rt_err_t rt_thermal_zone_notifier_register(struct rt_thermal_zone_device *zdev,
struct rt_thermal_notifier *notifier)
{
if (!zdev || !notifier)
{
return -RT_EINVAL;
}
notifier->zdev = zdev;
rt_list_init(&notifier->list);
rt_spin_lock(&zdev->nodes_lock);
rt_list_insert_after(&zdev->notifier_nodes, &notifier->list);
rt_spin_unlock(&zdev->nodes_lock);
return RT_EOK;
}
rt_err_t rt_thermal_zone_notifier_unregister(struct rt_thermal_zone_device *zdev,
struct rt_thermal_notifier *notifier)
{
if (!zdev || !notifier)
{
return -RT_EINVAL;
}
rt_spin_lock(&zdev->nodes_lock);
rt_list_remove(&notifier->list);
rt_spin_unlock(&zdev->nodes_lock);
return RT_EOK;
}
void rt_thermal_zone_device_update(struct rt_thermal_zone_device *zdev, rt_ubase_t msg)
{
rt_err_t err;
rt_bool_t passive = RT_FALSE, need_cool = RT_FALSE;
struct rt_thermal_notifier *notifier, *next_notifier;
RT_ASSERT(zdev != RT_NULL);
if (!rt_interrupt_get_nest())
{
rt_mutex_take(&zdev->mutex, RT_WAITING_FOREVER);
}
/* Check thermal zone status */
if (msg == RT_THERMAL_MSG_DEVICE_DOWN)
{
zdev->enabled = RT_FALSE;
}
else if (msg == RT_THERMAL_MSG_DEVICE_UP)
{
zdev->enabled = RT_TRUE;
}
/* Read temperature */
zdev->last_temperature = zdev->temperature;
zdev->ops->get_temp(zdev, &zdev->temperature);
for (int i = 0; i < zdev->trips_nr; ++i)
{
struct rt_thermal_trip *tmp_trip = &zdev->trips[i];
if (zdev->temperature <= tmp_trip->temperature)
{
continue;
}
switch (tmp_trip->type)
{
case RT_THERMAL_TRIP_PASSIVE:
passive = RT_TRUE;
goto cooling;
case RT_THERMAL_TRIP_CRITICAL:
if (zdev->ops->critical)
{
zdev->ops->critical(zdev);
}
else if (zdev->last_temperature > tmp_trip->temperature)
{
/* Tried to cool already, but failed */
rt_hw_cpu_reset();
}
else
{
goto cooling;
}
break;
case RT_THERMAL_TRIP_HOT:
if (zdev->ops->hot)
{
zdev->ops->hot(zdev);
break;
}
default:
cooling:
zdev->cooling = need_cool = RT_TRUE;
rt_thermal_cooling_device_kick(zdev);
break;
}
}
if (!need_cool && zdev->cooling)
{
rt_thermal_cooling_device_kick(zdev);
}
/* Set the new trips */
if (zdev->ops->set_trips)
{
rt_bool_t same_trip = RT_FALSE;
int low = -INT_MAX, high = INT_MAX;
struct rt_thermal_trip trip;
for (int i = 0; i < zdev->trips_nr; ++i)
{
int trip_low;
rt_bool_t low_set = RT_FALSE;
if (i >= zdev->trips_nr)
{
goto _call_notifier;
}
rt_memcpy(&trip, &zdev->trips[i], sizeof(trip));
trip_low = trip.temperature - trip.hysteresis;
if (trip_low < zdev->temperature && trip_low > low)
{
low = trip_low;
low_set = RT_TRUE;
same_trip = RT_FALSE;
}
if (trip.temperature > zdev->temperature && trip.temperature < high)
{
high = trip.temperature;
same_trip = low_set;
}
}
/* No need to change trip points */
if (zdev->prev_low_trip == low && zdev->prev_high_trip == high)
{
goto _call_notifier;
}
if (same_trip &&
(zdev->prev_low_trip != -INT_MAX || zdev->prev_high_trip != INT_MAX))
{
goto _call_notifier;
}
zdev->prev_low_trip = low;
zdev->prev_high_trip = high;
if ((err = zdev->ops->set_trips(zdev, low, high)))
{
LOG_E("%s: Set trips error = %s", rt_dm_dev_get_name(&zdev->parent),
rt_strerror(err));
}
}
/* Call all notifier, maybe have governor */
_call_notifier:
rt_spin_lock(&zdev->nodes_lock);
rt_list_for_each_entry_safe(notifier, next_notifier, &zdev->notifier_nodes, list)
{
rt_spin_unlock(&zdev->nodes_lock);
notifier->callback(notifier, msg);
rt_spin_lock(&zdev->nodes_lock);
}
rt_spin_unlock(&zdev->nodes_lock);
/* Prepare for the next report */
if (!zdev->enabled)
{
rt_work_cancel(&zdev->poller);
}
else if (passive && zdev->passive_delay)
{
rt_work_submit(&zdev->poller, zdev->passive_delay);
}
else if (zdev->polling_delay)
{
rt_work_submit(&zdev->poller, zdev->polling_delay);
}
if (!rt_interrupt_get_nest())
{
rt_mutex_release(&zdev->mutex);
}
}
void rt_thermal_cooling_device_kick(struct rt_thermal_zone_device *zdev)
{
RT_ASSERT(zdev != RT_NULL);
for (int i = 0; i < zdev->cooling_maps_nr; ++i)
{
rt_ubase_t level;
struct rt_thermal_cooling_device *cdev;
struct rt_thermal_cooling_cell *cell;
struct rt_thermal_cooling_map *map = &zdev->cooling_maps[i];
for (int c = 0; c < map->cells_nr; ++c)
{
cell = &map->cells[c];
cdev = cell->cooling_devices;
if (!cdev)
{
continue;
}
/* Update status */
if (cdev->ops->get_max_level(cdev, &cdev->max_level))
{
continue;
}
if (cdev->ops->get_cur_level(cdev, &level) || level > cdev->max_level)
{
continue;
}
/* Check if cooling is required */
if (level >= cell->level_range[0] && level <= cell->level_range[1])
{
/* Is cooling, not call */
continue;
}
cdev->gov->tuning(zdev, i, c, &level);
level = rt_min_t(rt_ubase_t, level, cdev->max_level);
cdev->ops->set_cur_level(cdev, level);
}
}
}
rt_err_t rt_thermal_zone_set_trip(struct rt_thermal_zone_device *zdev, int trip_id,
const struct rt_thermal_trip *trip)
{
rt_err_t err;
struct rt_thermal_trip tmp_trip;
if (!zdev || !trip)
{
return -RT_EINVAL;
}
rt_mutex_take(&zdev->mutex, RT_WAITING_FOREVER);
if (!zdev->ops->set_trip_temp && !zdev->ops->set_trip_hyst && !zdev->trips)
{
err = -RT_EINVAL;
goto _out_unlock;
}
if (trip_id >= zdev->trips_nr)
{
err = -RT_EINVAL;
goto _out_unlock;
}
rt_memcpy(&tmp_trip, &zdev->trips[trip_id], sizeof(tmp_trip));
if (tmp_trip.type != trip->type)
{
err = -RT_EINVAL;
goto _out_unlock;
}
if (tmp_trip.temperature != trip->temperature && zdev->ops->set_trip_temp)
{
if ((err = zdev->ops->set_trip_temp(zdev, trip_id, trip->temperature)))
{
goto _out_unlock;
}
}
if (tmp_trip.hysteresis != trip->hysteresis && zdev->ops->set_trip_hyst)
{
if ((err = zdev->ops->set_trip_hyst(zdev, trip_id, trip->hysteresis)))
{
goto _out_unlock;
}
}
if (zdev->trips &&
(tmp_trip.temperature != trip->temperature || tmp_trip.hysteresis != trip->hysteresis))
{
zdev->trips[trip_id] = *trip;
}
_out_unlock:
rt_mutex_release(&zdev->mutex);
if (!err)
{
rt_thermal_zone_device_update(zdev, RT_THERMAL_MSG_TRIP_CHANGED);
}
return err;
}
rt_err_t rt_thermal_zone_get_trip(struct rt_thermal_zone_device *zdev, int trip_id,
struct rt_thermal_trip *out_trip)
{
rt_err_t err = RT_EOK;
if (!zdev || !out_trip)
{
return -RT_EINVAL;
}
rt_mutex_take(&zdev->mutex, RT_WAITING_FOREVER);
if (!zdev->trips_nr)
{
err = -RT_ENOSYS;
goto _out_unlock;
}
if (trip_id >= zdev->trips_nr)
{
err = -RT_EINVAL;
goto _out_unlock;
}
*out_trip = zdev->trips[trip_id];
_out_unlock:
rt_mutex_release(&zdev->mutex);
return err;
}
#if defined(RT_USING_CONSOLE) && defined(RT_USING_MSH)
static int list_thermal(int argc, char**argv)
{
struct rt_thermal_zone_device *zdev;
/* Thermal is an important subsystem, please do not output too much. */
rt_spin_lock(&nodes_lock);
device_foreach(zdev, &thermal_zone_device_nodes)
{
int temperature = zdev->temperature;
rt_kprintf("%s-%d\n", rt_dm_dev_get_name(&zdev->parent), zdev->zone_id);
rt_kprintf("temperature:\t%+d.%u C\n", temperature / 1000, rt_abs(temperature) % 1000);
for (int i = 0, id = 0; i < zdev->cooling_maps_nr; ++i)
{
rt_ubase_t level;
struct rt_thermal_trip *trips;
struct rt_thermal_cooling_device *cdev;
struct rt_thermal_cooling_cell *cell;
struct rt_thermal_cooling_map *map = &zdev->cooling_maps[i];
for (int c = 0; c < map->cells_nr; ++c, ++id)
{
trips = map->trips;
cell = &map->cells[c];
cdev = cell->cooling_devices;
if (cdev)
{
cdev->ops->get_cur_level(cdev, &level);
rt_kprintf("cooling%u:\t%s[%+d.%u C] %d\n", id,
rt_dm_dev_get_name(&cdev->parent),
trips->temperature / 1000, rt_abs(trips->temperature) % 1000,
level);
}
else
{
rt_kprintf("cooling%u:\t%s[%+d.%u C] %d\n", id,
"(not supported)",
trips->temperature / 1000, rt_abs(trips->temperature) % 1000,
0);
}
}
}
}
rt_spin_unlock(&nodes_lock);
return 0;
}
MSH_CMD_EXPORT(list_thermal, dump all of thermal information);
#endif /* RT_USING_CONSOLE && RT_USING_MSH */

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-3-08 GuEe-GUI the first version
*/
#define DBG_TAG "rtdm.thermal"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#include "thermal_dm.h"
enum rt_thermal_trip_type thermal_type(const char *type)
{
if (!type)
{
return RT_THERMAL_TRIP_TYPE_MAX;
}
if (!rt_strcmp(type, "active"))
{
return RT_THERMAL_TRIP_ACTIVE;
}
else if (!rt_strcmp(type, "passive"))
{
return RT_THERMAL_TRIP_PASSIVE;
}
else if (!rt_strcmp(type, "hot"))
{
return RT_THERMAL_TRIP_HOT;
}
else if (!rt_strcmp(type, "critical"))
{
return RT_THERMAL_TRIP_CRITICAL;
}
return RT_THERMAL_TRIP_TYPE_MAX;
}
rt_err_t thermal_bind(struct rt_thermal_cooling_device *cdev,
struct rt_thermal_zone_device *zdev)
{
if (cdev->ops->bind)
{
return cdev->ops->bind(cdev, zdev);
}
return RT_EOK;
}
rt_err_t thermal_unbind(struct rt_thermal_cooling_device *cdev,
struct rt_thermal_zone_device *zdev)
{
if (cdev->ops->unbind)
{
return cdev->ops->unbind(cdev, zdev);
}
return RT_EOK;
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-3-08 GuEe-GUI the first version
*/
#ifndef __THERMAL_DM_H__
#define __THERMAL_DM_H__
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <drivers/ofw.h>
enum rt_thermal_trip_type thermal_type(const char *type);
rt_err_t thermal_bind(struct rt_thermal_cooling_device *cdev,
struct rt_thermal_zone_device *zdev);
rt_err_t thermal_unbind(struct rt_thermal_cooling_device *cdev,
struct rt_thermal_zone_device *zdev);
#endif /* __THERMAL_DM_H__ */