xref: /openbmc/linux/arch/arm/mach-exynos/pm.c (revision e8f6f3b4)
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 <plat/pm-common.h>
27 
28 #include "common.h"
29 #include "exynos-pmu.h"
30 #include "regs-pmu.h"
31 #include "regs-sys.h"
32 
33 static inline void __iomem *exynos_boot_vector_addr(void)
34 {
35 	if (samsung_rev() == EXYNOS4210_REV_1_1)
36 		return pmu_base_addr + S5P_INFORM7;
37 	else if (samsung_rev() == EXYNOS4210_REV_1_0)
38 		return sysram_base_addr + 0x24;
39 	return pmu_base_addr + S5P_INFORM0;
40 }
41 
42 static inline void __iomem *exynos_boot_vector_flag(void)
43 {
44 	if (samsung_rev() == EXYNOS4210_REV_1_1)
45 		return pmu_base_addr + S5P_INFORM6;
46 	else if (samsung_rev() == EXYNOS4210_REV_1_0)
47 		return sysram_base_addr + 0x20;
48 	return pmu_base_addr + S5P_INFORM1;
49 }
50 
51 #define S5P_CHECK_AFTR  0xFCBA0D10
52 
53 /* For Cortex-A9 Diagnostic and Power control register */
54 static unsigned int save_arm_register[2];
55 
56 void exynos_cpu_save_register(void)
57 {
58 	unsigned long tmp;
59 
60 	/* Save Power control register */
61 	asm ("mrc p15, 0, %0, c15, c0, 0"
62 	     : "=r" (tmp) : : "cc");
63 
64 	save_arm_register[0] = tmp;
65 
66 	/* Save Diagnostic register */
67 	asm ("mrc p15, 0, %0, c15, c0, 1"
68 	     : "=r" (tmp) : : "cc");
69 
70 	save_arm_register[1] = tmp;
71 }
72 
73 void exynos_cpu_restore_register(void)
74 {
75 	unsigned long tmp;
76 
77 	/* Restore Power control register */
78 	tmp = save_arm_register[0];
79 
80 	asm volatile ("mcr p15, 0, %0, c15, c0, 0"
81 		      : : "r" (tmp)
82 		      : "cc");
83 
84 	/* Restore Diagnostic register */
85 	tmp = save_arm_register[1];
86 
87 	asm volatile ("mcr p15, 0, %0, c15, c0, 1"
88 		      : : "r" (tmp)
89 		      : "cc");
90 }
91 
92 void exynos_pm_central_suspend(void)
93 {
94 	unsigned long tmp;
95 
96 	/* Setting Central Sequence Register for power down mode */
97 	tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
98 	tmp &= ~S5P_CENTRAL_LOWPWR_CFG;
99 	pmu_raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
100 
101 	/* Setting SEQ_OPTION register */
102 	pmu_raw_writel(S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0,
103 		       S5P_CENTRAL_SEQ_OPTION);
104 }
105 
106 int exynos_pm_central_resume(void)
107 {
108 	unsigned long tmp;
109 
110 	/*
111 	 * If PMU failed while entering sleep mode, WFI will be
112 	 * ignored by PMU and then exiting cpu_do_idle().
113 	 * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically
114 	 * in this situation.
115 	 */
116 	tmp = pmu_raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
117 	if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) {
118 		tmp |= S5P_CENTRAL_LOWPWR_CFG;
119 		pmu_raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
120 		/* clear the wakeup state register */
121 		pmu_raw_writel(0x0, S5P_WAKEUP_STAT);
122 		/* No need to perform below restore code */
123 		return -1;
124 	}
125 
126 	return 0;
127 }
128 
129 /* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */
130 static void exynos_set_wakeupmask(long mask)
131 {
132 	pmu_raw_writel(mask, S5P_WAKEUP_MASK);
133 }
134 
135 static void exynos_cpu_set_boot_vector(long flags)
136 {
137 	__raw_writel(virt_to_phys(exynos_cpu_resume),
138 		     exynos_boot_vector_addr());
139 	__raw_writel(flags, exynos_boot_vector_flag());
140 }
141 
142 static int exynos_aftr_finisher(unsigned long flags)
143 {
144 	int ret;
145 
146 	exynos_set_wakeupmask(0x0000ff3e);
147 	/* Set value of power down register for aftr mode */
148 	exynos_sys_powerdown_conf(SYS_AFTR);
149 
150 	ret = call_firmware_op(do_idle, FW_DO_IDLE_AFTR);
151 	if (ret == -ENOSYS) {
152 		if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9)
153 			exynos_cpu_save_register();
154 		exynos_cpu_set_boot_vector(S5P_CHECK_AFTR);
155 		cpu_do_idle();
156 	}
157 
158 	return 1;
159 }
160 
161 void exynos_enter_aftr(void)
162 {
163 	cpu_pm_enter();
164 
165 	exynos_pm_central_suspend();
166 
167 	cpu_suspend(0, exynos_aftr_finisher);
168 
169 	if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) {
170 		scu_enable(S5P_VA_SCU);
171 		if (call_firmware_op(resume) == -ENOSYS)
172 			exynos_cpu_restore_register();
173 	}
174 
175 	exynos_pm_central_resume();
176 
177 	cpu_pm_exit();
178 }
179