diff --git a/soc/xtensa/intel_adsp/Kconfig b/soc/xtensa/intel_adsp/Kconfig index 89780fa2d9562e..d61087bc2c3184 100644 --- a/soc/xtensa/intel_adsp/Kconfig +++ b/soc/xtensa/intel_adsp/Kconfig @@ -120,4 +120,11 @@ config ADSP_CPU_DYNAMIC_CLOCK_GATING (wait for interrupt) during idle. Clock gating will be disabled in which there is dependence on dependency to CCOUNT value or DSP clock interrupt. +config ADSP_CPU_DYNAMIC_CLOCK_SWITCHING + bool "DSP clock switching in Idle" + help + When true, FW will switch clock if all cores are in idle state. The dynamic + clock change is intended to reduce energy consumption when the DSP is waiting + for interrupt. + endif # SOC_FAMILY_INTEL_ADSP diff --git a/soc/xtensa/intel_adsp/ace/power.c b/soc/xtensa/intel_adsp/ace/power.c index 57b5e9e9c753ec..ace2b27d802199 100644 --- a/soc/xtensa/intel_adsp/ace/power.c +++ b/soc/xtensa/intel_adsp/ace/power.c @@ -381,9 +381,23 @@ void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id) #ifdef CONFIG_ARCH_CPU_IDLE_CUSTOM +extern void adsp_clock_idle_entry(void); +extern void adsp_clock_idle_exit(void); + void arch_cpu_idle(void) { sys_trace_idle(); +#if CONFIG_ADSP_CPU_DYNAMIC_CLOCK_SWITCHING + /* NOTE: Decreasing a power state lock counter before entering idle. Primary core is + * increasing/decreasing lock counter each time secondary core is powerup/shutdown. + * This way state lock is active for non-idle cores. + */ + pm_policy_state_lock_put(PM_STATE_ACTIVE, 2); + + if (pm_policy_state_lock_is_active(PM_STATE_ACTIVE, 2)) { + adsp_clock_idle_entry(); + } +#endif /* CONFIG_ADSP_CPU_DYNAMIC_CLOCK_SWITCHING */ #if CONFIG_ADSP_CPU_DYNAMIC_CLOCK_GATING /* TODO: Add enum for substates (1 -> clock gating) */ @@ -392,9 +406,14 @@ void arch_cpu_idle(void) } else { DSPCS.bootctl[arch_proc_id()].bctl &= ~DSPBR_BCTL_WAITIPCG; } -#endif +#endif /* CONFIG_ADSP_CPU_DYNAMIC_CLOCK_GATING */ + wait_for_interrupt(0); +#if CONFIG_ADSP_CPU_DYNAMIC_CLOCK_SWITCHING + pm_policy_state_lock_get(PM_STATE_ACTIVE, 2); + adsp_clock_idle_exit(); +#endif /* CONFIG_ADSP_CPU_DYNAMIC_CLOCK_SWITCHING */ } -#endif +#endif /* CONFIG_ARCH_CPU_IDLE_CUSTOM */ #endif diff --git a/soc/xtensa/intel_adsp/common/clk.c b/soc/xtensa/intel_adsp/common/clk.c index 0f3f3d32b80ed5..77aed0bba44d43 100644 --- a/soc/xtensa/intel_adsp/common/clk.c +++ b/soc/xtensa/intel_adsp/common/clk.c @@ -153,3 +153,52 @@ uint32_t adsp_clock_source_frequency(int source) return adsp_clk_src_info[source].frequency; } + +#ifdef CONFIG_ADSP_CPU_DYNAMIC_CLOCK_SWITCHING +/** + * @brief Clock change counter. + * + * A atomic variable that counts how many times the clock has been tried to be lowered. + */ +atomic_t clock_change_count; + +/** + * @brief Clock switch on idle entry. + * + * This function should be called in Idle thread. Intel ADSP can change clock according to + * its needs. But in idle, when core is waiting for interrupt, clock can be change to + * lower frequanze. + * + * @see adsp_clock_idle_exit() + */ +void adsp_clock_idle_entry(void) +{ + /* we are already at the lowest clock, there is no need to do anything */ + if (platform_cpu_clocks[0].current_freq != platform_cpu_clocks[0].lowest_freq) { + select_cpu_clock_hw(platform_cpu_clocks[0].lowest_freq); + (void)atomic_inc(&clock_change_count); + } +} + +/** + * @brief Clock restore on idle exit. + * + * This function must be called when exiting the idle state. During idle entry core could switch + * clock to the lowest frequency and we need to restore previous settings. + * + * @see adsp_clock_idle_entry() + */ +void adsp_clock_idle_exit(void) +{ + /* clock has not been changed */ + if (!atomic_get(&clock_change_count)) + return; + + /* If the DSP should run at a higher clock than the lowest, restore this setting */ + if (platform_cpu_clocks[0].current_freq != platform_cpu_clocks[0].lowest_freq) { + select_cpu_clock_hw(platform_cpu_clocks[0].current_freq); + (void)atomic_clear(&clock_change_count); + } +} + +#endif /* CONFIG_ADSP_CPU_DYNAMIC_CLOCK_SWITCHING */