From 31114ae443a5a608e16bc3692761af04ed9f4c71 Mon Sep 17 00:00:00 2001 From: GuEe-GUI <2991707448@qq.com> Date: Mon, 15 Jun 2026 11:21:24 +0800 Subject: [PATCH 1/3] [dm][dvfs] support Dynamic Voltage and Frequency Scaling (DVFS) 1. Support DVFS and finsh cmd, there are 6 governors: - conservative - freedom - performance - powersave - schedutil 2. Support DVFS for SCMI. 3. Port the Cooling device for DVFS. 4. Port the PM with DVFS. Signed-off-by: GuEe-GUI <2991707448@qq.com> --- components/drivers/Kconfig | 1 + components/drivers/dvfs/Kconfig | 51 + components/drivers/dvfs/SConscript | 38 + components/drivers/dvfs/dvfs-ofw.c | 186 ++++ components/drivers/dvfs/dvfs-scmi-cpufreq.c | 232 +++++ components/drivers/dvfs/dvfs.c | 958 ++++++++++++++++++ components/drivers/dvfs/dvfs_cmd.c | 270 +++++ components/drivers/dvfs/dvfs_cpu.c | 48 + components/drivers/dvfs/dvfs_event.c | 246 +++++ components/drivers/dvfs/dvfs_governor.c | 141 +++ components/drivers/dvfs/dvfs_idle.c | 381 +++++++ components/drivers/dvfs/dvfs_opp.c | 336 ++++++ components/drivers/dvfs/dvfs_pm.c | 107 ++ components/drivers/dvfs/governor/SConscript | 10 + .../drivers/dvfs/governor/conservative.c | 329 ++++++ components/drivers/dvfs/governor/freedom.c | 106 ++ components/drivers/dvfs/governor/ondemand.c | 317 ++++++ .../drivers/dvfs/governor/performance.c | 115 +++ components/drivers/dvfs/governor/powersave.c | 115 +++ components/drivers/dvfs/governor/schedutil.c | 286 ++++++ components/drivers/include/drivers/dvfs.h | 353 +++++++ components/drivers/include/drivers/thermal.h | 4 + components/drivers/include/rtdevice.h | 4 + components/drivers/thermal/Kconfig | 6 + components/drivers/thermal/SConscript | 3 + .../drivers/thermal/thermal-cool-dvfs.c | 171 ++++ components/drivers/thermal/thermal.c | 82 +- components/drivers/thermal/thermal_dm.h | 2 + .../6.components/device-driver/INDEX.md | 1 + .../6.components/device-driver/dvfs/dvfs.md | 178 ++++ .../device-driver/thermal/thermal.md | 3 +- include/rtdef.h | 3 + 32 files changed, 5073 insertions(+), 10 deletions(-) create mode 100644 components/drivers/dvfs/Kconfig create mode 100644 components/drivers/dvfs/SConscript create mode 100644 components/drivers/dvfs/dvfs-ofw.c create mode 100644 components/drivers/dvfs/dvfs-scmi-cpufreq.c create mode 100644 components/drivers/dvfs/dvfs.c create mode 100644 components/drivers/dvfs/dvfs_cmd.c create mode 100644 components/drivers/dvfs/dvfs_cpu.c create mode 100644 components/drivers/dvfs/dvfs_event.c create mode 100644 components/drivers/dvfs/dvfs_governor.c create mode 100644 components/drivers/dvfs/dvfs_idle.c create mode 100644 components/drivers/dvfs/dvfs_opp.c create mode 100644 components/drivers/dvfs/dvfs_pm.c create mode 100644 components/drivers/dvfs/governor/SConscript create mode 100644 components/drivers/dvfs/governor/conservative.c create mode 100644 components/drivers/dvfs/governor/freedom.c create mode 100644 components/drivers/dvfs/governor/ondemand.c create mode 100644 components/drivers/dvfs/governor/performance.c create mode 100644 components/drivers/dvfs/governor/powersave.c create mode 100644 components/drivers/dvfs/governor/schedutil.c create mode 100644 components/drivers/include/drivers/dvfs.h create mode 100644 components/drivers/thermal/thermal-cool-dvfs.c create mode 100644 documentation/6.components/device-driver/dvfs/dvfs.md diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig index f613302d966..efe7431a553 100755 --- a/components/drivers/Kconfig +++ b/components/drivers/Kconfig @@ -34,6 +34,7 @@ rsource "scsi/Kconfig" rsource "ufs/Kconfig" rsource "firmware/Kconfig" rsource "hwcache/Kconfig" +rsource "dvfs/Kconfig" rsource "regulator/Kconfig" rsource "reset/Kconfig" rsource "pmdomain/Kconfig" diff --git a/components/drivers/dvfs/Kconfig b/components/drivers/dvfs/Kconfig new file mode 100644 index 00000000000..d35edccf4b6 --- /dev/null +++ b/components/drivers/dvfs/Kconfig @@ -0,0 +1,51 @@ +menuconfig RT_USING_DVFS + bool "Using Dynamic Voltage and Frequency Scaling (DVFS)" + depends on RT_USING_DM + depends on RT_USING_CLK + depends on RT_USING_REGULATOR + select RT_USING_ADT + select RT_USING_ADT_REF + select RT_USING_ADT_BITMAP + select RT_USING_DEVICE_IPC + select RT_USING_SYSTEM_WORKQUEUE + default n + +config RT_USING_DVFS_EVENT + bool "Event" + depends on RT_USING_DVFS + default y + +config RT_USING_DVFS_OPP_RETRY_MAX + int "OPP retry max time" + depends on RT_USING_DVFS + default 10 + +if RT_USING_DVFS && RT_USING_DVFS_EVENT + comment "DVFS Event Drivers" +endif + +if RT_USING_DVFS + osource "$(SOC_DM_DVFS_EVENT_DIR)/Kconfig" +endif + +if RT_USING_DVFS + comment "DVFS CPUfreq Drivers" +endif + +config RT_DVFS_SCMI_CPUFREQ + bool "SCMI CPUfreq driver" + depends on RT_USING_DVFS + depends on RT_FIRMWARE_ARM_SCMI + default n + +if RT_USING_DVFS + osource "$(SOC_DM_DVFS_CPUFREQ_DIR)/Kconfig" +endif + +if RT_USING_DVFS + comment "DVFS Devfreq Drivers" +endif + +if RT_USING_DVFS + osource "$(SOC_DM_DVFS_DEVFREQ_DIR)/Kconfig" +endif diff --git a/components/drivers/dvfs/SConscript b/components/drivers/dvfs/SConscript new file mode 100644 index 00000000000..befe1d579fb --- /dev/null +++ b/components/drivers/dvfs/SConscript @@ -0,0 +1,38 @@ +from building import * + +group = [] +objs = [] + +if not GetDepend(['RT_USING_DVFS']): + Return('group') + +cwd = GetCurrentDir() +list = os.listdir(cwd) +CPPPATH = [cwd + '/../include'] + +src = ['dvfs.c', 'dvfs_cpu.c', 'dvfs_governor.c', 'dvfs_idle.c', 'dvfs_opp.c'] + +if GetDepend(['RT_USING_CONSOLE', 'RT_USING_MSH']): + src += ['dvfs_cmd.c'] + +if GetDepend(['RT_USING_DVFS_EVENT']): + src += ['dvfs_event.c'] + +if GetDepend(['RT_USING_PM']): + src += ['dvfs_pm.c'] + +if GetDepend(['RT_USING_OFW']): + src += ['dvfs-ofw.c'] + +if GetDepend(['RT_DVFS_SCMI_CPUFREQ']): + src += ['dvfs-scmi-cpufreq.c'] + +group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) + +for d in list: + path = os.path.join(cwd, d) + if os.path.isfile(os.path.join(path, 'SConscript')): + objs = objs + SConscript(os.path.join(d, 'SConscript')) +objs = objs + group + +Return('group') diff --git a/components/drivers/dvfs/dvfs-ofw.c b/components/drivers/dvfs/dvfs-ofw.c new file mode 100644 index 00000000000..5676106ce86 --- /dev/null +++ b/components/drivers/dvfs/dvfs-ofw.c @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.ofw" +#define DBG_LVL DBG_INFO +#include + +static rt_err_t ofw_dvfs_setup(struct rt_platform_device *pdev, + struct rt_dvfs_devfreq *devfreq, rt_bool_t is_cpu) +{ + struct rt_device *dev = &pdev->parent; + struct rt_dvfs_scaling *scaling = rt_dvfs_devfreq_to_scaling(devfreq); + rt_err_t err; + + if (!devfreq) + { + return -RT_EINVAL; + } + + scaling->dev = dev; + scaling->ops = &rt_dvfs_devfreq_ops; + + if (!scaling->clk) + { + scaling->clk = rt_clk_get_by_index(dev, 0); + if (rt_is_err(scaling->clk)) + { + LOG_E("Get clock failed"); + return rt_ptr_err(scaling->clk); + } + } + + if (!scaling->supply) + { + if (is_cpu) + { + scaling->supply = rt_regulator_get(dev, "cpu"); + } + else + { + scaling->supply = rt_regulator_get(dev, "dev"); + if (rt_is_err(scaling->supply)) + { + scaling->supply = rt_regulator_get(dev, "mem"); + } + } + + if (rt_is_err(scaling->supply)) + { + scaling->supply = RT_NULL; + } + } + + if (!scaling->transition_latency) + { + scaling->transition_latency = 1000000; + } + + if (!scaling->retry_delay) + { + scaling->retry_delay = 100000; + } + +#ifdef RT_USING_DVFS_EVENT + if (!is_cpu && !devfreq->ev && dev->ofw_node) + { + devfreq->ev = rt_dvfs_event_get(dev, "devfreq-event", 0); + if (rt_is_err_or_null(devfreq->ev)) + { + devfreq->ev = RT_NULL; + LOG_D("%s: no devfreq-event", rt_dm_dev_get_name(dev)); + } + } +#endif + + err = rt_dvfs_devfreq_register(devfreq); + if (err) + { + LOG_E("Register devfreq failed: %s", rt_strerror(err)); + goto fail; + } + + err = rt_dvfs_scaling_set_governor(scaling, RT_DVFS_GOVERNOR_TYPE_ONDEMAND); + if (err) + { + LOG_W("Set ondemand governor failed: %s", rt_strerror(err)); + err = rt_dvfs_scaling_set_governor(scaling, RT_DVFS_GOVERNOR_TYPE_PERFORMANCE); + } + + LOG_D("Devfreq registered for %s, freq range: %lu-%lu Hz", + rt_dm_dev_get_name(dev), scaling->min_freq, scaling->max_freq); + + return RT_EOK; + +fail: + if (scaling->clk) + { + rt_clk_put(scaling->clk); + scaling->clk = RT_NULL; + } + if (scaling->supply) + { + rt_regulator_put(scaling->supply); + scaling->supply = RT_NULL; + } + return err; +} + +static rt_err_t ofw_cpufreq_probe(struct rt_platform_device *pdev) +{ + struct rt_dvfs_cpufreq *cpufreq = pdev->priv; + + if (!cpufreq) + { + LOG_E("No cpufreq data"); + return -RT_EINVAL; + } + + return ofw_dvfs_setup(pdev, rt_dvfs_cpufreq_to_devfreq(cpufreq), RT_TRUE); +} + +static rt_err_t ofw_cpufreq_remove(struct rt_platform_device *pdev) +{ + struct rt_dvfs_cpufreq *cpufreq = pdev->priv; + + if (cpufreq) + { + rt_dvfs_cpufreq_unregister(cpufreq); + pdev->priv = RT_NULL; + } + + return RT_EOK; +} + +static rt_err_t ofw_devfreq_probe(struct rt_platform_device *pdev) +{ + struct rt_dvfs_devfreq *devfreq = pdev->priv; + + if (!devfreq) + { + LOG_E("No devfreq data"); + return -RT_EINVAL; + } + + return ofw_dvfs_setup(pdev, devfreq, RT_FALSE); +} + +static rt_err_t ofw_devfreq_remove(struct rt_platform_device *pdev) +{ + struct rt_dvfs_devfreq *devfreq = pdev->priv; + + if (devfreq) + { + rt_dvfs_devfreq_unregister(devfreq); + pdev->priv = RT_NULL; + } + + return RT_EOK; +} + +static struct rt_platform_driver ofw_cpufreq_driver = +{ + .name = "ofw-cpufreq", + .probe = ofw_cpufreq_probe, + .remove = ofw_cpufreq_remove, +}; +RT_PLATFORM_DRIVER_EXPORT(ofw_cpufreq_driver); + +static struct rt_platform_driver ofw_devfreq_driver = +{ + .name = "ofw-devfreq", + .probe = ofw_devfreq_probe, + .remove = ofw_devfreq_remove, +}; +RT_PLATFORM_DRIVER_EXPORT(ofw_devfreq_driver); diff --git a/components/drivers/dvfs/dvfs-scmi-cpufreq.c b/components/drivers/dvfs/dvfs-scmi-cpufreq.c new file mode 100644 index 00000000000..94521513a8c --- /dev/null +++ b/components/drivers/dvfs/dvfs-scmi-cpufreq.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include +#include + +#define DBG_TAG "dvfs.scmi.cpufreq" +#define DBG_LVL DBG_INFO +#include + +struct scmi_cpufreq +{ + struct rt_scmi_device *sdev; + int domain_id; + struct rt_dvfs_cpufreq cpufreq; +}; + +static rt_err_t scmi_perf_level_set(struct scmi_cpufreq *scmi_cpufreq, rt_uint64_t level) +{ + struct scmi_perf_level_set_in in = + { + .flags = rt_cpu_to_le32(0), + .domain = rt_cpu_to_le32(scmi_cpufreq->domain_id), + .level_lsb = rt_cpu_to_le32((rt_uint32_t)level), + .level_msb = rt_cpu_to_le32((rt_uint32_t)(level >> 32)), + }; + struct scmi_perf_level_set_out out; + struct rt_scmi_msg msg = RT_SCMI_MSG_IN_OUT(SCMI_PERF_LEVEL_SET, &in, &out); + + return rt_scmi_process_msg(scmi_cpufreq->sdev, &msg); +} + +static rt_err_t scmi_cpufreq_set_opp(struct rt_dvfs_scaling *scaling, struct rt_dvfs_opp *opp) +{ + struct rt_dvfs_devfreq *devfreq = rt_container_of(scaling, struct rt_dvfs_devfreq, parent); + struct scmi_cpufreq *scmi_cpufreq = rt_container_of(devfreq, struct scmi_cpufreq, cpufreq.parent); + + if (!opp) + { + return -RT_EINVAL; + } + + return scmi_perf_level_set(scmi_cpufreq, opp->freq); +} + +static rt_err_t scmi_cpufreq_add_opps(struct scmi_cpufreq *scmi_cpufreq) +{ + struct rt_dvfs_scaling *dvfs = rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq); + struct scmi_perf_describe_levels_in in = + { + .domain = rt_cpu_to_le32(scmi_cpufreq->domain_id), + .level_index = rt_cpu_to_le32(0), + }; + struct scmi_perf_describe_levels_out *out; + rt_size_t out_size = sizeof(*out) + sizeof(struct scmi_perf_level) * SCMI_MAX_NUM_RATES; + rt_err_t err; + rt_uint32_t i, num_levels; + + out = rt_calloc(1, out_size); + if (!out) + { + return -RT_ENOMEM; + } + + do + { + struct rt_scmi_msg msg = RT_SCMI_MSG_RAW(SCMI_PERF_DESCRIBE_LEVELS, + &in, sizeof(in), out, out_size); + + err = rt_scmi_process_msg(scmi_cpufreq->sdev, &msg); + if (err) + { + break; + } + + num_levels = rt_le32_to_cpu(out->num_levels) & 0xfff; + + for (i = 0; i < num_levels; ++i) + { + rt_uint64_t hz = SCMI_PERF_LEVEL_TO_U64(out->level[i]); + struct rt_dvfs_opp *opp; + + if (!hz) + { + continue; + } + + opp = rt_dvfs_scaling_add_opp(dvfs, (rt_ubase_t)hz, 0); + if (opp) + { + opp->available = RT_TRUE; + opp->power = rt_le32_to_cpu(out->level[i].power) / 1000; + } + } + + in.level_index = rt_cpu_to_le32(rt_le32_to_cpu(in.level_index) + num_levels); + } while ((rt_le32_to_cpu(out->num_levels) >> 16) > 0); + + rt_free(out); + + return err; +} + +static struct rt_dvfs_scaling_ops scmi_cpufreq_ops = +{ + .set_opp = scmi_cpufreq_set_opp, +}; + +static rt_err_t scmi_cpufreq_probe(struct rt_scmi_device *sdev) +{ + struct scmi_cpufreq *scmi_cpufreq; + struct scmi_perf_attributes attr; + struct rt_scmi_msg msg = RT_SCMI_MSG_OUT(SCMI_COM_MSG_ATTRIBUTES, &attr); + rt_err_t err; + int domain; + + if ((err = rt_scmi_process_msg(sdev, &msg))) + { + LOG_E("Get SCMI perf attributes failed: %s", rt_strerror(err)); + return err; + } + + if (!(scmi_cpufreq = rt_calloc(1, sizeof(*scmi_cpufreq)))) + { + return -RT_ENOMEM; + } + + scmi_cpufreq->sdev = sdev; + + for (domain = 0; domain < rt_le16_to_cpu(attr.num_domains); ++domain) + { + struct scmi_perf_domain_attr_in domain_in = + { + .domain = rt_cpu_to_le32(domain), + }; + struct scmi_perf_domain_attr_out domain_out; + struct rt_scmi_msg domain_msg = RT_SCMI_MSG_IN_OUT(SCMI_PERF_DOMAIN_ATTRIBUTES, + &domain_in, &domain_out); + + rt_memset(&scmi_cpufreq->cpufreq, 0, sizeof(scmi_cpufreq->cpufreq)); + + if ((err = rt_scmi_process_msg(sdev, &domain_msg))) + { + continue; + } + + scmi_cpufreq->domain_id = domain; + rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)->ops = &scmi_cpufreq_ops; + rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)->dev = &sdev->parent; + scmi_cpufreq->cpufreq.master_cpu = 0; + + if ((err = scmi_cpufreq_add_opps(scmi_cpufreq))) + { + LOG_W("Domain %d: add OPPs failed: %s", domain, rt_strerror(err)); + rt_dvfs_scaling_remove_opp_all(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)); + continue; + } + + if ((err = rt_dvfs_cpufreq_register(&scmi_cpufreq->cpufreq))) + { + LOG_E("Domain %d: register cpufreq failed: %s", domain, rt_strerror(err)); + rt_dvfs_scaling_remove_opp_all(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)); + continue; + } + + err = rt_dvfs_scaling_set_governor(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq), + RT_DVFS_GOVERNOR_TYPE_ONDEMAND); + if (err) + { + rt_dvfs_scaling_set_governor(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq), + RT_DVFS_GOVERNOR_TYPE_PERFORMANCE); + } + + LOG_D("SCMI cpufreq domain %d registered, freq range: %lu-%lu Hz", + domain, + rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)->min_freq, + rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)->max_freq); + + sdev->parent.user_data = scmi_cpufreq; + return RT_EOK; + } + + rt_free(scmi_cpufreq); + + return -RT_ENOSYS; +} + +static rt_err_t scmi_cpufreq_remove(struct rt_scmi_device *sdev) +{ + struct scmi_cpufreq *scmi_cpufreq = sdev->parent.user_data; + + if (scmi_cpufreq) + { + rt_dvfs_cpufreq_unregister(&scmi_cpufreq->cpufreq); + rt_dvfs_scaling_remove_opp_all(rt_dvfs_cpufreq_to_scaling(&scmi_cpufreq->cpufreq)); + rt_free(scmi_cpufreq); + sdev->parent.user_data = RT_NULL; + } + + return RT_EOK; +} + +static const struct rt_scmi_device_id scmi_cpufreq_ids[] = +{ + { SCMI_PROTOCOL_ID_PERF, "cpufreq" }, + { 0, RT_NULL }, +}; + +static struct rt_scmi_driver scmi_cpufreq_driver = +{ + .name = "scmi-cpufreq", + .ids = scmi_cpufreq_ids, + .probe = scmi_cpufreq_probe, + .remove = scmi_cpufreq_remove, +}; + +static int scmi_cpufreq_init(void) +{ + rt_scmi_driver_register(&scmi_cpufreq_driver); + + return 0; +} +INIT_DEVICE_EXPORT(scmi_cpufreq_init); diff --git a/components/drivers/dvfs/dvfs.c b/components/drivers/dvfs/dvfs.c new file mode 100644 index 00000000000..c3544f545d8 --- /dev/null +++ b/components/drivers/dvfs/dvfs.c @@ -0,0 +1,958 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "rtdm.dvfs" +#define DBG_LVL DBG_INFO +#include + +static RT_DEFINE_SPINLOCK(_dvfs_scaling_lock); + +#ifdef RT_USING_OFW +static rt_err_t dvfs_ofw_parse_opp(struct rt_dvfs_scaling *dvfs) +{ + struct rt_dvfs_opp *opp; + struct rt_ofw_node *opp_np, *opp_child_np; + + if (!dvfs->dev->ofw_node) + { + return RT_EOK; + } + + opp_np = rt_ofw_parse_phandle(dvfs->dev->ofw_node, "operating-points-v2", 0); + + if (!opp_np) + { + return RT_EOK; + } + + rt_ofw_foreach_child_node(opp_np, opp_child_np) + { + rt_uint64_t hz = 0; + rt_uint32_t uvolt[3] = {0}, uvolt_nr = 0; + + if (rt_ofw_prop_read_u64(opp_child_np, "opp-hz", &hz)) + { + continue; + } + + uvolt_nr = rt_ofw_prop_read_u32_array_index(opp_child_np, + "opp-microvolt", 0, RT_ARRAY_SIZE(uvolt), uvolt); + + if ((int)uvolt_nr < 0) + { + /* If previous voltage is unknown, assume 0 to ensure a voltage ramp-up */ + uvolt[0] = 0; + } + + if (!(opp = rt_dvfs_scaling_add_opp(dvfs, (rt_ubase_t)hz, (rt_ubase_t)uvolt[0]))) + { + continue; + } + + if (dvfs->ops && dvfs->ops->parse_opp) + { + rt_err_t err = dvfs->ops->parse_opp(dvfs, opp, (void *)opp_child_np); + + if (err) + { + LOG_W("%s: Parse OPP %s error = %s", rt_dm_dev_get_name(dvfs->dev), + rt_ofw_node_full_name(opp_child_np), rt_strerror(err)); + } + } + } + + dvfs->opp_table->share = rt_ofw_prop_read_bool(opp_np, "opp-shared"); + dvfs->opp_table->priv = dvfs->opp_table->priv ? : opp_np; /* Default value, DVFS unused */ + + return RT_EOK; +} +#endif /* RT_USING_OFW */ + +static void dvfs_gov_params_init_default(struct rt_dvfs_governor_params *params) +{ + params->sampling_rate_ms = 1000; + params->up_threshold = 80; + params->down_differential = 20; + params->sampling_down_factor = 1; + params->freq_step = 5; + params->ignore_nice_load = RT_FALSE; + params->powersave_bias = 0; +} + +static rt_err_t dvfs_scaling_init_frequency(struct rt_dvfs_scaling *dvfs) +{ + struct rt_dvfs_opp *opp; + + if (!dvfs->opp_table || rt_list_isempty(&dvfs->opp_table->opp_nodes)) + { + return RT_EOK; + } + + if (dvfs->suspend_freq && + (opp = rt_dvfs_scaling_find_opp(dvfs, dvfs->suspend_freq))) + { + return rt_dvfs_scaling_apply_opp(dvfs, opp); + } + + opp = rt_dvfs_scaling_find_ceil_opp(dvfs, dvfs->max_freq); + if (!opp) + { + opp = rt_list_entry(dvfs->opp_table->opp_nodes.next, struct rt_dvfs_opp, list); + } + + return rt_dvfs_scaling_apply_opp(dvfs, opp); +} + +rt_err_t rt_dvfs_scaling_register(struct rt_dvfs_scaling *dvfs) +{ + rt_err_t err = RT_EOK; + + if (!dvfs || !dvfs->dev || !dvfs->ops) + { + return -RT_EINVAL; + } + + RT_ASSERT(dvfs->ops->set_opp != RT_NULL); + + if (!dvfs->gov_params.sampling_rate_ms) + { + dvfs_gov_params_init_default(&dvfs->gov_params); + } + +#ifdef RT_USING_OFW + if ((err = dvfs_ofw_parse_opp(dvfs))) + { + return err; + } +#endif /* RT_USING_OFW */ + + if (dvfs->opp_table && !dvfs->cur_freq) + { + err = dvfs_scaling_init_frequency(dvfs); + if (err) + { + LOG_W("%s: init frequency error = %s", + rt_dm_dev_get_name(dvfs->dev), rt_strerror(err)); + } + } + + rt_dm_dev_bind_fwdata(dvfs->dev, RT_NULL, dvfs); + dvfs->dev->dvfs_scaling = dvfs; + + return RT_EOK; +} + +rt_err_t rt_dvfs_scaling_unregister(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + if (dvfs->gov) + { + if (dvfs->gov->stop) + { + dvfs->gov->stop(dvfs); + } + + rt_dvfs_governor_put(dvfs->gov); + dvfs->gov = RT_NULL; + } + + dvfs->gov_data = RT_NULL; + dvfs->load_update = RT_NULL; + + dvfs->dev->dvfs_scaling = RT_NULL; + rt_dm_dev_unbind_fwdata(dvfs->dev, RT_NULL); + + /* Free the OPP by Drivers */ + + return RT_EOK; +} + +void rt_dvfs_scaling_enter(struct rt_dvfs_scaling *dvfs) +{ + if (dvfs) + { + rt_spin_lock(&_dvfs_scaling_lock); + } +} + +void rt_dvfs_scaling_leave(struct rt_dvfs_scaling *dvfs) +{ + if (dvfs) + { + rt_spin_unlock(&_dvfs_scaling_lock); + } +} + +void rt_dvfs_ns_sleep(rt_uint32_t ns) +{ + rt_uint32_t us; + + if (!ns) + { + return; + } + + us = (ns + 999) / 1000; + + if (us < 1000 || rt_hw_interrupt_is_disabled()) + { + rt_hw_us_delay(us); + } + else + { + rt_thread_mdelay(us / 1000); + } +} + +rt_err_t rt_dvfs_scaling_suspend(struct rt_dvfs_scaling *dvfs) +{ + rt_err_t err = RT_EOK; + + if (!dvfs) + { + return -RT_EINVAL; + } + + if (dvfs->gov && dvfs->gov->suspend) + { + if ((err = dvfs->gov->suspend(dvfs))) + { + LOG_W("%s: governor suspend error = %s", + rt_dm_dev_get_name(dvfs->dev), rt_strerror(err)); + } + } + + if (dvfs->suspend_freq) + { + if ((err = rt_dvfs_scaling_set_frequency(dvfs, dvfs->suspend_freq))) + { + LOG_W("%s: set suspend frequency(%lu) error = %s", + rt_dm_dev_get_name(dvfs->dev), dvfs->suspend_freq, rt_strerror(err)); + } + } + + if (dvfs->ops && dvfs->ops->suspend) + { + rt_dvfs_scaling_enter(dvfs); + err = dvfs->ops->suspend(dvfs); + rt_dvfs_scaling_leave(dvfs); + } + + return err; +} + +rt_err_t rt_dvfs_scaling_resume(struct rt_dvfs_scaling *dvfs) +{ + rt_err_t err = RT_EOK; + + if (!dvfs) + { + return -RT_EINVAL; + } + + if (dvfs->ops && dvfs->ops->resume) + { + rt_dvfs_scaling_enter(dvfs); + err = dvfs->ops->resume(dvfs); + rt_dvfs_scaling_leave(dvfs); + } + + if (dvfs->gov && dvfs->gov->resume) + { + rt_err_t gov_err = dvfs->gov->resume(dvfs); + + if (gov_err && !err) + { + err = gov_err; + } + } + + return err; +} + +rt_err_t rt_dvfs_scaling_set_governor(struct rt_dvfs_scaling *dvfs, rt_uint32_t governor) +{ + rt_err_t err = RT_EOK; + struct rt_dvfs_governor *gov; + + if (!dvfs) + { + return -RT_EINVAL; + } + + if (!(gov = rt_dvfs_governor_get(governor))) + { + return -RT_ENOSYS; + } + + if (dvfs->gov) + { + if (dvfs->gov->stop) + { + if ((err = dvfs->gov->stop(dvfs))) + { + rt_dvfs_governor_put(gov); + return err; + } + } + + rt_dvfs_governor_put(dvfs->gov); + } + + dvfs->gov = gov; + + if (dvfs->gov->start) + { + if ((err = dvfs->gov->start(dvfs))) + { + rt_dvfs_governor_put(dvfs->gov); + dvfs->gov = RT_NULL; + } + } + + return err; +} + +rt_err_t rt_dvfs_scaling_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t frequency) +{ + rt_err_t err; + struct rt_dvfs_opp *opp = RT_NULL; + + if (!dvfs || !dvfs->opp_table) + { + return -RT_EINVAL; + } + + if (dvfs->min_freq && frequency < dvfs->min_freq) + { + frequency = dvfs->min_freq; + } + if (dvfs->max_freq && frequency > dvfs->max_freq) + { + frequency = dvfs->max_freq; + } + + if (!(opp = rt_dvfs_scaling_find_opp(dvfs, frequency))) + { + if (!(opp = rt_dvfs_scaling_find_floor_opp(dvfs, frequency))) + { + opp = rt_dvfs_scaling_find_ceil_opp(dvfs, frequency); + } + } + + if (!opp || !opp->available) + { + return -RT_ENOENT; + } + + err = rt_dvfs_scaling_apply_opp(dvfs, opp); + + return err; +} + +static rt_err_t dvfs_regulator_set_voltage_retry(struct rt_regulator *supply, + rt_ubase_t uvolt, rt_uint32_t retry_ns) +{ + for (int i = 0; i < RT_USING_DVFS_OPP_RETRY_MAX; ++i) + { + rt_err_t err = rt_regulator_set_voltage(supply, uvolt, uvolt); + + if (err == -RT_EBUSY) + { + rt_dvfs_ns_sleep(retry_ns); + continue; + } + + return err; + } + + return -RT_EBUSY; +} + +static rt_err_t dvfs_clk_set_rate_retry(struct rt_clk *clk, + rt_ubase_t rate, rt_uint32_t retry_ns) +{ + for (int i = 0; i < RT_USING_DVFS_OPP_RETRY_MAX; ++i) + { + rt_err_t err = rt_clk_set_rate(clk, rate); + + if (err == -RT_EBUSY) + { + rt_dvfs_ns_sleep(retry_ns); + continue; + } + + return err; + } + + return -RT_EBUSY; +} + +rt_err_t rt_dvfs_scaling_apply_opp(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp) +{ + rt_err_t err; + + if (!dvfs || !opp || !dvfs->ops || !dvfs->ops->set_opp || !dvfs->opp_table) + { + return -RT_EINVAL; + } + + if (!opp->available) + { + return -RT_EINVAL; + } + + if ((dvfs->min_freq && opp->freq < dvfs->min_freq) || + (dvfs->max_freq && opp->freq > dvfs->max_freq)) + { + return -RT_EINVAL; + } + + if (dvfs->ops->set_opp) + { + err = -RT_EBUSY; + + for (int tries = 0; tries < RT_USING_DVFS_OPP_RETRY_MAX; ++tries) + { + err = dvfs->ops->set_opp(dvfs, opp); + + if (err != -RT_EBUSY) + { + break; + } + + rt_dvfs_ns_sleep(dvfs->retry_delay); + } + + if (err) + { + return err; + } + + rt_dvfs_ns_sleep(dvfs->transition_latency); + } + else + { + rt_uint32_t retry_delay = dvfs->retry_delay; + rt_ubase_t old_uvolt, old_freq, new_uvolt, new_freq; + struct rt_dvfs_opp *old = dvfs->opp_table->current_opp; + + /* If previous voltage is unknown, assume 0 to ensure a voltage ramp-up */ + old_uvolt = old ? old->uvolt : 0; + old_freq = dvfs->cur_freq; + + new_uvolt = opp->uvolt; + new_freq = opp->freq; + + if (new_freq > old_freq) + { + /* Scale up: raise voltage first, then increase frequency */ + if (dvfs->supply && new_uvolt > old_uvolt) + { + if ((err = dvfs_regulator_set_voltage_retry(dvfs->supply, new_uvolt, retry_delay))) + { + return err; + } + } + + if (dvfs->clk) + { + if ((err = dvfs_clk_set_rate_retry(dvfs->clk, new_freq, retry_delay))) + { + return err; + } + } + } + else if (new_freq < old_freq) + { + /* Scale down: lower frequency first, then lower voltage */ + if (dvfs->clk) + { + if ((err = dvfs_clk_set_rate_retry(dvfs->clk, new_freq, retry_delay))) + { + return err; + } + } + + if (dvfs->supply && new_uvolt < old_uvolt) + { + if ((err = dvfs_regulator_set_voltage_retry(dvfs->supply, new_uvolt, retry_delay))) + { + return err; + } + } + } + else + { + /* Frequency unchanged: adjust voltage only if needed */ + if (dvfs->supply && new_uvolt != old_uvolt) + { + if ((err = dvfs_regulator_set_voltage_retry(dvfs->supply, new_uvolt, retry_delay))) + { + return err; + } + } + } + + rt_dvfs_ns_sleep(dvfs->transition_latency); + } + + rt_dvfs_scaling_enter(dvfs); + dvfs->cur_freq = opp->freq; + dvfs->opp_table->current_opp = opp; + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +/* CPU Load Monitoring */ +#ifdef RT_USING_IDLE_HOOK + +static rt_uint64_t _idle_tick_total = 0; +static rt_tick_t _idle_start_tick = 0; +static rt_bool_t _in_idle = RT_FALSE; + +static void dvfs_idle_hook(void) +{ + rt_base_t level; + + level = rt_hw_interrupt_disable(); + + if (!_in_idle) + { + _in_idle = RT_TRUE; + _idle_start_tick = rt_tick_get(); + } + + rt_hw_interrupt_enable(level); +} + +static int dvfs_load_init(void) +{ + /* Install idle hook */ + rt_thread_idle_sethook(dvfs_idle_hook); + + return 0; +} +INIT_DEVICE_EXPORT(dvfs_load_init); + +void rt_dvfs_load_update(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return; + } + + if (dvfs->load_update) + { + dvfs->load_update(dvfs); + } + else + { + rt_dvfs_cpu_load_update(&dvfs->cpu_load); + } +} + +void rt_dvfs_cpu_load_update(struct rt_dvfs_cpu_load *load) +{ + rt_tick_t now; + rt_base_t level; + + if (!load) + { + return; + } + + level = rt_hw_interrupt_disable(); + + now = rt_tick_get(); + + /* Exit idle state if in idle */ + if (_in_idle) + { + rt_uint64_t idle_ticks = now - _idle_start_tick; + _idle_tick_total += idle_ticks; + _in_idle = RT_FALSE; + } + + if (load->last_update == 0) + { + /* First update */ + load->last_update = now; + load->total_tick = 0; + load->idle_tick = 0; + load->load_percentage = 0; + } + else + { + rt_uint64_t total_elapsed; + rt_uint64_t idle_elapsed; + + /* Calculate total elapsed ticks */ + total_elapsed = now - load->last_update; + + /* Get idle ticks accumulated since last update */ + idle_elapsed = _idle_tick_total - load->idle_tick; + + /* Update counters */ + load->total_tick = total_elapsed; + load->idle_tick = _idle_tick_total; + load->last_update = now; + + /* Calculate load percentage */ + if (total_elapsed > 0) + { + rt_uint64_t busy_ticks = (idle_elapsed > total_elapsed) ? 0 : (total_elapsed - idle_elapsed); + load->load_percentage = (busy_ticks * 100) / total_elapsed; + + /* Clamp to 0-100 range */ + if (load->load_percentage > 100) + { + load->load_percentage = 100; + } + } + else + { + load->load_percentage = 0; + } + } + + rt_hw_interrupt_enable(level); +} + +#else /* RT_USING_IDLE_HOOK */ + +void rt_dvfs_cpu_load_update(struct rt_dvfs_cpu_load *load) +{ + rt_tick_t now; + + if (!load) + { + return; + } + + now = rt_tick_get(); + + if (load->last_update == 0) + { + load->last_update = now; + load->total_tick = 0; + load->idle_tick = 0; + load->load_percentage = 50; /* Default to medium load */ + } + else + { + /* Without idle hook, use default load estimation */ + load->total_tick = now - load->last_update; + load->last_update = now; + + /* Estimate load based on scheduler activity */ + /* This is a simple heuristic - actual load depends on scheduler */ + extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; + rt_uint32_t ready_count = 0; + rt_base_t level; + + level = rt_hw_interrupt_disable(); + for (int i = 0; i < RT_THREAD_PRIORITY_MAX; i++) + { + if (!rt_list_isempty(&rt_thread_priority_table[i])) + { + ready_count++; + } + } + rt_hw_interrupt_enable(level); + + /* Estimate: 1 ready thread = 50%, more threads = higher load */ + load->load_percentage = (ready_count > 5) ? 90 : (ready_count * 15 + 30); + if (load->load_percentage > 100) + { + load->load_percentage = 100; + } + } +} + +#endif /* RT_USING_IDLE_HOOK */ + +rt_uint32_t rt_dvfs_cpu_load_get(struct rt_dvfs_cpu_load *load) +{ + if (!load) + { + return 0; + } + + return load->load_percentage; +} + +/* Governor parameter management */ +rt_err_t rt_dvfs_governor_set_params(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_governor_params *params) +{ + if (!dvfs || !params) + { + return -RT_EINVAL; + } + + /* Validate parameters */ + if (params->up_threshold > 100 || params->down_differential > 100) + { + return -RT_EINVAL; + } + + if (params->sampling_rate_ms > 0 && params->sampling_rate_ms < 10) + { + LOG_W("Sampling rate too small, adjusting to 10ms"); + params->sampling_rate_ms = 10; + } + + rt_dvfs_scaling_enter(dvfs); + rt_memcpy(&dvfs->gov_params, params, sizeof(*params)); + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +rt_err_t rt_dvfs_governor_get_params(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_governor_params *params) +{ + if (!dvfs || !params) + { + return -RT_EINVAL; + } + + rt_dvfs_scaling_enter(dvfs); + rt_memcpy(params, &dvfs->gov_params, sizeof(*params)); + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +static rt_err_t dvfs_default_set_opp(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp) +{ + rt_err_t err; + rt_ubase_t old_freq, old_uvolt, new_freq, new_uvolt; + struct rt_dvfs_opp *old_opp; + + if (!dvfs || !opp) + { + return -RT_EINVAL; + } + + old_opp = dvfs->opp_table ? dvfs->opp_table->current_opp : RT_NULL; + old_freq = dvfs->cur_freq; + old_uvolt = old_opp ? old_opp->uvolt : 0; + new_freq = opp->freq; + new_uvolt = opp->uvolt; + + if (new_freq > old_freq) + { + if (dvfs->supply && new_uvolt > old_uvolt) + { + err = rt_regulator_set_voltage(dvfs->supply, new_uvolt, new_uvolt); + if (err) + { + return err; + } + + if (dvfs->transition_latency) + { + rt_dvfs_ns_sleep(dvfs->transition_latency); + } + } + + if (dvfs->clk) + { + err = rt_clk_set_rate(dvfs->clk, new_freq); + if (err) + { + if (dvfs->supply && new_uvolt > old_uvolt) + { + rt_regulator_set_voltage(dvfs->supply, old_uvolt, old_uvolt); + } + return err; + } + } + } + else if (new_freq < old_freq) + { + if (dvfs->clk) + { + err = rt_clk_set_rate(dvfs->clk, new_freq); + if (err) + { + return err; + } + } + + if (dvfs->supply && new_uvolt < old_uvolt) + { + err = rt_regulator_set_voltage(dvfs->supply, new_uvolt, new_uvolt); + if (err) + { + LOG_W("%s: set voltage %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), new_uvolt, rt_strerror(err)); + } + } + } + else if (dvfs->supply && new_uvolt != old_uvolt) + { + err = rt_regulator_set_voltage(dvfs->supply, new_uvolt, new_uvolt); + if (err) + { + return err; + } + } + + if (dvfs->transition_latency) + { + rt_dvfs_ns_sleep(dvfs->transition_latency); + } + + return RT_EOK; +} + +static rt_err_t dvfs_default_parse_opp(struct rt_dvfs_scaling *dvfs, + struct rt_dvfs_opp *opp, void *fw_np) +{ +#ifdef RT_USING_OFW + struct rt_ofw_node *opp_np = (struct rt_ofw_node *)fw_np; + rt_uint32_t power = 0; + + if (!opp || !opp_np) + { + return -RT_EINVAL; + } + + if (!rt_ofw_prop_read_u32(opp_np, "opp-microwatt", &power)) + { + opp->power = power / 1000; + } + + opp->available = RT_TRUE; + + if (rt_ofw_prop_read_bool(opp_np, "opp-suspend")) + { + dvfs->suspend_freq = opp->freq; + } +#else + RT_UNUSED(dvfs); + RT_UNUSED(opp); + RT_UNUSED(fw_np); +#endif + + return RT_EOK; +} + +struct rt_dvfs_scaling_ops rt_dvfs_devfreq_ops = +{ + .set_opp = dvfs_default_set_opp, + .parse_opp = dvfs_default_parse_opp, +}; + +static void devfreq_load_from_event(struct rt_dvfs_scaling *scaling) +{ + struct rt_dvfs_devfreq *devfreq = rt_container_of(scaling, struct rt_dvfs_devfreq, parent); + struct rt_dvfs_event_data evd; + rt_err_t err; + + if (!devfreq->ev) + { + return; + } + + if ((err = rt_dvfs_event_read(devfreq->ev, &evd))) + { + LOG_D("%s: read dvfs event error = %s", + rt_dm_dev_get_name(scaling->dev), rt_strerror(err)); + return; + } + + if (evd.total_count) + { + scaling->cpu_load.load_percentage = (rt_uint32_t)((evd.load_count * 100) / evd.total_count); + + if (scaling->cpu_load.load_percentage > 100) + { + scaling->cpu_load.load_percentage = 100; + } + } + else + { + scaling->cpu_load.load_percentage = 0; + } +} + +rt_err_t rt_dvfs_devfreq_register(struct rt_dvfs_devfreq *devfreq) +{ + rt_err_t err; + struct rt_dvfs_scaling *scaling; + + if (!devfreq) + { + return -RT_EINVAL; + } + + scaling = rt_dvfs_devfreq_to_scaling(devfreq); + + if (!scaling->load_update && devfreq->ev) + { + scaling->load_update = devfreq_load_from_event; + } + + if (devfreq->ev) + { + err = rt_dvfs_event_enable(devfreq->ev); + if (err) + { + LOG_W("%s: enable devfreq event error = %s", + rt_dm_dev_get_name(scaling->dev), rt_strerror(err)); + } + } + + err = rt_dvfs_scaling_register(scaling); + if (err) + { + if (devfreq->ev) + { + rt_dvfs_event_disable(devfreq->ev); + } + return err; + } + + LOG_D("Devfreq registered for device %s", rt_dm_dev_get_name(scaling->dev)); + + return RT_EOK; +} + +rt_err_t rt_dvfs_devfreq_unregister(struct rt_dvfs_devfreq *devfreq) +{ + rt_err_t err; + + if (!devfreq) + { + return -RT_EINVAL; + } + + err = rt_dvfs_scaling_unregister(rt_dvfs_devfreq_to_scaling(devfreq)); + + if (devfreq->ev) + { + rt_dvfs_event_disable(devfreq->ev); + rt_dvfs_event_put(devfreq->ev); + devfreq->ev = RT_NULL; + } + + return err; +} diff --git a/components/drivers/dvfs/dvfs_cmd.c b/components/drivers/dvfs/dvfs_cmd.c new file mode 100644 index 00000000000..3bdbb0a6130 --- /dev/null +++ b/components/drivers/dvfs/dvfs_cmd.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.cmd" +#define DBG_LVL DBG_INFO +#include + +#include +#include +#include +#include + +#define GOVERNOR_NAME_MAX sizeof(((struct rt_dvfs_governor *)RT_NULL)->name) + +static int list_dvfs(int argc, char**argv) +{ + rt_ubase_t level; + struct rt_object *obj; + struct rt_device *dev; + struct rt_dvfs_scaling *dvfs; + struct rt_object_information *info = rt_object_get_information(RT_Object_Class_Device); + + level = rt_hw_interrupt_disable(); + + rt_kprintf("%-*.s %-*.s %-*.s Frequency (Hz)\n", + RT_NAME_MAX, "Name", + RT_NAME_MAX, "Device", + GOVERNOR_NAME_MAX, "Governor"); + + rt_list_for_each_entry(obj, &info->object_list, list) + { + dev = rt_container_of(obj, struct rt_device, parent); + + if (!(dvfs = dev->dvfs_scaling)) + { + continue; + } + + rt_kprintf("%-*.s %-*.s %-*.s %lu\n", + RT_NAME_MAX, rt_dm_dev_get_name(dev), + RT_NAME_MAX, rt_dm_dev_get_name(dvfs->dev), + GOVERNOR_NAME_MAX, dvfs->gov ? dvfs->gov->name : "N/A", + dvfs->cur_freq); + } + + rt_hw_interrupt_enable(level); + + return 0; +} +MSH_CMD_EXPORT(list_dvfs, dump all of dvfs information); + +enum +{ + DVFS_OPT_DUMP = 1, + DVFS_OPT_SET_GOVERNOR, + DVFS_OPT_SET_FREQUENCY, +}; + +CMD_OPTIONS_STATEMENT(dvfs) + +static struct rt_dvfs_scaling *dvfs_scaling(const char *name) +{ + struct rt_device *dev = rt_device_find(name); + + if (!dev) + { + LOG_E("Device %s not found", name); + return RT_NULL; + } + + if (!dev->dvfs_scaling) + { + LOG_E("Device %s not supported dvfs", name); + return RT_NULL; + } + + return dev->dvfs_scaling; +} + +static int dvfs(int argc, char**argv) +{ + struct rt_dvfs_scaling *dvfs; + + if (argc < 2) + { + goto _usage; + } + + if (MSH_OPT_ID_GET(dvfs) == DVFS_OPT_DUMP) + { + rt_uint32_t opp_id = 0; + struct rt_dvfs_opp *opp; + + if (argc != 3 || !(dvfs = dvfs_scaling(argv[2]))) + { + goto _usage; + } + + rt_dvfs_scaling_enter(dvfs); + + rt_kprintf("name: %s\n", argv[2]); + rt_kprintf("device: %s\n", rt_dm_dev_get_name(dvfs->dev)); + rt_kprintf("governor: %s\n", dvfs->gov ? dvfs->gov->name : "N/A"); + rt_kprintf("min frequency: %lu Hz\n", dvfs->min_freq); + rt_kprintf("max frequency: %lu Hz\n", dvfs->max_freq); + rt_kprintf("current frequency: %lu Hz", dvfs->cur_freq); + if (dvfs->opp_table && dvfs->opp_table->current_opp) + { + rt_kprintf(" @ %lu uV", dvfs->opp_table->current_opp->uvolt); + } + rt_kprintf("\n"); + rt_kprintf("suspend frequency: %lu Hz\n", dvfs->suspend_freq); + +#ifdef RT_USING_DVFS_EVENT + if (dvfs->load_update) + { + struct rt_dvfs_devfreq *devfreq; + struct rt_dvfs_event_data evd = {0}; + + dvfs->load_update(dvfs); + rt_kprintf("event load: %u%%\n", dvfs->cpu_load.load_percentage); + + devfreq = rt_container_of(dvfs, struct rt_dvfs_devfreq, parent); + if (devfreq->ev && rt_dvfs_event_read(devfreq->ev, &evd) == RT_EOK && evd.total_count) + { + rt_uint64_t load_p = evd.load_count * 100000000ULL / evd.total_count; + rt_uint32_t load_int = (rt_uint32_t)(load_p / 1000000ULL); + rt_uint32_t load_frac = (rt_uint32_t)(load_p % 1000000ULL); + + rt_kprintf("event counters: %lu / %lu (%u.%06u%%)\n", + (unsigned long)evd.load_count, + (unsigned long)evd.total_count, + load_int, load_frac); + } + } +#endif + + if (dvfs->opp_table) + { + struct rt_dvfs_opp *current = dvfs->opp_table->current_opp; + const char *state; + + rt_kprintf("opp table:\n"); + + rt_list_for_each_entry(opp, &dvfs->opp_table->opp_nodes, list) + { + if (!opp->available) + { + state = "disabled"; + } + else if (opp == current) + { + state = "active"; + } + else if (opp->freq == dvfs->suspend_freq) + { + state = "suspend"; + } + else + { + state = RT_NULL; + } + + rt_kprintf(" opp[%02u]: %10lu Hz %7lu uV %5lu mW", + opp_id, opp->freq, opp->uvolt, opp->power); + + if (state) + { + rt_kprintf(" (%s)", state); + } + + rt_kprintf("\n"); + + ++opp_id; + } + } + + rt_dvfs_scaling_leave(dvfs); + + return 0; + } + else if (MSH_OPT_ID_GET(dvfs) == DVFS_OPT_SET_GOVERNOR) + { + rt_err_t err; + struct rt_dvfs_governor *gov; + + if (argc != 4 || !(dvfs = dvfs_scaling(argv[2]))) + { + goto _usage; + } + + if (!(gov = rt_dvfs_governor_get_by_name(argv[3]))) + { + LOG_E("Governor %s is not supported", argv[3]); + goto _usage; + } + + err = rt_dvfs_scaling_set_governor(dvfs, gov->type); + rt_dvfs_governor_put(gov); + + return err; + } + else if (MSH_OPT_ID_GET(dvfs) == DVFS_OPT_SET_FREQUENCY) + { + rt_err_t err; + rt_ubase_t freq; + + if (argc != 4 || !(dvfs = dvfs_scaling(argv[2]))) + { + goto _usage; + } + + freq = atol(argv[3]); + + if (freq < dvfs->min_freq || freq > dvfs->max_freq) + { + LOG_E("Frequency %lu is not supported", freq); + goto _usage; + } + + if (dvfs->gov && dvfs->gov->type != RT_DVFS_GOVERNOR_TYPE_FREEDOM) + { + err = rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_FREEDOM); + if (err) + { + LOG_E("switch governor failed: %s", rt_strerror(err)); + return err; + } + } + + err = rt_dvfs_scaling_set_frequency(dvfs, freq); + if (err) + { + LOG_E("%s: set frequency %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), freq, rt_strerror(err)); + } + else + { + rt_kprintf("%s: frequency set to %lu Hz\n", + rt_dm_dev_get_name(dvfs->dev), dvfs->cur_freq); + } + + return err; + } + +_usage: + rt_kprintf("Usage:\n"); + rt_kprintf("dvfs dump - dump dvfs information\n"); + rt_kprintf("dvfs set_governor - set dvfs governor\n"); + rt_kprintf("dvfs set_frequency - set dvfs frequency\n"); + + return (int)-RT_EINVAL; +} +CMD_OPTIONS_NODE_START(dvfs) +CMD_OPTIONS_NODE(DVFS_OPT_DUMP, dump, dump dvfs information) +CMD_OPTIONS_NODE(DVFS_OPT_SET_GOVERNOR, set_governor, set dvfs governor) +CMD_OPTIONS_NODE(DVFS_OPT_SET_FREQUENCY, set_frequency, set dvfs frequency) +CMD_OPTIONS_NODE_END +MSH_CMD_EXPORT_ALIAS(dvfs, dvfs, dvfs operation, optenable); diff --git a/components/drivers/dvfs/dvfs_cpu.c b/components/drivers/dvfs/dvfs_cpu.c new file mode 100644 index 00000000000..6b68a0dec65 --- /dev/null +++ b/components/drivers/dvfs/dvfs_cpu.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.cpu" +#define DBG_LVL DBG_INFO +#include + +rt_err_t rt_dvfs_cpufreq_register(struct rt_dvfs_cpufreq *cpufreq) +{ + rt_err_t err; + + if (!cpufreq) + { + return -RT_EINVAL; + } + + err = rt_dvfs_devfreq_register(rt_dvfs_cpufreq_to_devfreq(cpufreq)); + if (err) + { + return err; + } + + LOG_D("CPUfreq registered for device %s", + rt_dm_dev_get_name(rt_dvfs_cpufreq_to_scaling(cpufreq)->dev)); + + return RT_EOK; +} + +rt_err_t rt_dvfs_cpufreq_unregister(struct rt_dvfs_cpufreq *cpufreq) +{ + if (!cpufreq) + { + return -RT_EINVAL; + } + + return rt_dvfs_devfreq_unregister(rt_dvfs_cpufreq_to_devfreq(cpufreq)); +} diff --git a/components/drivers/dvfs/dvfs_event.c b/components/drivers/dvfs/dvfs_event.c new file mode 100644 index 00000000000..45f329e264f --- /dev/null +++ b/components/drivers/dvfs/dvfs_event.c @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.event" +#define DBG_LVL DBG_INFO +#include + +rt_err_t rt_dvfs_event_register(struct rt_dvfs_event *ev) +{ + if (!ev || !ev->dev || !ev->ops) + { + return -RT_EINVAL; + } + + RT_ASSERT(ev->ops->ready != RT_NULL); + RT_ASSERT(ev->ops->read != RT_NULL); + + ev->enable_count = 0; + rt_spin_lock_init(&ev->lock); + + rt_dm_dev_bind_fwdata(ev->dev, RT_NULL, ev); + + return RT_EOK; +} + +rt_err_t rt_dvfs_event_unregister(struct rt_dvfs_event *ev) +{ + if (!ev || !ev->dev) + { + return -RT_EINVAL; + } + +#ifdef RT_USING_OFW + if (ev->dev->ofw_node) + { + struct rt_ofw_node *np = ev->dev->ofw_node; + + if (rt_ref_read(&np->ref) > 1) + { + return -RT_EBUSY; + } + } +#endif /* RT_USING_OFW */ + + rt_dvfs_event_disable(ev); + + rt_dm_dev_unbind_fwdata(ev->dev, RT_NULL); + + return RT_EOK; +} + +rt_err_t rt_dvfs_event_ready(struct rt_dvfs_event *ev) +{ + rt_err_t err; + + if (!ev) + { + return -RT_EINVAL; + } + + if (!rt_dvfs_event_is_enabled(ev)) + { + return -RT_EIO; + } + + rt_spin_lock(&ev->lock); + err = ev->ops->ready(ev); + rt_spin_unlock(&ev->lock); + + return err; +} + +rt_err_t rt_dvfs_event_read(struct rt_dvfs_event *ev, struct rt_dvfs_event_data *evd) +{ + rt_err_t err; + + if (!ev || !evd) + { + return -RT_EINVAL; + } + + if (!rt_dvfs_event_is_enabled(ev)) + { + return -RT_EIO; + } + + evd->total_count = 0; + evd->load_count = 0; + + rt_spin_lock(&ev->lock); + err = ev->ops->read(ev, evd); + rt_spin_unlock(&ev->lock); + + return err; +} + +rt_err_t rt_dvfs_event_enable(struct rt_dvfs_event *ev) +{ + rt_err_t err = RT_EOK; + + if (!ev) + { + return -RT_EINVAL; + } + + rt_spin_lock(&ev->lock); + + if (ev->ops->enable && ev->enable_count == 0) + { + if ((err = ev->ops->enable(ev))) + { + goto _out_lock; + } + } + ++ev->enable_count; + +_out_lock: + rt_spin_unlock(&ev->lock); + + return err; +} + +rt_err_t rt_dvfs_event_disable(struct rt_dvfs_event *ev) +{ + rt_err_t err = RT_EOK; + + if (!ev) + { + return -RT_EINVAL; + } + + rt_spin_lock(&ev->lock); + + if (ev->enable_count <= 0) + { + LOG_W("%s: No enabled before", rt_dm_dev_get_name(ev->dev)); + err = -RT_EIO; + goto _out_lock; + } + + if (ev->ops->disable && ev->enable_count == 1) + { + if ((err = ev->ops->disable(ev))) + { + goto _out_lock; + } + } + --ev->enable_count; + +_out_lock: + rt_spin_unlock(&ev->lock); + + return err; +} + +rt_bool_t rt_dvfs_event_is_enabled(struct rt_dvfs_event *ev) +{ + rt_bool_t res; + + if (!ev) + { + return RT_FALSE; + } + + rt_spin_lock(&ev->lock); + + res = ev->enable_count > 0; + + rt_spin_unlock(&ev->lock); + + return res; +} + +rt_err_t rt_dvfs_event_reset(struct rt_dvfs_event *ev) +{ + rt_err_t err; + + if (!ev) + { + return -RT_EINVAL; + } + + if (!rt_dvfs_event_is_enabled(ev)) + { + return -RT_EIO; + } + + rt_spin_lock(&ev->lock); + + if (ev->ops->reset) + { + err = ev->ops->reset(ev); + } + else + { + err = RT_EOK; + } + + rt_spin_unlock(&ev->lock); + + return err; +} + +struct rt_dvfs_event *rt_dvfs_event_get(struct rt_device *dev, const char *name, int index) +{ + struct rt_dvfs_event *ev = rt_err_ptr(-RT_ENOSYS); + +#ifdef RT_USING_OFW + if (dev->ofw_node) + { + struct rt_ofw_node *np = rt_ofw_parse_phandle(dev->ofw_node, name, index); + + if (!np) + { + return rt_err_ptr(-RT_EEMPTY); + } + + ev = rt_ofw_data(np); + } +#endif /* RT_USING_OFW */ + + return ev; +} + +void rt_dvfs_event_put(struct rt_dvfs_event *ev) +{ + if (!ev) + { + return; + } + +#ifdef RT_USING_OFW + rt_ofw_node_put(ev->dev->ofw_node); +#endif +} diff --git a/components/drivers/dvfs/dvfs_governor.c b/components/drivers/dvfs/dvfs_governor.c new file mode 100644 index 00000000000..e0bf4946a0c --- /dev/null +++ b/components/drivers/dvfs/dvfs_governor.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.governor" +#define DBG_LVL DBG_INFO +#include + +static RT_DEFINE_SPINLOCK(dvfs_governor_nodes_lock); +static rt_list_t dvfs_governor_nodes = RT_LIST_OBJECT_INIT(dvfs_governor_nodes); + +static struct rt_dvfs_governor *dvfs_governor_find(rt_uint32_t governor, const char *name) +{ + struct rt_dvfs_governor *gov; + + rt_list_for_each_entry(gov, &dvfs_governor_nodes, list) + { + if (governor != RT_UINT32_MAX && gov->type == governor) + { + return gov; + } + else if (name && !rt_strncmp(name, gov->name, sizeof(gov->name) - 1)) + { + return gov; + } + } + + return RT_NULL; +} + +rt_err_t rt_dvfs_governor_register(struct rt_dvfs_governor *gov) +{ + rt_err_t err = RT_EOK; + + if (!gov) + { + return -RT_EINVAL; + } + + RT_ASSERT(gov->set_frequency != RT_NULL); + + rt_spin_lock(&dvfs_governor_nodes_lock); + + if (dvfs_governor_find(gov->type, gov->name)) + { + LOG_E("Governor %s[%u] is exists", gov->name, gov->type); + err = -RT_EFULL; + + goto _out_lock; + } + + rt_list_init(&gov->list); + rt_ref_init(&gov->ref); + rt_list_insert_before(&dvfs_governor_nodes, &gov->list); + +_out_lock: + rt_spin_unlock(&dvfs_governor_nodes_lock); + + return err; +} + +rt_err_t rt_dvfs_governor_unregister(struct rt_dvfs_governor *gov) +{ + rt_err_t err = RT_EOK; + + if (!gov) + { + return -RT_EINVAL; + } + + rt_spin_lock(&dvfs_governor_nodes_lock); + + if (rt_ref_read(&gov->ref) > 1) + { + err = -RT_EBUSY; + goto _out_lock; + } + + rt_list_remove(&gov->list); + +_out_lock: + rt_spin_unlock(&dvfs_governor_nodes_lock); + + return err; +} + +struct rt_dvfs_governor *rt_dvfs_governor_get(rt_uint32_t governor) +{ + struct rt_dvfs_governor *gov; + + rt_spin_lock(&dvfs_governor_nodes_lock); + + if ((gov = dvfs_governor_find(governor, RT_NULL))) + { + rt_ref_get(&gov->ref); + } + + rt_spin_unlock(&dvfs_governor_nodes_lock); + + return gov; +} + +struct rt_dvfs_governor *rt_dvfs_governor_get_by_name(const char *name) +{ + struct rt_dvfs_governor *gov; + + rt_spin_lock(&dvfs_governor_nodes_lock); + + if ((gov = dvfs_governor_find(RT_UINT32_MAX, name))) + { + rt_ref_get(&gov->ref); + } + + rt_spin_unlock(&dvfs_governor_nodes_lock); + + return gov; +} + +static void dvfs_governor_release(struct rt_ref *ref) +{ +} + +void rt_dvfs_governor_put(struct rt_dvfs_governor *gov) +{ + if (!gov) + { + return; + } + + rt_ref_put(&gov->ref, dvfs_governor_release); +} diff --git a/components/drivers/dvfs/dvfs_idle.c b/components/drivers/dvfs/dvfs_idle.c new file mode 100644 index 00000000000..80d2370dca6 --- /dev/null +++ b/components/drivers/dvfs/dvfs_idle.c @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.idle" +#define DBG_LVL DBG_INFO +#include + +static RT_DEFINE_SPINLOCK(_dvfs_idle_lock); + +/* Idle prediction data */ +static rt_uint32_t _last_idle_duration_us = 0; +static rt_uint32_t _predicted_idle_us = 0; + +rt_inline void dvfs_idle_lock(void) +{ + rt_spin_lock(&_dvfs_idle_lock); +} + +rt_inline void dvfs_idle_unlock(void) +{ + rt_spin_unlock(&_dvfs_idle_lock); +} + +/* Predict next idle duration based on history */ +static rt_uint32_t dvfs_predict_idle_duration(void) +{ + /* + * Simple prediction: exponentially weighted moving average + * predicted = 0.7 * last_actual + 0.3 * previous_predicted + */ + rt_uint32_t predicted = (_last_idle_duration_us * 7 + _predicted_idle_us * 3) / 10; + + /* Clamp to reasonable range */ + if (predicted < 100) + { + predicted = 100; /* Minimum 100us */ + } + + _predicted_idle_us = predicted; + + return predicted; +} + +/* Update prediction with actual idle duration */ +static void dvfs_update_idle_prediction(rt_uint32_t actual_duration_us) +{ + _last_idle_duration_us = actual_duration_us; +} + +rt_err_t rt_dvfs_idle_register(struct rt_dvfs_idle *idle) +{ + if (!idle || !idle->dev || !idle->ops) + { + return -RT_EINVAL; + } + + rt_dm_dev_bind_fwdata(idle->dev, RT_NULL, idle); + + return RT_EOK; +} + +rt_err_t rt_dvfs_idle_unregister(struct rt_dvfs_idle *idle) +{ + rt_err_t err = RT_EOK; + + if (!idle) + { + return -RT_EINVAL; + } + + dvfs_idle_lock(); + + if (idle->ref_count != 0 || idle->entry_count != 0) + { + err = -RT_EBUSY; + goto _unlock; + } + + rt_dm_dev_unbind_fwdata(idle->dev, RT_NULL); + +_unlock: + dvfs_idle_unlock(); + + return err; +} + +rt_err_t rt_dvfs_idle_add_status(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status) +{ + if (!idle || !status) + { + return -RT_EINVAL; + } + + if (!idle->status_table) + { + if (!(idle->status_table = rt_calloc(1, sizeof(*idle->status_table)))) + { + return -RT_ENOMEM; + } + + rt_list_init(&idle->status_table->status_nodes); + } + + rt_list_init(&status->list); + + dvfs_idle_lock(); + rt_list_insert_before(&idle->status_table->status_nodes, &status->list); + dvfs_idle_unlock(); + + return RT_EOK; +} + +void rt_dvfs_idle_remove_status(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status) +{ + if (!idle || !status) + { + return; + } + + RT_ASSERT(idle->status_table != RT_NULL); + + dvfs_idle_lock(); + rt_list_remove(&status->list); + dvfs_idle_unlock(); +} + +void rt_dvfs_idle_remove_status_all(struct rt_dvfs_idle *idle, + void (*release)(struct rt_dvfs_idle *, struct rt_dvfs_idle_status *)) +{ + struct rt_dvfs_idle_status_table *status_table; + struct rt_dvfs_idle_status *status, *status_next; + + if (!idle) + { + return; + } + + RT_ASSERT(idle->status_table != RT_NULL); + status_table = idle->status_table; + + dvfs_idle_lock(); + + rt_list_for_each_entry_safe(status, status_next, &status_table->status_nodes, list) + { + rt_list_remove(&status->list); + + dvfs_idle_unlock(); + + if (release) + { + release(idle, status); + } + + dvfs_idle_lock(); + } + + dvfs_idle_unlock(); +} + +rt_err_t rt_dvfs_idle_entry(struct rt_dvfs_idle *idle) +{ + rt_err_t err; + rt_bool_t can_stop_timer = RT_TRUE; + struct rt_dvfs_idle_status_table *table; + struct rt_dvfs_idle_status *it, *best = RT_NULL; + rt_uint32_t predicted_idle_us; + rt_tick_t entry_tick; + + if (!idle) + { + return -RT_EINVAL; + } + + if (!(table = idle->status_table)) + { + return -RT_ENOSYS; + } + + if (idle->ops->timer_can_stop) + { + can_stop_timer = idle->ops->timer_can_stop(idle); + } + + /* Predict idle duration */ + predicted_idle_us = dvfs_predict_idle_duration(); + + /* + * Select the best idle state based on predicted idle duration: + * - Choose the deepest sleep state that has: + * - entry_latency + exit_latency < predicted_idle + * - min_residency <= predicted_idle + * - This maximizes power savings while ensuring timely wakeup + */ + rt_list_for_each_entry(it, &table->status_nodes, list) + { + rt_uint32_t total_latency = it->entry_latency_us + it->exit_latency_us; + + /* Skip states that require timer stop if timer can't stop */ + if (it->timer_stop && !can_stop_timer) + { + continue; + } + + /* Check if this state is suitable for predicted idle time */ + if (predicted_idle_us >= total_latency && predicted_idle_us >= it->min_residency_us) + { + /* Choose the deepest suitable state (highest min_residency) */ + if (!best || it->min_residency_us > best->min_residency_us) + { + best = it; + } + } + } + + /* If no suitable state found, try to find a fallback */ + if (!best) + { + /* Find shallowest state that doesn't require timer stop */ + rt_list_for_each_entry(it, &table->status_nodes, list) + { + if (!it->timer_stop || can_stop_timer) + { + if (!best || it->entry_latency_us < best->entry_latency_us) + { + best = it; + } + } + } + } + + if (!best) + { + return -RT_EEMPTY; + } + + dvfs_idle_lock(); + + if (idle->entry_count != 0) + { + dvfs_idle_unlock(); + return -RT_EBUSY; + } + + table->current_status = best; + ++idle->entry_count; + entry_tick = rt_tick_get(); + + dvfs_idle_unlock(); + + LOG_D("%s: enter idle, predicted=%uus, selected state min_residency=%uus", + rt_dm_dev_get_name(idle->dev), predicted_idle_us, best->min_residency_us); + + if ((err = idle->ops->entry(idle, best))) + { + dvfs_idle_lock(); + table->current_status = RT_NULL; + --idle->entry_count; + dvfs_idle_unlock(); + return err; + } + + /* Store entry time for exit calculation */ + idle->priv = (void *)(rt_ubase_t)entry_tick; + + return RT_EOK; +} + +rt_err_t rt_dvfs_idle_exit(struct rt_dvfs_idle *idle) +{ + rt_err_t err; + struct rt_dvfs_idle_status *cur; + struct rt_dvfs_idle_status_table *table; + rt_tick_t exit_tick, entry_tick; + rt_uint32_t actual_idle_us; + + if (!idle) + { + return -RT_EINVAL; + } + + if (!(table = idle->status_table)) + { + return -RT_ENOSYS; + } + + dvfs_idle_lock(); + + if (idle->entry_count == 0 || table->current_status == RT_NULL) + { + dvfs_idle_unlock(); + return -RT_EINVAL; + } + cur = table->current_status; + entry_tick = (rt_tick_t)(rt_ubase_t)idle->priv; + + dvfs_idle_unlock(); + + exit_tick = rt_tick_get(); + + /* Calculate actual idle duration in microseconds */ + if (exit_tick >= entry_tick) + { + actual_idle_us = (exit_tick - entry_tick) * (1000000 / RT_TICK_PER_SECOND); + } + else + { + /* Tick overflow */ + actual_idle_us = (RT_TICK_MAX - entry_tick + exit_tick + 1) * (1000000 / RT_TICK_PER_SECOND); + } + + err = idle->ops->exit(idle, cur); + + dvfs_idle_lock(); + + if (idle->entry_count > 0) + { + --idle->entry_count; + } + table->current_status = RT_NULL; + idle->priv = RT_NULL; + + dvfs_idle_unlock(); + + /* Update prediction with actual duration */ + dvfs_update_idle_prediction(actual_idle_us); + + LOG_D("%s: exit idle, actual=%uus", rt_dm_dev_get_name(idle->dev), actual_idle_us); + + return err; +} + +struct rt_dvfs_idle *rt_dvfs_idle_get(struct rt_device *dev) +{ + struct rt_dvfs_idle *idle = RT_NULL; + + if (!dev) + { + return rt_err_ptr(-RT_EINVAL); + } + + dvfs_idle_lock(); + +#ifdef RT_USING_OFW + if (dev && dev->ofw_node) + { + idle = rt_ofw_data(dev->ofw_node); + } +#endif /* RT_USING_OFW */ + + if (!rt_is_err_or_null(idle)) + { + ++idle->ref_count; + } + + dvfs_idle_unlock(); + + return idle; +} + +void rt_dvfs_idle_put(struct rt_dvfs_idle *idle) +{ + if (!idle) + { + return; + } + + dvfs_idle_lock(); + --idle->ref_count; + dvfs_idle_unlock(); +} diff --git a/components/drivers/dvfs/dvfs_opp.c b/components/drivers/dvfs/dvfs_opp.c new file mode 100644 index 00000000000..2c91758d381 --- /dev/null +++ b/components/drivers/dvfs/dvfs_opp.c @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.opp" +#define DBG_LVL DBG_INFO +#include + +static struct rt_dvfs_opp *dvfs_opp_find_freq(struct rt_dvfs_opp_table *opp_table, rt_ubase_t freq, int dir) +{ + struct rt_dvfs_opp *opp, *opp_next, *best = RT_NULL; + + if (!opp_table) + { + return RT_NULL; + } + + rt_list_for_each_entry_safe(opp, opp_next, &opp_table->opp_nodes, list) + { + if (dir == 0) + { + if (opp->freq == freq) + { + return opp; + } + } + else if (dir > 0) + { + if (opp->freq >= freq && (!best || opp->freq < best->freq)) + { + best = opp; + } + } + else + { + if (opp->freq <= freq) + { + best = opp; + } + } + } + + return best; +} + +struct rt_dvfs_opp *rt_dvfs_scaling_add_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq, rt_ubase_t uvolt) +{ + struct rt_dvfs_opp_table *opp_table; + struct rt_dvfs_opp *opp, *opp_next; + + if (!dvfs) + { + return RT_NULL; + } + + if (!dvfs->opp_table) + { + if (!(dvfs->opp_table = rt_calloc(1, sizeof(*dvfs->opp_table)))) + { + return RT_NULL; + } + + rt_list_init(&dvfs->opp_table->opp_nodes); + } + + opp = rt_dvfs_scaling_find_opp(dvfs, freq); + + if (opp) + { + if (opp->uvolt == uvolt) + { + return opp; + } + + LOG_W("%s: OPP { %lu Hz, %lu uV } is exists { %lu Hz, %lu uV }", + rt_dm_dev_get_name(dvfs->dev), freq, uvolt, opp->freq, opp->uvolt); + + return RT_NULL; + } + + opp = rt_calloc(1, sizeof(*opp)); + + if (!opp) + { + LOG_E("%s: No memory to create OPP { %lu Hz, %lu uV }", + rt_dm_dev_get_name(dvfs->dev), freq, uvolt); + return RT_NULL; + } + + opp->freq = freq; + opp->uvolt = uvolt; + opp->available = RT_TRUE; + rt_list_init(&opp->list); + + rt_dvfs_scaling_enter(dvfs); + + opp_table = dvfs->opp_table; + + if (rt_list_isempty(&opp_table->opp_nodes)) + { + dvfs->min_freq = opp->freq; + dvfs->max_freq = opp->freq; + rt_list_insert_after(&opp_table->opp_nodes, &opp->list); + } + else + { + rt_list_for_each_entry(opp_next, &opp_table->opp_nodes, list) + { + if (opp->freq < opp_next->freq) + { + rt_list_insert_before(&opp_next->list, &opp->list); + break; + } + } + + if (rt_list_isempty(&opp->list)) + { + rt_list_insert_before(&opp_table->opp_nodes, &opp->list); + dvfs->max_freq = opp->freq; + } + else if (opp->freq < dvfs->min_freq) + { + dvfs->min_freq = opp->freq; + } + else if (opp->freq > dvfs->max_freq) + { + dvfs->max_freq = opp->freq; + } + } + + rt_dvfs_scaling_leave(dvfs); + + return opp; +} + +rt_err_t rt_dvfs_scaling_remove_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp_table *opp_table; + struct rt_dvfs_opp *opp = rt_dvfs_scaling_find_opp(dvfs, freq); + + if (!opp) + { + return -RT_EINVAL; + } + + opp_table = dvfs->opp_table; + + if (opp_table->current_opp == opp) + { + return -RT_EBUSY; + } + + rt_dvfs_scaling_enter(dvfs); + + rt_list_remove(&opp->list); + + if (!rt_list_isempty(&opp_table->opp_nodes)) + { + if (dvfs->min_freq == opp->freq) + { + opp = rt_list_entry(opp_table->opp_nodes.next, struct rt_dvfs_opp, list); + + dvfs->min_freq = opp->freq; + } + else if (dvfs->max_freq == opp->freq) + { + opp = rt_list_entry(opp_table->opp_nodes.prev, struct rt_dvfs_opp, list); + + dvfs->max_freq = opp->freq; + } + } + else + { + LOG_W("%s: OPP is empty", rt_dm_dev_get_name(dvfs->dev)); + + dvfs->min_freq = 0; + dvfs->max_freq = 0; + } + + rt_dvfs_scaling_leave(dvfs); + + rt_free(opp); + + return RT_EOK; +} + +rt_err_t rt_dvfs_scaling_remove_opp_all(struct rt_dvfs_scaling *dvfs) +{ + struct rt_dvfs_opp *opp, *opp_next; + struct rt_dvfs_opp_table *opp_table; + + if (!dvfs) + { + return -RT_EINVAL; + } + + rt_dvfs_scaling_enter(dvfs); + + /* User will free, so free ignore current */ + dvfs->min_freq = 0; + dvfs->max_freq = 0; + + opp_table = dvfs->opp_table; + opp_table->current_opp = RT_NULL; + + rt_list_for_each_entry_safe(opp, opp_next, &opp_table->opp_nodes, list) + { + rt_list_remove(&opp->list); + + rt_dvfs_scaling_leave(dvfs); + + rt_free(opp); + + rt_dvfs_scaling_enter(dvfs); + } + + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +rt_err_t rt_dvfs_scaling_enable_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp *opp = rt_dvfs_scaling_find_opp(dvfs, freq); + + if (!opp) + { + return -RT_EINVAL; + } + + rt_dvfs_scaling_enter(dvfs); + opp->available = RT_TRUE; + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +rt_err_t rt_dvfs_scaling_disable_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + rt_err_t err; + struct rt_dvfs_opp *opp = rt_dvfs_scaling_find_opp(dvfs, freq); + + if (!opp) + { + return -RT_EINVAL; + } + + rt_dvfs_scaling_enter(dvfs); + + if (dvfs->opp_table->current_opp != opp) + { + opp->available = RT_FALSE; + err = RT_EOK; + } + else + { + err = -RT_EBUSY; + } + + rt_dvfs_scaling_leave(dvfs); + + return err; +} + +struct rt_dvfs_opp *rt_dvfs_scaling_find_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp *opp = RT_NULL; + + if (!dvfs || !dvfs->opp_table) + { + return RT_NULL; + } + + rt_dvfs_scaling_enter(dvfs); + + if (freq >= dvfs->min_freq && freq <= dvfs->max_freq) + { + opp = dvfs_opp_find_freq(dvfs->opp_table, freq, 0); + } + + rt_dvfs_scaling_leave(dvfs); + + return opp; +} + +struct rt_dvfs_opp *rt_dvfs_scaling_find_ceil_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp *opp = RT_NULL; + + if (!dvfs || !dvfs->opp_table) + { + return RT_NULL; + } + + rt_dvfs_scaling_enter(dvfs); + + if (freq <= dvfs->max_freq) + { + opp = dvfs_opp_find_freq(dvfs->opp_table, freq, 1); + } + + rt_dvfs_scaling_leave(dvfs); + + return opp; +} + +struct rt_dvfs_opp *rt_dvfs_scaling_find_floor_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq) +{ + struct rt_dvfs_opp *opp = RT_NULL; + + if (!dvfs || !dvfs->opp_table) + { + return RT_NULL; + } + + rt_dvfs_scaling_enter(dvfs); + + if (freq >= dvfs->min_freq) + { + opp = dvfs_opp_find_freq(dvfs->opp_table, freq, -1); + } + + rt_dvfs_scaling_leave(dvfs); + + return opp; +} diff --git a/components/drivers/dvfs/dvfs_pm.c b/components/drivers/dvfs/dvfs_pm.c new file mode 100644 index 00000000000..15ae49fec2e --- /dev/null +++ b/components/drivers/dvfs/dvfs_pm.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.pm" +#define DBG_LVL DBG_INFO +#include + +rt_err_t rt_dvfs_scaling_pm_suspend(struct rt_device_pm *device_pm, rt_uint8_t mode) +{ + struct rt_dvfs_scaling *dvfs = device_pm->device->dvfs_scaling; + + if (!dvfs) + { + return RT_EOK; + } + + return rt_dvfs_scaling_suspend(dvfs); +} + +rt_err_t rt_dvfs_scaling_pm_resume(struct rt_device_pm *device_pm, rt_uint8_t mode) +{ + struct rt_dvfs_scaling *dvfs = device_pm->device->dvfs_scaling; + + if (!dvfs) + { + return RT_EOK; + } + + return rt_dvfs_scaling_resume(dvfs); +} + +rt_err_t rt_dvfs_scaling_pm_frequency_change(struct rt_device_pm *device_pm, rt_uint8_t mode) +{ + rt_uint32_t governor_type = RT_DVFS_GOVERNOR_TYPE_ONDEMAND; + struct rt_dvfs_scaling *dvfs = device_pm->device->dvfs_scaling; + + if (!dvfs) + { + return RT_EOK; + } + + rt_dvfs_scaling_enter(dvfs); + + if (dvfs->gov) + { + governor_type = dvfs->gov->type; + } + + rt_dvfs_scaling_leave(dvfs); + + if (governor_type == RT_DVFS_GOVERNOR_TYPE_FREEDOM) + { + switch (mode) + { + case PM_RUN_MODE_HIGH_SPEED: + return rt_dvfs_scaling_set_frequency(dvfs, dvfs->max_freq); + + case PM_RUN_MODE_NORMAL_SPEED: + /* + * It's not a good idea, but the OPP doesn't seem to have defaults. + */ + return rt_dvfs_scaling_set_frequency(dvfs, (dvfs->max_freq + dvfs->min_freq) / 2); + + case PM_RUN_MODE_MEDIUM_SPEED: + return rt_dvfs_scaling_set_frequency(dvfs, dvfs->suspend_freq); + + case PM_RUN_MODE_LOW_SPEED: + return rt_dvfs_scaling_set_frequency(dvfs, dvfs->min_freq); + + default: + break; + } + } + else + { + switch (mode) + { + case PM_RUN_MODE_HIGH_SPEED: + return rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_PERFORMANCE); + + case PM_RUN_MODE_NORMAL_SPEED: + return rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_ONDEMAND); + + case PM_RUN_MODE_MEDIUM_SPEED: + return rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_CONSERVATIVE); + + case PM_RUN_MODE_LOW_SPEED: + return rt_dvfs_scaling_set_governor(dvfs, RT_DVFS_GOVERNOR_TYPE_POWERSAVE); + + default: + break; + } + } + + return -RT_EINVAL; +} diff --git a/components/drivers/dvfs/governor/SConscript b/components/drivers/dvfs/governor/SConscript new file mode 100644 index 00000000000..7c6d0921d0b --- /dev/null +++ b/components/drivers/dvfs/governor/SConscript @@ -0,0 +1,10 @@ +from building import * + +cwd = GetCurrentDir() +CPPPATH = [cwd + '/../../include'] + +src = Glob('*.c') + +group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/drivers/dvfs/governor/conservative.c b/components/drivers/dvfs/governor/conservative.c new file mode 100644 index 00000000000..6aac1e8a2c3 --- /dev/null +++ b/components/drivers/dvfs/governor/conservative.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.governor.conservative" +#define DBG_LVL DBG_INFO +#include + +/* Default parameters (Linux-like) */ +#define CONSERVATIVE_DEFAULT_SAMPLING_RATE_MS 1000 +#define CONSERVATIVE_DEFAULT_UP_THRESHOLD 80 +#define CONSERVATIVE_DEFAULT_DOWN_DIFFERENTIAL 20 +#define CONSERVATIVE_DEFAULT_FREQ_STEP 5 +#define CONSERVATIVE_DEFAULT_SAMPLING_DOWN_FACTOR 1 + +struct governor_conservative_data +{ + struct rt_work monitor_work; + rt_bool_t running; +}; + +static rt_bool_t governor_conservative_active(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs || !dvfs->gov || + dvfs->gov->type != RT_DVFS_GOVERNOR_TYPE_CONSERVATIVE) + { + return RT_FALSE; + } + + data = dvfs->gov_data; + + return data && data->running; +} + +static void governor_conservative_monitor(struct rt_work *work, void *work_data) +{ + struct rt_dvfs_scaling *dvfs = (struct rt_dvfs_scaling *)work_data; + struct governor_conservative_data *data; + struct rt_dvfs_opp *opp; + rt_ubase_t target_freq; + rt_uint32_t load; + + if (!governor_conservative_active(dvfs)) + { + return; + } + + data = dvfs->gov_data; + + /* Update CPU load statistics */ + rt_dvfs_load_update(dvfs); + load = rt_dvfs_cpu_load_get(&dvfs->cpu_load); + + target_freq = dvfs->cur_freq; + + /* Determine target frequency based on load */ + if (load >= dvfs->gov_params.up_threshold) + { + /* High load: step up frequency */ + rt_ubase_t step_freq = (dvfs->max_freq * dvfs->gov_params.freq_step) / 100; + target_freq = dvfs->cur_freq + step_freq; + + /* Find ceil OPP */ + opp = rt_dvfs_scaling_find_ceil_opp(dvfs, target_freq); + if (opp && opp->available && opp->freq > dvfs->cur_freq) + { + target_freq = opp->freq; + } + else + { + target_freq = dvfs->max_freq; + } + + LOG_D("%s: load=%u%% >= up_threshold=%u%%, step up to %lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, + dvfs->gov_params.up_threshold, target_freq); + } + else if (load < dvfs->gov_params.down_differential) + { + /* Low load: step down frequency */ + rt_ubase_t step_freq = (dvfs->max_freq * dvfs->gov_params.freq_step) / 100; + target_freq = (dvfs->cur_freq > step_freq) ? (dvfs->cur_freq - step_freq) : dvfs->min_freq; + + /* Find floor OPP */ + opp = rt_dvfs_scaling_find_floor_opp(dvfs, target_freq); + if (opp && opp->available && opp->freq < dvfs->cur_freq) + { + target_freq = opp->freq; + } + else + { + target_freq = dvfs->min_freq; + } + + LOG_D("%s: load=%u%% < down_threshold=%u%%, step down to %lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, + dvfs->gov_params.down_differential, target_freq); + } + else + { + /* Medium load: keep current frequency */ + LOG_D("%s: load=%u%% in range, keep freq %lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, dvfs->cur_freq); + } + + /* Set target frequency if different */ + if (target_freq != dvfs->cur_freq && governor_conservative_active(dvfs)) + { + rt_err_t err = rt_dvfs_scaling_set_frequency(dvfs, target_freq); + if (err) + { + LOG_W("%s: set frequency %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), target_freq, rt_strerror(err)); + } + } + + /* Reschedule monitoring work */ + if (governor_conservative_active(dvfs)) + { + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } +} + +static rt_err_t governor_conservative_start(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Initialize default parameters if not set */ + if (dvfs->gov_params.sampling_rate_ms == 0) + { + dvfs->gov_params.sampling_rate_ms = CONSERVATIVE_DEFAULT_SAMPLING_RATE_MS; + } + if (dvfs->gov_params.up_threshold == 0) + { + dvfs->gov_params.up_threshold = CONSERVATIVE_DEFAULT_UP_THRESHOLD; + } + if (dvfs->gov_params.down_differential == 0) + { + dvfs->gov_params.down_differential = CONSERVATIVE_DEFAULT_DOWN_DIFFERENTIAL; + } + if (dvfs->gov_params.freq_step == 0) + { + dvfs->gov_params.freq_step = CONSERVATIVE_DEFAULT_FREQ_STEP; + } + if (dvfs->gov_params.sampling_down_factor == 0) + { + dvfs->gov_params.sampling_down_factor = CONSERVATIVE_DEFAULT_SAMPLING_DOWN_FACTOR; + } + + /* Allocate governor data */ + data = rt_calloc(1, sizeof(*data)); + if (!data) + { + LOG_E("%s: no memory for conservative data", rt_dm_dev_get_name(dvfs->dev)); + return -RT_ENOMEM; + } + + dvfs->gov_data = data; + data->running = RT_TRUE; + + /* Initialize CPU load */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + dvfs->cpu_load.load_percentage = 0; + + /* Initialize monitoring work */ + rt_work_init(&data->monitor_work, governor_conservative_monitor, dvfs); + + /* Submit first monitoring work */ + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + + LOG_D("%s: conservative governor started (sampling=%ums, up_threshold=%u%%, down_threshold=%u%%, freq_step=%u%%)", + rt_dm_dev_get_name(dvfs->dev), + dvfs->gov_params.sampling_rate_ms, + dvfs->gov_params.up_threshold, + dvfs->gov_params.down_differential, + dvfs->gov_params.freq_step); + + return RT_EOK; +} + +static rt_err_t governor_conservative_stop(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + + /* Wait for work to complete */ + rt_thread_mdelay(10); + + rt_free(data); + dvfs->gov_data = RT_NULL; + } + + LOG_D("%s: conservative governor stopped", rt_dm_dev_get_name(dvfs->dev)); + + return RT_EOK; +} + +static rt_err_t governor_conservative_suspend(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Stop monitoring */ + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + } + + return RT_EOK; +} + +static rt_err_t governor_conservative_resume(struct rt_dvfs_scaling *dvfs) +{ + struct governor_conservative_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Resume monitoring */ + data = dvfs->gov_data; + if (data) + { + /* Reset CPU load statistics */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + + data->running = RT_TRUE; + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } + + return RT_EOK; +} + +static rt_err_t governor_conservative_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Minimum sampling interval: 10ms */ + if (interval_ms < 10) + { + interval_ms = 10; + } + + dvfs->gov_params.sampling_rate_ms = interval_ms; + + LOG_D("%s: conservative sampling interval set to %ums", + rt_dm_dev_get_name(dvfs->dev), interval_ms); + + return RT_EOK; +} + +static rt_err_t governor_conservative_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq) +{ + if (!dvfs || !out_freq) + { + return -RT_EINVAL; + } + + /* Conservative sets frequency based on load, not requested frequency */ + /* Return current frequency */ + *out_freq = dvfs->cur_freq; + + return RT_EOK; +} + +static struct rt_dvfs_governor governor_conservative = +{ + .name = "conservative", + .type = RT_DVFS_GOVERNOR_TYPE_CONSERVATIVE, + + .start = governor_conservative_start, + .stop = governor_conservative_stop, + .suspend = governor_conservative_suspend, + .resume = governor_conservative_resume, + .set_interval = governor_conservative_set_interval, + .set_frequency = governor_conservative_set_frequency, +}; + +static int governor_conservative_init(void) +{ + rt_dvfs_governor_register(&governor_conservative); + + return 0; +} +INIT_CORE_EXPORT(governor_conservative_init); diff --git a/components/drivers/dvfs/governor/freedom.c b/components/drivers/dvfs/governor/freedom.c new file mode 100644 index 00000000000..e785047355a --- /dev/null +++ b/components/drivers/dvfs/governor/freedom.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.governor.freedom" +#define DBG_LVL DBG_INFO +#include + +/* Freedom governor: user-controlled frequency, no automatic adjustment */ +static rt_err_t governor_freedom_start(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Keep current frequency, no automatic adjustment */ + LOG_D("%s: freedom governor started at %lu Hz", + rt_dm_dev_get_name(dvfs->dev), dvfs->cur_freq); + + return RT_EOK; +} + +static rt_err_t governor_freedom_stop(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + LOG_D("%s: freedom governor stopped", rt_dm_dev_get_name(dvfs->dev)); + + return RT_EOK; +} + +static rt_err_t governor_freedom_suspend(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Nothing special for suspend */ + return RT_EOK; +} + +static rt_err_t governor_freedom_resume(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Restore to current frequency */ + return rt_dvfs_scaling_set_frequency(dvfs, dvfs->cur_freq); +} + +static rt_err_t governor_freedom_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms) +{ + /* Freedom governor doesn't use sampling interval */ + return -RT_ENOSYS; +} + +static rt_err_t governor_freedom_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq) +{ + if (!dvfs || !out_freq) + { + return -RT_EINVAL; + } + + /* Return current frequency (user-controlled) */ + *out_freq = dvfs->cur_freq; + + return RT_EOK; +} + +static struct rt_dvfs_governor governor_freedom = +{ + .name = "freedom", + .type = RT_DVFS_GOVERNOR_TYPE_FREEDOM, + + .start = governor_freedom_start, + .stop = governor_freedom_stop, + .suspend = governor_freedom_suspend, + .resume = governor_freedom_resume, + .set_interval = governor_freedom_set_interval, + .set_frequency = governor_freedom_set_frequency, +}; + +static int governor_freedom_init(void) +{ + rt_dvfs_governor_register(&governor_freedom); + + return 0; +} +INIT_CORE_EXPORT(governor_freedom_init); diff --git a/components/drivers/dvfs/governor/ondemand.c b/components/drivers/dvfs/governor/ondemand.c new file mode 100644 index 00000000000..8e84e2ca854 --- /dev/null +++ b/components/drivers/dvfs/governor/ondemand.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.governor.ondemand" +#define DBG_LVL DBG_INFO +#include + +/* Default parameters (Linux-like) */ +#define ONDEMAND_DEFAULT_SAMPLING_RATE_MS 1000 +#define ONDEMAND_DEFAULT_UP_THRESHOLD 80 +#define ONDEMAND_DEFAULT_DOWN_DIFFERENTIAL 20 +#define ONDEMAND_DEFAULT_IGNORE_NICE RT_FALSE +#define ONDEMAND_DEFAULT_POWERSAVE_BIAS 0 + +struct governor_ondemand_data +{ + struct rt_work monitor_work; + rt_bool_t running; +}; + +static rt_bool_t governor_ondemand_active(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs || !dvfs->gov || + dvfs->gov->type != RT_DVFS_GOVERNOR_TYPE_ONDEMAND) + { + return RT_FALSE; + } + + data = dvfs->gov_data; + + return data && data->running; +} + +static void governor_ondemand_monitor(struct rt_work *work, void *work_data) +{ + struct rt_dvfs_scaling *dvfs = (struct rt_dvfs_scaling *)work_data; + struct governor_ondemand_data *data; + struct rt_dvfs_opp *opp; + rt_ubase_t target_freq; + rt_uint32_t load; + + if (!governor_ondemand_active(dvfs)) + { + return; + } + + data = dvfs->gov_data; + + /* Update CPU load statistics */ + rt_dvfs_load_update(dvfs); + load = rt_dvfs_cpu_load_get(&dvfs->cpu_load); + + /* Determine target frequency based on load */ + if (load >= dvfs->gov_params.up_threshold) + { + /* High load: jump to max frequency */ + target_freq = dvfs->max_freq; + LOG_D("%s: load=%u%% >= threshold=%u%%, set max freq", + rt_dm_dev_get_name(dvfs->dev), load, + dvfs->gov_params.up_threshold); + } + else if (load < dvfs->gov_params.down_differential) + { + /* Low load: find appropriate frequency */ + rt_ubase_t freq_target = (dvfs->cur_freq * load) / 100; + + /* Apply powersave bias if configured */ + if (dvfs->gov_params.powersave_bias > 0) + { + freq_target = freq_target * (100 - dvfs->gov_params.powersave_bias) / 100; + } + + /* Find floor OPP (next lower or equal frequency) */ + opp = rt_dvfs_scaling_find_floor_opp(dvfs, freq_target); + if (opp && opp->available) + { + target_freq = opp->freq; + } + else + { + target_freq = dvfs->min_freq; + } + + LOG_D("%s: load=%u%% < diff=%u%%, target=%lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, + dvfs->gov_params.down_differential, target_freq); + } + else + { + /* Medium load: keep current frequency */ + target_freq = dvfs->cur_freq; + } + + /* Set target frequency if different */ + if (target_freq != dvfs->cur_freq && governor_ondemand_active(dvfs)) + { + rt_err_t err = rt_dvfs_scaling_set_frequency(dvfs, target_freq); + if (err) + { + LOG_W("%s: set frequency %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), target_freq, rt_strerror(err)); + } + } + + /* Reschedule monitoring work */ + if (governor_ondemand_active(dvfs)) + { + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } +} + +static rt_err_t governor_ondemand_start(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Initialize default parameters if not set */ + if (dvfs->gov_params.sampling_rate_ms == 0) + { + dvfs->gov_params.sampling_rate_ms = ONDEMAND_DEFAULT_SAMPLING_RATE_MS; + } + if (dvfs->gov_params.up_threshold == 0) + { + dvfs->gov_params.up_threshold = ONDEMAND_DEFAULT_UP_THRESHOLD; + } + if (dvfs->gov_params.down_differential == 0) + { + dvfs->gov_params.down_differential = ONDEMAND_DEFAULT_DOWN_DIFFERENTIAL; + } + if (dvfs->gov_params.ignore_nice_load == 0) + { + dvfs->gov_params.ignore_nice_load = ONDEMAND_DEFAULT_IGNORE_NICE; + } + if (dvfs->gov_params.powersave_bias == 0) + { + dvfs->gov_params.powersave_bias = ONDEMAND_DEFAULT_POWERSAVE_BIAS; + } + + /* Allocate governor data */ + data = rt_calloc(1, sizeof(*data)); + if (!data) + { + LOG_E("%s: no memory for ondemand data", rt_dm_dev_get_name(dvfs->dev)); + return -RT_ENOMEM; + } + + dvfs->gov_data = data; + data->running = RT_TRUE; + + /* Initialize CPU load */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + dvfs->cpu_load.load_percentage = 0; + + /* Initialize monitoring work */ + rt_work_init(&data->monitor_work, governor_ondemand_monitor, dvfs); + + /* Submit first monitoring work */ + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + + LOG_D("%s: ondemand governor started (sampling=%ums, up_threshold=%u%%, down_diff=%u%%)", + rt_dm_dev_get_name(dvfs->dev), + dvfs->gov_params.sampling_rate_ms, + dvfs->gov_params.up_threshold, + dvfs->gov_params.down_differential); + + return RT_EOK; +} + +static rt_err_t governor_ondemand_stop(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + + /* Wait for work to complete */ + rt_thread_mdelay(10); + + rt_free(data); + dvfs->gov_data = RT_NULL; + } + + LOG_D("%s: ondemand governor stopped", rt_dm_dev_get_name(dvfs->dev)); + + return RT_EOK; +} + +static rt_err_t governor_ondemand_suspend(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Stop monitoring */ + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + } + + return RT_EOK; +} + +static rt_err_t governor_ondemand_resume(struct rt_dvfs_scaling *dvfs) +{ + struct governor_ondemand_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Resume monitoring */ + data = dvfs->gov_data; + if (data) + { + /* Reset CPU load statistics */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + + data->running = RT_TRUE; + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } + + return RT_EOK; +} + +static rt_err_t governor_ondemand_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Minimum sampling interval: 10ms */ + if (interval_ms < 10) + { + interval_ms = 10; + } + + dvfs->gov_params.sampling_rate_ms = interval_ms; + + LOG_D("%s: ondemand sampling interval set to %ums", + rt_dm_dev_get_name(dvfs->dev), interval_ms); + + return RT_EOK; +} + +static rt_err_t governor_ondemand_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq) +{ + if (!dvfs || !out_freq) + { + return -RT_EINVAL; + } + + /* Ondemand sets frequency based on load, not requested frequency */ + /* Return current frequency */ + *out_freq = dvfs->cur_freq; + + return RT_EOK; +} + +static struct rt_dvfs_governor governor_ondemand = +{ + .name = "ondemand", + .type = RT_DVFS_GOVERNOR_TYPE_ONDEMAND, + + .start = governor_ondemand_start, + .stop = governor_ondemand_stop, + .suspend = governor_ondemand_suspend, + .resume = governor_ondemand_resume, + .set_interval = governor_ondemand_set_interval, + .set_frequency = governor_ondemand_set_frequency, +}; + +static int governor_ondemand_init(void) +{ + rt_dvfs_governor_register(&governor_ondemand); + + return 0; +} +INIT_CORE_EXPORT(governor_ondemand_init); diff --git a/components/drivers/dvfs/governor/performance.c b/components/drivers/dvfs/governor/performance.c new file mode 100644 index 00000000000..d2fba6e5e9a --- /dev/null +++ b/components/drivers/dvfs/governor/performance.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.governor.performance" +#define DBG_LVL DBG_INFO +#include + +static rt_err_t governor_performance_start(struct rt_dvfs_scaling *dvfs) +{ + rt_err_t err; + + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Set frequency to maximum */ + err = rt_dvfs_scaling_set_frequency(dvfs, dvfs->max_freq); + if (err) + { + LOG_E("%s: set max frequency failed: %s", + rt_dm_dev_get_name(dvfs->dev), rt_strerror(err)); + return err; + } + + LOG_D("%s: performance governor started at %lu Hz", + rt_dm_dev_get_name(dvfs->dev), dvfs->cur_freq); + + return RT_EOK; +} + +static rt_err_t governor_performance_stop(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + LOG_D("%s: performance governor stopped", rt_dm_dev_get_name(dvfs->dev)); + + return RT_EOK; +} + +static rt_err_t governor_performance_suspend(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Nothing special for suspend */ + return RT_EOK; +} + +static rt_err_t governor_performance_resume(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Restore to max frequency */ + return rt_dvfs_scaling_set_frequency(dvfs, dvfs->max_freq); +} + +static rt_err_t governor_performance_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms) +{ + /* Performance governor doesn't use sampling interval */ + return -RT_ENOSYS; +} + +static rt_err_t governor_performance_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq) +{ + if (!dvfs || !out_freq) + { + return -RT_EINVAL; + } + + /* Always return max frequency */ + *out_freq = dvfs->max_freq; + + return RT_EOK; +} + +static struct rt_dvfs_governor governor_performance = +{ + .name = "performance", + .type = RT_DVFS_GOVERNOR_TYPE_PERFORMANCE, + + .start = governor_performance_start, + .stop = governor_performance_stop, + .suspend = governor_performance_suspend, + .resume = governor_performance_resume, + .set_interval = governor_performance_set_interval, + .set_frequency = governor_performance_set_frequency, +}; + +static int governor_performance_init(void) +{ + rt_dvfs_governor_register(&governor_performance); + + return 0; +} +INIT_CORE_EXPORT(governor_performance_init); diff --git a/components/drivers/dvfs/governor/powersave.c b/components/drivers/dvfs/governor/powersave.c new file mode 100644 index 00000000000..9ad1bc2c9a3 --- /dev/null +++ b/components/drivers/dvfs/governor/powersave.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.governor.powersave" +#define DBG_LVL DBG_INFO +#include + +static rt_err_t governor_powersave_start(struct rt_dvfs_scaling *dvfs) +{ + rt_err_t err; + + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Set frequency to minimum */ + err = rt_dvfs_scaling_set_frequency(dvfs, dvfs->min_freq); + if (err) + { + LOG_E("%s: set min frequency failed: %s", + rt_dm_dev_get_name(dvfs->dev), rt_strerror(err)); + return err; + } + + LOG_D("%s: powersave governor started at %lu Hz", + rt_dm_dev_get_name(dvfs->dev), dvfs->cur_freq); + + return RT_EOK; +} + +static rt_err_t governor_powersave_stop(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + LOG_D("%s: powersave governor stopped", rt_dm_dev_get_name(dvfs->dev)); + + return RT_EOK; +} + +static rt_err_t governor_powersave_suspend(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Nothing special for suspend */ + return RT_EOK; +} + +static rt_err_t governor_powersave_resume(struct rt_dvfs_scaling *dvfs) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Restore to min frequency */ + return rt_dvfs_scaling_set_frequency(dvfs, dvfs->min_freq); +} + +static rt_err_t governor_powersave_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms) +{ + /* Powersave governor doesn't use sampling interval */ + return -RT_ENOSYS; +} + +static rt_err_t governor_powersave_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq) +{ + if (!dvfs || !out_freq) + { + return -RT_EINVAL; + } + + /* Always return min frequency */ + *out_freq = dvfs->min_freq; + + return RT_EOK; +} + +static struct rt_dvfs_governor governor_powersave = +{ + .name = "powersave", + .type = RT_DVFS_GOVERNOR_TYPE_POWERSAVE, + + .start = governor_powersave_start, + .stop = governor_powersave_stop, + .suspend = governor_powersave_suspend, + .resume = governor_powersave_resume, + .set_interval = governor_powersave_set_interval, + .set_frequency = governor_powersave_set_frequency, +}; + +static int governor_powersave_init(void) +{ + rt_dvfs_governor_register(&governor_powersave); + + return 0; +} +INIT_CORE_EXPORT(governor_powersave_init); diff --git a/components/drivers/dvfs/governor/schedutil.c b/components/drivers/dvfs/governor/schedutil.c new file mode 100644 index 00000000000..2a13d1b08d3 --- /dev/null +++ b/components/drivers/dvfs/governor/schedutil.c @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.governor.schedutil" +#define DBG_LVL DBG_INFO +#include + +/* Default parameters */ +#define SCHEDUTIL_DEFAULT_SAMPLING_RATE_MS 500 +#define SCHEDUTIL_UP_THRESHOLD 85 +#define SCHEDUTIL_DOWN_DIFFERENTIAL 20 + +struct governor_schedutil_data +{ + struct rt_work monitor_work; + rt_bool_t running; +}; + +static rt_bool_t governor_schedutil_active(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs || !dvfs->gov || + dvfs->gov->type != RT_DVFS_GOVERNOR_TYPE_SCHEDUTIL) + { + return RT_FALSE; + } + + data = dvfs->gov_data; + + return data && data->running; +} + +static void governor_schedutil_monitor(struct rt_work *work, void *work_data) +{ + struct rt_dvfs_scaling *dvfs = (struct rt_dvfs_scaling *)work_data; + struct governor_schedutil_data *data; + struct rt_dvfs_opp *opp; + rt_ubase_t target_freq; + rt_uint32_t load; + + if (!governor_schedutil_active(dvfs)) + { + return; + } + + data = dvfs->gov_data; + + /* Update CPU load statistics */ + rt_dvfs_load_update(dvfs); + load = rt_dvfs_cpu_load_get(&dvfs->cpu_load); + + /* + * Schedutil uses scheduler's utilization estimate. + * For RT-Thread, we use CPU load as approximation. + * In a full implementation, this would hook into the scheduler + * to get actual utilization metrics. + */ + if (load >= SCHEDUTIL_UP_THRESHOLD) + { + /* High utilization: use max frequency */ + target_freq = dvfs->max_freq; + } + else + { + /* Scale frequency proportionally to load */ + target_freq = dvfs->max_freq * load / 100; + + /* Find floor OPP */ + opp = rt_dvfs_scaling_find_floor_opp(dvfs, target_freq); + if (opp && opp->available) + { + target_freq = opp->freq; + } + else + { + target_freq = dvfs->min_freq; + } + } + + LOG_D("%s: schedutil load=%u%%, target_freq=%lu Hz", + rt_dm_dev_get_name(dvfs->dev), load, target_freq); + + /* Set target frequency if different */ + if (target_freq != dvfs->cur_freq && governor_schedutil_active(dvfs)) + { + rt_err_t err = rt_dvfs_scaling_set_frequency(dvfs, target_freq); + if (err) + { + LOG_W("%s: set frequency %lu failed: %s", + rt_dm_dev_get_name(dvfs->dev), target_freq, rt_strerror(err)); + } + } + + /* Reschedule monitoring work */ + if (governor_schedutil_active(dvfs)) + { + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } +} + +static rt_err_t governor_schedutil_start(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Initialize default parameters if not set */ + if (dvfs->gov_params.sampling_rate_ms == 0) + { + dvfs->gov_params.sampling_rate_ms = SCHEDUTIL_DEFAULT_SAMPLING_RATE_MS; + } + + /* Allocate governor data */ + data = rt_calloc(1, sizeof(*data)); + if (!data) + { + LOG_E("%s: no memory for schedutil data", rt_dm_dev_get_name(dvfs->dev)); + return -RT_ENOMEM; + } + + dvfs->gov_data = data; + data->running = RT_TRUE; + + /* Initialize CPU load */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + dvfs->cpu_load.load_percentage = 0; + + /* Initialize monitoring work */ + rt_work_init(&data->monitor_work, governor_schedutil_monitor, dvfs); + + /* Submit first monitoring work */ + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + + LOG_D("%s: schedutil governor started (sampling=%ums)", + rt_dm_dev_get_name(dvfs->dev), dvfs->gov_params.sampling_rate_ms); + + return RT_EOK; +} + +static rt_err_t governor_schedutil_stop(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + + /* Wait for work to complete */ + rt_thread_mdelay(10); + + rt_free(data); + dvfs->gov_data = RT_NULL; + } + + LOG_D("%s: schedutil governor stopped", rt_dm_dev_get_name(dvfs->dev)); + + return RT_EOK; +} + +static rt_err_t governor_schedutil_suspend(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Stop monitoring */ + data = dvfs->gov_data; + if (data) + { + data->running = RT_FALSE; + rt_work_cancel(&data->monitor_work); + } + + return RT_EOK; +} + +static rt_err_t governor_schedutil_resume(struct rt_dvfs_scaling *dvfs) +{ + struct governor_schedutil_data *data; + + if (!dvfs || !dvfs->gov) + { + return -RT_EINVAL; + } + + /* Resume monitoring */ + data = dvfs->gov_data; + if (data) + { + /* Reset CPU load statistics */ + dvfs->cpu_load.last_update = rt_tick_get(); + dvfs->cpu_load.total_tick = 0; + dvfs->cpu_load.idle_tick = 0; + + data->running = RT_TRUE; + rt_work_submit(&data->monitor_work, + rt_tick_from_millisecond(dvfs->gov_params.sampling_rate_ms)); + } + + return RT_EOK; +} + +static rt_err_t governor_schedutil_set_interval(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms) +{ + if (!dvfs) + { + return -RT_EINVAL; + } + + /* Minimum sampling interval: 10ms */ + if (interval_ms < 10) + { + interval_ms = 10; + } + + dvfs->gov_params.sampling_rate_ms = interval_ms; + + LOG_D("%s: schedutil sampling interval set to %ums", + rt_dm_dev_get_name(dvfs->dev), interval_ms); + + return RT_EOK; +} + +static rt_err_t governor_schedutil_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq) +{ + if (!dvfs || !out_freq) + { + return -RT_EINVAL; + } + + /* Schedutil sets frequency based on scheduler utilization */ + /* Return current frequency */ + *out_freq = dvfs->cur_freq; + + return RT_EOK; +} + +static struct rt_dvfs_governor governor_schedutil = +{ + .name = "schedutil", + .type = RT_DVFS_GOVERNOR_TYPE_SCHEDUTIL, + + .start = governor_schedutil_start, + .stop = governor_schedutil_stop, + .suspend = governor_schedutil_suspend, + .resume = governor_schedutil_resume, + .set_interval = governor_schedutil_set_interval, + .set_frequency = governor_schedutil_set_frequency, +}; + +static int governor_schedutil_init(void) +{ + rt_dvfs_governor_register(&governor_schedutil); + + return 0; +} +INIT_CORE_EXPORT(governor_schedutil_init); diff --git a/components/drivers/include/drivers/dvfs.h b/components/drivers/include/drivers/dvfs.h new file mode 100644 index 00000000000..0b224462198 --- /dev/null +++ b/components/drivers/include/drivers/dvfs.h @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#ifndef __DVFS_H__ +#define __DVFS_H__ + +#include +#include +#include +#include + +#include +#include + +enum +{ + /* Run the device at the maximum frequency */ + RT_DVFS_GOVERNOR_TYPE_PERFORMANCE = 0, + /* Run the device at the minimum frequency */ + RT_DVFS_GOVERNOR_TYPE_POWERSAVE, + /* Run the device at user specified frequencies */ + RT_DVFS_GOVERNOR_TYPE_FREEDOM, + /* + * Scales the frequency dynamically according to current load. + * Jumps to the highest frequency and then possibly back off as + * the idle time increases. + */ + RT_DVFS_GOVERNOR_TYPE_ONDEMAND, + /* + * Scales the frequency dynamically according to current load. + * Scales the frequency more gradually than ondemand. + */ + RT_DVFS_GOVERNOR_TYPE_CONSERVATIVE, + /* Scheduler-driven CPU frequency selection */ + RT_DVFS_GOVERNOR_TYPE_SCHEDUTIL, + + /* Custom for user start */ + RT_DVFS_GOVERNOR_TYPE_CUSTOM, +}; + +struct rt_dvfs_opp; +struct rt_dvfs_opp_table; +struct rt_dvfs_governor; +struct rt_dvfs_scaling_ops; +struct rt_dvfs_event_ops; +struct rt_dvfs_idle_ops; +struct rt_dvfs_idle_status; +struct rt_dvfs_idle_status_table; + +/* CPU load statistics */ +struct rt_dvfs_cpu_load +{ + rt_uint64_t total_tick; /* Total ticks in sampling period */ + rt_uint64_t idle_tick; /* Idle ticks in sampling period */ + rt_uint32_t load_percentage; /* Load percentage (0-100) */ + rt_tick_t last_update; /* Last update tick */ +}; + +/* Governor parameters (Linux-like defaults) */ +struct rt_dvfs_governor_params +{ + rt_uint32_t sampling_rate_ms; /* Sampling interval in ms, default 1000 */ + rt_uint32_t up_threshold; /* Up threshold percentage, default 80 */ + rt_uint32_t down_differential; /* Down differential percentage, default 20 */ + rt_uint32_t sampling_down_factor; /* Sampling down factor for conservative, default 1 */ + rt_uint32_t freq_step; /* Frequency step percentage for conservative, default 5 */ + rt_bool_t ignore_nice_load; /* Ignore nice tasks, default RT_FALSE */ + rt_uint32_t powersave_bias; /* Powersave bias percentage, default 0 */ +}; + +struct rt_dvfs_scaling +{ + struct rt_device *dev; + + struct rt_clk *clk; + struct rt_regulator *supply; + + const struct rt_dvfs_scaling_ops *ops; + + /* Hz */ + rt_ubase_t min_freq; + rt_ubase_t max_freq; + rt_ubase_t cur_freq; + rt_ubase_t suspend_freq; + + /* NS */ + rt_uint32_t retry_delay; + rt_uint32_t transition_latency; + + struct rt_dvfs_opp_table *opp_table; + + struct rt_dvfs_governor *gov; + struct rt_dvfs_governor_params gov_params; + void *gov_data; + + /* CPU load statistics */ + struct rt_dvfs_cpu_load cpu_load; + void (*load_update)(struct rt_dvfs_scaling *dvfs); + + void *priv; +}; + +struct rt_dvfs_scaling_ops +{ + rt_err_t (*suspend)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*resume)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*set_opp)(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp); + + rt_err_t (*parse_opp)(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp, void *fw_np); +}; + +/* + * DVFS device hierarchy: + * rt_dvfs_scaling - internal core (OPP / governor / frequency) + * rt_dvfs_devfreq - DVFS device (clk/regulator + optional event) + * rt_dvfs_cpufreq - CPU DVFS, extends devfreq (idle-hook load) + */ +struct rt_dvfs_devfreq +{ + struct rt_dvfs_scaling parent; + + struct rt_dvfs_event *ev; +}; + +struct rt_dvfs_cpufreq +{ + struct rt_dvfs_devfreq parent; + + int master_cpu; + RT_BITMAP_DECLARE(cpus_map, RT_CPUS_NR); +}; + +rt_inline struct rt_dvfs_scaling *rt_dvfs_devfreq_to_scaling(struct rt_dvfs_devfreq *devfreq) +{ + return &devfreq->parent; +} + +rt_inline struct rt_dvfs_devfreq *rt_dvfs_cpufreq_to_devfreq(struct rt_dvfs_cpufreq *cpufreq) +{ + return &cpufreq->parent; +} + +rt_inline struct rt_dvfs_scaling *rt_dvfs_cpufreq_to_scaling(struct rt_dvfs_cpufreq *cpufreq) +{ + return &cpufreq->parent.parent; +} + +extern struct rt_dvfs_scaling_ops rt_dvfs_devfreq_ops; + +struct rt_dvfs_idle +{ + struct rt_device *dev; + + rt_uint32_t ref_count; + rt_uint32_t entry_count; + + const struct rt_dvfs_idle_ops *ops; + + struct rt_dvfs_idle_status_table *status_table; + + void *priv; +}; + +struct rt_dvfs_idle_ops +{ + rt_err_t (*entry)(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status); + rt_err_t (*exit)(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status); + + rt_bool_t (*timer_can_stop)(struct rt_dvfs_idle *idle); +}; + +struct rt_dvfs_idle_status +{ + rt_list_t list; + + rt_uint32_t entry_latency_us; + rt_uint32_t exit_latency_us; + rt_uint32_t min_residency_us; + + rt_bool_t timer_stop; + + void *priv; +}; + +struct rt_dvfs_idle_status_table +{ + rt_list_t status_nodes; + struct rt_dvfs_idle_status *current_status; + + void *priv; +}; + +struct rt_dvfs_opp +{ + rt_list_t list; + + rt_ubase_t freq; /* Hz */ + rt_ubase_t uvolt; /* uV */ + rt_ubase_t power; /* mW */ + rt_bool_t available; + + void *priv; +}; + +struct rt_dvfs_opp_table +{ + rt_bool_t share; + + rt_list_t opp_nodes; + struct rt_dvfs_opp *current_opp; + + void *priv; +}; + +struct rt_dvfs_event_data +{ + /* Load count of dvfs-event device for the given period */ + rt_ubase_t load_count; + /* Total count of dvfs-event device for the given period */ + rt_ubase_t total_count; +}; + +struct rt_dvfs_event +{ + struct rt_device *dev; + + const struct rt_dvfs_event_ops *ops; + + rt_uint32_t enable_count; + struct rt_spinlock lock; + + void *priv; +}; + +struct rt_dvfs_event_ops +{ + rt_err_t (*ready)(struct rt_dvfs_event *ev); + rt_err_t (*read)(struct rt_dvfs_event *ev, struct rt_dvfs_event_data *evd); + + rt_err_t (*enable)(struct rt_dvfs_event *ev); + rt_err_t (*disable)(struct rt_dvfs_event *ev); + rt_err_t (*reset)(struct rt_dvfs_event *ev); +}; + +struct rt_dvfs_governor +{ + rt_list_t list; + char name[16]; + + rt_uint32_t type; + struct rt_ref ref; + + rt_err_t (*start)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*stop)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*suspend)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*resume)(struct rt_dvfs_scaling *dvfs); + rt_err_t (*set_interval)(struct rt_dvfs_scaling *dvfs, rt_uint32_t interval_ms); + rt_err_t (*set_frequency)(struct rt_dvfs_scaling *dvfs, rt_ubase_t *out_freq); +}; + +/* DVFS */ +rt_err_t rt_dvfs_scaling_register(struct rt_dvfs_scaling *dvfs); +rt_err_t rt_dvfs_scaling_unregister(struct rt_dvfs_scaling *dvfs); + +void rt_dvfs_scaling_enter(struct rt_dvfs_scaling *dvfs); +void rt_dvfs_scaling_leave(struct rt_dvfs_scaling *dvfs); + +void rt_dvfs_ns_sleep(rt_uint32_t ns); + +rt_err_t rt_dvfs_scaling_suspend(struct rt_dvfs_scaling *dvfs); +rt_err_t rt_dvfs_scaling_resume(struct rt_dvfs_scaling *dvfs); +rt_err_t rt_dvfs_scaling_set_governor(struct rt_dvfs_scaling *dvfs, rt_uint32_t governor); +rt_err_t rt_dvfs_scaling_set_frequency(struct rt_dvfs_scaling *dvfs, rt_ubase_t frequency); +rt_err_t rt_dvfs_scaling_apply_opp(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp); + +/* OPP */ +struct rt_dvfs_opp *rt_dvfs_scaling_add_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq, rt_ubase_t uvolt); +rt_err_t rt_dvfs_scaling_remove_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); +rt_err_t rt_dvfs_scaling_remove_opp_all(struct rt_dvfs_scaling *dvfs); + +rt_err_t rt_dvfs_scaling_enable_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); +rt_err_t rt_dvfs_scaling_disable_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); + +struct rt_dvfs_opp *rt_dvfs_scaling_find_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); +struct rt_dvfs_opp *rt_dvfs_scaling_find_ceil_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); +struct rt_dvfs_opp *rt_dvfs_scaling_find_floor_opp(struct rt_dvfs_scaling *dvfs, rt_ubase_t freq); + +/* CPU */ +rt_err_t rt_dvfs_cpufreq_register(struct rt_dvfs_cpufreq *cpufreq); +rt_err_t rt_dvfs_cpufreq_unregister(struct rt_dvfs_cpufreq *cpufreq); + +rt_err_t rt_dvfs_devfreq_register(struct rt_dvfs_devfreq *devfreq); +rt_err_t rt_dvfs_devfreq_unregister(struct rt_dvfs_devfreq *devfreq); + +/* CPU-Idle */ +rt_err_t rt_dvfs_idle_register(struct rt_dvfs_idle *idle); +rt_err_t rt_dvfs_idle_unregister(struct rt_dvfs_idle *idle); + +rt_err_t rt_dvfs_idle_add_status(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status); +void rt_dvfs_idle_remove_status(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status); +void rt_dvfs_idle_remove_status_all(struct rt_dvfs_idle *idle, + void (*release)(struct rt_dvfs_idle *, struct rt_dvfs_idle_status *)); + +rt_err_t rt_dvfs_idle_entry(struct rt_dvfs_idle *idle); +rt_err_t rt_dvfs_idle_exit(struct rt_dvfs_idle *idle); + +struct rt_dvfs_idle *rt_dvfs_idle_get(struct rt_device *dev); +void rt_dvfs_idle_put(struct rt_dvfs_idle *idle); + +/* Event */ +rt_err_t rt_dvfs_event_register(struct rt_dvfs_event *ev); +rt_err_t rt_dvfs_event_unregister(struct rt_dvfs_event *ev); + +rt_err_t rt_dvfs_event_ready(struct rt_dvfs_event *ev); +rt_err_t rt_dvfs_event_read(struct rt_dvfs_event *ev, struct rt_dvfs_event_data *evd); +rt_err_t rt_dvfs_event_enable(struct rt_dvfs_event *ev); +rt_err_t rt_dvfs_event_disable(struct rt_dvfs_event *ev); +rt_bool_t rt_dvfs_event_is_enabled(struct rt_dvfs_event *ev); +rt_err_t rt_dvfs_event_reset(struct rt_dvfs_event *ev); + +struct rt_dvfs_event *rt_dvfs_event_get(struct rt_device *dev, const char *name, int index); +void rt_dvfs_event_put(struct rt_dvfs_event *ev); + +/* Governor */ +rt_err_t rt_dvfs_governor_register(struct rt_dvfs_governor *gov); +rt_err_t rt_dvfs_governor_unregister(struct rt_dvfs_governor *gov); +struct rt_dvfs_governor *rt_dvfs_governor_get(rt_uint32_t governor); +struct rt_dvfs_governor *rt_dvfs_governor_get_by_name(const char *name); +void rt_dvfs_governor_put(struct rt_dvfs_governor *gov); + +rt_err_t rt_dvfs_governor_set_params(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_governor_params *params); +rt_err_t rt_dvfs_governor_get_params(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_governor_params *params); + +/* Load */ +void rt_dvfs_load_update(struct rt_dvfs_scaling *dvfs); +void rt_dvfs_cpu_load_update(struct rt_dvfs_cpu_load *load); +rt_uint32_t rt_dvfs_cpu_load_get(struct rt_dvfs_cpu_load *load); + +/* PM */ +struct rt_device_pm; + +rt_err_t rt_dvfs_scaling_pm_suspend(struct rt_device_pm *device_pm, rt_uint8_t mode); +rt_err_t rt_dvfs_scaling_pm_resume(struct rt_device_pm *device_pm, rt_uint8_t mode); +rt_err_t rt_dvfs_scaling_pm_frequency_change(struct rt_device_pm *device_pm, rt_uint8_t mode); + +#endif /* __DVFS_H__ */ diff --git a/components/drivers/include/drivers/thermal.h b/components/drivers/include/drivers/thermal.h index 0770c5c4539..c7c22445824 100644 --- a/components/drivers/include/drivers/thermal.h +++ b/components/drivers/include/drivers/thermal.h @@ -59,6 +59,10 @@ struct rt_thermal_cooling_cell struct rt_thermal_cooling_device *cooling_devices; rt_uint32_t level_range[2]; + +#ifdef RT_USING_OFW + struct rt_ofw_node *cooling_np; +#endif }; struct rt_thermal_cooling_map diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h index 0c831370870..b416b5e9280 100644 --- a/components/drivers/include/rtdevice.h +++ b/components/drivers/include/rtdevice.h @@ -153,6 +153,10 @@ extern "C" { #include "drivers/hwcache.h" #endif /* RT_USING_HWCACHE */ +#ifdef RT_USING_DVFS +#include "drivers/dvfs.h" +#endif /* RT_USING_DVFS */ + #ifdef RT_USING_NVMEM #include "drivers/nvmem.h" #endif /* RT_USING_NVMEM */ diff --git a/components/drivers/thermal/Kconfig b/components/drivers/thermal/Kconfig index 91a394fe8bd..bdde92c728b 100644 --- a/components/drivers/thermal/Kconfig +++ b/components/drivers/thermal/Kconfig @@ -21,6 +21,12 @@ if RT_USING_THERMAL comment "Thermal Cool Drivers" endif +config RT_THERMAL_COOL_DVFS + bool "DVFS" + depends on RT_USING_THERMAL + depends on RT_USING_DVFS + default n + config RT_THERMAL_COOL_PWM_FAN bool "PWM Fan" depends on RT_USING_THERMAL diff --git a/components/drivers/thermal/SConscript b/components/drivers/thermal/SConscript index 31ba3023819..015ff18d5a6 100644 --- a/components/drivers/thermal/SConscript +++ b/components/drivers/thermal/SConscript @@ -13,6 +13,9 @@ src = ['thermal.c', 'thermal_dm.c'] if GetDepend(['RT_THERMAL_SCMI']): src += ['thermal-scmi.c'] +if GetDepend(['RT_THERMAL_COOL_DVFS']): + src += ['thermal-cool-dvfs.c'] + if GetDepend(['RT_THERMAL_COOL_PWM_FAN']): src += ['thermal-cool-pwm-fan.c'] diff --git a/components/drivers/thermal/thermal-cool-dvfs.c b/components/drivers/thermal/thermal-cool-dvfs.c new file mode 100644 index 00000000000..fb72cec9a52 --- /dev/null +++ b/components/drivers/thermal/thermal-cool-dvfs.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-02-25 GuEe-GUI the first version + */ + +#include +#include +#include + +#define DBG_TAG "thermal.cool.dvfs" +#define DBG_LVL DBG_INFO +#include + +struct dvfs_cool +{ + struct rt_thermal_cooling_device parent; + + struct rt_dvfs_scaling *dvfs; +}; + +#define raw_to_dvfs_cool(raw) rt_container_of(raw, struct dvfs_cool, parent) + +static rt_err_t dvfs_cool_get_max_level(struct rt_thermal_cooling_device *cdev, + rt_ubase_t *out_level) +{ + struct dvfs_cool *dvfs_cool = raw_to_dvfs_cool(cdev); + struct rt_dvfs_scaling *dvfs = dvfs_cool->dvfs; + + rt_dvfs_scaling_enter(dvfs); + + if (dvfs->opp_table->opp_nodes.next) + { + *out_level = rt_list_len(&dvfs->opp_table->opp_nodes); + + if (*out_level > 0) + { + (*out_level)--; + } + } + else + { + *out_level = 0; + } + + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +static rt_err_t dvfs_cool_get_cur_level(struct rt_thermal_cooling_device *cdev, + rt_ubase_t *out_level) +{ + struct rt_dvfs_opp *opp; + struct dvfs_cool *dvfs_cool = raw_to_dvfs_cool(cdev); + struct rt_dvfs_scaling *dvfs = dvfs_cool->dvfs; + + rt_dvfs_scaling_enter(dvfs); + + *out_level = 0; + rt_list_for_each_entry(opp, &dvfs->opp_table->opp_nodes, list) + { + if (opp == dvfs->opp_table->current_opp) + { + break; + } + + (*out_level)++; + } + + rt_dvfs_scaling_leave(dvfs); + + return RT_EOK; +} + +static rt_err_t dvfs_cool_set_cur_level(struct rt_thermal_cooling_device *cdev, + rt_ubase_t level) +{ + struct rt_dvfs_opp *opp, *target_opp = RT_NULL; + struct dvfs_cool *dvfs_cool = raw_to_dvfs_cool(cdev); + struct rt_dvfs_scaling *dvfs = dvfs_cool->dvfs; + + rt_dvfs_scaling_enter(dvfs); + + rt_list_for_each_entry(opp, &dvfs->opp_table->opp_nodes, list) + { + if (!level) + { + target_opp = opp; + break; + } + + --level; + } + + rt_dvfs_scaling_leave(dvfs); + + if (target_opp) + { + return rt_dvfs_scaling_apply_opp(dvfs, target_opp); + } + + return -RT_EINVAL; +} + +const static struct rt_thermal_cooling_device_ops dvfs_cool_ops = +{ + .get_max_level = dvfs_cool_get_max_level, + .get_cur_level = dvfs_cool_get_cur_level, + .set_cur_level = dvfs_cool_set_cur_level, +}; + +static rt_err_t dvfs_cool_probe(struct rt_platform_device *pdev) +{ + rt_err_t err; + struct dvfs_cool *dvfs_cool; + struct rt_device *dev = &pdev->parent; + + if (!pdev->priv) + { + return -RT_EINVAL; + } + + if (!(dvfs_cool = rt_calloc(1, sizeof(*dvfs_cool)))) + { + return -RT_ENOMEM; + } + dvfs_cool->dvfs = pdev->priv; + + rt_dm_dev_set_name(&dvfs_cool->parent.parent, "%s-cool", + rt_dm_dev_get_name(dvfs_cool->dvfs->dev)); + dvfs_cool->parent.parent.ofw_node = dev->ofw_node; + dvfs_cool->parent.ops = &dvfs_cool_ops; + + if ((err = rt_thermal_cooling_device_register(&dvfs_cool->parent))) + { + goto _fail; + } + + dev->user_data = dvfs_cool; + + return RT_EOK; + +_fail: + rt_free(dvfs_cool); + + return err; +} + +static rt_err_t dvfs_cool_remove(struct rt_platform_device *pdev) +{ + struct dvfs_cool *dvfs_cool = pdev->parent.user_data; + + rt_thermal_cooling_device_unregister(&dvfs_cool->parent); + rt_free(dvfs_cool); + + return RT_EOK; +} + +static struct rt_platform_driver dvfs_cool_driver = +{ + .name = "dvfs-cool", + + .probe = dvfs_cool_probe, + .remove = dvfs_cool_remove, +}; +RT_PLATFORM_DRIVER_EXPORT(dvfs_cool_driver); diff --git a/components/drivers/thermal/thermal.c b/components/drivers/thermal/thermal.c index 7a5016dabc0..dc87ec87d03 100644 --- a/components/drivers/thermal/thermal.c +++ b/components/drivers/thermal/thermal.c @@ -199,6 +199,8 @@ static void thermal_ofw_setup(struct rt_ofw_node *np, struct rt_thermal_zone_dev } 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 @@ static void thermal_ofw_setup(struct rt_ofw_node *np, struct rt_thermal_zone_dev { 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(cell->cooling_devices, zdev); + } + +#ifdef RT_USING_OFW + if (cell->cooling_np) { - thermal_unbind(cdev, zdev); + 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; + } + + 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 > cdev->max_level) + if (cdev->ops->get_cur_level(cdev, &level) || level > max_level) { continue; } @@ -754,7 +818,7 @@ void rt_thermal_cooling_device_kick(struct rt_thermal_zone_device *zdev) } cdev->gov->tuning(zdev, i, c, &level); - level = rt_min_t(rt_ubase_t, level, cdev->max_level); + level = rt_min_t(rt_ubase_t, level, max_level); cdev->ops->set_cur_level(cdev, level); } diff --git a/components/drivers/thermal/thermal_dm.h b/components/drivers/thermal/thermal_dm.h index 15d3314a58c..cc8df32d3f6 100644 --- a/components/drivers/thermal/thermal_dm.h +++ b/components/drivers/thermal/thermal_dm.h @@ -24,4 +24,6 @@ rt_err_t thermal_bind(struct rt_thermal_cooling_device *cdev, rt_err_t thermal_unbind(struct rt_thermal_cooling_device *cdev, struct rt_thermal_zone_device *zdev); +void thermal_cooling_device_bind_zones(struct rt_thermal_cooling_device *cdev); + #endif /* __THERMAL_DM_H__ */ diff --git a/documentation/6.components/device-driver/INDEX.md b/documentation/6.components/device-driver/INDEX.md index 0994df23f72..a2659b4449f 100644 --- a/documentation/6.components/device-driver/INDEX.md +++ b/documentation/6.components/device-driver/INDEX.md @@ -34,6 +34,7 @@ - @subpage page_device_disk - @subpage page_device_partitions - @subpage page_device_hwcache +- @subpage page_device_dvfs - @subpage page_device_can - @subpage page_device_can_dm - @subpage page_device_clk diff --git a/documentation/6.components/device-driver/dvfs/dvfs.md b/documentation/6.components/device-driver/dvfs/dvfs.md new file mode 100644 index 00000000000..d6c667c57bb --- /dev/null +++ b/documentation/6.components/device-driver/dvfs/dvfs.md @@ -0,0 +1,178 @@ +@page page_device_dvfs DVFS + +# Dynamic Voltage and Frequency Scaling (DVFS) + +Headers: **`components/drivers/include/drivers/dvfs.h`**. Core: **`components/drivers/dvfs/dvfs.c`**, governors: **`components/drivers/dvfs/governor/`**, shell: **`components/drivers/dvfs/dvfs_cmd.c`**. + +DVFS selects an **OPP (Operating Performance Point)** — a frequency and supply voltage pair — according to a **governor** policy. The framework owns the OPP table, governor lifecycle, and MSH commands; **BSP or SoC drivers** implement how a given OPP is applied (clock, regulator, firmware calls). + +**Kconfig**: **`RT_USING_DVFS`** (requires **`RT_USING_DM`**, **`RT_USING_CLK`**, **`RT_USING_REGULATOR`**). Optional: **`RT_USING_DVFS_EVENT`**, **`RT_DVFS_SCMI_CPUFREQ`**, and BSP options under **`SOC_DM_DVFS_*`**. + +--- + +## Architecture + +``` +Device tree (operating-points-v2, supplies, clocks) + | + v +BSP cpufreq / devfreq driver (SOC_DM_DVFS_CPUFREQ_DIR, …) + fills rt_dvfs_scaling_ops (set_opp, parse_opp, …) + | + v +rt_dvfs_cpufreq_register() / rt_dvfs_devfreq_register() + | + v +rt_dvfs_scaling (OPP table, cur_freq, governor) + | + +-- governor (performance / ondemand / …) + +-- thermal-cool-dvfs (optional, RT_THERMAL_COOL_DVFS) + +-- rt_dvfs_scaling_set_frequency() / apply_opp() +``` + +| Layer | Location | Role | +| --- | --- | --- | +| Framework | `components/drivers/dvfs/` | OPP table, governors, load stats, PM hooks, MSH | +| Generic SCMI | `dvfs-scmi-cpufreq.c` | Optional SCMI-based CPUfreq (**`RT_DVFS_SCMI_CPUFREQ`**) | +| BSP | **`SOC_DM_DVFS_CPUFREQ_DIR`**, **`SOC_DM_DVFS_DEVFREQ_DIR`**, **`SOC_DM_DVFS_EVENT_DIR`** | Platform probe, **`set_opp`**, DT parsing | +| Thermal | `thermal-cool-dvfs.c` | Map cooling levels to OPP indices — @ref page_device_thermal_cool | + +When **`RT_USING_DVFS`** is enabled, **`struct rt_device::dvfs_scaling`** links a device to its scaling domain for **`list_dvfs`** / **`dvfs dump`**. + +--- + +## Registering a scaling domain (BSP driver) + +Typical CPU path: + +1. Allocate **`struct rt_dvfs_cpufreq`** (embeds **`struct rt_dvfs_scaling`**). +2. Fill **`scaling->ops`** — at minimum **`set_opp`** and **`parse_opp`** for DT-backed OPP tables. +3. Obtain **`scaling->clk`** and **`scaling->supply`** from the CPU / cluster device node. +4. Parse OPP nodes ( **`operating-points-v2`** ) into the scaling OPP table. +5. Call **`rt_dvfs_cpufreq_register()`**, then **`rt_dvfs_scaling_set_governor()`** with the desired default governor. + +Devfreq devices follow the same pattern with **`struct rt_dvfs_devfreq`** and optional **`struct rt_dvfs_event`** for load feedback (**`RT_USING_DVFS_EVENT`**). + +If the driver does not provide **`ops->set_opp`**, the framework falls back to a generic sequence using **`rt_clk_set_rate`** and **`rt_regulator_set_voltage`** on **`scaling->clk`** / **`scaling->supply`**. + +--- + +## Governors + +| Name | Behavior | +| --- | --- | +| **`performance`** | Always **`max_freq`** | +| **`powersave`** | Always **`min_freq`** | +| **`freedom`** | No automatic changes; user sets frequency manually | +| **`ondemand`** | Periodic load sampling; high load → max, low load → scaled down | +| **`conservative`** | Step up/down by **`freq_step`** | +| **`schedutil`** | Target frequency proportional to estimated load | + +Dynamic governors depend on **`RT_USING_IDLE_HOOK`** (load estimation) and **`RT_USING_SYSTEM_WORKQUEUE`** (monitor timer). The default governor is chosen by the BSP driver at registration time. + +Governor parameters (**`up_threshold`**, **`sampling_rate_ms`**, …) live in **`struct rt_dvfs_governor_params`** and can be adjusted via **`rt_dvfs_governor_set_params()`**. + +--- + +## MSH commands + +Requires **`RT_USING_CONSOLE`** and **`RT_USING_MSH`** (**`dvfs_cmd.c`**). + +```text +list_dvfs +dvfs dump +dvfs set_governor +dvfs set_frequency +``` + +**`set_frequency`** switches to **`freedom`** automatically when the active governor is not freedom. + +Example: + +```text +msh />dvfs set_governor cpu0 performance +msh />dvfs dump cpu0 +``` + +The scaling device **``** is assigned by the BSP driver (commonly **`cpufreq0`**, **`dmc`**, etc.). + +--- + +## Device tree (typical CPU) + +Minimal pattern (details vary by SoC BSP): + +```dts +cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a55"; + operating-points-v2 = <&cpu0_opp_table>; + cpu-supply = <&cpu_reg>; + /* optional: #cooling-cells for thermal-cool-dvfs */ +}; + +cpu0_opp_table: opp-table { + compatible = "operating-points-v2"; + opp-1000000000 { + opp-hz = /bits/ 64 <1000000000>; + opp-microvolt = <900000>; + }; + /* additional OPP nodes … */ +}; +``` + +| Binding | Role | +| --- | --- | +| **`operating-points-v2`** | OPP table phandle on the scaling device | +| **`opp-hz` / `opp-microvolt`** | Frequency (Hz) and voltage (µV) per OPP | +| **`*-supply`** | Regulator phandle(s) used when scaling voltage | +| **`#cooling-cells`** | Enables **`dvfs-cool`** thermal device on the CPU node | + +Passive thermal **`cooling-maps`** can reference the DVFS cooling device to cap OPP on trip — see @ref page_device_thermal_cool. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_DVFS`** | Core framework + governors | +| **`RT_USING_DVFS_EVENT`** | **`dvfs_event.c`**, devfreq load via event devices | +| **`RT_USING_DVFS_OPP_RETRY_MAX`** | Retries on **`-RT_EBUSY`** during OPP transition | +| **`RT_DVFS_SCMI_CPUFREQ`** | Generic **`dvfs-scmi-cpufreq.c`** | +| **`RT_THERMAL_COOL_DVFS`** | **`thermal-cool-dvfs.c`** (also needs **`RT_USING_THERMAL`**) | +| **`RT_USING_IDLE_HOOK`** | Required for meaningful dynamic governor load stats | +| **`SOC_DM_DVFS_CPUFREQ_DIR`** | BSP CPUfreq driver(s) | +| **`SOC_DM_DVFS_DEVFREQ_DIR`** | BSP devfreq driver(s) | +| **`SOC_DM_DVFS_EVENT_DIR`** | BSP devfreq event source(s) | + +--- + +## OPP transition order + +When **`ops->set_opp`** is provided, the BSP defines the safe ramp sequence ( voltage-before-frequency on scale-up, etc.). + +When the framework generic path is used (**`dvfs.c`**): + +- **Scale up**: raise voltage (if needed) → optional **`transition_latency`** delay → **`rt_clk_set_rate`** +- **Scale down**: lower clock rate → lower voltage (if needed) + +On failure, **`cur_freq`** and **`current_opp`** are not updated. **`RT_USING_DVFS_OPP_RETRY_MAX`** controls busy retries on regulator or clock calls. + +--- + +## Pitfalls + +- **BSP `set_opp` must match hardware**: clocks, regulators, and any bus used to change voltage must remain usable across the full OPP range. Broken I2C/SPI/regulator access during a transition surfaces as stuck frequency or **`-RT_EBUSY`** retries. +- **Governor switch vs. pending work**: dynamic governors schedule work on the system workqueue. After **`set_governor`**, monitor callbacks must verify the active governor type before changing frequency (see governor sources). +- **Thermal cooling levels**: **`thermal-cool-dvfs`** maps cooling **level** to an OPP index; releasing cooling must restore full performance (highest OPP), not the most restrictive level. See **`rt_thermal_cooling_device_kick`** in **`thermal.c`**. +- **Verifying dynamic governors**: do not run a tight CPU loop inside the **FinSH / shell thread** — it usually has higher priority than the system workqueue and can prevent load sampling. Use a **separate background thread** at lower priority, or compare **`performance`** vs **`powersave`** (static governors) to confirm the scaling path works before testing **`ondemand`** / **`schedutil`**. + +--- + +## Related pages + +- @ref page_device_clk — **`rt_clk_set_rate`**, consumer clock API +- @ref page_device_regulator — supply rails referenced from OPP / CPU nodes +- @ref page_device_scmi — SCMI clock/regulator when firmware owns scaling resources +- @ref page_device_thermal_cool — **`thermal-cool-dvfs`** diff --git a/documentation/6.components/device-driver/thermal/thermal.md b/documentation/6.components/device-driver/thermal/thermal.md index 148dc1395a6..4a679223b57 100755 --- a/documentation/6.components/device-driver/thermal/thermal.md +++ b/documentation/6.components/device-driver/thermal/thermal.md @@ -213,7 +213,7 @@ Call **`update`** from the zone poller (automatic after register) or manually fo | **`thermal-scmi.c`** | **`RT_SCMI_DRIVER_EXPORT`**, protocol sensor `"thermal"` | One zone per SCMI temperature sensor | | **`thermal-cool-pwm-fan.c`** | **`compatible = "pwm-fan"`** | PWM + optional regulator — @ref page_device_thermal_cool | | **`thermal-cool-gpio-fan.c`** | GPIO + speed table | Discrete fan speeds | -| **`thermal-cool-dvfs.c`** | DVFS OPP levels as cooling steps | Throttle CPU/GPU via @ref page_device_clk / DVFS | +| **`thermal-cool-dvfs.c`** | DVFS OPP levels as cooling steps | Throttle CPU/GPU via @ref page_device_dvfs | BSP sensors (e.g. board thermal IC) typically **`rt_thermal_zone_device_register`** in platform **`probe`** after filling trips in code or relying entirely on **`thermal_ofw_setup`**. @@ -251,6 +251,7 @@ Constants: **`RT_THERMAL_TEMP_INVALID`** (−274000), **`RT_THERMAL_NO_LIMIT`**. ## See also - @ref page_device_thermal_cool — PWM fan cooling device +- @ref page_device_dvfs — DVFS framework and Rockchip cpufreq - @ref page_device_scmi — SCMI sensor protocol - @ref page_device_pwm - @ref page_device_regulator diff --git a/include/rtdef.h b/include/rtdef.h index db711aadff8..ab97aeb917d 100644 --- a/include/rtdef.h +++ b/include/rtdef.h @@ -1377,6 +1377,9 @@ struct rt_device void *ofw_node; /**< ofw node get from device tree */ #endif /* RT_USING_OFW */ void *power_domain_unit; +#ifdef RT_USING_DVFS + void *dvfs_scaling; +#endif #ifdef RT_USING_DMA const void *dma_ops; #endif From 5cf18744ae28b5417ec11cabc49adddccf535ca3 Mon Sep 17 00:00:00 2001 From: GuEe-GUI <2991707448@qq.com> Date: Mon, 15 Jun 2026 15:35:26 +0800 Subject: [PATCH 2/3] [libcpu][aarch64] support DVFS idle Signed-off-by: GuEe-GUI <2991707448@qq.com> --- libcpu/aarch64/Kconfig | 11 ++ libcpu/aarch64/common/SConscript | 3 + libcpu/aarch64/common/cpuidle.c | 203 +++++++++++++++++++++++++++++++ libcpu/aarch64/common/setup.c | 61 ++++++++++ 4 files changed, 278 insertions(+) create mode 100644 libcpu/aarch64/common/cpuidle.c diff --git a/libcpu/aarch64/Kconfig b/libcpu/aarch64/Kconfig index 5429751d3a1..d2f531da244 100644 --- a/libcpu/aarch64/Kconfig +++ b/libcpu/aarch64/Kconfig @@ -22,4 +22,15 @@ menu "AArch64 Architecture Configuration" config ARCH_INIT_PAGE_SIZE hex "Size of init page region" default 0x200000 + + config ARCH_USING_CPUIDLE + bool "Using ARM PSCI CPU idle (cpuidle)" + depends on RT_USING_DVFS + depends on RT_USING_OFW + default n + help + Enable PSCI-based CPU idle states from /cpus/idle-states. + Standby states use CPU_SUSPEND with entry_point=0. + Power-down states are skipped until full suspend context save + is implemented. endmenu diff --git a/libcpu/aarch64/common/SConscript b/libcpu/aarch64/common/SConscript index b0e26988988..a32e8c52d62 100644 --- a/libcpu/aarch64/common/SConscript +++ b/libcpu/aarch64/common/SConscript @@ -34,6 +34,9 @@ if GetDepend('RT_USING_PIC') == True: if GetDepend('RT_CLOCK_TIME_ARM_ARCH') == True: SrcRemove(src, ['gtimer.c']) +if GetDepend('ARCH_USING_CPUIDLE') == False: + SrcRemove(src, ['cpuidle.c']) + group = DefineGroup('CPU', src, depend = [''], CPPPATH = CPPPATH) # build for sub-directory diff --git a/libcpu/aarch64/common/cpuidle.c b/libcpu/aarch64/common/cpuidle.c new file mode 100644 index 00000000000..6dca5453842 --- /dev/null +++ b/libcpu/aarch64/common/cpuidle.c @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + */ + +#include +#include + +#include + +#define DBG_TAG "cpuidle" +#define DBG_LVL DBG_INFO +#include + +struct arm_cpuidle +{ + struct rt_dvfs_idle parent; + struct rt_device device; + + rt_bool_t arch_timer_wakeup_source; +}; + +#if defined(RT_CLOCK_TIME_ARM_ARCH) || defined(RT_HWTIMER_ARM_ARCH) +#define ARM_CPUIDLE_ARCH_TIMER 1 +#endif + +#ifdef __GNUC__ +__asm__ ( +"asm_arm_cpuidle_entry: \n\t" +" stp x29, x30, [sp, #-0x20]! \n\t" +" stp x19, x20, [sp, #0x10] \n\t" +" mov x19, x0 \n\t" +#ifdef ARM_CPUIDLE_ARCH_TIMER +" bl arm_arch_timer_local_disable\n\t" +#endif +" mov x0, x19 \n\t" +" mov x1, xzr \n\t" +" bl rt_psci_cpu_suspend \n\t" +" mov x20, x0 \n\t" +#ifdef ARM_CPUIDLE_ARCH_TIMER +" bl arm_arch_timer_local_enable \n\t" +#endif +" mov x0, x20 \n\t" +" ldp x19, x20, [sp, #0x10] \n\t" +" ldp x29, x30, [sp], #0x20 \n\t" +" ret \n\t" + ); +#else +#error "No suspend context save" +#endif + +extern rt_uint32_t asm_arm_cpuidle_entry(rt_uint32_t psci_param); + +static rt_bool_t arm_cpuidle_state_loses_context(rt_uint32_t state) +{ + return PSCI_POWER_STATE_TYPE_VAL(state) == PSCI_POWER_STATE_TYPE_POWER_DOWN; +} + +static rt_err_t arm_cpuidle_entry(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status) +{ + return asm_arm_cpuidle_entry((rt_ubase_t)status->priv) >= 0 ? RT_EOK : -RT_ENOSYS; +} + +static rt_err_t arm_cpuidle_exit(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status) +{ + return RT_EOK; +} + +static rt_bool_t arm_cpuidle_timer_can_stop(struct rt_dvfs_idle *idle) +{ + struct arm_cpuidle *cpuidle = rt_container_of(idle, struct arm_cpuidle, parent); + + return !cpuidle->arch_timer_wakeup_source; +} + +static struct rt_dvfs_idle_ops arm_cpuidle_ops = +{ + .entry = arm_cpuidle_entry, + .exit = arm_cpuidle_exit, + .timer_can_stop = arm_cpuidle_timer_can_stop, +}; + +#ifdef ARM_CPUIDLE_ARCH_TIMER +static const struct rt_ofw_node_id arm_arch_timer_ofw_ids[] = +{ + { .compatible = "arm,armv7-timer", }, + { .compatible = "arm,armv8-timer", }, + { /* sentinel */ } +}; +#endif + +static void arm_cpuidle_status_free(struct rt_dvfs_idle *idle, struct rt_dvfs_idle_status *status) +{ + rt_free(status); +} + +static int arm_cpuidle_probe(void) +{ + const char *entry_method; + struct arm_cpuidle *cpuidle; + struct rt_ofw_node *np, *child_np, *cpu_np; + struct rt_dvfs_idle_status *status; + rt_bool_t timer_wakeup = RT_FALSE; + + if (!(np = rt_ofw_find_node_by_path("/cpus/idle-states"))) + { + return (int)-RT_ENOSYS; + } + + if (rt_ofw_prop_read_string(np, "entry-method", &entry_method)) + { + return (int)-RT_EINVAL; + } + + if (rt_strcmp(entry_method, "psci") && + rt_strcmp(entry_method, "arm,psci")) + { + return (int)-RT_ENOSYS; + } + +#ifdef ARM_CPUIDLE_ARCH_TIMER + /* Check arch timer wakeup source */ + struct rt_ofw_node *timer_np; + if ((timer_np = rt_ofw_find_node_by_ids(RT_NULL, arm_arch_timer_ofw_ids))) + { + timer_wakeup = rt_ofw_prop_read_bool(timer_np, "wakeup-source"); + rt_ofw_node_put(timer_np); + } +#endif + + /* Iterate over all CPUs and register idle devices */ + rt_ofw_foreach_cpu_node(cpu_np) + { + int cpu_id = rt_ofw_get_cpu_id(cpu_np); + if (cpu_id < 0 || cpu_id >= RT_CPUS_NR) + { + continue; + } + + if (!(cpuidle = rt_calloc(1, sizeof(*cpuidle)))) + { + return (int)-RT_ENOMEM; + } + + /* Parse idle states */ + rt_ofw_foreach_child_node(np, child_np) + { + rt_uint32_t psci_param; + + if (rt_ofw_prop_read_u32(child_np, "arm,psci-suspend-param", &psci_param)) + { + psci_param = PSCI_POWER_STATE( + PSCI_POWER_STATE_LEVEL_CORES, PSCI_POWER_STATE_TYPE_STANDBY, 0); + } + + if (arm_cpuidle_state_loses_context(psci_param)) + { + LOG_W("skip power-down idle state on %s (context save not supported)", + rt_ofw_node_full_name(child_np)); + continue; + } + + status = rt_calloc(1, sizeof(*status)); + + if (!status) + { + rt_dvfs_idle_remove_status_all(&cpuidle->parent, arm_cpuidle_status_free); + rt_free(cpuidle); + return (int)-RT_ENOMEM; + } + + rt_ofw_prop_read_u32(child_np, "entry-latency-us", &status->entry_latency_us); + rt_ofw_prop_read_u32(child_np, "exit-latency-us", &status->exit_latency_us); + rt_ofw_prop_read_u32(child_np, "min-residency-us", &status->min_residency_us); + status->timer_stop = rt_ofw_prop_read_bool(child_np, "local-timer-stop"); + status->priv = (void *)(rt_ubase_t)psci_param; + + rt_dvfs_idle_add_status(&cpuidle->parent, status); + } + + /* Check if any idle states were added */ + if (!cpuidle->parent.status_table || rt_list_isempty(&cpuidle->parent.status_table->status_nodes)) + { + rt_free(cpuidle); + continue; + } + + cpuidle->device.ofw_node = cpu_np; + cpuidle->parent.dev = &cpuidle->device; + cpuidle->parent.ops = &arm_cpuidle_ops; + cpuidle->arch_timer_wakeup_source = timer_wakeup; + + rt_dvfs_idle_register(&cpuidle->parent); + } + + return RT_EOK; +} +INIT_PLATFORM_EXPORT(arm_cpuidle_probe); diff --git a/libcpu/aarch64/common/setup.c b/libcpu/aarch64/common/setup.c index 79df36ec5be..89acc150090 100644 --- a/libcpu/aarch64/common/setup.c +++ b/libcpu/aarch64/common/setup.c @@ -57,6 +57,9 @@ static struct cpu_ops_t *cpu_ops[] = #endif }; +#ifdef ARCH_USING_CPUIDLE +struct rt_dvfs_idle *cpu_idle[RT_CPUS_NR] = {}; +#endif static struct rt_ofw_node *cpu_np[RT_CPUS_NR] = { }; void rt_hw_fdt_install_early(void *fdt) @@ -136,6 +139,21 @@ static void cpu_us_delay(rt_uint32_t us) rt_weak void rt_hw_idle_wfi(void) { +#ifdef ARCH_USING_CPUIDLE + struct rt_dvfs_idle *cpuidle = cpu_idle[rt_hw_cpu_id()]; + + if (cpuidle) + { + rt_dvfs_idle_entry(cpuidle); + + __asm__ volatile ("wfi"); + + rt_dvfs_idle_exit(cpuidle); + + return; + } +#endif /* ARCH_USING_CPUIDLE */ + __asm__ volatile ("wfi"); } @@ -409,6 +427,34 @@ void rt_hw_common_setup(void) #endif } +#ifdef ARCH_USING_CPUIDLE +static int cpuidle_init(void) +{ + static struct rt_device cpuidle_dev = {}; + + for (int i = 0; i < RT_ARRAY_SIZE(cpu_idle); ++i) + { + struct rt_dvfs_idle *cpuidle; + + if (!cpu_np[i]) + { + continue; + } + + cpuidle_dev.ofw_node = cpu_np[i]; + cpuidle = rt_dvfs_idle_get(&cpuidle_dev); + + if (!rt_is_err(cpuidle)) + { + cpu_idle[i] = cpuidle; + } + } + + return 0; +} +INIT_PREV_EXPORT(cpuidle_init); +#endif /* ARCH_USING_CPUIDLE */ + #ifdef RT_USING_SMP rt_weak void rt_hw_secondary_cpu_up(void) { @@ -503,6 +549,21 @@ rt_weak void rt_hw_secondary_cpu_bsp_start(void) rt_weak void rt_hw_secondary_cpu_idle_exec(void) { +#ifdef ARCH_USING_CPUIDLE + struct rt_dvfs_idle *cpuidle = cpu_idle[rt_hw_cpu_id()]; + + if (cpuidle) + { + rt_dvfs_idle_entry(cpuidle); + + rt_hw_wfe(); + + rt_dvfs_idle_exit(cpuidle); + + return; + } +#endif /* ARCH_USING_CPUIDLE */ + rt_hw_wfe(); } #endif From b0e09a33b97e8aca0e44a4e15474c7694438c090 Mon Sep 17 00:00:00 2001 From: GuEe-GUI <2991707448@qq.com> Date: Mon, 15 Jun 2026 15:10:18 +0800 Subject: [PATCH 3/3] [bsp][rockchip] support DVFS for Rockchip The I2C will timeout in fan53555, fixed it. Signed-off-by: GuEe-GUI <2991707448@qq.com> --- bsp/rockchip/dm/Kconfig | 3 + bsp/rockchip/dm/dvfs/SConscript | 21 + bsp/rockchip/dm/dvfs/cpufreq/Kconfig | 10 + bsp/rockchip/dm/dvfs/cpufreq/SConscript | 13 + .../dm/dvfs/cpufreq/dvfs-rockchip-cpufreq.c | 343 ++++++ bsp/rockchip/dm/dvfs/devfreq/Kconfig | 10 + bsp/rockchip/dm/dvfs/devfreq/SConscript | 13 + .../dm/dvfs/devfreq/dvfs-rockchip-dmc.c | 181 +++ bsp/rockchip/dm/dvfs/event/Kconfig | 4 + bsp/rockchip/dm/dvfs/event/SConscript | 13 + .../dm/dvfs/event/event-rockchip-dfi.c | 463 +++++++ bsp/rockchip/dm/i2c/i2c-rk3x.c | 354 ++++-- bsp/rockchip/dm/include/opp-select.h | 102 ++ bsp/rockchip/dm/soc/SConscript | 3 + bsp/rockchip/dm/soc/opp-select.c | 829 +++++++++++++ bsp/rockchip/rk3500/.config | 96 +- bsp/rockchip/rk3500/rtconfig.h | 45 +- components/drivers/regulator/Kconfig | 6 + components/drivers/regulator/SConscript | 3 + .../drivers/regulator/regulator-fan53555.c | 1091 +++++++++++++++++ 20 files changed, 3456 insertions(+), 147 deletions(-) create mode 100755 bsp/rockchip/dm/dvfs/SConscript create mode 100755 bsp/rockchip/dm/dvfs/cpufreq/Kconfig create mode 100644 bsp/rockchip/dm/dvfs/cpufreq/SConscript create mode 100644 bsp/rockchip/dm/dvfs/cpufreq/dvfs-rockchip-cpufreq.c create mode 100755 bsp/rockchip/dm/dvfs/devfreq/Kconfig create mode 100644 bsp/rockchip/dm/dvfs/devfreq/SConscript create mode 100755 bsp/rockchip/dm/dvfs/devfreq/dvfs-rockchip-dmc.c create mode 100755 bsp/rockchip/dm/dvfs/event/Kconfig create mode 100644 bsp/rockchip/dm/dvfs/event/SConscript create mode 100755 bsp/rockchip/dm/dvfs/event/event-rockchip-dfi.c create mode 100644 bsp/rockchip/dm/include/opp-select.h create mode 100644 bsp/rockchip/dm/soc/opp-select.c create mode 100644 components/drivers/regulator/regulator-fan53555.c diff --git a/bsp/rockchip/dm/Kconfig b/bsp/rockchip/dm/Kconfig index 976548e8eb7..36d8d27cbbc 100755 --- a/bsp/rockchip/dm/Kconfig +++ b/bsp/rockchip/dm/Kconfig @@ -1,6 +1,9 @@ SOC_DM_ADC_DIR = $(SOC_DM_DIR)/adc SOC_DM_CAN_DIR = $(SOC_DM_DIR)/can SOC_DM_CLK_DIR = $(SOC_DM_DIR)/clk +SOC_DM_DVFS_EVENT_DIR = $(SOC_DM_DIR)/dvfs/event +SOC_DM_DVFS_CPUFREQ_DIR = $(SOC_DM_DIR)/dvfs/cpufreq +SOC_DM_DVFS_DEVFREQ_DIR = $(SOC_DM_DIR)/dvfs/devfreq SOC_DM_HWCRYPTO_DIR = $(SOC_DM_DIR)/hwcrypto SOC_DM_HWSPINLOCK_DIR = $(SOC_DM_DIR)/hwspinlock SOC_DM_CLOCK_TIME_DIR = $(SOC_DM_DIR)/hwtimer diff --git a/bsp/rockchip/dm/dvfs/SConscript b/bsp/rockchip/dm/dvfs/SConscript new file mode 100755 index 00000000000..07281ac7e76 --- /dev/null +++ b/bsp/rockchip/dm/dvfs/SConscript @@ -0,0 +1,21 @@ +from building import * + +group = [] + +if not GetDepend(['RT_USING_DVFS']): + Return('group') + +cwd = GetCurrentDir() +list = os.listdir(cwd) +CPPPATH = [cwd + '/../include'] + +src = [] + +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')): + group = group + SConscript(os.path.join(d, 'SConscript')) + +Return('group') diff --git a/bsp/rockchip/dm/dvfs/cpufreq/Kconfig b/bsp/rockchip/dm/dvfs/cpufreq/Kconfig new file mode 100755 index 00000000000..29be68288d8 --- /dev/null +++ b/bsp/rockchip/dm/dvfs/cpufreq/Kconfig @@ -0,0 +1,10 @@ +config RT_DVFS_ROCKCHIP_CPUFREQ + bool "Rockchip CPUfreq driver" + depends on RT_USING_DVFS + depends on RT_USING_OFW + default y + help + Rockchip CPUfreq with OPP table parsing and optional nvmem + bin/leakage voltage selection. Uses SCMI clock and DT cpu-supply + regulator (e.g. FAN53555). Thermal throttling uses + thermal-cool-dvfs on the cpufreq device. diff --git a/bsp/rockchip/dm/dvfs/cpufreq/SConscript b/bsp/rockchip/dm/dvfs/cpufreq/SConscript new file mode 100644 index 00000000000..e6e88250122 --- /dev/null +++ b/bsp/rockchip/dm/dvfs/cpufreq/SConscript @@ -0,0 +1,13 @@ +from building import * + +group = [] +src = [] +cwd = GetCurrentDir() +CPPPATH = [cwd + '/../../include'] + +if GetDepend(['RT_DVFS_ROCKCHIP_CPUFREQ']): + src += ['dvfs-rockchip-cpufreq.c'] + +group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') diff --git a/bsp/rockchip/dm/dvfs/cpufreq/dvfs-rockchip-cpufreq.c b/bsp/rockchip/dm/dvfs/cpufreq/dvfs-rockchip-cpufreq.c new file mode 100644 index 00000000000..c4b2d0c8317 --- /dev/null +++ b/bsp/rockchip/dm/dvfs/cpufreq/dvfs-rockchip-cpufreq.c @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define DBG_TAG "dvfs.cpufreq" +#define DBG_LVL DBG_INFO +#include + +#include + +struct cpufreq_cluster +{ + rt_list_t list; + struct rt_dvfs_cpufreq cpufreq; + struct opp_info opp_info; + struct rt_platform_device *pdev; +#ifdef RT_THERMAL_COOL_DVFS + struct rt_platform_device *cool_pdev; +#endif + RT_BITMAP_DECLARE(cpus_map, RT_CPUS_NR); +}; + +static rt_list_t cluster_list = RT_LIST_OBJECT_INIT(cluster_list); + +static struct cpufreq_cluster *cluster_by_opp(struct rt_ofw_node *opp_np) +{ + struct cpufreq_cluster *cluster; + + if (!opp_np) + { + return RT_NULL; + } + + rt_list_for_each_entry(cluster, &cluster_list, list) + { + if (cluster->opp_info.opp_np == opp_np) + { + return cluster; + } + } + + return RT_NULL; +} + +static rt_bool_t cpu_shares_opp(struct rt_ofw_node *cpu_np, + struct rt_ofw_node *opp_np) +{ + struct rt_ofw_node *cpu_opp; + + cpu_opp = rt_ofw_parse_phandle(cpu_np, "operating-points-v2", 0); + if (!cpu_opp) + { + return RT_FALSE; + } + + if (cpu_opp == opp_np) + { + rt_ofw_node_put(cpu_opp); + return RT_TRUE; + } + + rt_ofw_node_put(cpu_opp); + return RT_FALSE; +} + +static rt_err_t scaling_set_opp(struct rt_dvfs_scaling *scaling, + struct rt_dvfs_opp *opp) +{ + struct cpufreq_cluster *cluster = scaling->priv; + + return cpufreq_set_opp(scaling, opp, &cluster->opp_info); +} + +static rt_err_t cpufreq_parse_opp(struct rt_dvfs_scaling *dvfs, + struct rt_dvfs_opp *opp, void *fw_np) +{ + struct cpufreq_cluster *cluster = dvfs->priv; + + return opp_parse(dvfs, opp, (struct rt_ofw_node *)fw_np, &cluster->opp_info); +} + +static struct rt_dvfs_scaling_ops cpufreq_ops = +{ + .set_opp = scaling_set_opp, + .parse_opp = cpufreq_parse_opp, +}; + +#ifdef RT_THERMAL_COOL_DVFS +static rt_err_t cluster_cool_register(struct cpufreq_cluster *cluster, + struct rt_ofw_node *cpu_np, struct rt_dvfs_scaling *scaling) +{ + rt_err_t err; + struct rt_platform_device *cool; + + if (!rt_ofw_prop_read_bool(cpu_np, "#cooling-cells")) + { + return RT_EOK; + } + + if (!(cool = rt_platform_device_alloc("dvfs-cool"))) + { + return -RT_ENOMEM; + } + + cool->parent.ofw_node = cpu_np; + rt_ofw_node_get(cpu_np); + cool->priv = scaling; + + if ((err = rt_platform_device_register(cool))) + { + rt_ofw_node_put(cpu_np); + rt_free(cool); + return err; + } + + cluster->cool_pdev = cool; + + return RT_EOK; +} + +static void cluster_cool_unregister(struct cpufreq_cluster *cluster) +{ + struct rt_platform_device *cool = cluster->cool_pdev; + + if (!cool) + { + return; + } + + rt_bus_remove_device(&cool->parent); + rt_ofw_node_put(cool->parent.ofw_node); + rt_free(cool); + cluster->cool_pdev = RT_NULL; +} +#endif /* RT_THERMAL_COOL_DVFS */ + +static void cluster_destroy(struct cpufreq_cluster *cluster, + struct rt_device *dev, struct rt_dvfs_scaling *scaling) +{ +#ifdef RT_THERMAL_COOL_DVFS + cluster_cool_unregister(cluster); +#endif + + if (scaling) + { + rt_dvfs_cpufreq_unregister(&cluster->cpufreq); + } + + if (dev) + { + opp_table_uninit(dev, &cluster->opp_info); + rt_device_unregister(dev); + rt_ofw_node_put(dev->ofw_node); + } + + if (cluster->pdev) + { + rt_free(cluster->pdev); + cluster->pdev = RT_NULL; + } +} + +static rt_err_t cluster_init(struct cpufreq_cluster *cluster, + struct rt_ofw_node *cpu_np, struct rt_ofw_node *opp_np) +{ + rt_err_t err; + const char *reg_name; + struct rt_device *dev; + struct rt_dvfs_scaling *scaling; + struct rt_platform_device *pdev; + + if (!(reg_name = regulator_name(cpu_np))) + { + LOG_W("%s: no cpu supply property, skip cluster", + rt_ofw_node_full_name(cpu_np)); + return -RT_ENOENT; + } + + if (!(pdev = rt_platform_device_alloc("cpufreq"))) + { + return -RT_ENOMEM; + } + + dev = &pdev->parent; + dev->ofw_node = cpu_np; + rt_ofw_node_get(cpu_np); + + rt_dm_dev_set_name_auto(dev, "cpufreq"); + + if ((err = rt_device_register(dev, rt_dm_dev_get_name(dev), RT_DEVICE_FLAG_DEACTIVATE))) + { + rt_ofw_node_put(cpu_np); + rt_free(pdev); + return err; + } + + scaling = rt_dvfs_cpufreq_to_scaling(&cluster->cpufreq); + scaling->dev = dev; + scaling->ops = &cpufreq_ops; + scaling->priv = cluster; + + if ((err = opp_table_init(dev, &cluster->opp_info, reg_name))) + { + goto _fail; + } + + if (cluster->opp_info.opp_np != opp_np) + { + rt_ofw_node_put(cluster->opp_info.opp_np); + cluster->opp_info.opp_np = opp_np; + rt_ofw_node_get(opp_np); + } + + scaling->clk = cluster->opp_info.clk; + scaling->supply = cluster->opp_info.supply; + + if (!scaling->transition_latency) + { + scaling->transition_latency = 1000000; + } + + if ((err = rt_dvfs_cpufreq_register(&cluster->cpufreq))) + { + goto _fail; + } + + if ((err = opp_table_adjust(dev, &cluster->opp_info, scaling))) + { + goto _fail_scaling; + } + + if ((err = cpufreq_sync_hw_state(scaling))) + { + LOG_W("%s: sync hw state failed: %s, cpufreq may retry regulator", + rt_dm_dev_get_name(dev), rt_strerror(err)); + } + + if ((err = rt_dvfs_scaling_set_governor(scaling, RT_DVFS_GOVERNOR_TYPE_ONDEMAND))) + { + rt_dvfs_scaling_set_governor(scaling, RT_DVFS_GOVERNOR_TYPE_PERFORMANCE); + } +#ifdef RT_THERMAL_COOL_DVFS + if ((err = cluster_cool_register(cluster, cpu_np, scaling))) + { + LOG_W("%s: dvfs-cool register failed: %s", + rt_dm_dev_get_name(dev), rt_strerror(err)); + } +#endif + + cluster->pdev = pdev; + rt_list_insert_before(&cluster_list, &cluster->list); + + LOG_D("CPUFreq cluster %s %lu-%lu Hz on %s", + rt_ofw_node_full_name(cpu_np), + scaling->min_freq, scaling->max_freq, + rt_dm_dev_get_name(dev)); + + return RT_EOK; + +_fail_scaling: + rt_dvfs_cpufreq_unregister(&cluster->cpufreq); +_fail: + cluster_destroy(cluster, dev, RT_NULL); + return err; +} + +static int rockchip_dvfs_cpufreq_init(void) +{ + rt_err_t err; + int cpu_id = 0, registered = 0; + struct rt_ofw_node *cpu_np = RT_NULL; + struct cpufreq_cluster *cluster; + struct rt_ofw_node *opp_np; + + rt_ofw_foreach_cpu_node(cpu_np) + { + int share_id = 0; + struct rt_ofw_node *share_np = RT_NULL; + + if (!(opp_np = rt_ofw_parse_phandle(cpu_np, "operating-points-v2", 0))) + { + ++cpu_id; + continue; + } + + if (cluster_by_opp(opp_np)) + { + rt_ofw_node_put(opp_np); + ++cpu_id; + continue; + } + + if (!(cluster = rt_calloc(1, sizeof(*cluster)))) + { + rt_ofw_node_put(opp_np); + return -RT_ENOMEM; + } + + cluster->cpufreq.master_cpu = cpu_id; + rt_bitmap_set_bit(cluster->cpus_map, cpu_id); + + rt_ofw_foreach_cpu_node(share_np) + { + if (cpu_shares_opp(share_np, opp_np)) + { + rt_bitmap_set_bit(cluster->cpus_map, share_id); + } + ++share_id; + } + + rt_memcpy(cluster->cpufreq.cpus_map, cluster->cpus_map, + sizeof(cluster->cpus_map)); + + err = cluster_init(cluster, cpu_np, opp_np); + rt_ofw_node_put(opp_np); + + if (err) + { + LOG_W("CPUFreq cluster %s init failed: %s", + rt_ofw_node_full_name(cpu_np), rt_strerror(err)); + rt_free(cluster); + } + else + { + ++registered; + } + + ++cpu_id; + } + + if (!registered) + { + return (int)-RT_ENOENT; + } + + return (int)RT_EOK; +} +INIT_APP_EXPORT(rockchip_dvfs_cpufreq_init); diff --git a/bsp/rockchip/dm/dvfs/devfreq/Kconfig b/bsp/rockchip/dm/dvfs/devfreq/Kconfig new file mode 100755 index 00000000000..75d717f76ae --- /dev/null +++ b/bsp/rockchip/dm/dvfs/devfreq/Kconfig @@ -0,0 +1,10 @@ +config RT_DVFS_ROCKCHIP_DMC + bool "Rockchip DMC devfreq (DFI event test stub)" + depends on RT_USING_DVFS + depends on RT_USING_DVFS_EVENT + select RT_DVFS_EVENT_ROCKCHIP_DFI + default n + help + Minimal devfreq on the DMC node to bind DFI event and verify + devfreq_load_from_event. Does not perform ATF/SIP DDR scaling. + diff --git a/bsp/rockchip/dm/dvfs/devfreq/SConscript b/bsp/rockchip/dm/dvfs/devfreq/SConscript new file mode 100644 index 00000000000..0277d3f7cb7 --- /dev/null +++ b/bsp/rockchip/dm/dvfs/devfreq/SConscript @@ -0,0 +1,13 @@ +from building import * + +group = [] +src = [] +cwd = GetCurrentDir() +CPPPATH = [cwd + '/../../include'] + +if GetDepend(['RT_DVFS_ROCKCHIP_DMC']): + src += ['dvfs-rockchip-dmc.c'] + +group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') diff --git a/bsp/rockchip/dm/dvfs/devfreq/dvfs-rockchip-dmc.c b/bsp/rockchip/dm/dvfs/devfreq/dvfs-rockchip-dmc.c new file mode 100755 index 00000000000..d3225b492a2 --- /dev/null +++ b/bsp/rockchip/dm/dvfs/devfreq/dvfs-rockchip-dmc.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Minimal Rockchip DMC devfreq: bind DFI event and exercise the + * devfreq + event load path. No ATF/SIP DDR rate scaling. + */ + +#include +#include + +#define DBG_TAG "dvfs.rockchip.dmc" +#define DBG_LVL DBG_INFO +#include + +struct rockchip_dmcfreq +{ + struct rt_dvfs_devfreq devfreq; + struct rt_platform_device *pdev; +}; + +static const struct rt_ofw_node_id dfi_compat_ids[] = +{ + { .compatible = "rockchip,rk3568-dfi" }, + { .compatible = "rockchip,rk3562-dfi" }, + { .compatible = "rockchip,px30-dfi" }, + { /* sentinel */ } +}; + +static rt_err_t rockchip_dmc_bind_dfi_event(struct rockchip_dmcfreq *dmc, + struct rt_device *dev) +{ + struct rt_ofw_node *np = dev->ofw_node, *ev_np; + + for (int i = 0; ; ++i) + { + if (!(ev_np = rt_ofw_parse_phandle(np, "devfreq-events", i))) + { + break; + } + + if (rt_ofw_node_match(ev_np, dfi_compat_ids)) + { + dmc->devfreq.ev = rt_ofw_data(ev_np); + } + + rt_ofw_node_put(ev_np); + + if (dmc->devfreq.ev) + { + break; + } + } + + if (!dmc->devfreq.ev) + { + LOG_W("%s: no DFI devfreq-event", rt_dm_dev_get_name(dev)); + return -RT_ENOENT; + } + + return RT_EOK; +} + +static rt_err_t rockchip_dmc_probe(struct rt_platform_device *pdev) +{ + rt_err_t err; + struct rt_device *dev = &pdev->parent; + struct rockchip_dmcfreq *dmc; + struct rt_dvfs_scaling *scaling; + + if (!(dmc = rt_calloc(1, sizeof(*dmc)))) + { + return -RT_ENOMEM; + } + + dmc->pdev = pdev; + scaling = rt_dvfs_devfreq_to_scaling(&dmc->devfreq); + + rt_dm_dev_set_name_auto(dev, "dmc"); + + if ((err = rt_device_register(dev, rt_dm_dev_get_name(dev), RT_DEVICE_FLAG_DEACTIVATE))) + { + rt_free(dmc); + return err; + } + + scaling->dev = dev; + scaling->ops = &rt_dvfs_devfreq_ops; + scaling->clk = rt_clk_get_by_index(dev, 0); + if (rt_is_err(scaling->clk)) + { + LOG_W("%s: no dmc clk, event test only", rt_dm_dev_get_name(dev)); + scaling->clk = RT_NULL; + } + + scaling->supply = rt_regulator_get(dev, "center"); + if (rt_is_err(scaling->supply)) + { + scaling->supply = RT_NULL; + } + + if ((err = rockchip_dmc_bind_dfi_event(dmc, dev))) + { + goto _fail; + } + + if ((err = rt_dvfs_devfreq_register(&dmc->devfreq))) + { + goto _fail; + } + + if ((err = rt_dvfs_scaling_set_governor(scaling, RT_DVFS_GOVERNOR_TYPE_ONDEMAND))) + { + LOG_W("%s: ondemand failed: %s", rt_dm_dev_get_name(dev), rt_strerror(err)); + } + + pdev->priv = dmc; + + LOG_I("%s: event-only devfreq ready, freq %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); + } + if (scaling->supply) + { + rt_regulator_put(scaling->supply); + } + rt_device_unregister(dev); + rt_free(dmc); + return err; +} + +static rt_err_t rockchip_dmc_remove(struct rt_platform_device *pdev) +{ + struct rockchip_dmcfreq *dmc = pdev->priv; + + if (!dmc) + { + return RT_EOK; + } + + rt_dvfs_devfreq_unregister(&dmc->devfreq); + + struct rt_dvfs_scaling *scaling = rt_dvfs_devfreq_to_scaling(&dmc->devfreq); + if (scaling->clk) + { + rt_clk_put(scaling->clk); + } + if (scaling->supply) + { + rt_regulator_put(scaling->supply); + } + + rt_device_unregister(&pdev->parent); + rt_free(dmc); + pdev->priv = RT_NULL; + + return RT_EOK; +} + +static const struct rt_ofw_node_id rockchip_dmc_ofw_ids[] = +{ + { .compatible = "rockchip,rk3568-dmc" }, + { .compatible = "rockchip,rk3562-dmc" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver rockchip_dmc_driver = +{ + .name = "rockchip-dmc", + .ids = rockchip_dmc_ofw_ids, + .probe = rockchip_dmc_probe, + .remove = rockchip_dmc_remove, +}; +RT_PLATFORM_DRIVER_EXPORT(rockchip_dmc_driver); diff --git a/bsp/rockchip/dm/dvfs/event/Kconfig b/bsp/rockchip/dm/dvfs/event/Kconfig new file mode 100755 index 00000000000..9b711e616e1 --- /dev/null +++ b/bsp/rockchip/dm/dvfs/event/Kconfig @@ -0,0 +1,4 @@ +config RT_DVFS_EVENT_ROCKCHIP_DFI + bool "Rockchip DFI Devfreq event driver" + depends on RT_USING_DVFS_EVENT + default n diff --git a/bsp/rockchip/dm/dvfs/event/SConscript b/bsp/rockchip/dm/dvfs/event/SConscript new file mode 100644 index 00000000000..1cc3da291ef --- /dev/null +++ b/bsp/rockchip/dm/dvfs/event/SConscript @@ -0,0 +1,13 @@ +from building import * + +group = [] +src = [] +cwd = GetCurrentDir() +CPPPATH = [cwd + '/../../include'] + +if GetDepend(['RT_DVFS_EVENT_ROCKCHIP_DFI']): + src += ['event-rockchip-dfi.c'] + +group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') diff --git a/bsp/rockchip/dm/dvfs/event/event-rockchip-dfi.c b/bsp/rockchip/dm/dvfs/event/event-rockchip-dfi.c new file mode 100755 index 00000000000..d180741fd94 --- /dev/null +++ b/bsp/rockchip/dm/dvfs/event/event-rockchip-dfi.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-21 GuEe-GUI first version + * 2026-06-12 GuEe-GUI RT port for RK3568 DFI + */ + +#include +#include +#include + +#define DBG_TAG "dvfs.dfi" +#define DBG_LVL DBG_INFO +#include + +#define PX30_PMUGRF_OS_REG2 0x208 +#define PX30_PMUGRF_OS_REG3 0x20c + +#define MAX_DMC_NUM_CH 4 +#define READ_DRAMTYPE_INFO(n) (((n) >> 13) & 0x7) +#define READ_DRAMTYPE_INFO_V3(n, m) ((((n) >> 13) & 0x7) | ((((m) >> 12) & 0x3) << 3)) +#define READ_SYSREG_VERSION(m) (((m) >> 28) & 0xf) + +#define DDRMON_CTRL 0x04 +#define CLR_DDRMON_CTRL ((rt_uint32_t)0xffff0000 << 0) +#define DDR2_3_EN (0x10001 << 15) +#define LPDDR5_EN (0x10001 << 6) +#define DDR4_EN (0x10001 << 5) +#define LPDDR4_EN (0x10001 << 4) +#define HARDWARE_EN (0x10001 << 3) +#define LPDDR2_3_EN (0x10001 << 2) +#define SOFTWARE_EN (0x10001 << 1) +#define SOFTWARE_DIS (0x10000 << 1) + +enum rockchip_dram_type +{ + ROCKCHIP_DFI_DDR4 = 0, + ROCKCHIP_DFI_DDR2 = 2, + ROCKCHIP_DFI_DDR3 = 3, + ROCKCHIP_DFI_LPDDR2 = 5, + ROCKCHIP_DFI_LPDDR3 = 6, + ROCKCHIP_DFI_LPDDR4 = 7, + ROCKCHIP_DFI_LPDDR4X = 8, + ROCKCHIP_DFI_LPDDR5 = 9, +}; + +struct rockchip_dfi_usage +{ + rt_uint64_t access; + rt_uint64_t total; +}; + +struct rockchip_dfi +{ + struct rt_dvfs_event event; + struct rt_platform_device *pdev; + + struct rockchip_dfi_usage ch_usage[MAX_DMC_NUM_CH]; + + volatile rt_uint32_t *regs; + struct rt_syscon *pmugrf; + + rt_uint32_t dram_type; + rt_uint32_t mon_version; + rt_uint32_t mon_idx; + rt_uint32_t mon_ctrl0; + rt_uint32_t mon_access_num; + rt_uint32_t mon_count_num; + rt_uint32_t count_rate; + rt_uint32_t ch_msk; +}; + +static void rockchip_dfi_get_mon_version(struct rockchip_dfi *dfi) +{ + dfi->mon_version = dfi->regs[0]; + + if (dfi->mon_version < 0x40) + { + dfi->mon_ctrl0 = 0x4; + dfi->mon_access_num = 0x2c; + dfi->mon_count_num = 0x28; + } + else + { + dfi->mon_ctrl0 = 0x4; + dfi->mon_access_num = 0x34; + dfi->mon_count_num = 0x30; + } +} + +static void rockchip_dfi_start_counter(struct rockchip_dfi *dfi) +{ + rt_uint32_t mon_idx = dfi->mon_idx, ctrl0 = dfi->mon_ctrl0; + + for (rt_uint32_t i = 0; i < MAX_DMC_NUM_CH; ++i) + { + rt_uint32_t base; + + if (!(dfi->ch_msk & RT_BIT(i))) + { + continue; + } + + if (i > 0 && mon_idx == 0) + { + continue; + } + + base = i * mon_idx; + dfi->regs[(base + ctrl0) / 4] = CLR_DDRMON_CTRL; + + switch (dfi->dram_type) + { + case ROCKCHIP_DFI_LPDDR3: + case ROCKCHIP_DFI_LPDDR2: + dfi->regs[(base + ctrl0) / 4] = LPDDR2_3_EN; + break; + case ROCKCHIP_DFI_LPDDR4: + case ROCKCHIP_DFI_LPDDR4X: + dfi->regs[(base + ctrl0) / 4] = LPDDR4_EN; + break; + case ROCKCHIP_DFI_DDR2: + case ROCKCHIP_DFI_DDR3: + dfi->regs[(base + ctrl0) / 4] = DDR2_3_EN; + break; + case ROCKCHIP_DFI_DDR4: + dfi->regs[(base + ctrl0) / 4] = DDR4_EN; + break; + default: + break; + } + + dfi->regs[(base + ctrl0) / 4] = SOFTWARE_EN; + } +} + +static void rockchip_dfi_stop_counter(struct rockchip_dfi *dfi) +{ + rt_uint32_t mon_idx = dfi->mon_idx; + + for (rt_uint32_t i = 0; i < MAX_DMC_NUM_CH; ++i) + { + rt_uint32_t base; + + if (!(dfi->ch_msk & RT_BIT(i))) + { + continue; + } + + if (i > 0 && mon_idx == 0) + { + continue; + } + + base = i * mon_idx; + dfi->regs[(base + DDRMON_CTRL) / 4] = SOFTWARE_DIS; + } +} + +static int rockchip_dfi_get_busier_ch(struct rockchip_dfi *dfi) +{ + rt_uint32_t busier_ch = 0, max = 0; + rt_uint32_t mon_idx = dfi->mon_idx ? dfi->mon_idx : 0x14; + rt_uint32_t count_rate = dfi->count_rate ? dfi->count_rate : 1; + + rockchip_dfi_stop_counter(dfi); + + for (rt_uint32_t i = 0; i < MAX_DMC_NUM_CH; ++i) + { + rt_uint32_t tmp, base; + + if (!(dfi->ch_msk & RT_BIT(i))) + { + continue; + } + + base = i * mon_idx; + dfi->ch_usage[i].total = dfi->regs[(base + dfi->mon_count_num) / 4] * count_rate; + + tmp = dfi->regs[(base + dfi->mon_access_num) / 4]; + if (dfi->dram_type == ROCKCHIP_DFI_LPDDR4 || dfi->dram_type == ROCKCHIP_DFI_LPDDR4X) + { + tmp *= 8; + } + else + { + tmp *= 4; + } + dfi->ch_usage[i].access = tmp; + + if (tmp > max) + { + max = tmp; + busier_ch = i; + } + } + + rockchip_dfi_start_counter(dfi); + + return (int)busier_ch; +} + +static rt_err_t rockchip_dfi_event_ready(struct rt_dvfs_event *ev) +{ + RT_UNUSED(ev); + return RT_EOK; +} + +static rt_err_t rockchip_dfi_event_enable(struct rt_dvfs_event *ev) +{ + struct rockchip_dfi *dfi = ev->priv; + + rockchip_dfi_get_mon_version(dfi); + rockchip_dfi_start_counter(dfi); + + return RT_EOK; +} + +static rt_err_t rockchip_dfi_event_disable(struct rt_dvfs_event *ev) +{ + struct rockchip_dfi *dfi = ev->priv; + + rockchip_dfi_stop_counter(dfi); + + return RT_EOK; +} + +static rt_err_t rockchip_dfi_event_read(struct rt_dvfs_event *ev, + struct rt_dvfs_event_data *evd) +{ + int busier_ch; + rt_base_t level; + struct rockchip_dfi *dfi = ev->priv; + + level = rt_hw_interrupt_disable(); + busier_ch = rockchip_dfi_get_busier_ch(dfi); + rt_hw_interrupt_enable(level); + + evd->load_count = dfi->ch_usage[busier_ch].access; + evd->total_count = dfi->ch_usage[busier_ch].total; + + return RT_EOK; +} + +static const struct rt_dvfs_event_ops rockchip_dfi_event_ops = +{ + .ready = rockchip_dfi_event_ready, + .read = rockchip_dfi_event_read, + .enable = rockchip_dfi_event_enable, + .disable = rockchip_dfi_event_disable, +}; + +static rt_err_t rockchip_px30_dfi_init(struct rockchip_dfi *dfi, + struct rt_ofw_node *np) +{ + rt_uint32_t val_2 = 0, val_3 = 0; + + dfi->pmugrf = rt_syscon_find_by_ofw_phandle(np, "rockchip,pmugrf"); + if (!dfi->pmugrf) + { + return -RT_ENOENT; + } + + rt_syscon_read(dfi->pmugrf, PX30_PMUGRF_OS_REG2, &val_2); + rt_syscon_read(dfi->pmugrf, PX30_PMUGRF_OS_REG3, &val_3); + + if (READ_SYSREG_VERSION(val_3) >= 0x3) + { + dfi->dram_type = READ_DRAMTYPE_INFO_V3(val_2, val_3); + } + else + { + dfi->dram_type = READ_DRAMTYPE_INFO(val_2); + } + + dfi->ch_msk = 1; + dfi->count_rate = 1; + + return RT_EOK; +} + +static rt_err_t rockchip_dfi_probe(struct rt_platform_device *pdev) +{ + rt_err_t err; + struct rockchip_dfi *dfi; + struct rt_device *dev = &pdev->parent; + struct rt_ofw_node *np = dev->ofw_node; + const struct rt_ofw_node_id *match; + + if (!(dfi = rt_calloc(1, sizeof(*dfi)))) + { + return -RT_ENOMEM; + } + + dfi->pdev = pdev; + dfi->event.dev = dev; + dfi->event.ops = &rockchip_dfi_event_ops; + dfi->event.priv = dfi; + + if (!(dfi->regs = rt_dm_dev_iomap(dev, 0))) + { + err = -RT_EIO; + goto _fail; + } + + match = pdev->id; + if (match && match->data) + { + err = ((rt_err_t (*)(struct rockchip_dfi *, struct rt_ofw_node *))match->data)(dfi, np); + if (err) + { + goto _fail; + } + } + else + { + err = -RT_ENOSYS; + goto _fail; + } + + rt_dm_dev_set_name_auto(dev, "dfi"); + + if ((err = rt_device_register(dev, rt_dm_dev_get_name(dev), RT_DEVICE_FLAG_DEACTIVATE))) + { + goto _fail; + } + + if ((err = rt_dvfs_event_register(&dfi->event))) + { + goto _fail_dev; + } + + pdev->priv = dfi; + + LOG_I("%s: dram_type=%u ch_msk=0x%x", rt_dm_dev_get_name(dev), + dfi->dram_type, dfi->ch_msk); + + return RT_EOK; + +_fail_dev: + rt_device_unregister(dev); +_fail: + if (dfi) + { + rt_free(dfi); + } + return err; +} + +static rt_err_t rockchip_dfi_remove(struct rt_platform_device *pdev) +{ + struct rockchip_dfi *dfi = pdev->priv; + + if (!dfi) + { + return RT_EOK; + } + + rt_dvfs_event_unregister(&dfi->event); + rt_bus_remove_device(&dfi->pdev->parent); + rt_free(dfi); + pdev->priv = RT_NULL; + + return RT_EOK; +} + +static const struct rt_ofw_node_id rockchip_dfi_ofw_ids[] = +{ + { .compatible = "rockchip,rk3568-dfi", .data = rockchip_px30_dfi_init }, + { .compatible = "rockchip,rk3562-dfi", .data = rockchip_px30_dfi_init }, + { .compatible = "rockchip,px30-dfi", .data = rockchip_px30_dfi_init }, + { /* sentinel */ } +}; + +static struct rt_platform_driver rockchip_dfi_driver = +{ + .name = "rockchip-dfi", + .ids = rockchip_dfi_ofw_ids, + .probe = rockchip_dfi_probe, + .remove = rockchip_dfi_remove, +}; + +static int rockchip_dfi_driver_register(void) +{ + rt_platform_driver_register(&rockchip_dfi_driver); + + return 0; +} +INIT_PLATFORM_EXPORT(rockchip_dfi_driver_register); + +#ifdef RT_USING_FINSH +#include + +static struct rt_dvfs_event *dfi_event_get(const char *name) +{ + struct rt_device *dev = rt_device_find(name); + + if (!dev || !dev->ofw_node) + { + return RT_NULL; + } + + return rt_ofw_data(dev->ofw_node); +} + +static int dfi_read(int argc, char **argv) +{ + struct rt_dvfs_event *ev; + struct rt_dvfs_event_data evd; + rt_err_t err; + + if (argc != 2) + { + rt_kprintf("Usage: dfi_read \n"); + return -1; + } + + if (!(ev = dfi_event_get(argv[1]))) + { + rt_kprintf("dfi device not found\n"); + return -1; + } + + if ((err = rt_dvfs_event_enable(ev))) + { + rt_kprintf("enable failed: %s\n", rt_strerror(err)); + return -1; + } + + err = rt_dvfs_event_read(ev, &evd); + if (err) + { + rt_kprintf("read failed: %s\n", rt_strerror(err)); + rt_dvfs_event_disable(ev); + return -1; + } + + if (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("load_count=%lu total_count=%lu load=%u.%06u%%\n", + (unsigned long)evd.load_count, + (unsigned long)evd.total_count, + load_int, load_frac); + } + else + { + rt_kprintf("load_count=%lu total_count=%lu load=0%%\n", + (unsigned long)evd.load_count, + (unsigned long)evd.total_count); + } + + return 0; +} +MSH_CMD_EXPORT(dfi_read, read rockchip DFI devfreq-event counters); +#endif /* RT_USING_FINSH */ diff --git a/bsp/rockchip/dm/i2c/i2c-rk3x.c b/bsp/rockchip/dm/i2c/i2c-rk3x.c index 60918a1e442..d73873acd9d 100755 --- a/bsp/rockchip/dm/i2c/i2c-rk3x.c +++ b/bsp/rockchip/dm/i2c/i2c-rk3x.c @@ -28,6 +28,8 @@ #define REG_IEN 0x18 /* interrupt enable */ #define REG_IPD 0x1c /* interrupt pending */ #define REG_FCNT 0x20 /* finished count */ +#define REG_SCL_OE_DB 0x24 /* slave hold scl debounce */ +#define REG_CON1 0x228 /* control register1 */ /* Data buffer offsets */ #define TXBUFFER_BASE 0x100 @@ -57,6 +59,20 @@ enum #define REG_CON_STA_CFG(cfg) ((cfg) << 12) #define REG_CON_STO_CFG(cfg) ((cfg) << 14) +#define REG_CON_VERSION RT_GENMASK_ULL(24, 16) +#define REG_CON_VERSION_SHIFT 16 + +#define REG_CON1_AUTO_STOP RT_BIT(0) +#define REG_CON1_TRANSFER_AUTO_STOP RT_BIT(1) +#define REG_CON1_NACK_AUTO_STOP RT_BIT(2) + +enum +{ + RK_I2C_VERSION0 = 0, + RK_I2C_VERSION1, + RK_I2C_VERSION5 = 5, +}; + /* REG_MRXADDR bits */ #define REG_MRXADDR_VALID(x) RT_BIT(24 + (x)) /* [x*8+7:x*8] of MRX[R]ADDR valid */ @@ -68,10 +84,13 @@ enum #define REG_INT_START RT_BIT(4) /* START condition generated */ #define REG_INT_STOP RT_BIT(5) /* STOP condition generated */ #define REG_INT_NAKRCV RT_BIT(6) /* NACK received */ -#define REG_INT_ALL 0x7f +#define REG_INT_SLV_HDSCL RT_BIT(7) /* slave hold scl */ +#define REG_INT_ALL 0x3ff + +#define IEN_ALL_DISABLE 0 /* Constants */ -#define WAIT_TIMEOUT 1000 /* ms */ +#define WAIT_TIMEOUT 200 /* ms */ #define DEFAULT_SCL_RATE (100 * 1000) /* Hz */ /* I2C specification values for various modes */ @@ -138,7 +157,6 @@ struct rk3x_i2c_calced_timings enum rk3x_i2c_state { STATE_IDLE, - STATE_START, STATE_READ, STATE_WRITE, STATE_STOP @@ -177,10 +195,15 @@ struct rk3x_i2c enum rk3x_i2c_state state; rt_uint32_t processed; rt_err_t error; + + rt_bool_t autostop_supported; }; #define raw_to_rk3x_i2c(raw) rt_container_of(raw, struct rk3x_i2c, parent) +static rt_uint32_t rk3x_i2c_fill_transmit_buf(struct rk3x_i2c *i2c, rt_bool_t sendend); +static void rk3x_i2c_prepare_read(struct rk3x_i2c *i2c); + rt_inline void i2c_writel(struct rk3x_i2c *i2c, rt_uint32_t value, int offset) { HWREG32(i2c->regs + offset) = value; @@ -197,23 +220,140 @@ rt_inline void rk3x_i2c_clean_ipd(struct rk3x_i2c *i2c) i2c_writel(i2c, REG_INT_ALL, REG_IPD); } -/* Generate a START condition, which triggers a REG_INT_START interrupt */ +/* Force controller back to idle (disable adapter, clear IRQs) */ +static void rk3x_i2c_hw_idle(struct rk3x_i2c *i2c) +{ + rt_uint32_t val; + + i2c_writel(i2c, 0, REG_IEN); + val = i2c_readl(i2c, REG_CON) & REG_CON_TUNING_MASK; + i2c_writel(i2c, val, REG_CON); + rk3x_i2c_clean_ipd(i2c); + i2c->state = STATE_IDLE; +} + +static void rk3x_i2c_disable_irq(struct rk3x_i2c *i2c) +{ + i2c_writel(i2c, IEN_ALL_DISABLE, REG_IEN); +} + +static void rk3x_i2c_disable(struct rk3x_i2c *i2c) +{ + rt_uint32_t val = i2c_readl(i2c, REG_CON) & REG_CON_TUNING_MASK; + + i2c_writel(i2c, val, REG_CON); +} + +static rt_bool_t rk3x_i2c_auto_stop(struct rk3x_i2c *i2c) +{ + rt_uint32_t len, con1 = 0; + + if (!i2c->autostop_supported) + { + return RT_FALSE; + } + + if (!(i2c->msg->flags & RT_I2C_IGNORE_NACK)) + { + con1 = REG_CON1_NACK_AUTO_STOP | REG_CON1_AUTO_STOP; + } + + if (!i2c->is_last_msg) + { + goto out; + } + + len = i2c->msg->len - i2c->processed; + + if (len > 32) + { + goto out; + } + + if (i2c->msg->len == 32 && i2c->mode == REG_CON_MOD_TX && !i2c->processed) + { + goto out; + } + + i2c->state = STATE_STOP; + + con1 |= REG_CON1_TRANSFER_AUTO_STOP | REG_CON1_AUTO_STOP; + i2c_writel(i2c, con1, REG_CON1); + if (con1 & REG_CON1_NACK_AUTO_STOP) + { + i2c_writel(i2c, REG_INT_STOP, REG_IEN); + } + else + { + i2c_writel(i2c, REG_INT_STOP | REG_INT_NAKRCV, REG_IEN); + } + + return RT_TRUE; + +out: + i2c_writel(i2c, con1, REG_CON1); + + return RT_FALSE; +} + +static unsigned int rk3x_i2c_get_version(struct rk3x_i2c *i2c) +{ + rt_uint32_t version; + + rt_clk_enable(i2c->pclk); + version = (i2c_readl(i2c, REG_CON) & REG_CON_VERSION) >> REG_CON_VERSION_SHIFT; + rt_clk_disable(i2c->pclk); + + return version; +} + +static rt_bool_t rk3x_i2c_use_autostop(struct rk3x_i2c *i2c) +{ + return rk3x_i2c_get_version(i2c) >= RK_I2C_VERSION5; +} + +/* Generate a START condition and kick off the transfer (RK Linux FIFO path) */ static void rk3x_i2c_start(struct rk3x_i2c *i2c) { rt_uint32_t val = i2c_readl(i2c, REG_CON) & REG_CON_TUNING_MASK; + rt_bool_t auto_stop = rk3x_i2c_auto_stop(i2c); + rt_uint32_t length = 0; - i2c_writel(i2c, REG_INT_START, REG_IEN); + if (i2c->mode == REG_CON_MOD_TX) + { + if (!auto_stop) + { + i2c_writel(i2c, REG_INT_MBTF | REG_INT_NAKRCV, REG_IEN); + i2c->state = STATE_WRITE; + } + length = rk3x_i2c_fill_transmit_buf(i2c, RT_FALSE); + } + else + { + if (!auto_stop) + { + i2c_writel(i2c, REG_INT_MBRF | REG_INT_NAKRCV, REG_IEN); + i2c->state = STATE_READ; + } + } - /* enable adapter with correct mode, send START condition */ val |= REG_CON_EN | REG_CON_MOD(i2c->mode) | REG_CON_START; - /* if we want to react to NACK, set ACTACK bit */ if (!(i2c->msg->flags & RT_I2C_IGNORE_NACK)) { val |= REG_CON_ACTACK; } i2c_writel(i2c, val, REG_CON); + + if (i2c->mode == REG_CON_MOD_TX) + { + i2c_writel(i2c, length, REG_MTXCNT); + } + else + { + rk3x_i2c_prepare_read(i2c); + } } /* Generate a STOP condition, which triggers a REG_INT_STOP interrupt */ @@ -222,7 +362,6 @@ static void rk3x_i2c_stop(struct rk3x_i2c *i2c, rt_err_t error) rt_uint32_t ctrl; i2c->processed = 0; - i2c->msg = RT_NULL; i2c->error = error; if (i2c->is_last_msg) @@ -234,12 +373,14 @@ static void rk3x_i2c_stop(struct rk3x_i2c *i2c, rt_err_t error) ctrl = i2c_readl(i2c, REG_CON); ctrl |= REG_CON_STOP; + ctrl &= ~REG_CON_START; i2c_writel(i2c, ctrl, REG_CON); } else { /* Signal rk3x_i2c_xfer to start the next message. */ i2c->state = STATE_IDLE; + i2c->msg = RT_NULL; /* * The HW is actually not capable of REPEATED START. But we can @@ -249,7 +390,6 @@ static void rk3x_i2c_stop(struct rk3x_i2c *i2c, rt_err_t error) ctrl = i2c_readl(i2c, REG_CON) & REG_CON_TUNING_MASK; i2c_writel(i2c, ctrl, REG_CON); - /* signal that we are finished with the current msg */ rt_completion_done(&i2c->done); } } @@ -280,6 +420,10 @@ static void rk3x_i2c_prepare_read(struct rk3x_i2c *i2c) { con &= ~REG_CON_MOD_MASK; con |= REG_CON_MOD(REG_CON_MOD_RX); + if (con & REG_CON_START) + { + con &= ~REG_CON_START; + } } i2c_writel(i2c, con, REG_CON); @@ -287,7 +431,7 @@ static void rk3x_i2c_prepare_read(struct rk3x_i2c *i2c) } /* Fill the transmit buffer with data from i2c->msg */ -static void rk3x_i2c_fill_transmit_buf(struct rk3x_i2c *i2c) +static rt_uint32_t rk3x_i2c_fill_transmit_buf(struct rk3x_i2c *i2c, rt_bool_t sendend) { rt_uint32_t cnt = 0; @@ -325,43 +469,39 @@ static void rk3x_i2c_fill_transmit_buf(struct rk3x_i2c *i2c) } } - i2c_writel(i2c, cnt, REG_MTXCNT); -} - -/* IRQ handlers for individual states */ -static void rk3x_i2c_handle_start(struct rk3x_i2c *i2c, rt_uint32_t ipd) -{ - if (!(ipd & REG_INT_START)) + if (sendend) { - rk3x_i2c_stop(i2c, -RT_EIO); - LOG_W("Unexpected irq in START: 0x%x", ipd); - rk3x_i2c_clean_ipd(i2c); - - return; + i2c_writel(i2c, cnt, REG_MTXCNT); } - /* ack interrupt */ - i2c_writel(i2c, REG_INT_START, REG_IPD); + return cnt; +} - /* disable start bit */ - i2c_writel(i2c, i2c_readl(i2c, REG_CON) & ~REG_CON_START, REG_CON); +static void rk3x_i2c_read_chunk(struct rk3x_i2c *i2c) +{ + rt_uint32_t val = 0; + rt_uint32_t len = i2c->msg->len - i2c->processed; - /* enable appropriate interrupts and transition */ - if (i2c->mode == REG_CON_MOD_TX) + if (len > 32) { - i2c_writel(i2c, REG_INT_MBTF | REG_INT_NAKRCV, REG_IEN); - i2c->state = STATE_WRITE; - rk3x_i2c_fill_transmit_buf(i2c); + len = 32; } - else + + for (rt_uint32_t i = 0; i < len; ++i) { - /* in any other case, we are going to be reading. */ - i2c_writel(i2c, REG_INT_MBRF | REG_INT_NAKRCV, REG_IEN); - i2c->state = STATE_READ; - rk3x_i2c_prepare_read(i2c); + rt_uint8_t byte; + + if (i % 4 == 0) + { + val = i2c_readl(i2c, RXBUFFER_BASE + (i / 4) * 4); + } + + byte = (val >> ((i % 4) * 8)) & 0xff; + i2c->msg->buf[i2c->processed++] = byte; } } +/* IRQ handlers for individual states */ static void rk3x_i2c_handle_write(struct rk3x_i2c *i2c, rt_uint32_t ipd) { if (!(ipd & REG_INT_MBTF)) @@ -376,21 +516,20 @@ static void rk3x_i2c_handle_write(struct rk3x_i2c *i2c, rt_uint32_t ipd) /* ack interrupt */ i2c_writel(i2c, REG_INT_MBTF, REG_IPD); - /* are we finished? */ + rk3x_i2c_auto_stop(i2c); + if (i2c->processed == i2c->msg->len) { rk3x_i2c_stop(i2c, i2c->error); } else { - rk3x_i2c_fill_transmit_buf(i2c); + rk3x_i2c_fill_transmit_buf(i2c, RT_TRUE); } } -static void rk3x_i2c_handle_read(struct rk3x_i2c *i2c, unsigned int ipd) +static void rk3x_i2c_handle_read(struct rk3x_i2c *i2c, rt_uint32_t ipd) { - rt_uint32_t val = 0, len = i2c->msg->len - i2c->processed; - /* we only care for MBRF here. */ if (!(ipd & REG_INT_MBRF)) { @@ -400,27 +539,10 @@ static void rk3x_i2c_handle_read(struct rk3x_i2c *i2c, unsigned int ipd) /* ack interrupt (read also produces a spurious START flag, clear it too) */ i2c_writel(i2c, REG_INT_MBRF | REG_INT_START, REG_IPD); - /* Can only handle a maximum of 32 bytes at a time */ - if (len > 32) - { - len = 32; - } - - /* read the data from receive buffer */ - for (int i = 0; i < len; ++i) - { - rt_uint8_t byte; + rk3x_i2c_read_chunk(i2c); - if (i % 4 == 0) - { - val = i2c_readl(i2c, RXBUFFER_BASE + (i / 4) * 4); - } + rk3x_i2c_auto_stop(i2c); - byte = (val >> ((i % 4) * 8)) & 0xff; - i2c->msg->buf[i2c->processed++] = byte; - } - - /* are we finished? */ if (i2c->processed == i2c->msg->len) { rk3x_i2c_stop(i2c, i2c->error); @@ -437,24 +559,53 @@ static void rk3x_i2c_handle_stop(struct rk3x_i2c *i2c, rt_uint32_t ipd) if (!(ipd & REG_INT_STOP)) { - rk3x_i2c_stop(i2c, -RT_EIO); - LOG_E("Unexpected irq in STOP: 0x%x", ipd); + LOG_W("Unexpected irq in STOP: 0x%x", ipd); rk3x_i2c_clean_ipd(i2c); + if (i2c->is_last_msg) + { + i2c->error = -RT_EIO; + rk3x_i2c_hw_idle(i2c); + i2c->msg = RT_NULL; + i2c->state = STATE_IDLE; + rt_completion_done(&i2c->done); + } + else + { + rk3x_i2c_stop(i2c, -RT_EIO); + } + return; } /* ack interrupt */ i2c_writel(i2c, REG_INT_STOP, REG_IPD); + if (i2c->autostop_supported && !i2c->error) + { + if (i2c->mode != REG_CON_MOD_TX && i2c->msg) + { + if ((i2c->msg->len - i2c->processed) > 0) + { + rk3x_i2c_read_chunk(i2c); + } + } + + i2c->processed = 0; + } + /* disable STOP bit */ con = i2c_readl(i2c, REG_CON); con &= ~REG_CON_STOP; + if (i2c->autostop_supported) + { + con &= ~REG_CON_START; + } i2c_writel(i2c, con, REG_CON); i2c->state = STATE_IDLE; + i2c->msg = RT_NULL; - /* signal rk3x_i2c_xfer that we are finished */ rt_completion_done(&i2c->done); } @@ -462,7 +613,8 @@ static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, rt_ubase_t clk_rate) { rt_err_t err; rt_ubase_t level; - rt_uint32_t val; + rt_uint32_t val, cnt; + rt_ubase_t period, time_hold = (WAIT_TIMEOUT / 2) * 1000000; struct rk3x_i2c_calced_timings calc; struct i2c_timings *timings = &i2c->timings; @@ -481,6 +633,10 @@ static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, rt_ubase_t clk_rate) i2c_writel(i2c, val, REG_CON); i2c_writel(i2c, (calc.div_high << 16) | (calc.div_low & 0xffff), REG_CLKDIV); + period = RT_DIV_ROUND_UP(1000000000, clk_rate); + cnt = RT_DIV_ROUND_UP(time_hold, period); + i2c_writel(i2c, cnt, REG_SCL_OE_DB); + rt_spin_unlock_irqrestore(&i2c->lock, level); rt_clk_disable(i2c->pclk); @@ -939,12 +1095,17 @@ static rt_ssize_t rk3x_i2c_setup(struct rk3x_i2c *i2c, struct rt_i2c_msg *msgs, } i2c->addr = msgs[0].addr; - i2c->state = STATE_START; + i2c->state = STATE_IDLE; i2c->processed = 0; i2c->error = RT_EOK; rk3x_i2c_clean_ipd(i2c); + if (i2c->autostop_supported) + { + i2c_writel(i2c, 0, REG_CON1); + } + return res; } @@ -954,13 +1115,35 @@ static rt_ssize_t rk3x_i2c_master_xfer(struct rt_i2c_bus_device *bus, rt_ssize_t res = 0; rt_uint32_t val; rt_ubase_t level; + rt_err_t err; rt_err_t timeout_err; struct rk3x_i2c *i2c = raw_to_rk3x_i2c(bus); level = rt_spin_lock_irqsave(&i2c->lock); - rt_clk_enable(i2c->clk); - rt_clk_enable(i2c->pclk); + err = rt_clk_enable(i2c->clk); + if (err) + { + LOG_E("Can't enable i2c clk: %s", rt_strerror(err)); + rt_spin_unlock_irqrestore(&i2c->lock, level); + + return err; + } + + if (!rt_is_err_or_null(i2c->pclk) && i2c->pclk != i2c->clk) + { + err = rt_clk_enable(i2c->pclk); + if (err) + { + LOG_E("Can't enable i2c pclk: %s", rt_strerror(err)); + rt_clk_disable(i2c->clk); + rt_spin_unlock_irqrestore(&i2c->lock, level); + + return err; + } + } + + rk3x_i2c_hw_idle(i2c); i2c->is_last_msg = RT_FALSE; @@ -981,6 +1164,8 @@ static rt_ssize_t rk3x_i2c_master_xfer(struct rt_i2c_bus_device *bus, i2c->is_last_msg = RT_TRUE; } + rt_completion_init(&i2c->done); + rt_spin_unlock_irqrestore(&i2c->lock, level); rk3x_i2c_start(i2c); @@ -999,7 +1184,7 @@ static rt_ssize_t rk3x_i2c_master_xfer(struct rt_i2c_bus_device *bus, val |= REG_CON_EN | REG_CON_STOP; i2c_writel(i2c, val, REG_CON); - i2c->state = STATE_IDLE; + rk3x_i2c_hw_idle(i2c); res = timeout_err; break; @@ -1013,7 +1198,13 @@ static rt_ssize_t rk3x_i2c_master_xfer(struct rt_i2c_bus_device *bus, } } - rt_clk_disable(i2c->pclk); + rk3x_i2c_disable_irq(i2c); + rk3x_i2c_disable(i2c); + + if (!rt_is_err_or_null(i2c->pclk) && i2c->pclk != i2c->clk) + { + rt_clk_disable(i2c->pclk); + } rt_clk_disable(i2c->clk); rt_spin_unlock_irqrestore(&i2c->lock, level); @@ -1046,7 +1237,7 @@ static void rk3x_i2c_isr(int irqno, void *param) LOG_D("IRQ: state %d, ipd: %x", i2c->state, ipd); /* Clean interrupt bits we don't care about */ - ipd &= ~(REG_INT_BRF | REG_INT_BTF); + ipd &= ~(REG_INT_BRF | REG_INT_BTF | REG_INT_START); if (ipd & REG_INT_NAKRCV) { @@ -1061,9 +1252,15 @@ static void rk3x_i2c_isr(int irqno, void *param) if (!(i2c->msg->flags & RT_I2C_IGNORE_NACK)) { - LOG_E("Flags error"); - - rk3x_i2c_stop(i2c, -RT_EIO); + if (i2c->autostop_supported) + { + i2c->error = -RT_EIO; + i2c->state = STATE_STOP; + } + else + { + rk3x_i2c_stop(i2c, -RT_EIO); + } } } @@ -1075,10 +1272,6 @@ static void rk3x_i2c_isr(int irqno, void *param) switch (i2c->state) { - case STATE_START: - rk3x_i2c_handle_start(i2c, ipd); - break; - case STATE_WRITE: rk3x_i2c_handle_write(i2c, ipd); break; @@ -1131,7 +1324,7 @@ static void rk3x_i2c_free(struct rk3x_i2c *i2c) static rt_err_t rk3x_i2c_probe(struct rt_platform_device *pdev) { - int id = -1; + int id = pdev->dev_id; rt_err_t err; const char *dev_name; struct rt_device *dev = &pdev->parent; @@ -1169,8 +1362,6 @@ static rt_err_t rk3x_i2c_probe(struct rt_platform_device *pdev) struct rt_syscon *grf; struct rt_ofw_node *np = dev->ofw_node; - id = pdev->dev_id; - if (id < 0) { err = -RT_EINVAL; @@ -1251,6 +1442,8 @@ static rt_err_t rk3x_i2c_probe(struct rt_platform_device *pdev) rk3x_i2c_adapt_div(i2c, rt_clk_get_rate(i2c->clk)); rt_clk_disable(i2c->clk); + i2c->autostop_supported = rk3x_i2c_use_autostop(i2c); + rt_spin_lock_init(&i2c->lock); rt_completion_init(&i2c->done); @@ -1361,6 +1554,7 @@ static const struct rt_ofw_node_id rk3x_i2c_ofw_ids[] = { .compatible = "rockchip,rk3228-i2c", .data = &rk3228_soc_data }, { .compatible = "rockchip,rk3288-i2c", .data = &rk3288_soc_data }, { .compatible = "rockchip,rk3399-i2c", .data = &rk3399_soc_data }, + { .compatible = "rockchip,rk3568-i2c", .data = &rk3399_soc_data }, { /* sentinel */ } }; diff --git a/bsp/rockchip/dm/include/opp-select.h b/bsp/rockchip/dm/include/opp-select.h new file mode 100644 index 00000000000..45c4904a862 --- /dev/null +++ b/bsp/rockchip/dm/include/opp-select.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __OPP_SELECT_H__ +#define __OPP_SELECT_H__ + +#include +#include + +struct opp_info; + +struct volt_rm_table +{ + int volt; + int rm; +}; + +struct opp_data +{ + rt_err_t (*get_soc_info)(struct rt_device *dev, struct rt_ofw_node *np, + int *bin, int *process); + rt_err_t (*set_soc_info)(struct rt_device *dev, struct rt_ofw_node *np, + struct opp_info *info); + rt_err_t (*set_read_margin)(struct rt_device *dev, + struct opp_info *opp_info, rt_uint32_t rm); + rt_err_t (*config_regulators)(struct rt_device *dev, + struct rt_dvfs_opp *old_opp, struct rt_dvfs_opp *new_opp, + struct opp_info *info); +}; + +struct opp_info +{ + struct rt_device *dev; + struct rt_ofw_node *opp_np; + const struct opp_data *data; + + struct rt_clk *clk; + struct rt_regulator *supply; + struct rt_regulator *mem_supply; + + int bin; + int process; + int volt_sel; + int scale; + int leakage; + + rt_bool_t is_runtime_active; + + struct rt_spinlock dvfs_lock; +}; + +#if defined(RT_DVFS_ROCKCHIP_CPUFREQ) + +void get_opp_data(const struct rt_ofw_node_id *matches, struct opp_info *info); + +rt_err_t nvmem_cell_read_u8(struct rt_ofw_node *np, const char *cell_id, + rt_uint8_t *val); + +const char *regulator_name(struct rt_ofw_node *cpu_np); + +rt_err_t opp_table_init(struct rt_device *dev, struct opp_info *info, + const char *reg_name); + +void opp_table_uninit(struct rt_device *dev, struct opp_info *info); + +rt_err_t opp_table_adjust(struct rt_device *dev, struct opp_info *info, + struct rt_dvfs_scaling *scaling); + +rt_err_t opp_parse(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp, + struct rt_ofw_node *opp_np, struct opp_info *info); + +rt_err_t cpufreq_set_opp(struct rt_dvfs_scaling *scaling, struct rt_dvfs_opp *opp, + struct opp_info *info); + +rt_err_t cpufreq_sync_hw_state(struct rt_dvfs_scaling *scaling); + +void opp_dvfs_lock(struct opp_info *info); +void opp_dvfs_unlock(struct opp_info *info); + +#else + +static inline void get_opp_data(const struct rt_ofw_node_id *matches, + struct opp_info *info) +{ + RT_UNUSED(matches); + RT_UNUSED(info); +} + +static inline rt_err_t opp_table_init(struct rt_device *dev, + struct opp_info *info, const char *reg_name) +{ + RT_UNUSED(dev); + RT_UNUSED(info); + RT_UNUSED(reg_name); + return -RT_ENOSYS; +} + +#endif /* RT_DVFS_ROCKCHIP_CPUFREQ */ + +#endif /* __OPP_SELECT_H__ */ diff --git a/bsp/rockchip/dm/soc/SConscript b/bsp/rockchip/dm/soc/SConscript index e3fda61b3f0..59dfb3e198b 100755 --- a/bsp/rockchip/dm/soc/SConscript +++ b/bsp/rockchip/dm/soc/SConscript @@ -17,6 +17,9 @@ if GetDepend(['RT_SOC_ROCKCHIP_HW_DECOMPRESS']): if GetDepend(['RT_SOC_ROCKCHIP_IODOMAIN']): src += ['io-domain.c'] +if GetDepend(['RT_DVFS_ROCKCHIP_CPUFREQ']): + src += ['opp-select.c'] + group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) Return('group') diff --git a/bsp/rockchip/dm/soc/opp-select.c b/bsp/rockchip/dm/soc/opp-select.c new file mode 100644 index 00000000000..a6c68e9b149 --- /dev/null +++ b/bsp/rockchip/dm/soc/opp-select.c @@ -0,0 +1,829 @@ +/* + * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define DBG_TAG "dvfs.opp" +#define DBG_LVL DBG_INFO +#include + +#include +#ifdef RT_SOC_ROCKCHIP_PVTM +#include +#endif + +static rt_err_t rk3588_get_soc_info(struct rt_device *dev, struct rt_ofw_node *np, + int *bin, int *process) +{ + rt_uint8_t value = 0; + rt_err_t err; + + if (bin) + { + if (!(err = nvmem_cell_read_u8(np, "specification_serial_number", &value))) + { + if (value == 0xb) + { + *bin = 0; + } + else if (value == 0x1) + { + *bin = 1; + } + else if (value == 0x10) + { + *bin = 1; + } + LOG_D("%s: bin=%d", rt_dm_dev_get_name(dev), *bin); + } + } + + RT_UNUSED(process); + return RT_EOK; +} + +static const struct opp_data rk3588_cpu_opp_data = +{ + .get_soc_info = rk3588_get_soc_info, +}; + +static const struct rt_ofw_node_id cpu_opp_ids[] = +{ + { .compatible = "rockchip,rk3588", .data = &rk3588_cpu_opp_data }, + { .compatible = "rockchip,rk3576" }, + { .compatible = "rockchip,rk3568" }, + { .compatible = "rockchip,rk3399" }, + { .compatible = "rockchip,rk3308" }, + { .compatible = "rockchip,rk3328" }, + { .compatible = "rockchip,px30" }, + { /* sentinel */ } +}; + +void get_opp_data(const struct rt_ofw_node_id *matches, struct opp_info *info) +{ + struct rt_ofw_node *soc_np; + const struct rt_ofw_node_id *match; + + if (!info) + { + return; + } + + if (!matches) + { + matches = cpu_opp_ids; + } + + if (!(soc_np = rt_ofw_find_node_by_compatible(RT_NULL, "rockchip,rk3588"))) + { + return; + } + + match = rt_ofw_node_match(soc_np, matches); + if (match && match->data) + { + info->data = match->data; + } +} + +rt_err_t nvmem_cell_read_u8(struct rt_ofw_node *np, const char *cell_id, + rt_uint8_t *val) +{ + struct rt_device dev = { .ofw_node = (struct rt_ofw_node *)np }; + struct rt_nvmem_cell *cell; + rt_ssize_t ret; + + if (!np || !val) + { + return -RT_EINVAL; + } + + cell = rt_nvmem_get_cell_by_name(&dev, cell_id); + if (rt_is_err_or_null(cell)) + { + return -RT_ENOENT; + } + + ret = rt_nvmem_cell_read_u8(cell, val); + rt_nvmem_put_cell(cell); + + return ret < 0 ? (rt_err_t)ret : RT_EOK; +} + +static rt_err_t read_leakage(struct rt_ofw_node *opp_np, int *leakage) +{ + rt_uint8_t value = 0; + rt_err_t err; + + if (!opp_np || !leakage) + { + return -RT_EINVAL; + } + + err = nvmem_cell_read_u8(opp_np, "leakage", &value); + if (!err) + { + *leakage = value; + } + + return err; +} + +static rt_err_t get_soc_info_default(struct rt_device *dev, + struct rt_ofw_node *np, int *bin, int *process) +{ + rt_uint8_t value = 0; + rt_err_t err; + + if (bin) + { + err = nvmem_cell_read_u8(np, "performance", &value); + if (!err) + { + *bin = value; + LOG_D("%s: bin=%d", rt_dm_dev_get_name(dev), *bin); + } + } + + RT_UNUSED(process); + return RT_EOK; +} + +static const struct opp_data default_opp_data = +{ + .get_soc_info = get_soc_info_default, +}; + +void opp_dvfs_lock(struct opp_info *info) +{ + if (info) + { + rt_spin_lock(&info->dvfs_lock); + } +} + +void opp_dvfs_unlock(struct opp_info *info) +{ + if (info) + { + rt_spin_unlock(&info->dvfs_lock); + } +} + +static void opp_pick_voltages(struct rt_ofw_node *opp_np, struct opp_info *info, + rt_ubase_t *cpu_uvolt, rt_ubase_t *mem_uvolt) +{ + int nr, l = 0; + char prop_name[20]; + rt_uint32_t uvolt[6] = {0}; + + if (info->leakage > 0) + { + l = info->leakage; + } + else if (info->volt_sel > 0) + { + l = info->volt_sel; + } + + if (l > 0 && l <= 7) + { + rt_snprintf(prop_name, sizeof(prop_name), "opp-microvolt-L%d", l); + nr = rt_ofw_prop_read_u32_array_index(opp_np, prop_name, 0, + RT_ARRAY_SIZE(uvolt), uvolt); + if (nr >= 2) + { + *cpu_uvolt = uvolt[1] ? uvolt[1] : uvolt[0]; + if (nr >= 6) + { + *mem_uvolt = uvolt[4] ? uvolt[4] : uvolt[3]; + } + return; + } + } + + nr = rt_ofw_prop_read_u32_array_index(opp_np, "opp-microvolt", 0, + RT_ARRAY_SIZE(uvolt), uvolt); + if (nr >= 2) + { + *cpu_uvolt = uvolt[1] ? uvolt[1] : uvolt[0]; + if (nr >= 6) + { + *mem_uvolt = uvolt[4] ? uvolt[4] : uvolt[3]; + } + } + else if (nr >= 1) + { + *cpu_uvolt = uvolt[0]; + } +} + +rt_err_t opp_parse(struct rt_dvfs_scaling *dvfs, struct rt_dvfs_opp *opp, + struct rt_ofw_node *opp_np, struct opp_info *info) +{ + rt_ubase_t cpu_uvolt = opp->uvolt; + rt_ubase_t mem_uvolt = 0; + + if (!opp || !opp_np || !info) + { + return -RT_EINVAL; + } + + opp_pick_voltages(opp_np, info, &cpu_uvolt, &mem_uvolt); + opp->uvolt = cpu_uvolt; + opp->priv = (void *)(rt_ubase_t)mem_uvolt; + + opp->available = RT_TRUE; + + if (rt_ofw_prop_read_bool(opp_np, "opp-suspend")) + { + dvfs->suspend_freq = opp->freq; + } + + return RT_EOK; +} + +static rt_err_t parse_opp_table_props(struct rt_ofw_node *opp_np, + struct opp_info *info) +{ + if (!opp_np || !info) + { + return -RT_EINVAL; + } + + info->opp_np = opp_np; + rt_ofw_node_get(opp_np); + + info->bin = -1; + info->process = -1; + info->volt_sel = -1; + info->scale = 0; + info->leakage = 0; + + read_leakage(opp_np, &info->leakage); + + if (info->data && info->data->get_soc_info) + { + info->data->get_soc_info(info->dev, opp_np, &info->bin, &info->process); + } + else + { + get_soc_info_default(info->dev, opp_np, &info->bin, &info->process); + } + + return RT_EOK; +} + +const char *regulator_name(struct rt_ofw_node *cpu_np) +{ + struct rt_ofw_node *np; + + np = rt_ofw_parse_phandle(cpu_np, "cpu-supply", 0); + if (np) + { + rt_ofw_node_put(np); + return "cpu"; + } + + np = rt_ofw_parse_phandle(cpu_np, "cpu0-supply", 0); + if (np) + { + rt_ofw_node_put(np); + return "cpu0"; + } + + return RT_NULL; +} + +rt_err_t opp_table_init(struct rt_device *dev, struct opp_info *info, + const char *reg_name) +{ + rt_err_t err; + struct rt_ofw_node *cpu_np = dev->ofw_node, *opp_np; + + if (!dev || !info || !cpu_np) + { + return -RT_EINVAL; + } + + rt_memset(info, 0, sizeof(*info)); + rt_spin_lock_init(&info->dvfs_lock); + + info->dev = dev; + info->is_runtime_active = RT_TRUE; + info->data = &default_opp_data; + + if (!(opp_np = rt_ofw_parse_phandle(cpu_np, "operating-points-v2", 0))) + { + LOG_E("%s: no operating-points-v2", rt_dm_dev_get_name(dev)); + return -RT_ENOENT; + } + + get_opp_data(RT_NULL, info); + + if ((err = parse_opp_table_props(opp_np, info))) + { + rt_ofw_node_put(opp_np); + return err; + } + + if (!reg_name) + { + reg_name = regulator_name(cpu_np); + } + if (!reg_name) + { + LOG_E("%s: no cpu supply", rt_dm_dev_get_name(dev)); + err = -RT_ENOENT; + goto _out; + } + + info->clk = rt_clk_get_by_index(dev, 0); + if (rt_is_err(info->clk)) + { + LOG_E("%s: get clk failed", rt_dm_dev_get_name(dev)); + err = rt_ptr_err(info->clk); + goto _out; + } + + if ((err = rt_clk_prepare_enable(info->clk))) + if (err) + { + LOG_E("%s: enable clk failed: %s", + rt_dm_dev_get_name(dev), rt_strerror(err)); + rt_clk_put(info->clk); + info->clk = RT_NULL; + goto _out; + } + + info->supply = rt_regulator_get(dev, reg_name); + if (rt_is_err(info->supply)) + { + err = rt_ptr_err(info->supply); + LOG_E("%s: get %s supply failed: %s", + rt_dm_dev_get_name(dev), reg_name, rt_strerror(err)); + info->supply = RT_NULL; + goto _out; + } + + if (rt_ofw_prop_read_bool(cpu_np, "mem-supply")) + { + info->mem_supply = rt_regulator_get(dev, "mem"); + if (rt_is_err(info->mem_supply)) + { + info->mem_supply = RT_NULL; + } + } + + rt_ofw_node_put(opp_np); + return RT_EOK; + +_out: + rt_ofw_node_put(opp_np); + if (!rt_is_err(info->clk)) + { + rt_clk_put(info->clk); + } + return err; +} + +void opp_table_uninit(struct rt_device *dev, struct opp_info *info) +{ + RT_UNUSED(dev); + + if (!info) + { + return; + } + + if (!rt_is_err(info->clk)) + { + rt_clk_disable_unprepare(info->clk); + rt_clk_put(info->clk); + info->clk = RT_NULL; + } + + if (info->supply) + { + rt_regulator_put(info->supply); + info->supply = RT_NULL; + } + + if (info->mem_supply) + { + rt_regulator_put(info->mem_supply); + info->mem_supply = RT_NULL; + } + + if (info->opp_np) + { + rt_ofw_node_put(info->opp_np); + info->opp_np = RT_NULL; + } +} + +#ifdef RT_SOC_ROCKCHIP_PVTM +static rt_err_t nvmem_cell_read_u16(struct rt_ofw_node *np, const char *cell_id, + rt_uint16_t *val) +{ + rt_ssize_t ret; + struct rt_device dev = { .ofw_node = (struct rt_ofw_node *)np }; + struct rt_nvmem_cell *cell; + + if (!np || !val) + { + return -RT_EINVAL; + } + + cell = rt_nvmem_get_cell_by_name(&dev, cell_id); + if (rt_is_err_or_null(cell)) + { + return -RT_ENOENT; + } + + ret = rt_nvmem_cell_read_u16(cell, val); + rt_nvmem_put_cell(cell); + + return ret < 0 ? (rt_err_t)ret : RT_EOK; +} + +static rt_err_t get_prop_sel(struct rt_ofw_node *np, const char *prop_name, + rt_int32_t value, int *sel) +{ + rt_uint32_t tuple[3], count; + int picked = -1; + + if (!np || !prop_name || !sel) + { + return -RT_EINVAL; + } + + count = rt_ofw_prop_count_of_size(np, prop_name, sizeof(rt_uint32_t)); + if (count < 0 || (count % 3) != 0) + { + return -RT_EINVAL; + } + + for (rt_uint32_t i = 0; i < count; i += 3) + { + if (rt_ofw_prop_read_u32_array_index(np, prop_name, i, 3, tuple)) + { + continue; + } + + if (value >= (rt_int32_t)tuple[0]) + { + picked = (int)tuple[2]; + } + } + + if (picked < 0) + { + return -RT_ENOENT; + } + + *sel = picked; + return RT_EOK; +} + +static rt_int32_t read_pvtm(struct rt_ofw_node *opp_np) +{ + rt_uint16_t tmp = 0; + rt_uint32_t ch[2] = {0}, sample_time = 1000, pvtm; + + if (!opp_np) + { + return -RT_EINVAL; + } + + if (!nvmem_cell_read_u16(opp_np, "pvtm", &tmp)) + { + return (rt_int32_t)(tmp * 10); + } + + if (rt_ofw_prop_read_u32_array_index(opp_np, "rockchip,pvtm-ch", 0, 2, ch)) + { + return -RT_ENOENT; + } + + rt_ofw_prop_read_u32(opp_np, "rockchip,pvtm-sample-time", &sample_time); + + pvtm = rockchip_get_pvtm_value(ch[0], ch[1], sample_time); + if (pvtm <= 0) + { + return pvtm; + } + + return pvtm; +} + +static void apply_pvtm_sel(struct rt_device *dev, struct rt_ofw_node *opp_np, + struct opp_info *info) +{ + char prop_name[32]; + rt_int32_t pvtm; + int volt_sel = -1, scale_sel = -1; + + if (!dev || !opp_np || !info) + { + return; + } + + if (rt_ofw_prop_read_bool(opp_np, "rockchip,pvtm-pvtpll")) + { + return; + } + + pvtm = read_pvtm(opp_np); + if (pvtm <= 0) + { + return; + } + + if (info->process >= 0) + { + rt_snprintf(prop_name, sizeof(prop_name), + "rockchip,p%d-pvtm-voltage-sel", info->process); + } + else if (info->bin > 0) + { + rt_snprintf(prop_name, sizeof(prop_name), + "rockchip,pvtm-voltage-sel-B%d", info->bin); + } + else + { + rt_strncpy(prop_name, "rockchip,pvtm-voltage-sel", sizeof(prop_name)); + } + + if (!get_prop_sel(opp_np, prop_name, pvtm, &volt_sel)) + { + info->volt_sel = volt_sel; + } + else if (!get_prop_sel(opp_np, "rockchip,pvtm-voltage-sel", pvtm, &volt_sel)) + { + info->volt_sel = volt_sel; + } + + if (info->process >= 0) + { + rt_snprintf(prop_name, sizeof(prop_name), + "rockchip,p%d-pvtm-scaling-sel", info->process); + } + else + { + rt_strncpy(prop_name, "rockchip,pvtm-scaling-sel", sizeof(prop_name)); + } + + if (!get_prop_sel(opp_np, prop_name, pvtm, &scale_sel)) + { + info->scale = scale_sel; + } + + LOG_D("%s: pvtm=%d volt_sel=%d scale=%d", + rt_dm_dev_get_name(dev), pvtm, info->volt_sel, info->scale); +} +#endif /* RT_SOC_ROCKCHIP_PVTM */ + +rt_err_t opp_table_adjust(struct rt_device *dev, struct opp_info *info, + struct rt_dvfs_scaling *scaling) +{ + struct rt_dvfs_opp *opp; + struct rt_ofw_node *opp_np, *opp_child_np; + + if (!dev || !info || !scaling || !scaling->opp_table) + { + return -RT_EINVAL; + } + + opp_np = info->opp_np; + if (!opp_np) + { + return -RT_ENOENT; + } + +#ifdef RT_SOC_ROCKCHIP_PVTM + apply_pvtm_sel(dev, opp_np, info); +#endif + + rt_ofw_foreach_child_node(opp_np, opp_child_np) + { + rt_uint64_t hz = 0; + + if (rt_ofw_prop_read_u64(opp_child_np, "opp-hz", &hz)) + { + continue; + } + + opp = rt_dvfs_scaling_find_opp(scaling, (rt_ubase_t)hz); + if (!opp) + { + continue; + } + + opp_parse(scaling, opp, opp_child_np, info); + } + + if (info->data && info->data->set_soc_info) + { + info->data->set_soc_info(dev, opp_np, info); + } + + LOG_D("%s: OPP adjusted leakage=%d bin=%d %lu-%lu Hz", + rt_dm_dev_get_name(dev), info->leakage, info->bin, + scaling->min_freq, scaling->max_freq); + + return RT_EOK; +} + +static rt_err_t set_regulator_volt(struct rt_regulator *reg, + rt_ubase_t old_uvolt, rt_ubase_t new_uvolt) +{ + if (!reg) + { + return new_uvolt == old_uvolt ? RT_EOK : -RT_EINVAL; + } + + if (new_uvolt == old_uvolt) + { + return RT_EOK; + } + + return rt_regulator_set_voltage(reg, new_uvolt, new_uvolt); +} + +rt_err_t cpufreq_sync_hw_state(struct rt_dvfs_scaling *scaling) +{ + rt_ubase_t rate; + struct rt_dvfs_opp *opp; + + if (!scaling || !scaling->clk || !scaling->opp_table) + { + return -RT_EINVAL; + } + + if (!(rate = rt_clk_get_rate(scaling->clk))) + { + return -RT_ENOENT; + } + + if (!(opp = rt_dvfs_scaling_find_floor_opp(scaling, rate))) + { + opp = rt_dvfs_scaling_find_ceil_opp(scaling, rate); + } + if (!opp || !opp->available) + { + return -RT_ENOENT; + } + + scaling->cur_freq = rate; + scaling->opp_table->current_opp = opp; + + LOG_D("%s: synced hw state %lu Hz @ %lu uV", + rt_dm_dev_get_name(scaling->dev), rate, opp->uvolt); + + return RT_EOK; +} + +rt_err_t cpufreq_set_opp(struct rt_dvfs_scaling *scaling, + struct rt_dvfs_opp *opp, struct opp_info *info) +{ + rt_err_t err = RT_EOK; + struct rt_dvfs_opp *old_opp; + rt_ubase_t old_freq, old_cpu_uvolt, new_freq, new_cpu_uvolt, new_mem_uvolt, old_mem_uvolt = 0; + + if (!scaling || !opp || !info) + { + return -RT_EINVAL; + } + + opp_dvfs_lock(info); + + old_opp = scaling->opp_table ? scaling->opp_table->current_opp : RT_NULL; + old_freq = scaling->cur_freq; + if (old_opp) + { + old_cpu_uvolt = old_opp->uvolt; + if (old_opp->priv) + { + old_mem_uvolt = (rt_ubase_t)old_opp->priv; + } + } + + new_freq = opp->freq; + new_cpu_uvolt = opp->uvolt; + new_mem_uvolt = opp->priv ? (rt_ubase_t)opp->priv : 0; + + opp_dvfs_unlock(info); + + if (!old_opp && scaling->clk) + { + rt_ubase_t hw_rate = rt_clk_get_rate(scaling->clk); + + if (hw_rate) + { + struct rt_dvfs_opp *hw_opp; + + if (!old_freq) + { + old_freq = hw_rate; + } + + hw_opp = rt_dvfs_scaling_find_floor_opp(scaling, hw_rate); + if (!hw_opp) + { + hw_opp = rt_dvfs_scaling_find_ceil_opp(scaling, hw_rate); + } + if (hw_opp) + { + if (!old_opp) + { + old_opp = hw_opp; + old_cpu_uvolt = hw_opp->uvolt; + if (hw_opp->priv) + { + old_mem_uvolt = (rt_ubase_t)hw_opp->priv; + } + } + } + } + } + + if (info->data && info->data->config_regulators) + { + err = info->data->config_regulators(scaling->dev, old_opp, opp, info); + } + else if (new_freq > old_freq) + { + err = set_regulator_volt(scaling->supply, old_cpu_uvolt, new_cpu_uvolt); + if (!err && info->mem_supply) + { + err = set_regulator_volt(info->mem_supply, old_mem_uvolt, new_mem_uvolt); + } + if (!err && scaling->transition_latency) + { + rt_dvfs_ns_sleep(scaling->transition_latency); + } + if (!err) + { + if (scaling->clk) + { + err = rt_clk_set_rate(scaling->clk, new_freq); + } + else + { + err = -RT_ENOSYS; + } + } + } + else if (new_freq < old_freq) + { + if (scaling->clk) + { + err = rt_clk_set_rate(scaling->clk, new_freq); + } + else + { + err = -RT_ENOSYS; + } + if (!err) + { + err = set_regulator_volt(scaling->supply, old_cpu_uvolt, new_cpu_uvolt); + } + if (!err && info->mem_supply) + { + err = set_regulator_volt(info->mem_supply, old_mem_uvolt, new_mem_uvolt); + } + } + else + { + err = set_regulator_volt(scaling->supply, old_cpu_uvolt, new_cpu_uvolt); + if (!err && info->mem_supply) + { + err = set_regulator_volt(info->mem_supply, old_mem_uvolt, new_mem_uvolt); + } + } + + opp_dvfs_lock(info); + + if (!err) + { + scaling->cur_freq = new_freq; + if (scaling->opp_table) + { + scaling->opp_table->current_opp = opp; + } + } + else + { + LOG_W("%s: set OPP %lu Hz/%lu uV failed at %lu Hz/%lu uV: %s", + rt_dm_dev_get_name(scaling->dev), new_freq, new_cpu_uvolt, + old_freq, old_cpu_uvolt, rt_strerror(err)); + } + + opp_dvfs_unlock(info); + return err; +} diff --git a/bsp/rockchip/rk3500/.config b/bsp/rockchip/rk3500/.config index 91f13fc1266..cff7bafda2b 100644 --- a/bsp/rockchip/rk3500/.config +++ b/bsp/rockchip/rk3500/.config @@ -445,6 +445,7 @@ CONFIG_RT_MBOX_PIC=y CONFIG_RT_MBOX_ROCKCHIP=y CONFIG_RT_USING_HWSPINLOCK=y CONFIG_RT_HWSPINLOCK_ROCKCHIP=y +# CONFIG_RT_USING_RPMSG is not set CONFIG_RT_USING_PHYE=y # CONFIG_RT_PHYE_GENERIC_USB is not set CONFIG_RT_PHYE_ROCKCHIP_NANENG_COMBO=y @@ -464,11 +465,32 @@ CONFIG_RT_BLK_PARTITION_EFI=y # CONFIG_RT_USING_SCSI is not set CONFIG_RT_USING_FIRMWARE=y +# CONFIG_RT_FIRMWARE_QEMU_FW_CFG is not set CONFIG_RT_FIRMWARE_ARM_SCMI=y CONFIG_RT_FIRMWARE_ARM_SCMI_TRANSPORT_MAILBOX=y CONFIG_RT_FIRMWARE_ARM_SCMI_TRANSPORT_SMC=y # CONFIG_RT_USING_HWCACHE is not set +CONFIG_RT_USING_DVFS=y +CONFIG_RT_USING_DVFS_EVENT=y +CONFIG_RT_USING_DVFS_OPP_RETRY_MAX=10 + +# +# DVFS Event Drivers +# +CONFIG_RT_DVFS_EVENT_ROCKCHIP_DFI=y + +# +# DVFS CPUfreq Drivers +# +# CONFIG_RT_DVFS_SCMI_CPUFREQ is not set +CONFIG_RT_DVFS_ROCKCHIP_CPUFREQ=y + +# +# DVFS Devfreq Drivers +# +CONFIG_RT_DVFS_ROCKCHIP_DMC=y CONFIG_RT_USING_REGULATOR=y +CONFIG_RT_REGULATOR_FAN53555=y CONFIG_RT_REGULATOR_FIXED=y CONFIG_RT_REGULATOR_GPIO=y CONFIG_RT_REGULATOR_SCMI=y @@ -497,6 +519,7 @@ CONFIG_RT_THERMAL_ROCKCHIP_TSADC=y # # Thermal Cool Drivers # +CONFIG_RT_THERMAL_COOL_DVFS=y CONFIG_RT_THERMAL_COOL_PWM_FAN=y # CONFIG_RT_USING_VIRTIO is not set CONFIG_RT_USING_NVMEM=y @@ -785,6 +808,7 @@ CONFIG_RT_USING_VDSO=y # end of Using USB legacy version # CONFIG_RT_USING_FDT is not set +# CONFIG_RT_USING_LEGACY_VIRTIO is not set # CONFIG_RT_USING_RUST is not set # end of RT-Thread Components @@ -861,12 +885,31 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # CONFIG_RT_UTEST_TC_USING_DFS_API is not set # end of File System +# +# Tmpfs Testcase +# +# CONFIG_RT_UTEST_TMPFS_CP is not set +# end of Tmpfs Testcase + # # CPP11 # # CONFIG_RT_UTEST_CPP11_THREAD is not set # end of CPP11 +# +# LWP Testcase +# +# CONFIG_RT_UTEST_LWP is not set +# end of LWP Testcase + +# +# Memory Management Subsytem Testcase +# +# CONFIG_RT_UTEST_MM_API is not set +# CONFIG_RT_UTEST_MM_LWP is not set +# end of Memory Management Subsytem Testcase + # # Network # @@ -881,19 +924,6 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # CONFIG_RT_UTEST_SELF_PASS is not set # end of Utest Framework # end of Kernel Components - -# -# Memory Management Subsytem Testcase -# -# CONFIG_RT_UTEST_MM_API is not set -# CONFIG_RT_UTEST_MM_LWP is not set -# end of Memory Management Subsytem Testcase - -# -# Tmpfs Testcase -# -# CONFIG_RT_UTEST_TMPFS_CP is not set -# end of Tmpfs Testcase # end of RT-Thread Utestcases # @@ -1089,7 +1119,6 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # CONFIG_PKG_USING_U8G2 is not set # end of u8g2: a monochrome graphic library -# CONFIG_PKG_USING_NES_SIMULATOR is not set # CONFIG_PKG_USING_OPENMV is not set # CONFIG_PKG_USING_MUPDF is not set # CONFIG_PKG_USING_STEMWIN is not set @@ -1114,9 +1143,6 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # # tools packages # -# CONFIG_PKG_USING_VECTOR is not set -# CONFIG_PKG_USING_SORCH is not set -# CONFIG_PKG_USING_DICT is not set # CONFIG_PKG_USING_CMBACKTRACE is not set # CONFIG_PKG_USING_MCOREDUMP is not set # CONFIG_PKG_USING_EASYFLASH is not set @@ -1165,9 +1191,6 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # CONFIG_PKG_USING_RVBACKTRACE is not set # CONFIG_PKG_USING_HPATCHLITE is not set # CONFIG_PKG_USING_THREAD_METRIC is not set -# CONFIG_PKG_USING_UORB is not set -# CONFIG_PKG_USING_RT_TUNNEL is not set -# CONFIG_PKG_USING_VIRTUAL_TERMINAL is not set # end of tools packages # @@ -1262,9 +1285,6 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # CONFIG_PKG_USING_R_RHEALSTONE is not set # CONFIG_PKG_USING_HEARTBEAT is not set # CONFIG_PKG_USING_MICRO_ROS_RTTHREAD_PACKAGE is not set -# CONFIG_PKG_USING_CHERRYECAT is not set -# CONFIG_PKG_USING_EVENT_LOOP is not set -# CONFIG_PKG_USING_THREAD_MANAGER is not set # end of system packages # @@ -1410,7 +1430,9 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # # NUVOTON Drivers # +# CONFIG_PKG_USING_NUVOTON_CMSIS_DRIVER is not set # CONFIG_PKG_USING_NUVOTON_SERIES_DRIVER is not set +# CONFIG_PKG_USING_NUVOTON_ARM926_LIB is not set # end of NUVOTON Drivers # @@ -1418,24 +1440,7 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # # CONFIG_PKG_USING_GD32_ARM_CMSIS_DRIVER is not set # CONFIG_PKG_USING_GD32_ARM_SERIES_DRIVER is not set -# CONFIG_PKG_USING_GD32_RISCV_SERIES_DRIVER is not set -# CONFIG_PKG_USING_GD32VW55X_WIFI is not set # end of GD32 Drivers - -# -# HPMicro SDK -# -# CONFIG_PKG_USING_HPM_SDK is not set -# end of HPMicro SDK - -# -# FT32 HAL & SDK Drivers -# -# CONFIG_PKG_USING_FT32F0_STD_DRIVER is not set -# CONFIG_PKG_USING_FT32F0_CMSIS_DRIVER is not set -# CONFIG_PKG_USING_FT32F4_STD_DRIVER is not set -# CONFIG_PKG_USING_FT32F4_CMSIS_DRIVER is not set -# end of FT32 HAL & SDK Drivers # end of HAL & SDK Drivers # @@ -1481,11 +1486,9 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # CONFIG_PKG_USING_RT3020 is not set # CONFIG_PKG_USING_MLX90632 is not set # CONFIG_PKG_USING_MLX90382 is not set -# CONFIG_PKG_USING_MLX90384 is not set # CONFIG_PKG_USING_MLX90393 is not set # CONFIG_PKG_USING_MLX90392 is not set # CONFIG_PKG_USING_MLX90394 is not set -# CONFIG_PKG_USING_MLX90396 is not set # CONFIG_PKG_USING_MLX90397 is not set # CONFIG_PKG_USING_MS5611 is not set # CONFIG_PKG_USING_MAX31865 is not set @@ -1514,7 +1517,6 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # CONFIG_PKG_USING_P3T1755 is not set # CONFIG_PKG_USING_QMI8658 is not set # CONFIG_PKG_USING_ICM20948 is not set -# CONFIG_PKG_USING_SCD4X is not set # end of sensors drivers # @@ -1532,7 +1534,6 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # CONFIG_PKG_USING_CST812T is not set # end of touch drivers -# CONFIG_PKG_USING_LCD_SPI_DRIVER is not set # CONFIG_PKG_USING_REALTEK_AMEBA is not set # CONFIG_PKG_USING_BUTTON is not set # CONFIG_PKG_USING_PCF8574 is not set @@ -1612,13 +1613,6 @@ CONFIG_RT_UTEST_SMP_CALL_FUNC=y # CONFIG_PKG_USING_IC74HC165 is not set # CONFIG_PKG_USING_IST8310 is not set # CONFIG_PKG_USING_ST7789_SPI is not set -# CONFIG_PKG_USING_CAN_UDS is not set -# CONFIG_PKG_USING_ISOTP_C is not set -# CONFIG_PKG_USING_IKUNLED is not set -# CONFIG_PKG_USING_INS5T8025 is not set -# CONFIG_PKG_USING_IRUART is not set -# CONFIG_PKG_USING_ST7305 is not set -# CONFIG_PKG_USING_TM1668 is not set # CONFIG_PKG_USING_SPI_TOOLS is not set # end of peripheral libraries and drivers diff --git a/bsp/rockchip/rk3500/rtconfig.h b/bsp/rockchip/rk3500/rtconfig.h index e8e7204a045..bebf4706dff 100644 --- a/bsp/rockchip/rk3500/rtconfig.h +++ b/bsp/rockchip/rk3500/rtconfig.h @@ -305,7 +305,23 @@ #define RT_FIRMWARE_ARM_SCMI #define RT_FIRMWARE_ARM_SCMI_TRANSPORT_MAILBOX #define RT_FIRMWARE_ARM_SCMI_TRANSPORT_SMC +#define RT_USING_DVFS +#define RT_USING_DVFS_EVENT +#define RT_USING_DVFS_OPP_RETRY_MAX 10 + +/* DVFS Event Drivers */ + +#define RT_DVFS_EVENT_ROCKCHIP_DFI + +/* DVFS CPUfreq Drivers */ + +#define RT_DVFS_ROCKCHIP_CPUFREQ + +/* DVFS Devfreq Drivers */ + +#define RT_DVFS_ROCKCHIP_DMC #define RT_USING_REGULATOR +#define RT_REGULATOR_FAN53555 #define RT_REGULATOR_FIXED #define RT_REGULATOR_GPIO #define RT_REGULATOR_SCMI @@ -326,6 +342,7 @@ /* Thermal Cool Drivers */ +#define RT_THERMAL_COOL_DVFS #define RT_THERMAL_COOL_PWM_FAN #define RT_USING_NVMEM #define RT_NVMEM_ROCKCHIP_OTP @@ -576,10 +593,22 @@ /* end of File System */ +/* Tmpfs Testcase */ + +/* end of Tmpfs Testcase */ + /* CPP11 */ /* end of CPP11 */ +/* LWP Testcase */ + +/* end of LWP Testcase */ + +/* Memory Management Subsytem Testcase */ + +/* end of Memory Management Subsytem Testcase */ + /* Network */ /* end of Network */ @@ -588,14 +617,6 @@ /* end of Utest Framework */ /* end of Kernel Components */ - -/* Memory Management Subsytem Testcase */ - -/* end of Memory Management Subsytem Testcase */ - -/* Tmpfs Testcase */ - -/* end of Tmpfs Testcase */ /* end of RT-Thread Utestcases */ /* RT-Thread online packages */ @@ -719,14 +740,6 @@ /* GD32 Drivers */ /* end of GD32 Drivers */ - -/* HPMicro SDK */ - -/* end of HPMicro SDK */ - -/* FT32 HAL & SDK Drivers */ - -/* end of FT32 HAL & SDK Drivers */ /* end of HAL & SDK Drivers */ /* sensors drivers */ diff --git a/components/drivers/regulator/Kconfig b/components/drivers/regulator/Kconfig index 17d2f5b17aa..8767d59a7d6 100644 --- a/components/drivers/regulator/Kconfig +++ b/components/drivers/regulator/Kconfig @@ -4,6 +4,12 @@ menuconfig RT_USING_REGULATOR select RT_USING_ADT_REF default n +config RT_REGULATOR_FAN53555 + bool "Fairchild FAN53555 / TCS4525 Regulator" + depends on RT_USING_REGULATOR + depends on RT_USING_I2C + default n + config RT_REGULATOR_FIXED bool "Fixed regulator support" depends on RT_USING_REGULATOR diff --git a/components/drivers/regulator/SConscript b/components/drivers/regulator/SConscript index 2069f508695..70245b9c491 100755 --- a/components/drivers/regulator/SConscript +++ b/components/drivers/regulator/SConscript @@ -10,6 +10,9 @@ CPPPATH = [cwd + '/../include'] src = ['regulator.c', 'regulator_dm.c', 'regulator_cmd.c'] +if GetDepend(['RT_REGULATOR_FAN53555']): + src += ['regulator-fan53555.c'] + if GetDepend(['RT_REGULATOR_FIXED']): src += ['regulator-fixed.c'] diff --git a/components/drivers/regulator/regulator-fan53555.c b/components/drivers/regulator/regulator-fan53555.c new file mode 100644 index 00000000000..3d5e58eb54a --- /dev/null +++ b/components/drivers/regulator/regulator-fan53555.c @@ -0,0 +1,1091 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-26 GuEe-GUI first version + */ + +#define DBG_TAG "regulator.fan53555" +#define DBG_LVL DBG_INFO +#include + +#include "regulator_dm.h" + +/* Voltage setting */ +#define FAN53555_VSEL0 0x00 +#define FAN53555_VSEL1 0x01 + +#define TCS4525_VSEL0 0x11 +#define TCS4525_VSEL1 0x10 +#define TCS4525_TIME 0x13 +#define TCS4525_COMMAND 0x14 +#define TCS4525_LIMCONF 0x16 + +#define RK8600_VSEL0 FAN53555_VSEL0 +#define RK8600_VSEL1 FAN53555_VSEL1 +#define RK8602_VSEL0 0x06 +#define RK8602_VSEL1 0x07 +#define RK860X_MAX_SET 0x08 + +/* Control register */ +#define FAN53555_CONTROL 0x02 +/* IC Type */ +#define FAN53555_ID1 0x03 +/* IC mask version */ +#define FAN53555_ID2 0x04 +/* Monitor register */ +#define FAN53555_MONITOR 0x05 + +/* VSEL bit definitions */ +#define VSEL_BUCK_EN RT_BIT(7) +#define VSEL_MODE RT_BIT(6) +#define RK8600_VSEL_NSEL_MASK 0x3f +#define RK8602_VSEL_NSEL_MASK 0xff +/* Chip ID */ +#define DIE_ID 0x0f +#define DIE_REV 0x0f +/* Control bit definitions */ +#define CTL_OUTPUT_DISCHG RT_BIT(7) +#define CTL_SLEW_MASK (0x7 << 4) +#define CTL_SLEW_SHIFT 4 +#define CTL_RESET RT_BIT(2) +#define CTL_MODE_VSEL0_MODE RT_BIT(0) +#define CTL_MODE_VSEL1_MODE RT_BIT(1) + +#define FAN53555_NVOLTAGES 64 /* Numbers of voltages */ +#define FAN53526_NVOLTAGES 128 +#define RK8600_NVOLTAGES FAN53555_NVOLTAGES +#define RK8602_NVOLTAGES 160 + +#define TCS_VSEL0_MODE RT_BIT(7) +#define TCS_VSEL1_MODE RT_BIT(6) + +#define TCS_SLEW_MASK (0x3 << 3) +#define TCS_SLEW_SHIFT 3 + +/* VSEL ID */ +enum +{ + FAN53555_VSEL_ID_0 = 0, + FAN53555_VSEL_ID_1, +}; + +enum fan53555_vendor +{ + FAN53526_VENDOR_FAIRCHILD = 0, + FAN53555_VENDOR_FAIRCHILD, + RK8600_VENDOR_ROCKCHIP, /* RK8600, RK8601 */ + RK8602_VENDOR_ROCKCHIP, /* RK8602, RK8603 */ + FAN53555_VENDOR_SILERGY, + FAN53526_VENDOR_TCS, +}; + +enum +{ + FAN53526_CHIP_ID_01 = 1, +}; + +enum +{ + FAN53526_CHIP_REV_08 = 8, +}; + +/* IC Type */ +enum +{ + FAN53555_CHIP_ID_00 = 0, + FAN53555_CHIP_ID_01, + FAN53555_CHIP_ID_02, + FAN53555_CHIP_ID_03, + FAN53555_CHIP_ID_04, + FAN53555_CHIP_ID_05, + FAN53555_CHIP_ID_08 = 8, +}; + +enum +{ + RK8600_CHIP_ID_08 = 8, /* RK8600, RK8601 */ + RK8602_CHIP_ID_10 = 10, /* RK8602, RK8603 */ +}; + +enum +{ + TCS4525_CHIP_ID_12 = 12, +}; + +enum +{ + TCS4526_CHIP_ID_00 = 0, +}; + +/* IC mask revision */ +enum +{ + FAN53555_CHIP_REV_00 = 0x3, + FAN53555_CHIP_REV_13 = 0xf, +}; + +enum +{ + SILERGY_SYR82X = 8, + SILERGY_SYR83X = 9, +}; + +struct fan53555_regulator +{ + struct rt_regulator_node parent; + struct rt_regulator_param param; + + struct rt_i2c_client *client; + + rt_ubase_t vsel_pin; + + /* IC Type and Rev */ + int chip_id; + int chip_rev; + enum fan53555_vendor vendor; + + rt_uint32_t limit_volt; + + /* Voltage setting register */ + rt_uint32_t vol_reg; + rt_uint32_t vol_mask; + rt_uint32_t sleep_reg; + rt_uint32_t en_reg; + rt_uint32_t sleep_en_reg; + /* Mode */ + rt_uint32_t mode_reg; + rt_uint32_t mode_mask; + /* Slew rate */ + rt_uint32_t slew_reg; + rt_uint32_t slew_mask; + rt_uint32_t slew_shift; + /* Voltage range and step(linear) */ + rt_uint32_t vsel_min; + rt_uint32_t vsel_step; + rt_uint32_t vsel_count; + + /* Voltage slew rate limiting */ + rt_uint32_t slew_rate; + rt_uint32_t sleep_vsel_id; + + const int *ramp_delay_table; + int ramp_delay_nr; +}; + +#define raw_to_fan53555_regulator(raw) rt_container_of(raw, struct fan53555_regulator, parent) + +static rt_uint32_t fan53555_vol_mask(rt_uint32_t vsel_count) +{ + rt_uint32_t bit; + + if (!vsel_count) + { + return 0; + } + + bit = 32 - __rt_clz(vsel_count - 1); + + return (1U << bit) - 1; +} + +static const int slew_rates[] = +{ + 64000, 32000, 16000, 8000, 4000, 2000, 1000, 500, +}; + +static const int tcs_slew_rates[] = +{ + 18700, 9300, 4600, 2300, +}; + +static rt_err_t fan53555_regulator_write(struct fan53555_regulator *freg, + rt_uint8_t reg, rt_uint8_t value) +{ + rt_int32_t res; + struct rt_i2c_msg msg[1]; + rt_uint8_t data[sizeof(reg) + sizeof(value)] = { reg }; + struct rt_i2c_client *client = freg->client; + + rt_memcpy(&data[sizeof(reg)], &value, sizeof(value)); + + msg[0].buf = data; + msg[0].addr = client->client_addr; + msg[0].len = sizeof(data); + msg[0].flags = RT_I2C_WR; + + res = rt_i2c_transfer(client->bus, msg, 1); + + return res > 0 ? RT_EOK : res; +} + +static rt_err_t fan53555_regulator_read(struct fan53555_regulator *freg, + rt_uint8_t reg, rt_uint8_t *values) +{ + rt_int32_t res; + struct rt_i2c_msg msg[2]; + struct rt_i2c_client *client = freg->client; + + msg[0].buf = ® + msg[0].addr = client->client_addr; + msg[0].len = sizeof(reg); + msg[0].flags = RT_I2C_WR; + + msg[1].buf = (rt_uint8_t *)values; + msg[1].addr = client->client_addr; + msg[1].len = sizeof(*values); + msg[1].flags = RT_I2C_RD; + + res = rt_i2c_transfer(client->bus, msg, 2); + + return res >= 2 ? RT_EOK : (res < 0 ? (rt_err_t)res : -RT_EIO); +} + +static rt_err_t fan53555_regulator_update_bits(struct fan53555_regulator *freg, + rt_uint8_t reg, rt_uint8_t mask, rt_uint8_t data) +{ + rt_err_t err; + rt_uint8_t old, tmp; + + if ((err = fan53555_regulator_read(freg, reg, &old))) + { + return err; + } + + tmp = old & ~mask; + tmp |= (data & mask); + + return fan53555_regulator_write(freg, reg, tmp); +} + +static rt_err_t fan53555_regulator_enable(struct rt_regulator_node *reg_np) +{ + struct fan53555_regulator *freg = raw_to_fan53555_regulator(reg_np); + + if (freg->vsel_pin >= 0) + { + rt_pin_mode(freg->vsel_pin, PIN_MODE_OUTPUT); + rt_pin_write(freg->vsel_pin, !freg->sleep_vsel_id); + + return RT_EOK; + } + + return fan53555_regulator_update_bits(freg, freg->en_reg, VSEL_BUCK_EN, VSEL_BUCK_EN); +} + +static rt_err_t fan53555_regulator_disable(struct rt_regulator_node *reg_np) +{ + struct fan53555_regulator *freg = raw_to_fan53555_regulator(reg_np); + + if (freg->vsel_pin >= 0) + { + rt_pin_mode(freg->vsel_pin, PIN_MODE_OUTPUT); + rt_pin_write(freg->vsel_pin, freg->sleep_vsel_id); + + return RT_EOK; + } + + return fan53555_regulator_update_bits(freg, freg->en_reg, VSEL_BUCK_EN, 0); +} + +static rt_bool_t fan53555_regulator_is_enabled(struct rt_regulator_node *reg_np) +{ + rt_err_t err; + rt_uint8_t val; + struct fan53555_regulator *freg = raw_to_fan53555_regulator(reg_np); + + if (freg->vsel_pin >= 0) + { + rt_uint8_t pin_val; + + rt_pin_mode(freg->vsel_pin, PIN_MODE_INPUT); + pin_val = rt_pin_read(freg->vsel_pin); + + return freg->sleep_vsel_id ? !pin_val : pin_val; + } + + if ((err = fan53555_regulator_read(freg, freg->en_reg, &val))) + { + LOG_E("Read %s register error = %s", "enable", rt_strerror(err)); + + return RT_FALSE; + } + + if (val & VSEL_BUCK_EN) + { + return RT_TRUE; + } + + return RT_FALSE; +} + +static rt_err_t fan53555_regulator_set_voltage(struct rt_regulator_node *reg_np, + int min_uvolt, int max_uvolt) +{ + int uvolt; + rt_uint8_t selector; + struct fan53555_regulator *freg = raw_to_fan53555_regulator(reg_np); + + if (freg->vsel_count == 1 && freg->vsel_step == 0) + { + if (min_uvolt <= freg->vsel_min && freg->vsel_min <= max_uvolt) + { + return 0; + } + else + { + return -RT_EINVAL; + } + } + + if (!freg->vsel_step) + { + return -RT_EINVAL; + } + + if (min_uvolt < freg->vsel_min) + { + min_uvolt = freg->vsel_min; + } + + selector = RT_DIV_ROUND_UP(min_uvolt - freg->vsel_min, freg->vsel_step); + + if ((rt_int8_t)selector < 0) + { + return selector; + } + + if (selector >= freg->vsel_count) + { + return -RT_EINVAL; + } + + uvolt = freg->vsel_min + (freg->vsel_step * selector); + + if (uvolt < min_uvolt || uvolt > max_uvolt) + { + return -RT_EINVAL; + } + + selector <<= __rt_ffs(freg->vol_mask) - 1; + + return fan53555_regulator_update_bits(freg, freg->vol_reg, freg->vol_mask, selector); +} + +static int fan53555_regulator_get_voltage(struct rt_regulator_node *reg_np) +{ + rt_err_t err; + rt_uint8_t selector; + struct fan53555_regulator *freg = raw_to_fan53555_regulator(reg_np); + + if ((err = fan53555_regulator_read(freg, freg->vol_reg, &selector))) + { + return err; + } + + selector = (selector & freg->vol_mask) >> (__rt_ffs(freg->vol_mask) - 1); + + if (selector >= freg->vsel_count) + { + return -RT_EINVAL; + } + + return freg->vsel_min + (freg->vsel_step * selector); +} + +static rt_err_t fan53555_regulator_set_mode(struct rt_regulator_node *reg_np, + rt_uint32_t mode) +{ + struct fan53555_regulator *freg = raw_to_fan53555_regulator(reg_np); + + switch (mode) + { + case RT_REGULATOR_MODE_FAST: + fan53555_regulator_update_bits(freg, + freg->mode_reg, freg->mode_mask, freg->mode_mask); + break; + + case RT_REGULATOR_MODE_NORMAL: + fan53555_regulator_update_bits(freg, + freg->mode_reg, freg->mode_mask, 0); + break; + + default: + return -RT_EINVAL; + } + + return RT_EOK; +} + +static rt_int32_t fan53555_regulator_get_mode(struct rt_regulator_node *reg_np) +{ + rt_err_t err; + rt_uint8_t val; + struct fan53555_regulator *freg = raw_to_fan53555_regulator(reg_np); + + if ((err = fan53555_regulator_read(freg, freg->mode_reg, &val))) + { + LOG_E("Read %s register error = %s", "mode", rt_strerror(err)); + + return RT_FALSE; + } + + if (val & freg->mode_mask) + { + return RT_REGULATOR_MODE_FAST; + } + else + { + return RT_REGULATOR_MODE_NORMAL; + } +} + +static rt_err_t fan53555_regulator_set_ramp_delay(struct rt_regulator_node *reg_np, + int ramp) +{ + int regval = -1, ramp_delay_nr; + const int *ramp_delay_table; + struct fan53555_regulator *freg = raw_to_fan53555_regulator(reg_np); + + ramp_delay_nr = freg->ramp_delay_nr; + ramp_delay_table = freg->ramp_delay_table; + + for (int i = 0; i < ramp_delay_nr; ++i) + { + if (ramp <= ramp_delay_table[i]) + { + regval = i; + continue; + } + + break; + } + + if (regval < 0) + { + LOG_E("unsupported ramp value %d", ramp); + + return -RT_EINVAL; + } + + return fan53555_regulator_update_bits(freg, freg->slew_reg, + freg->slew_mask, regval << freg->slew_shift); +} + +static const struct rt_regulator_ops fan53555_regulator_switch_ops = +{ + .enable = fan53555_regulator_enable, + .disable = fan53555_regulator_disable, + .is_enabled = fan53555_regulator_is_enabled, + .set_voltage = fan53555_regulator_set_voltage, + .get_voltage = fan53555_regulator_get_voltage, + .set_mode = fan53555_regulator_set_mode, + .get_mode = fan53555_regulator_get_mode, + .set_ramp_delay = fan53555_regulator_set_ramp_delay, +}; + +#ifdef RT_USING_OFW +static rt_err_t fan53555_regulator_parse_ofw(struct fan53555_regulator *freg, + struct rt_device *dev) +{ + rt_err_t err; + struct rt_ofw_node *np = dev->ofw_node; + + if ((err = regulator_ofw_parse(np, &freg->param))) + { + return err; + } + + rt_ofw_prop_read_u32(np, "limit-microvolt", &freg->limit_volt); + + rt_ofw_prop_read_u32(np, "fcs,suspend-voltage-selector", &freg->sleep_vsel_id); + rt_ofw_prop_read_u32(np, "rockchip,suspend-voltage-selector", &freg->sleep_vsel_id); + + freg->vsel_pin = rt_ofw_get_named_pin(np, "vsel", 0, RT_NULL, RT_NULL); + if (freg->vsel_pin < 0) + { + freg->vsel_pin = PIN_NONE; + } + + return RT_EOK; +} +#else +static rt_err_t fan53555_regulator_parse_ofw(struct fan53555_regulator *freg, + struct rt_device *dev) +{ + return RT_EOK; +} +#endif /* RT_USING_OFW */ + +static rt_err_t fan53555_regulator_probe(struct rt_i2c_client *client) +{ + rt_err_t err; + rt_uint8_t val; + rt_bool_t hw_always_on; + rt_uint32_t ramp_delay; + struct rt_regulator_node *rgp; + struct rt_device *dev = &client->parent; + struct fan53555_regulator *freg = rt_calloc(1, sizeof(*freg)); + + if (!freg) + { + return -RT_ENOMEM; + } + + if ((err = fan53555_regulator_parse_ofw(freg, dev))) + { + goto _fail; + } + + if (dev->ofw_node) + { + freg->vendor = (rt_ubase_t)client->ofw_id->data; + } + else + { + if (!freg->param.ramp_delay) + { + int slew_idx = freg->slew_rate; + + if (slew_idx > RT_ARRAY_SIZE(slew_rates)) + { + LOG_E("Invalid slew rate"); + + err = -RT_EINVAL; + goto _fail; + } + + freg->param.ramp_delay = slew_rates[slew_idx]; + } + + freg->vendor = (rt_ubase_t)rt_i2c_client_id_data(client); + } + freg->client = client; + + /* Get chip ID */ + if ((err = fan53555_regulator_read(freg, FAN53555_ID1, &val))) + { + LOG_E("%s: read ID1 failed: %s", client->name, rt_strerror(err)); + goto _fail; + } + freg->chip_id = val & DIE_ID; + + /* Get chip revision */ + if ((err = fan53555_regulator_read(freg, FAN53555_ID2, &val))) + { + LOG_E("%s: read ID2 failed: %s", client->name, rt_strerror(err)); + goto _fail; + } + freg->chip_rev = val & DIE_REV; + + if (freg->sleep_vsel_id > FAN53555_VSEL_ID_1) + { + LOG_E("Invalid vsel-id = %d", freg->sleep_vsel_id); + goto _fail; + } + + /* + * For 00,01 options: + * VOUT = 0.7125V + NSELx * 12.5mV, from 0.7125 to 1.5V. + * For 02,03 options: + * VOUT = 0.5V + NSELx * 6.25mV, from 0.5 to 1.5V. + */ + + /* Setup voltage control register */ + freg->vol_reg = 0xff; + switch (freg->vendor) + { + case FAN53526_VENDOR_FAIRCHILD: + case FAN53555_VENDOR_FAIRCHILD: + case FAN53555_VENDOR_SILERGY: + switch (freg->sleep_vsel_id) + { + case FAN53555_VSEL_ID_0: + freg->sleep_reg = FAN53555_VSEL0; + freg->vol_reg = FAN53555_VSEL1; + break; + + case FAN53555_VSEL_ID_1: + freg->sleep_reg = FAN53555_VSEL1; + freg->vol_reg = FAN53555_VSEL0; + break; + + default: + break; + } + freg->sleep_en_reg = freg->sleep_reg; + freg->en_reg = freg->vol_reg; + break; + + case RK8600_VENDOR_ROCKCHIP: + switch (freg->sleep_vsel_id) + { + case FAN53555_VSEL_ID_0: + freg->sleep_reg = RK8600_VSEL0; + freg->vol_reg = RK8600_VSEL1; + break; + + case FAN53555_VSEL_ID_1: + freg->sleep_reg = RK8600_VSEL1; + freg->vol_reg = RK8600_VSEL0; + break; + + default: + break; + } + freg->sleep_en_reg = freg->sleep_reg; + freg->en_reg = freg->vol_reg; + freg->vol_reg = RK8600_VSEL_NSEL_MASK; + break; + + case RK8602_VENDOR_ROCKCHIP: + switch (freg->sleep_vsel_id) + { + case FAN53555_VSEL_ID_0: + freg->sleep_reg = RK8602_VSEL0; + freg->vol_reg = RK8602_VSEL1; + freg->sleep_en_reg = RK8600_VSEL0; + freg->en_reg = RK8600_VSEL1; + break; + + case FAN53555_VSEL_ID_1: + freg->sleep_reg = RK8602_VSEL1; + freg->vol_reg = RK8602_VSEL0; + freg->sleep_en_reg = RK8600_VSEL1; + freg->en_reg = RK8600_VSEL0; + break; + + default: + break; + } + freg->vol_reg = RK8602_VSEL_NSEL_MASK; + break; + + case FAN53526_VENDOR_TCS: + switch (freg->sleep_vsel_id) + { + case FAN53555_VSEL_ID_0: + freg->sleep_reg = TCS4525_VSEL0; + freg->vol_reg = TCS4525_VSEL1; + break; + + case FAN53555_VSEL_ID_1: + freg->sleep_reg = TCS4525_VSEL1; + freg->vol_reg = TCS4525_VSEL0; + break; + + default: + break; + } + freg->sleep_en_reg = freg->sleep_reg; + freg->en_reg = freg->vol_reg; + break; + + default: + break; + } + + /* Setup mode control register */ + switch (freg->vendor) + { + case FAN53526_VENDOR_FAIRCHILD: + freg->mode_reg = FAN53555_CONTROL; + + switch (freg->sleep_vsel_id) + { + case FAN53555_VSEL_ID_0: + freg->mode_mask = CTL_MODE_VSEL1_MODE; + break; + + case FAN53555_VSEL_ID_1: + freg->mode_mask = CTL_MODE_VSEL0_MODE; + break; + + default: + break; + } + break; + case FAN53555_VENDOR_FAIRCHILD: + case FAN53555_VENDOR_SILERGY: + case RK8600_VENDOR_ROCKCHIP: + freg->mode_reg = freg->vol_reg; + freg->mode_mask = VSEL_MODE; + break; + + case RK8602_VENDOR_ROCKCHIP: + freg->mode_mask = VSEL_MODE; + + switch (freg->sleep_vsel_id) + { + case FAN53555_VSEL_ID_0: + freg->mode_reg = RK8600_VSEL1; + break; + + case FAN53555_VSEL_ID_1: + freg->mode_reg = RK8600_VSEL0; + break; + + default: + break; + } + break; + + case FAN53526_VENDOR_TCS: + freg->mode_reg = TCS4525_COMMAND; + + switch (freg->sleep_vsel_id) + { + case FAN53555_VSEL_ID_0: + freg->mode_mask = TCS_VSEL1_MODE; + break; + + case FAN53555_VSEL_ID_1: + freg->mode_mask = TCS_VSEL0_MODE; + break; + + default: + break; + } + break; + + default: + break; + } + + /* Setup voltage range */ + switch (freg->vendor) + { + case FAN53526_VENDOR_FAIRCHILD: + /* Init voltage range and step */ + switch (freg->chip_id) + { + case FAN53526_CHIP_ID_01: + switch (freg->chip_rev) + { + case FAN53526_CHIP_REV_08: + freg->vsel_min = 600000; + freg->vsel_step = 6250; + break; + + default: + LOG_E("Chip ID %d with rev %d not supported", + freg->chip_id, freg->chip_rev); + err = -RT_EINVAL; + goto _fail; + } + break; + + default: + LOG_E("Chip ID %d not supported", freg->chip_id); + err = -RT_EINVAL; + goto _fail; + } + + freg->slew_reg = FAN53555_CONTROL; + freg->slew_mask = CTL_SLEW_MASK; + freg->slew_shift = CTL_SLEW_SHIFT; + freg->ramp_delay_table = slew_rates; + freg->ramp_delay_nr = RT_ARRAY_SIZE(slew_rates); + freg->vsel_count = FAN53526_NVOLTAGES; + break; + + case FAN53555_VENDOR_FAIRCHILD: + /* Init voltage range and step */ + switch (freg->chip_id) { + case FAN53555_CHIP_ID_00: + switch (freg->chip_rev) + { + case FAN53555_CHIP_REV_00: + freg->vsel_min = 600000; + freg->vsel_step = 10000; + break; + + case FAN53555_CHIP_REV_13: + freg->vsel_min = 800000; + freg->vsel_step = 10000; + break; + + default: + LOG_E("Chip ID %d with rev %d not supported", + freg->chip_id, freg->chip_rev); + err = -RT_EINVAL; + goto _fail; + } + break; + case FAN53555_CHIP_ID_01: + case FAN53555_CHIP_ID_03: + case FAN53555_CHIP_ID_05: + case FAN53555_CHIP_ID_08: + freg->vsel_min = 600000; + freg->vsel_step = 10000; + break; + + case FAN53555_CHIP_ID_04: + freg->vsel_min = 603000; + freg->vsel_step = 12826; + break; + + default: + LOG_E("Chip ID %d not supported", freg->chip_id); + err = -RT_EINVAL; + goto _fail; + } + + freg->slew_reg = FAN53555_CONTROL; + freg->slew_mask = CTL_SLEW_MASK; + freg->slew_shift = CTL_SLEW_SHIFT; + freg->ramp_delay_table = slew_rates; + freg->ramp_delay_nr = RT_ARRAY_SIZE(slew_rates); + freg->vsel_count = FAN53555_NVOLTAGES; + break; + + case FAN53555_VENDOR_SILERGY: + /* Init voltage range and step */ + switch (freg->chip_id) + { + case SILERGY_SYR82X: + case SILERGY_SYR83X: + freg->vsel_min = 712500; + freg->vsel_step = 12500; + break; + + default: + LOG_E("Chip ID %d not supported", freg->chip_id); + err = -RT_EINVAL; + goto _fail; + } + + freg->slew_reg = FAN53555_CONTROL; + freg->slew_mask = CTL_SLEW_MASK; + freg->slew_shift = CTL_SLEW_SHIFT; + freg->ramp_delay_table = slew_rates; + freg->ramp_delay_nr = RT_ARRAY_SIZE(slew_rates); + freg->vsel_count = FAN53555_NVOLTAGES; + break; + + case RK8600_VENDOR_ROCKCHIP: + /* Init voltage range and step */ + switch (freg->chip_id) + { + case RK8600_CHIP_ID_08: + freg->vsel_min = 712500; + freg->vsel_step = 12500; + break; + + default: + LOG_E("Chip ID %d not supported", freg->chip_id); + err = -RT_EINVAL; + goto _fail; + } + + freg->slew_reg = FAN53555_CONTROL; + freg->slew_mask = CTL_SLEW_MASK; + freg->slew_shift = CTL_SLEW_SHIFT; + freg->ramp_delay_table = slew_rates; + freg->ramp_delay_nr = RT_ARRAY_SIZE(slew_rates); + freg->vsel_count = RK8600_NVOLTAGES; + break; + + case RK8602_VENDOR_ROCKCHIP: + /* Init voltage range and step */ + switch (freg->chip_id) + { + case RK8602_CHIP_ID_10: + freg->vsel_min = 500000; + freg->vsel_step = 6250; + break; + + default: + LOG_E("Chip ID %d not supported", freg->chip_id); + err = -RT_EINVAL; + goto _fail; + } + + freg->slew_reg = FAN53555_CONTROL; + freg->slew_mask = CTL_SLEW_MASK; + freg->slew_shift = CTL_SLEW_SHIFT; + freg->ramp_delay_table = slew_rates; + freg->ramp_delay_nr = RT_ARRAY_SIZE(slew_rates); + freg->vsel_count = RK8602_NVOLTAGES; + break; + + case FAN53526_VENDOR_TCS: + switch (freg->chip_id) + { + case TCS4525_CHIP_ID_12: + case TCS4526_CHIP_ID_00: + freg->slew_reg = TCS4525_TIME; + freg->slew_mask = TCS_SLEW_MASK; + freg->slew_shift = TCS_SLEW_SHIFT; + freg->ramp_delay_table = tcs_slew_rates; + freg->ramp_delay_nr = RT_ARRAY_SIZE(tcs_slew_rates); + + /* Init voltage range and step */ + freg->vsel_min = 600000; + freg->vsel_step = 6250; + freg->vsel_count = FAN53526_NVOLTAGES; + break; + + default: + LOG_E("Chip ID %d not supported", freg->chip_id); + err = -RT_EINVAL; + goto _fail; + } + break; + + default: + break; + } + + if (freg->limit_volt) + { + if (freg->limit_volt < freg->vsel_min || freg->limit_volt > 1500000) + { + freg->limit_volt = 1500000; + } + + val = (freg->limit_volt - freg->vsel_min) / freg->vsel_step; + + if (freg->vendor == FAN53526_VENDOR_TCS) + { + err = fan53555_regulator_write(freg, TCS4525_LIMCONF, val); + } + else + { + err = fan53555_regulator_write(freg, RK860X_MAX_SET, val); + } + + if (err) + { + LOG_E("%s: Failed to set limit voltage", client->name); + goto _fail; + } + } + + freg->vol_mask = fan53555_vol_mask(freg->vsel_count); + + rgp = &freg->parent; + rgp->ops = &fan53555_regulator_switch_ops; + rgp->param = &freg->param; + rgp->dev = &client->parent; + rgp->supply_name = freg->param.name; + + freg->param.enable_delay = 400; + + client->parent.user_data = freg; + + /* + * U-Boot/loader already enables always-on rails (e.g. vdd_cpu). + * Skip boot-time enable/ramp I2C in rt_regulator_register() to avoid + * blocking early boot on a shared PMU I2C bus. + */ + hw_always_on = freg->param.always_on; + ramp_delay = freg->param.ramp_delay; + if (hw_always_on) + { + freg->param.boot_on = RT_FALSE; + freg->param.always_on = RT_FALSE; + freg->param.ramp_delay = 0; + freg->param.ramp_disable = RT_TRUE; + } + + if ((err = rt_regulator_register(rgp))) + { + goto _fail; + } + + if (hw_always_on) + { + rt_atomic_store(&rgp->enabled_count, 1); + } + + if (ramp_delay) + { + err = fan53555_regulator_set_ramp_delay(rgp, ramp_delay); + if (err) + { + LOG_W("%s: set ramp %u us failed: %s", client->name, + ramp_delay, rt_strerror(err)); + } + } + + return RT_EOK; + +_fail: + rt_free(freg); + + return err; +} + +static rt_err_t fan53555_regulator_shutdown(struct rt_i2c_client *client) +{ + rt_err_t err = RT_EOK; + struct fan53555_regulator *freg = client->parent.user_data; + + switch (freg->vendor) + { + case FAN53555_VENDOR_FAIRCHILD: + case FAN53555_VENDOR_SILERGY: + err = fan53555_regulator_update_bits(freg, freg->slew_reg, CTL_RESET, CTL_RESET); + break; + + case FAN53526_VENDOR_TCS: + err = fan53555_regulator_update_bits(freg, TCS4525_LIMCONF, CTL_RESET, CTL_RESET); + break; + + default: + break; + } + + if (err < 0) + { + LOG_E("Shutdown error = %s", rt_strerror(err)); + } + + return RT_EOK; +} + +static const struct rt_i2c_device_id fan53555_regulator_ids[] = +{ + { .name = "fan53526", .data = (void *)FAN53526_VENDOR_FAIRCHILD }, + { .name = "fan53555", .data = (void *)FAN53555_VENDOR_FAIRCHILD }, + { .name = "rk8600", .data = (void *)RK8600_VENDOR_ROCKCHIP }, + { .name = "rk8601", .data = (void *)RK8600_VENDOR_ROCKCHIP }, + { .name = "rk8602", .data = (void *)RK8602_VENDOR_ROCKCHIP }, + { .name = "rk8603", .data = (void *)RK8602_VENDOR_ROCKCHIP }, + { .name = "syr827", .data = (void *)FAN53555_VENDOR_SILERGY }, + { .name = "syr828", .data = (void *)FAN53555_VENDOR_SILERGY }, + { .name = "tcs4525", .data = (void *)FAN53526_VENDOR_TCS }, + { .name = "tcs4526", .data = (void *)FAN53526_VENDOR_TCS }, + { .name = "tcs452x", .data = (void *)FAN53526_VENDOR_TCS }, + { /* sentinel */ }, +}; + +static const struct rt_ofw_node_id fan53555_regulator_ofw_ids[] = +{ + { .compatible = "fcs,fan53526", .data = (void *)FAN53526_VENDOR_FAIRCHILD }, + { .compatible = "fcs,fan53555", .data = (void *)FAN53555_VENDOR_FAIRCHILD }, + { .compatible = "rockchip,rk8600", .data = (void *)RK8600_VENDOR_ROCKCHIP }, + { .compatible = "rockchip,rk8601", .data = (void *)RK8600_VENDOR_ROCKCHIP }, + { .compatible = "rockchip,rk8602", .data = (void *)RK8602_VENDOR_ROCKCHIP }, + { .compatible = "rockchip,rk8603", .data = (void *)RK8602_VENDOR_ROCKCHIP }, + { .compatible = "silergy,syr827", .data = (void *)FAN53555_VENDOR_SILERGY }, + { .compatible = "silergy,syr828", .data = (void *)FAN53555_VENDOR_SILERGY }, + { .compatible = "tcs,tcs4525", .data = (void *)FAN53526_VENDOR_TCS }, + { .compatible = "tcs,tcs4526", .data = (void *)FAN53526_VENDOR_TCS }, + { .compatible = "tcs,tcs452x", .data = (void *)FAN53526_VENDOR_TCS }, + { /* sentinel */ }, +}; + +static struct rt_i2c_driver fan53555_regulator_driver = +{ + .ids = fan53555_regulator_ids, + .ofw_ids = fan53555_regulator_ofw_ids, + + .probe = fan53555_regulator_probe, + .shutdown = fan53555_regulator_shutdown, +}; +RT_I2C_DRIVER_EXPORT(fan53555_regulator_driver);