ARM: OMAP4460: Workaround for ROM bug because of CA9 r2pX GIC control register change.
authorSantosh Shilimkar <santosh.shilimkar@ti.com>
Thu, 18 Oct 2012 09:20:05 +0000 (12:20 +0300)
committerKevin Hilman <khilman@ti.com>
Mon, 5 Nov 2012 22:26:43 +0000 (14:26 -0800)
On OMAP4+ devices, GIC register context is lost when MPUSS hits
the OSWR(Open Switch Retention). On the CPU wakeup path, ROM code
gets executed and one of the steps in it is to restore the
saved context of the GIC. The ROM Code GIC distributor restoration
is split in two parts: CPU specific register done by each CPU and
common register done by only one CPU.

Below is the abstract flow.

...............................................................
- MPUSS in OSWR state.
- CPU0 wakes up on the event(interrupt) and start executing ROM code.

[..]

- CPU0 executes "GIC Restoration:"

[...]

- CPU0 swicthes to non-secure mode and jumps to OS resume code.

[...]

- CPU0 is online in OS
- CPU0 enables the GIC distributor. GICD.Enable Non-secure = 1
- CPU0 wakes up CPU1 with clock-domain force wakeup method.
- CPU0 continues it's execution.
[..]

- CPU1 wakes up and start executing ROM code.

[..]

- CPU1 executes "GIC Restoration:"

[..]

- CPU1 swicthes to non-secure mode and jumps to OS resume code.

[...]

- CPU1 is online in OS and start executing.
[...]   -

GIC Restoration: /* Common routine for HS and GP devices */
{
       if (GICD != 1)  { /* This will be true in OSWR state */
               if (GIC_SAR_BACKUP_STATE == SAVED)
                       - CPU restores GIC distributor
               else
                       - reconfigure GIC distributor to boot values.

               GICD.Enable secure = 1
       }

       if (GIC_SAR_BACKUP_STATE == SAVED)
               - CPU restore its GIC CPU interface registers if saved.
       else
               - reconfigure its GIC CPU interface registers to boot
                       values.
}
...............................................................

So as mentioned in the flow, GICD != 1 condition decides how
the GIC registers are handled in ROM code wakeup path from
OSWR. As evident from the flow, ROM code relies on the entire
GICD register value and not specific register bits.

The assumption was valid till CortexA9 r1pX version since there
was only one banked bit to control secure and non-secure GICD.
Secure view which ROM code sees:
       bit 0 == Enable Non-secure
Non-secure view which HLOS sees:
       bit 0 == Enable secure

But GICD register has changed between CortexA9 r1pX and r2pX.
On r2pX GICD register is composed of 2 bits.
Secure view which ROM code sees:
       bit 1 == Enable Non-secure
       bit 0 == Enable secure
Non-secure view which HLOS sees:
       bit 0 == Enable Non-secure

Hence on OMAP4460(r2pX) devices, if you go through the
above flow again during CPU1 wakeup, GICD == 3 and hence
ROM code fails to understand the real wakeup power state
and reconfigures GIC distributor to boot values. This is
nasty since you loose the entire interrupt controller
context in a live system.

The ROM code fix done on next OMAP4 device (OMAP4470 - r2px) is to
check "GICD.Enable secure != 1" for GIC restoration in OSWR wakeup path.

Since ROM code can't be fixed on OMAP4460 devices, a work around
needs to be implemented. As evident from the flow, as long as
CPU1 sees GICD == 1 in it's wakeup path from OSWR, the issue
won't happen. Below is the flow with the work-around.

...............................................................
- MPUSS in OSWR state.
- CPU0 wakes up on the event(interrupt) and start executing ROM code.

[..]

- CPU0 executes "GIC Restoration:"

[..]

- CPU0 swicthes to non-secure mode and jumps to OS resume code.

[..]

- CPU0 is online in OS.
- CPU0 does GICD.Enable Non-secure = 0
- CPU0 wakes up CPU1 with clock domain force wakeup method.
- CPU0 waits for GICD.Enable Non-secure = 1
- CPU0 coninues it's execution.
[..]

- CPU1 wakes up and start executing ROM code.

[..]

- CPU1 executes "GIC Restoration:"

[..]

- CPU1 swicthes to non-secure mode and jumps to OS resume code.

[..]

- CPU1 is online in OS
- CPU1 does GICD.Enable Non-secure = 1
- CPU1 start executing
[...]
...............................................................

With this procedure, the GIC configuration done between the
CPU0 wakeup and CPU1 wakeup will not be lost but during this
short windows, the CPU0 will not receive interrupts.

