162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * linux/drivers/cpufreq/freq_table.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2002 - 2003 Dominik Brodowski 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/cpufreq.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci/********************************************************************* 1462306a36Sopenharmony_ci * FREQUENCY TABLE HELPERS * 1562306a36Sopenharmony_ci *********************************************************************/ 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cibool policy_has_boost_freq(struct cpufreq_policy *policy) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci struct cpufreq_frequency_table *pos, *table = policy->freq_table; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci if (!table) 2262306a36Sopenharmony_ci return false; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci cpufreq_for_each_valid_entry(pos, table) 2562306a36Sopenharmony_ci if (pos->flags & CPUFREQ_BOOST_FREQ) 2662306a36Sopenharmony_ci return true; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci return false; 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(policy_has_boost_freq); 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ciint cpufreq_frequency_table_cpuinfo(struct cpufreq_policy *policy, 3362306a36Sopenharmony_ci struct cpufreq_frequency_table *table) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci struct cpufreq_frequency_table *pos; 3662306a36Sopenharmony_ci unsigned int min_freq = ~0; 3762306a36Sopenharmony_ci unsigned int max_freq = 0; 3862306a36Sopenharmony_ci unsigned int freq; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci cpufreq_for_each_valid_entry(pos, table) { 4162306a36Sopenharmony_ci freq = pos->frequency; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci if ((!cpufreq_boost_enabled() || !policy->boost_enabled) 4462306a36Sopenharmony_ci && (pos->flags & CPUFREQ_BOOST_FREQ)) 4562306a36Sopenharmony_ci continue; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci pr_debug("table entry %u: %u kHz\n", (int)(pos - table), freq); 4862306a36Sopenharmony_ci if (freq < min_freq) 4962306a36Sopenharmony_ci min_freq = freq; 5062306a36Sopenharmony_ci if (freq > max_freq) 5162306a36Sopenharmony_ci max_freq = freq; 5262306a36Sopenharmony_ci } 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci policy->min = policy->cpuinfo.min_freq = min_freq; 5562306a36Sopenharmony_ci policy->max = max_freq; 5662306a36Sopenharmony_ci /* 5762306a36Sopenharmony_ci * If the driver has set its own cpuinfo.max_freq above max_freq, leave 5862306a36Sopenharmony_ci * it as is. 5962306a36Sopenharmony_ci */ 6062306a36Sopenharmony_ci if (policy->cpuinfo.max_freq < max_freq) 6162306a36Sopenharmony_ci policy->max = policy->cpuinfo.max_freq = max_freq; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (policy->min == ~0) 6462306a36Sopenharmony_ci return -EINVAL; 6562306a36Sopenharmony_ci else 6662306a36Sopenharmony_ci return 0; 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ciint cpufreq_frequency_table_verify(struct cpufreq_policy_data *policy, 7062306a36Sopenharmony_ci struct cpufreq_frequency_table *table) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct cpufreq_frequency_table *pos; 7362306a36Sopenharmony_ci unsigned int freq, next_larger = ~0; 7462306a36Sopenharmony_ci bool found = false; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci pr_debug("request for verification of policy (%u - %u kHz) for cpu %u\n", 7762306a36Sopenharmony_ci policy->min, policy->max, policy->cpu); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci cpufreq_verify_within_cpu_limits(policy); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci cpufreq_for_each_valid_entry(pos, table) { 8262306a36Sopenharmony_ci freq = pos->frequency; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci if ((freq >= policy->min) && (freq <= policy->max)) { 8562306a36Sopenharmony_ci found = true; 8662306a36Sopenharmony_ci break; 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if ((next_larger > freq) && (freq > policy->max)) 9062306a36Sopenharmony_ci next_larger = freq; 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci if (!found) { 9462306a36Sopenharmony_ci policy->max = next_larger; 9562306a36Sopenharmony_ci cpufreq_verify_within_cpu_limits(policy); 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci pr_debug("verification lead to (%u - %u kHz) for cpu %u\n", 9962306a36Sopenharmony_ci policy->min, policy->max, policy->cpu); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci return 0; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cpufreq_frequency_table_verify); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci/* 10662306a36Sopenharmony_ci * Generic routine to verify policy & frequency table, requires driver to set 10762306a36Sopenharmony_ci * policy->freq_table prior to it. 10862306a36Sopenharmony_ci */ 10962306a36Sopenharmony_ciint cpufreq_generic_frequency_table_verify(struct cpufreq_policy_data *policy) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci if (!policy->freq_table) 11262306a36Sopenharmony_ci return -ENODEV; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci return cpufreq_frequency_table_verify(policy, policy->freq_table); 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cpufreq_generic_frequency_table_verify); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ciint cpufreq_table_index_unsorted(struct cpufreq_policy *policy, 11962306a36Sopenharmony_ci unsigned int target_freq, 12062306a36Sopenharmony_ci unsigned int relation) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct cpufreq_frequency_table optimal = { 12362306a36Sopenharmony_ci .driver_data = ~0, 12462306a36Sopenharmony_ci .frequency = 0, 12562306a36Sopenharmony_ci }; 12662306a36Sopenharmony_ci struct cpufreq_frequency_table suboptimal = { 12762306a36Sopenharmony_ci .driver_data = ~0, 12862306a36Sopenharmony_ci .frequency = 0, 12962306a36Sopenharmony_ci }; 13062306a36Sopenharmony_ci struct cpufreq_frequency_table *pos; 13162306a36Sopenharmony_ci struct cpufreq_frequency_table *table = policy->freq_table; 13262306a36Sopenharmony_ci unsigned int freq, diff, i = 0; 13362306a36Sopenharmony_ci int index; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci pr_debug("request for target %u kHz (relation: %u) for cpu %u\n", 13662306a36Sopenharmony_ci target_freq, relation, policy->cpu); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci switch (relation) { 13962306a36Sopenharmony_ci case CPUFREQ_RELATION_H: 14062306a36Sopenharmony_ci suboptimal.frequency = ~0; 14162306a36Sopenharmony_ci break; 14262306a36Sopenharmony_ci case CPUFREQ_RELATION_L: 14362306a36Sopenharmony_ci case CPUFREQ_RELATION_C: 14462306a36Sopenharmony_ci optimal.frequency = ~0; 14562306a36Sopenharmony_ci break; 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci cpufreq_for_each_valid_entry_idx(pos, table, i) { 14962306a36Sopenharmony_ci freq = pos->frequency; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if ((freq < policy->min) || (freq > policy->max)) 15262306a36Sopenharmony_ci continue; 15362306a36Sopenharmony_ci if (freq == target_freq) { 15462306a36Sopenharmony_ci optimal.driver_data = i; 15562306a36Sopenharmony_ci break; 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci switch (relation) { 15862306a36Sopenharmony_ci case CPUFREQ_RELATION_H: 15962306a36Sopenharmony_ci if (freq < target_freq) { 16062306a36Sopenharmony_ci if (freq >= optimal.frequency) { 16162306a36Sopenharmony_ci optimal.frequency = freq; 16262306a36Sopenharmony_ci optimal.driver_data = i; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci } else { 16562306a36Sopenharmony_ci if (freq <= suboptimal.frequency) { 16662306a36Sopenharmony_ci suboptimal.frequency = freq; 16762306a36Sopenharmony_ci suboptimal.driver_data = i; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci break; 17162306a36Sopenharmony_ci case CPUFREQ_RELATION_L: 17262306a36Sopenharmony_ci if (freq > target_freq) { 17362306a36Sopenharmony_ci if (freq <= optimal.frequency) { 17462306a36Sopenharmony_ci optimal.frequency = freq; 17562306a36Sopenharmony_ci optimal.driver_data = i; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci } else { 17862306a36Sopenharmony_ci if (freq >= suboptimal.frequency) { 17962306a36Sopenharmony_ci suboptimal.frequency = freq; 18062306a36Sopenharmony_ci suboptimal.driver_data = i; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci break; 18462306a36Sopenharmony_ci case CPUFREQ_RELATION_C: 18562306a36Sopenharmony_ci diff = abs(freq - target_freq); 18662306a36Sopenharmony_ci if (diff < optimal.frequency || 18762306a36Sopenharmony_ci (diff == optimal.frequency && 18862306a36Sopenharmony_ci freq > table[optimal.driver_data].frequency)) { 18962306a36Sopenharmony_ci optimal.frequency = diff; 19062306a36Sopenharmony_ci optimal.driver_data = i; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci break; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci if (optimal.driver_data > i) { 19662306a36Sopenharmony_ci if (suboptimal.driver_data > i) { 19762306a36Sopenharmony_ci WARN(1, "Invalid frequency table: %d\n", policy->cpu); 19862306a36Sopenharmony_ci return 0; 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci index = suboptimal.driver_data; 20262306a36Sopenharmony_ci } else 20362306a36Sopenharmony_ci index = optimal.driver_data; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci pr_debug("target index is %u, freq is:%u kHz\n", index, 20662306a36Sopenharmony_ci table[index].frequency); 20762306a36Sopenharmony_ci return index; 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cpufreq_table_index_unsorted); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ciint cpufreq_frequency_table_get_index(struct cpufreq_policy *policy, 21262306a36Sopenharmony_ci unsigned int freq) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci struct cpufreq_frequency_table *pos, *table = policy->freq_table; 21562306a36Sopenharmony_ci int idx; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci if (unlikely(!table)) { 21862306a36Sopenharmony_ci pr_debug("%s: Unable to find frequency table\n", __func__); 21962306a36Sopenharmony_ci return -ENOENT; 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci cpufreq_for_each_valid_entry_idx(pos, table, idx) 22362306a36Sopenharmony_ci if (pos->frequency == freq) 22462306a36Sopenharmony_ci return idx; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci return -EINVAL; 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cpufreq_frequency_table_get_index); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci/* 23162306a36Sopenharmony_ci * show_available_freqs - show available frequencies for the specified CPU 23262306a36Sopenharmony_ci */ 23362306a36Sopenharmony_cistatic ssize_t show_available_freqs(struct cpufreq_policy *policy, char *buf, 23462306a36Sopenharmony_ci bool show_boost) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci ssize_t count = 0; 23762306a36Sopenharmony_ci struct cpufreq_frequency_table *pos, *table = policy->freq_table; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci if (!table) 24062306a36Sopenharmony_ci return -ENODEV; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci cpufreq_for_each_valid_entry(pos, table) { 24362306a36Sopenharmony_ci /* 24462306a36Sopenharmony_ci * show_boost = true and driver_data = BOOST freq 24562306a36Sopenharmony_ci * display BOOST freqs 24662306a36Sopenharmony_ci * 24762306a36Sopenharmony_ci * show_boost = false and driver_data = BOOST freq 24862306a36Sopenharmony_ci * show_boost = true and driver_data != BOOST freq 24962306a36Sopenharmony_ci * continue - do not display anything 25062306a36Sopenharmony_ci * 25162306a36Sopenharmony_ci * show_boost = false and driver_data != BOOST freq 25262306a36Sopenharmony_ci * display NON BOOST freqs 25362306a36Sopenharmony_ci */ 25462306a36Sopenharmony_ci if (show_boost ^ (pos->flags & CPUFREQ_BOOST_FREQ)) 25562306a36Sopenharmony_ci continue; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci count += sprintf(&buf[count], "%d ", pos->frequency); 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci count += sprintf(&buf[count], "\n"); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci return count; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci#define cpufreq_attr_available_freq(_name) \ 26662306a36Sopenharmony_cistruct freq_attr cpufreq_freq_attr_##_name##_freqs = \ 26762306a36Sopenharmony_ci__ATTR_RO(_name##_frequencies) 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci/* 27062306a36Sopenharmony_ci * scaling_available_frequencies_show - show available normal frequencies for 27162306a36Sopenharmony_ci * the specified CPU 27262306a36Sopenharmony_ci */ 27362306a36Sopenharmony_cistatic ssize_t scaling_available_frequencies_show(struct cpufreq_policy *policy, 27462306a36Sopenharmony_ci char *buf) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci return show_available_freqs(policy, buf, false); 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_cicpufreq_attr_available_freq(scaling_available); 27962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cpufreq_freq_attr_scaling_available_freqs); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci/* 28262306a36Sopenharmony_ci * scaling_boost_frequencies_show - show available boost frequencies for 28362306a36Sopenharmony_ci * the specified CPU 28462306a36Sopenharmony_ci */ 28562306a36Sopenharmony_cistatic ssize_t scaling_boost_frequencies_show(struct cpufreq_policy *policy, 28662306a36Sopenharmony_ci char *buf) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci return show_available_freqs(policy, buf, true); 28962306a36Sopenharmony_ci} 29062306a36Sopenharmony_cicpufreq_attr_available_freq(scaling_boost); 29162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cpufreq_freq_attr_scaling_boost_freqs); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_cistruct freq_attr *cpufreq_generic_attr[] = { 29462306a36Sopenharmony_ci &cpufreq_freq_attr_scaling_available_freqs, 29562306a36Sopenharmony_ci NULL, 29662306a36Sopenharmony_ci}; 29762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cpufreq_generic_attr); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_cistatic int set_freq_table_sorted(struct cpufreq_policy *policy) 30062306a36Sopenharmony_ci{ 30162306a36Sopenharmony_ci struct cpufreq_frequency_table *pos, *table = policy->freq_table; 30262306a36Sopenharmony_ci struct cpufreq_frequency_table *prev = NULL; 30362306a36Sopenharmony_ci int ascending = 0; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci policy->freq_table_sorted = CPUFREQ_TABLE_UNSORTED; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci cpufreq_for_each_valid_entry(pos, table) { 30862306a36Sopenharmony_ci if (!prev) { 30962306a36Sopenharmony_ci prev = pos; 31062306a36Sopenharmony_ci continue; 31162306a36Sopenharmony_ci } 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci if (pos->frequency == prev->frequency) { 31462306a36Sopenharmony_ci pr_warn("Duplicate freq-table entries: %u\n", 31562306a36Sopenharmony_ci pos->frequency); 31662306a36Sopenharmony_ci return -EINVAL; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci /* Frequency increased from prev to pos */ 32062306a36Sopenharmony_ci if (pos->frequency > prev->frequency) { 32162306a36Sopenharmony_ci /* But frequency was decreasing earlier */ 32262306a36Sopenharmony_ci if (ascending < 0) { 32362306a36Sopenharmony_ci pr_debug("Freq table is unsorted\n"); 32462306a36Sopenharmony_ci return 0; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci ascending++; 32862306a36Sopenharmony_ci } else { 32962306a36Sopenharmony_ci /* Frequency decreased from prev to pos */ 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci /* But frequency was increasing earlier */ 33262306a36Sopenharmony_ci if (ascending > 0) { 33362306a36Sopenharmony_ci pr_debug("Freq table is unsorted\n"); 33462306a36Sopenharmony_ci return 0; 33562306a36Sopenharmony_ci } 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci ascending--; 33862306a36Sopenharmony_ci } 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci prev = pos; 34162306a36Sopenharmony_ci } 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci if (ascending > 0) 34462306a36Sopenharmony_ci policy->freq_table_sorted = CPUFREQ_TABLE_SORTED_ASCENDING; 34562306a36Sopenharmony_ci else 34662306a36Sopenharmony_ci policy->freq_table_sorted = CPUFREQ_TABLE_SORTED_DESCENDING; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci pr_debug("Freq table is sorted in %s order\n", 34962306a36Sopenharmony_ci ascending > 0 ? "ascending" : "descending"); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci return 0; 35262306a36Sopenharmony_ci} 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ciint cpufreq_table_validate_and_sort(struct cpufreq_policy *policy) 35562306a36Sopenharmony_ci{ 35662306a36Sopenharmony_ci int ret; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci if (!policy->freq_table) { 35962306a36Sopenharmony_ci /* Freq table must be passed by drivers with target_index() */ 36062306a36Sopenharmony_ci if (has_target_index()) 36162306a36Sopenharmony_ci return -EINVAL; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci return 0; 36462306a36Sopenharmony_ci } 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci ret = cpufreq_frequency_table_cpuinfo(policy, policy->freq_table); 36762306a36Sopenharmony_ci if (ret) 36862306a36Sopenharmony_ci return ret; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci return set_freq_table_sorted(policy); 37162306a36Sopenharmony_ci} 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ciMODULE_AUTHOR("Dominik Brodowski <linux@brodo.de>"); 37462306a36Sopenharmony_ciMODULE_DESCRIPTION("CPUfreq frequency table helpers"); 375