18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * System Control and Power Interface (SCPI) based CPUFreq Interface driver 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2015 ARM Ltd. 58c2ecf20Sopenharmony_ci * Sudeep Holla <sudeep.holla@arm.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 88c2ecf20Sopenharmony_ci * it under the terms of the GNU General Public License version 2 as 98c2ecf20Sopenharmony_ci * published by the Free Software Foundation. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * This program is distributed "as is" WITHOUT ANY WARRANTY of any 128c2ecf20Sopenharmony_ci * kind, whether express or implied; without even the implied warranty 138c2ecf20Sopenharmony_ci * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 148c2ecf20Sopenharmony_ci * GNU General Public License for more details. 158c2ecf20Sopenharmony_ci */ 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <linux/clk.h> 208c2ecf20Sopenharmony_ci#include <linux/cpu.h> 218c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 228c2ecf20Sopenharmony_ci#include <linux/cpumask.h> 238c2ecf20Sopenharmony_ci#include <linux/export.h> 248c2ecf20Sopenharmony_ci#include <linux/module.h> 258c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 268c2ecf20Sopenharmony_ci#include <linux/pm_opp.h> 278c2ecf20Sopenharmony_ci#include <linux/scpi_protocol.h> 288c2ecf20Sopenharmony_ci#include <linux/slab.h> 298c2ecf20Sopenharmony_ci#include <linux/types.h> 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistruct scpi_data { 328c2ecf20Sopenharmony_ci struct clk *clk; 338c2ecf20Sopenharmony_ci struct device *cpu_dev; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic struct scpi_ops *scpi_ops; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic unsigned int scpi_cpufreq_get_rate(unsigned int cpu) 398c2ecf20Sopenharmony_ci{ 408c2ecf20Sopenharmony_ci struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu); 418c2ecf20Sopenharmony_ci struct scpi_data *priv = policy->driver_data; 428c2ecf20Sopenharmony_ci unsigned long rate = clk_get_rate(priv->clk); 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci return rate / 1000; 458c2ecf20Sopenharmony_ci} 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic int 488c2ecf20Sopenharmony_ciscpi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci u64 rate = policy->freq_table[index].frequency * 1000; 518c2ecf20Sopenharmony_ci struct scpi_data *priv = policy->driver_data; 528c2ecf20Sopenharmony_ci int ret; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci ret = clk_set_rate(priv->clk, rate); 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci if (ret) 578c2ecf20Sopenharmony_ci return ret; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci if (clk_get_rate(priv->clk) != rate) 608c2ecf20Sopenharmony_ci return -EIO; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci return 0; 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int 668c2ecf20Sopenharmony_ciscpi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci int cpu, domain, tdomain; 698c2ecf20Sopenharmony_ci struct device *tcpu_dev; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci domain = scpi_ops->device_domain_id(cpu_dev); 728c2ecf20Sopenharmony_ci if (domain < 0) 738c2ecf20Sopenharmony_ci return domain; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci for_each_possible_cpu(cpu) { 768c2ecf20Sopenharmony_ci if (cpu == cpu_dev->id) 778c2ecf20Sopenharmony_ci continue; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci tcpu_dev = get_cpu_device(cpu); 808c2ecf20Sopenharmony_ci if (!tcpu_dev) 818c2ecf20Sopenharmony_ci continue; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci tdomain = scpi_ops->device_domain_id(tcpu_dev); 848c2ecf20Sopenharmony_ci if (tdomain == domain) 858c2ecf20Sopenharmony_ci cpumask_set_cpu(cpu, cpumask); 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return 0; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic int scpi_cpufreq_init(struct cpufreq_policy *policy) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci int ret; 948c2ecf20Sopenharmony_ci unsigned int latency; 958c2ecf20Sopenharmony_ci struct device *cpu_dev; 968c2ecf20Sopenharmony_ci struct scpi_data *priv; 978c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *freq_table; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci cpu_dev = get_cpu_device(policy->cpu); 1008c2ecf20Sopenharmony_ci if (!cpu_dev) { 1018c2ecf20Sopenharmony_ci pr_err("failed to get cpu%d device\n", policy->cpu); 1028c2ecf20Sopenharmony_ci return -ENODEV; 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci ret = scpi_ops->add_opps_to_device(cpu_dev); 1068c2ecf20Sopenharmony_ci if (ret) { 1078c2ecf20Sopenharmony_ci dev_warn(cpu_dev, "failed to add opps to the device\n"); 1088c2ecf20Sopenharmony_ci return ret; 1098c2ecf20Sopenharmony_ci } 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci ret = scpi_get_sharing_cpus(cpu_dev, policy->cpus); 1128c2ecf20Sopenharmony_ci if (ret) { 1138c2ecf20Sopenharmony_ci dev_warn(cpu_dev, "failed to get sharing cpumask\n"); 1148c2ecf20Sopenharmony_ci return ret; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); 1188c2ecf20Sopenharmony_ci if (ret) { 1198c2ecf20Sopenharmony_ci dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n", 1208c2ecf20Sopenharmony_ci __func__, ret); 1218c2ecf20Sopenharmony_ci return ret; 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci ret = dev_pm_opp_get_opp_count(cpu_dev); 1258c2ecf20Sopenharmony_ci if (ret <= 0) { 1268c2ecf20Sopenharmony_ci dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n"); 1278c2ecf20Sopenharmony_ci ret = -EPROBE_DEFER; 1288c2ecf20Sopenharmony_ci goto out_free_opp; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 1328c2ecf20Sopenharmony_ci if (!priv) { 1338c2ecf20Sopenharmony_ci ret = -ENOMEM; 1348c2ecf20Sopenharmony_ci goto out_free_opp; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); 1388c2ecf20Sopenharmony_ci if (ret) { 1398c2ecf20Sopenharmony_ci dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); 1408c2ecf20Sopenharmony_ci goto out_free_priv; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci priv->cpu_dev = cpu_dev; 1448c2ecf20Sopenharmony_ci priv->clk = clk_get(cpu_dev, NULL); 1458c2ecf20Sopenharmony_ci if (IS_ERR(priv->clk)) { 1468c2ecf20Sopenharmony_ci dev_err(cpu_dev, "%s: Failed to get clk for cpu: %d\n", 1478c2ecf20Sopenharmony_ci __func__, cpu_dev->id); 1488c2ecf20Sopenharmony_ci ret = PTR_ERR(priv->clk); 1498c2ecf20Sopenharmony_ci goto out_free_cpufreq_table; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci policy->driver_data = priv; 1538c2ecf20Sopenharmony_ci policy->freq_table = freq_table; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci /* scpi allows DVFS request for any domain from any CPU */ 1568c2ecf20Sopenharmony_ci policy->dvfs_possible_from_any_cpu = true; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci latency = scpi_ops->get_transition_latency(cpu_dev); 1598c2ecf20Sopenharmony_ci if (!latency) 1608c2ecf20Sopenharmony_ci latency = CPUFREQ_ETERNAL; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci policy->cpuinfo.transition_latency = latency; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci policy->fast_switch_possible = false; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci dev_pm_opp_of_register_em(cpu_dev, policy->cpus); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci return 0; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ciout_free_cpufreq_table: 1718c2ecf20Sopenharmony_ci dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); 1728c2ecf20Sopenharmony_ciout_free_priv: 1738c2ecf20Sopenharmony_ci kfree(priv); 1748c2ecf20Sopenharmony_ciout_free_opp: 1758c2ecf20Sopenharmony_ci dev_pm_opp_remove_all_dynamic(cpu_dev); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci return ret; 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic int scpi_cpufreq_exit(struct cpufreq_policy *policy) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci struct scpi_data *priv = policy->driver_data; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci clk_put(priv->clk); 1858c2ecf20Sopenharmony_ci dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); 1868c2ecf20Sopenharmony_ci dev_pm_opp_remove_all_dynamic(priv->cpu_dev); 1878c2ecf20Sopenharmony_ci kfree(priv); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci return 0; 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_cistatic struct cpufreq_driver scpi_cpufreq_driver = { 1938c2ecf20Sopenharmony_ci .name = "scpi-cpufreq", 1948c2ecf20Sopenharmony_ci .flags = CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY | 1958c2ecf20Sopenharmony_ci CPUFREQ_NEED_INITIAL_FREQ_CHECK | 1968c2ecf20Sopenharmony_ci CPUFREQ_IS_COOLING_DEV, 1978c2ecf20Sopenharmony_ci .verify = cpufreq_generic_frequency_table_verify, 1988c2ecf20Sopenharmony_ci .attr = cpufreq_generic_attr, 1998c2ecf20Sopenharmony_ci .get = scpi_cpufreq_get_rate, 2008c2ecf20Sopenharmony_ci .init = scpi_cpufreq_init, 2018c2ecf20Sopenharmony_ci .exit = scpi_cpufreq_exit, 2028c2ecf20Sopenharmony_ci .target_index = scpi_cpufreq_set_target, 2038c2ecf20Sopenharmony_ci}; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic int scpi_cpufreq_probe(struct platform_device *pdev) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci int ret; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci scpi_ops = get_scpi_ops(); 2108c2ecf20Sopenharmony_ci if (!scpi_ops) 2118c2ecf20Sopenharmony_ci return -EIO; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci ret = cpufreq_register_driver(&scpi_cpufreq_driver); 2148c2ecf20Sopenharmony_ci if (ret) 2158c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "%s: registering cpufreq failed, err: %d\n", 2168c2ecf20Sopenharmony_ci __func__, ret); 2178c2ecf20Sopenharmony_ci return ret; 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic int scpi_cpufreq_remove(struct platform_device *pdev) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci cpufreq_unregister_driver(&scpi_cpufreq_driver); 2238c2ecf20Sopenharmony_ci scpi_ops = NULL; 2248c2ecf20Sopenharmony_ci return 0; 2258c2ecf20Sopenharmony_ci} 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cistatic struct platform_driver scpi_cpufreq_platdrv = { 2288c2ecf20Sopenharmony_ci .driver = { 2298c2ecf20Sopenharmony_ci .name = "scpi-cpufreq", 2308c2ecf20Sopenharmony_ci }, 2318c2ecf20Sopenharmony_ci .probe = scpi_cpufreq_probe, 2328c2ecf20Sopenharmony_ci .remove = scpi_cpufreq_remove, 2338c2ecf20Sopenharmony_ci}; 2348c2ecf20Sopenharmony_cimodule_platform_driver(scpi_cpufreq_platdrv); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:scpi-cpufreq"); 2378c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); 2388c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ARM SCPI CPUFreq interface driver"); 2398c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 240