162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. 462306a36Sopenharmony_ci// http://www.samsung.com 562306a36Sopenharmony_ci// 662306a36Sopenharmony_ci// Exynos - Power Management support 762306a36Sopenharmony_ci// 862306a36Sopenharmony_ci// Based on arch/arm/mach-s3c2410/pm.c 962306a36Sopenharmony_ci// Copyright (c) 2006 Simtec Electronics 1062306a36Sopenharmony_ci// Ben Dooks <ben@simtec.co.uk> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/suspend.h> 1462306a36Sopenharmony_ci#include <linux/cpu_pm.h> 1562306a36Sopenharmony_ci#include <linux/io.h> 1662306a36Sopenharmony_ci#include <linux/of.h> 1762306a36Sopenharmony_ci#include <linux/soc/samsung/exynos-regs-pmu.h> 1862306a36Sopenharmony_ci#include <linux/soc/samsung/exynos-pmu.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <asm/firmware.h> 2162306a36Sopenharmony_ci#include <asm/smp_scu.h> 2262306a36Sopenharmony_ci#include <asm/suspend.h> 2362306a36Sopenharmony_ci#include <asm/cacheflush.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include "common.h" 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic inline void __iomem *exynos_boot_vector_addr(void) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci if (exynos_rev() == EXYNOS4210_REV_1_1) 3062306a36Sopenharmony_ci return pmu_base_addr + S5P_INFORM7; 3162306a36Sopenharmony_ci else if (exynos_rev() == EXYNOS4210_REV_1_0) 3262306a36Sopenharmony_ci return sysram_base_addr + 0x24; 3362306a36Sopenharmony_ci return pmu_base_addr + S5P_INFORM0; 3462306a36Sopenharmony_ci} 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic inline void __iomem *exynos_boot_vector_flag(void) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci if (exynos_rev() == EXYNOS4210_REV_1_1) 3962306a36Sopenharmony_ci return pmu_base_addr + S5P_INFORM6; 4062306a36Sopenharmony_ci else if (exynos_rev() == EXYNOS4210_REV_1_0) 4162306a36Sopenharmony_ci return sysram_base_addr + 0x20; 4262306a36Sopenharmony_ci return pmu_base_addr + S5P_INFORM1; 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#define S5P_CHECK_AFTR 0xFCBA0D10 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci/* For Cortex-A9 Diagnostic and Power control register */ 4862306a36Sopenharmony_cistatic unsigned int save_arm_register[2]; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_civoid exynos_cpu_save_register(void) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci unsigned long tmp; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci /* Save Power control register */ 5562306a36Sopenharmony_ci asm ("mrc p15, 0, %0, c15, c0, 0" 5662306a36Sopenharmony_ci : "=r" (tmp) : : "cc"); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci save_arm_register[0] = tmp; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci /* Save Diagnostic register */ 6162306a36Sopenharmony_ci asm ("mrc p15, 0, %0, c15, c0, 1" 6262306a36Sopenharmony_ci : "=r" (tmp) : : "cc"); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci save_arm_register[1] = tmp; 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_civoid exynos_cpu_restore_register(void) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci unsigned long tmp; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci /* Restore Power control register */ 7262306a36Sopenharmony_ci tmp = save_arm_register[0]; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci asm volatile ("mcr p15, 0, %0, c15, c0, 0" 7562306a36Sopenharmony_ci : : "r" (tmp) 7662306a36Sopenharmony_ci : "cc"); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci /* Restore Diagnostic register */ 7962306a36Sopenharmony_ci tmp = save_arm_register[1]; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci asm volatile ("mcr p15, 0, %0, c15, c0, 1" 8262306a36Sopenharmony_ci : : "r" (tmp) 8362306a36Sopenharmony_ci : "cc"); 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_civoid exynos_pm_central_suspend(void) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci unsigned long tmp; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci /* Setting Central Sequence Register for power down mode */ 9162306a36Sopenharmony_ci tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); 9262306a36Sopenharmony_ci tmp &= ~S5P_CENTRAL_LOWPWR_CFG; 9362306a36Sopenharmony_ci pmu_raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ciint exynos_pm_central_resume(void) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci unsigned long tmp; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci /* 10162306a36Sopenharmony_ci * If PMU failed while entering sleep mode, WFI will be 10262306a36Sopenharmony_ci * ignored by PMU and then exiting cpu_do_idle(). 10362306a36Sopenharmony_ci * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically 10462306a36Sopenharmony_ci * in this situation. 10562306a36Sopenharmony_ci */ 10662306a36Sopenharmony_ci tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); 10762306a36Sopenharmony_ci if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) { 10862306a36Sopenharmony_ci tmp |= S5P_CENTRAL_LOWPWR_CFG; 10962306a36Sopenharmony_ci pmu_raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); 11062306a36Sopenharmony_ci /* clear the wakeup state register */ 11162306a36Sopenharmony_ci pmu_raw_writel(0x0, S5P_WAKEUP_STAT); 11262306a36Sopenharmony_ci /* No need to perform below restore code */ 11362306a36Sopenharmony_ci return -1; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci return 0; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci/* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */ 12062306a36Sopenharmony_cistatic void exynos_set_wakeupmask(long mask) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci pmu_raw_writel(mask, S5P_WAKEUP_MASK); 12362306a36Sopenharmony_ci if (soc_is_exynos3250()) 12462306a36Sopenharmony_ci pmu_raw_writel(0x0, S5P_WAKEUP_MASK2); 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic void exynos_cpu_set_boot_vector(long flags) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci writel_relaxed(__pa_symbol(exynos_cpu_resume), 13062306a36Sopenharmony_ci exynos_boot_vector_addr()); 13162306a36Sopenharmony_ci writel_relaxed(flags, exynos_boot_vector_flag()); 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic int exynos_aftr_finisher(unsigned long flags) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci int ret; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci exynos_set_wakeupmask(soc_is_exynos3250() ? 0x40003ffe : 0x0000ff3e); 13962306a36Sopenharmony_ci /* Set value of power down register for aftr mode */ 14062306a36Sopenharmony_ci exynos_sys_powerdown_conf(SYS_AFTR); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci ret = call_firmware_op(do_idle, FW_DO_IDLE_AFTR); 14362306a36Sopenharmony_ci if (ret == -ENOSYS) { 14462306a36Sopenharmony_ci if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) 14562306a36Sopenharmony_ci exynos_cpu_save_register(); 14662306a36Sopenharmony_ci exynos_cpu_set_boot_vector(S5P_CHECK_AFTR); 14762306a36Sopenharmony_ci cpu_do_idle(); 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci return 1; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_civoid exynos_enter_aftr(void) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci unsigned int cpuid = smp_processor_id(); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci cpu_pm_enter(); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (soc_is_exynos3250()) 16062306a36Sopenharmony_ci exynos_set_boot_flag(cpuid, C2_STATE); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci exynos_pm_central_suspend(); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci if (soc_is_exynos4212() || soc_is_exynos4412()) { 16562306a36Sopenharmony_ci /* Setting SEQ_OPTION register */ 16662306a36Sopenharmony_ci pmu_raw_writel(S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0, 16762306a36Sopenharmony_ci S5P_CENTRAL_SEQ_OPTION); 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci cpu_suspend(0, exynos_aftr_finisher); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) { 17362306a36Sopenharmony_ci exynos_scu_enable(); 17462306a36Sopenharmony_ci if (call_firmware_op(resume) == -ENOSYS) 17562306a36Sopenharmony_ci exynos_cpu_restore_register(); 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci exynos_pm_central_resume(); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci if (soc_is_exynos3250()) 18162306a36Sopenharmony_ci exynos_clear_boot_flag(cpuid, C2_STATE); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci cpu_pm_exit(); 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci#if defined(CONFIG_SMP) && defined(CONFIG_ARM_EXYNOS_CPUIDLE) 18762306a36Sopenharmony_cistatic atomic_t cpu1_wakeup = ATOMIC_INIT(0); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic int exynos_cpu0_enter_aftr(void) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci int ret = -1; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* 19462306a36Sopenharmony_ci * If the other cpu is powered on, we have to power it off, because 19562306a36Sopenharmony_ci * the AFTR state won't work otherwise 19662306a36Sopenharmony_ci */ 19762306a36Sopenharmony_ci if (cpu_online(1)) { 19862306a36Sopenharmony_ci /* 19962306a36Sopenharmony_ci * We reach a sync point with the coupled idle state, we know 20062306a36Sopenharmony_ci * the other cpu will power down itself or will abort the 20162306a36Sopenharmony_ci * sequence, let's wait for one of these to happen 20262306a36Sopenharmony_ci */ 20362306a36Sopenharmony_ci while (exynos_cpu_power_state(1)) { 20462306a36Sopenharmony_ci unsigned long boot_addr; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci /* 20762306a36Sopenharmony_ci * The other cpu may skip idle and boot back 20862306a36Sopenharmony_ci * up again 20962306a36Sopenharmony_ci */ 21062306a36Sopenharmony_ci if (atomic_read(&cpu1_wakeup)) 21162306a36Sopenharmony_ci goto abort; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* 21462306a36Sopenharmony_ci * The other cpu may bounce through idle and 21562306a36Sopenharmony_ci * boot back up again, getting stuck in the 21662306a36Sopenharmony_ci * boot rom code 21762306a36Sopenharmony_ci */ 21862306a36Sopenharmony_ci ret = exynos_get_boot_addr(1, &boot_addr); 21962306a36Sopenharmony_ci if (ret) 22062306a36Sopenharmony_ci goto fail; 22162306a36Sopenharmony_ci ret = -1; 22262306a36Sopenharmony_ci if (boot_addr == 0) 22362306a36Sopenharmony_ci goto abort; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci cpu_relax(); 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci exynos_enter_aftr(); 23062306a36Sopenharmony_ci ret = 0; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ciabort: 23362306a36Sopenharmony_ci if (cpu_online(1)) { 23462306a36Sopenharmony_ci unsigned long boot_addr = __pa_symbol(exynos_cpu_resume); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci /* 23762306a36Sopenharmony_ci * Set the boot vector to something non-zero 23862306a36Sopenharmony_ci */ 23962306a36Sopenharmony_ci ret = exynos_set_boot_addr(1, boot_addr); 24062306a36Sopenharmony_ci if (ret) 24162306a36Sopenharmony_ci goto fail; 24262306a36Sopenharmony_ci dsb(); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci /* 24562306a36Sopenharmony_ci * Turn on cpu1 and wait for it to be on 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_ci exynos_cpu_power_up(1); 24862306a36Sopenharmony_ci while (exynos_cpu_power_state(1) != S5P_CORE_LOCAL_PWR_EN) 24962306a36Sopenharmony_ci cpu_relax(); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci if (soc_is_exynos3250()) { 25262306a36Sopenharmony_ci while (!pmu_raw_readl(S5P_PMU_SPARE2) && 25362306a36Sopenharmony_ci !atomic_read(&cpu1_wakeup)) 25462306a36Sopenharmony_ci cpu_relax(); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci if (!atomic_read(&cpu1_wakeup)) 25762306a36Sopenharmony_ci exynos_core_restart(1); 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci while (!atomic_read(&cpu1_wakeup)) { 26162306a36Sopenharmony_ci smp_rmb(); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci /* 26462306a36Sopenharmony_ci * Poke cpu1 out of the boot rom 26562306a36Sopenharmony_ci */ 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci ret = exynos_set_boot_addr(1, boot_addr); 26862306a36Sopenharmony_ci if (ret) 26962306a36Sopenharmony_ci goto fail; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci call_firmware_op(cpu_boot, 1); 27262306a36Sopenharmony_ci dsb_sev(); 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_cifail: 27662306a36Sopenharmony_ci return ret; 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic int exynos_wfi_finisher(unsigned long flags) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci if (soc_is_exynos3250()) 28262306a36Sopenharmony_ci flush_cache_all(); 28362306a36Sopenharmony_ci cpu_do_idle(); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci return -1; 28662306a36Sopenharmony_ci} 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic int exynos_cpu1_powerdown(void) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci int ret = -1; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci /* 29362306a36Sopenharmony_ci * Idle sequence for cpu1 29462306a36Sopenharmony_ci */ 29562306a36Sopenharmony_ci if (cpu_pm_enter()) 29662306a36Sopenharmony_ci goto cpu1_aborted; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci /* 29962306a36Sopenharmony_ci * Turn off cpu 1 30062306a36Sopenharmony_ci */ 30162306a36Sopenharmony_ci exynos_cpu_power_down(1); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (soc_is_exynos3250()) 30462306a36Sopenharmony_ci pmu_raw_writel(0, S5P_PMU_SPARE2); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci ret = cpu_suspend(0, exynos_wfi_finisher); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci cpu_pm_exit(); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_cicpu1_aborted: 31162306a36Sopenharmony_ci dsb(); 31262306a36Sopenharmony_ci /* 31362306a36Sopenharmony_ci * Notify cpu 0 that cpu 1 is awake 31462306a36Sopenharmony_ci */ 31562306a36Sopenharmony_ci atomic_set(&cpu1_wakeup, 1); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci return ret; 31862306a36Sopenharmony_ci} 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_cistatic void exynos_pre_enter_aftr(void) 32162306a36Sopenharmony_ci{ 32262306a36Sopenharmony_ci unsigned long boot_addr = __pa_symbol(exynos_cpu_resume); 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci (void)exynos_set_boot_addr(1, boot_addr); 32562306a36Sopenharmony_ci} 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cistatic void exynos_post_enter_aftr(void) 32862306a36Sopenharmony_ci{ 32962306a36Sopenharmony_ci atomic_set(&cpu1_wakeup, 0); 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistruct cpuidle_exynos_data cpuidle_coupled_exynos_data = { 33362306a36Sopenharmony_ci .cpu0_enter_aftr = exynos_cpu0_enter_aftr, 33462306a36Sopenharmony_ci .cpu1_powerdown = exynos_cpu1_powerdown, 33562306a36Sopenharmony_ci .pre_enter_aftr = exynos_pre_enter_aftr, 33662306a36Sopenharmony_ci .post_enter_aftr = exynos_post_enter_aftr, 33762306a36Sopenharmony_ci}; 33862306a36Sopenharmony_ci#endif /* CONFIG_SMP && CONFIG_ARM_EXYNOS_CPUIDLE */ 339