From 0682cfefdb888807bef6ee7f3bb81615282e0390 Mon Sep 17 00:00:00 2001 From: Hung-Te Lin Date: Tue, 6 Aug 2013 20:37:55 +0800 Subject: armv7/exynos5420: Configure CPU cores for kernel to enable SMP. The SMP on Exynos 5420 requires setting a special page and entry wrappers in firmware side (SRAM) so kernel can start cores (and to switch clusters). Change-Id: I77ca98bb6cff5b13e95dd29228e4536302f0aee9 Signed-off-by: Hung-Te Lin Reviewed-on: https://gerrit.chromium.org/gerrit/64770 Reviewed-by: Ronald G. Minnich (cherry picked from commit 4a11c7ab78cc0811df0f88763b0af8b9f24e5433) Signed-off-by: Isaac Christensen Reviewed-on: http://review.coreboot.org/6405 Tested-by: build bot (Jenkins) Reviewed-by: Paul Menzel Reviewed-by: Edward O'Callaghan --- src/arch/armv7/include/arch/cpu.h | 6 + src/cpu/samsung/exynos5420/Kconfig | 9 +- src/cpu/samsung/exynos5420/Makefile.inc | 1 + src/cpu/samsung/exynos5420/cpu.h | 6 +- src/cpu/samsung/exynos5420/smp.c | 305 ++++++++++++++++++++++++++++++++ src/mainboard/google/pit/romstage.c | 1 + 6 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 src/cpu/samsung/exynos5420/smp.c diff --git a/src/arch/armv7/include/arch/cpu.h b/src/arch/armv7/include/arch/cpu.h index 2613025348..ec37a969ce 100644 --- a/src/arch/armv7/include/arch/cpu.h +++ b/src/arch/armv7/include/arch/cpu.h @@ -98,4 +98,10 @@ inline static void sev(void) asm volatile ("sev"); } +/* puts CPU into SVC32 mode and disable interrupts. */ +inline static void set_svc32_mode(void) +{ + asm volatile("msr cpsr_c, %0" :: "r"(0x13 | 0xc0)); +} + #endif /* __ARCH_CPU_H__ */ diff --git a/src/cpu/samsung/exynos5420/Kconfig b/src/cpu/samsung/exynos5420/Kconfig index d7adf6cfde..606604091b 100644 --- a/src/cpu/samsung/exynos5420/Kconfig +++ b/src/cpu/samsung/exynos5420/Kconfig @@ -46,6 +46,11 @@ config CBFS_ROM_OFFSET # 0x0202_4400: variable length bootblock checksum header. # 0x0202_4410: bootblock, assume up to 32KB in size # 0x0203_0000: romstage, assume up to 128KB in size. +# 0x0206_0000: cache for CBFS data. +# 0x0207_3000: shared (with kernel) page for cpu & secondary core states. +# the shared data is currently only <0x50 bytes so we can share +# this page with stack. +# 0x0207_3100: stack bottom # 0x0207_4000: stack pointer config BOOTBLOCK_BASE @@ -71,11 +76,11 @@ config STACK_TOP config STACK_BOTTOM hex - default 0x02073000 + default 0x02073100 config STACK_SIZE hex - default 0x1000 + default 0x0f00 # TODO We may probably move this to board-specific implementation files instead # of KConfig values. diff --git a/src/cpu/samsung/exynos5420/Makefile.inc b/src/cpu/samsung/exynos5420/Makefile.inc index 279e803a4c..319162707b 100644 --- a/src/cpu/samsung/exynos5420/Makefile.inc +++ b/src/cpu/samsung/exynos5420/Makefile.inc @@ -17,6 +17,7 @@ bootblock-y += gpio.c bootblock-$(CONFIG_BOOTBLOCK_CONSOLE) += timer.c romstage-y += spi.c +romstage-y += smp.c romstage-y += clock.c romstage-y += clock_init.c romstage-y += pinmux.c # required by s3c24x0_i2c and uart. diff --git a/src/cpu/samsung/exynos5420/cpu.h b/src/cpu/samsung/exynos5420/cpu.h index 22ef8dbf40..08f315a57d 100644 --- a/src/cpu/samsung/exynos5420/cpu.h +++ b/src/cpu/samsung/exynos5420/cpu.h @@ -224,8 +224,6 @@ #define EXYNOS5_SPI_NUM_CONTROLLERS 5 #define EXYNOS_I2C_MAX_CONTROLLERS 8 -void exynos5420_config_l2_cache(void); - extern struct tmu_info exynos5420_tmu_info; /* TODO clean up defines. */ @@ -264,4 +262,8 @@ static inline u32 get_fb_base_kb(void) return RAM_BASE_KB + RAM_SIZE_KB - FB_SIZE_KB; } +/* Procedures to setup Exynos5420 CPU */ +void exynos5420_config_l2_cache(void); +void exynos5420_config_smp(void); + #endif /* _EXYNOS5420_CPU_H */ diff --git a/src/cpu/samsung/exynos5420/smp.c b/src/cpu/samsung/exynos5420/smp.c new file mode 100644 index 0000000000..392f82d097 --- /dev/null +++ b/src/cpu/samsung/exynos5420/smp.c @@ -0,0 +1,305 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2013 Google Inc. + * Copyright (C) 2012 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +#include +#include + +/* ACTLR, L2CTLR L2ACTLR constants used in SMP core power up. */ + +#define ACTLR_SMP (1 << 6) + +#define L2CTLR_ECC_PARITY (1 << 21) +#define L2CTLR_DATA_RAM_LATENCY_MASK (7 << 0) +#define L2CTLR_TAG_RAM_LATENCY_MASK (7 << 6) +#define L2CTLR_DATA_RAM_LATENCY_CYCLES_3 (2 << 0) +#define L2CTLR_TAG_RAM_LATENCY_CYCLES_3 (2 << 6) + +#define L2ACTLR_DISABLE_CLEAN_EVICT_PUSH_EXTERNAL (1 << 3) +#define L2ACTLR_ENABLE_HAZARD_DETECT_TIMEOUT (1 << 7) +#define L2ACTLR_FORCE_L2_LOGIC_CLOCK_ENABLE_ACTIVE (1 << 27) + +/* Part number in CPU ID (MPIDR). */ +#define PART_NUMBER_CORTEX_A15 (0xc0f) + +/* State of CPU cores in Exynos 5420. */ +#define CORE_STATE_RESET (1 << 0) +#define CORE_STATE_SECONDARY_RESET (1 << 1) +#define CORE_STATE_SWITCH_CLUSTER (1 << 4) + +/* The default address to re-power on a code. */ +#define CORE_RESET_INIT_ADDRESS ((void*)0x00000000) + +/* Vectors in BL1 (0x02020000 = base of iRAM). */ +#define VECTOR_CORE_SEV_HANDLER ((void*)(intptr_t)0x02020004) +#define VECTOR_LOW_POWER_FLAG ((void*)(intptr_t)0x02020028) +#define VECTOR_LOW_POWER_ADDRESS ((void*)(intptr_t)0x0202002C) + +/* The data structure for the "CPU state" memory page (shared with kernel) + * controlling cores in active cluster. Kernel will put starting address for one + * core in "hotplug_address" before power on. Note the address is hard-coded in + * kernel (EXYNOS5420_PA_SYSRAM_NS = 0x02073000). */ +volatile struct exynos5420_cpu_states +{ + uint32_t _reserved[2]; /* RESV, +0x00 */ + uint32_t resume_address; /* REG0, +0x08 */ + uint32_t resume_flag; /* REG1, +0x0C */ + uint32_t _reg2; /* REG2, +0x10 */ + uint32_t _reg3; /* REG3, +0x14 */ + uint32_t switch_address; /* REG4, +0x18, cluster switching */ + uint32_t hotplug_address; /* REG5, +0x1C, core hotplug */ + uint32_t _reg6; /* REG6, +0x20 */ + uint32_t c2_address; /* REG7, +0x24, C2 state change */ + + /* Managed per core status for active cluster, offset: +0x28~0x38 */ + uint32_t cpu_states[4]; + + /* Managed per core GIC status for active cluster, offset: 0x38~0x48 */ + uint32_t cpu_gic_states[4]; +} *exynos_cpu_states = (volatile struct exynos5420_cpu_states*)0x02073000; + +/* When leaving core handlers and jump to hot-plug address (or cluster + * switching), we are not sure if the destination is Thumb or ARM mode. + * So a BX command is required. + */ +inline static void jump_bx(void *address) +{ + asm volatile ("bx %0" : : "r"(address)); + /* never returns. */ +} + +/* Extracts arbitrary bits from a 32-bit unsigned int. */ +inline static uint32_t get_bits(uint32_t value, uint32_t start, uint32_t len) +{ + return ((value << (sizeof(value) * 8 - len - start)) >> + (sizeof(value) * 8 - len)); +} + +/* Waits the referenced address to be ready (non-zero) and then jump into it. */ +static void wait_and_jump(volatile uint32_t* reference) +{ + while (!*reference) { + wfe(); + } + jump_bx((void*)*reference); +} + +/* Configures L2 Control Register to use 3 cycles for DATA/TAG RAM latency. */ +static void configure_l2ctlr(void) +{ + uint32_t val; + + val = read_l2ctlr(); + val &= ~(L2CTLR_DATA_RAM_LATENCY_MASK | L2CTLR_TAG_RAM_LATENCY_MASK); + val |= (L2CTLR_DATA_RAM_LATENCY_CYCLES_3 | L2CTLR_TAG_RAM_LATENCY_CYCLES_3 | + L2CTLR_ECC_PARITY); + write_l2ctlr(val); +} + +/* Configures L2 Auxiliary Control Register for Cortex A15. */ +static void configure_l2actlr(void) +{ + uint32_t val; + + val = read_l2actlr(); + val |= (L2ACTLR_DISABLE_CLEAN_EVICT_PUSH_EXTERNAL | + L2ACTLR_ENABLE_HAZARD_DETECT_TIMEOUT | + L2ACTLR_FORCE_L2_LOGIC_CLOCK_ENABLE_ACTIVE); + write_l2actlr(val); +} + +/* Initializes the CPU states to reset state. */ +static void init_exynos_cpu_states(void) { + memset((void*)exynos_cpu_states, 0, sizeof(*exynos_cpu_states)); + exynos_cpu_states->cpu_states[0] = CORE_STATE_RESET; + exynos_cpu_states->cpu_states[1] = CORE_STATE_SECONDARY_RESET; + exynos_cpu_states->cpu_states[2] = CORE_STATE_SECONDARY_RESET; + exynos_cpu_states->cpu_states[3] = CORE_STATE_SECONDARY_RESET; +} + +/* + * Ensures that the L2 logic has been used within the previous 256 cycles + * before modifying the ACTLR.SMP bit. This is required during boot before + * MMU has been enabled, or during a specified reset or power down sequence. + */ +static void enable_smp(void) +{ + uint32_t actlr, val; + + /* Enable SMP mode */ + actlr = read_actlr(); + actlr |= ACTLR_SMP; + + /* Dummy read to assure L2 access */ + val = readl((void*)INF_REG_BASE); + val &= 0; + actlr |= val; + + write_actlr(actlr); + dsb(); + isb(); +} + +/* Starts the core and jumps to correct location by its state. */ +static void core_start_execution(void) +{ + u32 cpu_id, cpu_state; + struct exynos5_power *power = samsung_get_base_power(); + + enable_smp(); + set_svc32_mode(); + + cpu_id = read_mpidr() & 0x3; /* up to 4 processors for one cluster. */ + cpu_state = exynos_cpu_states->cpu_states[cpu_id]; + + if (cpu_state & CORE_STATE_SWITCH_CLUSTER) { + wait_and_jump(&exynos_cpu_states->switch_address); + /* never returns. */ + } + + /* Standard Exynos suspend/resume. */ + if (power->inform1) { + power->inform1 = 0; + jump_bx((void*)power->inform0); + /* never returns. */ + } + + if (cpu_state & CORE_STATE_RESET) { + /* For Reset, U-Boot jumps to its starting address; + * on Coreboot, seems ok to ignore for now. */ + } + wait_and_jump(&exynos_cpu_states->hotplug_address); + /* never returns. */ +} + +/* The entry point for hotplug-in and cluster switching. */ +static void low_power_start(void) +{ + uint32_t sctlr, reg_val; + + /* On warm reset, because iRAM is not cleared, all cores will enter + * low_power_start, not the initial address. So we need to check reset + * status again, and jump to 0x0 in that case. */ + reg_val = readl((void*)RST_FLAG_REG); + if (reg_val != RST_FLAG_VAL) { + writel(0x0, VECTOR_LOW_POWER_FLAG); + jump_bx(CORE_RESET_INIT_ADDRESS); + /* restart cpu execution and never returns. */ + } + + /* Workaround for iROM EVT1. A7 core execution may flow into incorrect + * path, bypassing first jump address and makes final jump address 0x0, + * so we try to make any core set again low_power_start address, if that + * becomes zero. */ + reg_val = readl(VECTOR_CORE_SEV_HANDLER); + if (reg_val != (intptr_t)low_power_start) { + writel((intptr_t)low_power_start, VECTOR_CORE_SEV_HANDLER); + dsb(); + /* ask all cores to power on again. */ + sev(); + } + + set_svc32_mode(); + + /* Whenever a Cortex A-15 core powers on, iROM resets its L2 cache + * so we need to configure again. */ + if (get_bits(read_midr(), 4, 12) == PART_NUMBER_CORTEX_A15) { + configure_l2ctlr(); + configure_l2actlr(); + } + + /* Invalidate L1 & TLB */ + tlbiall(); + iciallu(); + + /* Disable MMU stuff and caches */ + sctlr = read_sctlr(); + sctlr &= ~(SCTLR_V | SCTLR_M | SCTLR_C); + sctlr |= (SCTLR_I | SCTLR_Z | SCTLR_A); + write_sctlr(sctlr); + + core_start_execution(); + /* The core should not return. But in order to prevent unexpected + * errors, a WFI command will help to put CPU back to idle state. */ + wfi(); +} + +/* Callback to shutdown a core, safe to be set as hot-plug address. */ +static void power_down_core(void) +{ + uint32_t mpidr, core_id; + + /* MPIDR: 0~2=ID, 8~11=cluster. On Exynos 5420, cluster will be only 0 + * or 1. */ + mpidr = read_mpidr(); + core_id = get_bits(mpidr, 0, 2) | (get_bits(mpidr, 8, 4) << 2); + + /* Set the status of the core to low. + * S5E5420A User Manual, 8.8.1.202, ARM_CORE0_CONFIGURATION, two bits to + * control power state in each power down level. + */ + writel(0x0, (void*)(ARM_CORE0_CONFIG + core_id * CORE_CONFIG_OFFSET)); + + /* S5E5420A User Manual, 8.4.2.5, after ARM_CORE*_CONFIGURATION has been + * set to zero, PMU will detect and wait for WFI then run power-down + * sequence. */ + wfi(); +} + +/* Configures the CPU states shard memory page and then shutdown all cores. */ +static void configure_secondary_cores(void) +{ + configure_l2ctlr(); + + /* Currently we use power_down_core as callback for each core to + * shutdown itself, but it is also ok to directly set ARM_CORE*_CONFIG + * to zero by CPU0 because every secondary cores should be already in + * WFI state (in bootblock). The power_down_core will be more helpful + * when we want to use SMP inside firmware. */ + + /* Clear boot reg (hotplug address) in cpu states */ + writel(0, (void*)&exynos_cpu_states->hotplug_address); + + /* set low_power flag and address */ + writel((intptr_t)low_power_start, VECTOR_LOW_POWER_ADDRESS); + writel(RST_FLAG_VAL, VECTOR_LOW_POWER_FLAG); + writel(RST_FLAG_VAL, (void*)RST_FLAG_REG); + + /* On next SEV, shutdown all cores. */ + writel((intptr_t)power_down_core, VECTOR_CORE_SEV_HANDLER); + + /* Ask all cores in WFE mode to shutdown. */ + dsb(); + sev(); +} + +/* Configures the SMP cores on Exynos 5420 SOC (and shutdown all secondary + * cores) */ +void exynos5420_config_smp(void) +{ + init_exynos_cpu_states(); + configure_secondary_cores(); +} + diff --git a/src/mainboard/google/pit/romstage.c b/src/mainboard/google/pit/romstage.c index 7733eca1c7..7037112d04 100644 --- a/src/mainboard/google/pit/romstage.c +++ b/src/mainboard/google/pit/romstage.c @@ -246,6 +246,7 @@ void main(void) int is_resume = (get_wakeup_state() != IS_NOT_WAKEUP); int power_init_failed; + exynos5420_config_smp(); power_init_failed = setup_power(is_resume); /* Clock must be initialized before console_init, otherwise you may need -- cgit v1.2.3