Exynos542x: Add workaround for exynos iROM errata
authorAkshay Saraswat <akshay.s@samsung.com>
Fri, 20 Feb 2015 07:57:15 +0000 (13:27 +0530)
committerMinkyu Kang <mk7.kang@samsung.com>
Sat, 28 Feb 2015 09:03:46 +0000 (18:03 +0900)
iROM logic provides undesired jump address for CPU2.
This patch adds a programmable susbstitute for a part of
iROM logic which wakes up cores and provides jump addresses.
This patch creates a logic to make all secondary cores jump
to a particular address which evades the possibility of CPU2
jumping to wrong address and create undesired results.

Logic of the workaround:

Step-1: iROM code checks value at address 0x2020028.
Step-2: If value is 0xc9cfcfcf, it jumps to the address (0x202000+CPUid*4),
else, it continues executing normally.
Step-3: Primary core puts secondary cores in WFE and store 0xc9cfcfcf in
0x2020028 and jump address (pointer to function low_power_start)
in (0x202000+CPUid*4).
Step-4: When secondary cores recieve event signal they jump to this address
and continue execution.

Signed-off-by: Kimoon Kim <kimoon.kim@samsung.com>
Signed-off-by: Akshay Saraswat <akshay.s@samsung.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Tested-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Minkyu Kang <mk7.kang@samsung.com>
arch/arm/cpu/armv7/exynos/Makefile
arch/arm/cpu/armv7/exynos/lowlevel_init.c
arch/arm/cpu/armv7/exynos/sec_boot.S [new file with mode: 0644]

index e207bd6af04a22e346d7cbc8d3b86afd26a01328..8542f896cfc9228f598781b376ba11e4f0da13d7 100644 (file)
@@ -7,6 +7,8 @@
 
 obj-y  += clock.o power.o soc.o system.o pinmux.o tzpc.o
 
+obj-$(CONFIG_EXYNOS5420)       += sec_boot.o
+
 ifdef CONFIG_SPL_BUILD
 obj-$(CONFIG_EXYNOS5)  += clock_init_exynos5.o
 obj-$(CONFIG_EXYNOS5)  += dmc_common.o dmc_init_ddr3.o
index e36f2fad6d3a3e08c291626c2ea99bae003d357b..b94e49f84980658c8ac59defdbd37a5fc32c1ec5 100644 (file)
@@ -32,6 +32,7 @@
 #include <asm/arch/periph.h>
 #include <asm/arch/pinmux.h>
 #include <asm/arch/system.h>
+#include <asm/armv7.h>
 #include "common_setup.h"
 #include "exynos5_setup.h"
 
@@ -45,6 +46,61 @@ enum {
 };
 
 #ifdef CONFIG_EXYNOS5420
