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