[PATCH 1/3] cpufreq: Add a driver frequency resolution callback

From: Christian Loehle

Date: Tue Sep 29 2026 - 06:39:26 EST


Without a frequency table, cpufreq treats the policy range as continuous
even when the driver selects discrete performance levels. Governors can
then issue different kHz requests for the same driver setting.

Add ->resolve_freq() for table-less ->target() drivers to canonicalize
requests within the supplied limits using CPUFREQ_RELATION_{L,H,C}.
Document the callback and verification contracts so callers can safely
cache resolved requests.

Signed-off-by: Christian Loehle <christian.loehle@xxxxxxx>
---
Documentation/admin-guide/pm/cpufreq.rst | 4 ++++
Documentation/cpu-freq/cpu-drivers.rst | 19 +++++++++++++++++++
drivers/cpufreq/cpufreq.c | 17 +++++++++++++++--
include/linux/cpufreq.h | 10 ++++++++++
4 files changed, 48 insertions(+), 2 deletions(-)

diff --git a/Documentation/admin-guide/pm/cpufreq.rst b/Documentation/admin-guide/pm/cpufreq.rst
index 34baf20cc202..e634b87a62a8 100644
--- a/Documentation/admin-guide/pm/cpufreq.rst
+++ b/Documentation/admin-guide/pm/cpufreq.rst
@@ -144,6 +144,10 @@ that belong to the same policy (including both online and offline CPUs). That
mask is then used by the core to populate the policy pointers for all of the
CPUs in it.

+A table-less driver whose discrete frequencies are derived at runtime can
+instead provide a ``->resolve_freq()`` callback to map arbitrary requests to
+deterministic, supported frequencies.
+
The next major initialization step for a new policy object is to attach a
scaling governor to it (to begin with, that is the default scaling governor
determined by the kernel command line or configuration, but it may be changed
diff --git a/Documentation/cpu-freq/cpu-drivers.rst b/Documentation/cpu-freq/cpu-drivers.rst
index 17c69f83691e..4f327760bc04 100644
--- a/Documentation/cpu-freq/cpu-drivers.rst
+++ b/Documentation/cpu-freq/cpu-drivers.rst
@@ -86,6 +86,9 @@ And optionally
.set_boost - A pointer to a per-policy function to enable/disable boost
frequencies.

+ .resolve_freq - A pointer to a frequency-resolution function for table-less
+ drivers with discrete, runtime-derived frequencies. See below.
+

1.2 Per-CPU Initialization
--------------------------
@@ -170,6 +173,22 @@ limits on their own. These shall use the ->setpolicy() callback.
1.5. target/target_index
------------------------

+Table-less ``->target()`` drivers may provide ``->resolve_freq()`` to map a
+clamped target to a supported frequency within the supplied limits.
+``CPUFREQ_RELATION_L`` selects the lowest frequency at or above the target,
+H the highest at or below it, and C the closest, choosing higher on ties.
+If no supported frequency is at or above the target, L returns the highest
+supported frequency in the interval. If none is at or below the target,
+H returns the lowest supported frequency in the interval.
+``CPUFREQ_RELATION_E`` is stripped before the call.
+
+Equivalent requests must resolve to the same frequency, which the target
+callbacks must map to one canonical driver request even if performance levels
+share a kHz value. This lets callers cache resolved requests. ``->verify()``
+must leave a supported frequency in every accepted limit interval.
+The callback must not sleep: it may run in scheduler context.
+Policies providing both a frequency table and this callback are rejected.
+
The target_index call has two arguments: ``struct cpufreq_policy *policy``,
and ``unsigned int`` index (into the exposed frequency table).

diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 54dde8419bdc..44bda2f32fcf 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -480,8 +480,12 @@ static unsigned int __resolve_freq(struct cpufreq_policy *policy,

target_freq = clamp_val(target_freq, min, max);

- if (!policy->freq_table)
+ if (!policy->freq_table) {
+ if (cpufreq_driver->resolve_freq)
+ return cpufreq_driver->resolve_freq(policy, target_freq, min, max,
+ relation & ~CPUFREQ_RELATION_E);
return target_freq;
+ }

idx = cpufreq_frequency_table_target(policy, target_freq, min, max, relation);
policy->cached_resolved_idx = idx;
@@ -495,7 +499,10 @@ static unsigned int __resolve_freq(struct cpufreq_policy *policy,
* @policy: associated policy to interrogate
* @target_freq: target frequency to resolve.
*
- * The target to driver frequency mapping is cached in the policy.
+ * The frequency-table resolution path caches the mapping in the policy.
+ *
+ * Keep the policy active and exclude driver teardown; a policy reference
+ * alone does not protect driver-private data.
*
* Return: Lowest driver-supported frequency greater than or equal to the
* given target_freq, subject to policy (min/max) and driver limitations.
@@ -1445,6 +1452,11 @@ static int cpufreq_policy_online(struct cpufreq_policy *policy,
* If there is a problem with its frequency table, take it
* offline and drop it.
*/
+ if (policy->freq_table && cpufreq_driver->resolve_freq) {
+ ret = -EINVAL;
+ goto out_offline_policy;
+ }
+
ret = cpufreq_table_validate_and_sort(policy);
if (ret)
goto out_offline_policy;
@@ -2925,6 +2937,7 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data)

if (!driver_data || !driver_data->verify || !driver_data->init ||
(driver_data->target_index && driver_data->target) ||
+ (driver_data->resolve_freq && !driver_data->target) ||
(!!driver_data->setpolicy == (driver_data->target_index || driver_data->target)) ||
(!driver_data->get_intermediate != !driver_data->target_intermediate) ||
(!driver_data->online != !driver_data->offline) ||
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
index d3d0d9d02aa4..a0a7619d11fd 100644
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -364,6 +364,16 @@ struct cpufreq_driver {
int (*target)(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation); /* Deprecated */
+ /*
+ * Optional for table-less ->target() drivers. Resolve a clamped request
+ * within the supplied limits using CPUFREQ_RELATION_{L,H,C}. Must not
+ * sleep. See Documentation/cpu-freq/cpu-drivers.rst for the contract.
+ */
+ unsigned int (*resolve_freq)(struct cpufreq_policy *policy,
+ unsigned int target_freq,
+ unsigned int min_freq,
+ unsigned int max_freq,
+ unsigned int relation);
int (*target_index)(struct cpufreq_policy *policy,
unsigned int index);
unsigned int (*fast_switch)(struct cpufreq_policy *policy,