+/*
+ * Power up secondary CPUs.
+ */
+static void secondary_cpu_start(void)
+{
+       v7_enable_smp(EXYNOS5420_INFORM_BASE);
+       svc32_mode_en();
+       set_pc(CONFIG_EXYNOS_RELOCATE_CODE_BASE);
+}
+
+/*
+ * This is the entry point of hotplug-in and
+ * cluster switching.
+ */
+static void low_power_start(void)
+{
+       uint32_t val, reg_val;
+
+       reg_val = readl(EXYNOS5420_SPARE_BASE);
+       if (reg_val != CPU_RST_FLAG_VAL) {
+               writel(0x0, CONFIG_LOWPOWER_FLAG);
+               set_pc(0x0);
+       }
+
+       reg_val = readl(CONFIG_PHY_IRAM_BASE + 0x4);
+       if (reg_val != (uint32_t)&low_power_start) {
+               /* Store jump address as low_power_start if not present */
+               writel((uint32_t)&low_power_start, CONFIG_PHY_IRAM_BASE + 0x4);
+               dsb();
+               sev();
+       }
+
+       /* Set the CPU to SVC32 mode */
+       svc32_mode_en();
+       v7_enable_l2_hazard_detect();
+
+       /* Invalidate L1 & TLB */
+       val = 0x0;
+       mcr_tlb(val);
+       mcr_icache(val);
+
+       /* Disable MMU stuff and caches */
+       mrc_sctlr(val);
+
+       val &= ~((0x2 << 12) | 0x7);
+       val |= ((0x1 << 12) | (0x8 << 8) | 0x2);
+       mcr_sctlr(val);
+
+       /* CPU state is hotplug or reset */
+       secondary_cpu_start();
+
+       /* Core should not enter into WFI here */
+       wfi();
+}
+
 /*
  * Pointer to this function is stored in iRam which is used
  * for jump and power down of a specific core.
@@ -81,29 +137,25 @@ static void power_down_core(void)
  */
 static void secondary_cores_configure(void)
 {
-       uint32_t core_id;
+       /* Setup L2 cache */
+       v7_enable_l2_hazard_detect();
 
-       /* Store jump address for power down of secondary cores */
+       /* Clear secondary boot iRAM base */
+       writel(0x0, (CONFIG_EXYNOS_RELOCATE_CODE_BASE + 0x1C));
+
+       /* set lowpower flag and address */
+       writel(CPU_RST_FLAG_VAL, CONFIG_LOWPOWER_FLAG);
+       writel((uint32_t)&low_power_start, CONFIG_LOWPOWER_ADDR);
+       writel(CPU_RST_FLAG_VAL, EXYNOS5420_SPARE_BASE);
+       /* Store jump address for power down */
        writel((uint32_t)&power_down_core, CONFIG_PHY_IRAM_BASE + 0x4);
 
        /* Need all core power down check */
        dsb();
        sev();
-
-       /*
-        * Power down all cores(secondary) while primary core must
-        * wait for all cores to go down.
-        */
-       for (core_id = 1; core_id != CONFIG_CORE_COUNT; core_id++) {
-               while ((readl(EXYNOS5420_CPU_STATUS_BASE
-                       + (core_id * CPU_CONFIG_STATUS_OFFSET))
-                       & 0xff) != 0x0) {
-                       isb();
-                       sev();
-               }
-               isb();
-       }
 }
+
+extern void relocate_wait_code(void);
 #endif
 
 int do_lowlevel_init(void)
@@ -114,6 +166,8 @@ int do_lowlevel_init(void)
        arch_cpu_init();
 
 #ifdef CONFIG_EXYNOS5420
+       relocate_wait_code();
+
        /* Reconfigure secondary cores */
        secondary_cores_configure();
 #endif
