| xj | b04a402 | 2021-11-25 15:01:52 +0800 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 | 
|  | 2 | /* | 
|  | 3 | * Copyright (c) 2019 MediaTek Inc. | 
|  | 4 | */ | 
|  | 5 | #include <linux/clk.h> | 
|  | 6 | #include <linux/cpu.h> | 
|  | 7 | #include <linux/cpufreq.h> | 
|  | 8 | #include <linux/cpumask.h> | 
|  | 9 | #include <linux/energy_model.h> | 
|  | 10 | #include <linux/kernel.h> | 
|  | 11 | #include <linux/module.h> | 
|  | 12 | #include <linux/of.h> | 
|  | 13 | #include <linux/of_address.h> | 
|  | 14 | #include <linux/of_device.h> | 
|  | 15 | #include <linux/platform_device.h> | 
|  | 16 | #include <linux/pm_opp.h> | 
|  | 17 | #include <linux/regulator/consumer.h> | 
|  | 18 | #include "../misc/mediatek/sspm/sspm_ipi.h" | 
|  | 19 |  | 
|  | 20 | #define OFFS_WFI_S	0x037c | 
|  | 21 | #define DVFS_D_LEN	(4) | 
|  | 22 |  | 
|  | 23 | struct mtk_cpu_dvfs_info { | 
|  | 24 | struct cpumask cpus; | 
|  | 25 | struct clk *cpu_clk; | 
|  | 26 | struct device *cpu_dev; | 
|  | 27 | struct list_head list_head; | 
|  | 28 | struct mutex lock; | 
|  | 29 | struct regulator *proc_reg; | 
|  | 30 | struct regulator *sram_reg; | 
|  | 31 | void __iomem *csram_base; | 
|  | 32 | }; | 
|  | 33 |  | 
|  | 34 | static LIST_HEAD(dvfs_info_list); | 
|  | 35 |  | 
|  | 36 | enum cpu_dvfs_ipi_type { | 
|  | 37 | IPI_DVFS_INIT, | 
|  | 38 | IPI_SET_CLUSTER_ON_OFF, | 
|  | 39 | NR_DVFS_IPI, | 
|  | 40 | }; | 
|  | 41 |  | 
|  | 42 | struct cdvfs_data { | 
|  | 43 | unsigned int cmd; | 
|  | 44 | union { | 
|  | 45 | struct { | 
|  | 46 | unsigned int arg[3]; | 
|  | 47 | } set_fv; | 
|  | 48 | } u; | 
|  | 49 | }; | 
|  | 50 |  | 
|  | 51 | int dvfs_to_spm2_command(u32 cmd, struct cdvfs_data *cdvfs_d) | 
|  | 52 | { | 
|  | 53 | unsigned int len = DVFS_D_LEN; | 
|  | 54 | int ack_data; | 
|  | 55 | unsigned int ret = 0; | 
|  | 56 |  | 
|  | 57 | switch (cmd) { | 
|  | 58 | case IPI_DVFS_INIT: | 
|  | 59 | cdvfs_d->cmd = cmd; | 
|  | 60 | sspm_ipi_send_sync_new(IPI_ID_CPU_DVFS, IPI_OPT_POLLING, | 
|  | 61 | cdvfs_d, len, &ack_data, 1); | 
|  | 62 | if (ret) { | 
|  | 63 | pr_debug("#@# %s(%d) sspm_ipi_send_sync ret %d\n", | 
|  | 64 | __func__, __LINE__, ret); | 
|  | 65 | } else if (ack_data < 0) { | 
|  | 66 | ret = ack_data; | 
|  | 67 | pr_debug("#@# %s(%d) cmd(%d) return %d\n", | 
|  | 68 | __func__, __LINE__, cmd, ret); | 
|  | 69 | } | 
|  | 70 | break; | 
|  | 71 |  | 
|  | 72 | case IPI_SET_CLUSTER_ON_OFF: | 
|  | 73 | cdvfs_d->cmd = cmd; | 
|  | 74 | sspm_ipi_send_sync_new(IPI_ID_CPU_DVFS, IPI_OPT_POLLING, | 
|  | 75 | cdvfs_d, len, &ack_data, 1); | 
|  | 76 | if (ret) { | 
|  | 77 | pr_debug("ret = %d, set cluster%d ON/OFF state to %d\n", | 
|  | 78 | ret, cdvfs_d->u.set_fv.arg[0], | 
|  | 79 | cdvfs_d->u.set_fv.arg[1]); | 
|  | 80 | } else if (ack_data < 0) { | 
|  | 81 | pr_debug("ret = %d, set cluster%d ON/OFF state to %d\n", | 
|  | 82 | ret, cdvfs_d->u.set_fv.arg[0], | 
|  | 83 | cdvfs_d->u.set_fv.arg[1]); | 
|  | 84 | } | 
|  | 85 | break; | 
|  | 86 |  | 
|  | 87 | default: | 
|  | 88 | break; | 
|  | 89 | } | 
|  | 90 |  | 
|  | 91 | return ret; | 
|  | 92 | } | 
|  | 93 |  | 
|  | 94 | static struct mtk_cpu_dvfs_info *mtk_cpu_dvfs_info_lookup(int cpu) | 
|  | 95 | { | 
|  | 96 | struct mtk_cpu_dvfs_info *info; | 
|  | 97 | struct list_head *list; | 
|  | 98 |  | 
|  | 99 | list_for_each(list, &dvfs_info_list) { | 
|  | 100 | info = list_entry(list, struct mtk_cpu_dvfs_info, list_head); | 
|  | 101 | if (cpumask_test_cpu(cpu, &info->cpus)) | 
|  | 102 | return info; | 
|  | 103 | } | 
|  | 104 | return NULL; | 
|  | 105 | } | 
|  | 106 |  | 
|  | 107 | static int mtk_cpufreq_set_target(struct cpufreq_policy *policy, | 
|  | 108 | unsigned int index) | 
|  | 109 | { | 
|  | 110 | struct mtk_cpu_dvfs_info *info = policy->driver_data; | 
|  | 111 | unsigned int cluster_id = policy->cpu / 6; | 
|  | 112 |  | 
|  | 113 | writel_relaxed(index, info->csram_base + (OFFS_WFI_S + (cluster_id * 4)) | 
|  | 114 | ); | 
|  | 115 | arch_set_freq_scale(policy->related_cpus, | 
|  | 116 | policy->freq_table[index].frequency, | 
|  | 117 | policy->cpuinfo.max_freq); | 
|  | 118 |  | 
|  | 119 | return 0; | 
|  | 120 | } | 
|  | 121 |  | 
|  | 122 | static int mtk_cpu_dvfs_info_init(struct mtk_cpu_dvfs_info *info, int cpu) | 
|  | 123 | { | 
|  | 124 | struct device *cpu_dev; | 
|  | 125 | struct regulator *proc_reg = ERR_PTR(-ENODEV); | 
|  | 126 | struct regulator *sram_reg = ERR_PTR(-ENODEV); | 
|  | 127 | struct clk *cpu_clk = ERR_PTR(-ENODEV); | 
|  | 128 | int ret; | 
|  | 129 |  | 
|  | 130 | cpu_dev = get_cpu_device(cpu); | 
|  | 131 | if (!cpu_dev) | 
|  | 132 | return -ENODEV; | 
|  | 133 |  | 
|  | 134 | cpu_clk = devm_clk_get(cpu_dev, "cpu"); | 
|  | 135 | if (IS_ERR(cpu_clk)) { | 
|  | 136 | if (PTR_ERR(cpu_clk) == -EPROBE_DEFER) | 
|  | 137 | pr_debug("cpu clk for cpu%d not ready, retry.\n", cpu); | 
|  | 138 | else | 
|  | 139 | pr_debug("failed to get cpu clk for cpu%d\n", cpu); | 
|  | 140 | ret = PTR_ERR(cpu_clk); | 
|  | 141 | return ret; | 
|  | 142 | } | 
|  | 143 |  | 
|  | 144 | ret = clk_prepare_enable(cpu_clk); | 
|  | 145 | if (ret) | 
|  | 146 | pr_debug("cannot enable parent clock: %d\n", ret); | 
|  | 147 |  | 
|  | 148 | proc_reg = regulator_get_optional(cpu_dev, "proc"); | 
|  | 149 | if (IS_ERR(proc_reg)) { | 
|  | 150 | if (PTR_ERR(proc_reg) == -EPROBE_DEFER) | 
|  | 151 | pr_debug("proc regulator for cpu%d not ready, retry.\n", | 
|  | 152 | cpu); | 
|  | 153 | else | 
|  | 154 | pr_debug("failed to get proc regulator for cpu%d\n", | 
|  | 155 | cpu); | 
|  | 156 |  | 
|  | 157 | ret = PTR_ERR(proc_reg); | 
|  | 158 | goto out_free_resources; | 
|  | 159 | } | 
|  | 160 |  | 
|  | 161 | /* Both presence and absence of sram regulator are valid cases. */ | 
|  | 162 | sram_reg = regulator_get_optional(cpu_dev, "sram"); | 
|  | 163 |  | 
|  | 164 | /* Get OPP-sharing information from "operating-points-v2" bindings */ | 
|  | 165 | ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, &info->cpus); | 
|  | 166 | if (ret) | 
|  | 167 | goto out_free_resources; | 
|  | 168 |  | 
|  | 169 | ret = dev_pm_opp_of_cpumask_add_table(&info->cpus); | 
|  | 170 | if (ret) | 
|  | 171 | goto out_free_resources; | 
|  | 172 |  | 
|  | 173 | info->cpu_dev = cpu_dev; | 
|  | 174 | info->proc_reg = proc_reg; | 
|  | 175 | info->sram_reg = IS_ERR(sram_reg) ? NULL : sram_reg; | 
|  | 176 | info->cpu_clk = cpu_clk; | 
|  | 177 | mutex_init(&info->lock); | 
|  | 178 |  | 
|  | 179 | return 0; | 
|  | 180 |  | 
|  | 181 | out_free_resources: | 
|  | 182 | if (!IS_ERR(proc_reg)) | 
|  | 183 | regulator_put(proc_reg); | 
|  | 184 | if (!IS_ERR(sram_reg)) | 
|  | 185 | regulator_put(sram_reg); | 
|  | 186 | if (!IS_ERR(cpu_clk)) | 
|  | 187 | clk_put(cpu_clk); | 
|  | 188 |  | 
|  | 189 | return ret; | 
|  | 190 | } | 
|  | 191 |  | 
|  | 192 | static void mtk_cpu_dvfs_info_release(struct mtk_cpu_dvfs_info *info) | 
|  | 193 | { | 
|  | 194 | if (!IS_ERR(info->proc_reg)) | 
|  | 195 | regulator_put(info->proc_reg); | 
|  | 196 | if (!IS_ERR(info->sram_reg)) | 
|  | 197 | regulator_put(info->sram_reg); | 
|  | 198 | if (!IS_ERR(info->cpu_clk)) | 
|  | 199 | clk_put(info->cpu_clk); | 
|  | 200 |  | 
|  | 201 | dev_pm_opp_of_cpumask_remove_table(&info->cpus); | 
|  | 202 | } | 
|  | 203 |  | 
|  | 204 | static int mtk_cpufreq_init(struct cpufreq_policy *policy) | 
|  | 205 | { | 
|  | 206 | struct mtk_cpu_dvfs_info *info; | 
|  | 207 | struct cdvfs_data cdvfs_d; | 
|  | 208 | struct cpufreq_frequency_table *freq_table; | 
|  | 209 | struct em_data_callback em_cb = EM_DATA_CB(of_dev_pm_opp_get_cpu_power); | 
|  | 210 | int ret; | 
|  | 211 |  | 
|  | 212 | info = mtk_cpu_dvfs_info_lookup(policy->cpu); | 
|  | 213 | if (!info) | 
|  | 214 | return -EINVAL; | 
|  | 215 |  | 
|  | 216 | ret = dev_pm_opp_init_cpufreq_table(info->cpu_dev, &freq_table); | 
|  | 217 | if (ret) | 
|  | 218 | return ret; | 
|  | 219 |  | 
|  | 220 | ret = cpufreq_frequency_table_verify(policy, freq_table); | 
|  | 221 | if (ret) | 
|  | 222 | goto out_free_cpufreq_table; | 
|  | 223 |  | 
|  | 224 | ret = dev_pm_opp_get_opp_count(info->cpu_dev); | 
|  | 225 | if (ret <= 0) { | 
|  | 226 | ret = -EINVAL; | 
|  | 227 | goto out_free_opp; | 
|  | 228 | } | 
|  | 229 |  | 
|  | 230 | cpumask_copy(policy->cpus, &info->cpus); | 
|  | 231 | em_register_perf_domain(policy->cpus, ret, &em_cb); | 
|  | 232 | policy->driver_data = info; | 
|  | 233 | policy->clk = info->cpu_clk; | 
|  | 234 | policy->freq_table = freq_table; | 
|  | 235 | policy->transition_delay_us = 1000; /* us */ | 
|  | 236 | /* Cluster, ON:1/OFF:0 */ | 
|  | 237 | cdvfs_d.u.set_fv.arg[0] = policy->cpu / 6; | 
|  | 238 | cdvfs_d.u.set_fv.arg[1] = 1; | 
|  | 239 | dvfs_to_spm2_command(IPI_SET_CLUSTER_ON_OFF, &cdvfs_d); | 
|  | 240 |  | 
|  | 241 | return 0; | 
|  | 242 |  | 
|  | 243 | out_free_opp: | 
|  | 244 | dev_pm_opp_of_cpumask_remove_table(policy->cpus); | 
|  | 245 | out_free_cpufreq_table: | 
|  | 246 | dev_pm_opp_free_cpufreq_table(info->cpu_dev, &freq_table); | 
|  | 247 | return ret; | 
|  | 248 | } | 
|  | 249 |  | 
|  | 250 | static int mtk_cpufreq_exit(struct cpufreq_policy *policy) | 
|  | 251 | { | 
|  | 252 | struct cdvfs_data cdvfs_d; | 
|  | 253 | struct mtk_cpu_dvfs_info *info = policy->driver_data; | 
|  | 254 |  | 
|  | 255 | /* Cluster, ON:1/OFF:0 */ | 
|  | 256 | cdvfs_d.u.set_fv.arg[0] = policy->cpu / 6; | 
|  | 257 | cdvfs_d.u.set_fv.arg[1] = 0; | 
|  | 258 | dvfs_to_spm2_command(IPI_SET_CLUSTER_ON_OFF, &cdvfs_d); | 
|  | 259 | dev_pm_opp_free_cpufreq_table(info->cpu_dev, &policy->freq_table); | 
|  | 260 |  | 
|  | 261 | return 0; | 
|  | 262 | } | 
|  | 263 |  | 
|  | 264 | static struct cpufreq_driver mtk_cpufreq_driver = { | 
|  | 265 | .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK | | 
|  | 266 | CPUFREQ_HAVE_GOVERNOR_PER_POLICY | CPUFREQ_IS_COOLING_DEV, | 
|  | 267 | .verify = cpufreq_generic_frequency_table_verify, | 
|  | 268 | .target_index = mtk_cpufreq_set_target, | 
|  | 269 | .init = mtk_cpufreq_init, | 
|  | 270 | .exit = mtk_cpufreq_exit, | 
|  | 271 | .name = "mtk-cpufreq", | 
|  | 272 | .attr = cpufreq_generic_attr, | 
|  | 273 | }; | 
|  | 274 |  | 
|  | 275 | static int mtk_cpufreq_probe(struct platform_device *pdev) | 
|  | 276 | { | 
|  | 277 | struct mtk_cpu_dvfs_info *info; | 
|  | 278 | struct list_head *list, *tmp; | 
|  | 279 | int cpu, ret; | 
|  | 280 | struct cdvfs_data cdvfs_d; | 
|  | 281 |  | 
|  | 282 | cdvfs_d.u.set_fv.arg[0] = 0; | 
|  | 283 | dvfs_to_spm2_command(IPI_DVFS_INIT, &cdvfs_d); | 
|  | 284 |  | 
|  | 285 | for_each_possible_cpu(cpu) { | 
|  | 286 | info = mtk_cpu_dvfs_info_lookup(cpu); | 
|  | 287 | if (info) | 
|  | 288 | continue; | 
|  | 289 |  | 
|  | 290 | info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); | 
|  | 291 | if (!info) { | 
|  | 292 | ret = -ENOMEM; | 
|  | 293 | goto release_dvfs_info_list; | 
|  | 294 | } | 
|  | 295 |  | 
|  | 296 | info->csram_base = of_iomap(pdev->dev.of_node, 0); | 
|  | 297 | if (!info->csram_base) { | 
|  | 298 | ret = -ENOMEM; | 
|  | 299 | goto release_dvfs_info_list; | 
|  | 300 | } | 
|  | 301 |  | 
|  | 302 | ret = mtk_cpu_dvfs_info_init(info, cpu); | 
|  | 303 | if (ret) | 
|  | 304 | goto release_dvfs_info_list; | 
|  | 305 |  | 
|  | 306 | list_add(&info->list_head, &dvfs_info_list); | 
|  | 307 | } | 
|  | 308 |  | 
|  | 309 | ret = cpufreq_register_driver(&mtk_cpufreq_driver); | 
|  | 310 | if (ret) | 
|  | 311 | goto release_dvfs_info_list; | 
|  | 312 |  | 
|  | 313 | return 0; | 
|  | 314 |  | 
|  | 315 | release_dvfs_info_list: | 
|  | 316 | list_for_each_safe(list, tmp, &dvfs_info_list) { | 
|  | 317 | info = list_entry(list, struct mtk_cpu_dvfs_info, list_head); | 
|  | 318 | mtk_cpu_dvfs_info_release(info); | 
|  | 319 | list_del(list); | 
|  | 320 | } | 
|  | 321 |  | 
|  | 322 | return ret; | 
|  | 323 | } | 
|  | 324 |  | 
|  | 325 | /* List of machines supported by this driver */ | 
|  | 326 | static const struct of_device_id mtk_cpufreq_machines[] = { | 
|  | 327 | { .compatible = "mediatek,sspm-dvfsp", }, | 
|  | 328 | { } | 
|  | 329 | }; | 
|  | 330 |  | 
|  | 331 | MODULE_DEVICE_TABLE(of, mtk_cpufreq_machines); | 
|  | 332 |  | 
|  | 333 | static struct platform_driver mtk_cpufreq_platdrv = { | 
|  | 334 | .probe		= mtk_cpufreq_probe, | 
|  | 335 | .driver = { | 
|  | 336 | .name	= "dvfsp", | 
|  | 337 | .of_match_table = of_match_ptr(mtk_cpufreq_machines), | 
|  | 338 | }, | 
|  | 339 | }; | 
|  | 340 | module_platform_driver(mtk_cpufreq_platdrv); | 
|  | 341 |  | 
|  | 342 | MODULE_AUTHOR("Wei-Chia Su <Wei-Chia.Su@mediatek.com>"); | 
|  | 343 | MODULE_DESCRIPTION("Medaitek SSPM CPUFreq Platform driver"); | 
|  | 344 | MODULE_LICENSE("GPL v2"); |