1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/drivers/cpufreq/freq_table.c |
4 | * |
5 | * Copyright (C) 2002 - 2003 Dominik Brodowski |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | |
10 | #include <linux/cpufreq.h> |
11 | #include <linux/module.h> |
12 | |
13 | /********************************************************************* |
14 | * FREQUENCY TABLE HELPERS * |
15 | *********************************************************************/ |
16 | |
17 | bool policy_has_boost_freq(struct cpufreq_policy *policy) |
18 | { |
19 | struct cpufreq_frequency_table *pos, *table = policy->freq_table; |
20 | |
21 | if (!table) |
22 | return false; |
23 | |
24 | cpufreq_for_each_valid_entry(pos, table) |
25 | if (pos->flags & CPUFREQ_BOOST_FREQ) |
26 | return true; |
27 | |
28 | return false; |
29 | } |
30 | EXPORT_SYMBOL_GPL(policy_has_boost_freq); |
31 | |
32 | int cpufreq_frequency_table_cpuinfo(struct cpufreq_policy *policy, |
33 | struct cpufreq_frequency_table *table) |
34 | { |
35 | struct cpufreq_frequency_table *pos; |
36 | unsigned int min_freq = ~0; |
37 | unsigned int max_freq = 0; |
38 | unsigned int freq; |
39 | |
40 | cpufreq_for_each_valid_entry(pos, table) { |
41 | freq = pos->frequency; |
42 | |
43 | if (!cpufreq_boost_enabled() |
44 | && (pos->flags & CPUFREQ_BOOST_FREQ)) |
45 | continue; |
46 | |
47 | pr_debug("table entry %u: %u kHz\n" , (int)(pos - table), freq); |
48 | if (freq < min_freq) |
49 | min_freq = freq; |
50 | if (freq > max_freq) |
51 | max_freq = freq; |
52 | } |
53 | |
54 | policy->min = policy->cpuinfo.min_freq = min_freq; |
55 | policy->max = max_freq; |
56 | /* |
57 | * If the driver has set its own cpuinfo.max_freq above max_freq, leave |
58 | * it as is. |
59 | */ |
60 | if (policy->cpuinfo.max_freq < max_freq) |
61 | policy->max = policy->cpuinfo.max_freq = max_freq; |
62 | |
63 | if (policy->min == ~0) |
64 | return -EINVAL; |
65 | else |
66 | return 0; |
67 | } |
68 | |
69 | int cpufreq_frequency_table_verify(struct cpufreq_policy_data *policy, |
70 | struct cpufreq_frequency_table *table) |
71 | { |
72 | struct cpufreq_frequency_table *pos; |
73 | unsigned int freq, next_larger = ~0; |
74 | bool found = false; |
75 | |
76 | pr_debug("request for verification of policy (%u - %u kHz) for cpu %u\n" , |
77 | policy->min, policy->max, policy->cpu); |
78 | |
79 | cpufreq_verify_within_cpu_limits(policy); |
80 | |
81 | cpufreq_for_each_valid_entry(pos, table) { |
82 | freq = pos->frequency; |
83 | |
84 | if ((freq >= policy->min) && (freq <= policy->max)) { |
85 | found = true; |
86 | break; |
87 | } |
88 | |
89 | if ((next_larger > freq) && (freq > policy->max)) |
90 | next_larger = freq; |
91 | } |
92 | |
93 | if (!found) { |
94 | policy->max = next_larger; |
95 | cpufreq_verify_within_cpu_limits(policy); |
96 | } |
97 | |
98 | pr_debug("verification lead to (%u - %u kHz) for cpu %u\n" , |
99 | policy->min, policy->max, policy->cpu); |
100 | |
101 | return 0; |
102 | } |
103 | EXPORT_SYMBOL_GPL(cpufreq_frequency_table_verify); |
104 | |
105 | /* |
106 | * Generic routine to verify policy & frequency table, requires driver to set |
107 | * policy->freq_table prior to it. |
108 | */ |
109 | int cpufreq_generic_frequency_table_verify(struct cpufreq_policy_data *policy) |
110 | { |
111 | if (!policy->freq_table) |
112 | return -ENODEV; |
113 | |
114 | return cpufreq_frequency_table_verify(policy, policy->freq_table); |
115 | } |
116 | EXPORT_SYMBOL_GPL(cpufreq_generic_frequency_table_verify); |
117 | |
118 | int cpufreq_table_index_unsorted(struct cpufreq_policy *policy, |
119 | unsigned int target_freq, |
120 | unsigned int relation) |
121 | { |
122 | struct cpufreq_frequency_table optimal = { |
123 | .driver_data = ~0, |
124 | .frequency = 0, |
125 | }; |
126 | struct cpufreq_frequency_table suboptimal = { |
127 | .driver_data = ~0, |
128 | .frequency = 0, |
129 | }; |
130 | struct cpufreq_frequency_table *pos; |
131 | struct cpufreq_frequency_table *table = policy->freq_table; |
132 | unsigned int freq, diff, i = 0; |
133 | int index; |
134 | |
135 | pr_debug("request for target %u kHz (relation: %u) for cpu %u\n" , |
136 | target_freq, relation, policy->cpu); |
137 | |
138 | switch (relation) { |
139 | case CPUFREQ_RELATION_H: |
140 | suboptimal.frequency = ~0; |
141 | break; |
142 | case CPUFREQ_RELATION_L: |
143 | case CPUFREQ_RELATION_C: |
144 | optimal.frequency = ~0; |
145 | break; |
146 | } |
147 | |
148 | cpufreq_for_each_valid_entry_idx(pos, table, i) { |
149 | freq = pos->frequency; |
150 | |
151 | if ((freq < policy->min) || (freq > policy->max)) |
152 | continue; |
153 | if (freq == target_freq) { |
154 | optimal.driver_data = i; |
155 | break; |
156 | } |
157 | switch (relation) { |
158 | case CPUFREQ_RELATION_H: |
159 | if (freq < target_freq) { |
160 | if (freq >= optimal.frequency) { |
161 | optimal.frequency = freq; |
162 | optimal.driver_data = i; |
163 | } |
164 | } else { |
165 | if (freq <= suboptimal.frequency) { |
166 | suboptimal.frequency = freq; |
167 | suboptimal.driver_data = i; |
168 | } |
169 | } |
170 | break; |
171 | case CPUFREQ_RELATION_L: |
172 | if (freq > target_freq) { |
173 | if (freq <= optimal.frequency) { |
174 | optimal.frequency = freq; |
175 | optimal.driver_data = i; |
176 | } |
177 | } else { |
178 | if (freq >= suboptimal.frequency) { |
179 | suboptimal.frequency = freq; |
180 | suboptimal.driver_data = i; |
181 | } |
182 | } |
183 | break; |
184 | case CPUFREQ_RELATION_C: |
185 | diff = abs(freq - target_freq); |
186 | if (diff < optimal.frequency || |
187 | (diff == optimal.frequency && |
188 | freq > table[optimal.driver_data].frequency)) { |
189 | optimal.frequency = diff; |
190 | optimal.driver_data = i; |
191 | } |
192 | break; |
193 | } |
194 | } |
195 | if (optimal.driver_data > i) { |
196 | if (suboptimal.driver_data > i) { |
197 | WARN(1, "Invalid frequency table: %d\n" , policy->cpu); |
198 | return 0; |
199 | } |
200 | |
201 | index = suboptimal.driver_data; |
202 | } else |
203 | index = optimal.driver_data; |
204 | |
205 | pr_debug("target index is %u, freq is:%u kHz\n" , index, |
206 | table[index].frequency); |
207 | return index; |
208 | } |
209 | EXPORT_SYMBOL_GPL(cpufreq_table_index_unsorted); |
210 | |
211 | int cpufreq_frequency_table_get_index(struct cpufreq_policy *policy, |
212 | unsigned int freq) |
213 | { |
214 | struct cpufreq_frequency_table *pos, *table = policy->freq_table; |
215 | int idx; |
216 | |
217 | if (unlikely(!table)) { |
218 | pr_debug("%s: Unable to find frequency table\n" , __func__); |
219 | return -ENOENT; |
220 | } |
221 | |
222 | cpufreq_for_each_valid_entry_idx(pos, table, idx) |
223 | if (pos->frequency == freq) |
224 | return idx; |
225 | |
226 | return -EINVAL; |
227 | } |
228 | EXPORT_SYMBOL_GPL(cpufreq_frequency_table_get_index); |
229 | |
230 | /* |
231 | * show_available_freqs - show available frequencies for the specified CPU |
232 | */ |
233 | static ssize_t show_available_freqs(struct cpufreq_policy *policy, char *buf, |
234 | bool show_boost) |
235 | { |
236 | ssize_t count = 0; |
237 | struct cpufreq_frequency_table *pos, *table = policy->freq_table; |
238 | |
239 | if (!table) |
240 | return -ENODEV; |
241 | |
242 | cpufreq_for_each_valid_entry(pos, table) { |
243 | /* |
244 | * show_boost = true and driver_data = BOOST freq |
245 | * display BOOST freqs |
246 | * |
247 | * show_boost = false and driver_data = BOOST freq |
248 | * show_boost = true and driver_data != BOOST freq |
249 | * continue - do not display anything |
250 | * |
251 | * show_boost = false and driver_data != BOOST freq |
252 | * display NON BOOST freqs |
253 | */ |
254 | if (show_boost ^ (pos->flags & CPUFREQ_BOOST_FREQ)) |
255 | continue; |
256 | |
257 | count += sprintf(buf: &buf[count], fmt: "%d " , pos->frequency); |
258 | } |
259 | count += sprintf(buf: &buf[count], fmt: "\n" ); |
260 | |
261 | return count; |
262 | |
263 | } |
264 | |
265 | #define cpufreq_attr_available_freq(_name) \ |
266 | struct freq_attr cpufreq_freq_attr_##_name##_freqs = \ |
267 | __ATTR_RO(_name##_frequencies) |
268 | |
269 | /* |
270 | * scaling_available_frequencies_show - show available normal frequencies for |
271 | * the specified CPU |
272 | */ |
273 | static ssize_t scaling_available_frequencies_show(struct cpufreq_policy *policy, |
274 | char *buf) |
275 | { |
276 | return show_available_freqs(policy, buf, show_boost: false); |
277 | } |
278 | cpufreq_attr_available_freq(scaling_available); |
279 | EXPORT_SYMBOL_GPL(cpufreq_freq_attr_scaling_available_freqs); |
280 | |
281 | /* |
282 | * scaling_boost_frequencies_show - show available boost frequencies for |
283 | * the specified CPU |
284 | */ |
285 | static ssize_t scaling_boost_frequencies_show(struct cpufreq_policy *policy, |
286 | char *buf) |
287 | { |
288 | return show_available_freqs(policy, buf, show_boost: true); |
289 | } |
290 | cpufreq_attr_available_freq(scaling_boost); |
291 | EXPORT_SYMBOL_GPL(cpufreq_freq_attr_scaling_boost_freqs); |
292 | |
293 | struct freq_attr *cpufreq_generic_attr[] = { |
294 | &cpufreq_freq_attr_scaling_available_freqs, |
295 | NULL, |
296 | }; |
297 | EXPORT_SYMBOL_GPL(cpufreq_generic_attr); |
298 | |
299 | static int set_freq_table_sorted(struct cpufreq_policy *policy) |
300 | { |
301 | struct cpufreq_frequency_table *pos, *table = policy->freq_table; |
302 | struct cpufreq_frequency_table *prev = NULL; |
303 | int ascending = 0; |
304 | |
305 | policy->freq_table_sorted = CPUFREQ_TABLE_UNSORTED; |
306 | |
307 | cpufreq_for_each_valid_entry(pos, table) { |
308 | if (!prev) { |
309 | prev = pos; |
310 | continue; |
311 | } |
312 | |
313 | if (pos->frequency == prev->frequency) { |
314 | pr_warn("Duplicate freq-table entries: %u\n" , |
315 | pos->frequency); |
316 | return -EINVAL; |
317 | } |
318 | |
319 | /* Frequency increased from prev to pos */ |
320 | if (pos->frequency > prev->frequency) { |
321 | /* But frequency was decreasing earlier */ |
322 | if (ascending < 0) { |
323 | pr_debug("Freq table is unsorted\n" ); |
324 | return 0; |
325 | } |
326 | |
327 | ascending++; |
328 | } else { |
329 | /* Frequency decreased from prev to pos */ |
330 | |
331 | /* But frequency was increasing earlier */ |
332 | if (ascending > 0) { |
333 | pr_debug("Freq table is unsorted\n" ); |
334 | return 0; |
335 | } |
336 | |
337 | ascending--; |
338 | } |
339 | |
340 | prev = pos; |
341 | } |
342 | |
343 | if (ascending > 0) |
344 | policy->freq_table_sorted = CPUFREQ_TABLE_SORTED_ASCENDING; |
345 | else |
346 | policy->freq_table_sorted = CPUFREQ_TABLE_SORTED_DESCENDING; |
347 | |
348 | pr_debug("Freq table is sorted in %s order\n" , |
349 | ascending > 0 ? "ascending" : "descending" ); |
350 | |
351 | return 0; |
352 | } |
353 | |
354 | int cpufreq_table_validate_and_sort(struct cpufreq_policy *policy) |
355 | { |
356 | int ret; |
357 | |
358 | if (!policy->freq_table) { |
359 | /* Freq table must be passed by drivers with target_index() */ |
360 | if (has_target_index()) |
361 | return -EINVAL; |
362 | |
363 | return 0; |
364 | } |
365 | |
366 | ret = cpufreq_frequency_table_cpuinfo(policy, table: policy->freq_table); |
367 | if (ret) |
368 | return ret; |
369 | |
370 | return set_freq_table_sorted(policy); |
371 | } |
372 | |
373 | MODULE_AUTHOR("Dominik Brodowski <linux@brodo.de>" ); |
374 | MODULE_DESCRIPTION("CPUfreq frequency table helpers" ); |
375 | |