/* * 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 */