|
|
|
|
@@ -10,14 +10,18 @@
|
|
|
|
|
|
|
|
|
|
#define pr_fmt(fmt) "CPPC Cpufreq:" fmt
|
|
|
|
|
|
|
|
|
|
#include <linux/arch_topology.h>
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
|
#include <linux/delay.h>
|
|
|
|
|
#include <linux/cpu.h>
|
|
|
|
|
#include <linux/cpufreq.h>
|
|
|
|
|
#include <linux/dmi.h>
|
|
|
|
|
#include <linux/irq_work.h>
|
|
|
|
|
#include <linux/kthread.h>
|
|
|
|
|
#include <linux/time.h>
|
|
|
|
|
#include <linux/vmalloc.h>
|
|
|
|
|
#include <uapi/linux/sched/types.h>
|
|
|
|
|
|
|
|
|
|
#include <asm/unaligned.h>
|
|
|
|
|
|
|
|
|
|
@@ -57,6 +61,216 @@ static struct cppc_workaround_oem_info wa_info[] = {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE
|
|
|
|
|
|
|
|
|
|
/* Frequency invariance support */
|
|
|
|
|
struct cppc_freq_invariance {
|
|
|
|
|
int cpu;
|
|
|
|
|
struct irq_work irq_work;
|
|
|
|
|
struct kthread_work work;
|
|
|
|
|
struct cppc_perf_fb_ctrs prev_perf_fb_ctrs;
|
|
|
|
|
struct cppc_cpudata *cpu_data;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static DEFINE_PER_CPU(struct cppc_freq_invariance, cppc_freq_inv);
|
|
|
|
|
static struct kthread_worker *kworker_fie;
|
|
|
|
|
|
|
|
|
|
static struct cpufreq_driver cppc_cpufreq_driver;
|
|
|
|
|
static unsigned int hisi_cppc_cpufreq_get_rate(unsigned int cpu);
|
|
|
|
|
static int cppc_perf_from_fbctrs(struct cppc_cpudata *cpu_data,
|
|
|
|
|
struct cppc_perf_fb_ctrs *fb_ctrs_t0,
|
|
|
|
|
struct cppc_perf_fb_ctrs *fb_ctrs_t1);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* cppc_scale_freq_workfn - CPPC arch_freq_scale updater for frequency invariance
|
|
|
|
|
* @work: The work item.
|
|
|
|
|
*
|
|
|
|
|
* The CPPC driver register itself with the topology core to provide its own
|
|
|
|
|
* implementation (cppc_scale_freq_tick()) of topology_scale_freq_tick() which
|
|
|
|
|
* gets called by the scheduler on every tick.
|
|
|
|
|
*
|
|
|
|
|
* Note that the arch specific counters have higher priority than CPPC counters,
|
|
|
|
|
* if available, though the CPPC driver doesn't need to have any special
|
|
|
|
|
* handling for that.
|
|
|
|
|
*
|
|
|
|
|
* On an invocation of cppc_scale_freq_tick(), we schedule an irq work (since we
|
|
|
|
|
* reach here from hard-irq context), which then schedules a normal work item
|
|
|
|
|
* and cppc_scale_freq_workfn() updates the per_cpu arch_freq_scale variable
|
|
|
|
|
* based on the counter updates since the last tick.
|
|
|
|
|
*/
|
|
|
|
|
static void cppc_scale_freq_workfn(struct kthread_work *work)
|
|
|
|
|
{
|
|
|
|
|
struct cppc_freq_invariance *cppc_fi;
|
|
|
|
|
struct cppc_perf_fb_ctrs fb_ctrs = {0};
|
|
|
|
|
struct cppc_cpudata *cpu_data;
|
|
|
|
|
unsigned long local_freq_scale;
|
|
|
|
|
u64 perf;
|
|
|
|
|
|
|
|
|
|
cppc_fi = container_of(work, struct cppc_freq_invariance, work);
|
|
|
|
|
cpu_data = cppc_fi->cpu_data;
|
|
|
|
|
|
|
|
|
|
if (cppc_get_perf_ctrs(cppc_fi->cpu, &fb_ctrs)) {
|
|
|
|
|
pr_warn("%s: failed to read perf counters\n", __func__);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
perf = cppc_perf_from_fbctrs(cpu_data, &cppc_fi->prev_perf_fb_ctrs,
|
|
|
|
|
&fb_ctrs);
|
|
|
|
|
cppc_fi->prev_perf_fb_ctrs = fb_ctrs;
|
|
|
|
|
|
|
|
|
|
perf <<= SCHED_CAPACITY_SHIFT;
|
|
|
|
|
local_freq_scale = div64_u64(perf, cpu_data->perf_caps.highest_perf);
|
|
|
|
|
|
|
|
|
|
/* This can happen due to counter's overflow */
|
|
|
|
|
if (unlikely(local_freq_scale > 1024))
|
|
|
|
|
local_freq_scale = 1024;
|
|
|
|
|
|
|
|
|
|
per_cpu(arch_freq_scale, cppc_fi->cpu) = local_freq_scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cppc_irq_work(struct irq_work *irq_work)
|
|
|
|
|
{
|
|
|
|
|
struct cppc_freq_invariance *cppc_fi;
|
|
|
|
|
|
|
|
|
|
cppc_fi = container_of(irq_work, struct cppc_freq_invariance, irq_work);
|
|
|
|
|
kthread_queue_work(kworker_fie, &cppc_fi->work);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cppc_scale_freq_tick(void)
|
|
|
|
|
{
|
|
|
|
|
struct cppc_freq_invariance *cppc_fi = &per_cpu(cppc_freq_inv, smp_processor_id());
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* cppc_get_perf_ctrs() can potentially sleep, call that from the right
|
|
|
|
|
* context.
|
|
|
|
|
*/
|
|
|
|
|
irq_work_queue(&cppc_fi->irq_work);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct scale_freq_data cppc_sftd = {
|
|
|
|
|
.source = SCALE_FREQ_SOURCE_CPPC,
|
|
|
|
|
.set_freq_scale = cppc_scale_freq_tick,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy)
|
|
|
|
|
{
|
|
|
|
|
struct cppc_freq_invariance *cppc_fi;
|
|
|
|
|
int cpu, ret;
|
|
|
|
|
|
|
|
|
|
if (cppc_cpufreq_driver.get == hisi_cppc_cpufreq_get_rate)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for_each_cpu(cpu, policy->cpus) {
|
|
|
|
|
cppc_fi = &per_cpu(cppc_freq_inv, cpu);
|
|
|
|
|
cppc_fi->cpu = cpu;
|
|
|
|
|
cppc_fi->cpu_data = policy->driver_data;
|
|
|
|
|
kthread_init_work(&cppc_fi->work, cppc_scale_freq_workfn);
|
|
|
|
|
init_irq_work(&cppc_fi->irq_work, cppc_irq_work);
|
|
|
|
|
|
|
|
|
|
ret = cppc_get_perf_ctrs(cpu, &cppc_fi->prev_perf_fb_ctrs);
|
|
|
|
|
if (ret) {
|
|
|
|
|
pr_warn("%s: failed to read perf counters for cpu:%d: %d\n",
|
|
|
|
|
__func__, cpu, ret);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Don't abort if the CPU was offline while the driver
|
|
|
|
|
* was getting registered.
|
|
|
|
|
*/
|
|
|
|
|
if (cpu_online(cpu))
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Register for freq-invariance */
|
|
|
|
|
topology_set_scale_freq_source(&cppc_sftd, policy->cpus);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We free all the resources on policy's removal and not on CPU removal as the
|
|
|
|
|
* irq-work are per-cpu and the hotplug core takes care of flushing the pending
|
|
|
|
|
* irq-works (hint: smpcfd_dying_cpu()) on CPU hotplug. Even if the kthread-work
|
|
|
|
|
* fires on another CPU after the concerned CPU is removed, it won't harm.
|
|
|
|
|
*
|
|
|
|
|
* We just need to make sure to remove them all on policy->exit().
|
|
|
|
|
*/
|
|
|
|
|
static void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy)
|
|
|
|
|
{
|
|
|
|
|
struct cppc_freq_invariance *cppc_fi;
|
|
|
|
|
int cpu;
|
|
|
|
|
|
|
|
|
|
if (cppc_cpufreq_driver.get == hisi_cppc_cpufreq_get_rate)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* policy->cpus will be empty here, use related_cpus instead */
|
|
|
|
|
topology_clear_scale_freq_source(SCALE_FREQ_SOURCE_CPPC, policy->related_cpus);
|
|
|
|
|
|
|
|
|
|
for_each_cpu(cpu, policy->related_cpus) {
|
|
|
|
|
cppc_fi = &per_cpu(cppc_freq_inv, cpu);
|
|
|
|
|
irq_work_sync(&cppc_fi->irq_work);
|
|
|
|
|
kthread_cancel_work_sync(&cppc_fi->work);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void __init cppc_freq_invariance_init(void)
|
|
|
|
|
{
|
|
|
|
|
struct sched_attr attr = {
|
|
|
|
|
.size = sizeof(struct sched_attr),
|
|
|
|
|
.sched_policy = SCHED_DEADLINE,
|
|
|
|
|
.sched_nice = 0,
|
|
|
|
|
.sched_priority = 0,
|
|
|
|
|
/*
|
|
|
|
|
* Fake (unused) bandwidth; workaround to "fix"
|
|
|
|
|
* priority inheritance.
|
|
|
|
|
*/
|
|
|
|
|
.sched_runtime = 1000000,
|
|
|
|
|
.sched_deadline = 10000000,
|
|
|
|
|
.sched_period = 10000000,
|
|
|
|
|
};
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (cppc_cpufreq_driver.get == hisi_cppc_cpufreq_get_rate)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
kworker_fie = kthread_create_worker(0, "cppc_fie");
|
|
|
|
|
if (IS_ERR(kworker_fie))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ret = sched_setattr_nocheck(kworker_fie->task, &attr);
|
|
|
|
|
if (ret) {
|
|
|
|
|
pr_warn("%s: failed to set SCHED_DEADLINE: %d\n", __func__,
|
|
|
|
|
ret);
|
|
|
|
|
kthread_destroy_worker(kworker_fie);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cppc_freq_invariance_exit(void)
|
|
|
|
|
{
|
|
|
|
|
if (cppc_cpufreq_driver.get == hisi_cppc_cpufreq_get_rate)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
kthread_destroy_worker(kworker_fie);
|
|
|
|
|
kworker_fie = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
static inline void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void cppc_freq_invariance_init(void)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void cppc_freq_invariance_exit(void)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
#endif /* CONFIG_ACPI_CPPC_CPUFREQ_FIE */
|
|
|
|
|
|
|
|
|
|
/* Callback function used to retrieve the max frequency from DMI */
|
|
|
|
|
static void cppc_find_dmi_mhz(const struct dmi_header *dm, void *private)
|
|
|
|
|
{
|
|
|
|
|
@@ -341,6 +555,7 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cppc_cpufreq_cpu_fie_init(policy);
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
@@ -355,6 +570,8 @@ static int cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
|
|
|
|
|
unsigned int cpu = policy->cpu;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
cppc_cpufreq_cpu_fie_exit(policy);
|
|
|
|
|
|
|
|
|
|
cpu_data->perf_ctrls.desired_perf = caps->lowest_perf;
|
|
|
|
|
|
|
|
|
|
ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
|
|
|
|
|
@@ -374,12 +591,12 @@ static inline u64 get_delta(u64 t1, u64 t0)
|
|
|
|
|
return (u32)t1 - (u32)t0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int cppc_get_rate_from_fbctrs(struct cppc_cpudata *cpu_data,
|
|
|
|
|
struct cppc_perf_fb_ctrs *fb_ctrs_t0,
|
|
|
|
|
struct cppc_perf_fb_ctrs *fb_ctrs_t1)
|
|
|
|
|
static int cppc_perf_from_fbctrs(struct cppc_cpudata *cpu_data,
|
|
|
|
|
struct cppc_perf_fb_ctrs *fb_ctrs_t0,
|
|
|
|
|
struct cppc_perf_fb_ctrs *fb_ctrs_t1)
|
|
|
|
|
{
|
|
|
|
|
u64 delta_reference, delta_delivered;
|
|
|
|
|
u64 reference_perf, delivered_perf;
|
|
|
|
|
u64 reference_perf;
|
|
|
|
|
|
|
|
|
|
reference_perf = fb_ctrs_t0->reference_perf;
|
|
|
|
|
|
|
|
|
|
@@ -388,14 +605,11 @@ static int cppc_get_rate_from_fbctrs(struct cppc_cpudata *cpu_data,
|
|
|
|
|
delta_delivered = get_delta(fb_ctrs_t1->delivered,
|
|
|
|
|
fb_ctrs_t0->delivered);
|
|
|
|
|
|
|
|
|
|
/* Check to avoid divide-by zero */
|
|
|
|
|
if (delta_reference || delta_delivered)
|
|
|
|
|
delivered_perf = (reference_perf * delta_delivered) /
|
|
|
|
|
delta_reference;
|
|
|
|
|
else
|
|
|
|
|
delivered_perf = cpu_data->perf_ctrls.desired_perf;
|
|
|
|
|
/* Check to avoid divide-by zero and invalid delivered_perf */
|
|
|
|
|
if (!delta_reference || !delta_delivered)
|
|
|
|
|
return cpu_data->perf_ctrls.desired_perf;
|
|
|
|
|
|
|
|
|
|
return cppc_cpufreq_perf_to_khz(cpu_data, delivered_perf);
|
|
|
|
|
return (reference_perf * delta_delivered) / delta_reference;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static unsigned int cppc_cpufreq_get_rate(unsigned int cpu)
|
|
|
|
|
@@ -403,6 +617,7 @@ static unsigned int cppc_cpufreq_get_rate(unsigned int cpu)
|
|
|
|
|
struct cppc_perf_fb_ctrs fb_ctrs_t0 = {0}, fb_ctrs_t1 = {0};
|
|
|
|
|
struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
|
|
|
|
|
struct cppc_cpudata *cpu_data = policy->driver_data;
|
|
|
|
|
u64 delivered_perf;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
cpufreq_cpu_put(policy);
|
|
|
|
|
@@ -417,7 +632,10 @@ static unsigned int cppc_cpufreq_get_rate(unsigned int cpu)
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
return cppc_get_rate_from_fbctrs(cpu_data, &fb_ctrs_t0, &fb_ctrs_t1);
|
|
|
|
|
delivered_perf = cppc_perf_from_fbctrs(cpu_data, &fb_ctrs_t0,
|
|
|
|
|
&fb_ctrs_t1);
|
|
|
|
|
|
|
|
|
|
return cppc_cpufreq_perf_to_khz(cpu_data, delivered_perf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int cppc_cpufreq_set_boost(struct cpufreq_policy *policy, int state)
|
|
|
|
|
@@ -518,14 +736,21 @@ static void cppc_check_hisi_workaround(void)
|
|
|
|
|
|
|
|
|
|
static int __init cppc_cpufreq_init(void)
|
|
|
|
|
{
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if ((acpi_disabled) || !acpi_cpc_valid())
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&cpu_data_list);
|
|
|
|
|
|
|
|
|
|
cppc_check_hisi_workaround();
|
|
|
|
|
cppc_freq_invariance_init();
|
|
|
|
|
|
|
|
|
|
return cpufreq_register_driver(&cppc_cpufreq_driver);
|
|
|
|
|
ret = cpufreq_register_driver(&cppc_cpufreq_driver);
|
|
|
|
|
if (ret)
|
|
|
|
|
cppc_freq_invariance_exit();
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void free_cpu_data(void)
|
|
|
|
|
@@ -543,6 +768,7 @@ static inline void free_cpu_data(void)
|
|
|
|
|
static void __exit cppc_cpufreq_exit(void)
|
|
|
|
|
{
|
|
|
|
|
cpufreq_unregister_driver(&cppc_cpufreq_driver);
|
|
|
|
|
cppc_freq_invariance_exit();
|
|
|
|
|
|
|
|
|
|
free_cpu_data();
|
|
|
|
|
}
|
|
|
|
|
|