The BUG is applicable to only OMAP4460(r2pX) devices.
OMAP4470 (also r2pX) is not affected by this bug because
ROM code has been fixed.

Signed-off-by: Santosh Shilimkar <santosh.shilimkar@ti.com>
Signed-off-by: Tero Kristo <t-kristo@ti.com>
Signed-off-by: Kevin Hilman <khilman@ti.com>
arch/arm/mach-omap2/common.h
arch/arm/mach-omap2/omap-headsmp.S
arch/arm/mach-omap2/omap-mpuss-lowpower.c
arch/arm/mach-omap2/omap-smp.c
arch/arm/mach-omap2/omap4-common.c
arch/arm/mach-omap2/pm.h

index 7045e4d61ac39608acb1a46ea60c5c020e067726..70993a9fcb500269e916e6bcb12a061d1743c0d0 100644 (file)
@@ -275,6 +275,7 @@ static inline void __iomem *omap4_get_scu_base(void)
 #endif
 
 extern void __init gic_init_irq(void);
+extern void gic_dist_disable(void);
 extern void omap_smc1(u32 fn, u32 arg);
 extern void __iomem *omap4_get_sar_ram_base(void);
 extern void omap_do_wfi(void);
@@ -282,6 +283,7 @@ extern void omap_do_wfi(void);
 #ifdef CONFIG_SMP
 /* Needed for secondary core boot */
 extern void omap_secondary_startup(void);
+extern void omap_secondary_startup_4460(void);
 extern u32 omap_modify_auxcoreboot0(u32 set_mask, u32 clear_mask);
 extern void omap_auxcoreboot_addr(u32 cpu_addr);
 extern u32 omap_read_auxcoreboot0(void);
index 502e3135aad3a7ca4ea41f848ec449447307be0c..0ea09faf327b2faa48d1889d63cc49246ee9eb37 100644 (file)
@@ -18,6 +18,8 @@
 #include <linux/linkage.h>
 #include <linux/init.h>
 
+#include "omap44xx.h"
+
        __CPUINIT
 
 /* Physical address needed since MMU not enabled yet on secondary core */
@@ -64,3 +66,39 @@ hold:        ldr     r12,=0x103
        b       secondary_startup
 ENDPROC(omap_secondary_startup)
 
+ENTRY(omap_secondary_startup_4460)
+hold_2:        ldr     r12,=0x103
+       dsb
+       smc     #0                      @ read from AuxCoreBoot0
+       mov     r0, r0, lsr #9
+       mrc     p15, 0, r4, c0, c0, 5
+       and     r4, r4, #0x0f
+       cmp     r0, r4
+       bne     hold_2
+
+       /*
+        * 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.
+        */
+       ldr     r1, =OMAP44XX_GIC_DIST_BASE
+       ldr     r0, [r1]
+       orr     r0, #1
+       str     r0, [r1]
+
+       /*
+        * we've been released from the wait loop,secondary_stack
+        * should now contain the SVC stack for this core
+        */
+       b       secondary_startup
+ENDPROC(omap_secondary_startup_4460)
index ff4e6a0e9c7c7ef367ddffe6d92735a7e7e3389c..c8bc3ad85f68e3042201ac1d0a1beaaadbdc482d 100644 (file)
@@ -67,6 +67,7 @@ struct omap4_cpu_pm_info {
        void __iomem *scu_sar_addr;
        void __iomem *wkup_sar_addr;
        void __iomem *l2x0_sar_addr;
+       void (*secondary_startup)(void);
 };
 
 static DEFINE_PER_CPU(struct omap4_cpu_pm_info, omap4_pm_info);
@@ -299,6 +300,7 @@ int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state)
 int __cpuinit omap4_hotplug_cpu(unsigned int cpu, unsigned int power_state)
 {
        unsigned int cpu_state = 0;
+       struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu);
 
        if (omap_rev() == OMAP4430_REV_ES1_0)
                return -ENXIO;
@@ -308,7 +310,7 @@ int __cpuinit omap4_hotplug_cpu(unsigned int cpu, unsigned int power_state)
 
        clear_cpu_prev_pwrst(cpu);
        set_cpu_next_pwrst(cpu, power_state);
