aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-omap2/omap4-mpuss-lowpower.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-omap2/omap4-mpuss-lowpower.c')
-rw-r--r--arch/arm/mach-omap2/omap4-mpuss-lowpower.c824
1 files changed, 824 insertions, 0 deletions
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..81511e8
--- /dev/null
+++ b/arch/arm/mach-omap2/omap4-mpuss-lowpower.c
@@ -0,0 +1,824 @@
+/*
+ * OMAP4 MPUSS low power code
+ *
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ * Written by Santosh Shilimkar <santosh.shilimkar@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
+ * OFF OFF OFF
+ * ----------------------------------------------
+ *
+ * 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 <linux/dma-mapping.h>
+
+#include <asm/tlbflush.h>
+#include <asm/smp_scu.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+#include <asm/hardware/gic.h>
+#include <asm/hardware/cache-l2x0.h>
+
+#include <plat/omap44xx.h>
+#include <mach/omap4-common.h>
+#include <mach/omap-wakeupgen.h>
+
+#include "omap4-sar-layout.h"
+#include "pm.h"
+#include "prcm_mpu44xx.h"
+#include "prminst44xx.h"
+#include "prcm44xx.h"
+#include "prm44xx.h"
+#include "prm-regbits-44xx.h"
+#include "cm.h"
+#include "prm.h"
+#include "cm44xx.h"
+#include "prcm-common.h"
+
+#ifdef CONFIG_SMP
+
+#define GIC_MASK_ALL 0x0
+#define GIC_ISR_NON_SECURE 0xffffffff
+#define SPI_ENABLE_SET_OFFSET 0x04
+#define PPI_PRI_OFFSET 0x1c
+#define SPI_PRI_OFFSET 0x20
+#define SPI_TARGET_OFFSET 0x20
+#define SPI_CONFIG_OFFSET 0x20
+
+/* GIC save SAR bank base */
+static struct powerdomain *mpuss_pd;
+/*
+ * Maximum Secure memory storage size.
+ */
+#define OMAP4_SECURE_RAM_STORAGE (88 * SZ_1K)
+/*
+ * Physical address of secure memory storage
+ */
+dma_addr_t omap4_secure_ram_phys;
+static void *secure_ram;
+
+/* Variables to store maximum spi(Shared Peripheral Interrupts) registers. */
+static u32 max_spi_irq, max_spi_reg;
+
+struct omap4_cpu_pm_info {
+ struct powerdomain *pwrdm;
+ void __iomem *scu_sar_addr;
+};
+
+static void __iomem *gic_dist_base;
+static void __iomem *gic_cpu_base;
+static void __iomem *sar_base;
+
+static DEFINE_PER_CPU(struct omap4_cpu_pm_info, omap4_pm_info);
+
+#define PPI_CONTEXT_SIZE 11
+static DEFINE_PER_CPU(u32[PPI_CONTEXT_SIZE], gic_ppi_context);
+static DEFINE_PER_CPU(u32, gic_ppi_enable_mask);
+
+/* Helper functions */
+static inline void sar_writel(u32 val, u32 offset, u8 idx)
+{
+ __raw_writel(val, sar_base + offset + 4 * idx);
+}
+
+static inline u32 gic_readl(u32 offset, u8 idx)
+{
+ return __raw_readl(gic_dist_base + offset + 4 * idx);
+}
+
+u32 gic_cpu_read(u32 reg)
+{
+ return __raw_readl(gic_cpu_base + reg);
+}
+
+/*
+ * 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);
+}
+
+struct reg_tuple {
+ void __iomem *addr;
+ u32 val;
+};
+
+static struct reg_tuple tesla_reg[] = {
+ {.addr = OMAP4430_CM_TESLA_CLKSTCTRL},
+ {.addr = OMAP4430_CM_TESLA_TESLA_CLKCTRL},
+ {.addr = OMAP4430_PM_TESLA_PWRSTCTRL},
+};
+
+static struct reg_tuple ivahd_reg[] = {
+ {.addr = OMAP4430_CM_IVAHD_CLKSTCTRL},
+ {.addr = OMAP4430_CM_IVAHD_IVAHD_CLKCTRL},
+ {.addr = OMAP4430_CM_IVAHD_SL2_CLKCTRL},
+ {.addr = OMAP4430_PM_IVAHD_PWRSTCTRL}
+};
+
+static struct reg_tuple l3instr_reg[] = {
+ {.addr = OMAP4430_CM_L3INSTR_L3_3_CLKCTRL},
+ {.addr = OMAP4430_CM_L3INSTR_L3_INSTR_CLKCTRL},
+ {.addr = OMAP4430_CM_L3INSTR_OCP_WP1_CLKCTRL},
+};
+
+/*
+ * 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);
+}
+
+static void gic_save_ppi(void)
+{
+ void __iomem *gic_dist_base = omap4_get_gic_dist_base();
+ u32 *context = __get_cpu_var(gic_ppi_context);
+ int i = 0;
+
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_PRI);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_PRI + 0x4);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_PRI + 0x8);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_PRI + 0xc);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_PRI + 0x10);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_PRI + 0x14);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_PRI + 0x18);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_PRI + 0x1c);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_CONFIG);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_CONFIG + 0x4);
+ context[i++] = readl_relaxed(gic_dist_base + GIC_DIST_ENABLE_SET);
+
+ BUG_ON(i != PPI_CONTEXT_SIZE);
+}
+
+static void gic_restore_ppi(void)
+{
+ void __iomem *gic_dist_base = omap4_get_gic_dist_base();
+ u32 *context = __get_cpu_var(gic_ppi_context);
+ int i = 0;
+
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_PRI);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_PRI + 0x4);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_PRI + 0x8);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_PRI + 0xc);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_PRI + 0x10);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_PRI + 0x14);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_PRI + 0x18);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_PRI + 0x1c);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_CONFIG);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_CONFIG + 0x4);
+ writel_relaxed(context[i++], gic_dist_base + GIC_DIST_ENABLE_SET);
+
+ BUG_ON(i != PPI_CONTEXT_SIZE);
+}
+
+/*
+ * Mask all the PPIs. This should only be called after they have been saved
+ * through secure trap or through save_ppi(). This is primarily needed to
+ * mask the local timer irq that could be pending since timekeeping gets
+ * suspended after the local irqs are disabled. The pending interrupt would
+ * kick the CPU out of WFI immediately, and prevent it from going to the lower
+ * power states. The correct value will be restored when the CPU is brought
+ * back up by restore.
+ */
+static void gic_mask_ppi(void)
+{
+ void __iomem *gic_dist_base = omap4_get_gic_dist_base();
+
+ __get_cpu_var(gic_ppi_enable_mask) =
+ readl_relaxed(gic_dist_base + GIC_DIST_ENABLE_SET);
+ writel_relaxed(0xffffffff, gic_dist_base + GIC_DIST_ENABLE_CLEAR);
+}
+
+static void gic_unmask_ppi(void)
+{
+ void __iomem *gic_dist_base = omap4_get_gic_dist_base();
+ writel_relaxed(__get_cpu_var(gic_ppi_enable_mask),
+ gic_dist_base + GIC_DIST_ENABLE_SET);
+}
+
+/*
+ * Save GIC context in SAR RAM. Restore is done by ROM code
+ * GIC is lost only when MPU hits OSWR or OFF. It consists
+ * of a distributor and a per-CPU interface module. The GIC
+ * save restore is optimised to save only necessary registers.
+ */
+static void gic_save_context(void)
+{
+ u8 i;
+ u32 val;
+
+ /*
+ * Interrupt Clear Enable registers are inverse of set enable
+ * and hence not needed to be saved. ROM code programs it
+ * based on Set Enable register values.
+ */
+
+ /* Save CPU 0 Interrupt Set Enable register */
+ val = gic_readl(GIC_DIST_ENABLE_SET, 0);
+ sar_writel(val, ICDISER_CPU0_OFFSET, 0);
+
+ /* Disable interrupts on CPU1 */
+ sar_writel(GIC_MASK_ALL, ICDISER_CPU1_OFFSET, 0);
+
+ /* Save all SPI Set Enable register */
+ for (i = 0; i < max_spi_reg; i++) {
+ val = gic_readl(GIC_DIST_ENABLE_SET + SPI_ENABLE_SET_OFFSET, i);
+ sar_writel(val, ICDISER_SPI_OFFSET, i);
+ }
+
+ /*
+ * Interrupt Priority Registers
+ * Secure sw accesses, last 5 bits of the 8 bits (bit[7:3] are used)
+ * Non-Secure sw accesses, last 4 bits (i.e. bits[7:4] are used)
+ * But the Secure Bits[7:3] are shifted by 1 in Non-Secure access.
+ * Secure (bits[7:3] << 1)== Non Secure bits[7:4]
+ * Hence right shift the value by 1 while saving the priority
+ */
+
+ /* Save SGI priority registers (Software Generated Interrupt) */
+ for (i = 0; i < 4; i++) {
+ val = gic_readl(GIC_DIST_PRI, i);
+
+ /* Save the priority bits of the Interrupts */
+ sar_writel(val >> 0x1, ICDIPR_SFI_CPU0_OFFSET, i);
+
+ /* Disable the interrupts on CPU1 */
+ sar_writel(GIC_MASK_ALL, ICDIPR_SFI_CPU1_OFFSET, i);
+ }
+
+ /* Save PPI priority registers (Private Peripheral Intterupts) */
+ val = gic_readl(GIC_DIST_PRI + PPI_PRI_OFFSET, 0);
+ sar_writel(val >> 0x1, ICDIPR_PPI_CPU0_OFFSET, 0);
+ sar_writel(GIC_MASK_ALL, ICDIPR_PPI_CPU1_OFFSET, 0);
+
+ /* SPI priority registers - 4 interrupts/register */
+ for (i = 0; i < (max_spi_irq / 4); i++) {
+ val = gic_readl((GIC_DIST_PRI + SPI_PRI_OFFSET), i);
+ sar_writel(val >> 0x1, ICDIPR_SPI_OFFSET, i);
+ }
+
+ /* SPI Interrupt Target registers - 4 interrupts/register */
+ for (i = 0; i < (max_spi_irq / 4); i++) {
+ val = gic_readl((GIC_DIST_TARGET + SPI_TARGET_OFFSET), i);
+ sar_writel(val, ICDIPTR_SPI_OFFSET, i);
+ }
+
+ /* SPI Interrupt Congigeration eegisters- 16 interrupts/register */
+ for (i = 0; i < (max_spi_irq / 16); i++) {
+ val = gic_readl((GIC_DIST_CONFIG + SPI_CONFIG_OFFSET), i);
+ sar_writel(val, ICDICFR_OFFSET, i);
+ }
+
+ /* Set the Backup Bit Mask status for GIC */
+ val = __raw_readl(sar_base + SAR_BACKUP_STATUS_OFFSET);
+ val |= (SAR_BACKUP_STATUS_GIC_CPU0 | SAR_BACKUP_STATUS_GIC_CPU1);
+ __raw_writel(val, sar_base + SAR_BACKUP_STATUS_OFFSET);
+}
+/*
+ * API to save GIC and Wakeupgen using secure API
+ * for HS/EMU device
+ */
+static void save_gic_wakeupgen_secure(void)
+{
+ u32 ret;
+ ret = omap4_secure_dispatcher(HAL_SAVEGIC_INDEX,
+ FLAG_START_CRITICAL,
+ 0, 0, 0, 0, 0);
+ if (!ret)
+ pr_debug("GIC and Wakeupgen context save failed\n");
+}
+
+
+/*
+ * API to save Secure RAM, GIC, WakeupGen Registers using secure API
+ * for HS/EMU device
+ */
+static void save_secure_all(void)
+{
+ u32 ret;
+ ret = omap4_secure_dispatcher(HAL_SAVEALL_INDEX,
+ FLAG_START_CRITICAL,
+ 1, omap4_secure_ram_phys, 0, 0, 0);
+ if (ret)
+ pr_debug("Secure all context save failed\n");
+}
+
+/*
+ * API to save Secure RAM using secure API
+ * for HS/EMU device
+ */
+static void save_secure_ram(void)
+{
+ u32 ret;
+ ret = omap4_secure_dispatcher(HAL_SAVESECURERAM_INDEX,
+ FLAG_START_CRITICAL,
+ 1, omap4_secure_ram_phys, 0, 0, 0);
+ if (!ret)
+ pr_debug("Secure ram context save failed\n");
+}
+
+/* Helper functions for MPUSS OSWR */
+static inline u32 mpuss_read_prev_logic_pwrst(void)
+{
+ u32 reg;
+
+ reg = omap4_prminst_read_inst_reg(OMAP4430_PRM_PARTITION,
+ OMAP4430_PRM_MPU_INST, OMAP4_RM_MPU_MPU_CONTEXT_OFFSET);
+ reg &= OMAP4430_LOSTCONTEXT_DFF_MASK;
+ return reg;
+}
+
+static inline void mpuss_clear_prev_logic_pwrst(void)
+{
+ u32 reg;
+
+ reg = omap4_prminst_read_inst_reg(OMAP4430_PRM_PARTITION,
+ OMAP4430_PRM_MPU_INST, OMAP4_RM_MPU_MPU_CONTEXT_OFFSET);
+ omap4_prminst_write_inst_reg(reg, OMAP4430_PRM_PARTITION,
+ OMAP4430_PRM_MPU_INST, OMAP4_RM_MPU_MPU_CONTEXT_OFFSET);
+}
+
+static inline void cpu_clear_prev_logic_pwrst(unsigned int cpu_id)
+{
+ u32 reg;
+
+ if (cpu_id) {
+ reg = omap4_prcm_mpu_read_inst_reg(OMAP4430_PRCM_MPU_CPU1_INST,
+ OMAP4_RM_CPU1_CPU1_CONTEXT_OFFSET);
+ omap4_prcm_mpu_write_inst_reg(reg, OMAP4430_PRCM_MPU_CPU1_INST,
+ OMAP4_RM_CPU1_CPU1_CONTEXT_OFFSET);
+ } else {
+ reg = omap4_prcm_mpu_read_inst_reg(OMAP4430_PRCM_MPU_CPU0_INST,
+ OMAP4_RM_CPU0_CPU0_CONTEXT_OFFSET);
+ omap4_prcm_mpu_write_inst_reg(reg, OMAP4430_PRCM_MPU_CPU0_INST,
+ OMAP4_RM_CPU0_CPU0_CONTEXT_OFFSET);
+ }
+}
+
+static inline void save_ivahd_tesla_regs(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tesla_reg); i++)
+ tesla_reg[i].val = __raw_readl(tesla_reg[i].addr);
+
+ for (i = 0; i < ARRAY_SIZE(ivahd_reg); i++)
+ ivahd_reg[i].val = __raw_readl(ivahd_reg[i].addr);
+}
+
+static inline void restore_ivahd_tesla_regs(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tesla_reg); i++)
+ __raw_writel(tesla_reg[i].val, tesla_reg[i].addr);
+
+ for (i = 0; i < ARRAY_SIZE(ivahd_reg); i++)
+ __raw_writel(ivahd_reg[i].val, ivahd_reg[i].addr);
+}
+
+static inline void save_l3instr_regs(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(l3instr_reg); i++)
+ l3instr_reg[i].val = __raw_readl(l3instr_reg[i].addr);
+}
+
+static inline void restore_l3instr_regs(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(l3instr_reg); i++)
+ __raw_writel(l3instr_reg[i].val, l3instr_reg[i].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.
+ *
+ * MPUSS Low power states
+ * The basic rule is that the MPUSS power domain must be at the higher or
+ * equal power state (state that consume more power) than the higher of the
+ * two CPUs. For example, it is illegal for system power to be OFF, while
+ * the power of one or both of the CPU is DORMANT. When an illegal state is
+ * entered, then the hardware behavior is unpredictable.
+ *
+ * MPUSS state for the context save
+ * save_state =
+ * 0 - Nothing lost and no need to save: MPUSS INACTIVE
+ * 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
+ */
+int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state)
+{
+ unsigned int save_state = 0;
+ unsigned int wakeup_cpu;
+
+ 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
+ */
+ WARN_ON(1);
+ goto ret;
+ }
+
+ /*
+ * MPUSS book keeping should be executed by master
+ * CPU only which is also the last CPU to go down.
+ */
+ if (cpu)
+ goto cpu_prepare;
+
+ pwrdm_pre_transition();
+
+ /*
+ * Check MPUSS next state and save GIC if needed
+ * GIC lost during MPU OFF and OSWR
+ */
+ pwrdm_clear_all_prev_pwrst(mpuss_pd);
+ mpuss_clear_prev_logic_pwrst();
+ if (omap4_device_next_state_off()) {
+ if (omap_type() == OMAP2_DEVICE_TYPE_GP) {
+ omap_wakeupgen_save();
+ gic_save_context();
+ } else {
+ save_secure_all();
+ save_ivahd_tesla_regs();
+ save_l3instr_regs();
+ }
+ save_state = 3;
+ goto cpu_prepare;
+ }
+
+ switch (pwrdm_read_next_pwrst(mpuss_pd)) {
+ case PWRDM_POWER_RET:
+ /*
+ * MPUSS OSWR - Complete logic lost + L2$ retained.
+ * MPUSS CSWR - Complete logic retained + L2$ retained.
+ */
+ if (pwrdm_read_logic_retst(mpuss_pd) == PWRDM_POWER_OFF) {
+ if (omap_type() == OMAP2_DEVICE_TYPE_GP) {
+ omap_wakeupgen_save();
+ gic_save_context();
+ } else {
+ save_gic_wakeupgen_secure();
+ save_ivahd_tesla_regs();
+ save_l3instr_regs();
+ }
+ save_state = 2;
+ }
+ break;
+ case PWRDM_POWER_OFF:
+ /* MPUSS OFF - logic lost + L2$ lost */
+ if (omap_type() == OMAP2_DEVICE_TYPE_GP) {
+ omap_wakeupgen_save();
+ gic_save_context();
+ } else {
+ save_gic_wakeupgen_secure();
+ save_ivahd_tesla_regs();
+ save_l3instr_regs();
+ save_secure_ram();
+ }
+ save_state = 3;
+ break;
+ case PWRDM_POWER_ON:
+ case PWRDM_POWER_INACTIVE:
+ /* No need to save MPUSS context */
+ default:
+ ;
+ }
+
+cpu_prepare:
+ if (cpu)
+ gic_save_ppi();
+
+ /*
+ * mask all PPIs to prevent them from kicking us out of wfi.
+ */
+ gic_mask_ppi();
+
+ clear_cpu_prev_pwrst(cpu);
+ cpu_clear_prev_logic_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.
+ */
+ stop_critical_timings();
+ omap4_cpu_suspend(cpu, save_state);
+ start_critical_timings();
+
+ /*
+ * 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);
+
+ /*
+ * If we didn't actually get into the low power state (e.g. immediately
+ * exited wfi due to a pending interrupt), the secure side
+ * would not have restored CPU0's GIC PPI enable mask.
+ * For other CPUs, gic_restore_ppi will do that for us.
+ */
+ if (cpu)
+ gic_restore_ppi();
+ else
+ gic_unmask_ppi();
+
+ /*
+ * If !master cpu return to hotplug-path.
+ *
+ * GIC distributor control register has changed between
+ * CortexA9 r1pX and r2pX. The Control Register secure
+ * banked version is now composed of 2 bits:
+ * bit 0 == Secure Enable
+ * bit 1 == Non-Secure Enable
+ * The Non-Secure banked register has not changed
+ * Because the ROM Code is based on the r1pX GIC, the CPU1
+ * GIC restoration will cause a problem to CPU0 Non-Secure SW.
+ * The workaround must be:
+ * 1) Before doing the CPU1 wakeup, CPU0 must disable
+ * the GIC distributor
+ * 2) CPU1 must re-enable the GIC distributor on
+ * it's wakeup path.
+ */
+ if (wakeup_cpu) {
+ if (!cpu_is_omap443x())
+ gic_dist_enable();
+ goto ret;
+ }
+
+ /* Check if MPUSS lost it's logic */
+ if (mpuss_read_prev_logic_pwrst()) {
+ /* Clear SAR BACKUP status on GP devices */
+ if (omap_type() == OMAP2_DEVICE_TYPE_GP)
+ __raw_writel(0x0, sar_base + SAR_BACKUP_STATUS_OFFSET);
+ /* Enable GIC distributor and inteface on CPU0*/
+ gic_cpu_enable();
+ gic_dist_enable();
+
+ /*
+ * Dummy dispatcher call after OSWR and OFF
+ * Restore the right return Kernel address (with MMU on) for
+ * subsequent calls to secure ROM. Otherwise the return address
+ * will be to a PA return address and the system will hang.
+ */
+ if (omap_type() != OMAP2_DEVICE_TYPE_GP)
+ omap4_secure_dispatcher(PPA_SERVICE_0,
+ FLAG_START_CRITICAL,
+ 0, 0, 0, 0, 0);
+ }
+
+ if (omap4_device_prev_state_off()) {
+ restore_ivahd_tesla_regs();
+ restore_l3instr_regs();
+ }
+
+ pwrdm_post_transition();
+
+ret:
+ return 0;
+}
+
+static void save_l2x0_auxctrl(void)
+{
+#ifdef CONFIG_CACHE_L2X0
+ /*
+ * Save the L2X0 AUXCTRL value to SAR memory. Its used to
+ * in every restore patch MPUSS OFF path.
+ */
+ void __iomem *l2x0_base = omap4_get_l2cache_base();
+ u32 val;
+
+ val = __raw_readl(l2x0_base + L2X0_AUX_CTRL);
+ __raw_writel(val, sar_base + L2X0_AUXCTRL_OFFSET);
+
+ /*
+ * Save the L2X0 PREFETCH_CTRL value to SAR memory.
+ * Its used in every restore path MPUSS OFF path.
+ */
+
+ val = __raw_readl(l2x0_base + L2X0_PREFETCH_CTRL);
+ __raw_writel(val, sar_base + L2X0_PREFETCHCTRL_OFFSET);
+
+ /* Save L2X0 LOCKDOWN_OFFSET0 during SAR */
+ val = readl_relaxed(l2x0_base + 0x900);
+ writel_relaxed(val, sar_base + L2X0_LOCKDOWN_OFFSET0);
+#endif
+}
+
+/*
+ * Initialise OMAP4 MPUSS
+ */
+int __init omap4_mpuss_init(void)
+{
+ struct omap4_cpu_pm_info *pm_info;
+ u8 i;
+
+ /* Get GIC and SAR RAM base addresses */
+ sar_base = omap4_get_sar_ram_base();
+ gic_dist_base = omap4_get_gic_dist_base();
+ gic_cpu_base = omap4_get_gic_cpu_base();
+
+ if (omap_rev() == OMAP4430_REV_ES1_0) {
+ WARN(1, "Power Management not supported on OMAP4430 ES1.0\n");
+ return -ENODEV;
+ }
+
+ /* Initilaise per CPU PM information */
+ pm_info = &per_cpu(omap4_pm_info, 0x0);
+ pm_info->scu_sar_addr = sar_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);
+ cpu_clear_prev_logic_pwrst(0);
+
+ /* Initialise CPU0 power domain state to ON */
+ pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON);
+
+ pm_info = &per_cpu(omap4_pm_info, 0x1);
+ pm_info->scu_sar_addr = sar_base + SCU_OFFSET1;
+ pm_info->pwrdm = pwrdm_lookup("cpu1_pwrdm");
+ if (!pm_info->pwrdm) {
+ pr_err("Lookup failed for CPU1 pwrdm\n");
+ return -ENODEV;
+ }
+
+ /*
+ * Check the OMAP type and store it to scratchpad
+ */
+ if (omap_type() != OMAP2_DEVICE_TYPE_GP) {
+ /* Memory not released */
+ secure_ram = dma_alloc_coherent(NULL, OMAP4_SECURE_RAM_STORAGE,
+ (dma_addr_t *)&omap4_secure_ram_phys, GFP_ATOMIC);
+ if (!secure_ram)
+ pr_err("Unable to allocate secure ram storage\n");
+ writel(0x1, sar_base + OMAP_TYPE_OFFSET);
+ } else {
+ writel(0x0, sar_base + OMAP_TYPE_OFFSET);
+ }
+
+ /* Clear CPU previous power domain state */
+ pwrdm_clear_all_prev_pwrst(pm_info->pwrdm);
+ cpu_clear_prev_logic_pwrst(1);
+
+ /* 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_base + CPU1_WAKEUP_NS_PA_ADDR_OFFSET);
+ __raw_writel(virt_to_phys(omap4_cpu_resume),
+ sar_base + CPU0_WAKEUP_NS_PA_ADDR_OFFSET);
+
+ mpuss_pd = pwrdm_lookup("mpu_pwrdm");
+ if (!mpuss_pd) {
+ pr_err("Failed to get lookup for MPUSS pwrdm\n");
+ return -ENODEV;
+ }
+
+ /* Clear CPU previous power domain state */
+ pwrdm_clear_all_prev_pwrst(mpuss_pd);
+ mpuss_clear_prev_logic_pwrst();
+
+ /*
+ * Find out how many interrupts are supported.
+ * OMAP4 supports max of 128 SPIs where as GIC can support
+ * up to 1020 interrupt sources. On OMAP4, maximum SPIs are
+ * fused in DIST_CTR bit-fields as 128. Hence the code is safe
+ * from reserved register writes since its well within 1020.
+ */
+ max_spi_reg = __raw_readl(gic_dist_base + GIC_DIST_CTR) & 0x1f;
+ max_spi_irq = max_spi_reg * 32;
+
+ /*
+ * Mark the PPI and SPI interrupts as non-secure.
+ * program the SAR locations for interrupt security registers to
+ * reflect the same.
+ */
+ if (omap_type() == OMAP2_DEVICE_TYPE_GP) {
+ sar_writel(GIC_ISR_NON_SECURE, ICDISR_CPU0_OFFSET, 0);
+ sar_writel(GIC_ISR_NON_SECURE, ICDISR_CPU1_OFFSET, 0);
+ for (i = 0; i < max_spi_reg; i++)
+ sar_writel(GIC_ISR_NON_SECURE, ICDISR_SPI_OFFSET, i);
+ }
+ save_l2x0_auxctrl();
+
+ return 0;
+}
+
+#endif
+