Linux Power Management development
 help / color / mirror / Atom feed
* [PATCH] cpufreq: acpi-cpufreq: Add better support for amd zen
@ 2026-05-11  8:14 yoy95104
  0 siblings, 0 replies; only message in thread
From: yoy95104 @ 2026-05-11  8:14 UTC (permalink / raw)
  To: rafael, viresh.kumar; +Cc: linux-pm, yoy95104

On certain Amd Zen Laptops, Collaborative Processor
Performance Control (CPPC) is disabled by the OEM.
This forces to use of acpi-cpufreq, which uses a limited number of
fixed ACPI P-states. This can lead to worse frequency scaling
and higher temperatures during light tasks.

I aim to fix this by using a virtual P-state table.
By using direct MSR writes to MSR_AMD_PSTATE_DEF0, the driver can
provide better frequency granularity without the need of
the ACPI tables. This allows governors to scale frequencies more
precisely.

Signed-off-by: yoy95104 <yahia.a.abdrabou@gmail.com>
---
 drivers/cpufreq/acpi-cpufreq.c | 136 +++++++++++++++++++++++++--------
 1 file changed, 104 insertions(+), 32 deletions(-)

diff --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c
index 21639d9ac753..a488a2e2f914 100644
--- a/drivers/cpufreq/acpi-cpufreq.c
+++ b/drivers/cpufreq/acpi-cpufreq.c
@@ -50,6 +50,13 @@ enum {
 #define AMD_MSR_RANGE		(0x7)
 #define HYGON_MSR_RANGE		(0x7)
 
+#define MSR_AMD_PSTATE_DEF0 0xc0010064
+
+#ifndef rdmsrl_safe_on_cpu
+#define rdmsrl_safe_on_cpu(cpu, reg, val) rdmsrq_safe_on_cpu(cpu, reg, val)
+#define wrmsrl_safe_on_cpu(cpu, reg, val) wrmsrq_safe_on_cpu(cpu, reg, val)
+#endif
+
 struct acpi_cpufreq_data {
 	unsigned int resume;
 	unsigned int cpu_feature;
@@ -57,6 +64,8 @@ struct acpi_cpufreq_data {
 	cpumask_var_t freqdomain_cpus;
 	void (*cpu_freq_write)(struct acpi_pct_register *reg, u32 val);
 	u32 (*cpu_freq_read)(struct acpi_pct_register *reg);
+	int is_amd_zen;
+	int on_ac;
 };
 
 /* acpi_perf_data is a pointer to percpu data. */
@@ -454,35 +463,50 @@ static unsigned int acpi_cpufreq_fast_switch(struct cpufreq_policy *policy,
 					     unsigned int target_freq)
 {
 	struct acpi_cpufreq_data *data = policy->driver_data;
-	struct acpi_processor_performance *perf;
-	struct cpufreq_frequency_table *entry;
-	unsigned int next_perf_state, next_freq, index;
-
-	/*
-	 * Find the closest frequency above target_freq.
-	 */
-	if (policy->cached_target_freq == target_freq)
-		index = policy->cached_resolved_idx;
-	else
-		index = cpufreq_table_find_index_dl(policy, target_freq,
-						    false);
+	if (!data->is_amd_zen) {
+		struct acpi_processor_performance *perf;
+		struct cpufreq_frequency_table *entry;
+		unsigned int next_perf_state, next_freq, index;
 
-	entry = &policy->freq_table[index];
-	next_freq = entry->frequency;
-	next_perf_state = entry->driver_data;
-
-	perf = to_perf_data(data);
-	if (perf->state == next_perf_state) {
-		if (unlikely(data->resume))
-			data->resume = 0;
+		/*
+		 * Find the closest frequency above target_freq.
+		 */
+		if (policy->cached_target_freq == target_freq)
+			index = policy->cached_resolved_idx;
 		else
-			return next_freq;
-	}
+			index = cpufreq_table_find_index_dl(policy, target_freq,
+							    false);
+
+		entry = &policy->freq_table[index];
+		next_freq = entry->frequency;
+		next_perf_state = entry->driver_data;
+
+		perf = to_perf_data(data);
+		if (perf->state == next_perf_state) {
+			if (unlikely(data->resume))
+				data->resume = 0;
+			else
+				return next_freq;
+		}
 
-	data->cpu_freq_write(&perf->control_register,
-			     perf->states[next_perf_state].control);
-	perf->state = next_perf_state;
-	return next_freq;
+		data->cpu_freq_write(&perf->control_register,
+				     perf->states[next_perf_state].control);
+		perf->state = next_perf_state;
+		return next_freq;
+	} else {
+		u64 val;
+		u8 fid = (u8)((target_freq / 1000 * 8) / 200);
+
+		if (target_freq > policy->cpuinfo.max_freq)
+			target_freq = policy->cpuinfo.max_freq * 7 / 10;
+
+		if (rdmsrl_safe_on_cpu(policy->cpu, MSR_AMD_PSTATE_DEF0, &val))
+			return policy->cur;
+		val &= ~0xFFULL;
+		val |= fid;
+		wrmsrl_safe_on_cpu(policy->cpu, MSR_AMD_PSTATE_DEF0, val);
+		return target_freq;
+	}
 }
 
 static unsigned long
@@ -675,14 +699,25 @@ static inline u64 get_max_boost_ratio(unsigned int cpu, u64 *nominal_freq)
 }
 #endif
 
+static unsigned int get_max_freq(unsigned int cpu)
+{
+	u64 cap1;
+	u32 highest_perf;
+
+	if (rdmsrl_safe_on_cpu(cpu, MSR_AMD_CPPC_CAP1, &cap1))
+		return 0;
+	highest_perf = (cap1 >> 24) & 0xFF;
+	return highest_perf * 100 * 1000;
+}
+
 static void acpi_cpufreq_resolve_max_freq(struct cpufreq_policy *policy,
 					  unsigned int pss_max_freq)
 {
 #ifdef CONFIG_ACPI_CPPC_LIB
-	u64 max_speed = cppc_get_dmi_max_khz();
+	u64 max_speed = get_max_freq(policy->cpu);
 	/*
-	 * Use DMI "Max Speed" if it looks plausible: must be
-	 * above _PSS P0 frequency and within 2x of it.
+	  Use DMI "Max Speed" if it looks plausible: must be
+	  above _PSS P0 frequency and within 2x of it.
 	 */
 	if (max_speed > pss_max_freq && max_speed < pss_max_freq * 2) {
 		policy->cpuinfo.max_freq = max_speed;
@@ -698,6 +733,21 @@ static void acpi_cpufreq_resolve_max_freq(struct cpufreq_policy *policy,
 	arch_set_max_freq_ratio(true);
 }
 
+static unsigned int get_current_freq(unsigned int cpu)
+{
+	u64 val;
+	u8 fid, did;
+
+	if (rdmsrl_safe_on_cpu(cpu, MSR_AMD_PSTATE_DEF0, &val))
+		return 0;
+	fid = val & 0xFF;
+	did = (val >> 8) & 0x3F;
+
+	if (!did)
+		return 0;
+	return (200 * fid / did) * 1000;
+}
+
 static int acpi_cpufreq_cpu_init(struct cpufreq_policy *policy)
 {
 	struct cpufreq_frequency_table *freq_table;
@@ -735,7 +785,31 @@ static int acpi_cpufreq_cpu_init(struct cpufreq_policy *policy)
 	perf = per_cpu_ptr(acpi_perf_data, cpu);
 	data->acpi_perf_cpu = cpu;
 	policy->driver_data = data;
-
+	if (c->x86_vendor == X86_VENDOR_AMD && c->x86 >= 0x17 && c->x86_model >= 0x30) {
+		data->is_amd_zen = 1;
+		policy->cur = get_current_freq(cpu);
+		policy->shared_type = CPUFREQ_SHARED_TYPE_NONE;
+		unsigned int max_boost = get_max_freq(cpu);
+		u8 virtual_pstates = 16;
+		unsigned int max_freq = get_max_freq(policy->cpu);
+		unsigned int min_freq = perf->states[perf->state_count - 1].core_frequency * 1000;
+		unsigned int step = (max_freq - min_freq) / (virtual_pstates - 1);
+		freq_table = kcalloc(virtual_pstates + 1, sizeof(*freq_table), GFP_KERNEL);
+		for (int i = 0; i < virtual_pstates; i++) {
+			freq_table[i].driver_data = i;
+			freq_table[i].frequency = max_freq - (i * step);
+		}
+		freq_table[virtual_pstates].frequency = CPUFREQ_TABLE_END;
+		policy->freq_table = freq_table;
+		if (max_boost) {
+			policy->cpuinfo.max_freq = max_boost;
+			policy->max = max_boost;
+		} else {
+			policy->cpuinfo.max_freq = perf->states[0].core_frequency * 1000;
+		}
+	} else {
+		policy->shared_type = CPUFREQ_SHARED_TYPE_HW;
+	}
 	if (cpu_has(c, X86_FEATURE_CONSTANT_TSC))
 		acpi_cpufreq_driver.flags |= CPUFREQ_CONST_LOOPS;
 
@@ -743,8 +817,6 @@ static int acpi_cpufreq_cpu_init(struct cpufreq_policy *policy)
 	if (result)
 		goto err_free_mask;
 
-	policy->shared_type = perf->shared_type;
-
 	/*
 	 * Will let policy->cpus know about dependency only when software
 	 * coordination is required.
-- 
2.54.0


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-05-11  8:15 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11  8:14 [PATCH] cpufreq: acpi-cpufreq: Add better support for amd zen yoy95104

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox