[dm][dvfs] support Dynamic Voltage and Frequency Scaling (DVFS)

1. Support DVFS and finsh cmd, there are 6 governors:
   - conservative
   - freedom
   - performance
   - powersave
   - schedutil
2. Support DVFS for SCMI.
3. Port the Cooling device for DVFS.
4. Port the PM with DVFS.

Signed-off-by: GuEe-GUI <2991707448@qq.com>
This commit is contained in:
GuEe-GUI
2026-06-23 08:02:09 +08:00
committed by Rbb666
parent 1870d83956
commit e280f213bf
33 changed files with 5094 additions and 11 deletions
+1
View File
@@ -34,6 +34,7 @@ rsource "scsi/Kconfig"
rsource "ufs/Kconfig"
rsource "firmware/Kconfig"
rsource "hwcache/Kconfig"
rsource "dvfs/Kconfig"
rsource "regulator/Kconfig"
rsource "reset/Kconfig"
rsource "pmdomain/Kconfig"
+51
View File
@@ -0,0 +1,51 @@
menuconfig RT_USING_DVFS
bool "Using Dynamic Voltage and Frequency Scaling (DVFS)"
depends on RT_USING_DM
depends on RT_USING_CLK
depends on RT_USING_REGULATOR
select RT_USING_ADT
select RT_USING_ADT_REF
select RT_USING_ADT_BITMAP
select RT_USING_DEVICE_IPC
select RT_USING_SYSTEM_WORKQUEUE
default n
config RT_USING_DVFS_EVENT
bool "Event"
depends on RT_USING_DVFS
default y
config RT_USING_DVFS_OPP_RETRY_MAX
int "OPP retry max time"
depends on RT_USING_DVFS
default 10
if RT_USING_DVFS && RT_USING_DVFS_EVENT
comment "DVFS Event Drivers"
endif
if RT_USING_DVFS
osource "$(SOC_DM_DVFS_EVENT_DIR)/Kconfig"
endif
if RT_USING_DVFS
comment "DVFS CPUfreq Drivers"
endif
config RT_DVFS_SCMI_CPUFREQ
bool "SCMI CPUfreq driver"
depends on RT_USING_DVFS
depends on RT_FIRMWARE_ARM_SCMI
default n
if RT_USING_DVFS
osource "$(SOC_DM_DVFS_CPUFREQ_DIR)/Kconfig"
endif
if RT_USING_DVFS
comment "DVFS Devfreq Drivers"
endif
if RT_USING_DVFS
osource "$(SOC_DM_DVFS_DEVFREQ_DIR)/Kconfig"
endif
+38
View File
@@ -0,0 +1,38 @@
from building import *
group = []
objs = []
if not GetDepend(['RT_USING_DVFS']):
Return('group')
cwd = GetCurrentDir()
list = os.listdir(cwd)
CPPPATH = [cwd + '/../include']
src = ['dvfs.c', 'dvfs_cpu.c', 'dvfs_governor.c', 'dvfs_idle.c', 'dvfs_opp.c']
if GetDepend(['RT_USING_CONSOLE', 'RT_USING_MSH']):
src += ['dvfs_cmd.c']
if GetDepend(['RT_USING_DVFS_EVENT']):
src += ['dvfs_event.c']
if GetDepend(['RT_USING_PM']):
src += ['dvfs_pm.c']
if GetDepend(['RT_USING_OFW']):
src += ['dvfs-ofw.c']
if GetDepend(['RT_DVFS_SCMI_CPUFREQ']):
src += ['dvfs-scmi-cpufreq.c']
group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
for d in list:
path = os.path.join(cwd, d)
if os.path.isfile(os.path.join(path, 'SConscript')):
objs = objs + SConscript(os.path.join(d, 'SConscript'))
objs = objs + group
Return('group')
+186
View File
@@ -0,0 +1,186 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-11-21 GuEe-GUI first version
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "dvfs.ofw"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
static rt_err_t ofw_dvfs_setup(struct rt_platform_device *pdev,
struct rt_dvfs_devfreq *devfreq, rt_bool_t is_cpu)
{
struct rt_device *dev = &pdev->parent;
struct rt_dvfs_scaling *scaling = rt_dvfs_devfreq_to_scaling(devfreq);
rt_err_t err;
if (!devfreq)
{
return -RT_EINVAL;
}
scaling->dev = dev;
scaling->ops = &rt_dvfs_devfreq_ops;
if (!scaling->clk)
{
scaling->clk = rt_clk_get_by_index(dev, 0);
if (rt_is_err(scaling->clk))
{
LOG_E("Get clock failed");
return rt_ptr_err(scaling->clk);
}
}
if (!scaling->supply)
{
if (is_cpu)
{
scaling->supply = rt_regulator_get(dev, "cpu");
}
else
{
scaling->supply = rt_regulator_get(dev, "dev");
if (rt_is_err(scaling->supply))
{
scaling->supply = rt_regulator_get(dev, "mem");
}
}
if (rt_is_err(scaling->supply))
{
scaling->supply = RT_NULL;
}
}
if (!scaling->transition_latency)
{
scaling->transition_latency = 1000000;
}
if (!scaling->retry_delay)
{
scaling->retry_delay = 100000;
}
#ifdef RT_USING_DVFS_EVENT
if (!is_cpu && !devfreq->ev && dev->ofw_node)
{
devfreq->ev = rt_dvfs_event_get(dev, "devfreq-event", 0);
if (rt_is_err_or_null(devfreq->ev))
{
devfreq->ev = RT_NULL;
LOG_D("%s: no devfreq-event", rt_dm_dev_get_name(dev));
}
}
#endif
err = rt_dvfs_devfreq_register(devfreq);
if (err)
{
LOG_E("Register devfreq failed: %s", rt_strerror(err));
goto fail;
}
err = rt_dvfs_scaling_set_governor(scaling, RT_DVFS_GOVERNOR_TYPE_ONDEMAND);
if (err)
{
LOG_W("Set ondemand governor failed: %s", rt_strerror(err));
err = rt_dvfs_scaling_set_governor(scaling, RT_DVFS_GOVERNOR_TYPE_PERFORMANCE);
}
LOG_D("Devfreq registered for %s, freq range: %lu-%lu Hz",
rt_dm_dev_get_name(dev), scaling->min_freq, scaling->max_freq);
return RT_EOK;
fail:
if (scaling->clk)
{
rt_clk_put(scaling->clk);
scaling->clk = RT_NULL;
}
if (scaling->supply)
{
rt_regulator_put(scaling->supply);
scaling->supply = RT_NULL;
}
return err;
}
static rt_err_t ofw_cpufreq_probe(struct rt_platform_device *pdev)
{
struct rt_dvfs_cpufreq *cpufreq = pdev->priv;
if (!cpufreq)
{
LOG_E("No cpufreq data");
return -RT_EINVAL;
}
return ofw_dvfs_setup(pdev, rt_dvfs_cpufreq_to_devfreq(cpufreq), RT_TRUE);
}
static rt_err_t ofw_cpufreq_remove(struct rt_platform_device *pdev)
{
struct rt_dvfs_cpufreq *cpufreq = pdev->priv;
if (cpufreq)
{
rt_dvfs_cpufreq_unregister(cpufreq);
pdev->priv = RT_NULL;
}
return RT_EOK;
}
static rt_err_t ofw_devfreq_probe(struct rt_platform_device *pdev)
{
struct rt_dvfs_devfreq *devfreq = pdev->priv;
if (!devfreq)
{
LOG_E("No devfreq data");
return -RT_EINVAL;
}
return ofw_dvfs_setup(pdev, devfreq, RT_FALSE);
}
static rt_err_t ofw_devfreq_remove(struct rt_platform_device *pdev)
{
struct rt_dvfs_devfreq *devfreq = pdev->priv;
if (devfreq)
{
rt_dvfs_devfreq_unregister(devfreq);
pdev->priv = RT_NULL;
}
return RT_EOK;
}
static struct rt_platform_driver ofw_cpufreq_driver =
{
.name = "ofw-cpufreq",
.probe = ofw_cpufreq_probe,
.remove = ofw_cpufreq_remove,
};
RT_PLATFORM_DRIVER_EXPORT(ofw_cpufreq_driver);
static struct rt_platform_driver ofw_devfreq_driver =
{
.name = "ofw-devfreq",
.probe = ofw_devfreq_probe,
.remove = ofw_devfreq_remove,
};
RT_PLATFORM_DRIVER_EXPORT(ofw_devfreq_driver);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-11-21 GuEe-GUI first version
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "dvfs.cpu"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
rt_err_t rt_dvfs_cpufreq_register(struct rt_dvfs_cpufreq *cpufreq)
{
rt_err_t err;
if (!cpufreq)
{
return -RT_EINVAL;
}
err = rt_dvfs_devfreq_register(rt_dvfs_cpufreq_to_devfreq(cpufreq));
if (err)
{
return err;
}
LOG_D("CPUfreq registered for device %s",
rt_dm_dev_get_name(rt_dvfs_cpufreq_to_scaling(cpufreq)->dev));
return RT_EOK;
}
rt_err_t rt_dvfs_cpufreq_unregister(struct rt_dvfs_cpufreq *cpufreq)
{
if (!cpufreq)
{
return -RT_EINVAL;
}
return rt_dvfs_devfreq_unregister(rt_dvfs_cpufreq_to_devfreq(cpufreq));
}
File diff suppressed because it is too large Load Diff
+141
View File
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-11-21 GuEe-GUI first version
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "dvfs.governor"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
static RT_DEFINE_SPINLOCK(dvfs_governor_nodes_lock);
static rt_list_t dvfs_governor_nodes = RT_LIST_OBJECT_INIT(dvfs_governor_nodes);
static struct rt_dvfs_governor *dvfs_governor_find(rt_uint32_t governor, const char *name)
{
struct rt_dvfs_governor *gov;
rt_list_for_each_entry(gov, &dvfs_governor_nodes, list)
{
if (governor != RT_UINT32_MAX && gov->type == governor)
{
return gov;
}
else if (name && !rt_strncmp(name, gov->name, sizeof(gov->name) - 1))
{
return gov;
}
}
return RT_NULL;
}
rt_err_t rt_dvfs_governor_register(struct rt_dvfs_governor *gov)
{
rt_err_t err = RT_EOK;
if (!gov)
{
return -RT_EINVAL;
}
RT_ASSERT(gov->set_frequency != RT_NULL);
rt_spin_lock(&dvfs_governor_nodes_lock);
if (dvfs_governor_find(gov->type, gov->name))
{
LOG_E("Governor %s[%u] is exists", gov->name, gov->type);
err = -RT_EFULL;
goto _out_lock;
}
rt_list_init(&gov->list);
rt_ref_init(&gov->ref);
rt_list_insert_before(&dvfs_governor_nodes, &gov->list);
_out_lock:
rt_spin_unlock(&dvfs_governor_nodes_lock);
return err;
}
rt_err_t rt_dvfs_governor_unregister(struct rt_dvfs_governor *gov)
{
rt_err_t err = RT_EOK;
if (!gov)
{
return -RT_EINVAL;
}
rt_spin_lock(&dvfs_governor_nodes_lock);
if (rt_ref_read(&gov->ref) > 1)
{
err = -RT_EBUSY;
goto _out_lock;
}
rt_list_remove(&gov->list);
_out_lock:
rt_spin_unlock(&dvfs_governor_nodes_lock);
return err;
}
struct rt_dvfs_governor *rt_dvfs_governor_get(rt_uint32_t governor)
{
struct rt_dvfs_governor *gov;
rt_spin_lock(&dvfs_governor_nodes_lock);
if ((gov = dvfs_governor_find(governor, RT_NULL)))
{
rt_ref_get(&gov->ref);
}
rt_spin_unlock(&dvfs_governor_nodes_lock);
return gov;
}
struct rt_dvfs_governor *rt_dvfs_governor_get_by_name(const char *name)
{
struct rt_dvfs_governor *gov;
rt_spin_lock(&dvfs_governor_nodes_lock);
if ((gov = dvfs_governor_find(RT_UINT32_MAX, name)))
{
rt_ref_get(&gov->ref);
}
rt_spin_unlock(&dvfs_governor_nodes_lock);
return gov;
}
static void dvfs_governor_release(struct rt_ref *ref)
{
}
void rt_dvfs_governor_put(struct rt_dvfs_governor *gov)
{
if (!gov)
{
return;
}
rt_ref_put(&gov->ref, dvfs_governor_release);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-11-21 GuEe-GUI first version
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "dvfs.pm"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
rt_err_t rt_dvfs_scaling_pm_suspend(struct rt_device_pm *device_pm, rt_uint8_t mode)
{
struct rt_dvfs_scaling *dvfs = device_pm->device->dvfs_scaling;
if (!dvfs)
{
return RT_EOK;
}
return rt_dvfs_scaling_suspend(dvfs);
}
rt_err_t rt_dvfs_scaling_pm_resume(struct rt_device_pm *device_pm, rt_uint8_t mode)
{
struct rt_dvfs_scaling *dvfs = device_pm->device->dvfs_scaling;
if (!dvfs)
{
return RT_EOK;
}
return rt_dvfs_scaling_resume(dvfs);
}
rt_err_t rt_dvfs_scaling_pm_frequency_change(struct rt_device_pm *device_pm, rt_uint8_t mode)
{
rt_uint32_t governor_type = RT_DVFS_GOVERNOR_TYPE_ONDEMAND;
struct rt_dvfs_scaling *dvfs = device_pm->device->dvfs_scaling;
if (!dvfs)
{
return RT_EOK;
}
rt_dvfs_scaling_enter(dvfs);
if (dvfs->gov)
{
governor_type = dvfs->gov->type;
}
rt_dvfs_scaling_leave(dvfs);
if (governor_type == RT_DVFS_GOVERNOR_TYPE_FREEDOM)
{
switch (mode)
{
case PM_RUN_MODE_HIGH_SPEED:
return rt_dvfs_scaling_set_frequency(dvfs, dvfs->max_freq);
case PM_RUN_MODE_NORMAL_SPEED:
/*
* It's not a good idea, but the OPP doesn't seem to have defaults.
*/
return rt_dvfs_scaling_set_frequency(dvfs, (dvfs->max_freq + dvfs->min_freq) / 2);
case PM_RUN_MODE_MEDIUM_SPEED:
return rt_dvfs_scaling_set_frequency(dvfs, dvfs->suspend_freq);
case PM_RUN_MODE_LOW_SPEED:
return rt_dvfs_scaling_set_frequency(dvfs, dvfs->min_freq);
default:
break;
}
}
else
{
switch (mode)
{
case PM_RUN_MODE_HIGH_SPEED:
return rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_PERFORMANCE);
case PM_RUN_MODE_NORMAL_SPEED:
return rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_ONDEMAND);
case PM_RUN_MODE_MEDIUM_SPEED:
return rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_CONSERVATIVE);
case PM_RUN_MODE_LOW_SPEED:
return rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_POWERSAVE);
default:
break;
}
}
return -RT_EINVAL;
}
@@ -0,0 +1,10 @@
from building import *
cwd = GetCurrentDir()
CPPPATH = [cwd + '/../../include']
src = Glob('*.c')
group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
File diff suppressed because it is too large Load Diff
+106
View File
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-11-21 GuEe-GUI first version
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "dvfs.governor.freedom"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
/* Freedom governor: user-controlled frequency, no automatic adjustment */
static rt_err_t governor_freedom_start(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
/* Keep current frequency, no automatic adjustment */
LOG_D("%s: freedom governor started at %lu Hz",
rt_dm_dev_get_name(dvfs->dev), dvfs->cur_freq);
return RT_EOK;
}
static rt_err_t governor_freedom_stop(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
LOG_D("%s: freedom governor stopped", rt_dm_dev_get_name(dvfs->dev));
return RT_EOK;
}
static rt_err_t governor_freedom_suspend(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
/* Nothing special for suspend */
return RT_EOK;
}
static rt_err_t governor_freedom_resume(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
/* Restore to current frequency */
return rt_dvfs_scaling_set_frequency(dvfs, dvfs->cur_freq);
}
static rt_err_t governor_freedom_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms)
{
/* Freedom governor doesn't use sampling interval */
return -RT_ENOSYS;
}
static rt_err_t governor_freedom_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq)
{
if (!dvfs || !out_freq)
{
return -RT_EINVAL;
}
/* Return current frequency (user-controlled) */
*out_freq = dvfs->cur_freq;
return RT_EOK;
}
static struct rt_dvfs_governor governor_freedom =
{
.name = "freedom",
.type = RT_DVFS_GOVERNOR_TYPE_FREEDOM,
.start = governor_freedom_start,
.stop = governor_freedom_stop,
.suspend = governor_freedom_suspend,
.resume = governor_freedom_resume,
.set_interval = governor_freedom_set_interval,
.set_frequency = governor_freedom_set_frequency,
};
static int governor_freedom_init(void)
{
rt_dvfs_governor_register(&governor_freedom);
return 0;
}
INIT_CORE_EXPORT(governor_freedom_init);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-11-21 GuEe-GUI first version
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "dvfs.governor.performance"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
static rt_err_t governor_performance_start(struct rt_dvfs_scaling *dvfs)
{
rt_err_t err;
if (!dvfs)
{
return -RT_EINVAL;
}
/* Set frequency to maximum */
err = rt_dvfs_scaling_set_frequency(dvfs, dvfs->max_freq);
if (err)
{
LOG_E("%s: set max frequency failed: %s",
rt_dm_dev_get_name(dvfs->dev), rt_strerror(err));
return err;
}
LOG_D("%s: performance governor started at %lu Hz",
rt_dm_dev_get_name(dvfs->dev), dvfs->cur_freq);
return RT_EOK;
}
static rt_err_t governor_performance_stop(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
LOG_D("%s: performance governor stopped", rt_dm_dev_get_name(dvfs->dev));
return RT_EOK;
}
static rt_err_t governor_performance_suspend(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
/* Nothing special for suspend */
return RT_EOK;
}
static rt_err_t governor_performance_resume(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
/* Restore to max frequency */
return rt_dvfs_scaling_set_frequency(dvfs, dvfs->max_freq);
}
static rt_err_t governor_performance_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms)
{
/* Performance governor doesn't use sampling interval */
return -RT_ENOSYS;
}
static rt_err_t governor_performance_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq)
{
if (!dvfs || !out_freq)
{
return -RT_EINVAL;
}
/* Always return max frequency */
*out_freq = dvfs->max_freq;
return RT_EOK;
}
static struct rt_dvfs_governor governor_performance =
{
.name = "performance",
.type = RT_DVFS_GOVERNOR_TYPE_PERFORMANCE,
.start = governor_performance_start,
.stop = governor_performance_stop,
.suspend = governor_performance_suspend,
.resume = governor_performance_resume,
.set_interval = governor_performance_set_interval,
.set_frequency = governor_performance_set_frequency,
};
static int governor_performance_init(void)
{
rt_dvfs_governor_register(&governor_performance);
return 0;
}
INIT_CORE_EXPORT(governor_performance_init);
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-11-21 GuEe-GUI first version
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "dvfs.governor.powersave"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
static rt_err_t governor_powersave_start(struct rt_dvfs_scaling *dvfs)
{
rt_err_t err;
if (!dvfs)
{
return -RT_EINVAL;
}
/* Set frequency to minimum */
err = rt_dvfs_scaling_set_frequency(dvfs, dvfs->min_freq);
if (err)
{
LOG_E("%s: set min frequency failed: %s",
rt_dm_dev_get_name(dvfs->dev), rt_strerror(err));
return err;
}
LOG_D("%s: powersave governor started at %lu Hz",
rt_dm_dev_get_name(dvfs->dev), dvfs->cur_freq);
return RT_EOK;
}
static rt_err_t governor_powersave_stop(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
LOG_D("%s: powersave governor stopped", rt_dm_dev_get_name(dvfs->dev));
return RT_EOK;
}
static rt_err_t governor_powersave_suspend(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
/* Nothing special for suspend */
return RT_EOK;
}
static rt_err_t governor_powersave_resume(struct rt_dvfs_scaling *dvfs)
{
if (!dvfs)
{
return -RT_EINVAL;
}
/* Restore to min frequency */
return rt_dvfs_scaling_set_frequency(dvfs, dvfs->min_freq);
}
static rt_err_t governor_powersave_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms)
{
/* Powersave governor doesn't use sampling interval */
return -RT_ENOSYS;
}
static rt_err_t governor_powersave_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq)
{
if (!dvfs || !out_freq)
{
return -RT_EINVAL;
}
/* Always return min frequency */
*out_freq = dvfs->min_freq;
return RT_EOK;
}
static struct rt_dvfs_governor governor_powersave =
{
.name = "powersave",
.type = RT_DVFS_GOVERNOR_TYPE_POWERSAVE,
.start = governor_powersave_start,
.stop = governor_powersave_stop,
.suspend = governor_powersave_suspend,
.resume = governor_powersave_resume,
.set_interval = governor_powersave_set_interval,
.set_frequency = governor_powersave_set_frequency,
};
static int governor_powersave_init(void)
{
rt_dvfs_governor_register(&governor_powersave);
return 0;
}
INIT_CORE_EXPORT(governor_powersave_init);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -59,6 +59,10 @@ struct rt_thermal_cooling_cell
struct rt_thermal_cooling_device *cooling_devices;
rt_uint32_t level_range[2];
#ifdef RT_USING_OFW
struct rt_ofw_node *cooling_np;
#endif
};
struct rt_thermal_cooling_map
+4
View File
@@ -153,6 +153,10 @@ extern "C" {
#include "drivers/hwcache.h"
#endif /* RT_USING_HWCACHE */
#ifdef RT_USING_DVFS
#include "drivers/dvfs.h"
#endif /* RT_USING_DVFS */
#ifdef RT_USING_NVMEM
#include "drivers/nvmem.h"
#endif /* RT_USING_NVMEM */
+21 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2006-2024 RT-Thread Development Team
* Copyright (c) 2006-2026 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -11,11 +11,15 @@
* 2020-11-23 zhangsz update pm mode select
* 2020-11-27 zhangsz update pm 2.0
* 2024-07-04 wdfk-prog The device is registered and uninstalled by linked list
* 2026-06-12 GuEe-GUI port DVFS support
*/
#include <rthw.h>
#include <rtthread.h>
#include <drivers/pm.h>
#ifdef RT_USING_DVFS
#include <drivers/dvfs.h>
#endif
#include <stdlib.h>
#ifdef RT_USING_PM
@@ -119,6 +123,14 @@ static rt_err_t _pm_device_suspend(rt_uint8_t mode)
device_pm = rt_slist_entry(node, struct rt_device_pm, list);
if (device_pm->ops != RT_NULL && device_pm->ops->suspend != RT_NULL)
{
#ifdef RT_USING_DVFS
ret = rt_dvfs_scaling_pm_suspend(device_pm, mode);
if (ret != RT_EOK)
{
break;
}
#endif /* RT_USING_DVFS */
ret = device_pm->ops->suspend(device_pm->device, mode);
if(ret != RT_EOK)
{
@@ -144,6 +156,10 @@ static void _pm_device_resume(rt_uint8_t mode)
if (device_pm->ops != RT_NULL && device_pm->ops->resume != RT_NULL)
{
device_pm->ops->resume(device_pm->device, mode);
#ifdef RT_USING_DVFS
rt_dvfs_scaling_pm_resume(device_pm, mode);
#endif
}
}
}
@@ -161,6 +177,10 @@ static void _pm_device_frequency_change(rt_uint8_t mode)
device_pm = rt_slist_entry(node, struct rt_device_pm, list);
if (device_pm->ops->frequency_change != RT_NULL)
{
#ifdef RT_USING_DVFS
rt_dvfs_scaling_pm_frequency_change(device_pm, mode);
#endif
device_pm->ops->frequency_change(device_pm->device, mode);
}
}
+6
View File
@@ -21,6 +21,12 @@ if RT_USING_THERMAL
comment "Thermal Cool Drivers"
endif
config RT_THERMAL_COOL_DVFS
bool "DVFS"
depends on RT_USING_THERMAL
depends on RT_USING_DVFS
default n
config RT_THERMAL_COOL_PWM_FAN
bool "PWM Fan"
depends on RT_USING_THERMAL
+3
View File
@@ -13,6 +13,9 @@ src = ['thermal.c', 'thermal_dm.c']
if GetDepend(['RT_THERMAL_SCMI']):
src += ['thermal-scmi.c']
if GetDepend(['RT_THERMAL_COOL_DVFS']):
src += ['thermal-cool-dvfs.c']
if GetDepend(['RT_THERMAL_COOL_PWM_FAN']):
src += ['thermal-cool-pwm-fan.c']
@@ -0,0 +1,171 @@
/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2023-02-25 GuEe-GUI the first version
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "thermal.cool.dvfs"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
struct dvfs_cool
{
struct rt_thermal_cooling_device parent;
struct rt_dvfs_scaling *dvfs;
};
#define raw_to_dvfs_cool(raw) rt_container_of(raw, struct dvfs_cool, parent)
static rt_err_t dvfs_cool_get_max_level(struct rt_thermal_cooling_device *cdev,
rt_ubase_t *out_level)
{
struct dvfs_cool *dvfs_cool = raw_to_dvfs_cool(cdev);
struct rt_dvfs_scaling *dvfs = dvfs_cool->dvfs;
rt_dvfs_scaling_enter(dvfs);
if (dvfs->opp_table->opp_nodes.next)
{
*out_level = rt_list_len(&dvfs->opp_table->opp_nodes);
if (*out_level > 0)
{
(*out_level)--;
}
}
else
{
*out_level = 0;
}
rt_dvfs_scaling_leave(dvfs);
return RT_EOK;
}
static rt_err_t dvfs_cool_get_cur_level(struct rt_thermal_cooling_device *cdev,
rt_ubase_t *out_level)
{
struct rt_dvfs_opp *opp;
struct dvfs_cool *dvfs_cool = raw_to_dvfs_cool(cdev);
struct rt_dvfs_scaling *dvfs = dvfs_cool->dvfs;
rt_dvfs_scaling_enter(dvfs);
*out_level = 0;
rt_list_for_each_entry(opp, &dvfs->opp_table->opp_nodes, list)
{
if (opp == dvfs->opp_table->current_opp)
{
break;
}
(*out_level)++;
}
rt_dvfs_scaling_leave(dvfs);
return RT_EOK;
}
static rt_err_t dvfs_cool_set_cur_level(struct rt_thermal_cooling_device *cdev,
rt_ubase_t level)
{
struct rt_dvfs_opp *opp, *target_opp = RT_NULL;
struct dvfs_cool *dvfs_cool = raw_to_dvfs_cool(cdev);
struct rt_dvfs_scaling *dvfs = dvfs_cool->dvfs;
rt_dvfs_scaling_enter(dvfs);
rt_list_for_each_entry(opp, &dvfs->opp_table->opp_nodes, list)
{
if (!level)
{
target_opp = opp;
break;
}
--level;
}
rt_dvfs_scaling_leave(dvfs);
if (target_opp)
{
return rt_dvfs_scaling_apply_opp(dvfs, target_opp);
}
return -RT_EINVAL;
}
const static struct rt_thermal_cooling_device_ops dvfs_cool_ops =
{
.get_max_level = dvfs_cool_get_max_level,
.get_cur_level = dvfs_cool_get_cur_level,
.set_cur_level = dvfs_cool_set_cur_level,
};
static rt_err_t dvfs_cool_probe(struct rt_platform_device *pdev)
{
rt_err_t err;
struct dvfs_cool *dvfs_cool;
struct rt_device *dev = &pdev->parent;
if (!pdev->priv)
{
return -RT_EINVAL;
}
if (!(dvfs_cool = rt_calloc(1, sizeof(*dvfs_cool))))
{
return -RT_ENOMEM;
}
dvfs_cool->dvfs = pdev->priv;
rt_dm_dev_set_name(&dvfs_cool->parent.parent, "%s-cool",
rt_dm_dev_get_name(dvfs_cool->dvfs->dev));
dvfs_cool->parent.parent.ofw_node = dev->ofw_node;
dvfs_cool->parent.ops = &dvfs_cool_ops;
if ((err = rt_thermal_cooling_device_register(&dvfs_cool->parent)))
{
goto _fail;
}
dev->user_data = dvfs_cool;
return RT_EOK;
_fail:
rt_free(dvfs_cool);
return err;
}
static rt_err_t dvfs_cool_remove(struct rt_platform_device *pdev)
{
struct dvfs_cool *dvfs_cool = pdev->parent.user_data;
rt_thermal_cooling_device_unregister(&dvfs_cool->parent);
rt_free(dvfs_cool);
return RT_EOK;
}
static struct rt_platform_driver dvfs_cool_driver =
{
.name = "dvfs-cool",
.probe = dvfs_cool_probe,
.remove = dvfs_cool_remove,
};
RT_PLATFORM_DRIVER_EXPORT(dvfs_cool_driver);
+73 -9
View File
@@ -199,6 +199,8 @@ _scan_cooling:
}
cdev_np = args.data;
cell->cooling_np = cdev_np;
rt_ofw_node_get(cdev_np);
rt_spin_lock(&nodes_lock);
device_foreach(cdev, &thermal_cooling_device_nodes)
@@ -218,17 +220,54 @@ _scan_cooling:
{
thermal_bind(cell->cooling_devices, zdev);
}
rt_ofw_node_put(cdev_np);
}
}
_end:
;
}
void thermal_cooling_device_bind_zones(struct rt_thermal_cooling_device *cdev)
{
struct rt_thermal_zone_device *zdev;
if (!cdev || !cdev->parent.ofw_node)
{
return;
}
rt_spin_lock(&nodes_lock);
device_foreach(zdev, &thermal_zone_device_nodes)
{
for (int i = 0; i < zdev->cooling_maps_nr; ++i)
{
struct rt_thermal_cooling_map *map = &zdev->cooling_maps[i];
for (int c = 0; c < map->cells_nr; ++c)
{
struct rt_thermal_cooling_cell *cell = &map->cells[c];
if (cell->cooling_devices || cell->cooling_np != cdev->parent.ofw_node)
{
continue;
}
cell->cooling_devices = cdev;
thermal_bind(cdev, zdev);
}
}
}
rt_spin_unlock(&nodes_lock);
}
#else
rt_inline void thermal_ofw_setup(struct rt_ofw_node *np, struct rt_thermal_zone_device *zdev)
{
}
rt_inline void thermal_cooling_device_bind_zones(struct rt_thermal_cooling_device *cdev)
{
RT_UNUSED(cdev);
}
#endif /* RT_USING_OFW */
static void thermal_zone_poll(struct rt_work *work, void *work_data)
@@ -311,17 +350,24 @@ rt_err_t rt_thermal_zone_device_unregister(struct rt_thermal_zone_device *zdev)
{
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;
struct rt_thermal_cooling_cell *cell = &map->cells[c];
if (cdev)
if (cell->cooling_devices)
{
thermal_unbind(cdev, zdev);
thermal_unbind(cell->cooling_devices, zdev);
}
#ifdef RT_USING_OFW
if (cell->cooling_np)
{
rt_ofw_node_put(cell->cooling_np);
cell->cooling_np = RT_NULL;
}
#endif
}
rt_free(map->cells);
@@ -358,6 +404,10 @@ rt_err_t rt_thermal_cooling_device_register(struct rt_thermal_cooling_device *cd
rt_spin_unlock(&nodes_lock);
err = rt_thermal_cooling_device_change_governor(cdev, RT_NULL);
if (!err)
{
thermal_cooling_device_bind_zones(cdev);
}
return err;
}
@@ -624,6 +674,7 @@ void rt_thermal_zone_device_update(struct rt_thermal_zone_device *zdev, rt_ubase
if (!need_cool && zdev->cooling)
{
zdev->cooling = RT_FALSE;
rt_thermal_cooling_device_kick(zdev);
}
@@ -721,6 +772,7 @@ void rt_thermal_cooling_device_kick(struct rt_thermal_zone_device *zdev)
for (int i = 0; i < zdev->cooling_maps_nr; ++i)
{
rt_ubase_t level;
rt_ubase_t max_level;
struct rt_thermal_cooling_device *cdev;
struct rt_thermal_cooling_cell *cell;
struct rt_thermal_cooling_map *map = &zdev->cooling_maps[i];
@@ -736,12 +788,24 @@ void rt_thermal_cooling_device_kick(struct rt_thermal_zone_device *zdev)
}
/* Update status */
if (cdev->ops->get_max_level(cdev, &cdev->max_level))
if (cdev->ops->get_max_level(cdev, &max_level))
{
continue;
}
if (cdev->ops->get_cur_level(cdev, &level) || level > cdev->max_level)
cdev->max_level = max_level;
if (!zdev->cooling)
{
/* Release cooling: restore full performance (highest OPP). */
if (!cdev->ops->get_cur_level(cdev, &level) && level != max_level)
{
cdev->ops->set_cur_level(cdev, max_level);
}
continue;
}
if (cdev->ops->get_cur_level(cdev, &level) || level > max_level)
{
continue;
}
@@ -754,7 +818,7 @@ void rt_thermal_cooling_device_kick(struct rt_thermal_zone_device *zdev)
}
cdev->gov->tuning(zdev, i, c, &level);
level = rt_min_t(rt_ubase_t, level, cdev->max_level);
level = rt_min_t(rt_ubase_t, level, max_level);
cdev->ops->set_cur_level(cdev, level);
}
+2
View File
@@ -24,4 +24,6 @@ rt_err_t thermal_bind(struct rt_thermal_cooling_device *cdev,
rt_err_t thermal_unbind(struct rt_thermal_cooling_device *cdev,
struct rt_thermal_zone_device *zdev);
void thermal_cooling_device_bind_zones(struct rt_thermal_cooling_device *cdev);
#endif /* __THERMAL_DM_H__ */
@@ -34,6 +34,7 @@
- @subpage page_device_disk
- @subpage page_device_partitions
- @subpage page_device_hwcache
- @subpage page_device_dvfs
- @subpage page_device_can
- @subpage page_device_can_dm
- @subpage page_device_clk
@@ -0,0 +1,178 @@
@page page_device_dvfs DVFS
# Dynamic Voltage and Frequency Scaling (DVFS)
Headers: **`components/drivers/include/drivers/dvfs.h`**. Core: **`components/drivers/dvfs/dvfs.c`**, governors: **`components/drivers/dvfs/governor/`**, shell: **`components/drivers/dvfs/dvfs_cmd.c`**.
DVFS selects an **OPP (Operating Performance Point)** — a frequency and supply voltage pair — according to a **governor** policy. The framework owns the OPP table, governor lifecycle, and MSH commands; **BSP or SoC drivers** implement how a given OPP is applied (clock, regulator, firmware calls).
**Kconfig**: **`RT_USING_DVFS`** (requires **`RT_USING_DM`**, **`RT_USING_CLK`**, **`RT_USING_REGULATOR`**). Optional: **`RT_USING_DVFS_EVENT`**, **`RT_DVFS_SCMI_CPUFREQ`**, and BSP options under **`SOC_DM_DVFS_*`**.
---
## Architecture
```
Device tree (operating-points-v2, supplies, clocks)
|
v
BSP cpufreq / devfreq driver (SOC_DM_DVFS_CPUFREQ_DIR, …)
fills rt_dvfs_scaling_ops (set_opp, parse_opp, …)
|
v
rt_dvfs_cpufreq_register() / rt_dvfs_devfreq_register()
|
v
rt_dvfs_scaling (OPP table, cur_freq, governor)
|
+-- governor (performance / ondemand / …)
+-- thermal-cool-dvfs (optional, RT_THERMAL_COOL_DVFS)
+-- rt_dvfs_scaling_set_frequency() / apply_opp()
```
| Layer | Location | Role |
| --- | --- | --- |
| Framework | `components/drivers/dvfs/` | OPP table, governors, load stats, PM hooks, MSH |
| Generic SCMI | `dvfs-scmi-cpufreq.c` | Optional SCMI-based CPUfreq (**`RT_DVFS_SCMI_CPUFREQ`**) |
| BSP | **`SOC_DM_DVFS_CPUFREQ_DIR`**, **`SOC_DM_DVFS_DEVFREQ_DIR`**, **`SOC_DM_DVFS_EVENT_DIR`** | Platform probe, **`set_opp`**, DT parsing |
| Thermal | `thermal-cool-dvfs.c` | Map cooling levels to OPP indices — @ref page_device_thermal_cool |
When **`RT_USING_DVFS`** is enabled, **`struct rt_device::dvfs_scaling`** links a device to its scaling domain for **`list_dvfs`** / **`dvfs dump`**.
---
## Registering a scaling domain (BSP driver)
Typical CPU path:
1. Allocate **`struct rt_dvfs_cpufreq`** (embeds **`struct rt_dvfs_scaling`**).
2. Fill **`scaling->ops`** — at minimum **`set_opp`** and **`parse_opp`** for DT-backed OPP tables.
3. Obtain **`scaling->clk`** and **`scaling->supply`** from the CPU / cluster device node.
4. Parse OPP nodes ( **`operating-points-v2`** ) into the scaling OPP table.
5. Call **`rt_dvfs_cpufreq_register()`**, then **`rt_dvfs_scaling_set_governor()`** with the desired default governor.
Devfreq devices follow the same pattern with **`struct rt_dvfs_devfreq`** and optional **`struct rt_dvfs_event`** for load feedback (**`RT_USING_DVFS_EVENT`**).
If the driver does not provide **`ops->set_opp`**, the framework falls back to a generic sequence using **`rt_clk_set_rate`** and **`rt_regulator_set_voltage`** on **`scaling->clk`** / **`scaling->supply`**.
---
## Governors
| Name | Behavior |
| --- | --- |
| **`performance`** | Always **`max_freq`** |
| **`powersave`** | Always **`min_freq`** |
| **`freedom`** | No automatic changes; user sets frequency manually |
| **`ondemand`** | Periodic load sampling; high load → max, low load → scaled down |
| **`conservative`** | Step up/down by **`freq_step`** |
| **`schedutil`** | Target frequency proportional to estimated load |
Dynamic governors depend on **`RT_USING_IDLE_HOOK`** (load estimation) and **`RT_USING_SYSTEM_WORKQUEUE`** (monitor timer). The default governor is chosen by the BSP driver at registration time.
Governor parameters (**`up_threshold`**, **`sampling_rate_ms`**, …) live in **`struct rt_dvfs_governor_params`** and can be adjusted via **`rt_dvfs_governor_set_params()`**.
---
## MSH commands
Requires **`RT_USING_CONSOLE`** and **`RT_USING_MSH`** (**`dvfs_cmd.c`**).
```text
list_dvfs
dvfs dump <name>
dvfs set_governor <name> <governor>
dvfs set_frequency <name> <Hz>
```
**`set_frequency`** switches to **`freedom`** automatically when the active governor is not freedom.
Example:
```text
msh />dvfs set_governor cpu0 performance
msh />dvfs dump cpu0
```
The scaling device **`<name>`** is assigned by the BSP driver (commonly **`cpufreq0`**, **`dmc`**, etc.).
---
## Device tree (typical CPU)
Minimal pattern (details vary by SoC BSP):
```dts
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a55";
operating-points-v2 = <&cpu0_opp_table>;
cpu-supply = <&cpu_reg>;
/* optional: #cooling-cells for thermal-cool-dvfs */
};
cpu0_opp_table: opp-table {
compatible = "operating-points-v2";
opp-1000000000 {
opp-hz = /bits/ 64 <1000000000>;
opp-microvolt = <900000>;
};
/* additional OPP nodes … */
};
```
| Binding | Role |
| --- | --- |
| **`operating-points-v2`** | OPP table phandle on the scaling device |
| **`opp-hz` / `opp-microvolt`** | Frequency (Hz) and voltage (µV) per OPP |
| **`*-supply`** | Regulator phandle(s) used when scaling voltage |
| **`#cooling-cells`** | Enables **`dvfs-cool`** thermal device on the CPU node |
Passive thermal **`cooling-maps`** can reference the DVFS cooling device to cap OPP on trip — see @ref page_device_thermal_cool.
---
## Kconfig
| Option | Role |
| --- | --- |
| **`RT_USING_DVFS`** | Core framework + governors |
| **`RT_USING_DVFS_EVENT`** | **`dvfs_event.c`**, devfreq load via event devices |
| **`RT_USING_DVFS_OPP_RETRY_MAX`** | Retries on **`-RT_EBUSY`** during OPP transition |
| **`RT_DVFS_SCMI_CPUFREQ`** | Generic **`dvfs-scmi-cpufreq.c`** |
| **`RT_THERMAL_COOL_DVFS`** | **`thermal-cool-dvfs.c`** (also needs **`RT_USING_THERMAL`**) |
| **`RT_USING_IDLE_HOOK`** | Required for meaningful dynamic governor load stats |
| **`SOC_DM_DVFS_CPUFREQ_DIR`** | BSP CPUfreq driver(s) |
| **`SOC_DM_DVFS_DEVFREQ_DIR`** | BSP devfreq driver(s) |
| **`SOC_DM_DVFS_EVENT_DIR`** | BSP devfreq event source(s) |
---
## OPP transition order
When **`ops->set_opp`** is provided, the BSP defines the safe ramp sequence ( voltage-before-frequency on scale-up, etc.).
When the framework generic path is used (**`dvfs.c`**):
- **Scale up**: raise voltage (if needed) → optional **`transition_latency`** delay → **`rt_clk_set_rate`**
- **Scale down**: lower clock rate → lower voltage (if needed)
On failure, **`cur_freq`** and **`current_opp`** are not updated. **`RT_USING_DVFS_OPP_RETRY_MAX`** controls busy retries on regulator or clock calls.
---
## Pitfalls
- **BSP `set_opp` must match hardware**: clocks, regulators, and any bus used to change voltage must remain usable across the full OPP range. Broken I2C/SPI/regulator access during a transition surfaces as stuck frequency or **`-RT_EBUSY`** retries.
- **Governor switch vs. pending work**: dynamic governors schedule work on the system workqueue. After **`set_governor`**, monitor callbacks must verify the active governor type before changing frequency (see governor sources).
- **Thermal cooling levels**: **`thermal-cool-dvfs`** maps cooling **level** to an OPP index; releasing cooling must restore full performance (highest OPP), not the most restrictive level. See **`rt_thermal_cooling_device_kick`** in **`thermal.c`**.
- **Verifying dynamic governors**: do not run a tight CPU loop inside the **FinSH / shell thread** — it usually has higher priority than the system workqueue and can prevent load sampling. Use a **separate background thread** at lower priority, or compare **`performance`** vs **`powersave`** (static governors) to confirm the scaling path works before testing **`ondemand`** / **`schedutil`**.
---
## Related pages
- @ref page_device_clk — **`rt_clk_set_rate`**, consumer clock API
- @ref page_device_regulator — supply rails referenced from OPP / CPU nodes
- @ref page_device_scmi — SCMI clock/regulator when firmware owns scaling resources
- @ref page_device_thermal_cool — **`thermal-cool-dvfs`**
@@ -213,7 +213,7 @@ Call **`update`** from the zone poller (automatic after register) or manually fo
| **`thermal-scmi.c`** | **`RT_SCMI_DRIVER_EXPORT`**, protocol sensor `"thermal"` | One zone per SCMI temperature sensor |
| **`thermal-cool-pwm-fan.c`** | **`compatible = "pwm-fan"`** | PWM + optional regulator — @ref page_device_thermal_cool |
| **`thermal-cool-gpio-fan.c`** | GPIO + speed table | Discrete fan speeds |
| **`thermal-cool-dvfs.c`** | DVFS OPP levels as cooling steps | Throttle CPU/GPU via @ref page_device_clk / DVFS |
| **`thermal-cool-dvfs.c`** | DVFS OPP levels as cooling steps | Throttle CPU/GPU via @ref page_device_dvfs |
BSP sensors (e.g. board thermal IC) typically **`rt_thermal_zone_device_register`** in platform **`probe`** after filling trips in code or relying entirely on **`thermal_ofw_setup`**.
@@ -251,6 +251,7 @@ Constants: **`RT_THERMAL_TEMP_INVALID`** (−274000), **`RT_THERMAL_NO_LIMIT`**.
## See also
- @ref page_device_thermal_cool — PWM fan cooling device
- @ref page_device_dvfs — DVFS framework and Rockchip cpufreq
- @ref page_device_scmi — SCMI sensor protocol
- @ref page_device_pwm
- @ref page_device_regulator
+3
View File
@@ -1377,6 +1377,9 @@ struct rt_device
void *ofw_node; /**< ofw node get from device tree */
#endif /* RT_USING_OFW */
void *power_domain_unit;
#ifdef RT_USING_DVFS
void *dvfs_scaling;
#endif
#ifdef RT_USING_DMA
const void *dma_ops;
#endif