[PATCH v4] ARM: vexpress/TC2: Implement MCPM power_down_finish()
Dave Martin
Dave.Martin at arm.com
Wed Oct 2 10:49:55 EDT 2013
On Wed, Oct 02, 2013 at 03:45:55PM +0100, Dave Martin wrote:
> This patch implements the power_down_finish() method for TC2, to
> enable the kernel to confirm when CPUs are safely powered down.
>
> The information required for determining when a CPU is parked
> cannot be obtained from any single place, so a few sources of
> information must be combined:
>
> * mcpm_cpu_power_down() must be pending for the CPU, so that we
> don't get confused by false STANDBYWFI positives arising from
> CPUidle. This is detected by waiting for the tc2_pm use count
> for the target CPU to reach 0.
>
> * Either the SPC must report that the CPU has asserted
> STANDBYWFI, or the TC2 tile's reset control logic must be
> holding the CPU in reset.
>
> Just checking for STANDBYWFI is not sufficient, because this
> signal is not latched when the the cluster is clamped off and
> powered down: the relevant status bits just drop to zero. This
> means that STANDBYWFI status cannot be used for reliable
> detection of the last CPU in a cluster reaching WFI.
>
> This patch is required in order for kexec to work with MCPM on TC2.
>
> Signed-off-by: Dave Martin <Dave.Martin at arm.com>
> ---
> Changes since v3:
>
> * Fix the location of the RESET_A7_NCORERESET() macro so that the
> A7_NCORERESET bits in the reset control register start at bit 19,
> not bit 19.
And yes, that should read "the A7_NCORERESET bits in the reset control
register start at bit **16**, not bit 19."
Darn.
The patch should be right, though.
> It looks like I misread the TRM on the first pass :/
>
> Bits 19-21 are the A7_ETMRESET bits. In practice the SPC sets/
> clears all these bits simultaneously anyway, so the code still
> worked regardless, but future versions of the SPC firmware could
> behave differently.
>
> The A15 NCORERESET bits do indeed start at bit 2, so that doesn't
> need fixing.
>
> * Acks dropped -- Nico, if you can take a quick look to confirm I've
> not gone insane, that would be appreciated, thanks.
>
> * The generic MCPM patches from previous postings are in Russell's
> patch system pending his approval, so I've dropped them from this
> repost.
>
> arch/arm/mach-vexpress/spc.c | 39 +++++++++++++++++++++++
> arch/arm/mach-vexpress/spc.h | 1 +
> arch/arm/mach-vexpress/tc2_pm.c | 66 ++++++++++++++++++++++++++++++++++++---
> 3 files changed, 101 insertions(+), 5 deletions(-)
>
> diff --git a/arch/arm/mach-vexpress/spc.c b/arch/arm/mach-vexpress/spc.c
> index eefb029..6f6ac56 100644
> --- a/arch/arm/mach-vexpress/spc.c
> +++ b/arch/arm/mach-vexpress/spc.c
> @@ -35,6 +35,10 @@
> /* SPC per-CPU mailboxes */
> #define A15_BX_ADDR0 0x68
> #define A7_BX_ADDR0 0x78
> +/* SPC CPU/cluster reset statue */
> +#define STANDBYWFI_STAT 0x3c
> +#define STANDBYWFI_STAT_A15_CPU_MASK(cpu) (1 << (cpu))
> +#define STANDBYWFI_STAT_A7_CPU_MASK(cpu) (1 << (3 + (cpu)))
>
> /* wake-up interrupt masks */
> #define GBL_WAKEUP_INT_MSK (0x3 << 10)
> @@ -157,6 +161,41 @@ void ve_spc_powerdown(u32 cluster, bool enable)
> writel_relaxed(enable, info->baseaddr + pwdrn_reg);
> }
>
> +static u32 standbywfi_cpu_mask(u32 cpu, u32 cluster)
> +{
> + return cluster_is_a15(cluster) ?
> + STANDBYWFI_STAT_A15_CPU_MASK(cpu)
> + : STANDBYWFI_STAT_A7_CPU_MASK(cpu);
> +}
> +
> +/**
> + * ve_spc_cpu_in_wfi(u32 cpu, u32 cluster)
> + *
> + * @cpu: mpidr[7:0] bitfield describing CPU affinity level within cluster
> + * @cluster: mpidr[15:8] bitfield describing cluster affinity level
> + *
> + * @return: non-zero if and only if the specified CPU is in WFI
> + *
> + * Take care when interpreting the result of this function: a CPU might
> + * be in WFI temporarily due to idle, and is not necessarily safely
> + * parked.
> + */
> +int ve_spc_cpu_in_wfi(u32 cpu, u32 cluster)
> +{
> + int ret;
> + u32 mask = standbywfi_cpu_mask(cpu, cluster);
> +
> + if (cluster >= MAX_CLUSTERS)
> + return 1;
> +
> + ret = readl_relaxed(info->baseaddr + STANDBYWFI_STAT);
> +
> + pr_debug("%s: PCFGREG[0x%X] = 0x%08X, mask = 0x%X\n",
> + __func__, STANDBYWFI_STAT, ret, mask);
> +
> + return ret & mask;
> +}
> +
> int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid)
> {
> info = kzalloc(sizeof(*info), GFP_KERNEL);
> diff --git a/arch/arm/mach-vexpress/spc.h b/arch/arm/mach-vexpress/spc.h
> index 5f7e4a4..edb1b06 100644
> --- a/arch/arm/mach-vexpress/spc.h
> +++ b/arch/arm/mach-vexpress/spc.h
> @@ -20,5 +20,6 @@ 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);
> void ve_spc_powerdown(u32 cluster, bool enable);
> +int ve_spc_cpu_in_wfi(u32 cpu, u32 cluster);
>
> #endif
> diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c
> index e6eb481..867eed8 100644
> --- a/arch/arm/mach-vexpress/tc2_pm.c
> +++ b/arch/arm/mach-vexpress/tc2_pm.c
> @@ -12,6 +12,7 @@
> * published by the Free Software Foundation.
> */
>
> +#include <linux/delay.h>
> #include <linux/init.h>
> #include <linux/io.h>
> #include <linux/kernel.h>
> @@ -31,11 +32,17 @@
> #include "spc.h"
>
> /* SCC conf registers */
> +#define RESET_CTRL 0x018
> +#define RESET_A15_NCORERESET(cpu) (1 << (2 + (cpu)))
> +#define RESET_A7_NCORERESET(cpu) (1 << (16 + (cpu)))
> +
> #define A15_CONF 0x400
> #define A7_CONF 0x500
> #define SYS_INFO 0x700
> #define SPC_BASE 0xb00
>
> +static void __iomem *scc;
> +
> /*
> * We can't use regular spinlocks. In the switcher case, it is possible
> * for an outbound CPU to call power_down() after its inbound counterpart
> @@ -233,6 +240,55 @@ static void tc2_pm_power_down(void)
> tc2_pm_down(0);
> }
>
> +static int tc2_core_in_reset(unsigned int cpu, unsigned int cluster)
> +{
> + u32 mask = cluster ?
> + RESET_A7_NCORERESET(cpu)
> + : RESET_A15_NCORERESET(cpu);
> +
> + return !(readl_relaxed(scc + RESET_CTRL) & mask);
> +}
> +
> +#define POLL_MSEC 10
> +#define TIMEOUT_MSEC 1000
> +
> +static int tc2_pm_power_down_finish(unsigned int cpu, unsigned int cluster)
> +{
> + unsigned tries;
> +
> + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
> + BUG_ON(cluster >= TC2_CLUSTERS || cpu >= TC2_MAX_CPUS_PER_CLUSTER);
> +
> + for (tries = 0; tries < TIMEOUT_MSEC / POLL_MSEC; ++tries) {
> + /*
> + * Only examine the hardware state if the target CPU has
> + * caught up at least as far as tc2_pm_down():
> + */
> + if (ACCESS_ONCE(tc2_pm_use_count[cpu][cluster]) == 0) {
> + pr_debug("%s(cpu=%u, cluster=%u): RESET_CTRL = 0x%08X\n",
> + __func__, cpu, cluster,
> + readl_relaxed(scc + RESET_CTRL));
> +
> + /*
> + * We need the CPU to reach WFI, but the power
> + * controller may put the cluster in reset and
> + * power it off as soon as that happens, before
> + * we have a chance to see STANDBYWFI.
> + *
> + * So we need to check for both conditions:
> + */
> + if (tc2_core_in_reset(cpu, cluster) ||
> + ve_spc_cpu_in_wfi(cpu, cluster))
> + return 0; /* success: the CPU is halted */
> + }
> +
> + /* Otherwise, wait and retry: */
> + msleep(POLL_MSEC);
> + }
> +
> + return -ETIMEDOUT; /* timeout */
> +}
> +
> static void tc2_pm_suspend(u64 residency)
> {
> unsigned int mpidr, cpu, cluster;
> @@ -275,10 +331,11 @@ static void tc2_pm_powered_up(void)
> }
>
> static const struct mcpm_platform_ops tc2_pm_power_ops = {
> - .power_up = tc2_pm_power_up,
> - .power_down = tc2_pm_power_down,
> - .suspend = tc2_pm_suspend,
> - .powered_up = tc2_pm_powered_up,
> + .power_up = tc2_pm_power_up,
> + .power_down = tc2_pm_power_down,
> + .power_down_finish = tc2_pm_power_down_finish,
> + .suspend = tc2_pm_suspend,
> + .powered_up = tc2_pm_powered_up,
> };
>
> static bool __init tc2_pm_usage_count_init(void)
> @@ -312,7 +369,6 @@ static void __naked tc2_pm_power_up_setup(unsigned int affinity_level)
> static int __init tc2_pm_init(void)
> {
> int ret;
> - void __iomem *scc;
> u32 a15_cluster_id, a7_cluster_id, sys_info;
> struct device_node *np;
>
> --
> 1.7.9.5
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
More information about the linux-arm-kernel
mailing list