aboutsummaryrefslogtreecommitdiff
path: root/src/soc/samsung/exynos5420/smp.c
diff options
context:
space:
mode:
authorHung-Te Lin <hungte@chromium.org>2013-09-27 12:45:45 +0800
committerIsaac Christensen <isaac.christensen@se-eng.com>2014-08-26 17:55:18 +0200
commit22d0ca0ceb802675cdcab1472b8477066f729373 (patch)
tree79e2e38a2c6b34125f48b05cfd7f9ef3c88c833d /src/soc/samsung/exynos5420/smp.c
parentb123e0d3345554d7e93361bb4511a53bc95d41a1 (diff)
armv7: Move Exynos from 'cpu' to 'soc'.
The Exynos family and most ARM products are SoC, not just CPU. We used to put ARM code in src/cpu to avoid polluting the code base for what was essentially an experiment at the time. Now that it's past the experimental phase and we're going to see more SoCs (including intel/baytrail) in coreboot. Change-Id: I5ea1f822664244edf5f77087bc8018d7c535f81c Reviewed-on: https://chromium-review.googlesource.com/170891 Tested-by: Hung-Te Lin <hungte@chromium.org> Reviewed-by: Aaron Durbin <adurbin@chromium.org> Reviewed-by: Ronald Minnich <rminnich@chromium.org> Commit-Queue: Hung-Te Lin <hungte@chromium.org> (cherry picked from commit c8bb8fe0b20be37465f93c738d80e7e43033670a) Signed-off-by: Isaac Christensen <isaac.christensen@se-eng.com> Reviewed-on: http://review.coreboot.org/6739 Tested-by: build bot (Jenkins) Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
Diffstat (limited to 'src/soc/samsung/exynos5420/smp.c')
-rw-r--r--src/soc/samsung/exynos5420/smp.c307
1 files changed, 307 insertions, 0 deletions
diff --git a/src/soc/samsung/exynos5420/smp.c b/src/soc/samsung/exynos5420/smp.c
new file mode 100644
index 0000000000..6fc2fb01ea
--- /dev/null
+++ b/src/soc/samsung/exynos5420/smp.c
@@ -0,0 +1,307 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <types.h>
+#include <arch/cpu.h>
+#include <arch/io.h>
+
+#include "cpu.h"
+#include "power.h"
+
+/* 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(&exynos_power->inform0);
+ 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;
+
+ enable_smp();
+ set_system_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 (exynos_power->inform1) {
+ exynos_power->inform1 = 0;
+ jump_bx((void*)exynos_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(&exynos_power->spare0);
+ 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_system_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, &exynos_power->arm_core[core_id].config);
+
+ /* 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)
+{
+ if (get_bits(read_midr(), 4, 12) == PART_NUMBER_CORTEX_A15) {
+ configure_l2ctlr();
+ configure_l2actlr();
+ }
+
+ /* 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, &exynos_power->spare0);
+
+ /* 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();
+}
+