[PATCH 1/2] cpufreq: mediatek: Add MT8173 cpufreq driver

pi-cheng.chen pi-cheng.chen at linaro.org
Mon Apr 20 02:27:26 PDT 2015


This patch implements MT8173 specific cpufreq driver with OPP table defined
in the driver code.

Signed-off-by: pi-cheng.chen <pi-cheng.chen at linaro.org>
---
 drivers/cpufreq/Kconfig.arm      |   6 +
 drivers/cpufreq/Makefile         |   1 +
 drivers/cpufreq/mt8173-cpufreq.c | 509 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 516 insertions(+)
 create mode 100644 drivers/cpufreq/mt8173-cpufreq.c

diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 1b06fc4..25643c7 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -132,6 +132,12 @@ config ARM_KIRKWOOD_CPUFREQ
 	  This adds the CPUFreq driver for Marvell Kirkwood
 	  SoCs.
 
+config ARM_MT8173_CPUFREQ
+	bool "Mediatek MT8173 CPUFreq support"
+	depends on ARCH_MEDIATEK && REGULATOR
+	help
+	  This adds the CPUFreq driver support for Mediatek MT8173 SoC.
+
 config ARM_OMAP2PLUS_CPUFREQ
 	bool "TI OMAP2+"
 	depends on ARCH_OMAP2PLUS
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 82a1821..da9d616 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ)	+= highbank-cpufreq.o
 obj-$(CONFIG_ARM_IMX6Q_CPUFREQ)		+= imx6q-cpufreq.o
 obj-$(CONFIG_ARM_INTEGRATOR)		+= integrator-cpufreq.o
 obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ)	+= kirkwood-cpufreq.o
+obj-$(CONFIG_ARM_MT8173_CPUFREQ)	+= mt8173-cpufreq.o
 obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ)	+= omap-cpufreq.o
 obj-$(CONFIG_ARM_PXA2xx_CPUFREQ)	+= pxa2xx-cpufreq.o
 obj-$(CONFIG_PXA3xx)			+= pxa3xx-cpufreq.o
