[PATCH v4 4/4] cpufreq: CPPC: Preserve OSPM-set registers across suspend/resume
Sumit Gupta <[email protected]> Fri, 7 Aug 2026 01:38:57 +0530
| Newsgroups | org.kernel.vger.linux-tegra,dev.linux.lists.acpica-devel,org.kernel.vger.linux-acpi,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm |
|---|---|
| Message-ID | <[email protected]> |
The driver preserves the OSPM-set registers across CPU hotplug, but system
suspend/resume is a separate path. On platforms that reset those registers
or the performance controls across suspend, the values are lost.
Reuse the same save/restore mechanism for suspend/resume:
- suspend() saves the current OSPM-set values, restores the firmware
values and sets a per-policy flag. It runs before devices are
suspended and before the secondary CPUs go offline, while CPPC access
is still safe. It also covers a policy whose CPUs stay online, for
which offline() never runs.
- offline() sees the flag and leaves those register accesses alone,
parking only the performance request.
- resume() needs the same steps as online(). The core has already run
online() for a policy whose CPUs were offlined and brought back.
So, resume() calls it only for a policy that still has the flag set.
- online() clears the flag, so that a later offline() takes a fresh
snapshot.
Suggested-by: Christian Loehle <[email protected]>
Signed-off-by: Sumit Gupta <[email protected]>
---
drivers/cpufreq/cppc_cpufreq.c | 54 ++++++++++++++++++++++++++++++++--
1 file changed, 51 insertions(+), 3 deletions(-)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index f8628b1fc6b7..32f38b0c492b 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -87,6 +87,12 @@ struct cppc_saved_vals {
struct cppc_policy_state {
struct cppc_saved_vals regs[CPPC_NR_SAVED_REGS];
+ /*
+ * Set by suspend() after it saves the OSPM-set values and restores the
+ * firmware ones, so a later offline() does not repeat those accesses.
+ * Cleared at init() and by online().
+ */
+ bool suspend_regs_handled;
};
static DEFINE_PER_CPU(struct cppc_policy_state, cppc_policy_state);
@@ -125,6 +131,9 @@ static void cppc_cpufreq_save_regs(struct cpufreq_policy *policy,
u64 val;
int i;
+ if (saved_type == CPPC_SAVED_FIRMWARE)
+ st->suspend_regs_handled = false;
+
for (i = 0; i < CPPC_NR_SAVED_REGS; i++) {
if (cppc_saved_regs[i].get(cpu, &val))
val = U64_MAX;
@@ -952,9 +961,14 @@ static int cppc_cpufreq_cpu_offline(struct cpufreq_policy *policy)
unsigned int cpu = policy->cpu;
int ret;
- /* Leave the platform in its pre-driver state while offline. */
- cppc_cpufreq_save_regs(policy, CPPC_SAVED_REQUESTED);
- cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_FIRMWARE);
+ /*
+ * Leave the platform in its pre-driver state while offline, unless
+ * suspend() already did so earlier in this suspend cycle.
+ */
+ if (!cppc_cpufreq_policy_state(policy)->suspend_regs_handled) {
+ cppc_cpufreq_save_regs(policy, CPPC_SAVED_REQUESTED);
+ cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_FIRMWARE);
+ }
/*
* Request the lowest desired performance while the policy has no online
@@ -1014,6 +1028,8 @@ static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy)
unsigned int cpu = policy->cpu;
int ret;
+ cppc_cpufreq_policy_state(policy)->suspend_regs_handled = false;
+
cppc_cpufreq_cpu_fie_resync(policy);
ret = cppc_set_enable(cpu, true);
@@ -1050,6 +1066,36 @@ static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy)
return 0;
}
+/*
+ * Save the OSPM-set values and restore the firmware values here, while CPPC
+ * access is still safe. Secondary CPUs go offline much later, with devices
+ * already suspended, so offline() is too late for these accesses and leaves
+ * them alone until resume. Doing it here covers every policy, including one
+ * whose CPUs stay online, for which offline() never runs.
+ */
+static int cppc_cpufreq_cpu_suspend(struct cpufreq_policy *policy)
+{
+ cppc_cpufreq_save_regs(policy, CPPC_SAVED_REQUESTED);
+ cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_FIRMWARE);
+ cppc_cpufreq_policy_state(policy)->suspend_regs_handled = true;
+
+ return 0;
+}
+
+/*
+ * CPUs offlined during suspend come back before the core calls resume(), so
+ * online() has already run for their policies and cleared the flag. It remains
+ * set only for a policy whose CPUs stayed online, and only that policy needs
+ * online() here.
+ */
+static int cppc_cpufreq_cpu_resume(struct cpufreq_policy *policy)
+{
+ if (!cppc_cpufreq_policy_state(policy)->suspend_regs_handled)
+ return 0;
+
+ return cppc_cpufreq_cpu_online(policy);
+}
+
static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
@@ -1364,6 +1410,8 @@ static struct cpufreq_driver cppc_cpufreq_driver = {
.exit = cppc_cpufreq_cpu_exit,
.online = cppc_cpufreq_cpu_online,
.offline = cppc_cpufreq_cpu_offline,
+ .suspend = cppc_cpufreq_cpu_suspend,
+ .resume = cppc_cpufreq_cpu_resume,
.set_boost = cppc_cpufreq_set_boost,
.attr = cppc_cpufreq_attr,
.name = "cppc_cpufreq",
--
2.34.1