[PATCH 2/5] ARM: vexpress/TC2: add support for CPU DVFS

Nicolas Pitre nicolas.pitre at linaro.org
Fri Oct 18 14:27:03 EDT 2013


On Wed, 16 Oct 2013, Sudeep KarkadaNagesha wrote:

> From: Sudeep KarkadaNagesha <sudeep.karkadanagesha at arm.com>
> 
> SPC(Serial Power Controller) on TC2 also controls the CPU performance
> operating points which is essential to provide CPU DVFS. The M3
> microcontroller provides two sets of eight performance values, one set
> for each cluster (CA15 or CA7). Each of this value contains the
> frequency(kHz) and voltage(mV) at that performance level. It expects
> these performance level to be passed through the SPC PERF_LVL registers.
> 
> This patch adds support to populate these performance levels from M3,
> build the mapping to CPU OPPs at the boot and then use it to get and
> set the CPU performance level runtime.
> 
> Signed-off-by: Sudeep KarkadaNagesha <sudeep.karkadanagesha at arm.com>
> Cc: Pawel Moll <Pawel.Moll at arm.com>
> Cc: Viresh Kumar <viresh.kumar at linaro.org>

Minor comments below.  With those addressed, you may add

Acked-by: Nicolas Pitre <nico at linaro.org>

> ---
>  arch/arm/mach-vexpress/Kconfig  |   2 +
>  arch/arm/mach-vexpress/spc.c    | 240 +++++++++++++++++++++++++++++++++++++++-
>  arch/arm/mach-vexpress/spc.h    |   2 +-
>  arch/arm/mach-vexpress/tc2_pm.c |   7 +-
>  4 files changed, 247 insertions(+), 4 deletions(-)
> 
> diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig
> index 3657954..5989187 100644
> --- a/arch/arm/mach-vexpress/Kconfig
> +++ b/arch/arm/mach-vexpress/Kconfig
> @@ -1,5 +1,7 @@
>  config ARCH_VEXPRESS
>  	bool "ARM Ltd. Versatile Express family" if ARCH_MULTI_V7
> +	select ARCH_HAS_CPUFREQ
> +	select ARCH_HAS_OPP
>  	select ARCH_REQUIRE_GPIOLIB
>  	select ARM_AMBA
>  	select ARM_GIC

This is probably a bit too wide a selection for all CONFIG_ARCH_VEXPRESS 
targets.

I'd suggest creating a new CONFIG_VEXPRESS_SPC entry, making the build 
of spc.o depend on it, and that config entry could select
ARCH_HAS_CPUFREQ, ARCH_HAS_OPP and (as discussed in private) PM_OPP.  

Then this VEXPRESS_SPC would be selected by ARCH_VEXPRESS_TC2_PM.

Also the config dependency for ARM_VEXPRESS_SPC_CPUFREQ (in the later 
patch) would be better represented by VEXPRESS_SPC rather than 
ARCH_VEXPRESS_TC2_PM.

> diff --git a/arch/arm/mach-vexpress/spc.c b/arch/arm/mach-vexpress/spc.c
> index eefb029..ef7e652 100644
> --- a/arch/arm/mach-vexpress/spc.c
> +++ b/arch/arm/mach-vexpress/spc.c
> @@ -17,14 +17,26 @@
>   * GNU General Public License for more details.
>   */
>  
> +#include <linux/delay.h>
>  #include <linux/err.h>
> +#include <linux/interrupt.h>
>  #include <linux/io.h>
> +#include <linux/opp.h>
>  #include <linux/slab.h>
> +#include <linux/semaphore.h>
>  
>  #include <asm/cacheflush.h>
>  
>  #define SPCLOG "vexpress-spc: "
>  
> +#define PERF_LVL_A15		0x00
> +#define PERF_REQ_A15		0x04
> +#define PERF_LVL_A7		0x08
> +#define PERF_REQ_A7		0x0c
> +#define COMMS			0x10
> +#define COMMS_REQ		0x14
> +#define PWC_STATUS		0x18
> +#define PWC_FLAG		0x1c

Maybe a blank line here?

>  /* SPC wake-up IRQs status and mask */
>  #define WAKE_INT_MASK		0x24
>  #define WAKE_INT_RAW		0x28
> @@ -35,6 +47,18 @@
>  /* SPC per-CPU mailboxes */
>  #define A15_BX_ADDR0		0x68
>  #define A7_BX_ADDR0		0x78

