1 /* 2 * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. 3 * http://www.samsung.com 4 * 5 * EXYNOS - Power Management support 6 * 7 * Based on arch/arm/mach-s3c2410/pm.c 8 * Copyright (c) 2006 Simtec Electronics 9 * Ben Dooks <ben@simtec.co.uk> 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License version 2 as 13 * published by the Free Software Foundation. 14 */ 15 16 #include <linux/init.h> 17 #include <linux/suspend.h> 18 #include <linux/cpu_pm.h> 19 #include <linux/io.h> 20 #include <linux/err.h> 21 22 #include <asm/firmware.h> 23 #include <asm/smp_scu.h> 24 #include <asm/suspend.h> 25 26 #include <mach/map.h> 27 28 #include <plat/pm-common.h> 29 30 #include "common.h" 31 #include "exynos-pmu.h" 32 #include "regs-pmu.h" 33 34 static inline void __iomem *exynos_boot_vector_addr(void) 35 { 36 if (samsung_rev() == EXYNOS4210_REV_1_1) 37 return pmu_base_addr + S5P_INFORM7; 38 else if (samsung_rev() == EXYNOS4210_REV_1_0) 39 return sysram_base_addr + 0x24; 40 return pmu_base_addr + S5P_INFORM0; 41 } 42 43 static inline void __iomem *exynos_boot_vector_flag(void) 44 { 45 if (samsung_rev() == EXYNOS4210_REV_1_1) 46 return pmu_base_addr + S5P_INFORM6; 47 else if (samsung_rev() == EXYNOS4210_REV_1_0) 48 return sysram_base_addr + 0x20; 49 return pmu_base_addr + S5P_INFORM1; 50 } 51 52 #define S5P_CHECK_AFTR 0xFCBA0D10 53 54 /* For Cortex-A9 Diagnostic and Power control register */ 55 static unsigned int save_arm_register[2]; 56 57 void exynos_cpu_save_register(void) 58 { 59 unsigned long tmp; 60 61 /* Save Power control register */ 62 asm ("mrc p15, 0, %0, c15, c0, 0" 63 : "=r" (tmp) : : "cc"); 64 65 save_arm_register[0] = tmp; 66 67 /* Save Diagnostic register */ 68 asm ("mrc p15, 0, %0, c15, c0, 1" 69 : "=r" (tmp) : : "cc"); 70 71 save_arm_register[1] = tmp; 72 } 73 74 void exynos_cpu_restore_register(void) 75 { 76 unsigned long tmp; 77 78 /* Restore Power control register */ 79 tmp = save_arm_register[0]; 80 81 asm volatile ("mcr p15, 0, %0, c15, c0, 0" 82 : : "r" (tmp) 83 : "cc"); 84 85 /* Restore Diagnostic register */ 86 tmp = save_arm_register[1]; 87 88 asm volatile ("mcr p15, 0, %0, c15, c0, 1" 89 : : "r" (tmp) 90 : "cc"); 91 } 92 93 void exynos_pm_central_suspend(void) 94 { 95 unsigned long tmp; 96 97 /* Setting Central Sequence Register for power down mode */ 98 tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); 99 tmp &= ~S5P_CENTRAL_LOWPWR_CFG; 100 pmu_raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); 101 } 102 103 int exynos_pm_central_resume(void) 104 { 105 unsigned long tmp; 106 107 /* 108 * If PMU failed while entering sleep mode, WFI will be 109 * ignored by PMU and then exiting cpu_do_idle(). 110 * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically 111 * in this situation. 112 */ 113 tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); 114 if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) { 115 tmp |= S5P_CENTRAL_LOWPWR_CFG; 116 pmu_raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); 117 /* clear the wakeup state register */ 118 pmu_raw_writel(0x0, S5P_WAKEUP_STAT); 119 /* No need to perform below restore code */ 120 return -1; 121 } 122 123 return 0; 124 } 125 126 /* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */ 127 static void exynos_set_wakeupmask(long mask) 128 { 129 pmu_raw_writel(mask, S5P_WAKEUP_MASK); 130 } 131 132 static void exynos_cpu_set_boot_vector(long flags) 133 { 134 __raw_writel(virt_to_phys(exynos_cpu_resume), 135 exynos_boot_vector_addr()); 136 __raw_writel(flags, exynos_boot_vector_flag()); 137 } 138 139 static int exynos_aftr_finisher(unsigned long flags) 140 { 141 int ret; 142 143 exynos_set_wakeupmask(0x0000ff3e); 144 /* Set value of power down register for aftr mode */ 145 exynos_sys_powerdown_conf(SYS_AFTR); 146 147 ret = call_firmware_op(do_idle, FW_DO_IDLE_AFTR); 148 if (ret == -ENOSYS) { 149 if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) 150 exynos_cpu_save_register(); 151 exynos_cpu_set_boot_vector(S5P_CHECK_AFTR); 152 cpu_do_idle(); 153 } 154 155 return 1; 156 } 157 158 void exynos_enter_aftr(void) 159 { 160 cpu_pm_enter(); 161 162 exynos_pm_central_suspend(); 163 164 if (of_machine_is_compatible("samsung,exynos4212") || 165 of_machine_is_compatible("samsung,exynos4412")) { 166 /* Setting SEQ_OPTION register */ 167 pmu_raw_writel(S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0, 168 S5P_CENTRAL_SEQ_OPTION); 169 } 170 171 cpu_suspend(0, exynos_aftr_finisher); 172 173 if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) { 174 scu_enable(S5P_VA_SCU); 175 if (call_firmware_op(resume) == -ENOSYS) 176 exynos_cpu_restore_register(); 177 } 178 179 exynos_pm_central_resume(); 180 181 cpu_pm_exit(); 182 } 183 184 static atomic_t cpu1_wakeup = ATOMIC_INIT(0); 185 186 static int exynos_cpu0_enter_aftr(void) 187 { 188 int ret = -1; 189 190 /* 191 * If the other cpu is powered on, we have to power it off, because 192 * the AFTR state won't work otherwise 193 */ 194 if (cpu_online(1)) { 195 /* 196 * We reach a sync point with the coupled idle state, we know 197 * the other cpu will power down itself or will abort the 198 * sequence, let's wait for one of these to happen 199 */ 200 while (exynos_cpu_power_state(1)) { 201 /* 202 * The other cpu may skip idle and boot back 203 * up again 204 */ 205 if (atomic_read(&cpu1_wakeup)) 206 goto abort; 207 208 /* 209 * The other cpu may bounce through idle and 210 * boot back up again, getting stuck in the 211 * boot rom code 212 */ 213 if (__raw_readl(cpu_boot_reg_base()) == 0) 214 goto abort; 215 216 cpu_relax(); 217 } 218 } 219 220 exynos_enter_aftr(); 221 ret = 0; 222 223 abort: 224 if (cpu_online(1)) { 225 /* 226 * Set the boot vector to something non-zero 227 */ 228 __raw_writel(virt_to_phys(exynos_cpu_resume), 229 cpu_boot_reg_base()); 230 dsb(); 231 232 /* 233 * Turn on cpu1 and wait for it to be on 234 */ 235 exynos_cpu_power_up(1); 236 while (exynos_cpu_power_state(1) != S5P_CORE_LOCAL_PWR_EN) 237 cpu_relax(); 238 239 while (!atomic_read(&cpu1_wakeup)) { 240 /* 241 * Poke cpu1 out of the boot rom 242 */ 243 __raw_writel(virt_to_phys(exynos_cpu_resume), 244 cpu_boot_reg_base()); 245 246 arch_send_wakeup_ipi_mask(cpumask_of(1)); 247 } 248 } 249 250 return ret; 251 } 252 253 static int exynos_wfi_finisher(unsigned long flags) 254 { 255 cpu_do_idle(); 256 257 return -1; 258 } 259 260 static int exynos_cpu1_powerdown(void) 261 { 262 int ret = -1; 263 264 /* 265 * Idle sequence for cpu1 266 */ 267 if (cpu_pm_enter()) 268 goto cpu1_aborted; 269 270 /* 271 * Turn off cpu 1 272 */ 273 exynos_cpu_power_down(1); 274 275 ret = cpu_suspend(0, exynos_wfi_finisher); 276 277 cpu_pm_exit(); 278 279 cpu1_aborted: 280 dsb(); 281 /* 282 * Notify cpu 0 that cpu 1 is awake 283 */ 284 atomic_set(&cpu1_wakeup, 1); 285 286 return ret; 287 } 288 289 static void exynos_pre_enter_aftr(void) 290 { 291 __raw_writel(virt_to_phys(exynos_cpu_resume), cpu_boot_reg_base()); 292 } 293 294 static void exynos_post_enter_aftr(void) 295 { 296 atomic_set(&cpu1_wakeup, 0); 297 } 298 299 struct cpuidle_exynos_data cpuidle_coupled_exynos_data = { 300 .cpu0_enter_aftr = exynos_cpu0_enter_aftr, 301 .cpu1_powerdown = exynos_cpu1_powerdown, 302 .pre_enter_aftr = exynos_pre_enter_aftr, 303 .post_enter_aftr = exynos_post_enter_aftr, 304 }; 305