diff options
Diffstat (limited to 'arch/arm/mach-exynos/firmware.c')
-rw-r--r-- | arch/arm/mach-exynos/firmware.c | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/firmware.c b/arch/arm/mach-exynos/firmware.c new file mode 100644 index 000000000..2da5b60b5 --- /dev/null +++ b/arch/arm/mach-exynos/firmware.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2012 Samsung Electronics. +// Kyungmin Park <kyungmin.park@samsung.com> +// Tomasz Figa <t.figa@samsung.com> + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include <asm/cacheflush.h> +#include <asm/cputype.h> +#include <asm/firmware.h> +#include <asm/hardware/cache-l2x0.h> +#include <asm/suspend.h> + +#include "common.h" +#include "smc.h" + +#define EXYNOS_BOOT_ADDR 0x8 +#define EXYNOS_BOOT_FLAG 0xc + +static void exynos_save_cp15(void) +{ + /* Save Power control and Diagnostic registers */ + asm ("mrc p15, 0, %0, c15, c0, 0\n" + "mrc p15, 0, %1, c15, c0, 1\n" + : "=r" (cp15_save_power), "=r" (cp15_save_diag) + : : "cc"); +} + +static int exynos_do_idle(unsigned long mode) +{ + switch (mode) { + case FW_DO_IDLE_AFTR: + if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) + exynos_save_cp15(); + writel_relaxed(__pa_symbol(exynos_cpu_resume_ns), + sysram_ns_base_addr + 0x24); + writel_relaxed(EXYNOS_AFTR_MAGIC, sysram_ns_base_addr + 0x20); + if (soc_is_exynos3250()) { + flush_cache_all(); + exynos_smc(SMC_CMD_SAVE, OP_TYPE_CORE, + SMC_POWERSTATE_IDLE, 0); + exynos_smc(SMC_CMD_SHUTDOWN, OP_TYPE_CLUSTER, + SMC_POWERSTATE_IDLE, 0); + } else + exynos_smc(SMC_CMD_CPU0AFTR, 0, 0, 0); + break; + case FW_DO_IDLE_SLEEP: + exynos_smc(SMC_CMD_SLEEP, 0, 0, 0); + } + return 0; +} + +static int exynos_cpu_boot(int cpu) +{ + /* + * Exynos3250 doesn't need to send smc command for secondary CPU boot + * because Exynos3250 removes WFE in secure mode. + * + * On Exynos5 devices the call is ignored by trustzone firmware. + */ + if (!soc_is_exynos4210() && !soc_is_exynos4412()) + return 0; + + /* + * The second parameter of SMC_CMD_CPU1BOOT command means CPU id. + */ + exynos_smc(SMC_CMD_CPU1BOOT, cpu, 0, 0); + return 0; +} + +static int exynos_set_cpu_boot_addr(int cpu, unsigned long boot_addr) +{ + void __iomem *boot_reg; + + if (!sysram_ns_base_addr) + return -ENODEV; + + boot_reg = sysram_ns_base_addr + 0x1c; + + /* + * Almost all Exynos-series of SoCs that run in secure mode don't need + * additional offset for every CPU, with Exynos4412 being the only + * exception. + */ + if (soc_is_exynos4412()) + boot_reg += 4 * cpu; + + writel_relaxed(boot_addr, boot_reg); + return 0; +} + +static int exynos_get_cpu_boot_addr(int cpu, unsigned long *boot_addr) +{ + void __iomem *boot_reg; + + if (!sysram_ns_base_addr) + return -ENODEV; + + boot_reg = sysram_ns_base_addr + 0x1c; + + if (soc_is_exynos4412()) + boot_reg += 4 * cpu; + + *boot_addr = readl_relaxed(boot_reg); + return 0; +} + +static int exynos_cpu_suspend(unsigned long arg) +{ + flush_cache_all(); + outer_flush_all(); + + exynos_smc(SMC_CMD_SLEEP, 0, 0, 0); + + pr_info("Failed to suspend the system\n"); + writel(0, sysram_ns_base_addr + EXYNOS_BOOT_FLAG); + return 1; +} + +static int exynos_suspend(void) +{ + if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) + exynos_save_cp15(); + + writel(EXYNOS_SLEEP_MAGIC, sysram_ns_base_addr + EXYNOS_BOOT_FLAG); + writel(__pa_symbol(exynos_cpu_resume_ns), + sysram_ns_base_addr + EXYNOS_BOOT_ADDR); + + return cpu_suspend(0, exynos_cpu_suspend); +} + +static int exynos_resume(void) +{ + writel(0, sysram_ns_base_addr + EXYNOS_BOOT_FLAG); + + return 0; +} + +static const struct firmware_ops exynos_firmware_ops = { + .do_idle = IS_ENABLED(CONFIG_EXYNOS_CPU_SUSPEND) ? exynos_do_idle : NULL, + .set_cpu_boot_addr = exynos_set_cpu_boot_addr, + .get_cpu_boot_addr = exynos_get_cpu_boot_addr, + .cpu_boot = exynos_cpu_boot, + .suspend = IS_ENABLED(CONFIG_PM_SLEEP) ? exynos_suspend : NULL, + .resume = IS_ENABLED(CONFIG_EXYNOS_CPU_SUSPEND) ? exynos_resume : NULL, +}; + +static void exynos_l2_write_sec(unsigned long val, unsigned reg) +{ + static int l2cache_enabled; + + switch (reg) { + case L2X0_CTRL: + if (val & L2X0_CTRL_EN) { + /* + * Before the cache can be enabled, due to firmware + * design, SMC_CMD_L2X0INVALL must be called. + */ + if (!l2cache_enabled) { + exynos_smc(SMC_CMD_L2X0INVALL, 0, 0, 0); + l2cache_enabled = 1; + } + } else { + l2cache_enabled = 0; + } + exynos_smc(SMC_CMD_L2X0CTRL, val, 0, 0); + break; + + case L2X0_DEBUG_CTRL: + exynos_smc(SMC_CMD_L2X0DEBUG, val, 0, 0); + break; + + default: + WARN_ONCE(1, "%s: ignoring write to reg 0x%x\n", __func__, reg); + } +} + +static void exynos_l2_configure(const struct l2x0_regs *regs) +{ + exynos_smc(SMC_CMD_L2X0SETUP1, regs->tag_latency, regs->data_latency, + regs->prefetch_ctrl); + exynos_smc(SMC_CMD_L2X0SETUP2, regs->pwr_ctrl, regs->aux_ctrl, 0); +} + +bool __init exynos_secure_firmware_available(void) +{ + struct device_node *nd; + const __be32 *addr; + + nd = of_find_compatible_node(NULL, NULL, + "samsung,secure-firmware"); + if (!nd) + return false; + + addr = of_get_address(nd, 0, NULL, NULL); + of_node_put(nd); + if (!addr) { + pr_err("%s: No address specified.\n", __func__); + return false; + } + + return true; +} + +void __init exynos_firmware_init(void) +{ + if (!exynos_secure_firmware_available()) + return; + + pr_info("Running under secure firmware.\n"); + + register_firmware_ops(&exynos_firmware_ops); + + /* + * Exynos 4 SoCs (based on Cortex A9 and equipped with L2C-310), + * running under secure firmware, require certain registers of L2 + * cache controller to be written in secure mode. Here .write_sec + * callback is provided to perform necessary SMC calls. + */ + if (IS_ENABLED(CONFIG_CACHE_L2X0) && + read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) { + outer_cache.write_sec = exynos_l2_write_sec; + outer_cache.configure = exynos_l2_configure; + } +} + +#define REG_CPU_STATE_ADDR (sysram_ns_base_addr + 0x28) +#define BOOT_MODE_MASK 0x1f + +void exynos_set_boot_flag(unsigned int cpu, unsigned int mode) +{ + unsigned int tmp; + + tmp = readl_relaxed(REG_CPU_STATE_ADDR + cpu * 4); + + if (mode & BOOT_MODE_MASK) + tmp &= ~BOOT_MODE_MASK; + + tmp |= mode; + writel_relaxed(tmp, REG_CPU_STATE_ADDR + cpu * 4); +} + +void exynos_clear_boot_flag(unsigned int cpu, unsigned int mode) +{ + unsigned int tmp; + + tmp = readl_relaxed(REG_CPU_STATE_ADDR + cpu * 4); + tmp &= ~mode; + writel_relaxed(tmp, REG_CPU_STATE_ADDR + cpu * 4); +} |