xref: /openbmc/linux/drivers/powercap/dtpm_cpu.c (revision 7a89d7ea)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright 2020 Linaro Limited
4  *
5  * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
6  *
7  * The DTPM CPU is based on the energy model. It hooks the CPU in the
8  * DTPM tree which in turns update the power number by propagating the
9  * power number from the CPU energy model information to the parents.
10  *
11  * The association between the power and the performance state, allows
12  * to set the power of the CPU at the OPP granularity.
13  *
14  * The CPU hotplug is supported and the power numbers will be updated
15  * if a CPU is hot plugged / unplugged.
16  */
17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18 
19 #include <linux/cpumask.h>
20 #include <linux/cpufreq.h>
21 #include <linux/cpuhotplug.h>
22 #include <linux/dtpm.h>
23 #include <linux/energy_model.h>
24 #include <linux/pm_qos.h>
25 #include <linux/slab.h>
26 #include <linux/units.h>
27 
28 static DEFINE_PER_CPU(struct dtpm *, dtpm_per_cpu);
29 
30 struct dtpm_cpu {
31 	struct freq_qos_request qos_req;
32 	int cpu;
33 };
34 
35 static u64 set_pd_power_limit(struct dtpm *dtpm, u64 power_limit)
36 {
37 	struct dtpm_cpu *dtpm_cpu = dtpm->private;
38 	struct em_perf_domain *pd = em_cpu_get(dtpm_cpu->cpu);
39 	struct cpumask cpus;
40 	unsigned long freq;
41 	u64 power;
42 	int i, nr_cpus;
43 
44 	cpumask_and(&cpus, cpu_online_mask, to_cpumask(pd->cpus));
45 	nr_cpus = cpumask_weight(&cpus);
46 
47 	for (i = 0; i < pd->nr_perf_states; i++) {
48 
49 		power = pd->table[i].power * MICROWATT_PER_MILLIWATT * nr_cpus;
50 
51 		if (power > power_limit)
52 			break;
53 	}
54 
55 	freq = pd->table[i - 1].frequency;
56 
57 	freq_qos_update_request(&dtpm_cpu->qos_req, freq);
58 
59 	power_limit = pd->table[i - 1].power *
60 		MICROWATT_PER_MILLIWATT * nr_cpus;
61 
62 	return power_limit;
63 }
64 
65 static u64 get_pd_power_uw(struct dtpm *dtpm)
66 {
67 	struct dtpm_cpu *dtpm_cpu = dtpm->private;
68 	struct em_perf_domain *pd;
69 	struct cpumask cpus;
70 	unsigned long freq;
71 	int i, nr_cpus;
72 
73 	pd = em_cpu_get(dtpm_cpu->cpu);
74 	freq = cpufreq_quick_get(dtpm_cpu->cpu);
75 
76 	cpumask_and(&cpus, cpu_online_mask, to_cpumask(pd->cpus));
77 	nr_cpus = cpumask_weight(&cpus);
78 
79 	for (i = 0; i < pd->nr_perf_states; i++) {
80 
81 		if (pd->table[i].frequency < freq)
82 			continue;
83 
84 		return pd->table[i].power *
85 			MICROWATT_PER_MILLIWATT * nr_cpus;
86 	}
87 
88 	return 0;
89 }
90 
91 static int update_pd_power_uw(struct dtpm *dtpm)
92 {
93 	struct dtpm_cpu *dtpm_cpu = dtpm->private;
94 	struct em_perf_domain *em = em_cpu_get(dtpm_cpu->cpu);
95 	struct cpumask cpus;
96 	int nr_cpus;
97 
98 	cpumask_and(&cpus, cpu_online_mask, to_cpumask(em->cpus));
99 	nr_cpus = cpumask_weight(&cpus);
100 
101 	dtpm->power_min = em->table[0].power;
102 	dtpm->power_min *= MICROWATT_PER_MILLIWATT;
103 	dtpm->power_min *= nr_cpus;
104 
105 	dtpm->power_max = em->table[em->nr_perf_states - 1].power;
106 	dtpm->power_max *= MICROWATT_PER_MILLIWATT;
107 	dtpm->power_max *= nr_cpus;
108 
109 	return 0;
110 }
111 
112 static void pd_release(struct dtpm *dtpm)
113 {
114 	struct dtpm_cpu *dtpm_cpu = dtpm->private;
115 
116 	if (freq_qos_request_active(&dtpm_cpu->qos_req))
117 		freq_qos_remove_request(&dtpm_cpu->qos_req);
118 
119 	kfree(dtpm_cpu);
120 }
121 
122 static struct dtpm_ops dtpm_ops = {
123 	.set_power_uw	 = set_pd_power_limit,
124 	.get_power_uw	 = get_pd_power_uw,
125 	.update_power_uw = update_pd_power_uw,
126 	.release	 = pd_release,
127 };
128 
129 static int cpuhp_dtpm_cpu_offline(unsigned int cpu)
130 {
131 	struct em_perf_domain *pd;
132 	struct dtpm *dtpm;
133 
134 	pd = em_cpu_get(cpu);
135 	if (!pd)
136 		return -EINVAL;
137 
138 	dtpm = per_cpu(dtpm_per_cpu, cpu);
139 
140 	return dtpm_update_power(dtpm);
141 }
142 
143 static int cpuhp_dtpm_cpu_online(unsigned int cpu)
144 {
145 	struct dtpm *dtpm;
146 	struct dtpm_cpu *dtpm_cpu;
147 	struct cpufreq_policy *policy;
148 	struct em_perf_domain *pd;
149 	char name[CPUFREQ_NAME_LEN];
150 	int ret = -ENOMEM;
151 
152 	policy = cpufreq_cpu_get(cpu);
153 	if (!policy)
154 		return 0;
155 
156 	pd = em_cpu_get(cpu);
157 	if (!pd)
158 		return -EINVAL;
159 
160 	dtpm = per_cpu(dtpm_per_cpu, cpu);
161 	if (dtpm)
162 		return dtpm_update_power(dtpm);
163 
164 	dtpm = dtpm_alloc(&dtpm_ops);
165 	if (!dtpm)
166 		return -EINVAL;
167 
168 	dtpm_cpu = kzalloc(sizeof(*dtpm_cpu), GFP_KERNEL);
169 	if (!dtpm_cpu)
170 		goto out_kfree_dtpm;
171 
172 	dtpm->private = dtpm_cpu;
173 	dtpm_cpu->cpu = cpu;
174 
175 	for_each_cpu(cpu, policy->related_cpus)
176 		per_cpu(dtpm_per_cpu, cpu) = dtpm;
177 
178 	snprintf(name, sizeof(name), "cpu%d-cpufreq", dtpm_cpu->cpu);
179 
180 	ret = dtpm_register(name, dtpm, NULL);
181 	if (ret)
182 		goto out_kfree_dtpm_cpu;
183 
184 	ret = freq_qos_add_request(&policy->constraints,
185 				   &dtpm_cpu->qos_req, FREQ_QOS_MAX,
186 				   pd->table[pd->nr_perf_states - 1].frequency);
187 	if (ret)
188 		goto out_dtpm_unregister;
189 
190 	return 0;
191 
192 out_dtpm_unregister:
193 	dtpm_unregister(dtpm);
194 	dtpm_cpu = NULL;
195 	dtpm = NULL;
196 
197 out_kfree_dtpm_cpu:
198 	for_each_cpu(cpu, policy->related_cpus)
199 		per_cpu(dtpm_per_cpu, cpu) = NULL;
200 	kfree(dtpm_cpu);
201 
202 out_kfree_dtpm:
203 	kfree(dtpm);
204 	return ret;
205 }
206 
207 static int __init dtpm_cpu_init(void)
208 {
209 	int ret;
210 
211 	/*
212 	 * The callbacks at CPU hotplug time are calling
213 	 * dtpm_update_power() which in turns calls update_pd_power().
214 	 *
215 	 * The function update_pd_power() uses the online mask to
216 	 * figure out the power consumption limits.
217 	 *
218 	 * At CPUHP_AP_ONLINE_DYN, the CPU is present in the CPU
219 	 * online mask when the cpuhp_dtpm_cpu_online function is
220 	 * called, but the CPU is still in the online mask for the
221 	 * tear down callback. So the power can not be updated when
222 	 * the CPU is unplugged.
223 	 *
224 	 * At CPUHP_AP_DTPM_CPU_DEAD, the situation is the opposite as
225 	 * above. The CPU online mask is not up to date when the CPU
226 	 * is plugged in.
227 	 *
228 	 * For this reason, we need to call the online and offline
229 	 * callbacks at different moments when the CPU online mask is
230 	 * consistent with the power numbers we want to update.
231 	 */
232 	ret = cpuhp_setup_state(CPUHP_AP_DTPM_CPU_DEAD, "dtpm_cpu:offline",
233 				NULL, cpuhp_dtpm_cpu_offline);
234 	if (ret < 0)
235 		return ret;
236 
237 	ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "dtpm_cpu:online",
238 				cpuhp_dtpm_cpu_online, NULL);
239 	if (ret < 0)
240 		return ret;
241 
242 	return 0;
243 }
244 
245 DTPM_DECLARE(dtpm_cpu, dtpm_cpu_init);
246