diff options
Diffstat (limited to 'arch/arm/mach-exynos/cpuidle.c')
-rw-r--r-- | arch/arm/mach-exynos/cpuidle.c | 151 |
1 files changed, 147 insertions, 4 deletions
diff --git a/arch/arm/mach-exynos/cpuidle.c b/arch/arm/mach-exynos/cpuidle.c index 4ebb382c5979..33ab4e7558af 100644 --- a/arch/arm/mach-exynos/cpuidle.c +++ b/arch/arm/mach-exynos/cpuidle.c | |||
@@ -11,25 +11,53 @@ | |||
11 | #include <linux/kernel.h> | 11 | #include <linux/kernel.h> |
12 | #include <linux/init.h> | 12 | #include <linux/init.h> |
13 | #include <linux/cpuidle.h> | 13 | #include <linux/cpuidle.h> |
14 | #include <linux/cpu_pm.h> | ||
14 | #include <linux/io.h> | 15 | #include <linux/io.h> |
15 | #include <linux/export.h> | 16 | #include <linux/export.h> |
16 | #include <linux/time.h> | 17 | #include <linux/time.h> |
17 | 18 | ||
18 | #include <asm/proc-fns.h> | 19 | #include <asm/proc-fns.h> |
20 | #include <asm/smp_scu.h> | ||
21 | #include <asm/suspend.h> | ||
22 | #include <asm/unified.h> | ||
23 | #include <mach/regs-pmu.h> | ||
24 | #include <mach/pmu.h> | ||
25 | |||
26 | #include <plat/cpu.h> | ||
27 | |||
28 | #define REG_DIRECTGO_ADDR (samsung_rev() == EXYNOS4210_REV_1_1 ? \ | ||
29 | S5P_INFORM7 : (samsung_rev() == EXYNOS4210_REV_1_0 ? \ | ||
30 | (S5P_VA_SYSRAM + 0x24) : S5P_INFORM0)) | ||
31 | #define REG_DIRECTGO_FLAG (samsung_rev() == EXYNOS4210_REV_1_1 ? \ | ||
32 | S5P_INFORM6 : (samsung_rev() == EXYNOS4210_REV_1_0 ? \ | ||
33 | (S5P_VA_SYSRAM + 0x20) : S5P_INFORM1)) | ||
34 | |||
35 | #define S5P_CHECK_AFTR 0xFCBA0D10 | ||
19 | 36 | ||
20 | static int exynos4_enter_idle(struct cpuidle_device *dev, | 37 | static int exynos4_enter_idle(struct cpuidle_device *dev, |
21 | struct cpuidle_driver *drv, | 38 | struct cpuidle_driver *drv, |
22 | int index); | 39 | int index); |
40 | static int exynos4_enter_lowpower(struct cpuidle_device *dev, | ||
41 | struct cpuidle_driver *drv, | ||
42 | int index); | ||
23 | 43 | ||
24 | static struct cpuidle_state exynos4_cpuidle_set[] = { | 44 | static struct cpuidle_state exynos4_cpuidle_set[] __initdata = { |
25 | [0] = { | 45 | [0] = { |
26 | .enter = exynos4_enter_idle, | 46 | .enter = exynos4_enter_idle, |
27 | .exit_latency = 1, | 47 | .exit_latency = 1, |
28 | .target_residency = 100000, | 48 | .target_residency = 100000, |
29 | .flags = CPUIDLE_FLAG_TIME_VALID, | 49 | .flags = CPUIDLE_FLAG_TIME_VALID, |
30 | .name = "IDLE", | 50 | .name = "C0", |
31 | .desc = "ARM clock gating(WFI)", | 51 | .desc = "ARM clock gating(WFI)", |
32 | }, | 52 | }, |
53 | [1] = { | ||
54 | .enter = exynos4_enter_lowpower, | ||
55 | .exit_latency = 300, | ||
56 | .target_residency = 100000, | ||
57 | .flags = CPUIDLE_FLAG_TIME_VALID, | ||
58 | .name = "C1", | ||
59 | .desc = "ARM power down", | ||
60 | }, | ||
33 | }; | 61 | }; |
34 | 62 | ||
35 | static DEFINE_PER_CPU(struct cpuidle_device, exynos4_cpuidle_device); | 63 | static DEFINE_PER_CPU(struct cpuidle_device, exynos4_cpuidle_device); |
@@ -39,9 +67,102 @@ static struct cpuidle_driver exynos4_idle_driver = { | |||
39 | .owner = THIS_MODULE, | 67 | .owner = THIS_MODULE, |
40 | }; | 68 | }; |
41 | 69 | ||
70 | /* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */ | ||
71 | static void exynos4_set_wakeupmask(void) | ||
72 | { | ||
73 | __raw_writel(0x0000ff3e, S5P_WAKEUP_MASK); | ||
74 | } | ||
75 | |||
76 | static unsigned int g_pwr_ctrl, g_diag_reg; | ||
77 | |||
78 | static void save_cpu_arch_register(void) | ||
79 | { | ||
80 | /*read power control register*/ | ||
81 | asm("mrc p15, 0, %0, c15, c0, 0" : "=r"(g_pwr_ctrl) : : "cc"); | ||
82 | /*read diagnostic register*/ | ||
83 | asm("mrc p15, 0, %0, c15, c0, 1" : "=r"(g_diag_reg) : : "cc"); | ||
84 | return; | ||
85 | } | ||
86 | |||
87 | static void restore_cpu_arch_register(void) | ||
88 | { | ||
89 | /*write power control register*/ | ||
90 | asm("mcr p15, 0, %0, c15, c0, 0" : : "r"(g_pwr_ctrl) : "cc"); | ||
91 | /*write diagnostic register*/ | ||
92 | asm("mcr p15, 0, %0, c15, c0, 1" : : "r"(g_diag_reg) : "cc"); | ||
93 | return; | ||
94 | } | ||
95 | |||
96 | static int idle_finisher(unsigned long flags) | ||
97 | { | ||
98 | cpu_do_idle(); | ||
99 | return 1; | ||
100 | } | ||
101 | |||
102 | static int exynos4_enter_core0_aftr(struct cpuidle_device *dev, | ||
103 | struct cpuidle_driver *drv, | ||
104 | int index) | ||
105 | { | ||
106 | struct timeval before, after; | ||
107 | int idle_time; | ||
108 | unsigned long tmp; | ||
109 | |||
110 | local_irq_disable(); | ||
111 | do_gettimeofday(&before); | ||
112 | |||
113 | exynos4_set_wakeupmask(); | ||
114 | |||
115 | /* Set value of power down register for aftr mode */ | ||
116 | exynos4_sys_powerdown_conf(SYS_AFTR); | ||
117 | |||
118 | __raw_writel(virt_to_phys(s3c_cpu_resume), REG_DIRECTGO_ADDR); | ||
119 | __raw_writel(S5P_CHECK_AFTR, REG_DIRECTGO_FLAG); | ||
120 | |||
121 | save_cpu_arch_register(); | ||
122 | |||
123 | /* Setting Central Sequence Register for power down mode */ | ||
124 | tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); | ||
125 | tmp &= ~S5P_CENTRAL_LOWPWR_CFG; | ||
126 | __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); | ||
127 | |||
128 | cpu_pm_enter(); | ||
129 | cpu_suspend(0, idle_finisher); | ||
130 | |||
131 | #ifdef CONFIG_SMP | ||
132 | scu_enable(S5P_VA_SCU); | ||
133 | #endif | ||
134 | cpu_pm_exit(); | ||
135 | |||
136 | restore_cpu_arch_register(); | ||
137 | |||
138 | /* | ||
139 | * If PMU failed while entering sleep mode, WFI will be | ||
140 | * ignored by PMU and then exiting cpu_do_idle(). | ||
141 | * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically | ||
142 | * in this situation. | ||
143 | */ | ||
144 | tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION); | ||
145 | if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) { | ||
146 | tmp |= S5P_CENTRAL_LOWPWR_CFG; | ||
147 | __raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION); | ||
148 | } | ||
149 | |||
150 | /* Clear wakeup state register */ | ||
151 | __raw_writel(0x0, S5P_WAKEUP_STAT); | ||
152 | |||
153 | do_gettimeofday(&after); | ||
154 | |||
155 | local_irq_enable(); | ||
156 | idle_time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC + | ||
157 | (after.tv_usec - before.tv_usec); | ||
158 | |||
159 | dev->last_residency = idle_time; | ||
160 | return index; | ||
161 | } | ||
162 | |||
42 | static int exynos4_enter_idle(struct cpuidle_device *dev, | 163 | static int exynos4_enter_idle(struct cpuidle_device *dev, |
43 | struct cpuidle_driver *drv, | 164 | struct cpuidle_driver *drv, |
44 | int index) | 165 | int index) |
45 | { | 166 | { |
46 | struct timeval before, after; | 167 | struct timeval before, after; |
47 | int idle_time; | 168 | int idle_time; |
@@ -60,6 +181,22 @@ static int exynos4_enter_idle(struct cpuidle_device *dev, | |||
60 | return index; | 181 | return index; |
61 | } | 182 | } |
62 | 183 | ||
184 | static int exynos4_enter_lowpower(struct cpuidle_device *dev, | ||
185 | struct cpuidle_driver *drv, | ||
186 | int index) | ||
187 | { | ||
188 | int new_index = index; | ||
189 | |||
190 | /* This mode only can be entered when other core's are offline */ | ||
191 | if (num_online_cpus() > 1) | ||
192 | new_index = drv->safe_state_index; | ||
193 | |||
194 | if (new_index == 0) | ||
195 | return exynos4_enter_idle(dev, drv, new_index); | ||
196 | else | ||
197 | return exynos4_enter_core0_aftr(dev, drv, new_index); | ||
198 | } | ||
199 | |||
63 | static int __init exynos4_init_cpuidle(void) | 200 | static int __init exynos4_init_cpuidle(void) |
64 | { | 201 | { |
65 | int i, max_cpuidle_state, cpu_id; | 202 | int i, max_cpuidle_state, cpu_id; |
@@ -74,19 +211,25 @@ static int __init exynos4_init_cpuidle(void) | |||
74 | memcpy(&drv->states[i], &exynos4_cpuidle_set[i], | 211 | memcpy(&drv->states[i], &exynos4_cpuidle_set[i], |
75 | sizeof(struct cpuidle_state)); | 212 | sizeof(struct cpuidle_state)); |
76 | } | 213 | } |
214 | drv->safe_state_index = 0; | ||
77 | cpuidle_register_driver(&exynos4_idle_driver); | 215 | cpuidle_register_driver(&exynos4_idle_driver); |
78 | 216 | ||
79 | for_each_cpu(cpu_id, cpu_online_mask) { | 217 | for_each_cpu(cpu_id, cpu_online_mask) { |
80 | device = &per_cpu(exynos4_cpuidle_device, cpu_id); | 218 | device = &per_cpu(exynos4_cpuidle_device, cpu_id); |
81 | device->cpu = cpu_id; | 219 | device->cpu = cpu_id; |
82 | 220 | ||
83 | device->state_count = drv->state_count; | 221 | if (cpu_id == 0) |
222 | device->state_count = (sizeof(exynos4_cpuidle_set) / | ||
223 | sizeof(struct cpuidle_state)); | ||
224 | else | ||
225 | device->state_count = 1; /* Support IDLE only */ | ||
84 | 226 | ||
85 | if (cpuidle_register_device(device)) { | 227 | if (cpuidle_register_device(device)) { |
86 | printk(KERN_ERR "CPUidle register device failed\n,"); | 228 | printk(KERN_ERR "CPUidle register device failed\n,"); |
87 | return -EIO; | 229 | return -EIO; |
88 | } | 230 | } |
89 | } | 231 | } |
232 | |||
90 | return 0; | 233 | return 0; |
91 | } | 234 | } |
92 | device_initcall(exynos4_init_cpuidle); | 235 | device_initcall(exynos4_init_cpuidle); |