[PATCH 04/17] omap4: pm: Add CPUx OFF mode support
Kevin Hilman
khilman at ti.com
Wed Mar 2 17:12:47 EST 2011
Santosh Shilimkar <santosh.shilimkar at ti.com> writes:
> This patch adds the CPU0 and CPU1 off mode support. CPUX close switch
s/CPUX/CPUx/
> retention (CSWR) is not supported by hardware design.
>
> The CPUx OFF mode isn't supported on OMAP4430 ES1.0
>
> CPUx sleep code is common for hotplug, suspend and cpuilde.
s/cpuilde/CPUidle/
> Signed-off-by: Santosh Shilimkar <santosh.shilimkar at ti.com>
> Reviewed-by: Kevin Hilman <khilman at ti.com>
> ---
> arch/arm/mach-omap2/Makefile | 4 +-
> arch/arm/mach-omap2/include/mach/omap4-common.h | 46 +++
> arch/arm/mach-omap2/omap4-mpuss-lowpower.c | 241 ++++++++++++++++
> arch/arm/mach-omap2/omap4-sar-layout.h | 14 +
> arch/arm/mach-omap2/pm44xx.c | 6 +
> arch/arm/mach-omap2/sleep44xx.S | 334 +++++++++++++++++++++++
> 6 files changed, 644 insertions(+), 1 deletions(-)
> create mode 100644 arch/arm/mach-omap2/omap4-mpuss-lowpower.c
> create mode 100644 arch/arm/mach-omap2/sleep44xx.S
>
> diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
> index 54ff219..5d94f7e 100644
> --- a/arch/arm/mach-omap2/Makefile
> +++ b/arch/arm/mach-omap2/Makefile
> @@ -63,13 +63,15 @@ obj-$(CONFIG_ARCH_OMAP2) += pm24xx.o
> obj-$(CONFIG_ARCH_OMAP2) += sleep24xx.o pm_bus.o voltage.o
> obj-$(CONFIG_ARCH_OMAP3) += pm34xx.o sleep34xx.o voltage.o \
> cpuidle34xx.o pm_bus.o
> -obj-$(CONFIG_ARCH_OMAP4) += pm44xx.o voltage.o pm_bus.o
> +obj-$(CONFIG_ARCH_OMAP4) += pm44xx.o voltage.o pm_bus.o \
> + omap4-mpuss-lowpower.o sleep44xx.o
> obj-$(CONFIG_PM_DEBUG) += pm-debug.o
> obj-$(CONFIG_OMAP_SMARTREFLEX) += sr_device.o smartreflex.o
> obj-$(CONFIG_OMAP_SMARTREFLEX_CLASS3) += smartreflex-class3.o
>
> AFLAGS_sleep24xx.o :=-Wa,-march=armv6
> AFLAGS_sleep34xx.o :=-Wa,-march=armv7-a
> +AFLAGS_sleep44xx.o :=-Wa,-march=armv7-a
>
> ifeq ($(CONFIG_PM_VERBOSE),y)
> CFLAGS_pm_bus.o += -DDEBUG
> diff --git a/arch/arm/mach-omap2/include/mach/omap4-common.h b/arch/arm/mach-omap2/include/mach/omap4-common.h
> index 0e5edd8..74c9aa7 100644
> --- a/arch/arm/mach-omap2/include/mach/omap4-common.h
> +++ b/arch/arm/mach-omap2/include/mach/omap4-common.h
> @@ -13,6 +13,9 @@
> #ifndef OMAP_ARCH_OMAP4_COMMON_H
> #define OMAP_ARCH_OMAP4_COMMON_H
>
> +#include <asm/proc-fns.h>
> +
> +#ifndef __ASSEMBLER__
> /*
> * wfi used in low power code. Directly opcode is used instead
> * of instruction to avoid mulit-omap build break
> @@ -33,4 +36,47 @@ extern void __iomem *scu_base;
> extern void __init gic_init_irq(void);
> extern void omap_smc1(u32 fn, u32 arg);
>
> +/*
> + * Read MPIDR: Multiprocessor affinity register
> + */
> +static inline unsigned int hard_smp_processor_id(void)
> +{
> + unsigned int cpunum;
> +
> + asm volatile (
> + "mrc p15, 0, %0, c0, c0, 5\n"
> + : "=r" (cpunum));
> + return cpunum &= 0x0F;
minor: lower-case hex numbers are preferred
> +}
> +
> +#if defined(CONFIG_SMP) && defined(CONFIG_PM)
s/CONFIG_PM/CONFIG_SUSPEND/
> +extern int omap4_mpuss_init(void);
> +extern int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state);
> +extern void omap4_cpu_suspend(unsigned int cpu, unsigned int save_state);
> +extern void omap4_cpu_resume(void);
> +
> +#else
> +
> +static inline int omap4_enter_lowpower(unsigned int cpu,
> + unsigned int power_state)
> +{
> + cpu_do_idle();
> + return 0;
> +}
> +
> +static inline int omap4_mpuss_init(void)
> +{
> + return 0;
> +}
> +
> +static inline void omap4_cpu_suspend(unsigned int cpu, unsigned int save_state)
> +{
> +}
> +
> +static inline void omap4_cpu_resume(void)
> +{
> +}
> +
> #endif
> +#endif /* __ASSEMBLER__ */
> +#endif /* OMAP_ARCH_OMAP4_COMMON_H */
> diff --git a/arch/arm/mach-omap2/omap4-mpuss-lowpower.c b/arch/arm/mach-omap2/omap4-mpuss-lowpower.c
> new file mode 100644
> index 0000000..c0f358d
> --- /dev/null
> +++ b/arch/arm/mach-omap2/omap4-mpuss-lowpower.c
> @@ -0,0 +1,241 @@
> +/*
> + * OMAP4 MPUSS low power code
> + *
> + * Copyright (C) 2011 Texas Instruments, Inc.
> + * Written by Santosh Shilimkar <santosh.shilimkar at ti.com>
> + *
> + * OMAP4430 MPUSS mainly consists of dual Cortex-A9 with per-CPU
> + * Local timer and Watchdog, GIC, SCU, PL310 L2 cache controller,
> + * CPU0 and CPU1 LPRM modules.
> + * CPU0, CPU1 and MPUSS each have there own power domain and
> + * hence multiple low power combinations of MPUSS are possible.
> + *
> + * The CPU0 and CPU1 can't support Closed switch Retention (CSWR)
> + * because the mode is not supported by hw constraints of dormant
> + * mode. While waking up from the dormant mode, a reset signal
> + * to the Cortex-A9 processor must be asserted by the external
> + * power controller.
> + *
> + * With architectural inputs and hardware recommendations, only
> + * below modes are supported from power gain vs latency point of view.
> + *
> + * CPU0 CPU1 MPUSS
> + * ----------------------------------------------
> + * ON ON ON
> + * ON(Inactive) OFF ON(Inactive)
> + * OFF OFF CSWR
> + * OFF OFF OSWR (*TBD)
> + * OFF OFF OFF* (*TBD)
> + * ----------------------------------------------
> + *
> + * Note: CPU0 is the master core and it is the last CPU to go down
> + * and first to wake-up when MPUSS low power states are excercised
> + *
> + *
> + * 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.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/io.h>
> +#include <linux/errno.h>
> +#include <linux/linkage.h>
> +#include <linux/smp.h>
> +
> +#include <asm/cacheflush.h>
> +#include <asm/tlbflush.h>
> +#include <asm/smp_scu.h>
> +#include <asm/system.h>
> +
> +#include <plat/omap44xx.h>
> +#include <mach/omap4-common.h>
> +
> +#include "omap4-sar-layout.h"
> +#include "pm.h"
> +#include "powerdomain.h"
> +
> +#ifdef CONFIG_SMP
> +
> +#define CPU0_ID 0x0
> +#define CPU1_ID 0x1
These are also defined in the wakeupgen module, and are not really
needed. As these are only ever used in per_cpu() context, just using
the number directly is fine with me.
> +struct omap4_cpu_pm_info {
> + struct powerdomain *pwrdm;
> + void __iomem *scu_sar_addr;
> +};
> +
> +static DEFINE_PER_CPU(struct omap4_cpu_pm_info, omap4_pm_info);
> +
> +/*
> + * Set the CPUx powerdomain's previous power state
> + */
> +static inline void set_cpu_next_pwrst(unsigned int cpu_id,
> + unsigned int power_state)
> +{
> + struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id);
> +
> + pwrdm_set_next_pwrst(pm_info->pwrdm, power_state);
> +}
> +
> +/*
> + * Read CPU's previous power state
> + */
> +static inline unsigned int read_cpu_prev_pwrst(unsigned int cpu_id)
> +{
> + struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id);
> +
> + return pwrdm_read_prev_pwrst(pm_info->pwrdm);
> +}
> +
> +/*
> + * Clear the CPUx powerdomain's previous power state
> + */
> +static inline void clear_cpu_prev_pwrst(unsigned int cpu_id)
> +{
> + struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id);
> +
> + pwrdm_clear_all_prev_pwrst(pm_info->pwrdm);
> +}
> +
> +/*
> + * Store the SCU power status value to scratchpad memory
> + */
> +static void scu_pwrst_prepare(unsigned int cpu_id, unsigned int cpu_state)
> +{
> + struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id);
> + u32 scu_pwr_st;
> +
> + switch (cpu_state) {
> + case PWRDM_POWER_RET:
> + scu_pwr_st = SCU_PM_DORMANT;
> + break;
> + case PWRDM_POWER_OFF:
> + scu_pwr_st = SCU_PM_POWEROFF;
> + break;
> + case PWRDM_POWER_ON:
> + case PWRDM_POWER_INACTIVE:
> + default:
> + scu_pwr_st = SCU_PM_NORMAL;
> + break;
> + }
> +
> + __raw_writel(scu_pwr_st, pm_info->scu_sar_addr);
> +}
> +
> +/*
> + * OMAP4 MPUSS Low Power Entry Function
> + *
> + * The purpose of this function is to manage low power programming
> + * of OMAP4 MPUSS subsystem
> + * Paramenters:
> + * cpu : CPU ID
> + * power_state: Targetted Low power state.
> + */
> +int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state)
> +{
> + unsigned int save_state = 0;
> + unsigned int wakeup_cpu = hard_smp_processor_id();
> +
> + if ((cpu > NR_CPUS) || (omap_rev() == OMAP4430_REV_ES1_0))
> + goto ret;
> +
> + switch (power_state) {
> + case PWRDM_POWER_ON:
> + case PWRDM_POWER_INACTIVE:
> + save_state = 0;
> + break;
> + case PWRDM_POWER_OFF:
> + save_state = 1;
> + break;
> + case PWRDM_POWER_RET:
> + default:
> + /*
> + * CPUx CSWR is invalid hardware state. Also CPUx OSWR
> + * doesn't make much scense, since logic is lost and $L1
> + * needs to be cleaned because of coherency. This makes
> + * CPUx OSWR equivalent to CPUX OFF and hence not supported
> + */
a WARN() of some sort here would probably be useful to detect incorrect
programming of power state.
> + goto ret;
> + }
> +
> + clear_cpu_prev_pwrst(cpu);
> + set_cpu_next_pwrst(cpu, power_state);
> + scu_pwrst_prepare(cpu, power_state);
> +
> + /*
> + * Call low level function with targeted CPU id
> + * and its low power state.
> + */
> + omap4_cpu_suspend(cpu, save_state);
> +
> + /*
> + * Restore the CPUx power state to ON otherwise CPUx
> + * power domain can transitions to programmed low power
> + * state while doing WFI outside the low powe code. On
> + * secure devices, CPUx does WFI which can result in
> + * domain transition
> + */
> + wakeup_cpu = hard_smp_processor_id();
> + set_cpu_next_pwrst(wakeup_cpu, PWRDM_POWER_ON);
> +
> +ret:
> + return 0;
> +}
> +
> +/*
> + * Initialise OMAP4 MPUSS
> + */
> +int __init omap4_mpuss_init(void)
> +{
> + struct omap4_cpu_pm_info *pm_info;
> +
> + if (omap_rev() == OMAP4430_REV_ES1_0) {
> + WARN(1, "Power Management not supported on OMAP4430 ES1.0\n");
> + return -EPERM;
-ENODEV is probably more appropriate here
> + }
> +
> + /* Initilaise per CPU PM information */
> + pm_info = &per_cpu(omap4_pm_info, CPU0_ID);
> + pm_info->scu_sar_addr = sar_ram_base + SCU_OFFSET0;
> + pm_info->pwrdm = pwrdm_lookup("cpu0_pwrdm");
> + if (!pm_info->pwrdm) {
> + pr_err("Lookup failed for CPU0 pwrdm\n");
> + return -ENODEV;
> + }
> +
> + /* Clear CPU previous power domain state */
> + pwrdm_clear_all_prev_pwrst(pm_info->pwrdm);
> +
> + /* Initialise CPU0 power domain state to ON */
> + pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON);
> +
> + pm_info = &per_cpu(omap4_pm_info, CPU1_ID);
> + pm_info->scu_sar_addr = sar_ram_base + SCU_OFFSET1;
> + pm_info->pwrdm = pwrdm_lookup("cpu1_pwrdm");
> + if (!pm_info->pwrdm) {
> + pr_err("Lookup failed for CPU1 pwrdm\n");
> + return -ENODEV;
> + }
> +
> + /* Clear CPU previous power domain state */
> + pwrdm_clear_all_prev_pwrst(pm_info->pwrdm);
> +
> + /* Initialise CPU1 power domain state to ON */
> + pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON);
> +
> + /*
> + * Program the wakeup routine address for the CPU0 and CPU1
> + * used for OFF or DORMANT wakeup. Wakeup routine address
> + * is fixed so programit in init itself.
> + */
> + __raw_writel(virt_to_phys(omap4_cpu_resume),
> + sar_ram_base + CPU1_WAKEUP_NS_PA_ADDR_OFFSET);
> + __raw_writel(virt_to_phys(omap4_cpu_resume),
> + sar_ram_base + CPU0_WAKEUP_NS_PA_ADDR_OFFSET);
> +
> + return 0;
> +}
> +
> +#endif
> +
> diff --git a/arch/arm/mach-omap2/omap4-sar-layout.h b/arch/arm/mach-omap2/omap4-sar-layout.h
> index bb66816..c4251db 100644
> --- a/arch/arm/mach-omap2/omap4-sar-layout.h
> +++ b/arch/arm/mach-omap2/omap4-sar-layout.h
> @@ -19,6 +19,20 @@
> #define SAR_BANK3_OFFSET 0x2000
> #define SAR_BANK4_OFFSET 0x3000
>
> +/* Scratch pad memory offsets from SAR_BANK1 */
> +#define CPU0_SAVE_OFFSET 0xb00
> +#define CPU1_SAVE_OFFSET 0xc00
> +#define MMU_OFFSET 0xd00
> +#define SCU_OFFSET0 0xd20
> +#define SCU_OFFSET1 0xd24
> +
> +/* CPUx Wakeup Non-Secure Physical Address offsets in SAR_BANK3 */
> +#define CPU0_WAKEUP_NS_PA_ADDR_OFFSET 0xa04
> +#define CPU1_WAKEUP_NS_PA_ADDR_OFFSET 0xa08
> +
> +#ifndef __ASSEMBLER__
> +
> extern void __iomem *sar_ram_base;
>
> #endif
> +#endif
> diff --git a/arch/arm/mach-omap2/pm44xx.c b/arch/arm/mach-omap2/pm44xx.c
> index 8431d41..b142673 100644
> --- a/arch/arm/mach-omap2/pm44xx.c
> +++ b/arch/arm/mach-omap2/pm44xx.c
> @@ -115,6 +115,12 @@ static int __init omap4_pm_init(void)
>
> /* Enable autoidle for all clks which support it*/
> omap_clk_enable_autoidle();
> +
> + ret = omap4_mpuss_init();
> + if (ret) {
> + pr_err("Failed to initialise OMAP4 MPUSS\n");
> + goto err2;
> + }
> #endif
>
> #ifdef CONFIG_SUSPEND
> diff --git a/arch/arm/mach-omap2/sleep44xx.S b/arch/arm/mach-omap2/sleep44xx.S
> new file mode 100644
> index 0000000..bb42a7a
> --- /dev/null
> +++ b/arch/arm/mach-omap2/sleep44xx.S
> @@ -0,0 +1,334 @@
> +/*
> + * OMAP44xx CPU low power powerdown and powerup code.
> + *
> + * Copyright (C) 2011 Texas Instruments, Inc.
> + * Written by Santosh Shilimkar <santosh.shilimkar at ti.com>
> + *
> + * 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.
> + */
> +
> +#include <linux/linkage.h>
> +#include <asm/system.h>
> +#include <asm/smp_scu.h>
> +#include <asm/memory.h>
> +
> +#include <plat/omap44xx.h>
> +#include <mach/omap4-common.h>
> +#include <asm/hardware/cache-l2x0.h>
> +
> +#include "omap4-sar-layout.h"
> +
> +#ifdef CONFIG_SMP
> +
> +/* Masks used for MMU manipulation */
> +#define TTRBIT_MASK 0xffffc000
> +#define TABLE_INDEX_MASK 0xfff00000
> +#define TABLE_ENTRY 0x00000c02
> +#define CACHE_DISABLE_MASK 0xffffe7fb
> +
> +/*
> + * =============================
> + * == CPU suspend entry point ==
> + * =============================
> + *
> + * void omap4_cpu_suspend(unsigned int cpu, unsigned int save_state)
> + *
> + * This function code saves the CPU context and performs the CPU
> + * power down sequence. Calling WFI effectively changes the CPU
> + * power domains states to the desired target power state.
> + *
> + * @cpu : contains cpu id (r0)
> + * @save_state : contains context save state (r1)
> + * 0 - No context lost
> + * 1 - CPUx L1 and logic lost: MPUSS CSWR
> + * 2 - CPUx L1 and logic lost + GIC lost: MPUSS OSWR
> + * 3 - CPUx L1 and logic lost + GIC + L2 lost: MPUSS OFF
> + * @return: This function never returns for CPU OFF and DORMANT power states.
> + * It retunrs to the caller for CPU INACTIVE and ON power states or in case
typo: returns
> + * CPU failed to transition to targeted OFF/DORMANT state.
to avoid confusion, what happens for OFF/dormant should probably be
summarized too.
I didn't do a detailed review of the below assembly since you're much
more knowlegable there than me.
However, will the assembly code here work in Thumb-2 mode? Dave Martin
has been working on that for OMAP3, but we should make sure the OMAP4
stuff is Thumb-2 ready out of the box.
[...]
Kevin
More information about the linux-arm-kernel
mailing list