1 /* 2 * System Control and Power Interface (SCPI) based CPUFreq Interface driver 3 * 4 * It provides necessary ops to arm_big_little cpufreq driver. 5 * 6 * Copyright (C) 2015 ARM Ltd. 7 * Sudeep Holla <sudeep.holla@arm.com> 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2 as 11 * published by the Free Software Foundation. 12 * 13 * This program is distributed "as is" WITHOUT ANY WARRANTY of any 14 * kind, whether express or implied; without even the implied warranty 15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 */ 18 19 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 20 21 #include <linux/cpu.h> 22 #include <linux/cpufreq.h> 23 #include <linux/module.h> 24 #include <linux/platform_device.h> 25 #include <linux/pm_opp.h> 26 #include <linux/scpi_protocol.h> 27 #include <linux/types.h> 28 29 #include "arm_big_little.h" 30 31 static struct scpi_ops *scpi_ops; 32 33 static struct scpi_dvfs_info *scpi_get_dvfs_info(struct device *cpu_dev) 34 { 35 int domain = topology_physical_package_id(cpu_dev->id); 36 37 if (domain < 0) 38 return ERR_PTR(-EINVAL); 39 return scpi_ops->dvfs_get_info(domain); 40 } 41 42 static int scpi_get_transition_latency(struct device *cpu_dev) 43 { 44 struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev); 45 46 if (IS_ERR(info)) 47 return PTR_ERR(info); 48 return info->latency; 49 } 50 51 static int scpi_init_opp_table(const struct cpumask *cpumask) 52 { 53 int idx, ret; 54 struct scpi_opp *opp; 55 struct device *cpu_dev = get_cpu_device(cpumask_first(cpumask)); 56 struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev); 57 58 if (IS_ERR(info)) 59 return PTR_ERR(info); 60 61 if (!info->opps) 62 return -EIO; 63 64 for (opp = info->opps, idx = 0; idx < info->count; idx++, opp++) { 65 ret = dev_pm_opp_add(cpu_dev, opp->freq, opp->m_volt * 1000); 66 if (ret) { 67 dev_warn(cpu_dev, "failed to add opp %uHz %umV\n", 68 opp->freq, opp->m_volt); 69 while (idx-- > 0) 70 dev_pm_opp_remove(cpu_dev, (--opp)->freq); 71 return ret; 72 } 73 } 74 75 ret = dev_pm_opp_set_sharing_cpus(cpu_dev, cpumask); 76 if (ret) 77 dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n", 78 __func__, ret); 79 return ret; 80 } 81 82 static struct cpufreq_arm_bL_ops scpi_cpufreq_ops = { 83 .name = "scpi", 84 .get_transition_latency = scpi_get_transition_latency, 85 .init_opp_table = scpi_init_opp_table, 86 .free_opp_table = dev_pm_opp_cpumask_remove_table, 87 }; 88 89 static int scpi_cpufreq_probe(struct platform_device *pdev) 90 { 91 scpi_ops = get_scpi_ops(); 92 if (!scpi_ops) 93 return -EIO; 94 95 return bL_cpufreq_register(&scpi_cpufreq_ops); 96 } 97 98 static int scpi_cpufreq_remove(struct platform_device *pdev) 99 { 100 bL_cpufreq_unregister(&scpi_cpufreq_ops); 101 scpi_ops = NULL; 102 return 0; 103 } 104 105 static struct platform_driver scpi_cpufreq_platdrv = { 106 .driver = { 107 .name = "scpi-cpufreq", 108 .owner = THIS_MODULE, 109 }, 110 .probe = scpi_cpufreq_probe, 111 .remove = scpi_cpufreq_remove, 112 }; 113 module_platform_driver(scpi_cpufreq_platdrv); 114 115 MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); 116 MODULE_DESCRIPTION("ARM SCPI CPUFreq interface driver"); 117 MODULE_LICENSE("GPL v2"); 118