| /* |
| * Copyright (c) 2025 Analog Devices, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/init.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/cpu_freq/policy.h> |
| #include <zephyr/cpu_freq/cpu_freq.h> |
| |
| LOG_MODULE_REGISTER(cpu_freq, CONFIG_CPU_FREQ_LOG_LEVEL); |
| |
| #if defined(CONFIG_SMP) && (CONFIG_MP_MAX_NUM_CPUS > 1) |
| |
| static struct k_ipi_work cpu_freq_work; |
| |
| #endif /* CONFIG_SMP && (CONFIG_MP_MAX_NUM_CPUS > 1) */ |
| |
| static void cpu_freq_timer_handler(struct k_timer *timer); |
| K_TIMER_DEFINE(cpu_freq_timer, cpu_freq_timer_handler, NULL); |
| |
| static void cpu_freq_next_pstate(void) |
| { |
| int ret; |
| |
| /* Get next performance state */ |
| const struct pstate *pstate_next; |
| |
| cpu_freq_policy_reset(); |
| |
| ret = cpu_freq_policy_select_pstate(&pstate_next); |
| if (ret) { |
| LOG_ERR("Failed to get pstate: %d", ret); |
| return; |
| } |
| |
| cpu_freq_policy_pstate_set(pstate_next); |
| } |
| |
| #if defined(CONFIG_SMP) && (CONFIG_MP_MAX_NUM_CPUS > 1) |
| static void cpu_freq_ipi_handler(struct k_ipi_work *work) |
| { |
| ARG_UNUSED(work); |
| |
| cpu_freq_next_pstate(); |
| } |
| #endif |
| |
| /* |
| * Timer that expires periodically to execute the selected policy algorithm |
| * and pass the next P-state to the P-state driver. |
| */ |
| static void cpu_freq_timer_handler(struct k_timer *timer) |
| { |
| ARG_UNUSED(timer); |
| |
| #if defined(CONFIG_SMP) && (CONFIG_MP_MAX_NUM_CPUS > 1) |
| uint32_t num_cpus = arch_num_cpus(); |
| uint32_t target_cpus; |
| int ret; |
| |
| __ASSERT(num_cpus <= 32U, "Too many CPUs"); |
| |
| /* |
| * Create a bitmask identifying all the CPUs except the current CPU. |
| * A special case is required for 32 CPUs since shifting a 32-bit |
| * integer left by 32 bits is undefined behavior. |
| */ |
| |
| target_cpus = (num_cpus == 32U) ? 0xFFFFFFFF : (1U << num_cpus) - 1U; |
| target_cpus ^= (1U << _current_cpu->id), |
| |
| ret = k_ipi_work_add(&cpu_freq_work, |
| target_cpus ^ (1U << _current_cpu->id), |
| cpu_freq_ipi_handler); |
| |
| if (ret != 0) { |
| /* |
| * The previous CPU frequency work item has yet to finish |
| * processing. Either one or more of the previously target CPUs |
| * has been too busy to process it, and/or the CPU frequency |
| * policy algorithm is taking too long to complete. Log the |
| * error and try again on the next timer expiration. |
| */ |
| |
| LOG_ERR("Failed to add IPI work: %d", ret); |
| |
| return; |
| } |
| cpu_freq_policy_reset(); |
| |
| k_ipi_work_signal(); |
| #else |
| cpu_freq_policy_reset(); |
| #endif |
| |
| cpu_freq_next_pstate(); |
| } |
| |
| static int cpu_freq_init(void) |
| { |
| #if defined(CONFIG_SMP) && (CONFIG_MP_MAX_NUM_CPUS > 1) |
| k_ipi_work_init(&cpu_freq_work); |
| #endif |
| k_timer_start(&cpu_freq_timer, K_MSEC(CONFIG_CPU_FREQ_INTERVAL_MS), |
| K_MSEC(CONFIG_CPU_FREQ_INTERVAL_MS)); |
| LOG_INF("CPU frequency subsystem initialized with interval %d ms", |
| CONFIG_CPU_FREQ_INTERVAL_MS); |
| return 0; |
| } |
| |
| SYS_INIT(cpu_freq_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |