18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2013 Freescale Semiconductor, Inc. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * CPU Frequency Scaling driver for Freescale QorIQ SoCs. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/clk.h> 118c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 128c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 138c2ecf20Sopenharmony_ci#include <linux/errno.h> 148c2ecf20Sopenharmony_ci#include <linux/init.h> 158c2ecf20Sopenharmony_ci#include <linux/kernel.h> 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/mutex.h> 188c2ecf20Sopenharmony_ci#include <linux/of.h> 198c2ecf20Sopenharmony_ci#include <linux/slab.h> 208c2ecf20Sopenharmony_ci#include <linux/smp.h> 218c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/** 248c2ecf20Sopenharmony_ci * struct cpu_data 258c2ecf20Sopenharmony_ci * @pclk: the parent clock of cpu 268c2ecf20Sopenharmony_ci * @table: frequency table 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_cistruct cpu_data { 298c2ecf20Sopenharmony_ci struct clk **pclk; 308c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *table; 318c2ecf20Sopenharmony_ci}; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci/** 348c2ecf20Sopenharmony_ci * struct soc_data - SoC specific data 358c2ecf20Sopenharmony_ci * @flags: SOC_xxx 368c2ecf20Sopenharmony_ci */ 378c2ecf20Sopenharmony_cistruct soc_data { 388c2ecf20Sopenharmony_ci u32 flags; 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic u32 get_bus_freq(void) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci struct device_node *soc; 448c2ecf20Sopenharmony_ci u32 sysfreq; 458c2ecf20Sopenharmony_ci struct clk *pltclk; 468c2ecf20Sopenharmony_ci int ret; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci /* get platform freq by searching bus-frequency property */ 498c2ecf20Sopenharmony_ci soc = of_find_node_by_type(NULL, "soc"); 508c2ecf20Sopenharmony_ci if (soc) { 518c2ecf20Sopenharmony_ci ret = of_property_read_u32(soc, "bus-frequency", &sysfreq); 528c2ecf20Sopenharmony_ci of_node_put(soc); 538c2ecf20Sopenharmony_ci if (!ret) 548c2ecf20Sopenharmony_ci return sysfreq; 558c2ecf20Sopenharmony_ci } 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci /* get platform freq by its clock name */ 588c2ecf20Sopenharmony_ci pltclk = clk_get(NULL, "cg-pll0-div1"); 598c2ecf20Sopenharmony_ci if (IS_ERR(pltclk)) { 608c2ecf20Sopenharmony_ci pr_err("%s: can't get bus frequency %ld\n", 618c2ecf20Sopenharmony_ci __func__, PTR_ERR(pltclk)); 628c2ecf20Sopenharmony_ci return PTR_ERR(pltclk); 638c2ecf20Sopenharmony_ci } 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci return clk_get_rate(pltclk); 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic struct clk *cpu_to_clk(int cpu) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci struct device_node *np; 718c2ecf20Sopenharmony_ci struct clk *clk; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci if (!cpu_present(cpu)) 748c2ecf20Sopenharmony_ci return NULL; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci np = of_get_cpu_node(cpu, NULL); 778c2ecf20Sopenharmony_ci if (!np) 788c2ecf20Sopenharmony_ci return NULL; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci clk = of_clk_get(np, 0); 818c2ecf20Sopenharmony_ci of_node_put(np); 828c2ecf20Sopenharmony_ci return clk; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci/* traverse cpu nodes to get cpu mask of sharing clock wire */ 868c2ecf20Sopenharmony_cistatic void set_affected_cpus(struct cpufreq_policy *policy) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci struct cpumask *dstp = policy->cpus; 898c2ecf20Sopenharmony_ci struct clk *clk; 908c2ecf20Sopenharmony_ci int i; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci for_each_present_cpu(i) { 938c2ecf20Sopenharmony_ci clk = cpu_to_clk(i); 948c2ecf20Sopenharmony_ci if (IS_ERR(clk)) { 958c2ecf20Sopenharmony_ci pr_err("%s: no clock for cpu %d\n", __func__, i); 968c2ecf20Sopenharmony_ci continue; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci if (clk_is_match(policy->clk, clk)) 1008c2ecf20Sopenharmony_ci cpumask_set_cpu(i, dstp); 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci/* reduce the duplicated frequencies in frequency table */ 1058c2ecf20Sopenharmony_cistatic void freq_table_redup(struct cpufreq_frequency_table *freq_table, 1068c2ecf20Sopenharmony_ci int count) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci int i, j; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci for (i = 1; i < count; i++) { 1118c2ecf20Sopenharmony_ci for (j = 0; j < i; j++) { 1128c2ecf20Sopenharmony_ci if (freq_table[j].frequency == CPUFREQ_ENTRY_INVALID || 1138c2ecf20Sopenharmony_ci freq_table[j].frequency != 1148c2ecf20Sopenharmony_ci freq_table[i].frequency) 1158c2ecf20Sopenharmony_ci continue; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci freq_table[i].frequency = CPUFREQ_ENTRY_INVALID; 1188c2ecf20Sopenharmony_ci break; 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci/* sort the frequencies in frequency table in descenting order */ 1248c2ecf20Sopenharmony_cistatic void freq_table_sort(struct cpufreq_frequency_table *freq_table, 1258c2ecf20Sopenharmony_ci int count) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci int i, j, ind; 1288c2ecf20Sopenharmony_ci unsigned int freq, max_freq; 1298c2ecf20Sopenharmony_ci struct cpufreq_frequency_table table; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci for (i = 0; i < count - 1; i++) { 1328c2ecf20Sopenharmony_ci max_freq = freq_table[i].frequency; 1338c2ecf20Sopenharmony_ci ind = i; 1348c2ecf20Sopenharmony_ci for (j = i + 1; j < count; j++) { 1358c2ecf20Sopenharmony_ci freq = freq_table[j].frequency; 1368c2ecf20Sopenharmony_ci if (freq == CPUFREQ_ENTRY_INVALID || 1378c2ecf20Sopenharmony_ci freq <= max_freq) 1388c2ecf20Sopenharmony_ci continue; 1398c2ecf20Sopenharmony_ci ind = j; 1408c2ecf20Sopenharmony_ci max_freq = freq; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (ind != i) { 1448c2ecf20Sopenharmony_ci /* exchange the frequencies */ 1458c2ecf20Sopenharmony_ci table.driver_data = freq_table[i].driver_data; 1468c2ecf20Sopenharmony_ci table.frequency = freq_table[i].frequency; 1478c2ecf20Sopenharmony_ci freq_table[i].driver_data = freq_table[ind].driver_data; 1488c2ecf20Sopenharmony_ci freq_table[i].frequency = freq_table[ind].frequency; 1498c2ecf20Sopenharmony_ci freq_table[ind].driver_data = table.driver_data; 1508c2ecf20Sopenharmony_ci freq_table[ind].frequency = table.frequency; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic int qoriq_cpufreq_cpu_init(struct cpufreq_policy *policy) 1568c2ecf20Sopenharmony_ci{ 1578c2ecf20Sopenharmony_ci struct device_node *np; 1588c2ecf20Sopenharmony_ci int i, count; 1598c2ecf20Sopenharmony_ci u32 freq; 1608c2ecf20Sopenharmony_ci struct clk *clk; 1618c2ecf20Sopenharmony_ci const struct clk_hw *hwclk; 1628c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *table; 1638c2ecf20Sopenharmony_ci struct cpu_data *data; 1648c2ecf20Sopenharmony_ci unsigned int cpu = policy->cpu; 1658c2ecf20Sopenharmony_ci u64 u64temp; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci np = of_get_cpu_node(cpu, NULL); 1688c2ecf20Sopenharmony_ci if (!np) 1698c2ecf20Sopenharmony_ci return -ENODEV; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci data = kzalloc(sizeof(*data), GFP_KERNEL); 1728c2ecf20Sopenharmony_ci if (!data) 1738c2ecf20Sopenharmony_ci goto err_np; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci policy->clk = of_clk_get(np, 0); 1768c2ecf20Sopenharmony_ci if (IS_ERR(policy->clk)) { 1778c2ecf20Sopenharmony_ci pr_err("%s: no clock information\n", __func__); 1788c2ecf20Sopenharmony_ci goto err_nomem2; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci hwclk = __clk_get_hw(policy->clk); 1828c2ecf20Sopenharmony_ci count = clk_hw_get_num_parents(hwclk); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci data->pclk = kcalloc(count, sizeof(struct clk *), GFP_KERNEL); 1858c2ecf20Sopenharmony_ci if (!data->pclk) 1868c2ecf20Sopenharmony_ci goto err_nomem2; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci table = kcalloc(count + 1, sizeof(*table), GFP_KERNEL); 1898c2ecf20Sopenharmony_ci if (!table) 1908c2ecf20Sopenharmony_ci goto err_pclk; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci for (i = 0; i < count; i++) { 1938c2ecf20Sopenharmony_ci clk = clk_hw_get_parent_by_index(hwclk, i)->clk; 1948c2ecf20Sopenharmony_ci data->pclk[i] = clk; 1958c2ecf20Sopenharmony_ci freq = clk_get_rate(clk); 1968c2ecf20Sopenharmony_ci table[i].frequency = freq / 1000; 1978c2ecf20Sopenharmony_ci table[i].driver_data = i; 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci freq_table_redup(table, count); 2008c2ecf20Sopenharmony_ci freq_table_sort(table, count); 2018c2ecf20Sopenharmony_ci table[i].frequency = CPUFREQ_TABLE_END; 2028c2ecf20Sopenharmony_ci policy->freq_table = table; 2038c2ecf20Sopenharmony_ci data->table = table; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci /* update ->cpus if we have cluster, no harm if not */ 2068c2ecf20Sopenharmony_ci set_affected_cpus(policy); 2078c2ecf20Sopenharmony_ci policy->driver_data = data; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci /* Minimum transition latency is 12 platform clocks */ 2108c2ecf20Sopenharmony_ci u64temp = 12ULL * NSEC_PER_SEC; 2118c2ecf20Sopenharmony_ci do_div(u64temp, get_bus_freq()); 2128c2ecf20Sopenharmony_ci policy->cpuinfo.transition_latency = u64temp + 1; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci of_node_put(np); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci return 0; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_cierr_pclk: 2198c2ecf20Sopenharmony_ci kfree(data->pclk); 2208c2ecf20Sopenharmony_cierr_nomem2: 2218c2ecf20Sopenharmony_ci kfree(data); 2228c2ecf20Sopenharmony_cierr_np: 2238c2ecf20Sopenharmony_ci of_node_put(np); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci return -ENODEV; 2268c2ecf20Sopenharmony_ci} 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cistatic int qoriq_cpufreq_cpu_exit(struct cpufreq_policy *policy) 2298c2ecf20Sopenharmony_ci{ 2308c2ecf20Sopenharmony_ci struct cpu_data *data = policy->driver_data; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci kfree(data->pclk); 2338c2ecf20Sopenharmony_ci kfree(data->table); 2348c2ecf20Sopenharmony_ci kfree(data); 2358c2ecf20Sopenharmony_ci policy->driver_data = NULL; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci return 0; 2388c2ecf20Sopenharmony_ci} 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_cistatic int qoriq_cpufreq_target(struct cpufreq_policy *policy, 2418c2ecf20Sopenharmony_ci unsigned int index) 2428c2ecf20Sopenharmony_ci{ 2438c2ecf20Sopenharmony_ci struct clk *parent; 2448c2ecf20Sopenharmony_ci struct cpu_data *data = policy->driver_data; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci parent = data->pclk[data->table[index].driver_data]; 2478c2ecf20Sopenharmony_ci return clk_set_parent(policy->clk, parent); 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_cistatic struct cpufreq_driver qoriq_cpufreq_driver = { 2518c2ecf20Sopenharmony_ci .name = "qoriq_cpufreq", 2528c2ecf20Sopenharmony_ci .flags = CPUFREQ_CONST_LOOPS | 2538c2ecf20Sopenharmony_ci CPUFREQ_IS_COOLING_DEV, 2548c2ecf20Sopenharmony_ci .init = qoriq_cpufreq_cpu_init, 2558c2ecf20Sopenharmony_ci .exit = qoriq_cpufreq_cpu_exit, 2568c2ecf20Sopenharmony_ci .verify = cpufreq_generic_frequency_table_verify, 2578c2ecf20Sopenharmony_ci .target_index = qoriq_cpufreq_target, 2588c2ecf20Sopenharmony_ci .get = cpufreq_generic_get, 2598c2ecf20Sopenharmony_ci .attr = cpufreq_generic_attr, 2608c2ecf20Sopenharmony_ci}; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_cistatic const struct of_device_id qoriq_cpufreq_blacklist[] = { 2638c2ecf20Sopenharmony_ci /* e6500 cannot use cpufreq due to erratum A-008083 */ 2648c2ecf20Sopenharmony_ci { .compatible = "fsl,b4420-clockgen", }, 2658c2ecf20Sopenharmony_ci { .compatible = "fsl,b4860-clockgen", }, 2668c2ecf20Sopenharmony_ci { .compatible = "fsl,t2080-clockgen", }, 2678c2ecf20Sopenharmony_ci { .compatible = "fsl,t4240-clockgen", }, 2688c2ecf20Sopenharmony_ci {} 2698c2ecf20Sopenharmony_ci}; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_cistatic int qoriq_cpufreq_probe(struct platform_device *pdev) 2728c2ecf20Sopenharmony_ci{ 2738c2ecf20Sopenharmony_ci int ret; 2748c2ecf20Sopenharmony_ci struct device_node *np; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci np = of_find_matching_node(NULL, qoriq_cpufreq_blacklist); 2778c2ecf20Sopenharmony_ci if (np) { 2788c2ecf20Sopenharmony_ci of_node_put(np); 2798c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "Disabling due to erratum A-008083"); 2808c2ecf20Sopenharmony_ci return -ENODEV; 2818c2ecf20Sopenharmony_ci } 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci ret = cpufreq_register_driver(&qoriq_cpufreq_driver); 2848c2ecf20Sopenharmony_ci if (ret) 2858c2ecf20Sopenharmony_ci return ret; 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "Freescale QorIQ CPU frequency scaling driver\n"); 2888c2ecf20Sopenharmony_ci return 0; 2898c2ecf20Sopenharmony_ci} 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cistatic int qoriq_cpufreq_remove(struct platform_device *pdev) 2928c2ecf20Sopenharmony_ci{ 2938c2ecf20Sopenharmony_ci cpufreq_unregister_driver(&qoriq_cpufreq_driver); 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci return 0; 2968c2ecf20Sopenharmony_ci} 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_cistatic struct platform_driver qoriq_cpufreq_platform_driver = { 2998c2ecf20Sopenharmony_ci .driver = { 3008c2ecf20Sopenharmony_ci .name = "qoriq-cpufreq", 3018c2ecf20Sopenharmony_ci }, 3028c2ecf20Sopenharmony_ci .probe = qoriq_cpufreq_probe, 3038c2ecf20Sopenharmony_ci .remove = qoriq_cpufreq_remove, 3048c2ecf20Sopenharmony_ci}; 3058c2ecf20Sopenharmony_cimodule_platform_driver(qoriq_cpufreq_platform_driver); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:qoriq-cpufreq"); 3088c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3098c2ecf20Sopenharmony_ciMODULE_AUTHOR("Tang Yuantian <Yuantian.Tang@freescale.com>"); 3108c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("cpufreq driver for Freescale QorIQ series SoCs"); 311