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