Ditto.

> +/* SPC system config interface registers */
> +#define SYSCFG_WDATA		0x70
> +#define SYSCFG_RDATA		0x74
> +
> +/* A15/A7 OPP virtual register base */
> +#define A15_PERFVAL_BASE	0xC10
> +#define A7_PERFVAL_BASE		0xC30
> +
> +/* Config interface control bits */
> +#define SYSCFG_START		(1 << 31)
> +#define SYSCFG_SCC		(6 << 20)
> +#define SYSCFG_STAT		(14 << 20)
>  
>  /* wake-up interrupt masks */
>  #define GBL_WAKEUP_INT_MSK	(0x3 << 10)
> @@ -42,6 +66,26 @@
>  /* TC2 static dual-cluster configuration */
>  #define MAX_CLUSTERS		2
>  
> +/*
> + * Even though the SPC takes max 3-5 ms to complete any OPP/COMMS
> + * operation, the operation could start just before jiffie is about
> + * to be incremented. So setting timeout value of 20ms = 2jiffies at 100Hz
> + */
> +#define TIMEOUT_US	20000
> +
> +#define MAX_OPPS	8
> +#define CA15_DVFS	0
> +#define CA7_DVFS	1
> +#define SPC_SYS_CFG	2
> +#define STAT_COMPLETE(type)	((1 << 0) << (type << 2))
> +#define STAT_ERR(type)		((1 << 1) << (type << 2))
> +#define RESPONSE_MASK(type)	(STAT_COMPLETE(type) | STAT_ERR(type))
> +
> +struct ve_spc_opp {
> +	unsigned long freq;
> +	unsigned long u_volt;
> +};
> +
>  struct ve_spc_drvdata {
>  	void __iomem *baseaddr;
>  	/*
> @@ -49,6 +93,12 @@ struct ve_spc_drvdata {
>  	 * It corresponds to A15 processors MPIDR[15:8] bitfield
>  	 */
>  	u32 a15_clusid;
> +	uint32_t cur_rsp_mask;
> +	uint32_t cur_rsp_stat;
> +	struct semaphore sem;
> +	struct completion done;
> +	struct ve_spc_opp *opps[MAX_CLUSTERS];
> +	int num_opps[MAX_CLUSTERS];
>  };
>  
>  static struct ve_spc_drvdata *info;
> @@ -157,8 +207,177 @@ void ve_spc_powerdown(u32 cluster, bool enable)
>  	writel_relaxed(enable, info->baseaddr + pwdrn_reg);
>  }
>  
> -int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid)
> +static int ve_spc_get_performance(int cluster, u32 *freq)
> +{
> +	struct ve_spc_opp *opps = info->opps[cluster];
> +	u32 perf_cfg_reg = 0;
> +	int perf;
> +
> +	perf_cfg_reg = cluster_is_a15(cluster) ? PERF_LVL_A15 : PERF_LVL_A7;
> +
> +	perf = readl_relaxed(info->baseaddr + perf_cfg_reg);
> +	opps += perf;

This might be a good idea to validate the hardware returned index before 
dereferencing memory i.e "if (perf >= info->num_opps[cluster]) ..." with 
perf declared as an unsigned int.

> +	*freq = opps->freq;
> +
> +	return 0;
> +}
> +
> +/* find closest match to given frequency in OPP table */
> +static int ve_spc_round_performance(int cluster, u32 freq)
> +{
> +	int idx, max_opp = info->num_opps[cluster];
> +	struct ve_spc_opp *opps = info->opps[cluster];
> +	u32 fmin = 0, fmax = ~0, ftmp;
> +
> +	freq /= 1000; /* OPP entries in kHz */
> +	for (idx = 0; idx < max_opp; idx++, opps++) {
> +		ftmp = opps->freq;
> +		if (ftmp >= freq) {
> +			if (ftmp <= fmax)
> +				fmax = ftmp;
> +		} else {
> +			if (ftmp >= fmin)
> +				fmin = ftmp;
> +		}
> +	}
> +	if (fmax != ~0)
> +		return fmax * 1000;
> +	else
> +		return fmin * 1000;
> +}
> +
> +static int ve_spc_find_performance_index(int cluster, u32 freq)
> +{
> +	int idx, max_opp = info->num_opps[cluster];
> +	struct ve_spc_opp *opps = info->opps[cluster];
> +
> +	for (idx = 0; idx < max_opp; idx++, opps++)
> +		if (opps->freq == freq)
> +			break;
> +	return (idx == max_opp) ? -EINVAL : idx;
> +}
> +
> +static int ve_spc_waitforcompletion(int req_type)
> +{
> +	int ret = wait_for_completion_interruptible_timeout(
> +			&info->done, usecs_to_jiffies(TIMEOUT_US));
> +	if (ret == 0)
> +		ret = -ETIMEDOUT;
> +	else if (ret > 0)
> +		ret = info->cur_rsp_stat & STAT_COMPLETE(req_type) ? 0 : -EIO;
> +	return ret;
> +}
> +
> +static int ve_spc_set_performance(int cluster, u32 freq)
> +{
> +	u32 perf_cfg_reg, perf_stat_reg;
> +	int ret, perf, req_type;
> +
> +	if (cluster_is_a15(cluster)) {
> +		req_type = CA15_DVFS;
> +		perf_cfg_reg = PERF_LVL_A15;
> +		perf_stat_reg = PERF_REQ_A15;
> +	} else {
> +		req_type = CA7_DVFS;
> +		perf_cfg_reg = PERF_LVL_A7;
> +		perf_stat_reg = PERF_REQ_A7;
> +	}
> +
> +	perf = ve_spc_find_performance_index(cluster, freq);
> +
> +	if (perf < 0)
> +		return perf;
> +
> +	if (down_timeout(&info->sem, usecs_to_jiffies(TIMEOUT_US)))
> +		return -ETIME;
> +
> +	init_completion(&info->done);
> +	info->cur_rsp_mask = RESPONSE_MASK(req_type);
> +
> +	writel(perf, info->baseaddr + perf_cfg_reg);
> +	ret = ve_spc_waitforcompletion(req_type);
> +
> +	info->cur_rsp_mask = 0;
> +	up(&info->sem);
> +
> +	return ret;
> +}
> +
> +static int ve_spc_read_sys_cfg(int func, int offset, uint32_t *data)
> +{
> +	int ret;
> +
> +	if (down_timeout(&info->sem, usecs_to_jiffies(TIMEOUT_US)))
> +		return -ETIME;
> +
> +	init_completion(&info->done);
> +	info->cur_rsp_mask = RESPONSE_MASK(SPC_SYS_CFG);
> +
> +	/* Set the control value */
> +	writel(SYSCFG_START | func | offset >> 2, info->baseaddr + COMMS);
> +	ret = ve_spc_waitforcompletion(SPC_SYS_CFG);
> +
> +	if (ret == 0)
> +		*data = readl(info->baseaddr + SYSCFG_RDATA);
> +
> +	info->cur_rsp_mask = 0;
> +	up(&info->sem);
> +
> +	return ret;
> +}
> +
> +static irqreturn_t ve_spc_irq_handler(int irq, void *data)
> +{
> +	struct ve_spc_drvdata *drv_data = data;
> +	uint32_t status = readl_relaxed(drv_data->baseaddr + PWC_STATUS);
> +
> +	if (info->cur_rsp_mask & status) {
> +		info->cur_rsp_stat = status;
> +		complete(&drv_data->done);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/*
> + *  +--------------------------+
> + *  | 31      20 | 19        0 |
> + *  +--------------------------+
> + *  |   u_volt   |  freq(kHz)  |
> + *  +--------------------------+
> + */
> +#define MULT_FACTOR	20
> +#define VOLT_SHIFT	20
> +#define FREQ_MASK	(0xFFFFF)
> +static int ve_spc_populate_opps(uint32_t cluster)
> +{
> +	uint32_t data = 0, off, ret, idx;
> +	struct ve_spc_opp *opps;
> +
> +	opps = kzalloc(sizeof(*opps) * MAX_OPPS, GFP_KERNEL);
> +	if (!opps)
> +		return -ENOMEM;
> +
> +	info->opps[cluster] = opps;
> +
> +	off = cluster_is_a15(cluster) ? A15_PERFVAL_BASE : A7_PERFVAL_BASE;
> +	for (idx = 0; idx < MAX_OPPS; idx++, off += 4, opps++) {
> +		ret = ve_spc_read_sys_cfg(SYSCFG_SCC, off, &data);
> +		if (!ret) {
> +			opps->freq = (data & FREQ_MASK) * MULT_FACTOR;
> +			opps->u_volt = data >> VOLT_SHIFT;
> +		} else {
> +			break;
> +		}
> +	}
> +	info->num_opps[cluster] = idx;
> +
> +	return ret;
> +}
> +
> +int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid, int irq)
>  {
> +	int ret;
>  	info = kzalloc(sizeof(*info), GFP_KERNEL);
>  	if (!info) {
>  		pr_err(SPCLOG "unable to allocate mem\n");
> @@ -168,6 +387,25 @@ int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid)
>  	info->baseaddr = baseaddr;
>  	info->a15_clusid = a15_clusid;
>  
> +	if (irq <= 0) {
> +		pr_err(SPCLOG "Invalid IRQ %d\n", irq);
> +		kfree(info);
> +		return -EINVAL;
> +	}
> +
> +	init_completion(&info->done);
> +
> +	readl_relaxed(info->baseaddr + PWC_STATUS);
> +
> +	ret = request_irq(irq, ve_spc_irq_handler, IRQF_TRIGGER_HIGH
> +				| IRQF_ONESHOT, "vexpress-spc", info);
> +	if (ret) {
> +		pr_err(SPCLOG "IRQ %d request failed\n", irq);
> +		kfree(info);
> +		return -ENODEV;
> +	}
> +
> +	sema_init(&info->sem, 1);
>  	/*
>  	 * Multi-cluster systems may need this data when non-coherent, during
>  	 * cluster power-up/power-down. Make sure driver info reaches main
> diff --git a/arch/arm/mach-vexpress/spc.h b/arch/arm/mach-vexpress/spc.h
> index 5f7e4a4..dbd44c3 100644
> --- a/arch/arm/mach-vexpress/spc.h
> +++ b/arch/arm/mach-vexpress/spc.h
> @@ -15,7 +15,7 @@
>  #ifndef __SPC_H_
>  #define __SPC_H_
>  
> -int __init ve_spc_init(void __iomem *base, u32 a15_clusid);
> +int __init ve_spc_init(void __iomem *base, u32 a15_clusid, int irq);
>  void ve_spc_global_wakeup_irq(bool set);
>  void ve_spc_cpu_wakeup_irq(u32 cluster, u32 cpu, bool set);
>  void ve_spc_set_resume_addr(u32 cluster, u32 cpu, u32 addr);
> diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c
> index e6eb481..d38130a 100644
> --- a/arch/arm/mach-vexpress/tc2_pm.c
> +++ b/arch/arm/mach-vexpress/tc2_pm.c
> @@ -16,6 +16,7 @@
>  #include <linux/io.h>
>  #include <linux/kernel.h>
>  #include <linux/of_address.h>
> +#include <linux/of_irq.h>
>  #include <linux/spinlock.h>
>  #include <linux/errno.h>
>  #include <linux/irqchip/arm-gic.h>
> @@ -311,7 +312,7 @@ static void __naked tc2_pm_power_up_setup(unsigned int affinity_level)
>  
>  static int __init tc2_pm_init(void)
>  {
> -	int ret;
> +	int ret, irq;
>  	void __iomem *scc;
>  	u32 a15_cluster_id, a7_cluster_id, sys_info;
>  	struct device_node *np;
> @@ -336,13 +337,15 @@ static int __init tc2_pm_init(void)
>  	tc2_nr_cpus[a15_cluster_id] = (sys_info >> 16) & 0xf;
>  	tc2_nr_cpus[a7_cluster_id] = (sys_info >> 20) & 0xf;
>  
> +	irq = irq_of_parse_and_map(np, 0);
> +
>  	/*
>  	 * A subset of the SCC registers is also used to communicate
>  	 * with the SPC (power controller). We need to be able to
>  	 * drive it very early in the boot process to power up
>  	 * processors, so we initialize the SPC driver here.
>  	 */
> -	ret = ve_spc_init(scc + SPC_BASE, a15_cluster_id);
> +	ret = ve_spc_init(scc + SPC_BASE, a15_cluster_id, irq);
>  	if (ret)
>  		return ret;
>  
> -- 
> 1.8.1.2
> 



More information about the linux-arm-kernel mailing list