]>
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 | */ | |
10 | #include <linux/delay.h> | |
11 | #include <linux/init.h> | |
12 | #include <linux/io.h> | |
13 | #include <linux/ioport.h> | |
14 | #include <linux/of_address.h> | |
15 | #include <linux/smp.h> | |
16 | #include <asm/cacheflush.h> | |
17 | #include <asm/cp15.h> | |
18 | #include <asm/smp_plat.h> | |
fd44aa5e | 19 | #include "common.h" |
a112de8c MD |
20 | |
21 | static struct { | |
22 | void __iomem *iomem; | |
23 | int bit; | |
24 | } apmu_cpus[CONFIG_NR_CPUS]; | |
25 | ||
26 | #define WUPCR_OFFS 0x10 | |
27 | #define PSTR_OFFS 0x40 | |
28 | #define CPUNCR_OFFS(n) (0x100 + (0x10 * (n))) | |
29 | ||
30 | static int apmu_power_on(void __iomem *p, int bit) | |
31 | { | |
32 | /* request power on */ | |
33 | writel_relaxed(BIT(bit), p + WUPCR_OFFS); | |
34 | ||
35 | /* wait for APMU to finish */ | |
36 | while (readl_relaxed(p + WUPCR_OFFS) != 0) | |
37 | ; | |
38 | ||
39 | return 0; | |
40 | } | |
41 | ||
42 | static int apmu_power_off(void __iomem *p, int bit) | |
43 | { | |
44 | /* request Core Standby for next WFI */ | |
45 | writel_relaxed(3, p + CPUNCR_OFFS(bit)); | |
46 | return 0; | |
47 | } | |
48 | ||
49 | static int apmu_power_off_poll(void __iomem *p, int bit) | |
50 | { | |
51 | int k; | |
52 | ||
53 | for (k = 0; k < 1000; k++) { | |
54 | if (((readl_relaxed(p + PSTR_OFFS) >> (bit * 4)) & 0x03) == 3) | |
55 | return 1; | |
56 | ||
57 | mdelay(1); | |
58 | } | |
59 | ||
60 | return 0; | |
61 | } | |
62 | ||
63 | static int apmu_wrap(int cpu, int (*fn)(void __iomem *p, int cpu)) | |
64 | { | |
65 | void __iomem *p = apmu_cpus[cpu].iomem; | |
66 | ||
67 | return p ? fn(p, apmu_cpus[cpu].bit) : -EINVAL; | |
68 | } | |
69 | ||
70 | static void apmu_init_cpu(struct resource *res, int cpu, int bit) | |
71 | { | |
72 | if (apmu_cpus[cpu].iomem) | |
73 | return; | |
74 | ||
75 | apmu_cpus[cpu].iomem = ioremap_nocache(res->start, resource_size(res)); | |
76 | apmu_cpus[cpu].bit = bit; | |
77 | ||
56ff8731 | 78 | pr_debug("apmu ioremap %d %d %pr\n", cpu, bit, res); |
a112de8c MD |
79 | } |
80 | ||
81 | static struct { | |
82 | struct resource iomem; | |
83 | int cpus[4]; | |
84 | } apmu_config[] = { | |
85 | { | |
86 | .iomem = DEFINE_RES_MEM(0xe6152000, 0x88), | |
87 | .cpus = { 0, 1, 2, 3 }, | |
43651b15 MD |
88 | }, |
89 | { | |
90 | .iomem = DEFINE_RES_MEM(0xe6151000, 0x88), | |
91 | .cpus = { 0x100, 0x101, 0x102, 0x103 }, | |
a112de8c MD |
92 | } |
93 | }; | |
94 | ||
95 | static void apmu_parse_cfg(void (*fn)(struct resource *res, int cpu, int bit)) | |
96 | { | |
97 | u32 id; | |
98 | int k; | |
99 | int bit, index; | |
ee490bcc | 100 | bool is_allowed; |
a112de8c MD |
101 | |
102 | for (k = 0; k < ARRAY_SIZE(apmu_config); k++) { | |
ee490bcc MD |
103 | /* only enable the cluster that includes the boot CPU */ |
104 | is_allowed = false; | |
105 | for (bit = 0; bit < ARRAY_SIZE(apmu_config[k].cpus); bit++) { | |
106 | id = apmu_config[k].cpus[bit]; | |
107 | if (id >= 0) { | |
108 | if (id == cpu_logical_map(0)) | |
109 | is_allowed = true; | |
110 | } | |
111 | } | |
112 | if (!is_allowed) | |
113 | continue; | |
114 | ||
a112de8c MD |
115 | for (bit = 0; bit < ARRAY_SIZE(apmu_config[k].cpus); bit++) { |
116 | id = apmu_config[k].cpus[bit]; | |
117 | if (id >= 0) { | |
118 | index = get_logical_index(id); | |
119 | if (index >= 0) | |
120 | fn(&apmu_config[k].iomem, index, bit); | |
121 | } | |
122 | } | |
123 | } | |
124 | } | |
125 | ||
126 | void __init shmobile_smp_apmu_prepare_cpus(unsigned int max_cpus) | |
127 | { | |
128 | /* install boot code shared by all CPUs */ | |
129 | shmobile_boot_fn = virt_to_phys(shmobile_smp_boot); | |
130 | shmobile_boot_arg = MPIDR_HWID_BITMASK; | |
131 | ||
132 | /* perform per-cpu setup */ | |
133 | apmu_parse_cfg(apmu_init_cpu); | |
134 | } | |
135 | ||
136 | int shmobile_smp_apmu_boot_secondary(unsigned int cpu, struct task_struct *idle) | |
137 | { | |
138 | /* For this particular CPU register boot vector */ | |
139 | shmobile_smp_hook(cpu, virt_to_phys(shmobile_invalidate_start), 0); | |
140 | ||
141 | return apmu_wrap(cpu, apmu_power_on); | |
142 | } | |
143 | ||
144 | #ifdef CONFIG_HOTPLUG_CPU | |
145 | /* nicked from arch/arm/mach-exynos/hotplug.c */ | |
146 | static inline void cpu_enter_lowpower_a15(void) | |
147 | { | |
148 | unsigned int v; | |
149 | ||
150 | asm volatile( | |
151 | " mrc p15, 0, %0, c1, c0, 0\n" | |
152 | " bic %0, %0, %1\n" | |
153 | " mcr p15, 0, %0, c1, c0, 0\n" | |
154 | : "=&r" (v) | |
155 | : "Ir" (CR_C) | |
156 | : "cc"); | |
157 | ||
158 | flush_cache_louis(); | |
159 | ||
160 | asm volatile( | |
161 | /* | |
162 | * Turn off coherency | |
163 | */ | |
164 | " mrc p15, 0, %0, c1, c0, 1\n" | |
165 | " bic %0, %0, %1\n" | |
166 | " mcr p15, 0, %0, c1, c0, 1\n" | |
167 | : "=&r" (v) | |
168 | : "Ir" (0x40) | |
169 | : "cc"); | |
170 | ||
171 | isb(); | |
172 | dsb(); | |
173 | } | |
174 | ||
175 | void shmobile_smp_apmu_cpu_die(unsigned int cpu) | |
176 | { | |
177 | /* For this particular CPU deregister boot vector */ | |
178 | shmobile_smp_hook(cpu, 0, 0); | |
179 | ||
180 | /* Select next sleep mode using the APMU */ | |
181 | apmu_wrap(cpu, apmu_power_off); | |
182 | ||
183 | /* Do ARM specific CPU shutdown */ | |
184 | cpu_enter_lowpower_a15(); | |
185 | ||
186 | /* jump to shared mach-shmobile sleep / reset code */ | |
187 | shmobile_smp_sleep(); | |
188 | } | |
189 | ||
190 | int shmobile_smp_apmu_cpu_kill(unsigned int cpu) | |
191 | { | |
192 | return apmu_wrap(cpu, apmu_power_off_poll); | |
193 | } | |
194 | #endif |