diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig index 5248fd0ff7..95ce1eefd1 100755 --- a/components/drivers/Kconfig +++ b/components/drivers/Kconfig @@ -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" diff --git a/components/drivers/include/drivers/thermal.h b/components/drivers/include/drivers/thermal.h new file mode 100644 index 0000000000..0770c5c453 --- /dev/null +++ b/components/drivers/include/drivers/thermal.h @@ -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 +#include + +/* 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__ */ diff --git a/components/drivers/include/dt-bindings/thermal/thermal.h b/components/drivers/include/dt-bindings/thermal/thermal.h new file mode 100644 index 0000000000..60414cf29a --- /dev/null +++ b/components/drivers/include/dt-bindings/thermal/thermal.h @@ -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__ */ diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h index ef395d3192..81a96e66fd 100644 --- a/components/drivers/include/rtdevice.h +++ b/components/drivers/include/rtdevice.h @@ -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 diff --git a/components/drivers/thermal/Kconfig b/components/drivers/thermal/Kconfig new file mode 100644 index 0000000000..533ab24ec6 --- /dev/null +++ b/components/drivers/thermal/Kconfig @@ -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 diff --git a/components/drivers/thermal/SConscript b/components/drivers/thermal/SConscript new file mode 100644 index 0000000000..ef7ea04398 --- /dev/null +++ b/components/drivers/thermal/SConscript @@ -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') diff --git a/components/drivers/thermal/thermal.c b/components/drivers/thermal/thermal.c new file mode 100644 index 0000000000..e9765c8715 --- /dev/null +++ b/components/drivers/thermal/thermal.c @@ -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 + +#define DBG_TAG "rtdm.thermal" +#define DBG_LVL DBG_INFO +#include + +#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(¬ifier->list); + + rt_spin_lock(&zdev->nodes_lock); + rt_list_insert_after(&zdev->notifier_nodes, ¬ifier->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(¬ifier->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 */ diff --git a/components/drivers/thermal/thermal_dm.c b/components/drivers/thermal/thermal_dm.c new file mode 100644 index 0000000000..9c1d81f417 --- /dev/null +++ b/components/drivers/thermal/thermal_dm.c @@ -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 + +#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; +} diff --git a/components/drivers/thermal/thermal_dm.h b/components/drivers/thermal/thermal_dm.h new file mode 100644 index 0000000000..15d3314a58 --- /dev/null +++ b/components/drivers/thermal/thermal_dm.h @@ -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 +#include +#include + +#include + +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__ */