diff --git a/arch/arm/cpu/armv7/exynos/sec_boot.S b/arch/arm/cpu/armv7/exynos/sec_boot.S
new file mode 100644 (file)
index 0000000..dfc3455
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2013 Samsung Electronics
+ * Akshay Saraswat <akshay.s@samsung.com>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+
+#include <config.h>
+#include <asm/arch/cpu.h>
+
+       .globl relocate_wait_code
+relocate_wait_code:
+       adr     r0, code_base           @ r0: source address (start)
+       adr     r1, code_end            @ r1: source address (end)
+       ldr     r2, =0x02073000         @ r2: target address
+1:
+       ldmia   r0!, {r3-r6}
+       stmia   r2!, {r3-r6}
+       cmp     r0, r1
+       blt     1b
+       b       code_end
+       .ltorg
+/*
+ * Secondary core waits here until Primary wake it up.
+ * Below code is copied to CONFIG_EXYNOS_RELOCATE_CODE_BASE.
+ * This is a workaround code which is supposed to act as a
+ * substitute/supplement to the iROM code.
+ *
+ * This workaround code is relocated to the address 0x02073000
+ * because that comes out to be the last 4KB of the iRAM
+ * (Base Address - 0x02020000, Limit Address - 0x020740000).
+ *
+ * U-boot and kernel are aware of this code and flags by the simple
+ * fact that we are implementing a workaround in the last 4KB
+ * of the iRAM and we have already defined these flag and address
+ * values in both kernel and U-boot for our use.
+ */
+code_base:
+       b        1f
+/*
+ * These addresses are being used as flags in u-boot and kernel.
+ *
+ * Jump address for resume and flag to check for resume/reset:
+ * Resume address - 0x2073008
+ * Resume flag - 0x207300C
+ *
+ * Jump address for cluster switching:
+ * Switch address - 0x2073018
+ *
+ * Jump address for core hotplug:
+ * Hotplug address - 0x207301C
+ *
+ * Jump address for C2 state (Reserved for future not being used right now):
+ * C2 address - 0x2073024
+ *
+ * Managed per core status for the active cluster:
+ * CPU0 state - 0x2073028
+ * CPU1 state - 0x207302C
+ * CPU2 state - 0x2073030
+ * CPU3 state - 0x2073034
+ *
+ * Managed per core GIC status for the active cluster:
+ * CPU0 gic state - 0x2073038
+ * CPU1 gic state - 0x207303C
+ * CPU2 gic state - 0x2073040
+ * CPU3 gic state - 0x2073044
+ *
+ * Logic of the code:
+ * Step-1: Read current CPU status.
+ * Step-2: If it's a resume then continue, else jump to step 4.
+ * Step-3: Clear inform1 PMU register and jump to inform0 value.
+ * Step-4: If it's a switch, C2 or reset, get the hotplug address.
+ * Step-5: If address is not available, enter WFE.
+ * Step-6: If address is available, jump to that address.
+ */
+       nop                          @ for backward compatibility
+       .word   0x0                  @ REG0: RESUME_ADDR
+       .word   0x0                  @ REG1: RESUME_FLAG
+       .word   0x0                  @ REG2
+       .word   0x0                  @ REG3
+_switch_addr:
+       .word   0x0                  @ REG4: SWITCH_ADDR
+_hotplug_addr:
+       .word   0x0                  @ REG5: CPU1_BOOT_REG
+       .word   0x0                  @ REG6
+_c2_addr:
+       .word   0x0                  @ REG7: REG_C2_ADDR
+_cpu_state:
+       .word   0x1                  @ CPU0_STATE : RESET
+       .word   0x2                  @ CPU1_STATE : SECONDARY RESET
+       .word   0x2                  @ CPU2_STATE : SECONDARY RESET
+       .word   0x2                  @ CPU3_STATE : SECONDARY RESET
+_gic_state:
+       .word   0x0                  @ CPU0 - GICD_IGROUPR0
+       .word   0x0                  @ CPU1 - GICD_IGROUPR0
+       .word   0x0                  @ CPU2 - GICD_IGROUPR0
+       .word   0x0                  @ CPU3 - GICD_IGROUPR0
+1:
+       adr     r0, _cpu_state
+       mrc     p15, 0, r7, c0, c0, 5   @ read MPIDR
+       and     r7, r7, #0xf        @ r7 = cpu id
+/* Read the current cpu state */
+       ldr     r10, [r0, r7, lsl #2]
+svc_entry:
+       tst     r10, #(1 << 4)
+       adrne   r0, _switch_addr
+       bne     wait_for_addr
+/* Clear INFORM1 */
+       ldr     r0, =(0x10040000 + 0x804)
+       ldr     r1, [r0]
+       cmp     r1, #0x0
+       movne   r1, #0x0
+       strne   r1, [r0]
+/* Get INFORM0 */
+       ldrne   r1, =(0x10040000 + 0x800)
+       ldrne   pc, [r1]
+       tst     r10, #(1 << 0)
+       ldrne   pc, =0x23e00000
+       adr     r0, _hotplug_addr
+wait_for_addr:
+       ldr     r1, [r0]
+       cmp     r1, #0x0
+       bxne    r1
+       wfe
+       b        wait_for_addr
+       .ltorg
+code_end:
+       mov     pc, lr