diff options
Diffstat (limited to 'arch/arm/mach-exynos')
-rw-r--r-- | arch/arm/mach-exynos/Kconfig | 124 | ||||
-rw-r--r-- | arch/arm/mach-exynos/Makefile | 14 | ||||
-rw-r--r-- | arch/arm/mach-exynos/common.h | 165 | ||||
-rw-r--r-- | arch/arm/mach-exynos/exynos-smc.S | 20 | ||||
-rw-r--r-- | arch/arm/mach-exynos/exynos.c | 223 | ||||
-rw-r--r-- | arch/arm/mach-exynos/firmware.c | 255 | ||||
-rw-r--r-- | arch/arm/mach-exynos/headsmp.S | 39 | ||||
-rw-r--r-- | arch/arm/mach-exynos/mcpm-exynos.c | 310 | ||||
-rw-r--r-- | arch/arm/mach-exynos/platsmp.c | 450 | ||||
-rw-r--r-- | arch/arm/mach-exynos/pm.c | 338 | ||||
-rw-r--r-- | arch/arm/mach-exynos/sleep.S | 125 | ||||
-rw-r--r-- | arch/arm/mach-exynos/smc.h | 48 | ||||
-rw-r--r-- | arch/arm/mach-exynos/suspend.c | 701 |
13 files changed, 2812 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig new file mode 100644 index 000000000..4d3b40e40 --- /dev/null +++ b/arch/arm/mach-exynos/Kconfig @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. +# http://www.samsung.com/ + +# Configuration options for the Samsung Exynos + +menuconfig ARCH_EXYNOS + bool "Samsung Exynos" + depends on ARCH_MULTI_V7 + select ARM_AMBA + select ARM_GIC + select EXYNOS_IRQ_COMBINER + select COMMON_CLK_SAMSUNG + select EXYNOS_THERMAL + select EXYNOS_PMU + select EXYNOS_SROM + select EXYNOS_PM_DOMAINS if PM_GENERIC_DOMAINS + select HAVE_ARM_ARCH_TIMER if ARCH_EXYNOS5 + select HAVE_ARM_SCU if SMP + select PINCTRL + select PINCTRL_EXYNOS + select PM_GENERIC_DOMAINS if PM + select S5P_DEV_MFC + select SAMSUNG_MC + select SOC_SAMSUNG + select SRAM + select THERMAL + select THERMAL_OF + select MFD_SYSCON + select MEMORY + select CLKSRC_EXYNOS_MCT + select POWER_RESET + select POWER_RESET_SYSCON + select POWER_RESET_SYSCON_POWEROFF + help + Support for Samsung Exynos SoCs + +if ARCH_EXYNOS + +config S5P_DEV_MFC + bool + help + Compile in setup memory (init) code for MFC + +config ARCH_EXYNOS3 + bool "Samsung Exynos3" + default y + select ARM_CPU_SUSPEND if PM + help + Samsung Exynos3 (Cortex-A7) SoC based systems + +config ARCH_EXYNOS4 + bool "Samsung Exynos4" + default y + select ARM_CPU_SUSPEND if PM_SLEEP + select CLKSRC_SAMSUNG_PWM if CPU_EXYNOS4210 + select CPU_EXYNOS4210 + select GIC_NON_BANKED + help + Samsung Exynos4 (Cortex-A9) SoC based systems + +config ARCH_EXYNOS5 + bool "Samsung Exynos5" + default y + help + Samsung Exynos5 (Cortex-A15/A7) SoC based systems + +comment "Exynos SoCs" + +config SOC_EXYNOS3250 + bool "Samsung Exynos3250" + default y + depends on ARCH_EXYNOS3 + +config CPU_EXYNOS4210 + bool "Samsung Exynos4210" + default y + depends on ARCH_EXYNOS4 + +config SOC_EXYNOS4412 + bool "Samsung Exynos4412" + default y + depends on ARCH_EXYNOS4 + +config SOC_EXYNOS5250 + bool "Samsung Exynos5250" + default y + depends on ARCH_EXYNOS5 + +config SOC_EXYNOS5260 + bool "Samsung Exynos5260" + default y + depends on ARCH_EXYNOS5 + +config SOC_EXYNOS5410 + bool "Samsung Exynos5410" + default y + depends on ARCH_EXYNOS5 + +config SOC_EXYNOS5420 + bool "Samsung Exynos5420" + default y + depends on ARCH_EXYNOS5 + select EXYNOS_MCPM if SMP + select ARM_CCI400_PORT_CTRL + select ARM_CPU_SUSPEND + +config SOC_EXYNOS5800 + bool "Samsung EXYNOS5800" + default y + depends on SOC_EXYNOS5420 + select EXYNOS_REGULATOR_COUPLER + +config EXYNOS_MCPM + bool + select MCPM + +config EXYNOS_CPU_SUSPEND + bool + select ARM_CPU_SUSPEND + default PM_SLEEP || ARM_EXYNOS_CPUIDLE + +endif diff --git a/arch/arm/mach-exynos/Makefile b/arch/arm/mach-exynos/Makefile new file mode 100644 index 000000000..53fa363c8 --- /dev/null +++ b/arch/arm/mach-exynos/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. +# http://www.samsung.com/ + +obj-$(CONFIG_ARCH_EXYNOS) += exynos.o exynos-smc.o firmware.o + +obj-$(CONFIG_EXYNOS_CPU_SUSPEND) += pm.o sleep.o +obj-$(CONFIG_PM_SLEEP) += suspend.o + +obj-$(CONFIG_SMP) += platsmp.o headsmp.o + +obj-$(CONFIG_EXYNOS_MCPM) += mcpm-exynos.o +CFLAGS_mcpm-exynos.o += -march=armv7-a diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h new file mode 100644 index 000000000..29eb075b2 --- /dev/null +++ b/arch/arm/mach-exynos/common.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Common Header for Exynos machines + */ + +#ifndef __ARCH_ARM_MACH_EXYNOS_COMMON_H +#define __ARCH_ARM_MACH_EXYNOS_COMMON_H + +#include <linux/platform_data/cpuidle-exynos.h> + +#define EXYNOS3250_SOC_ID 0xE3472000 +#define EXYNOS3_SOC_MASK 0xFFFFF000 + +#define EXYNOS4210_CPU_ID 0x43210000 +#define EXYNOS4412_CPU_ID 0xE4412200 +#define EXYNOS4_CPU_MASK 0xFFFE0000 + +#define EXYNOS5250_SOC_ID 0x43520000 +#define EXYNOS5410_SOC_ID 0xE5410000 +#define EXYNOS5420_SOC_ID 0xE5420000 +#define EXYNOS5800_SOC_ID 0xE5422000 +#define EXYNOS5_SOC_MASK 0xFFFFF000 + +extern unsigned long exynos_cpu_id; + +#define IS_SAMSUNG_CPU(name, id, mask) \ +static inline int is_samsung_##name(void) \ +{ \ + return ((exynos_cpu_id & mask) == (id & mask)); \ +} + +IS_SAMSUNG_CPU(exynos3250, EXYNOS3250_SOC_ID, EXYNOS3_SOC_MASK) +IS_SAMSUNG_CPU(exynos4210, EXYNOS4210_CPU_ID, EXYNOS4_CPU_MASK) +IS_SAMSUNG_CPU(exynos4412, EXYNOS4412_CPU_ID, EXYNOS4_CPU_MASK) +IS_SAMSUNG_CPU(exynos5250, EXYNOS5250_SOC_ID, EXYNOS5_SOC_MASK) +IS_SAMSUNG_CPU(exynos5410, EXYNOS5410_SOC_ID, EXYNOS5_SOC_MASK) +IS_SAMSUNG_CPU(exynos5420, EXYNOS5420_SOC_ID, EXYNOS5_SOC_MASK) +IS_SAMSUNG_CPU(exynos5800, EXYNOS5800_SOC_ID, EXYNOS5_SOC_MASK) + +#if defined(CONFIG_SOC_EXYNOS3250) +# define soc_is_exynos3250() is_samsung_exynos3250() +#else +# define soc_is_exynos3250() 0 +#endif + +#if defined(CONFIG_CPU_EXYNOS4210) +# define soc_is_exynos4210() is_samsung_exynos4210() +#else +# define soc_is_exynos4210() 0 +#endif + +#if defined(CONFIG_SOC_EXYNOS4412) +# define soc_is_exynos4412() is_samsung_exynos4412() +#else +# define soc_is_exynos4412() 0 +#endif + +#define EXYNOS4210_REV_0 (0x0) +#define EXYNOS4210_REV_1_0 (0x10) +#define EXYNOS4210_REV_1_1 (0x11) + +#if defined(CONFIG_SOC_EXYNOS5250) +# define soc_is_exynos5250() is_samsung_exynos5250() +#else +# define soc_is_exynos5250() 0 +#endif + +#if defined(CONFIG_SOC_EXYNOS5410) +# define soc_is_exynos5410() is_samsung_exynos5410() +#else +# define soc_is_exynos5410() 0 +#endif + +#if defined(CONFIG_SOC_EXYNOS5420) +# define soc_is_exynos5420() is_samsung_exynos5420() +#else +# define soc_is_exynos5420() 0 +#endif + +#if defined(CONFIG_SOC_EXYNOS5800) +# define soc_is_exynos5800() is_samsung_exynos5800() +#else +# define soc_is_exynos5800() 0 +#endif + +extern u32 cp15_save_diag; +extern u32 cp15_save_power; + +extern void __iomem *sysram_ns_base_addr; +extern void __iomem *sysram_base_addr; +extern phys_addr_t sysram_base_phys; +extern void __iomem *pmu_base_addr; +void exynos_sysram_init(void); + +enum { + FW_DO_IDLE_SLEEP, + FW_DO_IDLE_AFTR, +}; + +void exynos_firmware_init(void); + +/* CPU BOOT mode flag for Exynos3250 SoC bootloader */ +#define C2_STATE (1 << 3) +/* + * Magic values for bootloader indicating chosen low power mode. + * See also Documentation/arm/samsung/bootloader-interface.rst + */ +#define EXYNOS_SLEEP_MAGIC 0x00000bad +#define EXYNOS_AFTR_MAGIC 0xfcba0d10 + +bool __init exynos_secure_firmware_available(void); +void exynos_set_boot_flag(unsigned int cpu, unsigned int mode); +void exynos_clear_boot_flag(unsigned int cpu, unsigned int mode); + +#ifdef CONFIG_PM_SLEEP +extern void __init exynos_pm_init(void); +#else +static inline void exynos_pm_init(void) {} +#endif + +extern void exynos_cpu_resume(void); +extern void exynos_cpu_resume_ns(void); + +extern const struct smp_operations exynos_smp_ops; + +extern void exynos_cpu_power_down(int cpu); +extern void exynos_cpu_power_up(int cpu); +extern int exynos_cpu_power_state(int cpu); +extern void exynos_cluster_power_down(int cluster); +extern void exynos_cluster_power_up(int cluster); +extern int exynos_cluster_power_state(int cluster); +extern void exynos_cpu_save_register(void); +extern void exynos_cpu_restore_register(void); +extern void exynos_pm_central_suspend(void); +extern int exynos_pm_central_resume(void); +extern void exynos_enter_aftr(void); +#ifdef CONFIG_SMP +extern void exynos_scu_enable(void); +#else +static inline void exynos_scu_enable(void) { } +#endif + +extern struct cpuidle_exynos_data cpuidle_coupled_exynos_data; + +extern void exynos_set_delayed_reset_assertion(bool enable); + +extern unsigned int exynos_rev(void); +extern void exynos_core_restart(u32 core_id); +extern int exynos_set_boot_addr(u32 core_id, unsigned long boot_addr); +extern int exynos_get_boot_addr(u32 core_id, unsigned long *boot_addr); + +static inline void pmu_raw_writel(u32 val, u32 offset) +{ + writel_relaxed(val, pmu_base_addr + offset); +} + +static inline u32 pmu_raw_readl(u32 offset) +{ + return readl_relaxed(pmu_base_addr + offset); +} + +#endif /* __ARCH_ARM_MACH_EXYNOS_COMMON_H */ diff --git a/arch/arm/mach-exynos/exynos-smc.S b/arch/arm/mach-exynos/exynos-smc.S new file mode 100644 index 000000000..6da31e6a7 --- /dev/null +++ b/arch/arm/mach-exynos/exynos-smc.S @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2012 Samsung Electronics. + * + * Copied from omap-smc.S Copyright (C) 2010 Texas Instruments, Inc. + */ + +#include <linux/linkage.h> + +/* + * Function signature: void exynos_smc(u32 cmd, u32 arg1, u32 arg2, u32 arg3) + */ + .arch armv7-a + .arch_extension sec +ENTRY(exynos_smc) + stmfd sp!, {r4-r11, lr} + dsb + smc #0 + ldmfd sp!, {r4-r11, pc} +ENDPROC(exynos_smc) diff --git a/arch/arm/mach-exynos/exynos.c b/arch/arm/mach-exynos/exynos.c new file mode 100644 index 000000000..51a247ca4 --- /dev/null +++ b/arch/arm/mach-exynos/exynos.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Samsung Exynos Flattened Device Tree enabled machine +// +// Copyright (c) 2010-2014 Samsung Electronics Co., Ltd. +// http://www.samsung.com + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_fdt.h> +#include <linux/platform_device.h> +#include <linux/irqchip.h> +#include <linux/soc/samsung/exynos-regs-pmu.h> + +#include <asm/cacheflush.h> +#include <asm/hardware/cache-l2x0.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include "common.h" + +#define S3C_ADDR_BASE 0xF6000000 +#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x)) +#define S5P_VA_CHIPID S3C_ADDR(0x02000000) + +static struct platform_device exynos_cpuidle = { + .name = "exynos_cpuidle", +#ifdef CONFIG_ARM_EXYNOS_CPUIDLE + .dev.platform_data = exynos_enter_aftr, +#endif + .id = -1, +}; + +void __iomem *sysram_base_addr __ro_after_init; +phys_addr_t sysram_base_phys __ro_after_init; +void __iomem *sysram_ns_base_addr __ro_after_init; + +unsigned long exynos_cpu_id; +static unsigned int exynos_cpu_rev; + +unsigned int exynos_rev(void) +{ + return exynos_cpu_rev; +} + +void __init exynos_sysram_init(void) +{ + struct device_node *node; + + for_each_compatible_node(node, NULL, "samsung,exynos4210-sysram") { + if (!of_device_is_available(node)) + continue; + sysram_base_addr = of_iomap(node, 0); + sysram_base_phys = of_translate_address(node, + of_get_address(node, 0, NULL, NULL)); + of_node_put(node); + break; + } + + for_each_compatible_node(node, NULL, "samsung,exynos4210-sysram-ns") { + if (!of_device_is_available(node)) + continue; + sysram_ns_base_addr = of_iomap(node, 0); + of_node_put(node); + break; + } +} + +static int __init exynos_fdt_map_chipid(unsigned long node, const char *uname, + int depth, void *data) +{ + struct map_desc iodesc; + const __be32 *reg; + int len; + + if (!of_flat_dt_is_compatible(node, "samsung,exynos4210-chipid")) + return 0; + + reg = of_get_flat_dt_prop(node, "reg", &len); + if (reg == NULL || len != (sizeof(unsigned long) * 2)) + return 0; + + iodesc.pfn = __phys_to_pfn(be32_to_cpu(reg[0])); + iodesc.length = be32_to_cpu(reg[1]) - 1; + iodesc.virtual = (unsigned long)S5P_VA_CHIPID; + iodesc.type = MT_DEVICE; + iotable_init(&iodesc, 1); + return 1; +} + +static void __init exynos_init_io(void) +{ + debug_ll_io_init(); + + of_scan_flat_dt(exynos_fdt_map_chipid, NULL); + + /* detect cpu id and rev. */ + exynos_cpu_id = readl_relaxed(S5P_VA_CHIPID); + exynos_cpu_rev = exynos_cpu_id & 0xFF; + + pr_info("Samsung CPU ID: 0x%08lx\n", exynos_cpu_id); + +} + +/* + * Set or clear the USE_DELAYED_RESET_ASSERTION option. Used by smp code + * and suspend. + * + * This is necessary only on Exynos4 SoCs. When system is running + * USE_DELAYED_RESET_ASSERTION should be set so the ARM CLK clock down + * feature could properly detect global idle state when secondary CPU is + * powered down. + * + * However this should not be set when such system is going into suspend. + */ +void exynos_set_delayed_reset_assertion(bool enable) +{ + if (of_machine_is_compatible("samsung,exynos4")) { + unsigned int tmp, core_id; + + for (core_id = 0; core_id < num_possible_cpus(); core_id++) { + tmp = pmu_raw_readl(EXYNOS_ARM_CORE_OPTION(core_id)); + if (enable) + tmp |= S5P_USE_DELAYED_RESET_ASSERTION; + else + tmp &= ~(S5P_USE_DELAYED_RESET_ASSERTION); + pmu_raw_writel(tmp, EXYNOS_ARM_CORE_OPTION(core_id)); + } + } +} + +/* + * Apparently, these SoCs are not able to wake-up from suspend using + * the PMU. Too bad. Should they suddenly become capable of such a + * feat, the matches below should be moved to suspend.c. + */ +static const struct of_device_id exynos_dt_pmu_match[] = { + { .compatible = "samsung,exynos5260-pmu" }, + { .compatible = "samsung,exynos5410-pmu" }, + { /*sentinel*/ }, +}; + +static void exynos_map_pmu(void) +{ + struct device_node *np; + + np = of_find_matching_node(NULL, exynos_dt_pmu_match); + if (np) + pmu_base_addr = of_iomap(np, 0); + of_node_put(np); +} + +static void __init exynos_init_irq(void) +{ + irqchip_init(); + /* + * Since platsmp.c needs pmu base address by the time + * DT is not unflatten so we can't use DT APIs before + * init_irq + */ + exynos_map_pmu(); +} + +static void __init exynos_dt_machine_init(void) +{ + /* + * This is called from smp_prepare_cpus if we've built for SMP, but + * we still need to set it up for PM and firmware ops if not. + */ + if (!IS_ENABLED(CONFIG_SMP)) + exynos_sysram_init(); + +#if defined(CONFIG_SMP) && defined(CONFIG_ARM_EXYNOS_CPUIDLE) + if (of_machine_is_compatible("samsung,exynos4210") || + of_machine_is_compatible("samsung,exynos3250")) + exynos_cpuidle.dev.platform_data = &cpuidle_coupled_exynos_data; +#endif + if (of_machine_is_compatible("samsung,exynos4210") || + (of_machine_is_compatible("samsung,exynos4412") && + (of_machine_is_compatible("samsung,trats2") || + of_machine_is_compatible("samsung,midas") || + of_machine_is_compatible("samsung,p4note"))) || + of_machine_is_compatible("samsung,exynos3250") || + of_machine_is_compatible("samsung,exynos5250")) + platform_device_register(&exynos_cpuidle); +} + +static char const *const exynos_dt_compat[] __initconst = { + "samsung,exynos3", + "samsung,exynos3250", + "samsung,exynos4", + "samsung,exynos4210", + "samsung,exynos4412", + "samsung,exynos5", + "samsung,exynos5250", + "samsung,exynos5260", + "samsung,exynos5420", + NULL +}; + +static void __init exynos_dt_fixup(void) +{ + /* + * Some versions of uboot pass garbage entries in the memory node, + * use the old CONFIG_ARM_NR_BANKS + */ + of_fdt_limit_memory(8); +} + +DT_MACHINE_START(EXYNOS_DT, "Samsung Exynos (Flattened Device Tree)") + .l2c_aux_val = 0x08400000, + .l2c_aux_mask = 0xf60fffff, + .smp = smp_ops(exynos_smp_ops), + .map_io = exynos_init_io, + .init_early = exynos_firmware_init, + .init_irq = exynos_init_irq, + .init_machine = exynos_dt_machine_init, + .init_late = exynos_pm_init, + .dt_compat = exynos_dt_compat, + .dt_fixup = exynos_dt_fixup, +MACHINE_END 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); +} diff --git a/arch/arm/mach-exynos/headsmp.S b/arch/arm/mach-exynos/headsmp.S new file mode 100644 index 000000000..0ac2cb9a7 --- /dev/null +++ b/arch/arm/mach-exynos/headsmp.S @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cloned from linux/arch/arm/mach-realview/headsmp.S + * + * Copyright (c) 2003 ARM Limited + * All Rights Reserved + */ +#include <linux/linkage.h> +#include <linux/init.h> + +#include <asm/assembler.h> + +/* + * exynos4 specific entry point for secondary CPUs. This provides + * a "holding pen" into which all secondary cores are held until we're + * ready for them to initialise. + */ +ENTRY(exynos4_secondary_startup) +ARM_BE8(setend be) + mrc p15, 0, r0, c0, c0, 5 + and r0, r0, #15 + adr r4, 1f + ldmia r4, {r5, r6} + sub r4, r4, r5 + add r6, r6, r4 +pen: ldr r7, [r6] + cmp r7, r0 + bne pen + + /* + * we've been released from the holding pen: secondary_stack + * should now contain the SVC stack for this core + */ + b secondary_startup +ENDPROC(exynos4_secondary_startup) + + .align 2 +1: .long . + .long exynos_pen_release diff --git a/arch/arm/mach-exynos/mcpm-exynos.c b/arch/arm/mach-exynos/mcpm-exynos.c new file mode 100644 index 000000000..fd0dbeb93 --- /dev/null +++ b/arch/arm/mach-exynos/mcpm-exynos.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2014 Samsung Electronics Co., Ltd. +// http://www.samsung.com +// +// Based on arch/arm/mach-vexpress/dcscb.c + +#include <linux/arm-cci.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/syscore_ops.h> +#include <linux/soc/samsung/exynos-regs-pmu.h> + +#include <asm/cputype.h> +#include <asm/cp15.h> +#include <asm/mcpm.h> +#include <asm/smp_plat.h> + +#include "common.h" + +#define EXYNOS5420_CPUS_PER_CLUSTER 4 +#define EXYNOS5420_NR_CLUSTERS 2 + +#define EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN BIT(9) +#define EXYNOS5420_USE_ARM_CORE_DOWN_STATE BIT(29) +#define EXYNOS5420_USE_L2_COMMON_UP_STATE BIT(30) + +static void __iomem *ns_sram_base_addr __ro_after_init; +static bool secure_firmware __ro_after_init; + +/* + * The common v7_exit_coherency_flush API could not be used because of the + * Erratum 799270 workaround. This macro is the same as the common one (in + * arch/arm/include/asm/cacheflush.h) except for the erratum handling. + */ +#define exynos_v7_exit_coherency_flush(level) \ + asm volatile( \ + "mrc p15, 0, r0, c1, c0, 0 @ get SCTLR\n\t" \ + "bic r0, r0, #"__stringify(CR_C)"\n\t" \ + "mcr p15, 0, r0, c1, c0, 0 @ set SCTLR\n\t" \ + "isb\n\t"\ + "bl v7_flush_dcache_"__stringify(level)"\n\t" \ + "mrc p15, 0, r0, c1, c0, 1 @ get ACTLR\n\t" \ + "bic r0, r0, #(1 << 6) @ disable local coherency\n\t" \ + /* Dummy Load of a device register to avoid Erratum 799270 */ \ + "ldr r4, [%0]\n\t" \ + "and r4, r4, #0\n\t" \ + "orr r0, r0, r4\n\t" \ + "mcr p15, 0, r0, c1, c0, 1 @ set ACTLR\n\t" \ + "isb\n\t" \ + "dsb\n\t" \ + : \ + : "Ir" (pmu_base_addr + S5P_INFORM0) \ + : "r0", "r1", "r2", "r3", "r4", "r5", "r6", \ + "r9", "r10", "ip", "lr", "memory") + +static int exynos_cpu_powerup(unsigned int cpu, unsigned int cluster) +{ + unsigned int cpunr = cpu + (cluster * EXYNOS5420_CPUS_PER_CLUSTER); + bool state; + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + if (cpu >= EXYNOS5420_CPUS_PER_CLUSTER || + cluster >= EXYNOS5420_NR_CLUSTERS) + return -EINVAL; + + state = exynos_cpu_power_state(cpunr); + exynos_cpu_power_up(cpunr); + if (!state && secure_firmware) { + /* + * This assumes the cluster number of the big cores(Cortex A15) + * is 0 and the Little cores(Cortex A7) is 1. + * When the system was booted from the Little core, + * they should be reset during power up cpu. + */ + if (cluster && + cluster == MPIDR_AFFINITY_LEVEL(cpu_logical_map(0), 1)) { + unsigned int timeout = 16; + + /* + * Before we reset the Little cores, we should wait + * the SPARE2 register is set to 1 because the init + * codes of the iROM will set the register after + * initialization. + */ + while (timeout && !pmu_raw_readl(S5P_PMU_SPARE2)) { + timeout--; + udelay(10); + } + + if (timeout == 0) { + pr_err("cpu %u cluster %u powerup failed\n", + cpu, cluster); + exynos_cpu_power_down(cpunr); + return -ETIMEDOUT; + } + + pmu_raw_writel(EXYNOS5420_KFC_CORE_RESET(cpu), + EXYNOS_SWRESET); + } + } + + return 0; +} + +static int exynos_cluster_powerup(unsigned int cluster) +{ + pr_debug("%s: cluster %u\n", __func__, cluster); + if (cluster >= EXYNOS5420_NR_CLUSTERS) + return -EINVAL; + + exynos_cluster_power_up(cluster); + return 0; +} + +static void exynos_cpu_powerdown_prepare(unsigned int cpu, unsigned int cluster) +{ + unsigned int cpunr = cpu + (cluster * EXYNOS5420_CPUS_PER_CLUSTER); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cpu >= EXYNOS5420_CPUS_PER_CLUSTER || + cluster >= EXYNOS5420_NR_CLUSTERS); + exynos_cpu_power_down(cpunr); +} + +static void exynos_cluster_powerdown_prepare(unsigned int cluster) +{ + pr_debug("%s: cluster %u\n", __func__, cluster); + BUG_ON(cluster >= EXYNOS5420_NR_CLUSTERS); + exynos_cluster_power_down(cluster); +} + +static void exynos_cpu_cache_disable(void) +{ + /* Disable and flush the local CPU cache. */ + exynos_v7_exit_coherency_flush(louis); +} + +static void exynos_cluster_cache_disable(void) +{ + if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A15) { + /* + * On the Cortex-A15 we need to disable + * L2 prefetching before flushing the cache. + */ + asm volatile( + "mcr p15, 1, %0, c15, c0, 3\n\t" + "isb\n\t" + "dsb" + : : "r" (0x400)); + } + + /* Flush all cache levels for this cluster. */ + exynos_v7_exit_coherency_flush(all); + + /* + * Disable cluster-level coherency by masking + * incoming snoops and DVM messages: + */ + cci_disable_port_by_cpu(read_cpuid_mpidr()); +} + +static int exynos_wait_for_powerdown(unsigned int cpu, unsigned int cluster) +{ + unsigned int tries = 100; + unsigned int cpunr = cpu + (cluster * EXYNOS5420_CPUS_PER_CLUSTER); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cpu >= EXYNOS5420_CPUS_PER_CLUSTER || + cluster >= EXYNOS5420_NR_CLUSTERS); + + /* Wait for the core state to be OFF */ + while (tries--) { + if ((exynos_cpu_power_state(cpunr) == 0)) + return 0; /* success: the CPU is halted */ + + /* Otherwise, wait and retry: */ + msleep(1); + } + + return -ETIMEDOUT; /* timeout */ +} + +static void exynos_cpu_is_up(unsigned int cpu, unsigned int cluster) +{ + /* especially when resuming: make sure power control is set */ + exynos_cpu_powerup(cpu, cluster); +} + +static const struct mcpm_platform_ops exynos_power_ops = { + .cpu_powerup = exynos_cpu_powerup, + .cluster_powerup = exynos_cluster_powerup, + .cpu_powerdown_prepare = exynos_cpu_powerdown_prepare, + .cluster_powerdown_prepare = exynos_cluster_powerdown_prepare, + .cpu_cache_disable = exynos_cpu_cache_disable, + .cluster_cache_disable = exynos_cluster_cache_disable, + .wait_for_powerdown = exynos_wait_for_powerdown, + .cpu_is_up = exynos_cpu_is_up, +}; + +/* + * Enable cluster-level coherency, in preparation for turning on the MMU. + */ +static void __naked exynos_pm_power_up_setup(unsigned int affinity_level) +{ + asm volatile ("\n" + "cmp r0, #1\n" + "bxne lr\n" + "b cci_enable_port_for_self"); +} + +static const struct of_device_id exynos_dt_mcpm_match[] = { + { .compatible = "samsung,exynos5420" }, + { .compatible = "samsung,exynos5800" }, + {}, +}; + +static void exynos_mcpm_setup_entry_point(void) +{ + /* + * U-Boot SPL is hardcoded to jump to the start of ns_sram_base_addr + * as part of secondary_cpu_start(). Let's redirect it to the + * mcpm_entry_point(). This is done during both secondary boot-up as + * well as system resume. + */ + __raw_writel(0xe59f0000, ns_sram_base_addr); /* ldr r0, [pc, #0] */ + __raw_writel(0xe12fff10, ns_sram_base_addr + 4); /* bx r0 */ + __raw_writel(__pa_symbol(mcpm_entry_point), ns_sram_base_addr + 8); +} + +static struct syscore_ops exynos_mcpm_syscore_ops = { + .resume = exynos_mcpm_setup_entry_point, +}; + +static int __init exynos_mcpm_init(void) +{ + struct device_node *node; + unsigned int value, i; + int ret; + + node = of_find_matching_node(NULL, exynos_dt_mcpm_match); + if (!node) + return -ENODEV; + of_node_put(node); + + if (!cci_probed()) + return -ENODEV; + + node = of_find_compatible_node(NULL, NULL, + "samsung,exynos4210-sysram-ns"); + if (!node) + return -ENODEV; + + ns_sram_base_addr = of_iomap(node, 0); + of_node_put(node); + if (!ns_sram_base_addr) { + pr_err("failed to map non-secure iRAM base address\n"); + return -ENOMEM; + } + + secure_firmware = exynos_secure_firmware_available(); + + /* + * To increase the stability of KFC reset we need to program + * the PMU SPARE3 register + */ + pmu_raw_writel(EXYNOS5420_SWRESET_KFC_SEL, S5P_PMU_SPARE3); + + ret = mcpm_platform_register(&exynos_power_ops); + if (!ret) + ret = mcpm_sync_init(exynos_pm_power_up_setup); + if (!ret) + ret = mcpm_loopback(exynos_cluster_cache_disable); /* turn on the CCI */ + if (ret) { + iounmap(ns_sram_base_addr); + return ret; + } + + mcpm_smp_set_ops(); + + pr_info("Exynos MCPM support installed\n"); + + /* + * On Exynos5420/5800 for the A15 and A7 clusters: + * + * EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN ensures that all the cores + * in a cluster are turned off before turning off the cluster L2. + * + * EXYNOS5420_USE_ARM_CORE_DOWN_STATE ensures that a cores is powered + * off before waking it up. + * + * EXYNOS5420_USE_L2_COMMON_UP_STATE ensures that cluster L2 will be + * turned on before the first man is powered up. + */ + for (i = 0; i < EXYNOS5420_NR_CLUSTERS; i++) { + value = pmu_raw_readl(EXYNOS_COMMON_OPTION(i)); + value |= EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN | + EXYNOS5420_USE_ARM_CORE_DOWN_STATE | + EXYNOS5420_USE_L2_COMMON_UP_STATE; + pmu_raw_writel(value, EXYNOS_COMMON_OPTION(i)); + } + + exynos_mcpm_setup_entry_point(); + + register_syscore_ops(&exynos_mcpm_syscore_ops); + + return ret; +} + +early_initcall(exynos_mcpm_init); diff --git a/arch/arm/mach-exynos/platsmp.c b/arch/arm/mach-exynos/platsmp.c new file mode 100644 index 000000000..fb4a394ec --- /dev/null +++ b/arch/arm/mach-exynos/platsmp.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. +// http://www.samsung.com +// +// Cloned from linux/arch/arm/mach-vexpress/platsmp.c +// +// Copyright (C) 2002 ARM Ltd. +// All Rights Reserved + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/smp.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/soc/samsung/exynos-regs-pmu.h> + +#include <asm/cacheflush.h> +#include <asm/cp15.h> +#include <asm/smp_plat.h> +#include <asm/smp_scu.h> +#include <asm/firmware.h> + +#include "common.h" + +extern void exynos4_secondary_startup(void); + +/* XXX exynos_pen_release is cargo culted code - DO NOT COPY XXX */ +volatile int exynos_pen_release = -1; + +#ifdef CONFIG_HOTPLUG_CPU +static inline void cpu_leave_lowpower(u32 core_id) +{ + unsigned int v; + + asm volatile( + "mrc p15, 0, %0, c1, c0, 0\n" + " orr %0, %0, %1\n" + " mcr p15, 0, %0, c1, c0, 0\n" + " mrc p15, 0, %0, c1, c0, 1\n" + " orr %0, %0, %2\n" + " mcr p15, 0, %0, c1, c0, 1\n" + : "=&r" (v) + : "Ir" (CR_C), "Ir" (0x40) + : "cc"); +} + +static inline void platform_do_lowpower(unsigned int cpu, int *spurious) +{ + u32 mpidr = cpu_logical_map(cpu); + u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0); + + for (;;) { + + /* Turn the CPU off on next WFI instruction. */ + exynos_cpu_power_down(core_id); + + wfi(); + + if (exynos_pen_release == core_id) { + /* + * OK, proper wakeup, we're done + */ + break; + } + + /* + * Getting here, means that we have come out of WFI without + * having been woken up - this shouldn't happen + * + * Just note it happening - when we're woken, we can report + * its occurrence. + */ + (*spurious)++; + } +} +#endif /* CONFIG_HOTPLUG_CPU */ + +/** + * exynos_cpu_power_down() - power down the specified cpu + * @cpu: the cpu to power down + * + * Power down the specified cpu. The sequence must be finished by a + * call to cpu_do_idle() + */ +void exynos_cpu_power_down(int cpu) +{ + u32 core_conf; + + if (cpu == 0 && (soc_is_exynos5420() || soc_is_exynos5800())) { + /* + * Bypass power down for CPU0 during suspend. Check for + * the SYS_PWR_REG value to decide if we are suspending + * the system. + */ + int val = pmu_raw_readl(EXYNOS5_ARM_CORE0_SYS_PWR_REG); + + if (!(val & S5P_CORE_LOCAL_PWR_EN)) + return; + } + + core_conf = pmu_raw_readl(EXYNOS_ARM_CORE_CONFIGURATION(cpu)); + core_conf &= ~S5P_CORE_LOCAL_PWR_EN; + pmu_raw_writel(core_conf, EXYNOS_ARM_CORE_CONFIGURATION(cpu)); +} + +/** + * exynos_cpu_power_up() - power up the specified cpu + * @cpu: the cpu to power up + * + * Power up the specified cpu + */ +void exynos_cpu_power_up(int cpu) +{ + u32 core_conf = S5P_CORE_LOCAL_PWR_EN; + + if (soc_is_exynos3250()) + core_conf |= S5P_CORE_AUTOWAKEUP_EN; + + pmu_raw_writel(core_conf, + EXYNOS_ARM_CORE_CONFIGURATION(cpu)); +} + +/** + * exynos_cpu_power_state() - returns the power state of the cpu + * @cpu: the cpu to retrieve the power state from + */ +int exynos_cpu_power_state(int cpu) +{ + return (pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(cpu)) & + S5P_CORE_LOCAL_PWR_EN); +} + +/** + * exynos_cluster_power_down() - power down the specified cluster + * @cluster: the cluster to power down + */ +void exynos_cluster_power_down(int cluster) +{ + pmu_raw_writel(0, EXYNOS_COMMON_CONFIGURATION(cluster)); +} + +/** + * exynos_cluster_power_up() - power up the specified cluster + * @cluster: the cluster to power up + */ +void exynos_cluster_power_up(int cluster) +{ + pmu_raw_writel(S5P_CORE_LOCAL_PWR_EN, + EXYNOS_COMMON_CONFIGURATION(cluster)); +} + +/** + * exynos_cluster_power_state() - returns the power state of the cluster + * @cluster: the cluster to retrieve the power state from + * + */ +int exynos_cluster_power_state(int cluster) +{ + return (pmu_raw_readl(EXYNOS_COMMON_STATUS(cluster)) & + S5P_CORE_LOCAL_PWR_EN); +} + +/** + * exynos_scu_enable() - enables SCU for Cortex-A9 based system + */ +void exynos_scu_enable(void) +{ + struct device_node *np; + static void __iomem *scu_base; + + if (!scu_base) { + np = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-scu"); + if (np) { + scu_base = of_iomap(np, 0); + of_node_put(np); + } else { + scu_base = ioremap(scu_a9_get_base(), SZ_4K); + } + } + scu_enable(scu_base); +} + +static void __iomem *cpu_boot_reg_base(void) +{ + if (soc_is_exynos4210() && exynos_rev() == EXYNOS4210_REV_1_1) + return pmu_base_addr + S5P_INFORM5; + return sysram_base_addr; +} + +static inline void __iomem *cpu_boot_reg(int cpu) +{ + void __iomem *boot_reg; + + boot_reg = cpu_boot_reg_base(); + if (!boot_reg) + return IOMEM_ERR_PTR(-ENODEV); + if (soc_is_exynos4412()) + boot_reg += 4*cpu; + else if (soc_is_exynos5420() || soc_is_exynos5800()) + boot_reg += 4; + return boot_reg; +} + +/* + * Set wake up by local power mode and execute software reset for given core. + * + * Currently this is needed only when booting secondary CPU on Exynos3250. + */ +void exynos_core_restart(u32 core_id) +{ + unsigned int timeout = 16; + u32 val; + + if (!soc_is_exynos3250()) + return; + + while (timeout && !pmu_raw_readl(S5P_PMU_SPARE2)) { + timeout--; + udelay(10); + } + if (timeout == 0) { + pr_err("cpu core %u restart failed\n", core_id); + return; + } + udelay(10); + + val = pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(core_id)); + val |= S5P_CORE_WAKEUP_FROM_LOCAL_CFG; + pmu_raw_writel(val, EXYNOS_ARM_CORE_STATUS(core_id)); + + pmu_raw_writel(EXYNOS_CORE_PO_RESET(core_id), EXYNOS_SWRESET); +} + +/* + * XXX CARGO CULTED CODE - DO NOT COPY XXX + * + * Write exynos_pen_release in a way that is guaranteed to be visible to + * all observers, irrespective of whether they're taking part in coherency + * or not. This is necessary for the hotplug code to work reliably. + */ +static void exynos_write_pen_release(int val) +{ + exynos_pen_release = val; + smp_wmb(); + sync_cache_w(&exynos_pen_release); +} + +static DEFINE_SPINLOCK(boot_lock); + +static void exynos_secondary_init(unsigned int cpu) +{ + /* + * let the primary processor know we're out of the + * pen, then head off into the C entry point + */ + exynos_write_pen_release(-1); + + /* + * Synchronise with the boot thread. + */ + spin_lock(&boot_lock); + spin_unlock(&boot_lock); +} + +int exynos_set_boot_addr(u32 core_id, unsigned long boot_addr) +{ + int ret; + + /* + * Try to set boot address using firmware first + * and fall back to boot register if it fails. + */ + ret = call_firmware_op(set_cpu_boot_addr, core_id, boot_addr); + if (ret && ret != -ENOSYS) + goto fail; + if (ret == -ENOSYS) { + void __iomem *boot_reg = cpu_boot_reg(core_id); + + if (IS_ERR(boot_reg)) { + ret = PTR_ERR(boot_reg); + goto fail; + } + writel_relaxed(boot_addr, boot_reg); + ret = 0; + } +fail: + return ret; +} + +int exynos_get_boot_addr(u32 core_id, unsigned long *boot_addr) +{ + int ret; + + /* + * Try to get boot address using firmware first + * and fall back to boot register if it fails. + */ + ret = call_firmware_op(get_cpu_boot_addr, core_id, boot_addr); + if (ret && ret != -ENOSYS) + goto fail; + if (ret == -ENOSYS) { + void __iomem *boot_reg = cpu_boot_reg(core_id); + + if (IS_ERR(boot_reg)) { + ret = PTR_ERR(boot_reg); + goto fail; + } + *boot_addr = readl_relaxed(boot_reg); + ret = 0; + } +fail: + return ret; +} + +static int exynos_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + unsigned long timeout; + u32 mpidr = cpu_logical_map(cpu); + u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0); + int ret = -ENOSYS; + + /* + * Set synchronisation state between this boot processor + * and the secondary one + */ + spin_lock(&boot_lock); + + /* + * The secondary processor is waiting to be released from + * the holding pen - release it, then wait for it to flag + * that it has been released by resetting exynos_pen_release. + * + * Note that "exynos_pen_release" is the hardware CPU core ID, whereas + * "cpu" is Linux's internal ID. + */ + exynos_write_pen_release(core_id); + + if (!exynos_cpu_power_state(core_id)) { + exynos_cpu_power_up(core_id); + timeout = 10; + + /* wait max 10 ms until cpu1 is on */ + while (exynos_cpu_power_state(core_id) + != S5P_CORE_LOCAL_PWR_EN) { + if (timeout == 0) + break; + timeout--; + mdelay(1); + } + + if (timeout == 0) { + printk(KERN_ERR "cpu1 power enable failed"); + spin_unlock(&boot_lock); + return -ETIMEDOUT; + } + } + + exynos_core_restart(core_id); + + /* + * Send the secondary CPU a soft interrupt, thereby causing + * the boot monitor to read the system wide flags register, + * and branch to the address found there. + */ + + timeout = jiffies + (1 * HZ); + while (time_before(jiffies, timeout)) { + unsigned long boot_addr; + + smp_rmb(); + + boot_addr = __pa_symbol(exynos4_secondary_startup); + + ret = exynos_set_boot_addr(core_id, boot_addr); + if (ret) + goto fail; + + call_firmware_op(cpu_boot, core_id); + + if (soc_is_exynos3250()) + dsb_sev(); + else + arch_send_wakeup_ipi_mask(cpumask_of(cpu)); + + if (exynos_pen_release == -1) + break; + + udelay(10); + } + + if (exynos_pen_release != -1) + ret = -ETIMEDOUT; + + /* + * now the secondary core is starting up let it run its + * calibrations, then wait for it to finish + */ +fail: + spin_unlock(&boot_lock); + + return exynos_pen_release != -1 ? ret : 0; +} + +static void __init exynos_smp_prepare_cpus(unsigned int max_cpus) +{ + exynos_sysram_init(); + + exynos_set_delayed_reset_assertion(true); + + if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) + exynos_scu_enable(); +} + +#ifdef CONFIG_HOTPLUG_CPU +/* + * platform-specific code to shutdown a CPU + * + * Called with IRQs disabled + */ +static void exynos_cpu_die(unsigned int cpu) +{ + int spurious = 0; + u32 mpidr = cpu_logical_map(cpu); + u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0); + + v7_exit_coherency_flush(louis); + + platform_do_lowpower(cpu, &spurious); + + /* + * bring this CPU back into the world of cache + * coherency, and then restore interrupts + */ + cpu_leave_lowpower(core_id); + + if (spurious) + pr_warn("CPU%u: %u spurious wakeup calls\n", cpu, spurious); +} +#endif /* CONFIG_HOTPLUG_CPU */ + +const struct smp_operations exynos_smp_ops __initconst = { + .smp_prepare_cpus = exynos_smp_prepare_cpus, + .smp_secondary_init = exynos_secondary_init, + .smp_boot_secondary = exynos_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_die = exynos_cpu_die, +#endif +}; diff --git a/arch/arm/mach-exynos/pm.c b/arch/arm/mach-exynos/pm.c new file mode 100644 index 000000000..30f4e55bf --- /dev/null +++ b/arch/arm/mach-exynos/pm.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. +// http://www.samsung.com +// +// Exynos - Power Management support +// +// Based on arch/arm/mach-s3c2410/pm.c +// Copyright (c) 2006 Simtec Electronics +// Ben Dooks <ben@simtec.co.uk> + +#include <linux/init.h> +#include <linux/suspend.h> +#include <linux/cpu_pm.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/soc/samsung/exynos-regs-pmu.h> +#include <linux/soc/samsung/exynos-pmu.h> + +#include <asm/firmware.h> +#include <asm/smp_scu.h> +#include <asm/suspend.h> +#include <asm/cacheflush.h> + +#include "common.h" + +static inline void __iomem *exynos_boot_vector_addr(void) +{ + if (exynos_rev() == EXYNOS4210_REV_1_1) + return pmu_base_addr + S5P_INFORM7; + else if (exynos_rev() == EXYNOS4210_REV_1_0) + return sysram_base_addr + 0x24; + return pmu_base_addr + S5P_INFORM0; +} + +static inline void __iomem *exynos_boot_vector_flag(void) +{ + if (exynos_rev() == EXYNOS4210_REV_1_1) + return pmu_base_addr + S5P_INFORM6; + else if (exynos_rev() == EXYNOS4210_REV_1_0) + return sysram_base_addr + 0x20; + return pmu_base_addr + S5P_INFORM1; +} + +#define S5P_CHECK_AFTR 0xFCBA0D10 + +/* For Cortex-A9 Diagnostic and Power control register */ +static unsigned int save_arm_register[2]; + +void exynos_cpu_save_register(void) +{ + unsigned long tmp; + + /* Save Power control register */ + asm ("mrc p15, 0, %0, c15, c0, 0" + : "=r" (tmp) : : "cc"); + + save_arm_register[0] = tmp; + + /* Save Diagnostic register */ + asm ("mrc p15, 0, %0, c15, c0, 1" + : "=r" (tmp) : : "cc"); + + save_arm_register[1] = tmp; +} + +void exynos_cpu_restore_register(void) +{ + unsigned long tmp; + + /* Restore Power control register */ + tmp = save_arm_register[0]; + + asm volatile ("mcr p15, 0, %0, c15, c0, 0" + : : "r" (tmp) + : "cc"); + + /* Restore Diagnostic register */ + tmp = save_arm_register[1]; + + asm volatile ("mcr p15, 0, %0, c15, c0, 1" + : : "r" (tmp) + : "cc"); +} + +void exynos_pm_central_suspend(void) +{ + unsigned long tmp; + + /* Setting Central Sequence Register for power down mode */ + tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); + tmp &= ~S5P_CENTRAL_LOWPWR_CFG; + pmu_raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); +} + +int exynos_pm_central_resume(void) +{ + unsigned long tmp; + + /* + * If PMU failed while entering sleep mode, WFI will be + * ignored by PMU and then exiting cpu_do_idle(). + * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically + * in this situation. + */ + tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); + if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) { + tmp |= S5P_CENTRAL_LOWPWR_CFG; + pmu_raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); + /* clear the wakeup state register */ + pmu_raw_writel(0x0, S5P_WAKEUP_STAT); + /* No need to perform below restore code */ + return -1; + } + + return 0; +} + +/* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */ +static void exynos_set_wakeupmask(long mask) +{ + pmu_raw_writel(mask, S5P_WAKEUP_MASK); + if (soc_is_exynos3250()) + pmu_raw_writel(0x0, S5P_WAKEUP_MASK2); +} + +static void exynos_cpu_set_boot_vector(long flags) +{ + writel_relaxed(__pa_symbol(exynos_cpu_resume), + exynos_boot_vector_addr()); + writel_relaxed(flags, exynos_boot_vector_flag()); +} + +static int exynos_aftr_finisher(unsigned long flags) +{ + int ret; + + exynos_set_wakeupmask(soc_is_exynos3250() ? 0x40003ffe : 0x0000ff3e); + /* Set value of power down register for aftr mode */ + exynos_sys_powerdown_conf(SYS_AFTR); + + ret = call_firmware_op(do_idle, FW_DO_IDLE_AFTR); + if (ret == -ENOSYS) { + if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) + exynos_cpu_save_register(); + exynos_cpu_set_boot_vector(S5P_CHECK_AFTR); + cpu_do_idle(); + } + + return 1; +} + +void exynos_enter_aftr(void) +{ + unsigned int cpuid = smp_processor_id(); + + cpu_pm_enter(); + + if (soc_is_exynos3250()) + exynos_set_boot_flag(cpuid, C2_STATE); + + exynos_pm_central_suspend(); + + if (soc_is_exynos4412()) { + /* Setting SEQ_OPTION register */ + pmu_raw_writel(S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0, + S5P_CENTRAL_SEQ_OPTION); + } + + cpu_suspend(0, exynos_aftr_finisher); + + if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) { + exynos_scu_enable(); + if (call_firmware_op(resume) == -ENOSYS) + exynos_cpu_restore_register(); + } + + exynos_pm_central_resume(); + + if (soc_is_exynos3250()) + exynos_clear_boot_flag(cpuid, C2_STATE); + + cpu_pm_exit(); +} + +#if defined(CONFIG_SMP) && defined(CONFIG_ARM_EXYNOS_CPUIDLE) +static atomic_t cpu1_wakeup = ATOMIC_INIT(0); + +static int exynos_cpu0_enter_aftr(void) +{ + int ret = -1; + + /* + * If the other cpu is powered on, we have to power it off, because + * the AFTR state won't work otherwise + */ + if (cpu_online(1)) { + /* + * We reach a sync point with the coupled idle state, we know + * the other cpu will power down itself or will abort the + * sequence, let's wait for one of these to happen + */ + while (exynos_cpu_power_state(1)) { + unsigned long boot_addr; + + /* + * The other cpu may skip idle and boot back + * up again + */ + if (atomic_read(&cpu1_wakeup)) + goto abort; + + /* + * The other cpu may bounce through idle and + * boot back up again, getting stuck in the + * boot rom code + */ + ret = exynos_get_boot_addr(1, &boot_addr); + if (ret) + goto fail; + ret = -1; + if (boot_addr == 0) + goto abort; + + cpu_relax(); + } + } + + exynos_enter_aftr(); + ret = 0; + +abort: + if (cpu_online(1)) { + unsigned long boot_addr = __pa_symbol(exynos_cpu_resume); + + /* + * Set the boot vector to something non-zero + */ + ret = exynos_set_boot_addr(1, boot_addr); + if (ret) + goto fail; + dsb(); + + /* + * Turn on cpu1 and wait for it to be on + */ + exynos_cpu_power_up(1); + while (exynos_cpu_power_state(1) != S5P_CORE_LOCAL_PWR_EN) + cpu_relax(); + + if (soc_is_exynos3250()) { + while (!pmu_raw_readl(S5P_PMU_SPARE2) && + !atomic_read(&cpu1_wakeup)) + cpu_relax(); + + if (!atomic_read(&cpu1_wakeup)) + exynos_core_restart(1); + } + + while (!atomic_read(&cpu1_wakeup)) { + smp_rmb(); + + /* + * Poke cpu1 out of the boot rom + */ + + ret = exynos_set_boot_addr(1, boot_addr); + if (ret) + goto fail; + + call_firmware_op(cpu_boot, 1); + dsb_sev(); + } + } +fail: + return ret; +} + +static int exynos_wfi_finisher(unsigned long flags) +{ + if (soc_is_exynos3250()) + flush_cache_all(); + cpu_do_idle(); + + return -1; +} + +static int exynos_cpu1_powerdown(void) +{ + int ret = -1; + + /* + * Idle sequence for cpu1 + */ + if (cpu_pm_enter()) + goto cpu1_aborted; + + /* + * Turn off cpu 1 + */ + exynos_cpu_power_down(1); + + if (soc_is_exynos3250()) + pmu_raw_writel(0, S5P_PMU_SPARE2); + + ret = cpu_suspend(0, exynos_wfi_finisher); + + cpu_pm_exit(); + +cpu1_aborted: + dsb(); + /* + * Notify cpu 0 that cpu 1 is awake + */ + atomic_set(&cpu1_wakeup, 1); + + return ret; +} + +static void exynos_pre_enter_aftr(void) +{ + unsigned long boot_addr = __pa_symbol(exynos_cpu_resume); + + (void)exynos_set_boot_addr(1, boot_addr); +} + +static void exynos_post_enter_aftr(void) +{ + atomic_set(&cpu1_wakeup, 0); +} + +struct cpuidle_exynos_data cpuidle_coupled_exynos_data = { + .cpu0_enter_aftr = exynos_cpu0_enter_aftr, + .cpu1_powerdown = exynos_cpu1_powerdown, + .pre_enter_aftr = exynos_pre_enter_aftr, + .post_enter_aftr = exynos_post_enter_aftr, +}; +#endif /* CONFIG_SMP && CONFIG_ARM_EXYNOS_CPUIDLE */ diff --git a/arch/arm/mach-exynos/sleep.S b/arch/arm/mach-exynos/sleep.S new file mode 100644 index 000000000..ed93f9185 --- /dev/null +++ b/arch/arm/mach-exynos/sleep.S @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Exynos low-level resume code + */ + +#include <linux/linkage.h> +#include <asm/asm-offsets.h> +#include <asm/hardware/cache-l2x0.h> +#include "smc.h" + +#define CPU_MASK 0xff0ffff0 +#define CPU_CORTEX_A9 0x410fc090 + + .text + .align + + /* + * sleep magic, to allow the bootloader to check for an valid + * image to resume to. Must be the first word before the + * exynos_cpu_resume entry. + */ + + .word 0x2bedf00d + + /* + * exynos_cpu_resume + * + * resume code entry for bootloader to call + */ + +ENTRY(exynos_cpu_resume) +#ifdef CONFIG_CACHE_L2X0 + mrc p15, 0, r0, c0, c0, 0 + ldr r1, =CPU_MASK + and r0, r0, r1 + ldr r1, =CPU_CORTEX_A9 + cmp r0, r1 + bleq l2c310_early_resume +#endif + b cpu_resume +ENDPROC(exynos_cpu_resume) + + .align + .arch armv7-a + .arch_extension sec +ENTRY(exynos_cpu_resume_ns) + mrc p15, 0, r0, c0, c0, 0 + ldr r1, =CPU_MASK + and r0, r0, r1 + ldr r1, =CPU_CORTEX_A9 + cmp r0, r1 + bne skip_cp15 + + adr r0, _cp15_save_power + ldr r1, [r0] + ldr r1, [r0, r1] + adr r0, _cp15_save_diag + ldr r2, [r0] + ldr r2, [r0, r2] + mov r0, #SMC_CMD_C15RESUME + dsb + smc #0 +#ifdef CONFIG_CACHE_L2X0 + adr r0, 1f + ldr r2, [r0] + add r0, r2, r0 + + /* Check that the address has been initialised. */ + ldr r1, [r0, #L2X0_R_PHY_BASE] + teq r1, #0 + beq skip_l2x0 + + /* Check if controller has been enabled. */ + ldr r2, [r1, #L2X0_CTRL] + tst r2, #0x1 + bne skip_l2x0 + + ldr r1, [r0, #L2X0_R_TAG_LATENCY] + ldr r2, [r0, #L2X0_R_DATA_LATENCY] + ldr r3, [r0, #L2X0_R_PREFETCH_CTRL] + mov r0, #SMC_CMD_L2X0SETUP1 + smc #0 + + /* Reload saved regs pointer because smc corrupts registers. */ + adr r0, 1f + ldr r2, [r0] + add r0, r2, r0 + + ldr r1, [r0, #L2X0_R_PWR_CTRL] + ldr r2, [r0, #L2X0_R_AUX_CTRL] + mov r0, #SMC_CMD_L2X0SETUP2 + smc #0 + + mov r0, #SMC_CMD_L2X0INVALL + smc #0 + + mov r1, #1 + mov r0, #SMC_CMD_L2X0CTRL + smc #0 +skip_l2x0: +#endif /* CONFIG_CACHE_L2X0 */ +skip_cp15: + b cpu_resume +ENDPROC(exynos_cpu_resume_ns) + + .align +_cp15_save_power: + .long cp15_save_power - . +_cp15_save_diag: + .long cp15_save_diag - . +#ifdef CONFIG_CACHE_L2X0 +1: .long l2x0_saved_regs - . +#endif /* CONFIG_CACHE_L2X0 */ + + .data + .align 2 + .globl cp15_save_diag +cp15_save_diag: + .long 0 @ cp15 diagnostic + .globl cp15_save_power +cp15_save_power: + .long 0 @ cp15 power control diff --git a/arch/arm/mach-exynos/smc.h b/arch/arm/mach-exynos/smc.h new file mode 100644 index 000000000..5c30feb8f --- /dev/null +++ b/arch/arm/mach-exynos/smc.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2012 Samsung Electronics. + * + * Exynos - SMC Call + */ + +#ifndef __ASM_ARCH_EXYNOS_SMC_H +#define __ASM_ARCH_EXYNOS_SMC_H + +#define SMC_CMD_INIT (-1) +#define SMC_CMD_INFO (-2) +/* For Power Management */ +#define SMC_CMD_SLEEP (-3) +#define SMC_CMD_CPU1BOOT (-4) +#define SMC_CMD_CPU0AFTR (-5) +#define SMC_CMD_SAVE (-6) +#define SMC_CMD_SHUTDOWN (-7) +/* For CP15 Access */ +#define SMC_CMD_C15RESUME (-11) +/* For L2 Cache Access */ +#define SMC_CMD_L2X0CTRL (-21) +#define SMC_CMD_L2X0SETUP1 (-22) +#define SMC_CMD_L2X0SETUP2 (-23) +#define SMC_CMD_L2X0INVALL (-24) +#define SMC_CMD_L2X0DEBUG (-25) + +/* For Accessing CP15/SFR (General) */ +#define SMC_CMD_REG (-101) + +/* defines for SMC_CMD_REG */ +#define SMC_REG_CLASS_SFR_W (0x1 << 30) +#define SMC_REG_ID_SFR_W(addr) (SMC_REG_CLASS_SFR_W | ((addr) >> 2)) + +#ifndef __ASSEMBLY__ + +extern void exynos_smc(u32 cmd, u32 arg1, u32 arg2, u32 arg3); + +#endif /* __ASSEMBLY__ */ + +/* op type for SMC_CMD_SAVE and SMC_CMD_SHUTDOWN */ +#define OP_TYPE_CORE 0x0 +#define OP_TYPE_CLUSTER 0x1 + +/* Power State required for SMC_CMD_SAVE and SMC_CMD_SHUTDOWN */ +#define SMC_POWERSTATE_IDLE 0x1 + +#endif diff --git a/arch/arm/mach-exynos/suspend.c b/arch/arm/mach-exynos/suspend.c new file mode 100644 index 000000000..3bf14ca78 --- /dev/null +++ b/arch/arm/mach-exynos/suspend.c @@ -0,0 +1,701 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. +// http://www.samsung.com +// +// Exynos - Suspend support +// +// Based on arch/arm/mach-s3c2410/pm.c +// Copyright (c) 2006 Simtec Electronics +// Ben Dooks <ben@simtec.co.uk> + +#include <linux/init.h> +#include <linux/suspend.h> +#include <linux/syscore_ops.h> +#include <linux/cpu_pm.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/of_address.h> +#include <linux/err.h> +#include <linux/regulator/machine.h> +#include <linux/soc/samsung/exynos-pmu.h> +#include <linux/soc/samsung/exynos-regs-pmu.h> + +#include <asm/cacheflush.h> +#include <asm/hardware/cache-l2x0.h> +#include <asm/firmware.h> +#include <asm/mcpm.h> +#include <asm/smp_scu.h> +#include <asm/suspend.h> + +#include "common.h" +#include "smc.h" + +#define REG_TABLE_END (-1U) + +#define EXYNOS5420_CPU_STATE 0x28 + +/** + * struct exynos_wkup_irq - PMU IRQ to mask mapping + * @hwirq: Hardware IRQ signal of the PMU + * @mask: Mask in PMU wake-up mask register + */ +struct exynos_wkup_irq { + unsigned int hwirq; + u32 mask; +}; + +struct exynos_pm_data { + const struct exynos_wkup_irq *wkup_irq; + unsigned int wake_disable_mask; + + void (*pm_prepare)(void); + void (*pm_resume_prepare)(void); + void (*pm_resume)(void); + int (*pm_suspend)(void); + int (*cpu_suspend)(unsigned long); +}; + +/* Used only on Exynos542x/5800 */ +struct exynos_pm_state { + int cpu_state; + unsigned int pmu_spare3; + void __iomem *sysram_base; + phys_addr_t sysram_phys; + bool secure_firmware; +}; + +static const struct exynos_pm_data *pm_data __ro_after_init; +static struct exynos_pm_state pm_state; + +/* + * GIC wake-up support + */ + +static u32 exynos_irqwake_intmask = 0xffffffff; + +static const struct exynos_wkup_irq exynos3250_wkup_irq[] = { + { 73, BIT(1) }, /* RTC alarm */ + { 74, BIT(2) }, /* RTC tick */ + { /* sentinel */ }, +}; + +static const struct exynos_wkup_irq exynos4_wkup_irq[] = { + { 44, BIT(1) }, /* RTC alarm */ + { 45, BIT(2) }, /* RTC tick */ + { /* sentinel */ }, +}; + +static const struct exynos_wkup_irq exynos5250_wkup_irq[] = { + { 43, BIT(1) }, /* RTC alarm */ + { 44, BIT(2) }, /* RTC tick */ + { /* sentinel */ }, +}; + +static u32 exynos_read_eint_wakeup_mask(void) +{ + return pmu_raw_readl(EXYNOS_EINT_WAKEUP_MASK); +} + +static int exynos_irq_set_wake(struct irq_data *data, unsigned int state) +{ + const struct exynos_wkup_irq *wkup_irq; + + if (!pm_data->wkup_irq) + return -ENOENT; + wkup_irq = pm_data->wkup_irq; + + while (wkup_irq->mask) { + if (wkup_irq->hwirq == data->hwirq) { + if (!state) + exynos_irqwake_intmask |= wkup_irq->mask; + else + exynos_irqwake_intmask &= ~wkup_irq->mask; + return 0; + } + ++wkup_irq; + } + + return -ENOENT; +} + +static struct irq_chip exynos_pmu_chip = { + .name = "PMU", + .irq_eoi = irq_chip_eoi_parent, + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_wake = exynos_irq_set_wake, +#ifdef CONFIG_SMP + .irq_set_affinity = irq_chip_set_affinity_parent, +#endif +}; + +static int exynos_pmu_domain_translate(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + if (is_of_node(fwspec->fwnode)) { + if (fwspec->param_count != 3) + return -EINVAL; + + /* No PPI should point to this domain */ + if (fwspec->param[0] != 0) + return -EINVAL; + + *hwirq = fwspec->param[1]; + *type = fwspec->param[2]; + return 0; + } + + return -EINVAL; +} + +static int exynos_pmu_domain_alloc(struct irq_domain *domain, + unsigned int virq, + unsigned int nr_irqs, void *data) +{ + struct irq_fwspec *fwspec = data; + struct irq_fwspec parent_fwspec; + irq_hw_number_t hwirq; + int i; + + if (fwspec->param_count != 3) + return -EINVAL; /* Not GIC compliant */ + if (fwspec->param[0] != 0) + return -EINVAL; /* No PPI should point to this domain */ + + hwirq = fwspec->param[1]; + + for (i = 0; i < nr_irqs; i++) + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, + &exynos_pmu_chip, NULL); + + parent_fwspec = *fwspec; + parent_fwspec.fwnode = domain->parent->fwnode; + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, + &parent_fwspec); +} + +static const struct irq_domain_ops exynos_pmu_domain_ops = { + .translate = exynos_pmu_domain_translate, + .alloc = exynos_pmu_domain_alloc, + .free = irq_domain_free_irqs_common, +}; + +static int __init exynos_pmu_irq_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *parent_domain, *domain; + + if (!parent) { + pr_err("%pOF: no parent, giving up\n", node); + return -ENODEV; + } + + parent_domain = irq_find_host(parent); + if (!parent_domain) { + pr_err("%pOF: unable to obtain parent domain\n", node); + return -ENXIO; + } + + pmu_base_addr = of_iomap(node, 0); + + if (!pmu_base_addr) { + pr_err("%pOF: failed to find exynos pmu register\n", node); + return -ENOMEM; + } + + domain = irq_domain_add_hierarchy(parent_domain, 0, 0, + node, &exynos_pmu_domain_ops, + NULL); + if (!domain) { + iounmap(pmu_base_addr); + pmu_base_addr = NULL; + return -ENOMEM; + } + + /* + * Clear the OF_POPULATED flag set in of_irq_init so that + * later the Exynos PMU platform device won't be skipped. + */ + of_node_clear_flag(node, OF_POPULATED); + + return 0; +} + +#define EXYNOS_PMU_IRQ(symbol, name) IRQCHIP_DECLARE(symbol, name, exynos_pmu_irq_init) + +EXYNOS_PMU_IRQ(exynos3250_pmu_irq, "samsung,exynos3250-pmu"); +EXYNOS_PMU_IRQ(exynos4210_pmu_irq, "samsung,exynos4210-pmu"); +EXYNOS_PMU_IRQ(exynos4412_pmu_irq, "samsung,exynos4412-pmu"); +EXYNOS_PMU_IRQ(exynos5250_pmu_irq, "samsung,exynos5250-pmu"); +EXYNOS_PMU_IRQ(exynos5420_pmu_irq, "samsung,exynos5420-pmu"); + +static int exynos_cpu_do_idle(void) +{ + /* issue the standby signal into the pm unit. */ + cpu_do_idle(); + + pr_info("Failed to suspend the system\n"); + return 1; /* Aborting suspend */ +} +static void exynos_flush_cache_all(void) +{ + flush_cache_all(); + outer_flush_all(); +} + +static int exynos_cpu_suspend(unsigned long arg) +{ + exynos_flush_cache_all(); + return exynos_cpu_do_idle(); +} + +static int exynos3250_cpu_suspend(unsigned long arg) +{ + flush_cache_all(); + return exynos_cpu_do_idle(); +} + +static int exynos5420_cpu_suspend(unsigned long arg) +{ + /* MCPM works with HW CPU identifiers */ + unsigned int mpidr = read_cpuid_mpidr(); + unsigned int cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + unsigned int cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + + if (IS_ENABLED(CONFIG_EXYNOS_MCPM)) { + mcpm_set_entry_vector(cpu, cluster, exynos_cpu_resume); + mcpm_cpu_suspend(); + } + + pr_info("Failed to suspend the system\n"); + + /* return value != 0 means failure */ + return 1; +} + +static void exynos_pm_set_wakeup_mask(void) +{ + /* + * Set wake-up mask registers + * EXYNOS_EINT_WAKEUP_MASK is set by pinctrl driver in late suspend. + */ + pmu_raw_writel(exynos_irqwake_intmask & ~BIT(31), S5P_WAKEUP_MASK); +} + +static void exynos_pm_enter_sleep_mode(void) +{ + /* Set value of power down register for sleep mode */ + exynos_sys_powerdown_conf(SYS_SLEEP); + pmu_raw_writel(EXYNOS_SLEEP_MAGIC, S5P_INFORM1); +} + +static void exynos_pm_prepare(void) +{ + exynos_set_delayed_reset_assertion(false); + + /* Set wake-up mask registers */ + exynos_pm_set_wakeup_mask(); + + exynos_pm_enter_sleep_mode(); + + /* ensure at least INFORM0 has the resume address */ + pmu_raw_writel(__pa_symbol(exynos_cpu_resume), S5P_INFORM0); +} + +static void exynos3250_pm_prepare(void) +{ + unsigned int tmp; + + /* Set wake-up mask registers */ + exynos_pm_set_wakeup_mask(); + + tmp = pmu_raw_readl(EXYNOS3_ARM_L2_OPTION); + tmp &= ~EXYNOS5_OPTION_USE_RETENTION; + pmu_raw_writel(tmp, EXYNOS3_ARM_L2_OPTION); + + exynos_pm_enter_sleep_mode(); + + /* ensure at least INFORM0 has the resume address */ + pmu_raw_writel(__pa_symbol(exynos_cpu_resume), S5P_INFORM0); +} + +static void exynos5420_pm_prepare(void) +{ + unsigned int tmp; + + /* Set wake-up mask registers */ + exynos_pm_set_wakeup_mask(); + + pm_state.pmu_spare3 = pmu_raw_readl(S5P_PMU_SPARE3); + /* + * The cpu state needs to be saved and restored so that the + * secondary CPUs will enter low power start. Though the U-Boot + * is setting the cpu state with low power flag, the kernel + * needs to restore it back in case, the primary cpu fails to + * suspend for any reason. + */ + pm_state.cpu_state = readl_relaxed(pm_state.sysram_base + + EXYNOS5420_CPU_STATE); + writel_relaxed(0x0, pm_state.sysram_base + EXYNOS5420_CPU_STATE); + if (pm_state.secure_firmware) + exynos_smc(SMC_CMD_REG, SMC_REG_ID_SFR_W(pm_state.sysram_phys + + EXYNOS5420_CPU_STATE), + 0, 0); + + exynos_pm_enter_sleep_mode(); + + /* ensure at least INFORM0 has the resume address */ + if (IS_ENABLED(CONFIG_EXYNOS_MCPM)) + pmu_raw_writel(__pa_symbol(mcpm_entry_point), S5P_INFORM0); + + tmp = pmu_raw_readl(EXYNOS_L2_OPTION(0)); + tmp &= ~EXYNOS_L2_USE_RETENTION; + pmu_raw_writel(tmp, EXYNOS_L2_OPTION(0)); + + tmp = pmu_raw_readl(EXYNOS5420_SFR_AXI_CGDIS1); + tmp |= EXYNOS5420_UFS; + pmu_raw_writel(tmp, EXYNOS5420_SFR_AXI_CGDIS1); + + tmp = pmu_raw_readl(EXYNOS5420_ARM_COMMON_OPTION); + tmp &= ~EXYNOS5420_L2RSTDISABLE_VALUE; + pmu_raw_writel(tmp, EXYNOS5420_ARM_COMMON_OPTION); + + tmp = pmu_raw_readl(EXYNOS5420_FSYS2_OPTION); + tmp |= EXYNOS5420_EMULATION; + pmu_raw_writel(tmp, EXYNOS5420_FSYS2_OPTION); + + tmp = pmu_raw_readl(EXYNOS5420_PSGEN_OPTION); + tmp |= EXYNOS5420_EMULATION; + pmu_raw_writel(tmp, EXYNOS5420_PSGEN_OPTION); +} + + +static int exynos_pm_suspend(void) +{ + exynos_pm_central_suspend(); + + /* Setting SEQ_OPTION register */ + pmu_raw_writel(S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0, + S5P_CENTRAL_SEQ_OPTION); + + if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) + exynos_cpu_save_register(); + + return 0; +} + +static int exynos5420_pm_suspend(void) +{ + u32 this_cluster; + + exynos_pm_central_suspend(); + + /* Setting SEQ_OPTION register */ + + this_cluster = MPIDR_AFFINITY_LEVEL(read_cpuid_mpidr(), 1); + if (!this_cluster) + pmu_raw_writel(EXYNOS5420_ARM_USE_STANDBY_WFI0, + S5P_CENTRAL_SEQ_OPTION); + else + pmu_raw_writel(EXYNOS5420_KFC_USE_STANDBY_WFI0, + S5P_CENTRAL_SEQ_OPTION); + return 0; +} + +static void exynos_pm_resume(void) +{ + u32 cpuid = read_cpuid_part(); + + if (exynos_pm_central_resume()) + goto early_wakeup; + + if (cpuid == ARM_CPU_PART_CORTEX_A9) + exynos_scu_enable(); + + if (call_firmware_op(resume) == -ENOSYS + && cpuid == ARM_CPU_PART_CORTEX_A9) + exynos_cpu_restore_register(); + +early_wakeup: + + /* Clear SLEEP mode set in INFORM1 */ + pmu_raw_writel(0x0, S5P_INFORM1); + exynos_set_delayed_reset_assertion(true); +} + +static void exynos3250_pm_resume(void) +{ + u32 cpuid = read_cpuid_part(); + + if (exynos_pm_central_resume()) + goto early_wakeup; + + pmu_raw_writel(S5P_USE_STANDBY_WFI_ALL, S5P_CENTRAL_SEQ_OPTION); + + if (call_firmware_op(resume) == -ENOSYS + && cpuid == ARM_CPU_PART_CORTEX_A9) + exynos_cpu_restore_register(); + +early_wakeup: + + /* Clear SLEEP mode set in INFORM1 */ + pmu_raw_writel(0x0, S5P_INFORM1); +} + +static void exynos5420_prepare_pm_resume(void) +{ + unsigned int mpidr, cluster; + + mpidr = read_cpuid_mpidr(); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + if (IS_ENABLED(CONFIG_EXYNOS_MCPM)) + WARN_ON(mcpm_cpu_powered_up()); + + if (IS_ENABLED(CONFIG_HW_PERF_EVENTS) && cluster != 0) { + /* + * When system is resumed on the LITTLE/KFC core (cluster 1), + * the DSCR is not properly updated until the power is turned + * on also for the cluster 0. Enable it for a while to + * propagate the SPNIDEN and SPIDEN signals from Secure JTAG + * block and avoid undefined instruction issue on CP14 reset. + */ + pmu_raw_writel(S5P_CORE_LOCAL_PWR_EN, + EXYNOS_COMMON_CONFIGURATION(0)); + pmu_raw_writel(0, + EXYNOS_COMMON_CONFIGURATION(0)); + } +} + +static void exynos5420_pm_resume(void) +{ + unsigned long tmp; + + /* Restore the CPU0 low power state register */ + tmp = pmu_raw_readl(EXYNOS5_ARM_CORE0_SYS_PWR_REG); + pmu_raw_writel(tmp | S5P_CORE_LOCAL_PWR_EN, + EXYNOS5_ARM_CORE0_SYS_PWR_REG); + + /* Restore the sysram cpu state register */ + writel_relaxed(pm_state.cpu_state, + pm_state.sysram_base + EXYNOS5420_CPU_STATE); + if (pm_state.secure_firmware) + exynos_smc(SMC_CMD_REG, + SMC_REG_ID_SFR_W(pm_state.sysram_phys + + EXYNOS5420_CPU_STATE), + EXYNOS_AFTR_MAGIC, 0); + + pmu_raw_writel(EXYNOS5420_USE_STANDBY_WFI_ALL, + S5P_CENTRAL_SEQ_OPTION); + + if (exynos_pm_central_resume()) + goto early_wakeup; + + pmu_raw_writel(pm_state.pmu_spare3, S5P_PMU_SPARE3); + +early_wakeup: + + tmp = pmu_raw_readl(EXYNOS5420_SFR_AXI_CGDIS1); + tmp &= ~EXYNOS5420_UFS; + pmu_raw_writel(tmp, EXYNOS5420_SFR_AXI_CGDIS1); + + tmp = pmu_raw_readl(EXYNOS5420_FSYS2_OPTION); + tmp &= ~EXYNOS5420_EMULATION; + pmu_raw_writel(tmp, EXYNOS5420_FSYS2_OPTION); + + tmp = pmu_raw_readl(EXYNOS5420_PSGEN_OPTION); + tmp &= ~EXYNOS5420_EMULATION; + pmu_raw_writel(tmp, EXYNOS5420_PSGEN_OPTION); + + /* Clear SLEEP mode set in INFORM1 */ + pmu_raw_writel(0x0, S5P_INFORM1); +} + +/* + * Suspend Ops + */ + +static int exynos_suspend_enter(suspend_state_t state) +{ + u32 eint_wakeup_mask = exynos_read_eint_wakeup_mask(); + int ret; + + pr_debug("%s: suspending the system...\n", __func__); + + pr_debug("%s: wakeup masks: %08x,%08x\n", __func__, + exynos_irqwake_intmask, eint_wakeup_mask); + + if (exynos_irqwake_intmask == -1U + && eint_wakeup_mask == EXYNOS_EINT_WAKEUP_MASK_DISABLED) { + pr_err("%s: No wake-up sources!\n", __func__); + pr_err("%s: Aborting sleep\n", __func__); + return -EINVAL; + } + + if (pm_data->pm_prepare) + pm_data->pm_prepare(); + flush_cache_all(); + + ret = call_firmware_op(suspend); + if (ret == -ENOSYS) + ret = cpu_suspend(0, pm_data->cpu_suspend); + if (ret) + return ret; + + if (pm_data->pm_resume_prepare) + pm_data->pm_resume_prepare(); + + pr_debug("%s: wakeup stat: %08x\n", __func__, + pmu_raw_readl(S5P_WAKEUP_STAT)); + + pr_debug("%s: resuming the system...\n", __func__); + + return 0; +} + +static int exynos_suspend_prepare(void) +{ + int ret; + + /* + * REVISIT: It would be better if struct platform_suspend_ops + * .prepare handler get the suspend_state_t as a parameter to + * avoid hard-coding the suspend to mem state. It's safe to do + * it now only because the suspend_valid_only_mem function is + * used as the .valid callback used to check if a given state + * is supported by the platform anyways. + */ + ret = regulator_suspend_prepare(PM_SUSPEND_MEM); + if (ret) { + pr_err("Failed to prepare regulators for suspend (%d)\n", ret); + return ret; + } + + return 0; +} + +static void exynos_suspend_finish(void) +{ + int ret; + + ret = regulator_suspend_finish(); + if (ret) + pr_warn("Failed to resume regulators from suspend (%d)\n", ret); +} + +static const struct platform_suspend_ops exynos_suspend_ops = { + .enter = exynos_suspend_enter, + .prepare = exynos_suspend_prepare, + .finish = exynos_suspend_finish, + .valid = suspend_valid_only_mem, +}; + +static const struct exynos_pm_data exynos3250_pm_data = { + .wkup_irq = exynos3250_wkup_irq, + .wake_disable_mask = ((0xFF << 8) | (0x1F << 1)), + .pm_suspend = exynos_pm_suspend, + .pm_resume = exynos3250_pm_resume, + .pm_prepare = exynos3250_pm_prepare, + .cpu_suspend = exynos3250_cpu_suspend, +}; + +static const struct exynos_pm_data exynos4_pm_data = { + .wkup_irq = exynos4_wkup_irq, + .wake_disable_mask = ((0xFF << 8) | (0x1F << 1)), + .pm_suspend = exynos_pm_suspend, + .pm_resume = exynos_pm_resume, + .pm_prepare = exynos_pm_prepare, + .cpu_suspend = exynos_cpu_suspend, +}; + +static const struct exynos_pm_data exynos5250_pm_data = { + .wkup_irq = exynos5250_wkup_irq, + .wake_disable_mask = ((0xFF << 8) | (0x1F << 1)), + .pm_suspend = exynos_pm_suspend, + .pm_resume = exynos_pm_resume, + .pm_prepare = exynos_pm_prepare, + .cpu_suspend = exynos_cpu_suspend, +}; + +static const struct exynos_pm_data exynos5420_pm_data = { + .wkup_irq = exynos5250_wkup_irq, + .wake_disable_mask = (0x7F << 7) | (0x1F << 1), + .pm_resume_prepare = exynos5420_prepare_pm_resume, + .pm_resume = exynos5420_pm_resume, + .pm_suspend = exynos5420_pm_suspend, + .pm_prepare = exynos5420_pm_prepare, + .cpu_suspend = exynos5420_cpu_suspend, +}; + +static const struct of_device_id exynos_pmu_of_device_ids[] __initconst = { + { + .compatible = "samsung,exynos3250-pmu", + .data = &exynos3250_pm_data, + }, { + .compatible = "samsung,exynos4210-pmu", + .data = &exynos4_pm_data, + }, { + .compatible = "samsung,exynos4412-pmu", + .data = &exynos4_pm_data, + }, { + .compatible = "samsung,exynos5250-pmu", + .data = &exynos5250_pm_data, + }, { + .compatible = "samsung,exynos5420-pmu", + .data = &exynos5420_pm_data, + }, + { /*sentinel*/ }, +}; + +static struct syscore_ops exynos_pm_syscore_ops; + +void __init exynos_pm_init(void) +{ + const struct of_device_id *match; + struct device_node *np; + u32 tmp; + + np = of_find_matching_node_and_match(NULL, exynos_pmu_of_device_ids, &match); + if (!np) { + pr_err("Failed to find PMU node\n"); + return; + } + + if (WARN_ON(!of_find_property(np, "interrupt-controller", NULL))) { + pr_warn("Outdated DT detected, suspend/resume will NOT work\n"); + of_node_put(np); + return; + } + of_node_put(np); + + pm_data = (const struct exynos_pm_data *) match->data; + + /* All wakeup disable */ + tmp = pmu_raw_readl(S5P_WAKEUP_MASK); + tmp |= pm_data->wake_disable_mask; + pmu_raw_writel(tmp, S5P_WAKEUP_MASK); + + exynos_pm_syscore_ops.suspend = pm_data->pm_suspend; + exynos_pm_syscore_ops.resume = pm_data->pm_resume; + + register_syscore_ops(&exynos_pm_syscore_ops); + suspend_set_ops(&exynos_suspend_ops); + + /* + * Applicable as of now only to Exynos542x. If booted under secure + * firmware, the non-secure region of sysram should be used. + */ + if (exynos_secure_firmware_available()) { + pm_state.sysram_phys = sysram_base_phys; + pm_state.sysram_base = sysram_ns_base_addr; + pm_state.secure_firmware = true; + } else { + pm_state.sysram_base = sysram_base_addr; + } +} |