-       set_cpu_wakeup_addr(cpu, virt_to_phys(omap_secondary_startup));
+       set_cpu_wakeup_addr(cpu, virt_to_phys(pm_info->secondary_startup));
        scu_pwrst_prepare(cpu, power_state);
 
        /*
@@ -359,6 +361,11 @@ int __init omap4_mpuss_init(void)
        pm_info->scu_sar_addr = sar_base + SCU_OFFSET1;
        pm_info->wkup_sar_addr = sar_base + CPU1_WAKEUP_NS_PA_ADDR_OFFSET;
        pm_info->l2x0_sar_addr = sar_base + L2X0_SAVE_OFFSET1;
+       if (cpu_is_omap446x())
+               pm_info->secondary_startup = omap_secondary_startup_4460;
+       else
+               pm_info->secondary_startup = omap_secondary_startup;
+
        pm_info->pwrdm = pwrdm_lookup("cpu1_pwrdm");
        if (!pm_info->pwrdm) {
                pr_err("Lookup failed for CPU1 pwrdm\n");
index 4d05fa8a4e487e78b59fb98784e51efee8fbaf41..7d9c0e3fedc4dac9e64903f23bc48f725b79fdfc 100644 (file)
@@ -32,6 +32,7 @@
 #include "iomap.h"
 #include "common.h"
 #include "clockdomain.h"
+#include "pm.h"
 
 #define CPU_MASK               0xff0ffff0
 #define CPU_CORTEX_A9          0x410FC090
@@ -118,6 +119,24 @@ static int __cpuinit omap4_boot_secondary(unsigned int cpu, struct task_struct *
         *      4.3.4.2 Power States of CPU0 and CPU1
         */
        if (booted) {
+               /*
+                * 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 (IS_PM44XX_ERRATUM(PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD))
+                       gic_dist_disable();
+
                clkdm_wakeup(cpu1_clkdm);
                clkdm_allow_idle(cpu1_clkdm);
        } else {
@@ -138,7 +157,14 @@ static int __cpuinit omap4_boot_secondary(unsigned int cpu, struct task_struct *
 
 static void __init wakeup_secondary(void)
 {
+       void *startup_addr = omap_secondary_startup;
        void __iomem *base = omap_get_wakeupgen_base();
+
+       if (cpu_is_omap446x()) {
+               startup_addr = omap_secondary_startup_4460;
+               pm44xx_errata |= PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD;
+       }
+
        /*
         * Write the address of secondary startup routine into the
         * AuxCoreBoot1 where ROM code will jump and start executing
@@ -146,7 +172,7 @@ static void __init wakeup_secondary(void)
         * A barrier is added to ensure that write buffer is drained
         */
        if (omap_secure_apis_support())
-               omap_auxcoreboot_addr(virt_to_phys(omap_secondary_startup));
+               omap_auxcoreboot_addr(virt_to_phys(startup_addr));
        else
                __raw_writel(virt_to_phys(omap5_secondary_startup),
                                                base + OMAP_AUX_CORE_BOOT_1);
index e1f289748c5d5d7021b8f079c878bb723466785e..72cf396a0fc2962ac219677ce3f949098c35c48c 100644 (file)
@@ -41,6 +41,7 @@ static void __iomem *l2cache_base;
 #endif
 
 static void __iomem *sar_ram_base;
+static void __iomem *gic_dist_base_addr;
 
 #ifdef CONFIG_OMAP4_ERRATA_I688
 /* Used to implement memory barrier on DRAM path */
@@ -95,7 +96,6 @@ void __init omap_barriers_init(void)
 void __init gic_init_irq(void)
 {
        void __iomem *omap_irq_base;
-       void __iomem *gic_dist_base_addr;
 
        /* Static mapping, never released */
        gic_dist_base_addr = ioremap(OMAP44XX_GIC_DIST_BASE, SZ_4K);
@@ -110,6 +110,12 @@ void __init gic_init_irq(void)
        gic_init(0, 29, gic_dist_base_addr, omap_irq_base);
 }
 
+void gic_dist_disable(void)
+{
+       if (gic_dist_base_addr)
+               __raw_writel(0x0, gic_dist_base_addr + GIC_DIST_CTRL);
+}
+
 #ifdef CONFIG_CACHE_L2X0
 
 void __iomem *omap4_get_l2cache_base(void)
index f76a0d0f839f9b55f037664ab2b859912b24ef1b..fc3c96d5e013fc55b6ed40b21b404f379b90ef19 100644 (file)
@@ -102,6 +102,8 @@ extern void enable_omap3630_toggle_l2_on_restore(void);
 static inline void enable_omap3630_toggle_l2_on_restore(void) { }
 #endif         /* defined(CONFIG_PM) && defined(CONFIG_ARCH_OMAP3) */
 
+#define PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD     (1 << 0)
+
 #if defined(CONFIG_ARCH_OMAP4)
 extern u16 pm44xx_errata;
 #define IS_PM44XX_ERRATUM(id)          (pm44xx_errata & (id))