18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * CPUFreq support for Armada 8K 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2018 Marvell 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Omri Itach <omrii@marvell.com> 88c2ecf20Sopenharmony_ci * Gregory Clement <gregory.clement@bootlin.com> 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/clk.h> 148c2ecf20Sopenharmony_ci#include <linux/cpu.h> 158c2ecf20Sopenharmony_ci#include <linux/err.h> 168c2ecf20Sopenharmony_ci#include <linux/init.h> 178c2ecf20Sopenharmony_ci#include <linux/kernel.h> 188c2ecf20Sopenharmony_ci#include <linux/module.h> 198c2ecf20Sopenharmony_ci#include <linux/of.h> 208c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 218c2ecf20Sopenharmony_ci#include <linux/pm_opp.h> 228c2ecf20Sopenharmony_ci#include <linux/slab.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* 258c2ecf20Sopenharmony_ci * Setup the opps list with the divider for the max frequency, that 268c2ecf20Sopenharmony_ci * will be filled at runtime. 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_cistatic const int opps_div[] __initconst = {1, 2, 3, 4}; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistatic struct platform_device *armada_8k_pdev; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistruct freq_table { 338c2ecf20Sopenharmony_ci struct device *cpu_dev; 348c2ecf20Sopenharmony_ci unsigned int freq[ARRAY_SIZE(opps_div)]; 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* If the CPUs share the same clock, then they are in the same cluster. */ 388c2ecf20Sopenharmony_cistatic void __init armada_8k_get_sharing_cpus(struct clk *cur_clk, 398c2ecf20Sopenharmony_ci struct cpumask *cpumask) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci int cpu; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci for_each_possible_cpu(cpu) { 448c2ecf20Sopenharmony_ci struct device *cpu_dev; 458c2ecf20Sopenharmony_ci struct clk *clk; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci cpu_dev = get_cpu_device(cpu); 488c2ecf20Sopenharmony_ci if (!cpu_dev) { 498c2ecf20Sopenharmony_ci pr_warn("Failed to get cpu%d device\n", cpu); 508c2ecf20Sopenharmony_ci continue; 518c2ecf20Sopenharmony_ci } 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci clk = clk_get(cpu_dev, 0); 548c2ecf20Sopenharmony_ci if (IS_ERR(clk)) { 558c2ecf20Sopenharmony_ci pr_warn("Cannot get clock for CPU %d\n", cpu); 568c2ecf20Sopenharmony_ci } else { 578c2ecf20Sopenharmony_ci if (clk_is_match(clk, cur_clk)) 588c2ecf20Sopenharmony_ci cpumask_set_cpu(cpu, cpumask); 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci clk_put(clk); 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int __init armada_8k_add_opp(struct clk *clk, struct device *cpu_dev, 668c2ecf20Sopenharmony_ci struct freq_table *freq_tables, 678c2ecf20Sopenharmony_ci int opps_index) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci unsigned int cur_frequency; 708c2ecf20Sopenharmony_ci unsigned int freq; 718c2ecf20Sopenharmony_ci int i, ret; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci /* Get nominal (current) CPU frequency. */ 748c2ecf20Sopenharmony_ci cur_frequency = clk_get_rate(clk); 758c2ecf20Sopenharmony_ci if (!cur_frequency) { 768c2ecf20Sopenharmony_ci dev_err(cpu_dev, "Failed to get clock rate for this CPU\n"); 778c2ecf20Sopenharmony_ci return -EINVAL; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci freq_tables[opps_index].cpu_dev = cpu_dev; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(opps_div); i++) { 838c2ecf20Sopenharmony_ci freq = cur_frequency / opps_div[i]; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci ret = dev_pm_opp_add(cpu_dev, freq, 0); 868c2ecf20Sopenharmony_ci if (ret) 878c2ecf20Sopenharmony_ci return ret; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci freq_tables[opps_index].freq[i] = freq; 908c2ecf20Sopenharmony_ci } 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci return 0; 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic void armada_8k_cpufreq_free_table(struct freq_table *freq_tables) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci int opps_index, nb_cpus = num_possible_cpus(); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci for (opps_index = 0 ; opps_index <= nb_cpus; opps_index++) { 1008c2ecf20Sopenharmony_ci int i; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* If cpu_dev is NULL then we reached the end of the array */ 1038c2ecf20Sopenharmony_ci if (!freq_tables[opps_index].cpu_dev) 1048c2ecf20Sopenharmony_ci break; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(opps_div); i++) { 1078c2ecf20Sopenharmony_ci /* 1088c2ecf20Sopenharmony_ci * A 0Hz frequency is not valid, this meant 1098c2ecf20Sopenharmony_ci * that it was not yet initialized so there is 1108c2ecf20Sopenharmony_ci * no more opp to free 1118c2ecf20Sopenharmony_ci */ 1128c2ecf20Sopenharmony_ci if (freq_tables[opps_index].freq[i] == 0) 1138c2ecf20Sopenharmony_ci break; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci dev_pm_opp_remove(freq_tables[opps_index].cpu_dev, 1168c2ecf20Sopenharmony_ci freq_tables[opps_index].freq[i]); 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci kfree(freq_tables); 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int __init armada_8k_cpufreq_init(void) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci int ret = 0, opps_index = 0, cpu, nb_cpus; 1268c2ecf20Sopenharmony_ci struct freq_table *freq_tables; 1278c2ecf20Sopenharmony_ci struct device_node *node; 1288c2ecf20Sopenharmony_ci struct cpumask cpus; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci node = of_find_compatible_node(NULL, NULL, "marvell,ap806-cpu-clock"); 1318c2ecf20Sopenharmony_ci if (!node || !of_device_is_available(node)) { 1328c2ecf20Sopenharmony_ci of_node_put(node); 1338c2ecf20Sopenharmony_ci return -ENODEV; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci of_node_put(node); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci nb_cpus = num_possible_cpus(); 1388c2ecf20Sopenharmony_ci freq_tables = kcalloc(nb_cpus, sizeof(*freq_tables), GFP_KERNEL); 1398c2ecf20Sopenharmony_ci if (!freq_tables) 1408c2ecf20Sopenharmony_ci return -ENOMEM; 1418c2ecf20Sopenharmony_ci cpumask_copy(&cpus, cpu_possible_mask); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci /* 1448c2ecf20Sopenharmony_ci * For each CPU, this loop registers the operating points 1458c2ecf20Sopenharmony_ci * supported (which are the nominal CPU frequency and full integer 1468c2ecf20Sopenharmony_ci * divisions of it). 1478c2ecf20Sopenharmony_ci */ 1488c2ecf20Sopenharmony_ci for_each_cpu(cpu, &cpus) { 1498c2ecf20Sopenharmony_ci struct cpumask shared_cpus; 1508c2ecf20Sopenharmony_ci struct device *cpu_dev; 1518c2ecf20Sopenharmony_ci struct clk *clk; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci cpu_dev = get_cpu_device(cpu); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci if (!cpu_dev) { 1568c2ecf20Sopenharmony_ci pr_err("Cannot get CPU %d\n", cpu); 1578c2ecf20Sopenharmony_ci continue; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci clk = clk_get(cpu_dev, 0); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci if (IS_ERR(clk)) { 1638c2ecf20Sopenharmony_ci pr_err("Cannot get clock for CPU %d\n", cpu); 1648c2ecf20Sopenharmony_ci ret = PTR_ERR(clk); 1658c2ecf20Sopenharmony_ci goto remove_opp; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci ret = armada_8k_add_opp(clk, cpu_dev, freq_tables, opps_index); 1698c2ecf20Sopenharmony_ci if (ret) { 1708c2ecf20Sopenharmony_ci clk_put(clk); 1718c2ecf20Sopenharmony_ci goto remove_opp; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci opps_index++; 1758c2ecf20Sopenharmony_ci cpumask_clear(&shared_cpus); 1768c2ecf20Sopenharmony_ci armada_8k_get_sharing_cpus(clk, &shared_cpus); 1778c2ecf20Sopenharmony_ci dev_pm_opp_set_sharing_cpus(cpu_dev, &shared_cpus); 1788c2ecf20Sopenharmony_ci cpumask_andnot(&cpus, &cpus, &shared_cpus); 1798c2ecf20Sopenharmony_ci clk_put(clk); 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci armada_8k_pdev = platform_device_register_simple("cpufreq-dt", -1, 1838c2ecf20Sopenharmony_ci NULL, 0); 1848c2ecf20Sopenharmony_ci ret = PTR_ERR_OR_ZERO(armada_8k_pdev); 1858c2ecf20Sopenharmony_ci if (ret) 1868c2ecf20Sopenharmony_ci goto remove_opp; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci platform_set_drvdata(armada_8k_pdev, freq_tables); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci return 0; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ciremove_opp: 1938c2ecf20Sopenharmony_ci armada_8k_cpufreq_free_table(freq_tables); 1948c2ecf20Sopenharmony_ci return ret; 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_cimodule_init(armada_8k_cpufreq_init); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic void __exit armada_8k_cpufreq_exit(void) 1998c2ecf20Sopenharmony_ci{ 2008c2ecf20Sopenharmony_ci struct freq_table *freq_tables = platform_get_drvdata(armada_8k_pdev); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci platform_device_unregister(armada_8k_pdev); 2038c2ecf20Sopenharmony_ci armada_8k_cpufreq_free_table(freq_tables); 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_cimodule_exit(armada_8k_cpufreq_exit); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic const struct of_device_id __maybe_unused armada_8k_cpufreq_of_match[] = { 2088c2ecf20Sopenharmony_ci { .compatible = "marvell,ap806-cpu-clock" }, 2098c2ecf20Sopenharmony_ci { }, 2108c2ecf20Sopenharmony_ci}; 2118c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, armada_8k_cpufreq_of_match); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ciMODULE_AUTHOR("Gregory Clement <gregory.clement@bootlin.com>"); 2148c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Armada 8K cpufreq driver"); 2158c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 216