Commit | Line | Data |
---|---|---|
a112de8c MD |
1 | /* |
2 | * SMP support for SoCs with APMU | |
3 | * | |
4 | * Copyright (C) 2013 Magnus Damm | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
d6d757c9 | 10 | #include <linux/cpu_pm.h> |
a112de8c MD |
11 | #include <linux/delay.h> |
12 | #include <linux/init.h> | |
13 | #include <linux/io.h> | |
14 | #include <linux/ioport.h> | |
15 | #include <linux/of_address.h> | |
16 | #include <linux/smp.h> | |
d6d757c9 | 17 | #include <linux/suspend.h> |
784500be | 18 | #include <linux/threads.h> |
a112de8c MD |
19 | #include <asm/cacheflush.h> |
20 | #include <asm/cp15.h> | |
d6d757c9 | 21 | #include <asm/proc-fns.h> |
a112de8c | 22 | #include <asm/smp_plat.h> |
d6d757c9 | 23 | #include <asm/suspend.h> |
fd44aa5e | 24 | #include "common.h" |
a112de8c MD |
25 | |
26 | static struct { | |
27 | void __iomem *iomem; | |
28 | int bit; | |
784500be | 29 | } apmu_cpus[NR_CPUS]; |
a112de8c MD |
30 | |
31 | #define WUPCR_OFFS 0x10 | |
32 | #define PSTR_OFFS 0x40 | |
33 | #define CPUNCR_OFFS(n) (0x100 + (0x10 * (n))) | |
34 | ||
784500be | 35 | static int __maybe_unused apmu_power_on(void __iomem *p, int bit) |
a112de8c MD |
36 | { |
37 | /* request power on */ | |
38 | writel_relaxed(BIT(bit), p + WUPCR_OFFS); | |
39 | ||
40 | /* wait for APMU to finish */ | |
41 | while (readl_relaxed(p + WUPCR_OFFS) != 0) | |
42 | ; | |
43 | ||
44 | return 0; | |
45 | } | |
46 | ||
47 | static int apmu_power_off(void __iomem *p, int bit) | |
48 | { | |
49 | /* request Core Standby for next WFI */ | |
50 | writel_relaxed(3, p + CPUNCR_OFFS(bit)); | |
51 | return 0; | |
52 | } | |
53 | ||
784500be | 54 | static int __maybe_unused apmu_power_off_poll(void __iomem *p, int bit) |
a112de8c MD |
55 | { |
56 | int k; | |
57 | ||
58 | for (k = 0; k < 1000; k++) { | |
59 | if (((readl_relaxed(p + PSTR_OFFS) >> (bit * 4)) & 0x03) == 3) | |
60 | return 1; | |
61 | ||
62 | mdelay(1); | |
63 | } | |
64 | ||
65 | return 0; | |
66 | } | |
67 | ||
68 | static int apmu_wrap(int cpu, int (*fn)(void __iomem *p, int cpu)) | |
69 | { | |
70 | void __iomem *p = apmu_cpus[cpu].iomem; | |
71 | ||
72 | return p ? fn(p, apmu_cpus[cpu].bit) : -EINVAL; | |
73 | } | |
74 | ||
75 | static void apmu_init_cpu(struct resource *res, int cpu, int bit) | |
76 | { | |
784500be | 77 | if ((cpu >= ARRAY_SIZE(apmu_cpus)) || apmu_cpus[cpu].iomem) |
a112de8c MD |
78 | return; |
79 | ||
80 | apmu_cpus[cpu].iomem = ioremap_nocache(res->start, resource_size(res)); | |
81 | apmu_cpus[cpu].bit = bit; | |
82 | ||
56ff8731 | 83 | pr_debug("apmu ioremap %d %d %pr\n", cpu, bit, res); |
a112de8c MD |
84 | } |
85 | ||
86 | static struct { | |
87 | struct resource iomem; | |
88 | int cpus[4]; | |
89 | } apmu_config[] = { | |
90 | { | |
91 | .iomem = DEFINE_RES_MEM(0xe6152000, 0x88), | |
92 | .cpus = { 0, 1, 2, 3 }, | |
43651b15 MD |
93 | }, |
94 | { | |
95 | .iomem = DEFINE_RES_MEM(0xe6151000, 0x88), | |
96 | .cpus = { 0x100, 0x101, 0x102, 0x103 }, | |
a112de8c MD |
97 | } |
98 | }; | |
99 | ||
100 | static void apmu_parse_cfg(void (*fn)(struct resource *res, int cpu, int bit)) | |
101 | { | |
102 | u32 id; | |
103 | int k; | |
104 | int bit, index; | |
ee490bcc | 105 | bool is_allowed; |
a112de8c MD |
106 | |
107 | for (k = 0; k < ARRAY_SIZE(apmu_config); k++) { | |
ee490bcc MD |
108 | /* only enable the cluster that includes the boot CPU */ |
109 | is_allowed = false; | |
110 | for (bit = 0; bit < ARRAY_SIZE(apmu_config[k].cpus); bit++) { | |
111 | id = apmu_config[k].cpus[bit]; | |
112 | if (id >= 0) { | |
113 | if (id == cpu_logical_map(0)) | |
114 | is_allowed = true; | |
115 | } | |
116 | } | |
117 | if (!is_allowed) | |
118 | continue; | |
119 | ||
a112de8c MD |
120 | for (bit = 0; bit < ARRAY_SIZE(apmu_config[k].cpus); bit++) { |
121 | id = apmu_config[k].cpus[bit]; | |
122 | if (id >= 0) { | |
123 | index = get_logical_index(id); | |
124 | if (index >= 0) | |
125 | fn(&apmu_config[k].iomem, index, bit); | |
126 | } | |
127 | } | |
128 | } | |
129 | } | |
130 | ||
131 | void __init shmobile_smp_apmu_prepare_cpus(unsigned int max_cpus) | |
132 | { | |
133 | /* install boot code shared by all CPUs */ | |
134 | shmobile_boot_fn = virt_to_phys(shmobile_smp_boot); | |
135 | shmobile_boot_arg = MPIDR_HWID_BITMASK; | |
136 | ||
137 | /* perform per-cpu setup */ | |
138 | apmu_parse_cfg(apmu_init_cpu); | |
139 | } | |
140 | ||
784500be | 141 | #ifdef CONFIG_SMP |
a112de8c MD |
142 | int shmobile_smp_apmu_boot_secondary(unsigned int cpu, struct task_struct *idle) |
143 | { | |
144 | /* For this particular CPU register boot vector */ | |
145 | shmobile_smp_hook(cpu, virt_to_phys(shmobile_invalidate_start), 0); | |
146 | ||
147 | return apmu_wrap(cpu, apmu_power_on); | |
148 | } | |
784500be | 149 | #endif |
a112de8c | 150 | |
d6d757c9 | 151 | #if defined(CONFIG_HOTPLUG_CPU) || defined(CONFIG_SUSPEND) |
a112de8c MD |
152 | /* nicked from arch/arm/mach-exynos/hotplug.c */ |
153 | static inline void cpu_enter_lowpower_a15(void) | |
154 | { | |
155 | unsigned int v; | |
156 | ||
157 | asm volatile( | |
158 | " mrc p15, 0, %0, c1, c0, 0\n" | |
159 | " bic %0, %0, %1\n" | |
160 | " mcr p15, 0, %0, c1, c0, 0\n" | |
161 | : "=&r" (v) | |
162 | : "Ir" (CR_C) | |
163 | : "cc"); | |
164 | ||
165 | flush_cache_louis(); | |
166 | ||
167 | asm volatile( | |
168 | /* | |
169 | * Turn off coherency | |
170 | */ | |
171 | " mrc p15, 0, %0, c1, c0, 1\n" | |
172 | " bic %0, %0, %1\n" | |
173 | " mcr p15, 0, %0, c1, c0, 1\n" | |
174 | : "=&r" (v) | |
175 | : "Ir" (0x40) | |
176 | : "cc"); | |
177 | ||
178 | isb(); | |
179 | dsb(); | |
180 | } | |
181 | ||
d6d757c9 | 182 | void shmobile_smp_apmu_cpu_shutdown(unsigned int cpu) |
a112de8c | 183 | { |
a112de8c MD |
184 | |
185 | /* Select next sleep mode using the APMU */ | |
186 | apmu_wrap(cpu, apmu_power_off); | |
187 | ||
188 | /* Do ARM specific CPU shutdown */ | |
189 | cpu_enter_lowpower_a15(); | |
d6d757c9 | 190 | } |
191 | ||
192 | static inline void cpu_leave_lowpower(void) | |
193 | { | |
194 | unsigned int v; | |
195 | ||
196 | asm volatile("mrc p15, 0, %0, c1, c0, 0\n" | |
197 | " orr %0, %0, %1\n" | |
198 | " mcr p15, 0, %0, c1, c0, 0\n" | |
199 | " mrc p15, 0, %0, c1, c0, 1\n" | |
200 | " orr %0, %0, %2\n" | |
201 | " mcr p15, 0, %0, c1, c0, 1\n" | |
202 | : "=&r" (v) | |
203 | : "Ir" (CR_C), "Ir" (0x40) | |
204 | : "cc"); | |
205 | } | |
206 | #endif | |
207 | ||
208 | #if defined(CONFIG_HOTPLUG_CPU) | |
209 | void shmobile_smp_apmu_cpu_die(unsigned int cpu) | |
210 | { | |
211 | /* For this particular CPU deregister boot vector */ | |
212 | shmobile_smp_hook(cpu, 0, 0); | |
213 | ||
214 | /* Shutdown CPU core */ | |
215 | shmobile_smp_apmu_cpu_shutdown(cpu); | |
a112de8c MD |
216 | |
217 | /* jump to shared mach-shmobile sleep / reset code */ | |
218 | shmobile_smp_sleep(); | |
219 | } | |
220 | ||
221 | int shmobile_smp_apmu_cpu_kill(unsigned int cpu) | |
222 | { | |
223 | return apmu_wrap(cpu, apmu_power_off_poll); | |
224 | } | |
225 | #endif | |
d6d757c9 | 226 | |
227 | #if defined(CONFIG_SUSPEND) | |
228 | static int shmobile_smp_apmu_do_suspend(unsigned long cpu) | |
229 | { | |
230 | shmobile_smp_hook(cpu, virt_to_phys(cpu_resume), 0); | |
231 | shmobile_smp_apmu_cpu_shutdown(cpu); | |
232 | cpu_do_idle(); /* WFI selects Core Standby */ | |
233 | return 1; | |
234 | } | |
235 | ||
236 | static int shmobile_smp_apmu_enter_suspend(suspend_state_t state) | |
237 | { | |
238 | cpu_suspend(smp_processor_id(), shmobile_smp_apmu_do_suspend); | |
239 | cpu_leave_lowpower(); | |
240 | return 0; | |
241 | } | |
242 | ||
0d77c9aa | 243 | void __init shmobile_smp_apmu_suspend_init(void) |
d6d757c9 | 244 | { |
245 | shmobile_suspend_ops.enter = shmobile_smp_apmu_enter_suspend; | |
246 | } | |
d6d757c9 | 247 | #endif |