18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2015 Linaro Ltd. 48c2ecf20Sopenharmony_ci * Author: Pi-Cheng Chen <pi-cheng.chen@linaro.org> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/clk.h> 88c2ecf20Sopenharmony_ci#include <linux/cpu.h> 98c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 108c2ecf20Sopenharmony_ci#include <linux/cpumask.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/of.h> 138c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 148c2ecf20Sopenharmony_ci#include <linux/pm_opp.h> 158c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <linux/thermal.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define MIN_VOLT_SHIFT (100000) 208c2ecf20Sopenharmony_ci#define MAX_VOLT_SHIFT (200000) 218c2ecf20Sopenharmony_ci#define MAX_VOLT_LIMIT (1150000) 228c2ecf20Sopenharmony_ci#define VOLT_TOL (10000) 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* 258c2ecf20Sopenharmony_ci * The struct mtk_cpu_dvfs_info holds necessary information for doing CPU DVFS 268c2ecf20Sopenharmony_ci * on each CPU power/clock domain of Mediatek SoCs. Each CPU cluster in 278c2ecf20Sopenharmony_ci * Mediatek SoCs has two voltage inputs, Vproc and Vsram. In some cases the two 288c2ecf20Sopenharmony_ci * voltage inputs need to be controlled under a hardware limitation: 298c2ecf20Sopenharmony_ci * 100mV < Vsram - Vproc < 200mV 308c2ecf20Sopenharmony_ci * 318c2ecf20Sopenharmony_ci * When scaling the clock frequency of a CPU clock domain, the clock source 328c2ecf20Sopenharmony_ci * needs to be switched to another stable PLL clock temporarily until 338c2ecf20Sopenharmony_ci * the original PLL becomes stable at target frequency. 348c2ecf20Sopenharmony_ci */ 358c2ecf20Sopenharmony_cistruct mtk_cpu_dvfs_info { 368c2ecf20Sopenharmony_ci struct cpumask cpus; 378c2ecf20Sopenharmony_ci struct device *cpu_dev; 388c2ecf20Sopenharmony_ci struct regulator *proc_reg; 398c2ecf20Sopenharmony_ci struct regulator *sram_reg; 408c2ecf20Sopenharmony_ci struct clk *cpu_clk; 418c2ecf20Sopenharmony_ci struct clk *inter_clk; 428c2ecf20Sopenharmony_ci struct list_head list_head; 438c2ecf20Sopenharmony_ci int intermediate_voltage; 448c2ecf20Sopenharmony_ci bool need_voltage_tracking; 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic struct platform_device *cpufreq_pdev; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic LIST_HEAD(dvfs_info_list); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic struct mtk_cpu_dvfs_info *mtk_cpu_dvfs_info_lookup(int cpu) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci struct mtk_cpu_dvfs_info *info; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci list_for_each_entry(info, &dvfs_info_list, list_head) { 568c2ecf20Sopenharmony_ci if (cpumask_test_cpu(cpu, &info->cpus)) 578c2ecf20Sopenharmony_ci return info; 588c2ecf20Sopenharmony_ci } 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci return NULL; 618c2ecf20Sopenharmony_ci} 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic int mtk_cpufreq_voltage_tracking(struct mtk_cpu_dvfs_info *info, 648c2ecf20Sopenharmony_ci int new_vproc) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct regulator *proc_reg = info->proc_reg; 678c2ecf20Sopenharmony_ci struct regulator *sram_reg = info->sram_reg; 688c2ecf20Sopenharmony_ci int old_vproc, old_vsram, new_vsram, vsram, vproc, ret; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci old_vproc = regulator_get_voltage(proc_reg); 718c2ecf20Sopenharmony_ci if (old_vproc < 0) { 728c2ecf20Sopenharmony_ci pr_err("%s: invalid Vproc value: %d\n", __func__, old_vproc); 738c2ecf20Sopenharmony_ci return old_vproc; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci /* Vsram should not exceed the maximum allowed voltage of SoC. */ 768c2ecf20Sopenharmony_ci new_vsram = min(new_vproc + MIN_VOLT_SHIFT, MAX_VOLT_LIMIT); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci if (old_vproc < new_vproc) { 798c2ecf20Sopenharmony_ci /* 808c2ecf20Sopenharmony_ci * When scaling up voltages, Vsram and Vproc scale up step 818c2ecf20Sopenharmony_ci * by step. At each step, set Vsram to (Vproc + 200mV) first, 828c2ecf20Sopenharmony_ci * then set Vproc to (Vsram - 100mV). 838c2ecf20Sopenharmony_ci * Keep doing it until Vsram and Vproc hit target voltages. 848c2ecf20Sopenharmony_ci */ 858c2ecf20Sopenharmony_ci do { 868c2ecf20Sopenharmony_ci old_vsram = regulator_get_voltage(sram_reg); 878c2ecf20Sopenharmony_ci if (old_vsram < 0) { 888c2ecf20Sopenharmony_ci pr_err("%s: invalid Vsram value: %d\n", 898c2ecf20Sopenharmony_ci __func__, old_vsram); 908c2ecf20Sopenharmony_ci return old_vsram; 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci old_vproc = regulator_get_voltage(proc_reg); 938c2ecf20Sopenharmony_ci if (old_vproc < 0) { 948c2ecf20Sopenharmony_ci pr_err("%s: invalid Vproc value: %d\n", 958c2ecf20Sopenharmony_ci __func__, old_vproc); 968c2ecf20Sopenharmony_ci return old_vproc; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci vsram = min(new_vsram, old_vproc + MAX_VOLT_SHIFT); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci if (vsram + VOLT_TOL >= MAX_VOLT_LIMIT) { 1028c2ecf20Sopenharmony_ci vsram = MAX_VOLT_LIMIT; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* 1058c2ecf20Sopenharmony_ci * If the target Vsram hits the maximum voltage, 1068c2ecf20Sopenharmony_ci * try to set the exact voltage value first. 1078c2ecf20Sopenharmony_ci */ 1088c2ecf20Sopenharmony_ci ret = regulator_set_voltage(sram_reg, vsram, 1098c2ecf20Sopenharmony_ci vsram); 1108c2ecf20Sopenharmony_ci if (ret) 1118c2ecf20Sopenharmony_ci ret = regulator_set_voltage(sram_reg, 1128c2ecf20Sopenharmony_ci vsram - VOLT_TOL, 1138c2ecf20Sopenharmony_ci vsram); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci vproc = new_vproc; 1168c2ecf20Sopenharmony_ci } else { 1178c2ecf20Sopenharmony_ci ret = regulator_set_voltage(sram_reg, vsram, 1188c2ecf20Sopenharmony_ci vsram + VOLT_TOL); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci vproc = vsram - MIN_VOLT_SHIFT; 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci if (ret) 1238c2ecf20Sopenharmony_ci return ret; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci ret = regulator_set_voltage(proc_reg, vproc, 1268c2ecf20Sopenharmony_ci vproc + VOLT_TOL); 1278c2ecf20Sopenharmony_ci if (ret) { 1288c2ecf20Sopenharmony_ci regulator_set_voltage(sram_reg, old_vsram, 1298c2ecf20Sopenharmony_ci old_vsram); 1308c2ecf20Sopenharmony_ci return ret; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci } while (vproc < new_vproc || vsram < new_vsram); 1338c2ecf20Sopenharmony_ci } else if (old_vproc > new_vproc) { 1348c2ecf20Sopenharmony_ci /* 1358c2ecf20Sopenharmony_ci * When scaling down voltages, Vsram and Vproc scale down step 1368c2ecf20Sopenharmony_ci * by step. At each step, set Vproc to (Vsram - 200mV) first, 1378c2ecf20Sopenharmony_ci * then set Vproc to (Vproc + 100mV). 1388c2ecf20Sopenharmony_ci * Keep doing it until Vsram and Vproc hit target voltages. 1398c2ecf20Sopenharmony_ci */ 1408c2ecf20Sopenharmony_ci do { 1418c2ecf20Sopenharmony_ci old_vproc = regulator_get_voltage(proc_reg); 1428c2ecf20Sopenharmony_ci if (old_vproc < 0) { 1438c2ecf20Sopenharmony_ci pr_err("%s: invalid Vproc value: %d\n", 1448c2ecf20Sopenharmony_ci __func__, old_vproc); 1458c2ecf20Sopenharmony_ci return old_vproc; 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci old_vsram = regulator_get_voltage(sram_reg); 1488c2ecf20Sopenharmony_ci if (old_vsram < 0) { 1498c2ecf20Sopenharmony_ci pr_err("%s: invalid Vsram value: %d\n", 1508c2ecf20Sopenharmony_ci __func__, old_vsram); 1518c2ecf20Sopenharmony_ci return old_vsram; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci vproc = max(new_vproc, old_vsram - MAX_VOLT_SHIFT); 1558c2ecf20Sopenharmony_ci ret = regulator_set_voltage(proc_reg, vproc, 1568c2ecf20Sopenharmony_ci vproc + VOLT_TOL); 1578c2ecf20Sopenharmony_ci if (ret) 1588c2ecf20Sopenharmony_ci return ret; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci if (vproc == new_vproc) 1618c2ecf20Sopenharmony_ci vsram = new_vsram; 1628c2ecf20Sopenharmony_ci else 1638c2ecf20Sopenharmony_ci vsram = max(new_vsram, vproc + MIN_VOLT_SHIFT); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (vsram + VOLT_TOL >= MAX_VOLT_LIMIT) { 1668c2ecf20Sopenharmony_ci vsram = MAX_VOLT_LIMIT; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci /* 1698c2ecf20Sopenharmony_ci * If the target Vsram hits the maximum voltage, 1708c2ecf20Sopenharmony_ci * try to set the exact voltage value first. 1718c2ecf20Sopenharmony_ci */ 1728c2ecf20Sopenharmony_ci ret = regulator_set_voltage(sram_reg, vsram, 1738c2ecf20Sopenharmony_ci vsram); 1748c2ecf20Sopenharmony_ci if (ret) 1758c2ecf20Sopenharmony_ci ret = regulator_set_voltage(sram_reg, 1768c2ecf20Sopenharmony_ci vsram - VOLT_TOL, 1778c2ecf20Sopenharmony_ci vsram); 1788c2ecf20Sopenharmony_ci } else { 1798c2ecf20Sopenharmony_ci ret = regulator_set_voltage(sram_reg, vsram, 1808c2ecf20Sopenharmony_ci vsram + VOLT_TOL); 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci if (ret) { 1848c2ecf20Sopenharmony_ci regulator_set_voltage(proc_reg, old_vproc, 1858c2ecf20Sopenharmony_ci old_vproc); 1868c2ecf20Sopenharmony_ci return ret; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci } while (vproc > new_vproc + VOLT_TOL || 1898c2ecf20Sopenharmony_ci vsram > new_vsram + VOLT_TOL); 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return 0; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic int mtk_cpufreq_set_voltage(struct mtk_cpu_dvfs_info *info, int vproc) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci if (info->need_voltage_tracking) 1988c2ecf20Sopenharmony_ci return mtk_cpufreq_voltage_tracking(info, vproc); 1998c2ecf20Sopenharmony_ci else 2008c2ecf20Sopenharmony_ci return regulator_set_voltage(info->proc_reg, vproc, 2018c2ecf20Sopenharmony_ci vproc + VOLT_TOL); 2028c2ecf20Sopenharmony_ci} 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_cistatic int mtk_cpufreq_set_target(struct cpufreq_policy *policy, 2058c2ecf20Sopenharmony_ci unsigned int index) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *freq_table = policy->freq_table; 2088c2ecf20Sopenharmony_ci struct clk *cpu_clk = policy->clk; 2098c2ecf20Sopenharmony_ci struct clk *armpll = clk_get_parent(cpu_clk); 2108c2ecf20Sopenharmony_ci struct mtk_cpu_dvfs_info *info = policy->driver_data; 2118c2ecf20Sopenharmony_ci struct device *cpu_dev = info->cpu_dev; 2128c2ecf20Sopenharmony_ci struct dev_pm_opp *opp; 2138c2ecf20Sopenharmony_ci long freq_hz, old_freq_hz; 2148c2ecf20Sopenharmony_ci int vproc, old_vproc, inter_vproc, target_vproc, ret; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci inter_vproc = info->intermediate_voltage; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci old_freq_hz = clk_get_rate(cpu_clk); 2198c2ecf20Sopenharmony_ci old_vproc = regulator_get_voltage(info->proc_reg); 2208c2ecf20Sopenharmony_ci if (old_vproc < 0) { 2218c2ecf20Sopenharmony_ci pr_err("%s: invalid Vproc value: %d\n", __func__, old_vproc); 2228c2ecf20Sopenharmony_ci return old_vproc; 2238c2ecf20Sopenharmony_ci } 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci freq_hz = freq_table[index].frequency * 1000; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz); 2288c2ecf20Sopenharmony_ci if (IS_ERR(opp)) { 2298c2ecf20Sopenharmony_ci pr_err("cpu%d: failed to find OPP for %ld\n", 2308c2ecf20Sopenharmony_ci policy->cpu, freq_hz); 2318c2ecf20Sopenharmony_ci return PTR_ERR(opp); 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci vproc = dev_pm_opp_get_voltage(opp); 2348c2ecf20Sopenharmony_ci dev_pm_opp_put(opp); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci /* 2378c2ecf20Sopenharmony_ci * If the new voltage or the intermediate voltage is higher than the 2388c2ecf20Sopenharmony_ci * current voltage, scale up voltage first. 2398c2ecf20Sopenharmony_ci */ 2408c2ecf20Sopenharmony_ci target_vproc = (inter_vproc > vproc) ? inter_vproc : vproc; 2418c2ecf20Sopenharmony_ci if (old_vproc < target_vproc) { 2428c2ecf20Sopenharmony_ci ret = mtk_cpufreq_set_voltage(info, target_vproc); 2438c2ecf20Sopenharmony_ci if (ret) { 2448c2ecf20Sopenharmony_ci pr_err("cpu%d: failed to scale up voltage!\n", 2458c2ecf20Sopenharmony_ci policy->cpu); 2468c2ecf20Sopenharmony_ci mtk_cpufreq_set_voltage(info, old_vproc); 2478c2ecf20Sopenharmony_ci return ret; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci /* Reparent the CPU clock to intermediate clock. */ 2528c2ecf20Sopenharmony_ci ret = clk_set_parent(cpu_clk, info->inter_clk); 2538c2ecf20Sopenharmony_ci if (ret) { 2548c2ecf20Sopenharmony_ci pr_err("cpu%d: failed to re-parent cpu clock!\n", 2558c2ecf20Sopenharmony_ci policy->cpu); 2568c2ecf20Sopenharmony_ci mtk_cpufreq_set_voltage(info, old_vproc); 2578c2ecf20Sopenharmony_ci WARN_ON(1); 2588c2ecf20Sopenharmony_ci return ret; 2598c2ecf20Sopenharmony_ci } 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci /* Set the original PLL to target rate. */ 2628c2ecf20Sopenharmony_ci ret = clk_set_rate(armpll, freq_hz); 2638c2ecf20Sopenharmony_ci if (ret) { 2648c2ecf20Sopenharmony_ci pr_err("cpu%d: failed to scale cpu clock rate!\n", 2658c2ecf20Sopenharmony_ci policy->cpu); 2668c2ecf20Sopenharmony_ci clk_set_parent(cpu_clk, armpll); 2678c2ecf20Sopenharmony_ci mtk_cpufreq_set_voltage(info, old_vproc); 2688c2ecf20Sopenharmony_ci return ret; 2698c2ecf20Sopenharmony_ci } 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci /* Set parent of CPU clock back to the original PLL. */ 2728c2ecf20Sopenharmony_ci ret = clk_set_parent(cpu_clk, armpll); 2738c2ecf20Sopenharmony_ci if (ret) { 2748c2ecf20Sopenharmony_ci pr_err("cpu%d: failed to re-parent cpu clock!\n", 2758c2ecf20Sopenharmony_ci policy->cpu); 2768c2ecf20Sopenharmony_ci mtk_cpufreq_set_voltage(info, inter_vproc); 2778c2ecf20Sopenharmony_ci WARN_ON(1); 2788c2ecf20Sopenharmony_ci return ret; 2798c2ecf20Sopenharmony_ci } 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci /* 2828c2ecf20Sopenharmony_ci * If the new voltage is lower than the intermediate voltage or the 2838c2ecf20Sopenharmony_ci * original voltage, scale down to the new voltage. 2848c2ecf20Sopenharmony_ci */ 2858c2ecf20Sopenharmony_ci if (vproc < inter_vproc || vproc < old_vproc) { 2868c2ecf20Sopenharmony_ci ret = mtk_cpufreq_set_voltage(info, vproc); 2878c2ecf20Sopenharmony_ci if (ret) { 2888c2ecf20Sopenharmony_ci pr_err("cpu%d: failed to scale down voltage!\n", 2898c2ecf20Sopenharmony_ci policy->cpu); 2908c2ecf20Sopenharmony_ci clk_set_parent(cpu_clk, info->inter_clk); 2918c2ecf20Sopenharmony_ci clk_set_rate(armpll, old_freq_hz); 2928c2ecf20Sopenharmony_ci clk_set_parent(cpu_clk, armpll); 2938c2ecf20Sopenharmony_ci return ret; 2948c2ecf20Sopenharmony_ci } 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci return 0; 2988c2ecf20Sopenharmony_ci} 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci#define DYNAMIC_POWER "dynamic-power-coefficient" 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_cistatic int mtk_cpu_dvfs_info_init(struct mtk_cpu_dvfs_info *info, int cpu) 3038c2ecf20Sopenharmony_ci{ 3048c2ecf20Sopenharmony_ci struct device *cpu_dev; 3058c2ecf20Sopenharmony_ci struct regulator *proc_reg = ERR_PTR(-ENODEV); 3068c2ecf20Sopenharmony_ci struct regulator *sram_reg = ERR_PTR(-ENODEV); 3078c2ecf20Sopenharmony_ci struct clk *cpu_clk = ERR_PTR(-ENODEV); 3088c2ecf20Sopenharmony_ci struct clk *inter_clk = ERR_PTR(-ENODEV); 3098c2ecf20Sopenharmony_ci struct dev_pm_opp *opp; 3108c2ecf20Sopenharmony_ci unsigned long rate; 3118c2ecf20Sopenharmony_ci int ret; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci cpu_dev = get_cpu_device(cpu); 3148c2ecf20Sopenharmony_ci if (!cpu_dev) { 3158c2ecf20Sopenharmony_ci pr_err("failed to get cpu%d device\n", cpu); 3168c2ecf20Sopenharmony_ci return -ENODEV; 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci cpu_clk = clk_get(cpu_dev, "cpu"); 3208c2ecf20Sopenharmony_ci if (IS_ERR(cpu_clk)) { 3218c2ecf20Sopenharmony_ci if (PTR_ERR(cpu_clk) == -EPROBE_DEFER) 3228c2ecf20Sopenharmony_ci pr_warn("cpu clk for cpu%d not ready, retry.\n", cpu); 3238c2ecf20Sopenharmony_ci else 3248c2ecf20Sopenharmony_ci pr_err("failed to get cpu clk for cpu%d\n", cpu); 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci ret = PTR_ERR(cpu_clk); 3278c2ecf20Sopenharmony_ci return ret; 3288c2ecf20Sopenharmony_ci } 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci inter_clk = clk_get(cpu_dev, "intermediate"); 3318c2ecf20Sopenharmony_ci if (IS_ERR(inter_clk)) { 3328c2ecf20Sopenharmony_ci if (PTR_ERR(inter_clk) == -EPROBE_DEFER) 3338c2ecf20Sopenharmony_ci pr_warn("intermediate clk for cpu%d not ready, retry.\n", 3348c2ecf20Sopenharmony_ci cpu); 3358c2ecf20Sopenharmony_ci else 3368c2ecf20Sopenharmony_ci pr_err("failed to get intermediate clk for cpu%d\n", 3378c2ecf20Sopenharmony_ci cpu); 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci ret = PTR_ERR(inter_clk); 3408c2ecf20Sopenharmony_ci goto out_free_resources; 3418c2ecf20Sopenharmony_ci } 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci proc_reg = regulator_get_optional(cpu_dev, "proc"); 3448c2ecf20Sopenharmony_ci if (IS_ERR(proc_reg)) { 3458c2ecf20Sopenharmony_ci if (PTR_ERR(proc_reg) == -EPROBE_DEFER) 3468c2ecf20Sopenharmony_ci pr_warn("proc regulator for cpu%d not ready, retry.\n", 3478c2ecf20Sopenharmony_ci cpu); 3488c2ecf20Sopenharmony_ci else 3498c2ecf20Sopenharmony_ci pr_err("failed to get proc regulator for cpu%d\n", 3508c2ecf20Sopenharmony_ci cpu); 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci ret = PTR_ERR(proc_reg); 3538c2ecf20Sopenharmony_ci goto out_free_resources; 3548c2ecf20Sopenharmony_ci } 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci /* Both presence and absence of sram regulator are valid cases. */ 3578c2ecf20Sopenharmony_ci sram_reg = regulator_get_exclusive(cpu_dev, "sram"); 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci /* Get OPP-sharing information from "operating-points-v2" bindings */ 3608c2ecf20Sopenharmony_ci ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, &info->cpus); 3618c2ecf20Sopenharmony_ci if (ret) { 3628c2ecf20Sopenharmony_ci pr_err("failed to get OPP-sharing information for cpu%d\n", 3638c2ecf20Sopenharmony_ci cpu); 3648c2ecf20Sopenharmony_ci goto out_free_resources; 3658c2ecf20Sopenharmony_ci } 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci ret = dev_pm_opp_of_cpumask_add_table(&info->cpus); 3688c2ecf20Sopenharmony_ci if (ret) { 3698c2ecf20Sopenharmony_ci pr_warn("no OPP table for cpu%d\n", cpu); 3708c2ecf20Sopenharmony_ci goto out_free_resources; 3718c2ecf20Sopenharmony_ci } 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci /* Search a safe voltage for intermediate frequency. */ 3748c2ecf20Sopenharmony_ci rate = clk_get_rate(inter_clk); 3758c2ecf20Sopenharmony_ci opp = dev_pm_opp_find_freq_ceil(cpu_dev, &rate); 3768c2ecf20Sopenharmony_ci if (IS_ERR(opp)) { 3778c2ecf20Sopenharmony_ci pr_err("failed to get intermediate opp for cpu%d\n", cpu); 3788c2ecf20Sopenharmony_ci ret = PTR_ERR(opp); 3798c2ecf20Sopenharmony_ci goto out_free_opp_table; 3808c2ecf20Sopenharmony_ci } 3818c2ecf20Sopenharmony_ci info->intermediate_voltage = dev_pm_opp_get_voltage(opp); 3828c2ecf20Sopenharmony_ci dev_pm_opp_put(opp); 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci info->cpu_dev = cpu_dev; 3858c2ecf20Sopenharmony_ci info->proc_reg = proc_reg; 3868c2ecf20Sopenharmony_ci info->sram_reg = IS_ERR(sram_reg) ? NULL : sram_reg; 3878c2ecf20Sopenharmony_ci info->cpu_clk = cpu_clk; 3888c2ecf20Sopenharmony_ci info->inter_clk = inter_clk; 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci /* 3918c2ecf20Sopenharmony_ci * If SRAM regulator is present, software "voltage tracking" is needed 3928c2ecf20Sopenharmony_ci * for this CPU power domain. 3938c2ecf20Sopenharmony_ci */ 3948c2ecf20Sopenharmony_ci info->need_voltage_tracking = !IS_ERR(sram_reg); 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci return 0; 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ciout_free_opp_table: 3998c2ecf20Sopenharmony_ci dev_pm_opp_of_cpumask_remove_table(&info->cpus); 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ciout_free_resources: 4028c2ecf20Sopenharmony_ci if (!IS_ERR(proc_reg)) 4038c2ecf20Sopenharmony_ci regulator_put(proc_reg); 4048c2ecf20Sopenharmony_ci if (!IS_ERR(sram_reg)) 4058c2ecf20Sopenharmony_ci regulator_put(sram_reg); 4068c2ecf20Sopenharmony_ci if (!IS_ERR(cpu_clk)) 4078c2ecf20Sopenharmony_ci clk_put(cpu_clk); 4088c2ecf20Sopenharmony_ci if (!IS_ERR(inter_clk)) 4098c2ecf20Sopenharmony_ci clk_put(inter_clk); 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci return ret; 4128c2ecf20Sopenharmony_ci} 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_cistatic void mtk_cpu_dvfs_info_release(struct mtk_cpu_dvfs_info *info) 4158c2ecf20Sopenharmony_ci{ 4168c2ecf20Sopenharmony_ci if (!IS_ERR(info->proc_reg)) 4178c2ecf20Sopenharmony_ci regulator_put(info->proc_reg); 4188c2ecf20Sopenharmony_ci if (!IS_ERR(info->sram_reg)) 4198c2ecf20Sopenharmony_ci regulator_put(info->sram_reg); 4208c2ecf20Sopenharmony_ci if (!IS_ERR(info->cpu_clk)) 4218c2ecf20Sopenharmony_ci clk_put(info->cpu_clk); 4228c2ecf20Sopenharmony_ci if (!IS_ERR(info->inter_clk)) 4238c2ecf20Sopenharmony_ci clk_put(info->inter_clk); 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci dev_pm_opp_of_cpumask_remove_table(&info->cpus); 4268c2ecf20Sopenharmony_ci} 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_cistatic int mtk_cpufreq_init(struct cpufreq_policy *policy) 4298c2ecf20Sopenharmony_ci{ 4308c2ecf20Sopenharmony_ci struct mtk_cpu_dvfs_info *info; 4318c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *freq_table; 4328c2ecf20Sopenharmony_ci int ret; 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_ci info = mtk_cpu_dvfs_info_lookup(policy->cpu); 4358c2ecf20Sopenharmony_ci if (!info) { 4368c2ecf20Sopenharmony_ci pr_err("dvfs info for cpu%d is not initialized.\n", 4378c2ecf20Sopenharmony_ci policy->cpu); 4388c2ecf20Sopenharmony_ci return -EINVAL; 4398c2ecf20Sopenharmony_ci } 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci ret = dev_pm_opp_init_cpufreq_table(info->cpu_dev, &freq_table); 4428c2ecf20Sopenharmony_ci if (ret) { 4438c2ecf20Sopenharmony_ci pr_err("failed to init cpufreq table for cpu%d: %d\n", 4448c2ecf20Sopenharmony_ci policy->cpu, ret); 4458c2ecf20Sopenharmony_ci return ret; 4468c2ecf20Sopenharmony_ci } 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci cpumask_copy(policy->cpus, &info->cpus); 4498c2ecf20Sopenharmony_ci policy->freq_table = freq_table; 4508c2ecf20Sopenharmony_ci policy->driver_data = info; 4518c2ecf20Sopenharmony_ci policy->clk = info->cpu_clk; 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci dev_pm_opp_of_register_em(info->cpu_dev, policy->cpus); 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci return 0; 4568c2ecf20Sopenharmony_ci} 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_cistatic int mtk_cpufreq_exit(struct cpufreq_policy *policy) 4598c2ecf20Sopenharmony_ci{ 4608c2ecf20Sopenharmony_ci struct mtk_cpu_dvfs_info *info = policy->driver_data; 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci dev_pm_opp_free_cpufreq_table(info->cpu_dev, &policy->freq_table); 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci return 0; 4658c2ecf20Sopenharmony_ci} 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_cistatic struct cpufreq_driver mtk_cpufreq_driver = { 4688c2ecf20Sopenharmony_ci .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK | 4698c2ecf20Sopenharmony_ci CPUFREQ_HAVE_GOVERNOR_PER_POLICY | 4708c2ecf20Sopenharmony_ci CPUFREQ_IS_COOLING_DEV, 4718c2ecf20Sopenharmony_ci .verify = cpufreq_generic_frequency_table_verify, 4728c2ecf20Sopenharmony_ci .target_index = mtk_cpufreq_set_target, 4738c2ecf20Sopenharmony_ci .get = cpufreq_generic_get, 4748c2ecf20Sopenharmony_ci .init = mtk_cpufreq_init, 4758c2ecf20Sopenharmony_ci .exit = mtk_cpufreq_exit, 4768c2ecf20Sopenharmony_ci .name = "mtk-cpufreq", 4778c2ecf20Sopenharmony_ci .attr = cpufreq_generic_attr, 4788c2ecf20Sopenharmony_ci}; 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_cistatic int mtk_cpufreq_probe(struct platform_device *pdev) 4818c2ecf20Sopenharmony_ci{ 4828c2ecf20Sopenharmony_ci struct mtk_cpu_dvfs_info *info, *tmp; 4838c2ecf20Sopenharmony_ci int cpu, ret; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci for_each_possible_cpu(cpu) { 4868c2ecf20Sopenharmony_ci info = mtk_cpu_dvfs_info_lookup(cpu); 4878c2ecf20Sopenharmony_ci if (info) 4888c2ecf20Sopenharmony_ci continue; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 4918c2ecf20Sopenharmony_ci if (!info) { 4928c2ecf20Sopenharmony_ci ret = -ENOMEM; 4938c2ecf20Sopenharmony_ci goto release_dvfs_info_list; 4948c2ecf20Sopenharmony_ci } 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_ci ret = mtk_cpu_dvfs_info_init(info, cpu); 4978c2ecf20Sopenharmony_ci if (ret) { 4988c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 4998c2ecf20Sopenharmony_ci "failed to initialize dvfs info for cpu%d\n", 5008c2ecf20Sopenharmony_ci cpu); 5018c2ecf20Sopenharmony_ci goto release_dvfs_info_list; 5028c2ecf20Sopenharmony_ci } 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci list_add(&info->list_head, &dvfs_info_list); 5058c2ecf20Sopenharmony_ci } 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci ret = cpufreq_register_driver(&mtk_cpufreq_driver); 5088c2ecf20Sopenharmony_ci if (ret) { 5098c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to register mtk cpufreq driver\n"); 5108c2ecf20Sopenharmony_ci goto release_dvfs_info_list; 5118c2ecf20Sopenharmony_ci } 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci return 0; 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_cirelease_dvfs_info_list: 5168c2ecf20Sopenharmony_ci list_for_each_entry_safe(info, tmp, &dvfs_info_list, list_head) { 5178c2ecf20Sopenharmony_ci mtk_cpu_dvfs_info_release(info); 5188c2ecf20Sopenharmony_ci list_del(&info->list_head); 5198c2ecf20Sopenharmony_ci } 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci return ret; 5228c2ecf20Sopenharmony_ci} 5238c2ecf20Sopenharmony_ci 5248c2ecf20Sopenharmony_cistatic struct platform_driver mtk_cpufreq_platdrv = { 5258c2ecf20Sopenharmony_ci .driver = { 5268c2ecf20Sopenharmony_ci .name = "mtk-cpufreq", 5278c2ecf20Sopenharmony_ci }, 5288c2ecf20Sopenharmony_ci .probe = mtk_cpufreq_probe, 5298c2ecf20Sopenharmony_ci}; 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_ci/* List of machines supported by this driver */ 5328c2ecf20Sopenharmony_cistatic const struct of_device_id mtk_cpufreq_machines[] __initconst = { 5338c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt2701", }, 5348c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt2712", }, 5358c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt7622", }, 5368c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt7623", }, 5378c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt817x", }, 5388c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt8173", }, 5398c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt8176", }, 5408c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt8183", }, 5418c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt8516", }, 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci { } 5448c2ecf20Sopenharmony_ci}; 5458c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtk_cpufreq_machines); 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_cistatic int __init mtk_cpufreq_driver_init(void) 5488c2ecf20Sopenharmony_ci{ 5498c2ecf20Sopenharmony_ci struct device_node *np; 5508c2ecf20Sopenharmony_ci const struct of_device_id *match; 5518c2ecf20Sopenharmony_ci int err; 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci np = of_find_node_by_path("/"); 5548c2ecf20Sopenharmony_ci if (!np) 5558c2ecf20Sopenharmony_ci return -ENODEV; 5568c2ecf20Sopenharmony_ci 5578c2ecf20Sopenharmony_ci match = of_match_node(mtk_cpufreq_machines, np); 5588c2ecf20Sopenharmony_ci of_node_put(np); 5598c2ecf20Sopenharmony_ci if (!match) { 5608c2ecf20Sopenharmony_ci pr_debug("Machine is not compatible with mtk-cpufreq\n"); 5618c2ecf20Sopenharmony_ci return -ENODEV; 5628c2ecf20Sopenharmony_ci } 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci err = platform_driver_register(&mtk_cpufreq_platdrv); 5658c2ecf20Sopenharmony_ci if (err) 5668c2ecf20Sopenharmony_ci return err; 5678c2ecf20Sopenharmony_ci 5688c2ecf20Sopenharmony_ci /* 5698c2ecf20Sopenharmony_ci * Since there's no place to hold device registration code and no 5708c2ecf20Sopenharmony_ci * device tree based way to match cpufreq driver yet, both the driver 5718c2ecf20Sopenharmony_ci * and the device registration codes are put here to handle defer 5728c2ecf20Sopenharmony_ci * probing. 5738c2ecf20Sopenharmony_ci */ 5748c2ecf20Sopenharmony_ci cpufreq_pdev = platform_device_register_simple("mtk-cpufreq", -1, NULL, 0); 5758c2ecf20Sopenharmony_ci if (IS_ERR(cpufreq_pdev)) { 5768c2ecf20Sopenharmony_ci pr_err("failed to register mtk-cpufreq platform device\n"); 5778c2ecf20Sopenharmony_ci platform_driver_unregister(&mtk_cpufreq_platdrv); 5788c2ecf20Sopenharmony_ci return PTR_ERR(cpufreq_pdev); 5798c2ecf20Sopenharmony_ci } 5808c2ecf20Sopenharmony_ci 5818c2ecf20Sopenharmony_ci return 0; 5828c2ecf20Sopenharmony_ci} 5838c2ecf20Sopenharmony_cimodule_init(mtk_cpufreq_driver_init) 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_cistatic void __exit mtk_cpufreq_driver_exit(void) 5868c2ecf20Sopenharmony_ci{ 5878c2ecf20Sopenharmony_ci platform_device_unregister(cpufreq_pdev); 5888c2ecf20Sopenharmony_ci platform_driver_unregister(&mtk_cpufreq_platdrv); 5898c2ecf20Sopenharmony_ci} 5908c2ecf20Sopenharmony_cimodule_exit(mtk_cpufreq_driver_exit) 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MediaTek CPUFreq driver"); 5938c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pi-Cheng Chen <pi-cheng.chen@linaro.org>"); 5948c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 595