18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * System Control and Power Interface (SCMI) based CPUFreq Interface driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2018 ARM Ltd. 68c2ecf20Sopenharmony_ci * Sudeep Holla <sudeep.holla@arm.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 128c2ecf20Sopenharmony_ci#include <linux/cpu.h> 138c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 148c2ecf20Sopenharmony_ci#include <linux/cpumask.h> 158c2ecf20Sopenharmony_ci#include <linux/energy_model.h> 168c2ecf20Sopenharmony_ci#include <linux/export.h> 178c2ecf20Sopenharmony_ci#include <linux/module.h> 188c2ecf20Sopenharmony_ci#include <linux/pm_opp.h> 198c2ecf20Sopenharmony_ci#include <linux/slab.h> 208c2ecf20Sopenharmony_ci#include <linux/scmi_protocol.h> 218c2ecf20Sopenharmony_ci#include <linux/types.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistruct scmi_data { 248c2ecf20Sopenharmony_ci int domain_id; 258c2ecf20Sopenharmony_ci struct device *cpu_dev; 268c2ecf20Sopenharmony_ci}; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistatic const struct scmi_handle *handle; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistatic unsigned int scmi_cpufreq_get_rate(unsigned int cpu) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu); 338c2ecf20Sopenharmony_ci const struct scmi_perf_ops *perf_ops = handle->perf_ops; 348c2ecf20Sopenharmony_ci struct scmi_data *priv = policy->driver_data; 358c2ecf20Sopenharmony_ci unsigned long rate; 368c2ecf20Sopenharmony_ci int ret; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci ret = perf_ops->freq_get(handle, priv->domain_id, &rate, false); 398c2ecf20Sopenharmony_ci if (ret) 408c2ecf20Sopenharmony_ci return 0; 418c2ecf20Sopenharmony_ci return rate / 1000; 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci/* 458c2ecf20Sopenharmony_ci * perf_ops->freq_set is not a synchronous, the actual OPP change will 468c2ecf20Sopenharmony_ci * happen asynchronously and can get notified if the events are 478c2ecf20Sopenharmony_ci * subscribed for by the SCMI firmware 488c2ecf20Sopenharmony_ci */ 498c2ecf20Sopenharmony_cistatic int 508c2ecf20Sopenharmony_ciscmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci struct scmi_data *priv = policy->driver_data; 538c2ecf20Sopenharmony_ci const struct scmi_perf_ops *perf_ops = handle->perf_ops; 548c2ecf20Sopenharmony_ci u64 freq = policy->freq_table[index].frequency; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci return perf_ops->freq_set(handle, priv->domain_id, freq * 1000, false); 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic unsigned int scmi_cpufreq_fast_switch(struct cpufreq_policy *policy, 608c2ecf20Sopenharmony_ci unsigned int target_freq) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct scmi_data *priv = policy->driver_data; 638c2ecf20Sopenharmony_ci const struct scmi_perf_ops *perf_ops = handle->perf_ops; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci if (!perf_ops->freq_set(handle, priv->domain_id, 668c2ecf20Sopenharmony_ci target_freq * 1000, true)) 678c2ecf20Sopenharmony_ci return target_freq; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci return 0; 708c2ecf20Sopenharmony_ci} 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic int 738c2ecf20Sopenharmony_ciscmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci int cpu, domain, tdomain; 768c2ecf20Sopenharmony_ci struct device *tcpu_dev; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci domain = handle->perf_ops->device_domain_id(cpu_dev); 798c2ecf20Sopenharmony_ci if (domain < 0) 808c2ecf20Sopenharmony_ci return domain; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci for_each_possible_cpu(cpu) { 838c2ecf20Sopenharmony_ci if (cpu == cpu_dev->id) 848c2ecf20Sopenharmony_ci continue; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci tcpu_dev = get_cpu_device(cpu); 878c2ecf20Sopenharmony_ci if (!tcpu_dev) 888c2ecf20Sopenharmony_ci continue; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci tdomain = handle->perf_ops->device_domain_id(tcpu_dev); 918c2ecf20Sopenharmony_ci if (tdomain == domain) 928c2ecf20Sopenharmony_ci cpumask_set_cpu(cpu, cpumask); 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci return 0; 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic int __maybe_unused 998c2ecf20Sopenharmony_ciscmi_get_cpu_power(unsigned long *power, unsigned long *KHz, 1008c2ecf20Sopenharmony_ci struct device *cpu_dev) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci unsigned long Hz; 1038c2ecf20Sopenharmony_ci int ret, domain; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci domain = handle->perf_ops->device_domain_id(cpu_dev); 1068c2ecf20Sopenharmony_ci if (domain < 0) 1078c2ecf20Sopenharmony_ci return domain; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci /* Get the power cost of the performance domain. */ 1108c2ecf20Sopenharmony_ci Hz = *KHz * 1000; 1118c2ecf20Sopenharmony_ci ret = handle->perf_ops->est_power_get(handle, domain, &Hz, power); 1128c2ecf20Sopenharmony_ci if (ret) 1138c2ecf20Sopenharmony_ci return ret; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci /* The EM framework specifies the frequency in KHz. */ 1168c2ecf20Sopenharmony_ci *KHz = Hz / 1000; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci return 0; 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic int scmi_cpufreq_init(struct cpufreq_policy *policy) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci int ret, nr_opp; 1248c2ecf20Sopenharmony_ci unsigned int latency; 1258c2ecf20Sopenharmony_ci struct device *cpu_dev; 1268c2ecf20Sopenharmony_ci struct scmi_data *priv; 1278c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *freq_table; 1288c2ecf20Sopenharmony_ci struct em_data_callback em_cb = EM_DATA_CB(scmi_get_cpu_power); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci cpu_dev = get_cpu_device(policy->cpu); 1318c2ecf20Sopenharmony_ci if (!cpu_dev) { 1328c2ecf20Sopenharmony_ci pr_err("failed to get cpu%d device\n", policy->cpu); 1338c2ecf20Sopenharmony_ci return -ENODEV; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci ret = handle->perf_ops->device_opps_add(handle, cpu_dev); 1378c2ecf20Sopenharmony_ci if (ret) { 1388c2ecf20Sopenharmony_ci dev_warn(cpu_dev, "failed to add opps to the device\n"); 1398c2ecf20Sopenharmony_ci return ret; 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus); 1438c2ecf20Sopenharmony_ci if (ret) { 1448c2ecf20Sopenharmony_ci dev_warn(cpu_dev, "failed to get sharing cpumask\n"); 1458c2ecf20Sopenharmony_ci return ret; 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); 1498c2ecf20Sopenharmony_ci if (ret) { 1508c2ecf20Sopenharmony_ci dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n", 1518c2ecf20Sopenharmony_ci __func__, ret); 1528c2ecf20Sopenharmony_ci return ret; 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci nr_opp = dev_pm_opp_get_opp_count(cpu_dev); 1568c2ecf20Sopenharmony_ci if (nr_opp <= 0) { 1578c2ecf20Sopenharmony_ci dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n"); 1588c2ecf20Sopenharmony_ci ret = -EPROBE_DEFER; 1598c2ecf20Sopenharmony_ci goto out_free_opp; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 1638c2ecf20Sopenharmony_ci if (!priv) { 1648c2ecf20Sopenharmony_ci ret = -ENOMEM; 1658c2ecf20Sopenharmony_ci goto out_free_opp; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); 1698c2ecf20Sopenharmony_ci if (ret) { 1708c2ecf20Sopenharmony_ci dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); 1718c2ecf20Sopenharmony_ci goto out_free_priv; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci priv->cpu_dev = cpu_dev; 1758c2ecf20Sopenharmony_ci priv->domain_id = handle->perf_ops->device_domain_id(cpu_dev); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci policy->driver_data = priv; 1788c2ecf20Sopenharmony_ci policy->freq_table = freq_table; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci /* SCMI allows DVFS request for any domain from any CPU */ 1818c2ecf20Sopenharmony_ci policy->dvfs_possible_from_any_cpu = true; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci latency = handle->perf_ops->transition_latency_get(handle, cpu_dev); 1848c2ecf20Sopenharmony_ci if (!latency) 1858c2ecf20Sopenharmony_ci latency = CPUFREQ_ETERNAL; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci policy->cpuinfo.transition_latency = latency; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci policy->fast_switch_possible = 1908c2ecf20Sopenharmony_ci handle->perf_ops->fast_switch_possible(handle, cpu_dev); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci em_dev_register_perf_domain(cpu_dev, nr_opp, &em_cb, policy->cpus); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci return 0; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ciout_free_priv: 1978c2ecf20Sopenharmony_ci kfree(priv); 1988c2ecf20Sopenharmony_ciout_free_opp: 1998c2ecf20Sopenharmony_ci dev_pm_opp_remove_all_dynamic(cpu_dev); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci return ret; 2028c2ecf20Sopenharmony_ci} 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_cistatic int scmi_cpufreq_exit(struct cpufreq_policy *policy) 2058c2ecf20Sopenharmony_ci{ 2068c2ecf20Sopenharmony_ci struct scmi_data *priv = policy->driver_data; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); 2098c2ecf20Sopenharmony_ci dev_pm_opp_remove_all_dynamic(priv->cpu_dev); 2108c2ecf20Sopenharmony_ci kfree(priv); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci return 0; 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic struct cpufreq_driver scmi_cpufreq_driver = { 2168c2ecf20Sopenharmony_ci .name = "scmi", 2178c2ecf20Sopenharmony_ci .flags = CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY | 2188c2ecf20Sopenharmony_ci CPUFREQ_NEED_INITIAL_FREQ_CHECK | 2198c2ecf20Sopenharmony_ci CPUFREQ_IS_COOLING_DEV, 2208c2ecf20Sopenharmony_ci .verify = cpufreq_generic_frequency_table_verify, 2218c2ecf20Sopenharmony_ci .attr = cpufreq_generic_attr, 2228c2ecf20Sopenharmony_ci .target_index = scmi_cpufreq_set_target, 2238c2ecf20Sopenharmony_ci .fast_switch = scmi_cpufreq_fast_switch, 2248c2ecf20Sopenharmony_ci .get = scmi_cpufreq_get_rate, 2258c2ecf20Sopenharmony_ci .init = scmi_cpufreq_init, 2268c2ecf20Sopenharmony_ci .exit = scmi_cpufreq_exit, 2278c2ecf20Sopenharmony_ci}; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic int scmi_cpufreq_probe(struct scmi_device *sdev) 2308c2ecf20Sopenharmony_ci{ 2318c2ecf20Sopenharmony_ci int ret; 2328c2ecf20Sopenharmony_ci struct device *dev = &sdev->dev; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci handle = sdev->handle; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci if (!handle || !handle->perf_ops) 2378c2ecf20Sopenharmony_ci return -ENODEV; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci#ifdef CONFIG_COMMON_CLK 2408c2ecf20Sopenharmony_ci /* dummy clock provider as needed by OPP if clocks property is used */ 2418c2ecf20Sopenharmony_ci if (of_property_present(dev->of_node, "#clock-cells")) { 2428c2ecf20Sopenharmony_ci ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, NULL); 2438c2ecf20Sopenharmony_ci if (ret) 2448c2ecf20Sopenharmony_ci return dev_err_probe(dev, ret, "%s: registering clock provider failed\n", __func__); 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci#endif 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci ret = cpufreq_register_driver(&scmi_cpufreq_driver); 2498c2ecf20Sopenharmony_ci if (ret) { 2508c2ecf20Sopenharmony_ci dev_err(dev, "%s: registering cpufreq failed, err: %d\n", 2518c2ecf20Sopenharmony_ci __func__, ret); 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci return ret; 2558c2ecf20Sopenharmony_ci} 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic void scmi_cpufreq_remove(struct scmi_device *sdev) 2588c2ecf20Sopenharmony_ci{ 2598c2ecf20Sopenharmony_ci cpufreq_unregister_driver(&scmi_cpufreq_driver); 2608c2ecf20Sopenharmony_ci} 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_cistatic const struct scmi_device_id scmi_id_table[] = { 2638c2ecf20Sopenharmony_ci { SCMI_PROTOCOL_PERF, "cpufreq" }, 2648c2ecf20Sopenharmony_ci { }, 2658c2ecf20Sopenharmony_ci}; 2668c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(scmi, scmi_id_table); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_cistatic struct scmi_driver scmi_cpufreq_drv = { 2698c2ecf20Sopenharmony_ci .name = "scmi-cpufreq", 2708c2ecf20Sopenharmony_ci .probe = scmi_cpufreq_probe, 2718c2ecf20Sopenharmony_ci .remove = scmi_cpufreq_remove, 2728c2ecf20Sopenharmony_ci .id_table = scmi_id_table, 2738c2ecf20Sopenharmony_ci}; 2748c2ecf20Sopenharmony_cimodule_scmi_driver(scmi_cpufreq_drv); 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); 2778c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ARM SCMI CPUFreq interface driver"); 2788c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 279