From mboxrd@z Thu Jan 1 00:00:00 1970 From: Dave.Martin@arm.com (Dave Martin) Date: Wed, 2 Oct 2013 15:49:55 +0100 Subject: [PATCH v4] ARM: vexpress/TC2: Implement MCPM power_down_finish() In-Reply-To: <1380725155-3936-1-git-send-email-Dave.Martin@arm.com> References: <1380725155-3936-1-git-send-email-Dave.Martin@arm.com> Message-ID: <20131002144955.GD3407@localhost.localdomain> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org 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 > --- > 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 > #include > #include > #include > @@ -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