diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c
index 80e2e6b32ce3..f49c6152a013 100644
--- a/drivers/acpi/cppc_acpi.c
+++ b/drivers/acpi/cppc_acpi.c
@@ -3471,11 +3471,214 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
}
EXPORT_SYMBOL_GPL(cppc_get_perf);
+enum cppc_perf_update {
+ CPPC_UPDATE_DESIRED = BIT(0),
+ CPPC_UPDATE_MIN = BIT(1),
+ CPPC_UPDATE_MAX = BIT(2),
+};
+
+/* The caller holds pcc_lock for write if either limit is in PCC. */
+static int cppc_read_perf_limits(int cpu, u32 *min_perf, u32 *max_perf)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+ struct cpc_register_resource *min_reg = &cpc_desc->cpc_regs[MIN_PERF];
+ struct cpc_register_resource *max_reg = &cpc_desc->cpc_regs[MAX_PERF];
+ u64 min = 0, max = U32_MAX;
+ int ret;
+
+ if (!cpc_is_readable(min_reg) || !cpc_is_readable(max_reg))
+ return -EOPNOTSUPP;
+
+ /* Firmware may have reset the controls, so read the live limits. */
+ if (CPC_IN_PCC(min_reg) || CPC_IN_PCC(max_reg)) {
+ ret = send_pcc_cmd(per_cpu(cpu_pcc_subspace_idx, cpu), CMD_READ);
+ if (ret)
+ return ret;
+ }
+
+ /* An absent optional limit leaves that side of the interval open. */
+ if (CPC_SUPPORTED(min_reg)) {
+ ret = cpc_read(cpu, min_reg, &min);
+ if (ret)
+ return ret;
+ }
+ if (CPC_SUPPORTED(max_reg)) {
+ ret = cpc_read(cpu, max_reg, &max);
+ if (ret)
+ return ret;
+ }
+ if (min > U32_MAX || max > U32_MAX)
+ return -EFAULT;
+ if (min > max)
+ pr_debug("CPU:%d live MIN_PERF %llu above MAX_PERF %llu\n",
+ cpu, min, max);
+
+ *min_perf = min;
+ *max_perf = max;
+ return 0;
+}
+
+static bool cppc_perf_updates_in_pcc(int cpu, unsigned int updates)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+
+ return ((updates & CPPC_UPDATE_DESIRED) &&
+ CPC_IN_PCC(&cpc_desc->cpc_regs[DESIRED_PERF])) ||
+ ((updates & CPPC_UPDATE_MIN) &&
+ CPC_IN_PCC(&cpc_desc->cpc_regs[MIN_PERF])) ||
+ ((updates & CPPC_UPDATE_MAX) &&
+ CPC_IN_PCC(&cpc_desc->cpc_regs[MAX_PERF]));
+}
+
+/* The caller holds pcc_lock for write if an updated control is in PCC. */
+static int cppc_write_perf_controls(int cpu,
+ struct cppc_perf_ctrls *perf_ctrls,
+ unsigned int updates, bool sync_desired)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+ struct cpc_register_resource *desired_reg =
+ &cpc_desc->cpc_regs[DESIRED_PERF];
+ struct cpc_register_resource *min_reg = &cpc_desc->cpc_regs[MIN_PERF];
+ struct cpc_register_resource *max_reg = &cpc_desc->cpc_regs[MAX_PERF];
+ struct cppc_pcc_data *pcc_ss_data;
+ int pcc_ss_id, ret;
+ bool pcc_update = false;
+
+ /* Direct writes precede the PCC command in each ordered phase. */
+ if ((updates & CPPC_UPDATE_DESIRED) && !CPC_IN_PCC(desired_reg)) {
+ ret = cpc_write(cpu, desired_reg, perf_ctrls->desired_perf);
+ if (ret)
+ return ret;
+ }
+ if ((updates & CPPC_UPDATE_MIN) && !CPC_IN_PCC(min_reg)) {
+ ret = cpc_write(cpu, min_reg, perf_ctrls->min_perf);
+ if (ret)
+ return ret;
+ }
+ if ((updates & CPPC_UPDATE_MAX) && !CPC_IN_PCC(max_reg)) {
+ ret = cpc_write(cpu, max_reg, perf_ctrls->max_perf);
+ if (ret)
+ return ret;
+ }
+
+ /* Keep Desired Performance valid in every intermediate PCC command. */
+ if (((updates & CPPC_UPDATE_DESIRED) || sync_desired) &&
+ CPC_IN_PCC(desired_reg)) {
+ ret = cpc_write(cpu, desired_reg, perf_ctrls->desired_perf);
+ if (ret)
+ return ret;
+ pcc_update = true;
+ }
+ if ((updates & CPPC_UPDATE_MIN) && CPC_IN_PCC(min_reg)) {
+ ret = cpc_write(cpu, min_reg, perf_ctrls->min_perf);
+ if (ret)
+ return ret;
+ pcc_update = true;
+ }
+ if ((updates & CPPC_UPDATE_MAX) && CPC_IN_PCC(max_reg)) {
+ ret = cpc_write(cpu, max_reg, perf_ctrls->max_perf);
+ if (ret)
+ return ret;
+ pcc_update = true;
+ }
+ if (!pcc_update)
+ return 0;
+
+ pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu);
+ pcc_ss_data = pcc_data[pcc_ss_id];
+ WRITE_ONCE(pcc_ss_data->pending_pcc_write_cmd, true);
+ cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt;
+ cpc_desc->write_cmd_status = 0;
+ return send_pcc_cmd(pcc_ss_id, CMD_WRITE);
+}
+
+/*
+ * Move from the live interval to the requested interval without exposing an
+ * invalid control tuple: widen, update Desired Performance, then narrow.
+ */
+static int cppc_write_ordered_perf(int cpu,
+ struct cppc_perf_ctrls *perf_ctrls,
+ bool desired_update, bool min_update,
+ bool max_update)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+ struct cpc_register_resource *desired_reg =
+ &cpc_desc->cpc_regs[DESIRED_PERF];
+ struct cppc_perf_ctrls ctrls = *perf_ctrls;
+ unsigned int widen = 0, narrow = 0;
+ u32 current_min, current_max;
+ u32 target_min, target_max;
+ bool desired_done = false;
+ bool sync_desired;
+ int ret;
+
+ ret = cppc_read_perf_limits(cpu, ¤t_min, ¤t_max);
+ if (ret)
+ return ret;
+
+ target_min = min_update ? perf_ctrls->min_perf : current_min;
+ target_max = max_update ? perf_ctrls->max_perf : current_max;
+
+ /*
+ * Check only the new interval. If the live one is inverted, this
+ * update fixes it.
+ */
+ if (target_min > target_max)
+ return -EINVAL;
+
+ /* Callers can round Desired just outside the new interval. */
+ if (desired_update && ctrls.desired_perf)
+ ctrls.desired_perf = clamp(ctrls.desired_perf, target_min,
+ target_max);
+
+ if (min_update) {
+ if (perf_ctrls->min_perf < current_min)
+ widen |= CPPC_UPDATE_MIN;
+ else
+ narrow |= CPPC_UPDATE_MIN;
+ }
+ if (max_update) {
+ if (perf_ctrls->max_perf > current_max)
+ widen |= CPPC_UPDATE_MAX;
+ else
+ narrow |= CPPC_UPDATE_MAX;
+ }
+
+ if (widen) {
+ sync_desired = desired_update && CPC_IN_PCC(desired_reg) &&
+ cppc_perf_updates_in_pcc(cpu, widen);
+ ret = cppc_write_perf_controls(cpu, &ctrls, widen,
+ sync_desired);
+ if (ret)
+ return ret;
+ desired_done = sync_desired;
+ }
+
+ if (desired_update && !desired_done) {
+ ret = cppc_write_perf_controls(cpu, &ctrls,
+ CPPC_UPDATE_DESIRED, false);
+ if (ret)
+ return ret;
+ }
+
+ if (!narrow)
+ return 0;
+
+ sync_desired = desired_update && CPC_IN_PCC(desired_reg) &&
+ cppc_perf_updates_in_pcc(cpu, narrow);
+ return cppc_write_perf_controls(cpu, &ctrls, narrow, sync_desired);
+}
+
/**
* cppc_set_perf - Set a CPU's performance controls.
* @cpu: CPU for which to set performance controls.
* @perf_ctrls: ptr to cppc_perf_ctrls. See cppc_acpi.h
*
+ * Callers must serialize updates to the same CPU's performance controls.
+ * Limit updates preserve MIN <= MAX, including intermediate states on
+ * directly accessed registers. An error may leave an earlier write applied.
+ * A nonzero Desired is clamped to the new [MIN, MAX] when a limit is updated.
+ *
* Return: 0 for success, -ERRNO otherwise.
*/
int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)@@ -3487,6 +3690,7 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
bool desired_update, min_update, max_update;
bool desired_pcc, min_pcc, max_pcc, pcc_update;
bool pcc_layout, direct_layout, mixed_layout;
+ u32 desired_perf;
int ret = 0;
if (!cpc_desc) {@@ -3502,10 +3706,21 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
(perf_ctrls->min_perf || perf_ctrls->min_perf_valid);
max_update = cpc_is_writable(max_perf_reg) &&
perf_ctrls->max_perf;
+
+ if (min_update && max_update &&
+ perf_ctrls->min_perf > perf_ctrls->max_perf)
+ return -EINVAL;
+
+ desired_perf = perf_ctrls->desired_perf;
+ if (desired_perf && min_update && max_update)
+ desired_perf = clamp(desired_perf, perf_ctrls->min_perf,
+ perf_ctrls->max_perf);
+
desired_pcc = desired_update && CPC_IN_PCC(desired_reg);
min_pcc = min_update && CPC_IN_PCC(min_perf_reg);
max_pcc = max_update && CPC_IN_PCC(max_perf_reg);
pcc_update = desired_pcc || min_pcc || max_pcc;
+
pcc_layout = (cpc_is_writable(desired_reg) && CPC_IN_PCC(desired_reg)) ||
(cpc_is_writable(min_perf_reg) && CPC_IN_PCC(min_perf_reg)) ||
(cpc_is_writable(max_perf_reg) && CPC_IN_PCC(max_perf_reg));
@@ -3531,9 +3746,10 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
* A mixed layout cannot batch fallible direct writes safely: another
* CPU's staged PCC values may no longer match if a direct write fails.
* Serialize the complete mixed transaction and drain an older batch
- * before changing a direct control.
+ * before changing a direct control. A single PCC limit update also
+ * needs the exclusive lock to read and check the unchanged limit.
*/
- if (mixed_layout) {
+ if (mixed_layout || (pcc_update && min_update != max_update)) {
down_write(&pcc_ss_data->pcc_lock);
if (pcc_ss_data->pending_pcc_write_cmd) {
ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE);@@ -3547,50 +3763,14 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
goto out_mixed_unlock;
}
- if (desired_update && !desired_pcc) {
- ret = cpc_write(cpu, desired_reg,
- perf_ctrls->desired_perf);
- if (ret)
- goto out_mixed_unlock;
- }
- if (min_update && !min_pcc) {
- ret = cpc_write(cpu, min_perf_reg,
- perf_ctrls->min_perf);
- if (ret)
- goto out_mixed_unlock;
- }
- if (max_update && !max_pcc) {
- ret = cpc_write(cpu, max_perf_reg,
- perf_ctrls->max_perf);
- if (ret)
- goto out_mixed_unlock;
- }
-
- if (desired_pcc) {
- ret = cpc_write(cpu, desired_reg,
- perf_ctrls->desired_perf);
- if (ret)
- goto out_mixed_unlock;
- }
- if (min_pcc) {
- ret = cpc_write(cpu, min_perf_reg,
- perf_ctrls->min_perf);
- if (ret)
- goto out_mixed_unlock;
- }
- if (max_pcc) {
- ret = cpc_write(cpu, max_perf_reg,
- perf_ctrls->max_perf);
- if (ret)
- goto out_mixed_unlock;
- }
-
- if (pcc_update) {
- WRITE_ONCE(pcc_ss_data->pending_pcc_write_cmd, true);
- cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt;
- cpc_desc->write_cmd_status = 0;
- ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE);
- }
+ if (min_update || max_update)
+ ret = cppc_write_ordered_perf(cpu, perf_ctrls,
+ desired_update,
+ min_update, max_update);
+ else if (desired_update)
+ ret = cppc_write_perf_controls(cpu, perf_ctrls,
+ CPPC_UPDATE_DESIRED,
+ false);
out_mixed_unlock:
up_write(&pcc_ss_data->pcc_lock);@@ -3599,22 +3779,14 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
/* A request without PCC updates has no payload to coordinate. */
if (!pcc_update) {
- if (desired_update) {
- ret = cpc_write(cpu, desired_reg,
- perf_ctrls->desired_perf);
- if (ret)
- return ret;
- }
- if (min_update) {
- ret = cpc_write(cpu, min_perf_reg,
- perf_ctrls->min_perf);
- if (ret)
- return ret;
- }
- if (max_update)
- ret = cpc_write(cpu, max_perf_reg,
- perf_ctrls->max_perf);
- return ret;
+ if (min_update || max_update)
+ return cppc_write_ordered_perf(cpu, perf_ctrls,
+ desired_update,
+ min_update, max_update);
+ if (desired_update)
+ return cpc_write(cpu, desired_reg,
+ perf_ctrls->desired_perf);
+ return 0;
}
down_read(&pcc_ss_data->pcc_lock); /* BEGIN Phase-I */@@ -3632,7 +3804,7 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
* achieve that goal here.
*/
if (desired_pcc) {
- ret = cpc_write(cpu, desired_reg, perf_ctrls->desired_perf);
+ ret = cpc_write(cpu, desired_reg, desired_perf);
if (ret)
goto out_pcc_read_unlock;
}diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 957fa7775260..ded4f935638e 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -321,60 +321,96 @@ static void cppc_cpufreq_get_perf_limits(struct cppc_cpudata *cpu_data,
*max_perf = clamp_t(u32, max, caps->lowest_perf, caps->highest_perf);
}
-static void cppc_cpufreq_update_perf_limits(struct cppc_cpudata *cpu_data,
- struct cpufreq_policy *policy)
-{
- cppc_cpufreq_get_perf_limits(cpu_data, policy,
- &cpu_data->perf_ctrls.min_perf,
- &cpu_data->perf_ctrls.max_perf);
-}
-
static int cppc_cpufreq_set_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
+ struct cppc_perf_ctrls perf_ctrls = {};
unsigned int cpu = policy->cpu;
struct cpufreq_freqs freqs;
+ u32 desired_perf;
int ret = 0;
- cpu_data->perf_ctrls.desired_perf =
- cppc_khz_to_perf(&cpu_data->perf_caps, target_freq);
- cppc_cpufreq_update_perf_limits(cpu_data, policy);
+ desired_perf = cppc_khz_to_perf(&cpu_data->perf_caps, target_freq);
+ cppc_cpufreq_get_perf_limits(cpu_data, policy, &perf_ctrls.min_perf,
+ &perf_ctrls.max_perf);
+ perf_ctrls.min_perf_valid = true;
+
+ /*
+ * Keep the cached Desired within [MIN, MAX]. Rounding can put it just
+ * outside at the ends of the range. The policy limits may also change
+ * after the governor picked target_freq.
+ */
+ perf_ctrls.desired_perf = clamp(desired_perf, perf_ctrls.min_perf,
+ perf_ctrls.max_perf);
freqs.old = policy->cur;
freqs.new = target_freq;
cpufreq_freq_transition_begin(policy, &freqs);
- ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
+ ret = cppc_set_perf(cpu, &perf_ctrls);
cpufreq_freq_transition_end(policy, &freqs, ret != 0);
- if (ret)
+ if (ret) {
+ WRITE_ONCE(cpu_data->perf_limits_valid, false);
pr_debug("Failed to set target on CPU:%d. ret:%d\n",
cpu, ret);
+ return ret;
+ }
- return ret;
+ WRITE_ONCE(cpu_data->perf_ctrls.desired_perf,
+ perf_ctrls.desired_perf);
+ WRITE_ONCE(cpu_data->perf_ctrls.min_perf, perf_ctrls.min_perf);
+ WRITE_ONCE(cpu_data->perf_ctrls.max_perf, perf_ctrls.max_perf);
+ WRITE_ONCE(cpu_data->perf_limits_valid, true);
+
+ return 0;
}
static unsigned int cppc_cpufreq_fast_switch(struct cpufreq_policy *policy,
unsigned int target_freq)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
+ struct cppc_perf_ctrls perf_ctrls = {};
unsigned int cpu = policy->cpu;
- u32 desired_perf;
+ u32 desired_perf, min_perf, max_perf;
+ bool update_limits;
int ret;
desired_perf = cppc_khz_to_perf(&cpu_data->perf_caps, target_freq);
- cpu_data->perf_ctrls.desired_perf = desired_perf;
- cppc_cpufreq_update_perf_limits(cpu_data, policy);
+ cppc_cpufreq_get_perf_limits(cpu_data, policy, &min_perf, &max_perf);
- ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
+ /*
+ * cppc_set_perf() does not check a Desired-only request, and rounding
+ * or a policy limit change can put Desired outside [MIN, MAX].
+ */
+ desired_perf = clamp(desired_perf, min_perf, max_perf);
+ perf_ctrls.desired_perf = desired_perf;
+ update_limits = !READ_ONCE(cpu_data->perf_limits_valid) ||
+ min_perf != READ_ONCE(cpu_data->perf_ctrls.min_perf) ||
+ max_perf != READ_ONCE(cpu_data->perf_ctrls.max_perf);
+ if (update_limits) {
+ perf_ctrls.min_perf = min_perf;
+ perf_ctrls.max_perf = max_perf;
+ perf_ctrls.min_perf_valid = true;
+ }
+
+ ret = cppc_set_perf(cpu, &perf_ctrls);
if (ret) {
+ WRITE_ONCE(cpu_data->perf_limits_valid, false);
pr_debug("Failed to set target on CPU:%d. ret:%d\n",
cpu, ret);
return 0;
}
+ WRITE_ONCE(cpu_data->perf_ctrls.desired_perf, desired_perf);
+ if (update_limits) {
+ WRITE_ONCE(cpu_data->perf_ctrls.min_perf, min_perf);
+ WRITE_ONCE(cpu_data->perf_ctrls.max_perf, max_perf);
+ WRITE_ONCE(cpu_data->perf_limits_valid, true);
+ }
+
return target_freq;
}
@@ -669,6 +705,7 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
unsigned int cpu = policy->cpu;
struct cppc_cpudata *cpu_data;
struct cppc_perf_caps *caps;
+ u32 desired_perf;
int ret;
cpu_data = cppc_cpufreq_get_cpu_data(cpu);
@@ -727,16 +764,39 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
if (caps->highest_perf > caps->nominal_perf)
policy->boost_supported = true;
- /* Set policy->cur to max now. The governors will adjust later. */
- policy->cur = cppc_perf_to_khz(caps, caps->highest_perf);
- cpu_data->perf_ctrls.desired_perf = caps->highest_perf;
+ /*
+ * The request starts from the live limits, which may be unusable.
+ * Firmware may leave them zeroed. An interrupted earlier update may
+ * have left MIN above MAX. cppc_set_perf() would reject either, so
+ * request the full range instead.
+ */
+ if (!cpu_data->perf_ctrls.max_perf ||
+ cpu_data->perf_ctrls.min_perf > cpu_data->perf_ctrls.max_perf) {
+ cpu_data->perf_ctrls.min_perf = caps->lowest_perf;
+ cpu_data->perf_ctrls.min_perf_valid = true;
+ cpu_data->perf_ctrls.max_perf = caps->highest_perf;
+ }
+
+ /*
+ * Keep the initial request within controls left by an earlier driver
+ * instance or firmware. The governor will update the full tuple later.
+ */
+ desired_perf = caps->highest_perf;
+ if (cpu_data->perf_ctrls.max_perf)
+ desired_perf = min(desired_perf, cpu_data->perf_ctrls.max_perf);
+ if (cpu_data->perf_ctrls.min_perf_valid)
+ desired_perf = max(desired_perf, cpu_data->perf_ctrls.min_perf);
+
+ policy->cur = cppc_perf_to_khz(caps, desired_perf);
+ cpu_data->perf_ctrls.desired_perf = desired_perf;
ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
if (ret) {
pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n",
- caps->highest_perf, cpu, ret);
+ desired_perf, cpu, ret);
goto out;
}
+ cpu_data->perf_limits_valid = true;
cppc_cpufreq_cpu_fie_init(policy);
return 0;@@ -756,6 +816,8 @@ static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
cppc_cpufreq_cpu_fie_exit(policy);
cpu_data->perf_ctrls.desired_perf = caps->lowest_perf;
+ cpu_data->perf_ctrls.min_perf = caps->lowest_perf;
+ cpu_data->perf_ctrls.min_perf_valid = true;
ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
if (ret)
@@ -765,6 +827,16 @@ static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
cppc_cpufreq_put_cpu_data(policy);
}
+static int cppc_cpufreq_cpu_resume(struct cpufreq_policy *policy)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+
+ /* The platform may have reset the controls during suspend. */
+ WRITE_ONCE(cpu_data->perf_limits_valid, false);
+
+ return 0;
+}
+
static inline u64 get_delta(u64 t1, u64 t0)
{
if (t1 > t0 || t0 > ~(u32)0)@@ -909,24 +981,34 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy,
cpu_data->perf_ctrls.auto_sel = val;
if (val) {
- u32 old_min_perf = cpu_data->perf_ctrls.min_perf;
- u32 old_max_perf = cpu_data->perf_ctrls.max_perf;
+ struct cppc_perf_ctrls perf_ctrls = {};
/*
* When enabling autonomous selection, program MIN_PERF and
* MAX_PERF from current policy limits so that the platform
* uses the correct performance bounds immediately.
*/
- cppc_cpufreq_update_perf_limits(cpu_data, policy);
-
- ret = cppc_set_perf(policy->cpu, &cpu_data->perf_ctrls);
+ cppc_cpufreq_get_perf_limits(cpu_data, policy,
+ &perf_ctrls.min_perf,
+ &perf_ctrls.max_perf);
+ perf_ctrls.min_perf_valid = true;
+ /* The cached Desired may be outside the new MIN and MAX. */
+ perf_ctrls.desired_perf =
+ clamp(cpu_data->perf_ctrls.desired_perf,
+ perf_ctrls.min_perf, perf_ctrls.max_perf);
+
+ ret = cppc_set_perf(policy->cpu, &perf_ctrls);
if (ret) {
- cpu_data->perf_ctrls.min_perf = old_min_perf;
- cpu_data->perf_ctrls.max_perf = old_max_perf;
+ WRITE_ONCE(cpu_data->perf_limits_valid, false);
cppc_set_auto_sel(policy->cpu, old_auto_sel);
cpu_data->perf_ctrls.auto_sel = old_auto_sel;
return ret;
}
+ WRITE_ONCE(cpu_data->perf_ctrls.desired_perf,
+ perf_ctrls.desired_perf);
+ WRITE_ONCE(cpu_data->perf_ctrls.min_perf, perf_ctrls.min_perf);
+ WRITE_ONCE(cpu_data->perf_ctrls.max_perf, perf_ctrls.max_perf);
+ WRITE_ONCE(cpu_data->perf_limits_valid, true);
}
return count;@@ -1059,6 +1141,7 @@ static struct cpufreq_driver cppc_cpufreq_driver = {
.fast_switch = cppc_cpufreq_fast_switch,
.init = cppc_cpufreq_cpu_init,
.exit = cppc_cpufreq_cpu_exit,
+ .resume = cppc_cpufreq_cpu_resume,
.set_boost = cppc_cpufreq_set_boost,
.attr = cppc_cpufreq_attr,
.name = "cppc_cpufreq",diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h
index 5bcdb0e553b9..4495ee4ab71e 100644
--- a/include/acpi/cppc_acpi.h
+++ b/include/acpi/cppc_acpi.h
@@ -165,6 +165,7 @@ struct cppc_cpudata {
struct cppc_perf_fb_ctrs perf_fb_ctrs;
unsigned int shared_type;
cpumask_var_t shared_cpu_map;
+ bool perf_limits_valid;
};
#ifdef CONFIG_ACPI_CPPC_LIB--
2.34.1