10e8f68d7SDaniel Lezcano // SPDX-License-Identifier: GPL-2.0-only
20e8f68d7SDaniel Lezcano /*
30e8f68d7SDaniel Lezcano * Copyright 2020 Linaro Limited
40e8f68d7SDaniel Lezcano *
50e8f68d7SDaniel Lezcano * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
60e8f68d7SDaniel Lezcano *
70e8f68d7SDaniel Lezcano * The DTPM CPU is based on the energy model. It hooks the CPU in the
80e8f68d7SDaniel Lezcano * DTPM tree which in turns update the power number by propagating the
90e8f68d7SDaniel Lezcano * power number from the CPU energy model information to the parents.
100e8f68d7SDaniel Lezcano *
110e8f68d7SDaniel Lezcano * The association between the power and the performance state, allows
120e8f68d7SDaniel Lezcano * to set the power of the CPU at the OPP granularity.
130e8f68d7SDaniel Lezcano *
140e8f68d7SDaniel Lezcano * The CPU hotplug is supported and the power numbers will be updated
150e8f68d7SDaniel Lezcano * if a CPU is hot plugged / unplugged.
160e8f68d7SDaniel Lezcano */
174570dddaSDaniel Lezcano #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
184570dddaSDaniel Lezcano
190e8f68d7SDaniel Lezcano #include <linux/cpumask.h>
200e8f68d7SDaniel Lezcano #include <linux/cpufreq.h>
210e8f68d7SDaniel Lezcano #include <linux/cpuhotplug.h>
220e8f68d7SDaniel Lezcano #include <linux/dtpm.h>
230e8f68d7SDaniel Lezcano #include <linux/energy_model.h>
2473dbcb6eSDaniel Lezcano #include <linux/of.h>
250e8f68d7SDaniel Lezcano #include <linux/pm_qos.h>
260e8f68d7SDaniel Lezcano #include <linux/slab.h>
270e8f68d7SDaniel Lezcano
280e8f68d7SDaniel Lezcano struct dtpm_cpu {
29d2cdc6adSDaniel Lezcano struct dtpm dtpm;
300e8f68d7SDaniel Lezcano struct freq_qos_request qos_req;
310e8f68d7SDaniel Lezcano int cpu;
320e8f68d7SDaniel Lezcano };
330e8f68d7SDaniel Lezcano
34d2cdc6adSDaniel Lezcano static DEFINE_PER_CPU(struct dtpm_cpu *, dtpm_per_cpu);
35d2cdc6adSDaniel Lezcano
to_dtpm_cpu(struct dtpm * dtpm)36d2cdc6adSDaniel Lezcano static struct dtpm_cpu *to_dtpm_cpu(struct dtpm *dtpm)
37d2cdc6adSDaniel Lezcano {
38d2cdc6adSDaniel Lezcano return container_of(dtpm, struct dtpm_cpu, dtpm);
39d2cdc6adSDaniel Lezcano }
40d2cdc6adSDaniel Lezcano
set_pd_power_limit(struct dtpm * dtpm,u64 power_limit)410e8f68d7SDaniel Lezcano static u64 set_pd_power_limit(struct dtpm *dtpm, u64 power_limit)
420e8f68d7SDaniel Lezcano {
43d2cdc6adSDaniel Lezcano struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
444570dddaSDaniel Lezcano struct em_perf_domain *pd = em_cpu_get(dtpm_cpu->cpu);
450e8f68d7SDaniel Lezcano struct cpumask cpus;
460e8f68d7SDaniel Lezcano unsigned long freq;
470e8f68d7SDaniel Lezcano u64 power;
480e8f68d7SDaniel Lezcano int i, nr_cpus;
490e8f68d7SDaniel Lezcano
500e8f68d7SDaniel Lezcano cpumask_and(&cpus, cpu_online_mask, to_cpumask(pd->cpus));
510e8f68d7SDaniel Lezcano nr_cpus = cpumask_weight(&cpus);
520e8f68d7SDaniel Lezcano
530e8f68d7SDaniel Lezcano for (i = 0; i < pd->nr_perf_states; i++) {
540e8f68d7SDaniel Lezcano
55ae6ccaa6SLukasz Luba power = pd->table[i].power * nr_cpus;
560e8f68d7SDaniel Lezcano
570e8f68d7SDaniel Lezcano if (power > power_limit)
580e8f68d7SDaniel Lezcano break;
590e8f68d7SDaniel Lezcano }
600e8f68d7SDaniel Lezcano
610e8f68d7SDaniel Lezcano freq = pd->table[i - 1].frequency;
620e8f68d7SDaniel Lezcano
630e8f68d7SDaniel Lezcano freq_qos_update_request(&dtpm_cpu->qos_req, freq);
640e8f68d7SDaniel Lezcano
65ae6ccaa6SLukasz Luba power_limit = pd->table[i - 1].power * nr_cpus;
660e8f68d7SDaniel Lezcano
670e8f68d7SDaniel Lezcano return power_limit;
680e8f68d7SDaniel Lezcano }
690e8f68d7SDaniel Lezcano
scale_pd_power_uw(struct cpumask * pd_mask,u64 power)70eb82baceSDaniel Lezcano static u64 scale_pd_power_uw(struct cpumask *pd_mask, u64 power)
71eb82baceSDaniel Lezcano {
72bb447999SDietmar Eggemann unsigned long max, sum_util = 0;
73eb82baceSDaniel Lezcano int cpu;
74eb82baceSDaniel Lezcano
75eb82baceSDaniel Lezcano /*
76eb82baceSDaniel Lezcano * The capacity is the same for all CPUs belonging to
77bb447999SDietmar Eggemann * the same perf domain.
78eb82baceSDaniel Lezcano */
79bb447999SDietmar Eggemann max = arch_scale_cpu_capacity(cpumask_first(pd_mask));
80eb82baceSDaniel Lezcano
81bb447999SDietmar Eggemann for_each_cpu_and(cpu, pd_mask, cpu_online_mask)
82bb447999SDietmar Eggemann sum_util += sched_cpu_util(cpu);
83bb447999SDietmar Eggemann
84bb447999SDietmar Eggemann return (power * ((sum_util << 10) / max)) >> 10;
85eb82baceSDaniel Lezcano }
86eb82baceSDaniel Lezcano
get_pd_power_uw(struct dtpm * dtpm)870e8f68d7SDaniel Lezcano static u64 get_pd_power_uw(struct dtpm *dtpm)
880e8f68d7SDaniel Lezcano {
89d2cdc6adSDaniel Lezcano struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
900e8f68d7SDaniel Lezcano struct em_perf_domain *pd;
91eb82baceSDaniel Lezcano struct cpumask *pd_mask;
920e8f68d7SDaniel Lezcano unsigned long freq;
93eb82baceSDaniel Lezcano int i;
940e8f68d7SDaniel Lezcano
950e8f68d7SDaniel Lezcano pd = em_cpu_get(dtpm_cpu->cpu);
964570dddaSDaniel Lezcano
97eb82baceSDaniel Lezcano pd_mask = em_span_cpus(pd);
98eb82baceSDaniel Lezcano
99eb82baceSDaniel Lezcano freq = cpufreq_quick_get(dtpm_cpu->cpu);
1000e8f68d7SDaniel Lezcano
1010e8f68d7SDaniel Lezcano for (i = 0; i < pd->nr_perf_states; i++) {
1020e8f68d7SDaniel Lezcano
1030e8f68d7SDaniel Lezcano if (pd->table[i].frequency < freq)
1040e8f68d7SDaniel Lezcano continue;
1050e8f68d7SDaniel Lezcano
1060376fd5fSLukasz Luba return scale_pd_power_uw(pd_mask, pd->table[i].power);
1070e8f68d7SDaniel Lezcano }
1080e8f68d7SDaniel Lezcano
1090e8f68d7SDaniel Lezcano return 0;
1100e8f68d7SDaniel Lezcano }
1110e8f68d7SDaniel Lezcano
update_pd_power_uw(struct dtpm * dtpm)1124570dddaSDaniel Lezcano static int update_pd_power_uw(struct dtpm *dtpm)
1134570dddaSDaniel Lezcano {
114d2cdc6adSDaniel Lezcano struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
1154570dddaSDaniel Lezcano struct em_perf_domain *em = em_cpu_get(dtpm_cpu->cpu);
1164570dddaSDaniel Lezcano struct cpumask cpus;
1174570dddaSDaniel Lezcano int nr_cpus;
1184570dddaSDaniel Lezcano
1194570dddaSDaniel Lezcano cpumask_and(&cpus, cpu_online_mask, to_cpumask(em->cpus));
1204570dddaSDaniel Lezcano nr_cpus = cpumask_weight(&cpus);
1214570dddaSDaniel Lezcano
1224570dddaSDaniel Lezcano dtpm->power_min = em->table[0].power;
1234570dddaSDaniel Lezcano dtpm->power_min *= nr_cpus;
1244570dddaSDaniel Lezcano
1254570dddaSDaniel Lezcano dtpm->power_max = em->table[em->nr_perf_states - 1].power;
1264570dddaSDaniel Lezcano dtpm->power_max *= nr_cpus;
1274570dddaSDaniel Lezcano
1284570dddaSDaniel Lezcano return 0;
1294570dddaSDaniel Lezcano }
1304570dddaSDaniel Lezcano
pd_release(struct dtpm * dtpm)1310e8f68d7SDaniel Lezcano static void pd_release(struct dtpm *dtpm)
1320e8f68d7SDaniel Lezcano {
133d2cdc6adSDaniel Lezcano struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
1340aea2e4eSDaniel Lezcano struct cpufreq_policy *policy;
1350e8f68d7SDaniel Lezcano
1360e8f68d7SDaniel Lezcano if (freq_qos_request_active(&dtpm_cpu->qos_req))
1370e8f68d7SDaniel Lezcano freq_qos_remove_request(&dtpm_cpu->qos_req);
1380e8f68d7SDaniel Lezcano
1390aea2e4eSDaniel Lezcano policy = cpufreq_cpu_get(dtpm_cpu->cpu);
1400aea2e4eSDaniel Lezcano if (policy) {
1410aea2e4eSDaniel Lezcano for_each_cpu(dtpm_cpu->cpu, policy->related_cpus)
1420aea2e4eSDaniel Lezcano per_cpu(dtpm_per_cpu, dtpm_cpu->cpu) = NULL;
143aa581b37SLukasz Luba
144aa581b37SLukasz Luba cpufreq_cpu_put(policy);
1450aea2e4eSDaniel Lezcano }
1460aea2e4eSDaniel Lezcano
1470e8f68d7SDaniel Lezcano kfree(dtpm_cpu);
1480e8f68d7SDaniel Lezcano }
1490e8f68d7SDaniel Lezcano
1500e8f68d7SDaniel Lezcano static struct dtpm_ops dtpm_ops = {
1510e8f68d7SDaniel Lezcano .set_power_uw = set_pd_power_limit,
1520e8f68d7SDaniel Lezcano .get_power_uw = get_pd_power_uw,
1534570dddaSDaniel Lezcano .update_power_uw = update_pd_power_uw,
1540e8f68d7SDaniel Lezcano .release = pd_release,
1550e8f68d7SDaniel Lezcano };
1560e8f68d7SDaniel Lezcano
cpuhp_dtpm_cpu_offline(unsigned int cpu)1570e8f68d7SDaniel Lezcano static int cpuhp_dtpm_cpu_offline(unsigned int cpu)
1580e8f68d7SDaniel Lezcano {
159d2cdc6adSDaniel Lezcano struct dtpm_cpu *dtpm_cpu;
1600e8f68d7SDaniel Lezcano
161d2cdc6adSDaniel Lezcano dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
1624d1cd144SDaniel Lezcano if (dtpm_cpu)
1634d1cd144SDaniel Lezcano dtpm_update_power(&dtpm_cpu->dtpm);
1640e8f68d7SDaniel Lezcano
1654d1cd144SDaniel Lezcano return 0;
1660e8f68d7SDaniel Lezcano }
1670e8f68d7SDaniel Lezcano
cpuhp_dtpm_cpu_online(unsigned int cpu)1680e8f68d7SDaniel Lezcano static int cpuhp_dtpm_cpu_online(unsigned int cpu)
1690e8f68d7SDaniel Lezcano {
1700e8f68d7SDaniel Lezcano struct dtpm_cpu *dtpm_cpu;
17173dbcb6eSDaniel Lezcano
17273dbcb6eSDaniel Lezcano dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
17373dbcb6eSDaniel Lezcano if (dtpm_cpu)
17473dbcb6eSDaniel Lezcano return dtpm_update_power(&dtpm_cpu->dtpm);
17573dbcb6eSDaniel Lezcano
17673dbcb6eSDaniel Lezcano return 0;
17773dbcb6eSDaniel Lezcano }
17873dbcb6eSDaniel Lezcano
__dtpm_cpu_setup(int cpu,struct dtpm * parent)17973dbcb6eSDaniel Lezcano static int __dtpm_cpu_setup(int cpu, struct dtpm *parent)
18073dbcb6eSDaniel Lezcano {
18173dbcb6eSDaniel Lezcano struct dtpm_cpu *dtpm_cpu;
1820e8f68d7SDaniel Lezcano struct cpufreq_policy *policy;
1830e8f68d7SDaniel Lezcano struct em_perf_domain *pd;
1840e8f68d7SDaniel Lezcano char name[CPUFREQ_NAME_LEN];
1850e8f68d7SDaniel Lezcano int ret = -ENOMEM;
1860e8f68d7SDaniel Lezcano
18773dbcb6eSDaniel Lezcano dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
18873dbcb6eSDaniel Lezcano if (dtpm_cpu)
18973dbcb6eSDaniel Lezcano return 0;
19073dbcb6eSDaniel Lezcano
1910e8f68d7SDaniel Lezcano policy = cpufreq_cpu_get(cpu);
1920e8f68d7SDaniel Lezcano if (!policy)
1930e8f68d7SDaniel Lezcano return 0;
1940e8f68d7SDaniel Lezcano
1950e8f68d7SDaniel Lezcano pd = em_cpu_get(cpu);
196aa581b37SLukasz Luba if (!pd || em_is_artificial(pd)) {
197aa581b37SLukasz Luba ret = -EINVAL;
198aa581b37SLukasz Luba goto release_policy;
199aa581b37SLukasz Luba }
2000e8f68d7SDaniel Lezcano
20166e713fbSColin Ian King dtpm_cpu = kzalloc(sizeof(*dtpm_cpu), GFP_KERNEL);
202aa581b37SLukasz Luba if (!dtpm_cpu) {
203aa581b37SLukasz Luba ret = -ENOMEM;
204aa581b37SLukasz Luba goto release_policy;
205aa581b37SLukasz Luba }
2060e8f68d7SDaniel Lezcano
207d2cdc6adSDaniel Lezcano dtpm_init(&dtpm_cpu->dtpm, &dtpm_ops);
2080e8f68d7SDaniel Lezcano dtpm_cpu->cpu = cpu;
2090e8f68d7SDaniel Lezcano
2100e8f68d7SDaniel Lezcano for_each_cpu(cpu, policy->related_cpus)
211d2cdc6adSDaniel Lezcano per_cpu(dtpm_per_cpu, cpu) = dtpm_cpu;
2120e8f68d7SDaniel Lezcano
2134570dddaSDaniel Lezcano snprintf(name, sizeof(name), "cpu%d-cpufreq", dtpm_cpu->cpu);
2140e8f68d7SDaniel Lezcano
21573dbcb6eSDaniel Lezcano ret = dtpm_register(name, &dtpm_cpu->dtpm, parent);
2160e8f68d7SDaniel Lezcano if (ret)
2170e8f68d7SDaniel Lezcano goto out_kfree_dtpm_cpu;
2180e8f68d7SDaniel Lezcano
2190e8f68d7SDaniel Lezcano ret = freq_qos_add_request(&policy->constraints,
2200e8f68d7SDaniel Lezcano &dtpm_cpu->qos_req, FREQ_QOS_MAX,
2210e8f68d7SDaniel Lezcano pd->table[pd->nr_perf_states - 1].frequency);
222*3b454d50SDaniel Lezcano if (ret < 0)
2234570dddaSDaniel Lezcano goto out_dtpm_unregister;
2240e8f68d7SDaniel Lezcano
225aa581b37SLukasz Luba cpufreq_cpu_put(policy);
2260e8f68d7SDaniel Lezcano return 0;
2270e8f68d7SDaniel Lezcano
2280e8f68d7SDaniel Lezcano out_dtpm_unregister:
229d2cdc6adSDaniel Lezcano dtpm_unregister(&dtpm_cpu->dtpm);
2300e8f68d7SDaniel Lezcano dtpm_cpu = NULL;
2310e8f68d7SDaniel Lezcano
2320e8f68d7SDaniel Lezcano out_kfree_dtpm_cpu:
2330e8f68d7SDaniel Lezcano for_each_cpu(cpu, policy->related_cpus)
2340e8f68d7SDaniel Lezcano per_cpu(dtpm_per_cpu, cpu) = NULL;
2350e8f68d7SDaniel Lezcano kfree(dtpm_cpu);
2360e8f68d7SDaniel Lezcano
237aa581b37SLukasz Luba release_policy:
238aa581b37SLukasz Luba cpufreq_cpu_put(policy);
2390e8f68d7SDaniel Lezcano return ret;
2400e8f68d7SDaniel Lezcano }
2410e8f68d7SDaniel Lezcano
dtpm_cpu_setup(struct dtpm * dtpm,struct device_node * np)24273dbcb6eSDaniel Lezcano static int dtpm_cpu_setup(struct dtpm *dtpm, struct device_node *np)
24373dbcb6eSDaniel Lezcano {
24473dbcb6eSDaniel Lezcano int cpu;
24573dbcb6eSDaniel Lezcano
24673dbcb6eSDaniel Lezcano cpu = of_cpu_node_to_id(np);
24773dbcb6eSDaniel Lezcano if (cpu < 0)
24873dbcb6eSDaniel Lezcano return 0;
24973dbcb6eSDaniel Lezcano
25073dbcb6eSDaniel Lezcano return __dtpm_cpu_setup(cpu, dtpm);
25173dbcb6eSDaniel Lezcano }
25273dbcb6eSDaniel Lezcano
dtpm_cpu_init(void)25373dbcb6eSDaniel Lezcano static int dtpm_cpu_init(void)
2540e8f68d7SDaniel Lezcano {
2554570dddaSDaniel Lezcano int ret;
2560e8f68d7SDaniel Lezcano
2574570dddaSDaniel Lezcano /*
2584570dddaSDaniel Lezcano * The callbacks at CPU hotplug time are calling
2594570dddaSDaniel Lezcano * dtpm_update_power() which in turns calls update_pd_power().
2604570dddaSDaniel Lezcano *
2614570dddaSDaniel Lezcano * The function update_pd_power() uses the online mask to
2624570dddaSDaniel Lezcano * figure out the power consumption limits.
2634570dddaSDaniel Lezcano *
2644570dddaSDaniel Lezcano * At CPUHP_AP_ONLINE_DYN, the CPU is present in the CPU
2654570dddaSDaniel Lezcano * online mask when the cpuhp_dtpm_cpu_online function is
2664570dddaSDaniel Lezcano * called, but the CPU is still in the online mask for the
2674570dddaSDaniel Lezcano * tear down callback. So the power can not be updated when
2684570dddaSDaniel Lezcano * the CPU is unplugged.
2694570dddaSDaniel Lezcano *
2704570dddaSDaniel Lezcano * At CPUHP_AP_DTPM_CPU_DEAD, the situation is the opposite as
2714570dddaSDaniel Lezcano * above. The CPU online mask is not up to date when the CPU
2724570dddaSDaniel Lezcano * is plugged in.
2734570dddaSDaniel Lezcano *
2744570dddaSDaniel Lezcano * For this reason, we need to call the online and offline
2754570dddaSDaniel Lezcano * callbacks at different moments when the CPU online mask is
2764570dddaSDaniel Lezcano * consistent with the power numbers we want to update.
2774570dddaSDaniel Lezcano */
2784570dddaSDaniel Lezcano ret = cpuhp_setup_state(CPUHP_AP_DTPM_CPU_DEAD, "dtpm_cpu:offline",
2794570dddaSDaniel Lezcano NULL, cpuhp_dtpm_cpu_offline);
2804570dddaSDaniel Lezcano if (ret < 0)
2814570dddaSDaniel Lezcano return ret;
2824570dddaSDaniel Lezcano
2834570dddaSDaniel Lezcano ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "dtpm_cpu:online",
2844570dddaSDaniel Lezcano cpuhp_dtpm_cpu_online, NULL);
2854570dddaSDaniel Lezcano if (ret < 0)
2864570dddaSDaniel Lezcano return ret;
2874570dddaSDaniel Lezcano
2884570dddaSDaniel Lezcano return 0;
2890e8f68d7SDaniel Lezcano }
2907a89d7eaSDaniel Lezcano
dtpm_cpu_exit(void)291bfded2caSDaniel Lezcano static void dtpm_cpu_exit(void)
292bfded2caSDaniel Lezcano {
293bfded2caSDaniel Lezcano cpuhp_remove_state_nocalls(CPUHP_AP_ONLINE_DYN);
294bfded2caSDaniel Lezcano cpuhp_remove_state_nocalls(CPUHP_AP_DTPM_CPU_DEAD);
295bfded2caSDaniel Lezcano }
296bfded2caSDaniel Lezcano
297b9794a82SDaniel Lezcano struct dtpm_subsys_ops dtpm_cpu_ops = {
298b9794a82SDaniel Lezcano .name = KBUILD_MODNAME,
299b9794a82SDaniel Lezcano .init = dtpm_cpu_init,
300bfded2caSDaniel Lezcano .exit = dtpm_cpu_exit,
30173dbcb6eSDaniel Lezcano .setup = dtpm_cpu_setup,
302b9794a82SDaniel Lezcano };
303