[PATCH 2/2] cpufreq: add cpufreq driver for Mediatek MT8173 SoC

Yingjoe Chen yingjoe.chen at mediatek.com
Tue Jan 13 23:43:39 PST 2015


Hi,

On Fri, 2015-01-09 at 17:54 +0800, pi-cheng.chen wrote:
> When doing DVFS on MT8173 SoC, 2 rules should be followed to make sure the
> SoC works properly:
> 1. When doing frequency scaling of CPUs, the clock source should be switched
>    to another PLL, then switched back to the orignal until it's stable.
> 2. When doing voltage scaling of CA57 cluster, Vproc and Vsram need to be
>    controlled concurrently and 2 limitations should be followed:
>    a. Vsram > Vproc
>    b. Vsram - Vproc < 200 mV

It seems to me this is not much different then other mtk big little
SoCs. Is it possible to aim to support both mt8173 & mt8135 with this
driver?

> To address these needs, we reuse cpufreq-dt but do voltage scaling in the
> cpufreq notifier.
> 
> 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 | 459 +++++++++++++++++++++++++++++++++++++++
>  3 files changed, 466 insertions(+)
>  create mode 100644 drivers/cpufreq/mt8173-cpufreq.c
> 
> diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
> index 0f9a2c3..97ed7dd 100644
> --- a/drivers/cpufreq/Kconfig.arm
> +++ b/drivers/cpufreq/Kconfig.arm
> @@ -255,3 +255,9 @@ config ARM_PXA2xx_CPUFREQ
>  	  This add the CPUFreq driver support for Intel PXA2xx SOCs.
>  
>  	  If in doubt, say N.
> +
> +config ARM_MT8173_CPUFREQ
> +	bool "Mediatek MT8173 CPUFreq support"
> +	depends on ARCH_MEDIATEK && CPUFREQ_DT && REGULATOR
> +	help
> +	  This adds the CPUFreq driver support for Mediatek MT8173 SoC.
> diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
> index b3ca7b0..67b7f17 100644
> --- a/drivers/cpufreq/Makefile
> +++ b/drivers/cpufreq/Makefile
> @@ -76,6 +76,7 @@ obj-$(CONFIG_ARM_SA1110_CPUFREQ)	+= sa1110-cpufreq.o
>  obj-$(CONFIG_ARM_SPEAR_CPUFREQ)		+= spear-cpufreq.o
>  obj-$(CONFIG_ARM_TEGRA_CPUFREQ)		+= tegra-cpufreq.o
>  obj-$(CONFIG_ARM_VEXPRESS_SPC_CPUFREQ)	+= vexpress-spc-cpufreq.o
> +obj-$(CONFIG_ARM_MT8173_CPUFREQ)	+= mt8173-cpufreq.o
>  
>  ##################################################################################
>  # PowerPC platform drivers
> diff --git a/drivers/cpufreq/mt8173-cpufreq.c b/drivers/cpufreq/mt8173-cpufreq.c
> new file mode 100644
> index 0000000..b578c10
> --- /dev/null
> +++ b/drivers/cpufreq/mt8173-cpufreq.c
> @@ -0,0 +1,459 @@
> +/*
> +* Copyright (c) 2015 Linaro.
> +* 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/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/cpufreq-dt.h>
> +#include <linux/cpumask.h>
> +#include <linux/slab.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/cpu.h>
> +#include <linux/pm_opp.h>
> +
> +#define CPUCLK_MUX_ARMPLL	0x1
> +#define CPUCLK_MUX_MAINPLL	0x2
> +#define VOLT_SHIFT_LIMIT	150000
> +
> +enum {
> +	LITTLE_CLUSTER = 0,
> +	BIG_CLUSTER,
> +	NR_CLUSTERS
> +};
> +
> +static struct cluster_dvfs_info {
> +	int cpu;
> +	struct cpumask cpus;
> +	struct device *cpu_dev;
> +	struct regulator *proc_reg;
> +	struct regulator *sram_reg;
> +	int inter_volt;
> +	u32 volt_tol;
> +} *dvfs_info;
> +
> +static unsigned long mainpll_freq;
> +static void __iomem *clk_mux_regs;
> +static spinlock_t lock;
> +
> +static void cpuclk_mux_set(int cluster, u32 sel)
> +{
> +	u32 val;
> +	u32 mask = 0x3;
> +
> +	if (cluster == BIG_CLUSTER) {
> +		mask <<= 2;
> +		sel <<= 2;
> +	}
> +
> +	spin_lock(&lock);
> +
> +	val = readl(clk_mux_regs);
> +	val = (val & ~mask) | sel;
> +	writel(val, clk_mux_regs);
> +
> +	spin_unlock(&lock);
> +}
> +
> +static int mt8173_cpufreq_voltage_step(struct cluster_dvfs_info *dvfs,
> +				       unsigned long old_vproc,
> +				       unsigned long new_vproc)
> +{
> +	int cur_vsram, cur_vproc, target_volt, tol;
> +
> +	if (old_vproc > new_vproc) {
> +		while (1) {
> +			cur_vsram = regulator_get_voltage(dvfs->sram_reg);
> +			if (cur_vsram - new_vproc < VOLT_SHIFT_LIMIT &&
> +			    cur_vsram > new_vproc) {
> +				tol = new_vproc * dvfs->volt_tol / 100;
> +				regulator_set_voltage_tol(dvfs->proc_reg,
> +							  new_vproc, tol);
> +				break;
> +			}
> +
> +			target_volt = cur_vsram - VOLT_SHIFT_LIMIT;
> +			tol = target_volt * dvfs->volt_tol / 100;

I don't quite get the logic for tol calculation here. What's the
expected value for volt_tol? Care to explain?

> +			regulator_set_voltage_tol(dvfs->proc_reg, target_volt,
> +						  tol);
> +
> +			cur_vproc = regulator_get_voltage(dvfs->proc_reg);
> +			target_volt = cur_vproc + 1;
> +			tol = target_volt * dvfs->volt_tol / 100;
> +			regulator_set_voltage_tol(dvfs->sram_reg, target_volt,
> +						  tol);
> +		}
> +	} else if (old_vproc < new_vproc) {
> +		while (1) {
> +			cur_vsram = regulator_get_voltage(dvfs->sram_reg);
> +			if (cur_vsram - new_vproc < VOLT_SHIFT_LIMIT &&
> +			    cur_vsram > new_vproc) {
> +				tol = new_vproc * dvfs->volt_tol / 100;
> +				regulator_set_voltage_tol(dvfs->proc_reg,
> +							  new_vproc, tol);
> +				break;
> +			}
> +
> +			cur_vproc = regulator_get_voltage(dvfs->proc_reg);
> +			target_volt = cur_vproc + VOLT_SHIFT_LIMIT;
> +			tol = target_volt * dvfs->volt_tol / 100;
> +			regulator_set_voltage_tol(dvfs->sram_reg, target_volt,
> +						  tol);
> +
> +			if (new_vproc - cur_vproc > VOLT_SHIFT_LIMIT) {
> +				cur_vsram = regulator_get_voltage(
> +							dvfs->sram_reg);
> +				target_volt = cur_vsram - 1;
> +				tol = target_volt * dvfs->volt_tol / 100;
> +				regulator_set_voltage_tol(dvfs->proc_reg,
> +							  target_volt, tol);
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int get_opp_voltage(struct device *dev, unsigned long freq_hz)
> +{
> +	struct dev_pm_opp *opp;
> +	int volt;
> +
> +	rcu_read_lock();
> +	opp = dev_pm_opp_find_freq_ceil(dev, &freq_hz);
> +	if (IS_ERR(opp)) {
> +		rcu_read_unlock();
> +		pr_err("%s: failed to find OPP for %lu\n", __func__, freq_hz);
> +		return PTR_ERR(opp);
> +	}
> +	volt = dev_pm_opp_get_voltage(opp);
> +	rcu_read_unlock();
> +
> +	return volt;
> +}
> +
> +static int mt8173_cpufreq_get_intermediate_voltage(int cluster)
> +{
> +	struct cluster_dvfs_info *dvfs = &dvfs_info[cluster];
> +
> +	if (dvfs->inter_volt == 0)
> +		dvfs->inter_volt = get_opp_voltage(dvfs->cpu_dev,
> +						   mainpll_freq);
> +
> +	return dvfs->inter_volt;
> +}
> +
> +static void mt8173_cpufreq_set_voltage(int cluster, int old_volt, int new_volt)
> +{
> +	struct cluster_dvfs_info *dvfs = &dvfs_info[cluster];
> +	int ret = 0;
> +
> +	if (cluster == LITTLE_CLUSTER) {
> +		int tol;
> +
> +		tol = new_volt * dvfs->volt_tol / 100;
> +		ret = regulator_set_voltage_tol(dvfs->proc_reg, new_volt, tol);
> +	} else {
> +		ret = mt8173_cpufreq_voltage_step(dvfs, old_volt, new_volt);
> +	}
> +
> +	if (ret)
> +		pr_err("%s: cluster%d failed to scale voltage (%dmV to %dmV)",
> +		       __func__, cluster, old_volt, new_volt);
> +}

It seems the only reason to check if it is a BIG or LITTLE cluster is to
control vsram correctly. If we assume we need to do the control whenever
sram_reg exists, we can drop all the BIG/LITTLE cluster check. I think
this will make code looks more compact and generic.

> +
> +static int mt8173_cpufreq_notify(struct notifier_block *nb,
> +				 unsigned long action, void *data)
> +{
> +	struct cpufreq_freqs *freqs = data;
> +	struct cluster_dvfs_info *dvfs;
> +	unsigned long old_volt, new_volt, inter_volt;
> +	int cluster;
> +
> +	if (freqs->cpu == dvfs_info[BIG_CLUSTER].cpu)
> +		cluster = BIG_CLUSTER;
> +	else if (freqs->cpu == dvfs_info[LITTLE_CLUSTER].cpu)
> +		cluster = LITTLE_CLUSTER;
> +	else
> +		return NOTIFY_DONE;
> +
> +	dvfs = &dvfs_info[cluster];
> +
> +	old_volt = regulator_get_voltage(dvfs->proc_reg);
> +
> +	new_volt = get_opp_voltage(dvfs->cpu_dev, freqs->new * 1000);
> +	inter_volt = mt8173_cpufreq_get_intermediate_voltage(cluster);

This is only used in PRECHANGE, please move this inside the if.

> +
> +	if (action == CPUFREQ_PRECHANGE) {
> +		if (old_volt < inter_volt || old_volt < new_volt) {
> +			new_volt = inter_volt > new_volt ?
> +				   inter_volt : new_volt;
> +			mt8173_cpufreq_set_voltage(cluster, old_volt,
> +						   new_volt);
> +		}
> +
> +		cpuclk_mux_set(cluster, CPUCLK_MUX_MAINPLL);
> +	} else if (action == CPUFREQ_POSTCHANGE) {
> +		cpuclk_mux_set(cluster, CPUCLK_MUX_ARMPLL);
> +
> +		if (new_volt < old_volt)
> +			mt8173_cpufreq_set_voltage(cluster, old_volt,
> +						   new_volt);
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +static struct notifier_block mt8173_cpufreq_nb = {
> +	.notifier_call = mt8173_cpufreq_notify,
> +};
> +
> +static struct cpufreq_dt_platform_data *pd;

Please drop this global variable. You can return it from
cpufreq_dt_pdata_init and pass to all the function need it.

> +
> +static int cpu_in_domain_list(struct list_head *domain_list, int cpu)
> +{
> +	struct list_head *node;
> +
> +	list_for_each(node, domain_list) {
> +		struct cpufreq_cpu_domain *domain;
> +
> +		domain = container_of(node, struct cpufreq_cpu_domain, node);
> +		if (cpumask_test_cpu(cpu, &domain->cpus))
> +			return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static void cpufreq_dt_pdata_release(void)
> +{
> +	if (!pd)
> +		return;
> +
> +	if (!list_empty(&pd->domain_list)) {
> +		struct list_head *node, *tmp;
> +		struct cpufreq_cpu_domain *domain;
> +
> +		list_for_each_safe(node, tmp, &pd->domain_list) {
> +			list_del(node);
> +			domain = container_of(node, struct cpufreq_cpu_domain,
> +					      node);
> +			kfree(domain);
> +		}
> +	}
> +
> +	kfree(pd);
> +}
> +
> +static int cpufreq_dt_pdata_init(void)
> +{
> +	int cpu;
> +
> +	pd = kmalloc(sizeof(*pd), GFP_KERNEL);
> +	if (!pd)
> +		return -ENOMEM;
> +
> +	pd->independent_clocks = 1,
> +	INIT_LIST_HEAD(&pd->domain_list);
> +
> +	for_each_possible_cpu(cpu) {
> +		struct cpufreq_cpu_domain *new_domain;
> +
> +		if (cpu_in_domain_list(&pd->domain_list, cpu))
> +			continue;
> +
> +		new_domain = kzalloc(sizeof(*new_domain), GFP_KERNEL);
> +		if (!new_domain) {
> +			cpufreq_dt_pdata_release();
> +			return -ENOMEM;
> +		}
> +
> +		cpumask_copy(&new_domain->cpus,
> +			     &cpu_topology[cpu].core_sibling);
> +		list_add(&new_domain->node, &pd->domain_list);
> +	}
> +
> +	return 0;
> +}
> +
> +static void mt8173_cpufreq_dvfs_info_release(void)
> +{
> +	int i;
> +
> +	if (!dvfs_info)
> +		return;
> +
> +	for (i = 0; i < NR_CLUSTERS; ++i) {
> +		struct cluster_dvfs_info *dvfs = &dvfs_info[i];
> +
> +		if (!IS_ERR_OR_NULL(dvfs->proc_reg))
> +			regulator_put(dvfs->proc_reg);
> +
> +		if (!IS_ERR_OR_NULL(dvfs->sram_reg))
> +			regulator_put(dvfs->sram_reg);
> +	}
> +
> +	if (clk_mux_regs)
> +		iounmap(clk_mux_regs);
> +}
> +
> +static int mt8173_cpufreq_dvfs_info_init(void)
> +{
> +	struct list_head *node;
> +	struct device_node *np;
> +	struct clk *mainpll;
> +	int i, ret;
> +
> +	dvfs_info = kzalloc(sizeof(*dvfs) * NR_CLUSTERS, GFP_KERNEL);
> +	if (!dvfs_info)
> +		return -ENOMEM;
> +
> +	list_for_each(node, &pd->domain_list) {
> +		struct cpufreq_cpu_domain *domain = container_of(
> +					node, struct cpufreq_cpu_domain, node);
> +		int cpu = cpumask_next(0, &domain->cpus);
> +
> +		np = of_get_cpu_node(cpu, NULL);
> +
> +		if (of_property_match_string(np, "compatible",
> +					     "arm,cortex-a53") >= 0)
> +			cpumask_copy(&dvfs_info[LITTLE_CLUSTER].cpus,
> +				     &domain->cpus);
> +		else if (of_property_match_string(np, "compatible",
> +						  "arm,cortex-a57") >= 0)
> +			cpumask_copy(&dvfs_info[BIG_CLUSTER].cpus,
> +				     &domain->cpus);
> +		else {
> +			of_node_put(np);
> +			pr_err("%s: unknown CPU core\n", __func__);
> +			return -EINVAL;
> +		}
> +
> +		of_node_put(np);
> +	}

It seems you hardcode the match CPU here to know which is big/little
cluster. If we still need this information, should we add this to
topology, maybe something similar to capacity in arm topology, instead
of coding here?

> +
> +	for (i = 0; i < NR_CLUSTERS; i++) {
> +		struct cluster_dvfs_info *dvfs = &dvfs_info[i];
> +		int cpu;
> +
> +		for_each_cpu_mask(cpu, dvfs->cpus) {
> +			struct device_node *np;
> +
> +			dvfs->cpu_dev = get_cpu_device(cpu);
> +			dvfs->proc_reg = regulator_get(dvfs->cpu_dev, "proc");
> +			if (IS_ERR(dvfs->proc_reg))
> +				continue;
> +
> +			dvfs->cpu = cpu;
> +			dvfs->sram_reg = regulator_get(dvfs->cpu_dev, "sram");
> +
> +			np = of_node_get(dvfs->cpu_dev->of_node);
> +			of_property_read_u32(np, "voltage-tolerance",
> +					     &dvfs->volt_tol);
> +			of_node_put(np);
> +			break;
> +		}
> +
> +		if (IS_ERR(dvfs->proc_reg)) {
> +			pr_err("%s: no proc regulator for cluster %d\n",
> +			       __func__, i);
> +			ret = -ENODEV;
> +			goto dvfs_info_release;
> +		}
> +
> +		if (IS_ERR(dvfs->sram_reg) && BIG_CLUSTER == i) {
> +			pr_err("%s: no sram regulator for cluster %d\n",
> +			       __func__, i);
> +			ret = -ENODEV;
> +			goto dvfs_info_release;
> +		}
> +	}
> +
> +	mainpll = __clk_lookup("mainpll");
> +	if (!mainpll) {
> +		pr_err("failed to get mainpll clk\n");
> +		ret = -ENOENT;
> +		goto dvfs_info_release;
> +	}
> +	mainpll_freq = clk_get_rate(mainpll);
> +
> +	np = of_find_compatible_node(NULL, NULL, "mediatek,mt8173-infrasys");
> +	if (!np) {
> +		pr_err("failed to find clock mux registers\n");
> +		ret = -ENODEV;
> +		goto dvfs_info_release;
> +	}
> +
> +	clk_mux_regs = of_iomap(np, 0);
> +	if (!clk_mux_regs) {
> +		pr_err("failed to map clock mux registers\n");
> +		ret = -EFAULT;
> +		goto dvfs_info_release;
> +	}
> +	of_node_put(np);

This is already used by mt8173 clock driver, and you are directly
accessing it registers to control clock mux. I think you should add
these 2 clk to clock driver and use clk API to control it. Also you can
drop the lock init below if you do this.

Joe.C

> +
> +	spin_lock_init(&lock);
> +
> +	return 0;
> +
> +dvfs_info_release:
> +	mt8173_cpufreq_dvfs_info_release();
> +	return ret;
> +}




More information about the linux-arm-kernel mailing list