From e280f213bfba8de1b6dd0da45c4cd04e647bb56e Mon Sep 17 00:00:00 2001 From: GuEe-GUI <2991707448@qq.com> Date: Mon, 15 Jun 2026 11:21:24 +0800 Subject: [PATCH] [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> --- components/drivers/Kconfig | 1 + components/drivers/dvfs/Kconfig | 51 + components/drivers/dvfs/SConscript | 38 + components/drivers/dvfs/dvfs-ofw.c | 186 ++++ components/drivers/dvfs/dvfs-scmi-cpufreq.c | 232 +++++ components/drivers/dvfs/dvfs.c | 958 ++++++++++++++++++ components/drivers/dvfs/dvfs_cmd.c | 270 +++++ components/drivers/dvfs/dvfs_cpu.c | 48 + components/drivers/dvfs/dvfs_event.c | 246 +++++ components/drivers/dvfs/dvfs_governor.c | 141 +++ components/drivers/dvfs/dvfs_idle.c | 381 +++++++ components/drivers/dvfs/dvfs_opp.c | 336 ++++++ components/drivers/dvfs/dvfs_pm.c | 107 ++ components/drivers/dvfs/governor/SConscript | 10 + .../drivers/dvfs/governor/conservative.c | 329 ++++++ components/drivers/dvfs/governor/freedom.c | 106 ++ components/drivers/dvfs/governor/ondemand.c | 317 ++++++ .../drivers/dvfs/governor/performance.c | 115 +++ components/drivers/dvfs/governor/powersave.c | 115 +++ components/drivers/dvfs/governor/schedutil.c | 286 ++++++ components/drivers/include/drivers/dvfs.h | 353 +++++++ components/drivers/include/drivers/thermal.h | 4 + components/drivers/include/rtdevice.h | 4 + components/drivers/pm/pm.c | 22 +- components/drivers/thermal/Kconfig | 6 + components/drivers/thermal/SConscript | 3 + .../drivers/thermal/thermal-cool-dvfs.c | 171 ++++ components/drivers/thermal/thermal.c | 82 +- components/drivers/thermal/thermal_dm.h | 2 + .../6.components/device-driver/INDEX.md | 1 + .../6.components/device-driver/dvfs/dvfs.md | 178 ++++ .../device-driver/thermal/thermal.md | 3 +- include/rtdef.h | 3 + 33 files changed, 5094 insertions(+), 11 deletions(-) create mode 100644 components/drivers/dvfs/Kconfig create mode 100644 components/drivers/dvfs/SConscript create mode 100644 components/drivers/dvfs/dvfs-ofw.c create mode 100644 components/drivers/dvfs/dvfs-scmi-cpufreq.c create mode 100644 components/drivers/dvfs/dvfs.c create mode 100644 components/drivers/dvfs/dvfs_cmd.c create mode 100644 components/drivers/dvfs/dvfs_cpu.c create mode 100644 components/drivers/dvfs/dvfs_event.c create mode 100644 components/drivers/dvfs/dvfs_governor.c create mode 100644 components/drivers/dvfs/dvfs_idle.c create mode 100644 components/drivers/dvfs/dvfs_opp.c create mode 100644 components/drivers/dvfs/dvfs_pm.c create mode 100644 components/drivers/dvfs/governor/SConscript create mode 100644 components/drivers/dvfs/governor/conservative.c create mode 100644 components/drivers/dvfs/governor/freedom.c create mode 100644 components/drivers/dvfs/governor/ondemand.c create mode 100644 components/drivers/dvfs/governor/performance.c create mode 100644 components/drivers/dvfs/governor/powersave.c create mode 100644 components/drivers/dvfs/governor/schedutil.c create mode 100644 components/drivers/include/drivers/dvfs.h create mode 100644 components/drivers/thermal/thermal-cool-dvfs.c create mode 100644 documentation/6.components/device-driver/dvfs/dvfs.md diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig index f613302d96..efe7431a55 100755 --- a/components/drivers/Kconfig +++ b/components/drivers/Kconfig @@ -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" diff --git a/components/drivers/dvfs/Kconfig b/components/drivers/dvfs/Kconfig new file mode 100644 index 0000000000..d35edccf4b --- /dev/null +++ b/components/drivers/dvfs/Kconfig @@ -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 diff --git a/components/drivers/dvfs/SConscript b/components/drivers/dvfs/SConscript new file mode 100644 index 0000000000..befe1d579f --- /dev/null +++ b/components/drivers/dvfs/SConscript @@ -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') diff --git a/components/drivers/dvfs/dvfs-ofw.c b/components/drivers/dvfs/dvfs-ofw.c new file mode 100644 index 0000000000..5676106ce8 --- /dev/null +++ b/components/drivers/dvfs/dvfs-ofw.c @@ -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 +#include +#include + +#define DBG_TAG "dvfs.ofw" +#define DBG_LVL DBG_INFO +#include + +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); diff --git a/components/drivers/dvfs/dvfs-scmi-cpufreq.c b/components/drivers/dvfs/dvfs-scmi-cpufreq.c new file mode 100644 index 0000000000..94521513a8 --- /dev/null +++ b/components/drivers/dvfs/dvfs-scmi-cpufreq.c @@ -0,0 +1,232 @@ +/* + * 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 +#include +#include +#include + +#define DBG_TAG "dvfs.scmi.cpufreq" +#define DBG_LVL DBG_INFO +#include + +struct scmi_cpufreq +{ + struct rt_scmi_device *sdev; + int domain_id; + struct rt_dvfs_cpufreq cpufreq; +}; + +static rt_err_t scmi_perf_level_set(struct scmi_cpufreq *scmi_cpufreq, rt_uint64_t level) +{ + struct scmi_perf_level_set_in in = + { + .flags = rt_cpu_to_le32(0), + .domain = rt_cpu_to_le32(scmi_cpufreq->domain_id), + .level_lsb = rt_cpu_to_le32((rt_uint32_t)level), + .level_msb = rt_cpu_to_le32((rt_uint32_t)(level >> 32)), + }; + struct scmi_perf_level_set_out out; + struct rt_scmi_msg msg = RT_SCMI_MSG_IN_OUT(SCMI_PERF_LEVEL_SET, &in, &out); + + return rt_scmi_process_msg(scmi_cpufreq->sdev, &msg); +} + +static rt_err_t scmi_cpufreq_set_opp(struct rt_dvfs_scaling *scaling, struct rt_dvfs_opp *opp) +{ + struct rt_dvfs_devfreq *devfreq = rt_container_of(scaling, struct rt_dvfs_devfreq, parent); + struct scmi_cpufreq *scmi_cpufreq = rt_container_of(devfreq, struct scmi_cpufreq, cpufreq.parent); + + if (!opp) + { + return -RT_EINVAL; + } + + return scmi_perf_level_set(scmi_cpufreq, opp->freq); +} + +static rt_err_t scmi_cpufreq_add_opps(struct scmi_cpufreq *scmi_cpufreq) +{ + struct rt_dvfs_scaling *dvfs = rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq); + struct scmi_perf_describe_levels_in in = + { + .domain = rt_cpu_to_le32(scmi_cpufreq->domain_id), + .level_index = rt_cpu_to_le32(0), + }; + struct scmi_perf_describe_levels_out *out; + rt_size_t out_size = sizeof(*out) + sizeof(struct scmi_perf_level) * SCMI_MAX_NUM_RATES; + rt_err_t err; + rt_uint32_t i, num_levels; + + out = rt_calloc(1, out_size); + if (!out) + { + return -RT_ENOMEM; + } + + do + { + struct rt_scmi_msg msg = RT_SCMI_MSG_RAW(SCMI_PERF_DESCRIBE_LEVELS, + &in, sizeof(in), out, out_size); + + err = rt_scmi_process_msg(scmi_cpufreq->sdev, &msg); + if (err) + { + break; + } + + num_levels = rt_le32_to_cpu(out->num_levels) & 0xfff; + + for (i = 0; i < num_levels; ++i) + { + rt_uint64_t hz = SCMI_PERF_LEVEL_TO_U64(out->level[i]); + struct rt_dvfs_opp *opp; + + if (!hz) + { + continue; + } + + opp = rt_dvfs_scaling_add_opp(dvfs, (rt_ubase_t)hz, 0); + if (opp) + { + opp->available = RT_TRUE; + opp->power = rt_le32_to_cpu(out->level[i].power) / 1000; + } + } + + in.level_index = rt_cpu_to_le32(rt_le32_to_cpu(in.level_index) + num_levels); + } while ((rt_le32_to_cpu(out->num_levels) >> 16) > 0); + + rt_free(out); + + return err; +} + +static struct rt_dvfs_scaling_ops scmi_cpufreq_ops = +{ + .set_opp = scmi_cpufreq_set_opp, +}; + +static rt_err_t scmi_cpufreq_probe(struct rt_scmi_device *sdev) +{ + struct scmi_cpufreq *scmi_cpufreq; + struct scmi_perf_attributes attr; + struct rt_scmi_msg msg = RT_SCMI_MSG_OUT(SCMI_COM_MSG_ATTRIBUTES, &attr); + rt_err_t err; + int domain; + + if ((err = rt_scmi_process_msg(sdev, &msg))) + { + LOG_E("Get SCMI perf attributes failed: %s", rt_strerror(err)); + return err; + } + + if (!(scmi_cpufreq = rt_calloc(1, sizeof(*scmi_cpufreq)))) + { + return -RT_ENOMEM; + } + + scmi_cpufreq->sdev = sdev; + + for (domain = 0; domain < rt_le16_to_cpu(attr.num_domains); ++domain) + { + struct scmi_perf_domain_attr_in domain_in = + { + .domain = rt_cpu_to_le32(domain), + }; + struct scmi_perf_domain_attr_out domain_out; + struct rt_scmi_msg domain_msg = RT_SCMI_MSG_IN_OUT(SCMI_PERF_DOMAIN_ATTRIBUTES, + &domain_in, &domain_out); + + rt_memset(&scmi_cpufreq->cpufreq, 0, sizeof(scmi_cpufreq->cpufreq)); + + if ((err = rt_scmi_process_msg(sdev, &domain_msg))) + { + continue; + } + + scmi_cpufreq->domain_id = domain; + rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)->ops = &scmi_cpufreq_ops; + rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)->dev = &sdev->parent; + scmi_cpufreq->cpufreq.master_cpu = 0; + + if ((err = scmi_cpufreq_add_opps(scmi_cpufreq))) + { + LOG_W("Domain %d: add OPPs failed: %s", domain, rt_strerror(err)); + rt_dvfs_scaling_remove_opp_all(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)); + continue; + } + + if ((err = rt_dvfs_cpufreq_register(&scmi_cpufreq->cpufreq))) + { + LOG_E("Domain %d: register cpufreq failed: %s", domain, rt_strerror(err)); + rt_dvfs_scaling_remove_opp_all(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)); + continue; + } + + err = rt_dvfs_scaling_set_governor(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq), + RT_DVFS_GOVERNOR_TYPE_ONDEMAND); + if (err) + { + rt_dvfs_scaling_set_governor(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq), + RT_DVFS_GOVERNOR_TYPE_PERFORMANCE); + } + + LOG_D("SCMI cpufreq domain %d registered, freq range: %lu-%lu Hz", + domain, + rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)->min_freq, + rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)->max_freq); + + sdev->parent.user_data = scmi_cpufreq; + return RT_EOK; + } + + rt_free(scmi_cpufreq); + + return -RT_ENOSYS; +} + +static rt_err_t scmi_cpufreq_remove(struct rt_scmi_device *sdev) +{ + struct scmi_cpufreq *scmi_cpufreq = sdev->parent.user_data; + + if (scmi_cpufreq) + { + rt_dvfs_cpufreq_unregister(&scmi_cpufreq->cpufreq); + rt_dvfs_scaling_remove_opp_all(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)); + rt_free(scmi_cpufreq); + sdev->parent.user_data = RT_NULL; + } + + return RT_EOK; +} + +static const struct rt_scmi_device_id scmi_cpufreq_ids[] = +{ + { SCMI_PROTOCOL_ID_PERF, "cpufreq" }, + { 0, RT_NULL }, +}; + +static struct rt_scmi_driver scmi_cpufreq_driver = +{ + .name = "scmi-cpufreq", + .ids = scmi_cpufreq_ids, + .probe = scmi_cpufreq_probe, + .remove = scmi_cpufreq_remove, +}; + +static int scmi_cpufreq_init(void) +{ + rt_scmi_driver_register(&scmi_cpufreq_driver); + + return 0; +} +INIT_DEVICE_EXPORT(scmi_cpufreq_init); diff --git a/components/drivers/dvfs/dvfs.c b/components/drivers/dvfs/dvfs.c new file mode 100644 index 0000000000..c3544f545d --- /dev/null +++ b/components/drivers/dvfs/dvfs.c @@ -0,0 +1,958 @@ +/* + * 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 +#include +#include + +#define DBG_TAG "rtdm.dvfs" +#define DBG_LVL DBG_INFO +#include + +static RT_DEFINE_SPINLOCK(_dvfs_scaling_lock); + +#ifdef RT_USING_OFW +static rt_err_t dvfs_ofw_parse_opp(struct rt_dvfs_scaling *dvfs) +{ + struct rt_dvfs_opp *opp; + struct rt_ofw_node *opp_np, *opp_child_np; + + if (!dvfs->dev->ofw_node) + { + return RT_EOK; + } + + opp_np = rt_ofw_parse_phandle(dvfs->dev->ofw_node, "operating-points-v2", 0); + + if (!opp_np) + { + return RT_EOK; + } + + rt_ofw_foreach_child_node(opp_np, opp_child_np) + { + rt_uint64_t hz = 0; + rt_uint32_t uvolt[3] = {0}, uvolt_nr = 0; + + if (rt_ofw_prop_read_u64(opp_child_np, "opp-hz", &hz)) + { + continue; + } + + uvolt_nr = rt_ofw_prop_read_u32_array_index(opp_child_np, + "opp-microvolt", 0, RT_ARRAY_SIZE(uvolt), uvolt); + + if ((int)uvolt_nr < 0) + { + /* If previous voltage is unknown, assume 0 to ensure a voltage ramp-up */ + uvolt[0] = 0; + } + + if (!(opp = rt_dvfs_scaling_add_opp(dvfs, (rt_ubase_t)hz, (rt_ubase_t)uvolt[0]))) + { + continue; + } + + if (dvfs->ops && dvfs->ops->parse_opp) + { + rt_err_t err = dvfs->ops->parse_opp(dvfs, opp, (void *)opp_child_np); + + if (err) + { + LOG_W("%s: Parse OPP %s error = %s", rt_dm_dev_get_name(dvfs->dev), + rt_ofw_node_full_name(opp_child_np), rt_strerror(err)); + } + } + } + + dvfs->opp_table->share = rt_ofw_prop_read_bool(opp_np, "opp-shared"); + dvfs->opp_table->priv = dvfs->opp_table->priv ? : opp_np; /* Default value, DVFS unused */ + + return RT_EOK; +} +#endif /* RT_USING_OFW */ + +static void dvfs_gov_params_init_default(struct rt_dvfs_governor_params *params) +{ + params->sampling_rate_ms = 1000; + params->up_threshold = 80; + params->down_differential = 20; + params->sampling_down_factor = 1; + params->freq_step = 5; + params->ignore_nice_load = RT_FALSE; + params->powersave_bias = 0; +} + +static rt_err_t dvfs_scaling_init_frequency(struct rt_dvfs_scaling *dvfs) +{ + struct rt_dvfs_opp *opp; + + if (!dvfs->opp_table || rt_list_isempty(&dvfs->opp_table->opp_nodes)) + { + return RT_EOK; + } + + if (dvfs->suspend_freq && + (opp = rt_dvfs_scaling_find_opp(dvfs, dvfs->suspend_freq))) + { + return rt_dvfs_scaling_apply_opp(dvfs, opp); + } + + opp = rt_dvfs_scaling_find_ceil_opp(dvfs, dvfs->max_freq); + if (!opp) + { + opp = rt_list_entry(dvfs->opp_table->opp_nodes.next, struct rt_dvfs_opp, list); + } + + return rt_dvfs_scaling_apply_opp(dvfs, opp); +} + +rt_err_t rt_dvfs_scaling_register(struct rt_dvfs_scaling *dvfs) +{ + rt_err_t err = RT_EOK; + + if (!dvfs || !dvfs->dev || !dvfs->ops) + { + return -RT_EINVAL; + } + + RT_ASSERT(dvfs->ops->set_opp != RT_NULL); + + if (!dvfs->gov_params.sampling_rate_ms) + { + dvfs_gov_params_init_default(&dvfs->gov_params); + } + +#ifdef RT_USING_OFW + if ((err = dvfs_ofw_parse_opp(dvfs))) + { + return err; + } +#endif /* RT_USING_OFW */ + + if (dvfs->opp_table && !dvfs->cur_freq) + { + err = dvfs_scaling_init_frequency(dvfs); + if (err) + { + LOG_W("%s: init frequency error = %s", + rt_dm_dev_get_name(dvfs->dev), rt_strerror(err)); + } + } + + rt_dm_dev_bind_fwdata(dvfs->dev, RT_NULL, dvfs); + dvfs->dev->dvfs_scaling = dvfs; + + return RT_EOK; +} + +rt_err_t rt_dvfs_scaling_unregister(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + if (dvfs->gov) + { + if (dvfs->gov->stop) + { + dvfs->gov->stop(dvfs); + } + + rt_dvfs_governor_put(dvfs->gov); + dvfs->gov = RT_NULL; + } + + dvfs->gov_data = RT_NULL; + dvfs->load_update = RT_NULL; + + dvfs->dev->dvfs_scaling = RT_NULL; + rt_dm_dev_unbind_fwdata(dvfs->dev, RT_NULL); + + /* Free the OPP by Drivers */ + + return RT_EOK; +} + +void rt_dvfs_scaling_enter(struct rt_dvfs_scaling *dvfs) +{ + if (dvfs) + { + rt_spin_lock(&_dvfs_scaling_lock); + } +} + +void rt_dvfs_scaling_leave(struct rt_dvfs_scaling *dvfs) +{ + if (dvfs) + { + rt_spin_unlock(&_dvfs_scaling_lock); + } +} + +void rt_dvfs_ns_sleep(rt_uint32_t ns) +{ + rt_uint32_t us; + + if (!ns) + { + return; + } + + us = (ns + 999) / 1000; + + if (us < 1000 || rt_hw_interrupt_is_disabled()) + { + rt_hw_us_delay(us); + } + else + { + rt_thread_mdelay(us / 1000); + } +} + +rt_err_t rt_dvfs_scaling_suspend(struct rt_dvfs_scaling *dvfs) +{ + rt_err_t err = RT_EOK; + + if (!dvfs) + { + return -RT_EINVAL; + } + + if (dvfs->gov && dvfs->gov->suspend) + { + if ((err = dvfs->gov->suspend(dvfs))) + { + LOG_W("%s: governor suspend error = %s", + rt_dm_dev_get_name(dvfs->dev), rt_strerror(err)); + } + } + + if (dvfs->suspend_freq) + { + if ((err = rt_dvfs_scaling_set_frequency(dvfs, dvfs->suspend_freq))) + { + LOG_W("%s: set suspend frequency(%lu) error = %s", + rt_dm_dev_get_name(dvfs->dev), dvfs->suspend_freq, rt_strerror(err)); + } + } + + if (dvfs->ops && dvfs->ops->suspend) + { + rt_dvfs_scaling_enter(dvfs); + err = dvfs->ops->suspend(dvfs); + rt_dvfs_scaling_leave(dvfs); + } + + return err; +} + +rt_err_t rt_dvfs_scaling_resume(struct rt_dvfs_scaling *dvfs) +{ + rt_err_t err = RT_EOK; + + if (!dvfs) + { + return -RT_EINVAL; + } + + if (dvfs->ops && dvfs->ops->resume) + { + rt_dvfs_scaling_enter(dvfs); + err = dvfs->ops->resume(dvfs); + rt_dvfs_scaling_leave(dvfs); + } + + if (dvfs->gov && dvfs->gov->resume) + { + rt_err_t gov_err = dvfs->gov->resume(dvfs); + + if (gov_err && !err) + { + err = gov_err; + } + } + + return err; +} + +rt_err_t rt_dvfs_scaling_set_governor(struct rt_dvfs_scaling *dvfs, rt_uint32_t governor) +{ + rt_err_t err = RT_EOK; + struct rt_dvfs_governor *gov; + + if (!dvfs) + { + return -RT_EINVAL; + } + + if (!(gov = rt_dvfs_governor_get(governor))) + { + return -RT_ENOSYS; + } + + if (dvfs->gov) + { + if (dvfs->gov->stop) + { + if ((err = dvfs->gov->stop(dvfs))) + { + rt_dvfs_governor_put(gov); + return err; + } + } + + rt_dvfs_governor_put(dvfs->gov); + } + + dvfs->gov = gov; + + if (dvfs->gov->start) + { + if ((err = dvfs->gov->start(dvfs))) + { + rt_dvfs_governor_put(dvfs->gov); + dvfs->gov = RT_NULL; + } + } + + return err; +} + +rt_err_t rt_dvfs_scaling_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t frequency) +{ + rt_err_t err; + struct rt_dvfs_opp *opp = RT_NULL; + + if (!dvfs || !dvfs->opp_table) + { + return -RT_EINVAL; + } + + if (dvfs->min_freq && frequency < dvfs->min_freq) + { + frequency = dvfs->min_freq; + } + if (dvfs->max_freq && frequency > dvfs->max_freq) + { + frequency = dvfs->max_freq; + } + + if (!(opp = rt_dvfs_scaling_find_opp(dvfs, frequency))) + { + if (!(opp = rt_dvfs_scaling_find_floor_opp(dvfs, frequency))) + { + opp = rt_dvfs_scaling_find_ceil_opp(dvfs, frequency); + } + } + + if (!opp || !opp->available) + { + return -RT_ENOENT; + } + + err = rt_dvfs_scaling_apply_opp(dvfs, opp); + + return err; +} + +static rt_err_t dvfs_regulator_set_voltage_retry(struct rt_regulator *supply, + rt_ubase_t uvolt, rt_uint32_t retry_ns) +{ + for (int i = 0; i < RT_USING_DVFS_OPP_RETRY_MAX; ++i) + { + rt_err_t err = rt_regulator_set_voltage(supply, uvolt, uvolt); + + if (err == -RT_EBUSY) + { + rt_dvfs_ns_sleep(retry_ns); + continue; + } + + return err; + } + + return -RT_EBUSY; +} + +static rt_err_t dvfs_clk_set_rate_retry(struct rt_clk *clk, + rt_ubase_t rate, rt_uint32_t retry_ns) +{ + for (int i = 0; i < RT_USING_DVFS_OPP_RETRY_MAX; ++i) + { + rt_err_t err = rt_clk_set_rate(clk, rate); + + if (err == -RT_EBUSY) + { + rt_dvfs_ns_sleep(retry_ns); + continue; + } + + return err; + } + + return -RT_EBUSY; +} + +rt_err_t rt_dvfs_scaling_apply_opp(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp) +{ + rt_err_t err; + + if (!dvfs || !opp || !dvfs->ops || !dvfs->ops->set_opp || !dvfs->opp_table) + { + return -RT_EINVAL; + } + + if (!opp->available) + { + return -RT_EINVAL; + } + + if ((dvfs->min_freq && opp->freq < dvfs->min_freq) || + (dvfs->max_freq && opp->freq > dvfs->max_freq)) + { + return -RT_EINVAL; + } + + if (dvfs->ops->set_opp) + { + err = -RT_EBUSY; + + for (int tries = 0; tries < RT_USING_DVFS_OPP_RETRY_MAX; ++tries) + { + err = dvfs->ops->set_opp(dvfs, opp); + + if (err != -RT_EBUSY) + { + break; + } + + rt_dvfs_ns_sleep(dvfs->retry_delay); + } + + if (err) + { + return err; + } + + rt_dvfs_ns_sleep(dvfs->transition_latency); + } + else + { + rt_uint32_t retry_delay = dvfs->retry_delay; + rt_ubase_t old_uvolt, old_freq, new_uvolt, new_freq; + struct rt_dvfs_opp *old = dvfs->opp_table->current_opp; + + /* If previous voltage is unknown, assume 0 to ensure a voltage ramp-up */ + old_uvolt = old ? old->uvolt : 0; + old_freq = dvfs->cur_freq; + + new_uvolt = opp->uvolt; + new_freq = opp->freq; + + if (new_freq > old_freq) + { + /* Scale up: raise voltage first, then increase frequency */ + if (dvfs->supply && new_uvolt > old_uvolt) + { + if ((err = dvfs_regulator_set_voltage_retry(dvfs->supply, new_uvolt, retry_delay))) + { + return err; + } + } + + if (dvfs->clk) + { + if ((err = dvfs_clk_set_rate_retry(dvfs->clk, new_freq, retry_delay))) + { + return err; + } + } + } + else if (new_freq < old_freq) + { + /* Scale down: lower frequency first, then lower voltage */ + if (dvfs->clk) + { + if ((err = dvfs_clk_set_rate_retry(dvfs->clk, new_freq, retry_delay))) + { + return err; + } + } + + if (dvfs->supply && new_uvolt < old_uvolt) + { + if ((err = dvfs_regulator_set_voltage_retry(dvfs->supply, new_uvolt, retry_delay))) + { + return err; + } + } + } + else + { + /* Frequency unchanged: adjust voltage only if needed */ + if (dvfs->supply && new_uvolt != old_uvolt) + { + if ((err = dvfs_regulator_set_voltage_retry(dvfs->supply, new_uvolt, retry_delay))) + { + return err; + } + } + } + + rt_dvfs_ns_sleep(dvfs->transition_latency); + } + + rt_dvfs_scaling_enter(dvfs); + dvfs->cur_freq = opp->freq; + dvfs->opp_table->current_opp = opp; + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +/* CPU Load Monitoring */ +#ifdef RT_USING_IDLE_HOOK + +static rt_uint64_t _idle_tick_total = 0; +static rt_tick_t _idle_start_tick = 0; +static rt_bool_t _in_idle = RT_FALSE; + +static void dvfs_idle_hook(void) +{ + rt_base_t level; + + level = rt_hw_interrupt_disable(); + + if (!_in_idle) + { + _in_idle = RT_TRUE; + _idle_start_tick = rt_tick_get(); + } + + rt_hw_interrupt_enable(level); +} + +static int dvfs_load_init(void) +{ + /* Install idle hook */ + rt_thread_idle_sethook(dvfs_idle_hook); + + return 0; +} +INIT_DEVICE_EXPORT(dvfs_load_init); + +void rt_dvfs_load_update(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return; + } + + if (dvfs->load_update) + { + dvfs->load_update(dvfs); + } + else + { + rt_dvfs_cpu_load_update(&dvfs->cpu_load); + } +} + +void rt_dvfs_cpu_load_update(struct rt_dvfs_cpu_load *load) +{ + rt_tick_t now; + rt_base_t level; + + if (!load) + { + return; + } + + level = rt_hw_interrupt_disable(); + + now = rt_tick_get(); + + /* Exit idle state if in idle */ + if (_in_idle) + { + rt_uint64_t idle_ticks = now - _idle_start_tick; + _idle_tick_total += idle_ticks; + _in_idle = RT_FALSE; + } + + if (load->last_update == 0) + { + /* First update */ + load->last_update = now; + load->total_tick = 0; + load->idle_tick = 0; + load->load_percentage = 0; + } + else + { + rt_uint64_t total_elapsed; + rt_uint64_t idle_elapsed; + + /* Calculate total elapsed ticks */ + total_elapsed = now - load->last_update; + + /* Get idle ticks accumulated since last update */ + idle_elapsed = _idle_tick_total - load->idle_tick; + + /* Update counters */ + load->total_tick = total_elapsed; + load->idle_tick = _idle_tick_total; + load->last_update = now; + + /* Calculate load percentage */ + if (total_elapsed > 0) + { + rt_uint64_t busy_ticks = (idle_elapsed > total_elapsed) ? 0 : (total_elapsed - idle_elapsed); + load->load_percentage = (busy_ticks * 100) / total_elapsed; + + /* Clamp to 0-100 range */ + if (load->load_percentage > 100) + { + load->load_percentage = 100; + } + } + else + { + load->load_percentage = 0; + } + } + + rt_hw_interrupt_enable(level); +} + +#else /* RT_USING_IDLE_HOOK */ + +void rt_dvfs_cpu_load_update(struct rt_dvfs_cpu_load *load) +{ + rt_tick_t now; + + if (!load) + { + return; + } + + now = rt_tick_get(); + + if (load->last_update == 0) + { + load->last_update = now; + load->total_tick = 0; + load->idle_tick = 0; + load->load_percentage = 50; /* Default to medium load */ + } + else + { + /* Without idle hook, use default load estimation */ + load->total_tick = now - load->last_update; + load->last_update = now; + + /* Estimate load based on scheduler activity */ + /* This is a simple heuristic - actual load depends on scheduler */ + extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; + rt_uint32_t ready_count = 0; + rt_base_t level; + + level = rt_hw_interrupt_disable(); + for (int i = 0; i < RT_THREAD_PRIORITY_MAX; i++) + { + if (!rt_list_isempty(&rt_thread_priority_table[i])) + { + ready_count++; + } + } + rt_hw_interrupt_enable(level); + + /* Estimate: 1 ready thread = 50%, more threads = higher load */ + load->load_percentage = (ready_count > 5) ? 90 : (ready_count * 15 + 30); + if (load->load_percentage > 100) + { + load->load_percentage = 100; + } + } +} + +#endif /* RT_USING_IDLE_HOOK */ + +rt_uint32_t rt_dvfs_cpu_load_get(struct rt_dvfs_cpu_load *load) +{ + if (!load) + { + return 0; + } + + return load->load_percentage; +} + +/* Governor parameter management */ +rt_err_t rt_dvfs_governor_set_params(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_governor_params *params) +{ + if (!dvfs || !params) + { + return -RT_EINVAL; + } + + /* Validate parameters */ + if (params->up_threshold > 100 || params->down_differential > 100) + { + return -RT_EINVAL; + } + + if (params->sampling_rate_ms > 0 && params->sampling_rate_ms < 10) + { + LOG_W("Sampling rate too small, adjusting to 10ms"); + params->sampling_rate_ms = 10; + } + + rt_dvfs_scaling_enter(dvfs); + rt_memcpy(&dvfs->gov_params, params, sizeof(*params)); + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +rt_err_t rt_dvfs_governor_get_params(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_governor_params *params) +{ + if (!dvfs || !params) + { + return -RT_EINVAL; + } + + rt_dvfs_scaling_enter(dvfs); + rt_memcpy(params, &dvfs->gov_params, sizeof(*params)); + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +static rt_err_t dvfs_default_set_opp(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp) +{ + rt_err_t err; + rt_ubase_t old_freq, old_uvolt, new_freq, new_uvolt; + struct rt_dvfs_opp *old_opp; + + if (!dvfs || !opp) + { + return -RT_EINVAL; + } + + old_opp = dvfs->opp_table ? dvfs->opp_table->current_opp : RT_NULL; + old_freq = dvfs->cur_freq; + old_uvolt = old_opp ? old_opp->uvolt : 0; + new_freq = opp->freq; + new_uvolt = opp->uvolt; + + if (new_freq > old_freq) + { + if (dvfs->supply && new_uvolt > old_uvolt) + { + err = rt_regulator_set_voltage(dvfs->supply, new_uvolt, new_uvolt); + if (err) + { + return err; + } + + if (dvfs->transition_latency) + { + rt_dvfs_ns_sleep(dvfs->transition_latency); + } + } + + if (dvfs->clk) + { + err = rt_clk_set_rate(dvfs->clk, new_freq); + if (err) + { + if (dvfs->supply && new_uvolt > old_uvolt) + { + rt_regulator_set_voltage(dvfs->supply, old_uvolt, old_uvolt); + } + return err; + } + } + } + else if (new_freq < old_freq) + { + if (dvfs->clk) + { + err = rt_clk_set_rate(dvfs->clk, new_freq); + if (err) + { + return err; + } + } + + if (dvfs->supply && new_uvolt < old_uvolt) + { + err = rt_regulator_set_voltage(dvfs->supply, new_uvolt, new_uvolt); + if (err) + { + LOG_W("%s: set voltage %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), new_uvolt, rt_strerror(err)); + } + } + } + else if (dvfs->supply && new_uvolt != old_uvolt) + { + err = rt_regulator_set_voltage(dvfs->supply, new_uvolt, new_uvolt); + if (err) + { + return err; + } + } + + if (dvfs->transition_latency) + { + rt_dvfs_ns_sleep(dvfs->transition_latency); + } + + return RT_EOK; +} + +static rt_err_t dvfs_default_parse_opp(struct rt_dvfs_scaling *dvfs, + struct rt_dvfs_opp *opp, void *fw_np) +{ +#ifdef RT_USING_OFW + struct rt_ofw_node *opp_np = (struct rt_ofw_node *)fw_np; + rt_uint32_t power = 0; + + if (!opp || !opp_np) + { + return -RT_EINVAL; + } + + if (!rt_ofw_prop_read_u32(opp_np, "opp-microwatt", &power)) + { + opp->power = power / 1000; + } + + opp->available = RT_TRUE; + + if (rt_ofw_prop_read_bool(opp_np, "opp-suspend")) + { + dvfs->suspend_freq = opp->freq; + } +#else + RT_UNUSED(dvfs); + RT_UNUSED(opp); + RT_UNUSED(fw_np); +#endif + + return RT_EOK; +} + +struct rt_dvfs_scaling_ops rt_dvfs_devfreq_ops = +{ + .set_opp = dvfs_default_set_opp, + .parse_opp = dvfs_default_parse_opp, +}; + +static void devfreq_load_from_event(struct rt_dvfs_scaling *scaling) +{ + struct rt_dvfs_devfreq *devfreq = rt_container_of(scaling, struct rt_dvfs_devfreq, parent); + struct rt_dvfs_event_data evd; + rt_err_t err; + + if (!devfreq->ev) + { + return; + } + + if ((err = rt_dvfs_event_read(devfreq->ev, &evd))) + { + LOG_D("%s: read dvfs event error = %s", + rt_dm_dev_get_name(scaling->dev), rt_strerror(err)); + return; + } + + if (evd.total_count) + { + scaling->cpu_load.load_percentage = (rt_uint32_t)((evd.load_count * 100) / evd.total_count); + + if (scaling->cpu_load.load_percentage > 100) + { + scaling->cpu_load.load_percentage = 100; + } + } + else + { + scaling->cpu_load.load_percentage = 0; + } +} + +rt_err_t rt_dvfs_devfreq_register(struct rt_dvfs_devfreq *devfreq) +{ + rt_err_t err; + struct rt_dvfs_scaling *scaling; + + if (!devfreq) + { + return -RT_EINVAL; + } + + scaling = rt_dvfs_devfreq_to_scaling(devfreq); + + if (!scaling->load_update && devfreq->ev) + { + scaling->load_update = devfreq_load_from_event; + } + + if (devfreq->ev) + { + err = rt_dvfs_event_enable(devfreq->ev); + if (err) + { + LOG_W("%s: enable devfreq event error = %s", + rt_dm_dev_get_name(scaling->dev), rt_strerror(err)); + } + } + + err = rt_dvfs_scaling_register(scaling); + if (err) + { + if (devfreq->ev) + { + rt_dvfs_event_disable(devfreq->ev); + } + return err; + } + + LOG_D("Devfreq registered for device %s", rt_dm_dev_get_name(scaling->dev)); + + return RT_EOK; +} + +rt_err_t rt_dvfs_devfreq_unregister(struct rt_dvfs_devfreq *devfreq) +{ + rt_err_t err; + + if (!devfreq) + { + return -RT_EINVAL; + } + + err = rt_dvfs_scaling_unregister(rt_dvfs_devfreq_to_scaling(devfreq)); + + if (devfreq->ev) + { + rt_dvfs_event_disable(devfreq->ev); + rt_dvfs_event_put(devfreq->ev); + devfreq->ev = RT_NULL; + } + + return err; +} diff --git a/components/drivers/dvfs/dvfs_cmd.c b/components/drivers/dvfs/dvfs_cmd.c new file mode 100644 index 0000000000..3bdbb0a613 --- /dev/null +++ b/components/drivers/dvfs/dvfs_cmd.c @@ -0,0 +1,270 @@ +/* + * 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 +#include +#include + +#define DBG_TAG "dvfs.cmd" +#define DBG_LVL DBG_INFO +#include + +#include +#include +#include +#include + +#define GOVERNOR_NAME_MAX sizeof(((struct rt_dvfs_governor *)RT_NULL)->name) + +static int list_dvfs(int argc, char**argv) +{ + rt_ubase_t level; + struct rt_object *obj; + struct rt_device *dev; + struct rt_dvfs_scaling *dvfs; + struct rt_object_information *info = rt_object_get_information(RT_Object_Class_Device); + + level = rt_hw_interrupt_disable(); + + rt_kprintf("%-*.s %-*.s %-*.s Frequency (Hz)\n", + RT_NAME_MAX, "Name", + RT_NAME_MAX, "Device", + GOVERNOR_NAME_MAX, "Governor"); + + rt_list_for_each_entry(obj, &info->object_list, list) + { + dev = rt_container_of(obj, struct rt_device, parent); + + if (!(dvfs = dev->dvfs_scaling)) + { + continue; + } + + rt_kprintf("%-*.s %-*.s %-*.s %lu\n", + RT_NAME_MAX, rt_dm_dev_get_name(dev), + RT_NAME_MAX, rt_dm_dev_get_name(dvfs->dev), + GOVERNOR_NAME_MAX, dvfs->gov ? dvfs->gov->name : "N/A", + dvfs->cur_freq); + } + + rt_hw_interrupt_enable(level); + + return 0; +} +MSH_CMD_EXPORT(list_dvfs, dump all of dvfs information); + +enum +{ + DVFS_OPT_DUMP = 1, + DVFS_OPT_SET_GOVERNOR, + DVFS_OPT_SET_FREQUENCY, +}; + +CMD_OPTIONS_STATEMENT(dvfs) + +static struct rt_dvfs_scaling *dvfs_scaling(const char *name) +{ + struct rt_device *dev = rt_device_find(name); + + if (!dev) + { + LOG_E("Device %s not found", name); + return RT_NULL; + } + + if (!dev->dvfs_scaling) + { + LOG_E("Device %s not supported dvfs", name); + return RT_NULL; + } + + return dev->dvfs_scaling; +} + +static int dvfs(int argc, char**argv) +{ + struct rt_dvfs_scaling *dvfs; + + if (argc < 2) + { + goto _usage; + } + + if (MSH_OPT_ID_GET(dvfs) == DVFS_OPT_DUMP) + { + rt_uint32_t opp_id = 0; + struct rt_dvfs_opp *opp; + + if (argc != 3 || !(dvfs = dvfs_scaling(argv[2]))) + { + goto _usage; + } + + rt_dvfs_scaling_enter(dvfs); + + rt_kprintf("name: %s\n", argv[2]); + rt_kprintf("device: %s\n", rt_dm_dev_get_name(dvfs->dev)); + rt_kprintf("governor: %s\n", dvfs->gov ? dvfs->gov->name : "N/A"); + rt_kprintf("min frequency: %lu Hz\n", dvfs->min_freq); + rt_kprintf("max frequency: %lu Hz\n", dvfs->max_freq); + rt_kprintf("current frequency: %lu Hz", dvfs->cur_freq); + if (dvfs->opp_table && dvfs->opp_table->current_opp) + { + rt_kprintf(" @ %lu uV", dvfs->opp_table->current_opp->uvolt); + } + rt_kprintf("\n"); + rt_kprintf("suspend frequency: %lu Hz\n", dvfs->suspend_freq); + +#ifdef RT_USING_DVFS_EVENT + if (dvfs->load_update) + { + struct rt_dvfs_devfreq *devfreq; + struct rt_dvfs_event_data evd = {0}; + + dvfs->load_update(dvfs); + rt_kprintf("event load: %u%%\n", dvfs->cpu_load.load_percentage); + + devfreq = rt_container_of(dvfs, struct rt_dvfs_devfreq, parent); + if (devfreq->ev && rt_dvfs_event_read(devfreq->ev, &evd) == RT_EOK && evd.total_count) + { + rt_uint64_t load_p = evd.load_count * 100000000ULL / evd.total_count; + rt_uint32_t load_int = (rt_uint32_t)(load_p / 1000000ULL); + rt_uint32_t load_frac = (rt_uint32_t)(load_p % 1000000ULL); + + rt_kprintf("event counters: %lu / %lu (%u.%06u%%)\n", + (unsigned long)evd.load_count, + (unsigned long)evd.total_count, + load_int, load_frac); + } + } +#endif + + if (dvfs->opp_table) + { + struct rt_dvfs_opp *current = dvfs->opp_table->current_opp; + const char *state; + + rt_kprintf("opp table:\n"); + + rt_list_for_each_entry(opp, &dvfs->opp_table->opp_nodes, list) + { + if (!opp->available) + { + state = "disabled"; + } + else if (opp == current) + { + state = "active"; + } + else if (opp->freq == dvfs->suspend_freq) + { + state = "suspend"; + } + else + { + state = RT_NULL; + } + + rt_kprintf(" opp[%02u]: %10lu Hz %7lu uV %5lu mW", + opp_id, opp->freq, opp->uvolt, opp->power); + + if (state) + { + rt_kprintf(" (%s)", state); + } + + rt_kprintf("\n"); + + ++opp_id; + } + } + + rt_dvfs_scaling_leave(dvfs); + + return 0; + } + else if (MSH_OPT_ID_GET(dvfs) == DVFS_OPT_SET_GOVERNOR) + { + rt_err_t err; + struct rt_dvfs_governor *gov; + + if (argc != 4 || !(dvfs = dvfs_scaling(argv[2]))) + { + goto _usage; + } + + if (!(gov = rt_dvfs_governor_get_by_name(argv[3]))) + { + LOG_E("Governor %s is not supported", argv[3]); + goto _usage; + } + + err = rt_dvfs_scaling_set_governor(dvfs, gov->type); + rt_dvfs_governor_put(gov); + + return err; + } + else if (MSH_OPT_ID_GET(dvfs) == DVFS_OPT_SET_FREQUENCY) + { + rt_err_t err; + rt_ubase_t freq; + + if (argc != 4 || !(dvfs = dvfs_scaling(argv[2]))) + { + goto _usage; + } + + freq = atol(argv[3]); + + if (freq < dvfs->min_freq || freq > dvfs->max_freq) + { + LOG_E("Frequency %lu is not supported", freq); + goto _usage; + } + + if (dvfs->gov && dvfs->gov->type != RT_DVFS_GOVERNOR_TYPE_FREEDOM) + { + err = rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_FREEDOM); + if (err) + { + LOG_E("switch governor failed: %s", rt_strerror(err)); + return err; + } + } + + err = rt_dvfs_scaling_set_frequency(dvfs, freq); + if (err) + { + LOG_E("%s: set frequency %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), freq, rt_strerror(err)); + } + else + { + rt_kprintf("%s: frequency set to %lu Hz\n", + rt_dm_dev_get_name(dvfs->dev), dvfs->cur_freq); + } + + return err; + } + +_usage: + rt_kprintf("Usage:\n"); + rt_kprintf("dvfs dump - dump dvfs information\n"); + rt_kprintf("dvfs set_governor - set dvfs governor\n"); + rt_kprintf("dvfs set_frequency - set dvfs frequency\n"); + + return (int)-RT_EINVAL; +} +CMD_OPTIONS_NODE_START(dvfs) +CMD_OPTIONS_NODE(DVFS_OPT_DUMP, dump, dump dvfs information) +CMD_OPTIONS_NODE(DVFS_OPT_SET_GOVERNOR, set_governor, set dvfs governor) +CMD_OPTIONS_NODE(DVFS_OPT_SET_FREQUENCY, set_frequency, set dvfs frequency) +CMD_OPTIONS_NODE_END +MSH_CMD_EXPORT_ALIAS(dvfs, dvfs, dvfs operation, optenable); diff --git a/components/drivers/dvfs/dvfs_cpu.c b/components/drivers/dvfs/dvfs_cpu.c new file mode 100644 index 0000000000..6b68a0dec6 --- /dev/null +++ b/components/drivers/dvfs/dvfs_cpu.c @@ -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 +#include +#include + +#define DBG_TAG "dvfs.cpu" +#define DBG_LVL DBG_INFO +#include + +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)); +} diff --git a/components/drivers/dvfs/dvfs_event.c b/components/drivers/dvfs/dvfs_event.c new file mode 100644 index 0000000000..45f329e264 --- /dev/null +++ b/components/drivers/dvfs/dvfs_event.c @@ -0,0 +1,246 @@ +/* + * 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 +#include +#include + +#define DBG_TAG "dvfs.event" +#define DBG_LVL DBG_INFO +#include + +rt_err_t rt_dvfs_event_register(struct rt_dvfs_event *ev) +{ + if (!ev || !ev->dev || !ev->ops) + { + return -RT_EINVAL; + } + + RT_ASSERT(ev->ops->ready != RT_NULL); + RT_ASSERT(ev->ops->read != RT_NULL); + + ev->enable_count = 0; + rt_spin_lock_init(&ev->lock); + + rt_dm_dev_bind_fwdata(ev->dev, RT_NULL, ev); + + return RT_EOK; +} + +rt_err_t rt_dvfs_event_unregister(struct rt_dvfs_event *ev) +{ + if (!ev || !ev->dev) + { + return -RT_EINVAL; + } + +#ifdef RT_USING_OFW + if (ev->dev->ofw_node) + { + struct rt_ofw_node *np = ev->dev->ofw_node; + + if (rt_ref_read(&np->ref) > 1) + { + return -RT_EBUSY; + } + } +#endif /* RT_USING_OFW */ + + rt_dvfs_event_disable(ev); + + rt_dm_dev_unbind_fwdata(ev->dev, RT_NULL); + + return RT_EOK; +} + +rt_err_t rt_dvfs_event_ready(struct rt_dvfs_event *ev) +{ + rt_err_t err; + + if (!ev) + { + return -RT_EINVAL; + } + + if (!rt_dvfs_event_is_enabled(ev)) + { + return -RT_EIO; + } + + rt_spin_lock(&ev->lock); + err = ev->ops->ready(ev); + rt_spin_unlock(&ev->lock); + + return err; +} + +rt_err_t rt_dvfs_event_read(struct rt_dvfs_event *ev, struct rt_dvfs_event_data *evd) +{ + rt_err_t err; + + if (!ev || !evd) + { + return -RT_EINVAL; + } + + if (!rt_dvfs_event_is_enabled(ev)) + { + return -RT_EIO; + } + + evd->total_count = 0; + evd->load_count = 0; + + rt_spin_lock(&ev->lock); + err = ev->ops->read(ev, evd); + rt_spin_unlock(&ev->lock); + + return err; +} + +rt_err_t rt_dvfs_event_enable(struct rt_dvfs_event *ev) +{ + rt_err_t err = RT_EOK; + + if (!ev) + { + return -RT_EINVAL; + } + + rt_spin_lock(&ev->lock); + + if (ev->ops->enable && ev->enable_count == 0) + { + if ((err = ev->ops->enable(ev))) + { + goto _out_lock; + } + } + ++ev->enable_count; + +_out_lock: + rt_spin_unlock(&ev->lock); + + return err; +} + +rt_err_t rt_dvfs_event_disable(struct rt_dvfs_event *ev) +{ + rt_err_t err = RT_EOK; + + if (!ev) + { + return -RT_EINVAL; + } + + rt_spin_lock(&ev->lock); + + if (ev->enable_count <= 0) + { + LOG_W("%s: No enabled before", rt_dm_dev_get_name(ev->dev)); + err = -RT_EIO; + goto _out_lock; + } + + if (ev->ops->disable && ev->enable_count == 1) + { + if ((err = ev->ops->disable(ev))) + { + goto _out_lock; + } + } + --ev->enable_count; + +_out_lock: + rt_spin_unlock(&ev->lock); + + return err; +} + +rt_bool_t rt_dvfs_event_is_enabled(struct rt_dvfs_event *ev) +{ + rt_bool_t res; + + if (!ev) + { + return RT_FALSE; + } + + rt_spin_lock(&ev->lock); + + res = ev->enable_count > 0; + + rt_spin_unlock(&ev->lock); + + return res; +} + +rt_err_t rt_dvfs_event_reset(struct rt_dvfs_event *ev) +{ + rt_err_t err; + + if (!ev) + { + return -RT_EINVAL; + } + + if (!rt_dvfs_event_is_enabled(ev)) + { + return -RT_EIO; + } + + rt_spin_lock(&ev->lock); + + if (ev->ops->reset) + { + err = ev->ops->reset(ev); + } + else + { + err = RT_EOK; + } + + rt_spin_unlock(&ev->lock); + + return err; +} + +struct rt_dvfs_event *rt_dvfs_event_get(struct rt_device *dev, const char *name, int index) +{ + struct rt_dvfs_event *ev = rt_err_ptr(-RT_ENOSYS); + +#ifdef RT_USING_OFW + if (dev->ofw_node) + { + struct rt_ofw_node *np = rt_ofw_parse_phandle(dev->ofw_node, name, index); + + if (!np) + { + return rt_err_ptr(-RT_EEMPTY); + } + + ev = rt_ofw_data(np); + } +#endif /* RT_USING_OFW */ + + return ev; +} + +void rt_dvfs_event_put(struct rt_dvfs_event *ev) +{ + if (!ev) + { + return; + } + +#ifdef RT_USING_OFW + rt_ofw_node_put(ev->dev->ofw_node); +#endif +} diff --git a/components/drivers/dvfs/dvfs_governor.c b/components/drivers/dvfs/dvfs_governor.c new file mode 100644 index 0000000000..e0bf4946a0 --- /dev/null +++ b/components/drivers/dvfs/dvfs_governor.c @@ -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 +#include +#include + +#define DBG_TAG "dvfs.governor" +#define DBG_LVL DBG_INFO +#include + +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); +} diff --git a/components/drivers/dvfs/dvfs_idle.c b/components/drivers/dvfs/dvfs_idle.c new file mode 100644 index 0000000000..80d2370dca --- /dev/null +++ b/components/drivers/dvfs/dvfs_idle.c @@ -0,0 +1,381 @@ +/* + * 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 +#include +#include + +#define DBG_TAG "dvfs.idle" +#define DBG_LVL DBG_INFO +#include + +static RT_DEFINE_SPINLOCK(_dvfs_idle_lock); + +/* Idle prediction data */ +static rt_uint32_t _last_idle_duration_us = 0; +static rt_uint32_t _predicted_idle_us = 0; + +rt_inline void dvfs_idle_lock(void) +{ + rt_spin_lock(&_dvfs_idle_lock); +} + +rt_inline void dvfs_idle_unlock(void) +{ + rt_spin_unlock(&_dvfs_idle_lock); +} + +/* Predict next idle duration based on history */ +static rt_uint32_t dvfs_predict_idle_duration(void) +{ + /* + * Simple prediction: exponentially weighted moving average + * predicted = 0.7 * last_actual + 0.3 * previous_predicted + */ + rt_uint32_t predicted = (_last_idle_duration_us * 7 + _predicted_idle_us * 3) / 10; + + /* Clamp to reasonable range */ + if (predicted < 100) + { + predicted = 100; /* Minimum 100us */ + } + + _predicted_idle_us = predicted; + + return predicted; +} + +/* Update prediction with actual idle duration */ +static void dvfs_update_idle_prediction(rt_uint32_t actual_duration_us) +{ + _last_idle_duration_us = actual_duration_us; +} + +rt_err_t rt_dvfs_idle_register(struct rt_dvfs_idle *idle) +{ + if (!idle || !idle->dev || !idle->ops) + { + return -RT_EINVAL; + } + + rt_dm_dev_bind_fwdata(idle->dev, RT_NULL, idle); + + return RT_EOK; +} + +rt_err_t rt_dvfs_idle_unregister(struct rt_dvfs_idle *idle) +{ + rt_err_t err = RT_EOK; + + if (!idle) + { + return -RT_EINVAL; + } + + dvfs_idle_lock(); + + if (idle->ref_count != 0 || idle->entry_count != 0) + { + err = -RT_EBUSY; + goto _unlock; + } + + rt_dm_dev_unbind_fwdata(idle->dev, RT_NULL); + +_unlock: + dvfs_idle_unlock(); + + return err; +} + +rt_err_t rt_dvfs_idle_add_status(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status) +{ + if (!idle || !status) + { + return -RT_EINVAL; + } + + if (!idle->status_table) + { + if (!(idle->status_table = rt_calloc(1, sizeof(*idle->status_table)))) + { + return -RT_ENOMEM; + } + + rt_list_init(&idle->status_table->status_nodes); + } + + rt_list_init(&status->list); + + dvfs_idle_lock(); + rt_list_insert_before(&idle->status_table->status_nodes, &status->list); + dvfs_idle_unlock(); + + return RT_EOK; +} + +void rt_dvfs_idle_remove_status(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status) +{ + if (!idle || !status) + { + return; + } + + RT_ASSERT(idle->status_table != RT_NULL); + + dvfs_idle_lock(); + rt_list_remove(&status->list); + dvfs_idle_unlock(); +} + +void rt_dvfs_idle_remove_status_all(struct rt_dvfs_idle *idle, + void (*release)(struct rt_dvfs_idle *, struct rt_dvfs_idle_status *)) +{ + struct rt_dvfs_idle_status_table *status_table; + struct rt_dvfs_idle_status *status, *status_next; + + if (!idle) + { + return; + } + + RT_ASSERT(idle->status_table != RT_NULL); + status_table = idle->status_table; + + dvfs_idle_lock(); + + rt_list_for_each_entry_safe(status, status_next, &status_table->status_nodes, list) + { + rt_list_remove(&status->list); + + dvfs_idle_unlock(); + + if (release) + { + release(idle, status); + } + + dvfs_idle_lock(); + } + + dvfs_idle_unlock(); +} + +rt_err_t rt_dvfs_idle_entry(struct rt_dvfs_idle *idle) +{ + rt_err_t err; + rt_bool_t can_stop_timer = RT_TRUE; + struct rt_dvfs_idle_status_table *table; + struct rt_dvfs_idle_status *it, *best = RT_NULL; + rt_uint32_t predicted_idle_us; + rt_tick_t entry_tick; + + if (!idle) + { + return -RT_EINVAL; + } + + if (!(table = idle->status_table)) + { + return -RT_ENOSYS; + } + + if (idle->ops->timer_can_stop) + { + can_stop_timer = idle->ops->timer_can_stop(idle); + } + + /* Predict idle duration */ + predicted_idle_us = dvfs_predict_idle_duration(); + + /* + * Select the best idle state based on predicted idle duration: + * - Choose the deepest sleep state that has: + * - entry_latency + exit_latency < predicted_idle + * - min_residency <= predicted_idle + * - This maximizes power savings while ensuring timely wakeup + */ + rt_list_for_each_entry(it, &table->status_nodes, list) + { + rt_uint32_t total_latency = it->entry_latency_us + it->exit_latency_us; + + /* Skip states that require timer stop if timer can't stop */ + if (it->timer_stop && !can_stop_timer) + { + continue; + } + + /* Check if this state is suitable for predicted idle time */ + if (predicted_idle_us >= total_latency && predicted_idle_us >= it->min_residency_us) + { + /* Choose the deepest suitable state (highest min_residency) */ + if (!best || it->min_residency_us > best->min_residency_us) + { + best = it; + } + } + } + + /* If no suitable state found, try to find a fallback */ + if (!best) + { + /* Find shallowest state that doesn't require timer stop */ + rt_list_for_each_entry(it, &table->status_nodes, list) + { + if (!it->timer_stop || can_stop_timer) + { + if (!best || it->entry_latency_us < best->entry_latency_us) + { + best = it; + } + } + } + } + + if (!best) + { + return -RT_EEMPTY; + } + + dvfs_idle_lock(); + + if (idle->entry_count != 0) + { + dvfs_idle_unlock(); + return -RT_EBUSY; + } + + table->current_status = best; + ++idle->entry_count; + entry_tick = rt_tick_get(); + + dvfs_idle_unlock(); + + LOG_D("%s: enter idle, predicted=%uus, selected state min_residency=%uus", + rt_dm_dev_get_name(idle->dev), predicted_idle_us, best->min_residency_us); + + if ((err = idle->ops->entry(idle, best))) + { + dvfs_idle_lock(); + table->current_status = RT_NULL; + --idle->entry_count; + dvfs_idle_unlock(); + return err; + } + + /* Store entry time for exit calculation */ + idle->priv = (void *)(rt_ubase_t)entry_tick; + + return RT_EOK; +} + +rt_err_t rt_dvfs_idle_exit(struct rt_dvfs_idle *idle) +{ + rt_err_t err; + struct rt_dvfs_idle_status *cur; + struct rt_dvfs_idle_status_table *table; + rt_tick_t exit_tick, entry_tick; + rt_uint32_t actual_idle_us; + + if (!idle) + { + return -RT_EINVAL; + } + + if (!(table = idle->status_table)) + { + return -RT_ENOSYS; + } + + dvfs_idle_lock(); + + if (idle->entry_count == 0 || table->current_status == RT_NULL) + { + dvfs_idle_unlock(); + return -RT_EINVAL; + } + cur = table->current_status; + entry_tick = (rt_tick_t)(rt_ubase_t)idle->priv; + + dvfs_idle_unlock(); + + exit_tick = rt_tick_get(); + + /* Calculate actual idle duration in microseconds */ + if (exit_tick >= entry_tick) + { + actual_idle_us = (exit_tick - entry_tick) * (1000000 / RT_TICK_PER_SECOND); + } + else + { + /* Tick overflow */ + actual_idle_us = (RT_TICK_MAX - entry_tick + exit_tick + 1) * (1000000 / RT_TICK_PER_SECOND); + } + + err = idle->ops->exit(idle, cur); + + dvfs_idle_lock(); + + if (idle->entry_count > 0) + { + --idle->entry_count; + } + table->current_status = RT_NULL; + idle->priv = RT_NULL; + + dvfs_idle_unlock(); + + /* Update prediction with actual duration */ + dvfs_update_idle_prediction(actual_idle_us); + + LOG_D("%s: exit idle, actual=%uus", rt_dm_dev_get_name(idle->dev), actual_idle_us); + + return err; +} + +struct rt_dvfs_idle *rt_dvfs_idle_get(struct rt_device *dev) +{ + struct rt_dvfs_idle *idle = RT_NULL; + + if (!dev) + { + return rt_err_ptr(-RT_EINVAL); + } + + dvfs_idle_lock(); + +#ifdef RT_USING_OFW + if (dev && dev->ofw_node) + { + idle = rt_ofw_data(dev->ofw_node); + } +#endif /* RT_USING_OFW */ + + if (!rt_is_err_or_null(idle)) + { + ++idle->ref_count; + } + + dvfs_idle_unlock(); + + return idle; +} + +void rt_dvfs_idle_put(struct rt_dvfs_idle *idle) +{ + if (!idle) + { + return; + } + + dvfs_idle_lock(); + --idle->ref_count; + dvfs_idle_unlock(); +} diff --git a/components/drivers/dvfs/dvfs_opp.c b/components/drivers/dvfs/dvfs_opp.c new file mode 100644 index 0000000000..2c91758d38 --- /dev/null +++ b/components/drivers/dvfs/dvfs_opp.c @@ -0,0 +1,336 @@ +/* + * 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 +#include +#include + +#define DBG_TAG "dvfs.opp" +#define DBG_LVL DBG_INFO +#include + +static struct rt_dvfs_opp *dvfs_opp_find_freq(struct rt_dvfs_opp_table *opp_table, rt_ubase_t freq, int dir) +{ + struct rt_dvfs_opp *opp, *opp_next, *best = RT_NULL; + + if (!opp_table) + { + return RT_NULL; + } + + rt_list_for_each_entry_safe(opp, opp_next, &opp_table->opp_nodes, list) + { + if (dir == 0) + { + if (opp->freq == freq) + { + return opp; + } + } + else if (dir > 0) + { + if (opp->freq >= freq && (!best || opp->freq < best->freq)) + { + best = opp; + } + } + else + { + if (opp->freq <= freq) + { + best = opp; + } + } + } + + return best; +} + +struct rt_dvfs_opp *rt_dvfs_scaling_add_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq, rt_ubase_t uvolt) +{ + struct rt_dvfs_opp_table *opp_table; + struct rt_dvfs_opp *opp, *opp_next; + + if (!dvfs) + { + return RT_NULL; + } + + if (!dvfs->opp_table) + { + if (!(dvfs->opp_table = rt_calloc(1, sizeof(*dvfs->opp_table)))) + { + return RT_NULL; + } + + rt_list_init(&dvfs->opp_table->opp_nodes); + } + + opp = rt_dvfs_scaling_find_opp(dvfs, freq); + + if (opp) + { + if (opp->uvolt == uvolt) + { + return opp; + } + + LOG_W("%s: OPP { %lu Hz, %lu uV } is exists { %lu Hz, %lu uV }", + rt_dm_dev_get_name(dvfs->dev), freq, uvolt, opp->freq, opp->uvolt); + + return RT_NULL; + } + + opp = rt_calloc(1, sizeof(*opp)); + + if (!opp) + { + LOG_E("%s: No memory to create OPP { %lu Hz, %lu uV }", + rt_dm_dev_get_name(dvfs->dev), freq, uvolt); + return RT_NULL; + } + + opp->freq = freq; + opp->uvolt = uvolt; + opp->available = RT_TRUE; + rt_list_init(&opp->list); + + rt_dvfs_scaling_enter(dvfs); + + opp_table = dvfs->opp_table; + + if (rt_list_isempty(&opp_table->opp_nodes)) + { + dvfs->min_freq = opp->freq; + dvfs->max_freq = opp->freq; + rt_list_insert_after(&opp_table->opp_nodes, &opp->list); + } + else + { + rt_list_for_each_entry(opp_next, &opp_table->opp_nodes, list) + { + if (opp->freq < opp_next->freq) + { + rt_list_insert_before(&opp_next->list, &opp->list); + break; + } + } + + if (rt_list_isempty(&opp->list)) + { + rt_list_insert_before(&opp_table->opp_nodes, &opp->list); + dvfs->max_freq = opp->freq; + } + else if (opp->freq < dvfs->min_freq) + { + dvfs->min_freq = opp->freq; + } + else if (opp->freq > dvfs->max_freq) + { + dvfs->max_freq = opp->freq; + } + } + + rt_dvfs_scaling_leave(dvfs); + + return opp; +} + +rt_err_t rt_dvfs_scaling_remove_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp_table *opp_table; + struct rt_dvfs_opp *opp = rt_dvfs_scaling_find_opp(dvfs, freq); + + if (!opp) + { + return -RT_EINVAL; + } + + opp_table = dvfs->opp_table; + + if (opp_table->current_opp == opp) + { + return -RT_EBUSY; + } + + rt_dvfs_scaling_enter(dvfs); + + rt_list_remove(&opp->list); + + if (!rt_list_isempty(&opp_table->opp_nodes)) + { + if (dvfs->min_freq == opp->freq) + { + opp = rt_list_entry(opp_table->opp_nodes.next, struct rt_dvfs_opp, list); + + dvfs->min_freq = opp->freq; + } + else if (dvfs->max_freq == opp->freq) + { + opp = rt_list_entry(opp_table->opp_nodes.prev, struct rt_dvfs_opp, list); + + dvfs->max_freq = opp->freq; + } + } + else + { + LOG_W("%s: OPP is empty", rt_dm_dev_get_name(dvfs->dev)); + + dvfs->min_freq = 0; + dvfs->max_freq = 0; + } + + rt_dvfs_scaling_leave(dvfs); + + rt_free(opp); + + return RT_EOK; +} + +rt_err_t rt_dvfs_scaling_remove_opp_all(struct rt_dvfs_scaling *dvfs) +{ + struct rt_dvfs_opp *opp, *opp_next; + struct rt_dvfs_opp_table *opp_table; + + if (!dvfs) + { + return -RT_EINVAL; + } + + rt_dvfs_scaling_enter(dvfs); + + /* User will free, so free ignore current */ + dvfs->min_freq = 0; + dvfs->max_freq = 0; + + opp_table = dvfs->opp_table; + opp_table->current_opp = RT_NULL; + + rt_list_for_each_entry_safe(opp, opp_next, &opp_table->opp_nodes, list) + { + rt_list_remove(&opp->list); + + rt_dvfs_scaling_leave(dvfs); + + rt_free(opp); + + rt_dvfs_scaling_enter(dvfs); + } + + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +rt_err_t rt_dvfs_scaling_enable_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp *opp = rt_dvfs_scaling_find_opp(dvfs, freq); + + if (!opp) + { + return -RT_EINVAL; + } + + rt_dvfs_scaling_enter(dvfs); + opp->available = RT_TRUE; + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +rt_err_t rt_dvfs_scaling_disable_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + rt_err_t err; + struct rt_dvfs_opp *opp = rt_dvfs_scaling_find_opp(dvfs, freq); + + if (!opp) + { + return -RT_EINVAL; + } + + rt_dvfs_scaling_enter(dvfs); + + if (dvfs->opp_table->current_opp != opp) + { + opp->available = RT_FALSE; + err = RT_EOK; + } + else + { + err = -RT_EBUSY; + } + + rt_dvfs_scaling_leave(dvfs); + + return err; +} + +struct rt_dvfs_opp *rt_dvfs_scaling_find_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp *opp = RT_NULL; + + if (!dvfs || !dvfs->opp_table) + { + return RT_NULL; + } + + rt_dvfs_scaling_enter(dvfs); + + if (freq >= dvfs->min_freq && freq <= dvfs->max_freq) + { + opp = dvfs_opp_find_freq(dvfs->opp_table, freq, 0); + } + + rt_dvfs_scaling_leave(dvfs); + + return opp; +} + +struct rt_dvfs_opp *rt_dvfs_scaling_find_ceil_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp *opp = RT_NULL; + + if (!dvfs || !dvfs->opp_table) + { + return RT_NULL; + } + + rt_dvfs_scaling_enter(dvfs); + + if (freq <= dvfs->max_freq) + { + opp = dvfs_opp_find_freq(dvfs->opp_table, freq, 1); + } + + rt_dvfs_scaling_leave(dvfs); + + return opp; +} + +struct rt_dvfs_opp *rt_dvfs_scaling_find_floor_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp *opp = RT_NULL; + + if (!dvfs || !dvfs->opp_table) + { + return RT_NULL; + } + + rt_dvfs_scaling_enter(dvfs); + + if (freq >= dvfs->min_freq) + { + opp = dvfs_opp_find_freq(dvfs->opp_table, freq, -1); + } + + rt_dvfs_scaling_leave(dvfs); + + return opp; +} diff --git a/components/drivers/dvfs/dvfs_pm.c b/components/drivers/dvfs/dvfs_pm.c new file mode 100644 index 0000000000..15ae49fec2 --- /dev/null +++ b/components/drivers/dvfs/dvfs_pm.c @@ -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 +#include +#include + +#define DBG_TAG "dvfs.pm" +#define DBG_LVL DBG_INFO +#include + +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; +} diff --git a/components/drivers/dvfs/governor/SConscript b/components/drivers/dvfs/governor/SConscript new file mode 100644 index 0000000000..7c6d0921d0 --- /dev/null +++ b/components/drivers/dvfs/governor/SConscript @@ -0,0 +1,10 @@ +from building import * + +cwd = GetCurrentDir() +CPPPATH = [cwd + '/../../include'] + +src = Glob('*.c') + +group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/drivers/dvfs/governor/conservative.c b/components/drivers/dvfs/governor/conservative.c new file mode 100644 index 0000000000..6aac1e8a2c --- /dev/null +++ b/components/drivers/dvfs/governor/conservative.c @@ -0,0 +1,329 @@ +/* + * 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 +#include +#include + +#define DBG_TAG "dvfs.governor.conservative" +#define DBG_LVL DBG_INFO +#include + +/* Default parameters (Linux-like) */ +#define CONSERVATIVE_DEFAULT_SAMPLING_RATE_MS 1000 +#define CONSERVATIVE_DEFAULT_UP_THRESHOLD 80 +#define CONSERVATIVE_DEFAULT_DOWN_DIFFERENTIAL 20 +#define CONSERVATIVE_DEFAULT_FREQ_STEP 5 +#define CONSERVATIVE_DEFAULT_SAMPLING_DOWN_FACTOR 1 + +struct governor_conservative_data +{ + struct rt_work monitor_work; + rt_bool_t running; +}; + +static rt_bool_t governor_conservative_active(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs || !dvfs->gov || + dvfs->gov->type != RT_DVFS_GOVERNOR_TYPE_CONSERVATIVE) + { + return RT_FALSE; + } + + data = dvfs->gov_data; + + return data && data->running; +} + +static void governor_conservative_monitor(struct rt_work *work, void *work_data) +{ + struct rt_dvfs_scaling *dvfs = (struct rt_dvfs_scaling *)work_data; + struct governor_conservative_data *data; + struct rt_dvfs_opp *opp; + rt_ubase_t target_freq; + rt_uint32_t load; + + if (!governor_conservative_active(dvfs)) + { + return; + } + + data = dvfs->gov_data; + + /* Update CPU load statistics */ + rt_dvfs_load_update(dvfs); + load = rt_dvfs_cpu_load_get(&dvfs->cpu_load); + + target_freq = dvfs->cur_freq; + + /* Determine target frequency based on load */ + if (load >= dvfs->gov_params.up_threshold) + { + /* High load: step up frequency */ + rt_ubase_t step_freq = (dvfs->max_freq * dvfs->gov_params.freq_step) / 100; + target_freq = dvfs->cur_freq + step_freq; + + /* Find ceil OPP */ + opp = rt_dvfs_scaling_find_ceil_opp(dvfs, target_freq); + if (opp && opp->available && opp->freq > dvfs->cur_freq) + { + target_freq = opp->freq; + } + else + { + target_freq = dvfs->max_freq; + } + + LOG_D("%s: load=%u%% >= up_threshold=%u%%, step up to %lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, + dvfs->gov_params.up_threshold, target_freq); + } + else if (load < dvfs->gov_params.down_differential) + { + /* Low load: step down frequency */ + rt_ubase_t step_freq = (dvfs->max_freq * dvfs->gov_params.freq_step) / 100; + target_freq = (dvfs->cur_freq > step_freq) ? (dvfs->cur_freq - step_freq) : dvfs->min_freq; + + /* Find floor OPP */ + opp = rt_dvfs_scaling_find_floor_opp(dvfs, target_freq); + if (opp && opp->available && opp->freq < dvfs->cur_freq) + { + target_freq = opp->freq; + } + else + { + target_freq = dvfs->min_freq; + } + + LOG_D("%s: load=%u%% < down_threshold=%u%%, step down to %lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, + dvfs->gov_params.down_differential, target_freq); + } + else + { + /* Medium load: keep current frequency */ + LOG_D("%s: load=%u%% in range, keep freq %lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, dvfs->cur_freq); + } + + /* Set target frequency if different */ + if (target_freq != dvfs->cur_freq && governor_conservative_active(dvfs)) + { + rt_err_t err = rt_dvfs_scaling_set_frequency(dvfs, target_freq); + if (err) + { + LOG_W("%s: set frequency %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), target_freq, rt_strerror(err)); + } + } + + /* Reschedule monitoring work */ + if (governor_conservative_active(dvfs)) + { + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } +} + +static rt_err_t governor_conservative_start(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Initialize default parameters if not set */ + if (dvfs->gov_params.sampling_rate_ms == 0) + { + dvfs->gov_params.sampling_rate_ms = CONSERVATIVE_DEFAULT_SAMPLING_RATE_MS; + } + if (dvfs->gov_params.up_threshold == 0) + { + dvfs->gov_params.up_threshold = CONSERVATIVE_DEFAULT_UP_THRESHOLD; + } + if (dvfs->gov_params.down_differential == 0) + { + dvfs->gov_params.down_differential = CONSERVATIVE_DEFAULT_DOWN_DIFFERENTIAL; + } + if (dvfs->gov_params.freq_step == 0) + { + dvfs->gov_params.freq_step = CONSERVATIVE_DEFAULT_FREQ_STEP; + } + if (dvfs->gov_params.sampling_down_factor == 0) + { + dvfs->gov_params.sampling_down_factor = CONSERVATIVE_DEFAULT_SAMPLING_DOWN_FACTOR; + } + + /* Allocate governor data */ + data = rt_calloc(1, sizeof(*data)); + if (!data) + { + LOG_E("%s: no memory for conservative data", rt_dm_dev_get_name(dvfs->dev)); + return -RT_ENOMEM; + } + + dvfs->gov_data = data; + data->running = RT_TRUE; + + /* Initialize CPU load */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + dvfs->cpu_load.load_percentage = 0; + + /* Initialize monitoring work */ + rt_work_init(&data->monitor_work, governor_conservative_monitor, dvfs); + + /* Submit first monitoring work */ + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + + LOG_D("%s: conservative governor started (sampling=%ums, up_threshold=%u%%, down_threshold=%u%%, freq_step=%u%%)", + rt_dm_dev_get_name(dvfs->dev), + dvfs->gov_params.sampling_rate_ms, + dvfs->gov_params.up_threshold, + dvfs->gov_params.down_differential, + dvfs->gov_params.freq_step); + + return RT_EOK; +} + +static rt_err_t governor_conservative_stop(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + + /* Wait for work to complete */ + rt_thread_mdelay(10); + + rt_free(data); + dvfs->gov_data = RT_NULL; + } + + LOG_D("%s: conservative governor stopped", rt_dm_dev_get_name(dvfs->dev)); + + return RT_EOK; +} + +static rt_err_t governor_conservative_suspend(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Stop monitoring */ + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + } + + return RT_EOK; +} + +static rt_err_t governor_conservative_resume(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Resume monitoring */ + data = dvfs->gov_data; + if (data) + { + /* Reset CPU load statistics */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + + data->running = RT_TRUE; + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } + + return RT_EOK; +} + +static rt_err_t governor_conservative_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Minimum sampling interval: 10ms */ + if (interval_ms < 10) + { + interval_ms = 10; + } + + dvfs->gov_params.sampling_rate_ms = interval_ms; + + LOG_D("%s: conservative sampling interval set to %ums", + rt_dm_dev_get_name(dvfs->dev), interval_ms); + + return RT_EOK; +} + +static rt_err_t governor_conservative_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq) +{ + if (!dvfs || !out_freq) + { + return -RT_EINVAL; + } + + /* Conservative sets frequency based on load, not requested frequency */ + /* Return current frequency */ + *out_freq = dvfs->cur_freq; + + return RT_EOK; +} + +static struct rt_dvfs_governor governor_conservative = +{ + .name = "conservative", + .type = RT_DVFS_GOVERNOR_TYPE_CONSERVATIVE, + + .start = governor_conservative_start, + .stop = governor_conservative_stop, + .suspend = governor_conservative_suspend, + .resume = governor_conservative_resume, + .set_interval = governor_conservative_set_interval, + .set_frequency = governor_conservative_set_frequency, +}; + +static int governor_conservative_init(void) +{ + rt_dvfs_governor_register(&governor_conservative); + + return 0; +} +INIT_CORE_EXPORT(governor_conservative_init); diff --git a/components/drivers/dvfs/governor/freedom.c b/components/drivers/dvfs/governor/freedom.c new file mode 100644 index 0000000000..e785047355 --- /dev/null +++ b/components/drivers/dvfs/governor/freedom.c @@ -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 +#include +#include + +#define DBG_TAG "dvfs.governor.freedom" +#define DBG_LVL DBG_INFO +#include + +/* 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); diff --git a/components/drivers/dvfs/governor/ondemand.c b/components/drivers/dvfs/governor/ondemand.c new file mode 100644 index 0000000000..8e84e2ca85 --- /dev/null +++ b/components/drivers/dvfs/governor/ondemand.c @@ -0,0 +1,317 @@ +/* + * 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 +#include +#include + +#define DBG_TAG "dvfs.governor.ondemand" +#define DBG_LVL DBG_INFO +#include + +/* Default parameters (Linux-like) */ +#define ONDEMAND_DEFAULT_SAMPLING_RATE_MS 1000 +#define ONDEMAND_DEFAULT_UP_THRESHOLD 80 +#define ONDEMAND_DEFAULT_DOWN_DIFFERENTIAL 20 +#define ONDEMAND_DEFAULT_IGNORE_NICE RT_FALSE +#define ONDEMAND_DEFAULT_POWERSAVE_BIAS 0 + +struct governor_ondemand_data +{ + struct rt_work monitor_work; + rt_bool_t running; +}; + +static rt_bool_t governor_ondemand_active(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs || !dvfs->gov || + dvfs->gov->type != RT_DVFS_GOVERNOR_TYPE_ONDEMAND) + { + return RT_FALSE; + } + + data = dvfs->gov_data; + + return data && data->running; +} + +static void governor_ondemand_monitor(struct rt_work *work, void *work_data) +{ + struct rt_dvfs_scaling *dvfs = (struct rt_dvfs_scaling *)work_data; + struct governor_ondemand_data *data; + struct rt_dvfs_opp *opp; + rt_ubase_t target_freq; + rt_uint32_t load; + + if (!governor_ondemand_active(dvfs)) + { + return; + } + + data = dvfs->gov_data; + + /* Update CPU load statistics */ + rt_dvfs_load_update(dvfs); + load = rt_dvfs_cpu_load_get(&dvfs->cpu_load); + + /* Determine target frequency based on load */ + if (load >= dvfs->gov_params.up_threshold) + { + /* High load: jump to max frequency */ + target_freq = dvfs->max_freq; + LOG_D("%s: load=%u%% >= threshold=%u%%, set max freq", + rt_dm_dev_get_name(dvfs->dev), load, + dvfs->gov_params.up_threshold); + } + else if (load < dvfs->gov_params.down_differential) + { + /* Low load: find appropriate frequency */ + rt_ubase_t freq_target = (dvfs->cur_freq * load) / 100; + + /* Apply powersave bias if configured */ + if (dvfs->gov_params.powersave_bias > 0) + { + freq_target = freq_target * (100 - dvfs->gov_params.powersave_bias) / 100; + } + + /* Find floor OPP (next lower or equal frequency) */ + opp = rt_dvfs_scaling_find_floor_opp(dvfs, freq_target); + if (opp && opp->available) + { + target_freq = opp->freq; + } + else + { + target_freq = dvfs->min_freq; + } + + LOG_D("%s: load=%u%% < diff=%u%%, target=%lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, + dvfs->gov_params.down_differential, target_freq); + } + else + { + /* Medium load: keep current frequency */ + target_freq = dvfs->cur_freq; + } + + /* Set target frequency if different */ + if (target_freq != dvfs->cur_freq && governor_ondemand_active(dvfs)) + { + rt_err_t err = rt_dvfs_scaling_set_frequency(dvfs, target_freq); + if (err) + { + LOG_W("%s: set frequency %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), target_freq, rt_strerror(err)); + } + } + + /* Reschedule monitoring work */ + if (governor_ondemand_active(dvfs)) + { + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } +} + +static rt_err_t governor_ondemand_start(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Initialize default parameters if not set */ + if (dvfs->gov_params.sampling_rate_ms == 0) + { + dvfs->gov_params.sampling_rate_ms = ONDEMAND_DEFAULT_SAMPLING_RATE_MS; + } + if (dvfs->gov_params.up_threshold == 0) + { + dvfs->gov_params.up_threshold = ONDEMAND_DEFAULT_UP_THRESHOLD; + } + if (dvfs->gov_params.down_differential == 0) + { + dvfs->gov_params.down_differential = ONDEMAND_DEFAULT_DOWN_DIFFERENTIAL; + } + if (dvfs->gov_params.ignore_nice_load == 0) + { + dvfs->gov_params.ignore_nice_load = ONDEMAND_DEFAULT_IGNORE_NICE; + } + if (dvfs->gov_params.powersave_bias == 0) + { + dvfs->gov_params.powersave_bias = ONDEMAND_DEFAULT_POWERSAVE_BIAS; + } + + /* Allocate governor data */ + data = rt_calloc(1, sizeof(*data)); + if (!data) + { + LOG_E("%s: no memory for ondemand data", rt_dm_dev_get_name(dvfs->dev)); + return -RT_ENOMEM; + } + + dvfs->gov_data = data; + data->running = RT_TRUE; + + /* Initialize CPU load */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + dvfs->cpu_load.load_percentage = 0; + + /* Initialize monitoring work */ + rt_work_init(&data->monitor_work, governor_ondemand_monitor, dvfs); + + /* Submit first monitoring work */ + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + + LOG_D("%s: ondemand governor started (sampling=%ums, up_threshold=%u%%, down_diff=%u%%)", + rt_dm_dev_get_name(dvfs->dev), + dvfs->gov_params.sampling_rate_ms, + dvfs->gov_params.up_threshold, + dvfs->gov_params.down_differential); + + return RT_EOK; +} + +static rt_err_t governor_ondemand_stop(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + + /* Wait for work to complete */ + rt_thread_mdelay(10); + + rt_free(data); + dvfs->gov_data = RT_NULL; + } + + LOG_D("%s: ondemand governor stopped", rt_dm_dev_get_name(dvfs->dev)); + + return RT_EOK; +} + +static rt_err_t governor_ondemand_suspend(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Stop monitoring */ + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + } + + return RT_EOK; +} + +static rt_err_t governor_ondemand_resume(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Resume monitoring */ + data = dvfs->gov_data; + if (data) + { + /* Reset CPU load statistics */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + + data->running = RT_TRUE; + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } + + return RT_EOK; +} + +static rt_err_t governor_ondemand_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Minimum sampling interval: 10ms */ + if (interval_ms < 10) + { + interval_ms = 10; + } + + dvfs->gov_params.sampling_rate_ms = interval_ms; + + LOG_D("%s: ondemand sampling interval set to %ums", + rt_dm_dev_get_name(dvfs->dev), interval_ms); + + return RT_EOK; +} + +static rt_err_t governor_ondemand_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq) +{ + if (!dvfs || !out_freq) + { + return -RT_EINVAL; + } + + /* Ondemand sets frequency based on load, not requested frequency */ + /* Return current frequency */ + *out_freq = dvfs->cur_freq; + + return RT_EOK; +} + +static struct rt_dvfs_governor governor_ondemand = +{ + .name = "ondemand", + .type = RT_DVFS_GOVERNOR_TYPE_ONDEMAND, + + .start = governor_ondemand_start, + .stop = governor_ondemand_stop, + .suspend = governor_ondemand_suspend, + .resume = governor_ondemand_resume, + .set_interval = governor_ondemand_set_interval, + .set_frequency = governor_ondemand_set_frequency, +}; + +static int governor_ondemand_init(void) +{ + rt_dvfs_governor_register(&governor_ondemand); + + return 0; +} +INIT_CORE_EXPORT(governor_ondemand_init); diff --git a/components/drivers/dvfs/governor/performance.c b/components/drivers/dvfs/governor/performance.c new file mode 100644 index 0000000000..d2fba6e5e9 --- /dev/null +++ b/components/drivers/dvfs/governor/performance.c @@ -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 +#include +#include + +#define DBG_TAG "dvfs.governor.performance" +#define DBG_LVL DBG_INFO +#include + +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); diff --git a/components/drivers/dvfs/governor/powersave.c b/components/drivers/dvfs/governor/powersave.c new file mode 100644 index 0000000000..9ad1bc2c9a --- /dev/null +++ b/components/drivers/dvfs/governor/powersave.c @@ -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 +#include +#include + +#define DBG_TAG "dvfs.governor.powersave" +#define DBG_LVL DBG_INFO +#include + +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); diff --git a/components/drivers/dvfs/governor/schedutil.c b/components/drivers/dvfs/governor/schedutil.c new file mode 100644 index 0000000000..2a13d1b08d --- /dev/null +++ b/components/drivers/dvfs/governor/schedutil.c @@ -0,0 +1,286 @@ +/* + * 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 +#include +#include + +#define DBG_TAG "dvfs.governor.schedutil" +#define DBG_LVL DBG_INFO +#include + +/* Default parameters */ +#define SCHEDUTIL_DEFAULT_SAMPLING_RATE_MS 500 +#define SCHEDUTIL_UP_THRESHOLD 85 +#define SCHEDUTIL_DOWN_DIFFERENTIAL 20 + +struct governor_schedutil_data +{ + struct rt_work monitor_work; + rt_bool_t running; +}; + +static rt_bool_t governor_schedutil_active(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs || !dvfs->gov || + dvfs->gov->type != RT_DVFS_GOVERNOR_TYPE_SCHEDUTIL) + { + return RT_FALSE; + } + + data = dvfs->gov_data; + + return data && data->running; +} + +static void governor_schedutil_monitor(struct rt_work *work, void *work_data) +{ + struct rt_dvfs_scaling *dvfs = (struct rt_dvfs_scaling *)work_data; + struct governor_schedutil_data *data; + struct rt_dvfs_opp *opp; + rt_ubase_t target_freq; + rt_uint32_t load; + + if (!governor_schedutil_active(dvfs)) + { + return; + } + + data = dvfs->gov_data; + + /* Update CPU load statistics */ + rt_dvfs_load_update(dvfs); + load = rt_dvfs_cpu_load_get(&dvfs->cpu_load); + + /* + * Schedutil uses scheduler's utilization estimate. + * For RT-Thread, we use CPU load as approximation. + * In a full implementation, this would hook into the scheduler + * to get actual utilization metrics. + */ + if (load >= SCHEDUTIL_UP_THRESHOLD) + { + /* High utilization: use max frequency */ + target_freq = dvfs->max_freq; + } + else + { + /* Scale frequency proportionally to load */ + target_freq = dvfs->max_freq * load / 100; + + /* Find floor OPP */ + opp = rt_dvfs_scaling_find_floor_opp(dvfs, target_freq); + if (opp && opp->available) + { + target_freq = opp->freq; + } + else + { + target_freq = dvfs->min_freq; + } + } + + LOG_D("%s: schedutil load=%u%%, target_freq=%lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, target_freq); + + /* Set target frequency if different */ + if (target_freq != dvfs->cur_freq && governor_schedutil_active(dvfs)) + { + rt_err_t err = rt_dvfs_scaling_set_frequency(dvfs, target_freq); + if (err) + { + LOG_W("%s: set frequency %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), target_freq, rt_strerror(err)); + } + } + + /* Reschedule monitoring work */ + if (governor_schedutil_active(dvfs)) + { + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } +} + +static rt_err_t governor_schedutil_start(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Initialize default parameters if not set */ + if (dvfs->gov_params.sampling_rate_ms == 0) + { + dvfs->gov_params.sampling_rate_ms = SCHEDUTIL_DEFAULT_SAMPLING_RATE_MS; + } + + /* Allocate governor data */ + data = rt_calloc(1, sizeof(*data)); + if (!data) + { + LOG_E("%s: no memory for schedutil data", rt_dm_dev_get_name(dvfs->dev)); + return -RT_ENOMEM; + } + + dvfs->gov_data = data; + data->running = RT_TRUE; + + /* Initialize CPU load */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + dvfs->cpu_load.load_percentage = 0; + + /* Initialize monitoring work */ + rt_work_init(&data->monitor_work, governor_schedutil_monitor, dvfs); + + /* Submit first monitoring work */ + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + + LOG_D("%s: schedutil governor started (sampling=%ums)", + rt_dm_dev_get_name(dvfs->dev), dvfs->gov_params.sampling_rate_ms); + + return RT_EOK; +} + +static rt_err_t governor_schedutil_stop(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + + /* Wait for work to complete */ + rt_thread_mdelay(10); + + rt_free(data); + dvfs->gov_data = RT_NULL; + } + + LOG_D("%s: schedutil governor stopped", rt_dm_dev_get_name(dvfs->dev)); + + return RT_EOK; +} + +static rt_err_t governor_schedutil_suspend(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Stop monitoring */ + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + } + + return RT_EOK; +} + +static rt_err_t governor_schedutil_resume(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Resume monitoring */ + data = dvfs->gov_data; + if (data) + { + /* Reset CPU load statistics */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + + data->running = RT_TRUE; + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } + + return RT_EOK; +} + +static rt_err_t governor_schedutil_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Minimum sampling interval: 10ms */ + if (interval_ms < 10) + { + interval_ms = 10; + } + + dvfs->gov_params.sampling_rate_ms = interval_ms; + + LOG_D("%s: schedutil sampling interval set to %ums", + rt_dm_dev_get_name(dvfs->dev), interval_ms); + + return RT_EOK; +} + +static rt_err_t governor_schedutil_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq) +{ + if (!dvfs || !out_freq) + { + return -RT_EINVAL; + } + + /* Schedutil sets frequency based on scheduler utilization */ + /* Return current frequency */ + *out_freq = dvfs->cur_freq; + + return RT_EOK; +} + +static struct rt_dvfs_governor governor_schedutil = +{ + .name = "schedutil", + .type = RT_DVFS_GOVERNOR_TYPE_SCHEDUTIL, + + .start = governor_schedutil_start, + .stop = governor_schedutil_stop, + .suspend = governor_schedutil_suspend, + .resume = governor_schedutil_resume, + .set_interval = governor_schedutil_set_interval, + .set_frequency = governor_schedutil_set_frequency, +}; + +static int governor_schedutil_init(void) +{ + rt_dvfs_governor_register(&governor_schedutil); + + return 0; +} +INIT_CORE_EXPORT(governor_schedutil_init); diff --git a/components/drivers/include/drivers/dvfs.h b/components/drivers/include/drivers/dvfs.h new file mode 100644 index 0000000000..0b22446219 --- /dev/null +++ b/components/drivers/include/drivers/dvfs.h @@ -0,0 +1,353 @@ +/* + * 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 + */ + +#ifndef __DVFS_H__ +#define __DVFS_H__ + +#include +#include +#include +#include + +#include +#include + +enum +{ + /* Run the device at the maximum frequency */ + RT_DVFS_GOVERNOR_TYPE_PERFORMANCE = 0, + /* Run the device at the minimum frequency */ + RT_DVFS_GOVERNOR_TYPE_POWERSAVE, + /* Run the device at user specified frequencies */ + RT_DVFS_GOVERNOR_TYPE_FREEDOM, + /* + * Scales the frequency dynamically according to current load. + * Jumps to the highest frequency and then possibly back off as + * the idle time increases. + */ + RT_DVFS_GOVERNOR_TYPE_ONDEMAND, + /* + * Scales the frequency dynamically according to current load. + * Scales the frequency more gradually than ondemand. + */ + RT_DVFS_GOVERNOR_TYPE_CONSERVATIVE, + /* Scheduler-driven CPU frequency selection */ + RT_DVFS_GOVERNOR_TYPE_SCHEDUTIL, + + /* Custom for user start */ + RT_DVFS_GOVERNOR_TYPE_CUSTOM, +}; + +struct rt_dvfs_opp; +struct rt_dvfs_opp_table; +struct rt_dvfs_governor; +struct rt_dvfs_scaling_ops; +struct rt_dvfs_event_ops; +struct rt_dvfs_idle_ops; +struct rt_dvfs_idle_status; +struct rt_dvfs_idle_status_table; + +/* CPU load statistics */ +struct rt_dvfs_cpu_load +{ + rt_uint64_t total_tick; /* Total ticks in sampling period */ + rt_uint64_t idle_tick; /* Idle ticks in sampling period */ + rt_uint32_t load_percentage; /* Load percentage (0-100) */ + rt_tick_t last_update; /* Last update tick */ +}; + +/* Governor parameters (Linux-like defaults) */ +struct rt_dvfs_governor_params +{ + rt_uint32_t sampling_rate_ms; /* Sampling interval in ms, default 1000 */ + rt_uint32_t up_threshold; /* Up threshold percentage, default 80 */ + rt_uint32_t down_differential; /* Down differential percentage, default 20 */ + rt_uint32_t sampling_down_factor; /* Sampling down factor for conservative, default 1 */ + rt_uint32_t freq_step; /* Frequency step percentage for conservative, default 5 */ + rt_bool_t ignore_nice_load; /* Ignore nice tasks, default RT_FALSE */ + rt_uint32_t powersave_bias; /* Powersave bias percentage, default 0 */ +}; + +struct rt_dvfs_scaling +{ + struct rt_device *dev; + + struct rt_clk *clk; + struct rt_regulator *supply; + + const struct rt_dvfs_scaling_ops *ops; + + /* Hz */ + rt_ubase_t min_freq; + rt_ubase_t max_freq; + rt_ubase_t cur_freq; + rt_ubase_t suspend_freq; + + /* NS */ + rt_uint32_t retry_delay; + rt_uint32_t transition_latency; + + struct rt_dvfs_opp_table *opp_table; + + struct rt_dvfs_governor *gov; + struct rt_dvfs_governor_params gov_params; + void *gov_data; + + /* CPU load statistics */ + struct rt_dvfs_cpu_load cpu_load; + void (*load_update)(struct rt_dvfs_scaling *dvfs); + + void *priv; +}; + +struct rt_dvfs_scaling_ops +{ + rt_err_t (*suspend)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*resume)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*set_opp)(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp); + + rt_err_t (*parse_opp)(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp, void *fw_np); +}; + +/* + * DVFS device hierarchy: + * rt_dvfs_scaling - internal core (OPP / governor / frequency) + * rt_dvfs_devfreq - DVFS device (clk/regulator + optional event) + * rt_dvfs_cpufreq - CPU DVFS, extends devfreq (idle-hook load) + */ +struct rt_dvfs_devfreq +{ + struct rt_dvfs_scaling parent; + + struct rt_dvfs_event *ev; +}; + +struct rt_dvfs_cpufreq +{ + struct rt_dvfs_devfreq parent; + + int master_cpu; + RT_BITMAP_DECLARE(cpus_map, RT_CPUS_NR); +}; + +rt_inline struct rt_dvfs_scaling *rt_dvfs_devfreq_to_scaling(struct rt_dvfs_devfreq *devfreq) +{ + return &devfreq->parent; +} + +rt_inline struct rt_dvfs_devfreq *rt_dvfs_cpufreq_to_devfreq(struct rt_dvfs_cpufreq *cpufreq) +{ + return &cpufreq->parent; +} + +rt_inline struct rt_dvfs_scaling *rt_dvfs_cpufreq_to_scaling(struct rt_dvfs_cpufreq *cpufreq) +{ + return &cpufreq->parent.parent; +} + +extern struct rt_dvfs_scaling_ops rt_dvfs_devfreq_ops; + +struct rt_dvfs_idle +{ + struct rt_device *dev; + + rt_uint32_t ref_count; + rt_uint32_t entry_count; + + const struct rt_dvfs_idle_ops *ops; + + struct rt_dvfs_idle_status_table *status_table; + + void *priv; +}; + +struct rt_dvfs_idle_ops +{ + rt_err_t (*entry)(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status); + rt_err_t (*exit)(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status); + + rt_bool_t (*timer_can_stop)(struct rt_dvfs_idle *idle); +}; + +struct rt_dvfs_idle_status +{ + rt_list_t list; + + rt_uint32_t entry_latency_us; + rt_uint32_t exit_latency_us; + rt_uint32_t min_residency_us; + + rt_bool_t timer_stop; + + void *priv; +}; + +struct rt_dvfs_idle_status_table +{ + rt_list_t status_nodes; + struct rt_dvfs_idle_status *current_status; + + void *priv; +}; + +struct rt_dvfs_opp +{ + rt_list_t list; + + rt_ubase_t freq; /* Hz */ + rt_ubase_t uvolt; /* uV */ + rt_ubase_t power; /* mW */ + rt_bool_t available; + + void *priv; +}; + +struct rt_dvfs_opp_table +{ + rt_bool_t share; + + rt_list_t opp_nodes; + struct rt_dvfs_opp *current_opp; + + void *priv; +}; + +struct rt_dvfs_event_data +{ + /* Load count of dvfs-event device for the given period */ + rt_ubase_t load_count; + /* Total count of dvfs-event device for the given period */ + rt_ubase_t total_count; +}; + +struct rt_dvfs_event +{ + struct rt_device *dev; + + const struct rt_dvfs_event_ops *ops; + + rt_uint32_t enable_count; + struct rt_spinlock lock; + + void *priv; +}; + +struct rt_dvfs_event_ops +{ + rt_err_t (*ready)(struct rt_dvfs_event *ev); + rt_err_t (*read)(struct rt_dvfs_event *ev, struct rt_dvfs_event_data *evd); + + rt_err_t (*enable)(struct rt_dvfs_event *ev); + rt_err_t (*disable)(struct rt_dvfs_event *ev); + rt_err_t (*reset)(struct rt_dvfs_event *ev); +}; + +struct rt_dvfs_governor +{ + rt_list_t list; + char name[16]; + + rt_uint32_t type; + struct rt_ref ref; + + rt_err_t (*start)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*stop)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*suspend)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*resume)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*set_interval)(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms); + rt_err_t (*set_frequency)(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq); +}; + +/* DVFS */ +rt_err_t rt_dvfs_scaling_register(struct rt_dvfs_scaling *dvfs); +rt_err_t rt_dvfs_scaling_unregister(struct rt_dvfs_scaling *dvfs); + +void rt_dvfs_scaling_enter(struct rt_dvfs_scaling *dvfs); +void rt_dvfs_scaling_leave(struct rt_dvfs_scaling *dvfs); + +void rt_dvfs_ns_sleep(rt_uint32_t ns); + +rt_err_t rt_dvfs_scaling_suspend(struct rt_dvfs_scaling *dvfs); +rt_err_t rt_dvfs_scaling_resume(struct rt_dvfs_scaling *dvfs); +rt_err_t rt_dvfs_scaling_set_governor(struct rt_dvfs_scaling *dvfs, rt_uint32_t governor); +rt_err_t rt_dvfs_scaling_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t frequency); +rt_err_t rt_dvfs_scaling_apply_opp(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp); + +/* OPP */ +struct rt_dvfs_opp *rt_dvfs_scaling_add_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq, rt_ubase_t uvolt); +rt_err_t rt_dvfs_scaling_remove_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); +rt_err_t rt_dvfs_scaling_remove_opp_all(struct rt_dvfs_scaling *dvfs); + +rt_err_t rt_dvfs_scaling_enable_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); +rt_err_t rt_dvfs_scaling_disable_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); + +struct rt_dvfs_opp *rt_dvfs_scaling_find_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); +struct rt_dvfs_opp *rt_dvfs_scaling_find_ceil_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); +struct rt_dvfs_opp *rt_dvfs_scaling_find_floor_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); + +/* CPU */ +rt_err_t rt_dvfs_cpufreq_register(struct rt_dvfs_cpufreq *cpufreq); +rt_err_t rt_dvfs_cpufreq_unregister(struct rt_dvfs_cpufreq *cpufreq); + +rt_err_t rt_dvfs_devfreq_register(struct rt_dvfs_devfreq *devfreq); +rt_err_t rt_dvfs_devfreq_unregister(struct rt_dvfs_devfreq *devfreq); + +/* CPU-Idle */ +rt_err_t rt_dvfs_idle_register(struct rt_dvfs_idle *idle); +rt_err_t rt_dvfs_idle_unregister(struct rt_dvfs_idle *idle); + +rt_err_t rt_dvfs_idle_add_status(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status); +void rt_dvfs_idle_remove_status(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status); +void rt_dvfs_idle_remove_status_all(struct rt_dvfs_idle *idle, + void (*release)(struct rt_dvfs_idle *, struct rt_dvfs_idle_status *)); + +rt_err_t rt_dvfs_idle_entry(struct rt_dvfs_idle *idle); +rt_err_t rt_dvfs_idle_exit(struct rt_dvfs_idle *idle); + +struct rt_dvfs_idle *rt_dvfs_idle_get(struct rt_device *dev); +void rt_dvfs_idle_put(struct rt_dvfs_idle *idle); + +/* Event */ +rt_err_t rt_dvfs_event_register(struct rt_dvfs_event *ev); +rt_err_t rt_dvfs_event_unregister(struct rt_dvfs_event *ev); + +rt_err_t rt_dvfs_event_ready(struct rt_dvfs_event *ev); +rt_err_t rt_dvfs_event_read(struct rt_dvfs_event *ev, struct rt_dvfs_event_data *evd); +rt_err_t rt_dvfs_event_enable(struct rt_dvfs_event *ev); +rt_err_t rt_dvfs_event_disable(struct rt_dvfs_event *ev); +rt_bool_t rt_dvfs_event_is_enabled(struct rt_dvfs_event *ev); +rt_err_t rt_dvfs_event_reset(struct rt_dvfs_event *ev); + +struct rt_dvfs_event *rt_dvfs_event_get(struct rt_device *dev, const char *name, int index); +void rt_dvfs_event_put(struct rt_dvfs_event *ev); + +/* Governor */ +rt_err_t rt_dvfs_governor_register(struct rt_dvfs_governor *gov); +rt_err_t rt_dvfs_governor_unregister(struct rt_dvfs_governor *gov); +struct rt_dvfs_governor *rt_dvfs_governor_get(rt_uint32_t governor); +struct rt_dvfs_governor *rt_dvfs_governor_get_by_name(const char *name); +void rt_dvfs_governor_put(struct rt_dvfs_governor *gov); + +rt_err_t rt_dvfs_governor_set_params(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_governor_params *params); +rt_err_t rt_dvfs_governor_get_params(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_governor_params *params); + +/* Load */ +void rt_dvfs_load_update(struct rt_dvfs_scaling *dvfs); +void rt_dvfs_cpu_load_update(struct rt_dvfs_cpu_load *load); +rt_uint32_t rt_dvfs_cpu_load_get(struct rt_dvfs_cpu_load *load); + +/* PM */ +struct rt_device_pm; + +rt_err_t rt_dvfs_scaling_pm_suspend(struct rt_device_pm *device_pm, rt_uint8_t mode); +rt_err_t rt_dvfs_scaling_pm_resume(struct rt_device_pm *device_pm, rt_uint8_t mode); +rt_err_t rt_dvfs_scaling_pm_frequency_change(struct rt_device_pm *device_pm, rt_uint8_t mode); + +#endif /* __DVFS_H__ */ diff --git a/components/drivers/include/drivers/thermal.h b/components/drivers/include/drivers/thermal.h index 0770c5c453..c7c2244582 100644 --- a/components/drivers/include/drivers/thermal.h +++ b/components/drivers/include/drivers/thermal.h @@ -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 diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h index 0c83137087..b416b5e928 100644 --- a/components/drivers/include/rtdevice.h +++ b/components/drivers/include/rtdevice.h @@ -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 */ diff --git a/components/drivers/pm/pm.c b/components/drivers/pm/pm.c index b4ad3b2bbd..9acbf00147 100644 --- a/components/drivers/pm/pm.c +++ b/components/drivers/pm/pm.c @@ -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 #include #include +#ifdef RT_USING_DVFS +#include +#endif #include #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); } } diff --git a/components/drivers/thermal/Kconfig b/components/drivers/thermal/Kconfig index 91a394fe8b..bdde92c728 100644 --- a/components/drivers/thermal/Kconfig +++ b/components/drivers/thermal/Kconfig @@ -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 diff --git a/components/drivers/thermal/SConscript b/components/drivers/thermal/SConscript index 31ba302381..015ff18d5a 100644 --- a/components/drivers/thermal/SConscript +++ b/components/drivers/thermal/SConscript @@ -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'] diff --git a/components/drivers/thermal/thermal-cool-dvfs.c b/components/drivers/thermal/thermal-cool-dvfs.c new file mode 100644 index 0000000000..fb72cec9a5 --- /dev/null +++ b/components/drivers/thermal/thermal-cool-dvfs.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 +#include +#include + +#define DBG_TAG "thermal.cool.dvfs" +#define DBG_LVL DBG_INFO +#include + +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); diff --git a/components/drivers/thermal/thermal.c b/components/drivers/thermal/thermal.c index 7a5016dabc..dc87ec87d0 100644 --- a/components/drivers/thermal/thermal.c +++ b/components/drivers/thermal/thermal.c @@ -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); } diff --git a/components/drivers/thermal/thermal_dm.h b/components/drivers/thermal/thermal_dm.h index 15d3314a58..cc8df32d3f 100644 --- a/components/drivers/thermal/thermal_dm.h +++ b/components/drivers/thermal/thermal_dm.h @@ -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__ */ diff --git a/documentation/6.components/device-driver/INDEX.md b/documentation/6.components/device-driver/INDEX.md index 0994df23f7..a2659b4449 100644 --- a/documentation/6.components/device-driver/INDEX.md +++ b/documentation/6.components/device-driver/INDEX.md @@ -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 diff --git a/documentation/6.components/device-driver/dvfs/dvfs.md b/documentation/6.components/device-driver/dvfs/dvfs.md new file mode 100644 index 0000000000..d6c667c57b --- /dev/null +++ b/documentation/6.components/device-driver/dvfs/dvfs.md @@ -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 +dvfs set_governor +dvfs set_frequency +``` + +**`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 **``** 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`** diff --git a/documentation/6.components/device-driver/thermal/thermal.md b/documentation/6.components/device-driver/thermal/thermal.md index 148dc1395a..4a679223b5 100755 --- a/documentation/6.components/device-driver/thermal/thermal.md +++ b/documentation/6.components/device-driver/thermal/thermal.md @@ -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 diff --git a/include/rtdef.h b/include/rtdef.h index db711aadff..ab97aeb917 100644 --- a/include/rtdef.h +++ b/include/rtdef.h @@ -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