| xj | b04a402 | 2021-11-25 15:01:52 +0800 | [diff] [blame] | 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/clk.h> | 
|  | 22 | #include <linux/cpu.h> | 
|  | 23 | #include <linux/cpufreq.h> | 
|  | 24 | #include <linux/cpumask.h> | 
|  | 25 | #include <linux/cpu_cooling.h> | 
|  | 26 | #include <linux/energy_model.h> | 
|  | 27 | #include <linux/export.h> | 
|  | 28 | #include <linux/module.h> | 
|  | 29 | #include <linux/of_platform.h> | 
|  | 30 | #include <linux/pm_opp.h> | 
|  | 31 | #include <linux/scpi_protocol.h> | 
|  | 32 | #include <linux/slab.h> | 
|  | 33 | #include <linux/types.h> | 
|  | 34 |  | 
|  | 35 | struct scpi_data { | 
|  | 36 | struct clk *clk; | 
|  | 37 | struct device *cpu_dev; | 
|  | 38 | struct thermal_cooling_device *cdev; | 
|  | 39 | }; | 
|  | 40 |  | 
|  | 41 | static struct scpi_ops *scpi_ops; | 
|  | 42 |  | 
|  | 43 | static unsigned int scpi_cpufreq_get_rate(unsigned int cpu) | 
|  | 44 | { | 
|  | 45 | struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu); | 
|  | 46 | struct scpi_data *priv = policy->driver_data; | 
|  | 47 | unsigned long rate = clk_get_rate(priv->clk); | 
|  | 48 |  | 
|  | 49 | return rate / 1000; | 
|  | 50 | } | 
|  | 51 |  | 
|  | 52 | static int | 
|  | 53 | scpi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index) | 
|  | 54 | { | 
|  | 55 | unsigned long freq = policy->freq_table[index].frequency; | 
|  | 56 | struct scpi_data *priv = policy->driver_data; | 
|  | 57 | u64 rate = freq * 1000; | 
|  | 58 | int ret; | 
|  | 59 |  | 
|  | 60 | ret = clk_set_rate(priv->clk, rate); | 
|  | 61 |  | 
|  | 62 | if (ret) | 
|  | 63 | return ret; | 
|  | 64 |  | 
|  | 65 | if (clk_get_rate(priv->clk) != rate) | 
|  | 66 | return -EIO; | 
|  | 67 |  | 
|  | 68 | arch_set_freq_scale(policy->related_cpus, freq, | 
|  | 69 | policy->cpuinfo.max_freq); | 
|  | 70 |  | 
|  | 71 | return 0; | 
|  | 72 | } | 
|  | 73 |  | 
|  | 74 | static int | 
|  | 75 | scpi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) | 
|  | 76 | { | 
|  | 77 | int cpu, domain, tdomain; | 
|  | 78 | struct device *tcpu_dev; | 
|  | 79 |  | 
|  | 80 | domain = scpi_ops->device_domain_id(cpu_dev); | 
|  | 81 | if (domain < 0) | 
|  | 82 | return domain; | 
|  | 83 |  | 
|  | 84 | for_each_possible_cpu(cpu) { | 
|  | 85 | if (cpu == cpu_dev->id) | 
|  | 86 | continue; | 
|  | 87 |  | 
|  | 88 | tcpu_dev = get_cpu_device(cpu); | 
|  | 89 | if (!tcpu_dev) | 
|  | 90 | continue; | 
|  | 91 |  | 
|  | 92 | tdomain = scpi_ops->device_domain_id(tcpu_dev); | 
|  | 93 | if (tdomain == domain) | 
|  | 94 | cpumask_set_cpu(cpu, cpumask); | 
|  | 95 | } | 
|  | 96 |  | 
|  | 97 | return 0; | 
|  | 98 | } | 
|  | 99 |  | 
|  | 100 | static int scpi_cpufreq_init(struct cpufreq_policy *policy) | 
|  | 101 | { | 
|  | 102 | int ret, nr_opp; | 
|  | 103 | unsigned int latency; | 
|  | 104 | struct device *cpu_dev; | 
|  | 105 | struct scpi_data *priv; | 
|  | 106 | struct cpufreq_frequency_table *freq_table; | 
|  | 107 | struct em_data_callback em_cb = EM_DATA_CB(of_dev_pm_opp_get_cpu_power); | 
|  | 108 |  | 
|  | 109 | cpu_dev = get_cpu_device(policy->cpu); | 
|  | 110 | if (!cpu_dev) { | 
|  | 111 | pr_err("failed to get cpu%d device\n", policy->cpu); | 
|  | 112 | return -ENODEV; | 
|  | 113 | } | 
|  | 114 |  | 
|  | 115 | ret = scpi_ops->add_opps_to_device(cpu_dev); | 
|  | 116 | if (ret) { | 
|  | 117 | dev_warn(cpu_dev, "failed to add opps to the device\n"); | 
|  | 118 | return ret; | 
|  | 119 | } | 
|  | 120 |  | 
|  | 121 | ret = scpi_get_sharing_cpus(cpu_dev, policy->cpus); | 
|  | 122 | if (ret) { | 
|  | 123 | dev_warn(cpu_dev, "failed to get sharing cpumask\n"); | 
|  | 124 | return ret; | 
|  | 125 | } | 
|  | 126 |  | 
|  | 127 | ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); | 
|  | 128 | if (ret) { | 
|  | 129 | dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n", | 
|  | 130 | __func__, ret); | 
|  | 131 | return ret; | 
|  | 132 | } | 
|  | 133 |  | 
|  | 134 | ret = dev_pm_opp_get_opp_count(cpu_dev); | 
|  | 135 | if (ret <= 0) { | 
|  | 136 | dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n"); | 
|  | 137 | ret = -EPROBE_DEFER; | 
|  | 138 | goto out_free_opp; | 
|  | 139 | } | 
|  | 140 | nr_opp = ret; | 
|  | 141 |  | 
|  | 142 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 
|  | 143 | if (!priv) { | 
|  | 144 | ret = -ENOMEM; | 
|  | 145 | goto out_free_opp; | 
|  | 146 | } | 
|  | 147 |  | 
|  | 148 | ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); | 
|  | 149 | if (ret) { | 
|  | 150 | dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); | 
|  | 151 | goto out_free_priv; | 
|  | 152 | } | 
|  | 153 |  | 
|  | 154 | priv->cpu_dev = cpu_dev; | 
|  | 155 | priv->clk = clk_get(cpu_dev, NULL); | 
|  | 156 | if (IS_ERR(priv->clk)) { | 
|  | 157 | dev_err(cpu_dev, "%s: Failed to get clk for cpu: %d\n", | 
|  | 158 | __func__, cpu_dev->id); | 
|  | 159 | ret = PTR_ERR(priv->clk); | 
|  | 160 | goto out_free_cpufreq_table; | 
|  | 161 | } | 
|  | 162 |  | 
|  | 163 | policy->driver_data = priv; | 
|  | 164 | policy->freq_table = freq_table; | 
|  | 165 |  | 
|  | 166 | /* scpi allows DVFS request for any domain from any CPU */ | 
|  | 167 | policy->dvfs_possible_from_any_cpu = true; | 
|  | 168 |  | 
|  | 169 | latency = scpi_ops->get_transition_latency(cpu_dev); | 
|  | 170 | if (!latency) | 
|  | 171 | latency = CPUFREQ_ETERNAL; | 
|  | 172 |  | 
|  | 173 | policy->cpuinfo.transition_latency = latency; | 
|  | 174 |  | 
|  | 175 | policy->fast_switch_possible = false; | 
|  | 176 |  | 
|  | 177 | em_register_perf_domain(policy->cpus, nr_opp, &em_cb); | 
|  | 178 |  | 
|  | 179 | return 0; | 
|  | 180 |  | 
|  | 181 | out_free_cpufreq_table: | 
|  | 182 | dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); | 
|  | 183 | out_free_priv: | 
|  | 184 | kfree(priv); | 
|  | 185 | out_free_opp: | 
|  | 186 | dev_pm_opp_cpumask_remove_table(policy->cpus); | 
|  | 187 |  | 
|  | 188 | return ret; | 
|  | 189 | } | 
|  | 190 |  | 
|  | 191 | static int scpi_cpufreq_exit(struct cpufreq_policy *policy) | 
|  | 192 | { | 
|  | 193 | struct scpi_data *priv = policy->driver_data; | 
|  | 194 |  | 
|  | 195 | cpufreq_cooling_unregister(priv->cdev); | 
|  | 196 | clk_put(priv->clk); | 
|  | 197 | dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); | 
|  | 198 | kfree(priv); | 
|  | 199 | dev_pm_opp_cpumask_remove_table(policy->related_cpus); | 
|  | 200 |  | 
|  | 201 | return 0; | 
|  | 202 | } | 
|  | 203 |  | 
|  | 204 | static void scpi_cpufreq_ready(struct cpufreq_policy *policy) | 
|  | 205 | { | 
|  | 206 | struct scpi_data *priv = policy->driver_data; | 
|  | 207 |  | 
|  | 208 | priv->cdev = of_cpufreq_cooling_register(policy); | 
|  | 209 | } | 
|  | 210 |  | 
|  | 211 | static struct cpufreq_driver scpi_cpufreq_driver = { | 
|  | 212 | .name	= "scpi-cpufreq", | 
|  | 213 | .flags	= CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY | | 
|  | 214 | CPUFREQ_NEED_INITIAL_FREQ_CHECK, | 
|  | 215 | .verify	= cpufreq_generic_frequency_table_verify, | 
|  | 216 | .attr	= cpufreq_generic_attr, | 
|  | 217 | .get	= scpi_cpufreq_get_rate, | 
|  | 218 | .init	= scpi_cpufreq_init, | 
|  | 219 | .exit	= scpi_cpufreq_exit, | 
|  | 220 | .ready	= scpi_cpufreq_ready, | 
|  | 221 | .target_index	= scpi_cpufreq_set_target, | 
|  | 222 | }; | 
|  | 223 |  | 
|  | 224 | static int scpi_cpufreq_probe(struct platform_device *pdev) | 
|  | 225 | { | 
|  | 226 | int ret; | 
|  | 227 |  | 
|  | 228 | scpi_ops = get_scpi_ops(); | 
|  | 229 | if (!scpi_ops) | 
|  | 230 | return -EIO; | 
|  | 231 |  | 
|  | 232 | ret = cpufreq_register_driver(&scpi_cpufreq_driver); | 
|  | 233 | if (ret) | 
|  | 234 | dev_err(&pdev->dev, "%s: registering cpufreq failed, err: %d\n", | 
|  | 235 | __func__, ret); | 
|  | 236 | return ret; | 
|  | 237 | } | 
|  | 238 |  | 
|  | 239 | static int scpi_cpufreq_remove(struct platform_device *pdev) | 
|  | 240 | { | 
|  | 241 | cpufreq_unregister_driver(&scpi_cpufreq_driver); | 
|  | 242 | scpi_ops = NULL; | 
|  | 243 | return 0; | 
|  | 244 | } | 
|  | 245 |  | 
|  | 246 | static struct platform_driver scpi_cpufreq_platdrv = { | 
|  | 247 | .driver = { | 
|  | 248 | .name	= "scpi-cpufreq", | 
|  | 249 | }, | 
|  | 250 | .probe		= scpi_cpufreq_probe, | 
|  | 251 | .remove		= scpi_cpufreq_remove, | 
|  | 252 | }; | 
|  | 253 | module_platform_driver(scpi_cpufreq_platdrv); | 
|  | 254 |  | 
|  | 255 | MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); | 
|  | 256 | MODULE_DESCRIPTION("ARM SCPI CPUFreq interface driver"); | 
|  | 257 | MODULE_LICENSE("GPL v2"); |