diff --git a/drivers/cpufreq/mt8173-cpufreq.c b/drivers/cpufreq/mt8173-cpufreq.c
new file mode 100644
index 0000000..a310e72
--- /dev/null
+++ b/drivers/cpufreq/mt8173-cpufreq.c
@@ -0,0 +1,509 @@
+/*
+* Copyright (c) 2015 Linaro Ltd.
+* Author: Pi-Cheng Chen <pi-cheng.chen at linaro.org>
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License version 2 as
+* published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*/
+
+#include <linux/clk.h>
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define MIN_VOLT_SHIFT		100000
+#define MAX_VOLT_SHIFT		200000
+
+#define OPP(f, vp, vs) {		\
+		.freq	= f,		\
+		.vproc	= vp,		\
+		.vsram	= vs,		\
+	}
+
+struct mtk_cpu_opp {
+	unsigned int freq;
+	int vproc;
+	int vsram;
+};
+
+/*
+ * The struct cpu_dvfs_info holds necessary information for doing CPU DVFS of
+ * each cluster. For Mediatek SoCs, each CPU cluster in SoC has two voltage
+ * inputs, Vproc and Vsram. For some cluster in SoC, the two voltage inputs are
+ * supplied by different PMICs. In this case, when scaling up/down the voltage
+ * of Vsram and Vproc, the two voltage inputs need to be controlled under a
+ * hardware limitation: 100mV < Vsram - Vproc < 200mV
+ * When scaling up/down the clock frequency of a cluster, the clock source need
+ * to be switched to another stable PLL clock temporarily, and switched back to
+ * the original PLL after the it becomes stable at target frequency.
+ * Hence the voltage inputs of cluster need to be set to an intermediate voltage
+ * before the clock frequency being scaled up/down.
+ */
+
+struct cpu_dvfs_info {
+	cpumask_t cpus;
+
+	struct mtk_cpu_opp *opp_tbl;
+	struct mtk_cpu_opp *intermediate_opp;
+	int nr_opp;
+
+	struct regulator *proc_reg;
+	struct regulator *sram_reg;
+	struct clk *cpu_clk;
+	struct clk *inter_pll;
+};
+
+/*
+ * This is a temporary solution until we have new OPPv2 bindings. Therefore we
+ * could describe the OPPs with (freq, volt, volt) tuple properly in device
+ * tree.
+ */
+
+/* OPP table for LITTLE cores of MT8173 */
+struct mtk_cpu_opp mt8173_l_opp[] = {
+	OPP(507000000, 859000, 0),
+	OPP(702000000, 908000, 0),
+	OPP(1001000000, 983000, 0),
+	OPP(1105000000, 1009000, 0),
+	OPP(1183000000, 1028000, 0),
+	OPP(1404000000, 1083000, 0),
+	OPP(1508000000, 1109000, 0),
+	OPP(1573000000, 1125000, 0),
+};
+
+/* OPP table for big cores of MT8173 */
+struct mtk_cpu_opp mt8173_b_opp[] = {
+	OPP(507000000, 828000, 928000),
+	OPP(702000000, 867000, 967000),
+	OPP(1001000000, 927000, 1027000),
+	OPP(1209000000, 968000, 1068000),
+	OPP(1404000000, 1007000, 1107000),
+	OPP(1612000000, 1049000, 1149000),
+	OPP(1807000000, 1089000, 1150000),
+	OPP(1989000000, 1125000, 1150000),
+};
+
+static inline int need_voltage_trace(struct cpu_dvfs_info *info)
+{
+	return (!IS_ERR_OR_NULL(info->proc_reg) &&
+		!IS_ERR_OR_NULL(info->sram_reg));
+}
+
+static struct mtk_cpu_opp *cpu_opp_find_freq_ceil(struct mtk_cpu_opp *opp_tbl,
+						  int nr_opp,
+						  unsigned long rate)
+{
+	int i;
+
+	for (i = 0; i < nr_opp; i++)
+		if (opp_tbl[i].freq >= rate)
+			return &opp_tbl[i];
+
+	return NULL;
+}
+
+/*
+ * Query the exact voltage value that is largest previous to the input voltage
+ * value supported by the regulator
+ */
+static int get_regulator_voltage_ceil(struct regulator *regulator, int voltage)
+{
+	int cnt, i, volt = -1;
+
+	if (IS_ERR_OR_NULL(regulator))
+		return -EINVAL;
+
+	cnt = regulator_count_voltages(regulator);
+	for (i = 0; i < cnt && volt < voltage; i++)
+		volt = regulator_list_voltage(regulator, i);
+
+	return (i >= cnt) ? -EINVAL : volt;
+}
+
+/*
+ * Query the exact voltage value that is smallest following to the input voltage
+ * value supported by the regulator
+ */
+static int get_regulator_voltage_floor(struct regulator *regulator, int voltage)
+{
+	int cnt, i, volt = -1;
+
+	if (IS_ERR_OR_NULL(regulator))
+		return -EINVAL;
+
+	cnt = regulator_count_voltages(regulator);
+	/* skip all trailing 0s in the list of supported voltages */
+	for (i = cnt - 1; i >= 0 && volt <= 0; i--)
+		volt = regulator_list_voltage(regulator, i);
+
+	for (; i >= 0; i--) {
+		volt = regulator_list_voltage(regulator, i);
+		if (volt <= voltage)
+			return volt;
+	}
+
+	return -EINVAL;
+}
+
+static int mtk_cpufreq_voltage_trace(struct cpu_dvfs_info *info,
+				     struct mtk_cpu_opp *opp)
+{
+	struct regulator *proc_reg = info->proc_reg;
+	struct regulator *sram_reg = info->sram_reg;
+	int old_vproc, new_vproc, old_vsram, new_vsram, vsram, vproc, ret;
+
+	old_vproc = regulator_get_voltage(proc_reg);
+	old_vsram = regulator_get_voltage(sram_reg);
+
+	new_vproc = opp->vproc;
+	new_vsram = opp->vsram;
+
+	/*
+	 * In the case the voltage is going to be scaled up, Vsram and Vproc
+	 * need to be scaled up step by step. In each step, Vsram needs to be
+	 * set to (Vproc + 200mV) first, then Vproc is set to (Vsram - 100mV).
+	 * Repeat the step until Vsram and Vproc are set to target voltage.
+	 */
+	if (old_vproc < new_vproc) {
+next_up_step:
+		old_vsram = regulator_get_voltage(sram_reg);
+
+		vsram = (new_vsram - old_vproc < MAX_VOLT_SHIFT) ?
+			new_vsram : old_vproc + MAX_VOLT_SHIFT;
+		vsram = get_regulator_voltage_floor(sram_reg, vsram);
+
+		ret = regulator_set_voltage(sram_reg, vsram, vsram);
+		if (ret)
+			return ret;
+
+		vproc = (new_vsram == vsram) ?
+			new_vproc : vsram - MIN_VOLT_SHIFT;
+		vproc = get_regulator_voltage_ceil(proc_reg, vproc);
+
+		ret = regulator_set_voltage(proc_reg, vproc, vproc);
+		if (ret) {
+			regulator_set_voltage(sram_reg, old_vsram, old_vsram);
+			return ret;
+		}
+
+		if (new_vproc == vproc && new_vsram == vsram)
+			return 0;
+
+		old_vproc = vproc;
+		goto next_up_step;
+
+	/*
+	 * In the case the voltage is going to be scaled down, Vsram and Vproc
+	 * need to be scaled down step by step. In each step, Vproc needs to be
+	 * set to (Vsram - 200mV) first, then Vproc is set to (Vproc + 100mV).
+	 * Repeat the step until Vsram and Vproc are set to target voltage.
+	 */
+	} else if (old_vproc > new_vproc) {
+next_down_step:
+		old_vproc = regulator_get_voltage(proc_reg);
+
+		vproc = (old_vsram - new_vproc < MAX_VOLT_SHIFT) ?
+			new_vproc : old_vsram - MAX_VOLT_SHIFT;
+		vproc = get_regulator_voltage_ceil(proc_reg, vproc);
+
+		ret = regulator_set_voltage(proc_reg, vproc, vproc);
+		if (ret)
+			return ret;
+
+		vsram = (new_vproc == vproc) ?
+			new_vsram : vproc + MIN_VOLT_SHIFT;
+		vsram = get_regulator_voltage_floor(sram_reg, vsram);
+
+		ret = regulator_set_voltage(sram_reg, vsram, vsram);
+		if (ret) {
+			regulator_set_voltage(proc_reg, old_vproc, old_vproc);
+			return ret;
+		}
+
+		if (new_vproc == vproc && new_vsram == vsram)
+			return 0;
+
+		old_vsram = vsram;
+		goto next_down_step;
+	}
+
+	WARN_ON(1);
+	return 0;
+}
+
+static int mt8173_cpufreq_set_voltage(struct cpu_dvfs_info *info,
+				      struct mtk_cpu_opp *opp)
+{
+	if (need_voltage_trace(info))
+		return mtk_cpufreq_voltage_trace(info, opp);
+	else
+		return regulator_set_voltage(info->proc_reg, opp->vproc,
+					     opp->vproc);
+}
+
+static int mt8173_cpufreq_set_target(struct cpufreq_policy *policy,
+				     unsigned int index)
+{
+	struct cpufreq_frequency_table *freq_table = policy->freq_table;
+	struct clk *cpu_clk = policy->clk;
+	struct clk *armpll = clk_get_parent(cpu_clk);
+	struct cpu_dvfs_info *info;
+	struct mtk_cpu_opp *new_opp, *target_opp, *inter_opp, *orig_opp;
+	long freq_hz, orig_freq_hz;
+	int old_vproc, ret;
+
+	info = (struct cpu_dvfs_info *)policy->driver_data;
+	inter_opp = info->intermediate_opp;
+	orig_freq_hz = clk_get_rate(cpu_clk);
+	orig_opp = cpu_opp_find_freq_ceil(info->opp_tbl, info->nr_opp,
+					  orig_freq_hz);
+	if (!orig_opp)
+		return -EINVAL;
+
+	old_vproc = regulator_get_voltage(info->proc_reg);
+	freq_hz = freq_table[index].frequency * 1000;
+	new_opp = cpu_opp_find_freq_ceil(info->opp_tbl, info->nr_opp, freq_hz);
+	target_opp = new_opp;
+
+	if (!new_opp)
+		return -EINVAL;
+
+	if (target_opp->vproc < inter_opp->vproc)
+		target_opp = info->intermediate_opp;
+
+	if (old_vproc < target_opp->vproc) {
+		ret = mt8173_cpufreq_set_voltage(info, target_opp);
+		if (ret) {
+			pr_err("cpu%d: failed to scale up voltage!\n",
+			       policy->cpu);
+			mt8173_cpufreq_set_voltage(info, orig_opp);
+			return ret;
+		}
+	}
+
+	ret = clk_set_parent(cpu_clk, info->inter_pll);
+	if (ret) {
+		pr_err("cpu%d: failed to re-parent cpu clock!\n",
+		       policy->cpu);
+		mt8173_cpufreq_set_voltage(info, orig_opp);
+		WARN_ON(1);
+		return ret;
+	}
+
+	ret = clk_set_rate(armpll, freq_hz);
+	if (ret) {
+		pr_err("cpu%d: failed to scale cpu clock rate!\n",
+		       policy->cpu);
+		clk_set_parent(cpu_clk, armpll);
+		mt8173_cpufreq_set_voltage(info, orig_opp);
+		return ret;
+	}
+
+	ret = clk_set_parent(cpu_clk, armpll);
+	if (ret) {
+		pr_err("cpu%d: failed to re-parent cpu clock!\n",
+		       policy->cpu);
+		mt8173_cpufreq_set_voltage(info, inter_opp);
+		WARN_ON(1);
+		return ret;
+	}
+
+	if (new_opp->vproc < inter_opp->vproc) {
+		ret = mt8173_cpufreq_set_voltage(info, new_opp);
+		if (ret) {
+			pr_err("cpu%d: failed to scale down voltage!\n",
+			       policy->cpu);
+			clk_set_parent(cpu_clk, info->inter_pll);
+			clk_set_rate(armpll, orig_freq_hz);
+			clk_set_parent(cpu_clk, armpll);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int mt8173_cpufreq_cpu_opp_fixup(struct cpu_dvfs_info *info)
+{
+	struct mtk_cpu_opp *opp_tbl = info->opp_tbl;
+	struct regulator *proc_reg = info->proc_reg;
+	struct regulator *sram_reg = info->sram_reg;
+	int vproc, vsram, i;
+
+	for (i = 0; i < info->nr_opp; i++) {
+		vproc = opp_tbl[i].vproc;
+		vsram = opp_tbl[i].vsram;
+
+		vproc = get_regulator_voltage_ceil(proc_reg, vproc);
+
+		if (!IS_ERR_OR_NULL(sram_reg))
+			vsram = get_regulator_voltage_ceil(sram_reg, vsram);
+
+		if (vproc < 0 || (!IS_ERR_OR_NULL(sram_reg) && vsram < 0)) {
+			pr_err("%s: Failed to get voltage setting of OPPs\n",
+			       __func__);
+			return -EINVAL;
+		}
+
+		opp_tbl[i].vproc = vproc;
+		opp_tbl[i].vsram = vsram;
+	}
+
+	return 0;
+}
+
+static int mt8173_cpufreq_dvfs_init(struct cpu_dvfs_info *info)
+{
+	struct device *cpu_dev;
+	struct regulator *proc_reg, *sram_reg;
+	struct clk *cpu_clk, *inter_pll;
+	unsigned long rate;
+	int cpu, ret;
+
+	cpu = cpumask_first(&info->cpus);
+
+try_next_cpu:
+	cpu_dev = get_cpu_device(cpu);
+	proc_reg = regulator_get_exclusive(cpu_dev, "proc");
+	sram_reg = regulator_get_exclusive(cpu_dev, "sram");
+	cpu_clk = clk_get(cpu_dev, "cpu");
+	inter_pll = clk_get(cpu_dev, "intermediate");
+
+	if (IS_ERR_OR_NULL(proc_reg) || IS_ERR_OR_NULL(cpu_clk) ||
+	    IS_ERR_OR_NULL(inter_pll)) {
+		cpu = cpumask_next(cpu, &info->cpus);
+		if (cpu >= nr_cpu_ids)
+			return -ENODEV;
+
+		goto try_next_cpu;
+	}
+
+	/* Both PROC and SRAM regulators are present. This is a big
+	 * cluster, and needs to do voltage tracing. */
+	if (!(IS_ERR_OR_NULL(proc_reg) || IS_ERR_OR_NULL(sram_reg))) {
+		info->opp_tbl = mt8173_b_opp;
+		info->nr_opp = sizeof(mt8173_b_opp) / sizeof(mt8173_b_opp[0]);
+	} else {
+		info->opp_tbl = mt8173_l_opp;
+		info->nr_opp = sizeof(mt8173_l_opp) / sizeof(mt8173_l_opp[0]);
+	}
+
+	info->proc_reg = proc_reg;
+	info->sram_reg = sram_reg;
+	info->cpu_clk = cpu_clk;
+	info->inter_pll = inter_pll;
+
+	ret = mt8173_cpufreq_cpu_opp_fixup(info);
+	if (ret) {
+		pr_err("%s: Failed to fixup opp table: %d\n", __func__, ret);
+		return ret;
+	}
+
+	rate = clk_get_rate(info->inter_pll);
+	info->intermediate_opp = cpu_opp_find_freq_ceil(info->opp_tbl,
+							info->nr_opp,
+							rate);
+	if (!info->intermediate_opp) {
+		pr_err("%s: Failed to setup intermediate opp\n", __func__);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void mt8173_cpufreq_dvfs_release(struct cpu_dvfs_info *info)
+{
+	regulator_put(info->proc_reg);
+	regulator_put(info->sram_reg);
+	clk_put(info->cpu_clk);
+	clk_put(info->inter_pll);
+
+	kfree(info);
+}
+
+static int mt8173_cpufreq_init(struct cpufreq_policy *policy)
+{
+	struct cpu_dvfs_info *info;
+	struct cpufreq_frequency_table *freq_table;
+	int ret, i;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	cpumask_copy(&info->cpus, &cpu_topology[policy->cpu].core_sibling);
+	ret = mt8173_cpufreq_dvfs_init(info);
+	if (ret) {
+		pr_err("%s: Failed to initialize DVFS info: %d\n", __func__,
+		       ret);
+		goto out_dvfs_release;
+	}
+
+	freq_table = kcalloc(info->nr_opp, sizeof(*freq_table), GFP_KERNEL);
+	if (!freq_table) {
+		ret = -ENOMEM;
+		goto out_dvfs_release;
+	}
+
+	for (i = 0; i < info->nr_opp; i++)
+		freq_table[i].frequency = info->opp_tbl[i].freq / 1000;
+
+	freq_table[i].frequency = CPUFREQ_TABLE_END;
+
+	ret = cpufreq_table_validate_and_show(policy, freq_table);
+	if (ret) {
+		pr_err("%s: invalid frequency table: %d\n", __func__, ret);
+		goto out_free_freq_table;
+	}
+
+	cpumask_copy(policy->cpus, &cpu_topology[policy->cpu].core_sibling);
+	policy->driver_data = info;
+	policy->clk = info->cpu_clk;
+
+	return 0;
+
+out_free_freq_table:
+	kfree(freq_table);
+out_dvfs_release:
+	mt8173_cpufreq_dvfs_release(info);
+	return ret;
+}
+
+static int mt8173_cpufreq_exit(struct cpufreq_policy *policy)
+{
+	kfree(&policy->freq_table);
+	policy->freq_table = NULL;
+
+	return 0;
+}
+
+static struct cpufreq_driver mt8173_cpufreq_driver = {
+	.flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK,
+	.verify = cpufreq_generic_frequency_table_verify,
+	.target_index = mt8173_cpufreq_set_target,
+	.get = cpufreq_generic_get,
+	.init = mt8173_cpufreq_init,
+	.exit = mt8173_cpufreq_exit,
+	.name = "mt8173-cpufreq",
+	.attr = cpufreq_generic_attr,
+};
+
+static int mt8173_cpufreq_driver_init(void)
+{
+	if (!of_machine_is_compatible("mediatek,mt8173"))
+		return -ENODEV;
+
+	return cpufreq_register_driver(&mt8173_cpufreq_driver);
+}
+
+module_init(mt8173_cpufreq_driver_init);
-- 
1.9.1




More information about the linux-arm-kernel mailing list