Commit | Line | Data |
---|---|---|
ba56a987 MY |
1 | /* |
2 | * Copyright (C) 2015 Masahiro Yamada <yamada.masahiro@socionext.com> | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | */ | |
14 | ||
b1e4006a MY |
15 | #define pr_fmt(fmt) "uniphier: " fmt |
16 | ||
ba56a987 MY |
17 | #include <linux/init.h> |
18 | #include <linux/io.h> | |
b1e4006a MY |
19 | #include <linux/ioport.h> |
20 | #include <linux/of.h> | |
21 | #include <linux/of_address.h> | |
22 | #include <linux/sizes.h> | |
23 | #include <asm/cacheflush.h> | |
24 | #include <asm/hardware/cache-uniphier.h> | |
25 | #include <asm/pgtable.h> | |
ba56a987 MY |
26 | #include <asm/smp.h> |
27 | #include <asm/smp_scu.h> | |
28 | ||
b1e4006a MY |
29 | /* |
30 | * The secondary CPUs check this register from the boot ROM for the jump | |
31 | * destination. After that, it can be reused as a scratch register. | |
32 | */ | |
307d40c5 | 33 | #define UNIPHIER_SMPCTRL_ROM_RSV2 0x208 |
ba56a987 | 34 | |
b1e4006a MY |
35 | static void __iomem *uniphier_smp_rom_boot_rsv2; |
36 | static unsigned int uniphier_smp_max_cpus; | |
37 | ||
38 | extern char uniphier_smp_trampoline; | |
39 | extern char uniphier_smp_trampoline_jump; | |
40 | extern char uniphier_smp_trampoline_poll_addr; | |
41 | extern char uniphier_smp_trampoline_end; | |
42 | ||
43 | /* | |
44 | * Copy trampoline code to the tail of the 1st section of the page table used | |
45 | * in the boot ROM. This area is directly accessible by the secondary CPUs | |
46 | * for all the UniPhier SoCs. | |
47 | */ | |
48 | static const phys_addr_t uniphier_smp_trampoline_dest_end = SECTION_SIZE; | |
49 | static phys_addr_t uniphier_smp_trampoline_dest; | |
50 | ||
51 | static int __init uniphier_smp_copy_trampoline(phys_addr_t poll_addr) | |
ba56a987 | 52 | { |
b1e4006a MY |
53 | size_t trmp_size; |
54 | static void __iomem *trmp_base; | |
ba56a987 | 55 | |
b1e4006a MY |
56 | if (!uniphier_cache_l2_is_enabled()) { |
57 | pr_warn("outer cache is needed for SMP, but not enabled\n"); | |
58 | return -ENODEV; | |
ba56a987 MY |
59 | } |
60 | ||
b1e4006a MY |
61 | uniphier_cache_l2_set_locked_ways(1); |
62 | ||
63 | outer_flush_all(); | |
64 | ||
65 | trmp_size = &uniphier_smp_trampoline_end - &uniphier_smp_trampoline; | |
66 | uniphier_smp_trampoline_dest = uniphier_smp_trampoline_dest_end - | |
67 | trmp_size; | |
68 | ||
69 | uniphier_cache_l2_touch_range(uniphier_smp_trampoline_dest, | |
70 | uniphier_smp_trampoline_dest_end); | |
71 | ||
72 | trmp_base = ioremap_cache(uniphier_smp_trampoline_dest, trmp_size); | |
73 | if (!trmp_base) { | |
74 | pr_err("failed to map trampoline destination area\n"); | |
75 | return -ENOMEM; | |
76 | } | |
77 | ||
78 | memcpy(trmp_base, &uniphier_smp_trampoline, trmp_size); | |
79 | ||
80 | writel(virt_to_phys(secondary_startup), | |
81 | trmp_base + (&uniphier_smp_trampoline_jump - | |
82 | &uniphier_smp_trampoline)); | |
83 | ||
84 | writel(poll_addr, trmp_base + (&uniphier_smp_trampoline_poll_addr - | |
85 | &uniphier_smp_trampoline)); | |
86 | ||
87 | flush_cache_all(); /* flush out trampoline code to outer cache */ | |
88 | ||
89 | iounmap(trmp_base); | |
90 | ||
91 | return 0; | |
92 | } | |
93 | ||
94 | static int __init uniphier_smp_prepare_trampoline(unsigned int max_cpus) | |
95 | { | |
96 | struct device_node *np; | |
97 | struct resource res; | |
98 | phys_addr_t rom_rsv2_phys; | |
99 | int ret; | |
100 | ||
307d40c5 | 101 | np = of_find_compatible_node(NULL, NULL, "socionext,uniphier-smpctrl"); |
9eca796e | 102 | of_node_put(np); |
307d40c5 MY |
103 | ret = of_address_to_resource(np, 0, &res); |
104 | if (!ret) { | |
105 | rom_rsv2_phys = res.start + UNIPHIER_SMPCTRL_ROM_RSV2; | |
106 | } else { | |
107 | /* try old binding too */ | |
108 | np = of_find_compatible_node(NULL, NULL, | |
109 | "socionext,uniphier-system-bus-controller"); | |
110 | of_node_put(np); | |
111 | ret = of_address_to_resource(np, 1, &res); | |
112 | if (ret) { | |
113 | pr_err("failed to get resource of SMP control\n"); | |
114 | return ret; | |
115 | } | |
116 | rom_rsv2_phys = res.start + 0x1000 + UNIPHIER_SMPCTRL_ROM_RSV2; | |
b1e4006a MY |
117 | } |
118 | ||
b1e4006a MY |
119 | ret = uniphier_smp_copy_trampoline(rom_rsv2_phys); |
120 | if (ret) | |
121 | return ret; | |
122 | ||
3137b716 | 123 | uniphier_smp_rom_boot_rsv2 = ioremap(rom_rsv2_phys, SZ_4); |
b1e4006a MY |
124 | if (!uniphier_smp_rom_boot_rsv2) { |
125 | pr_err("failed to map ROM_BOOT_RSV2 register\n"); | |
126 | return -ENOMEM; | |
127 | } | |
128 | ||
129 | writel(uniphier_smp_trampoline_dest, uniphier_smp_rom_boot_rsv2); | |
130 | asm("sev"); /* Bring up all secondary CPUs to the trampoline code */ | |
131 | ||
132 | uniphier_smp_max_cpus = max_cpus; /* save for later use */ | |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
137 | static void __init uniphier_smp_unprepare_trampoline(void) | |
138 | { | |
139 | iounmap(uniphier_smp_rom_boot_rsv2); | |
140 | ||
141 | if (uniphier_smp_trampoline_dest) | |
142 | outer_inv_range(uniphier_smp_trampoline_dest, | |
143 | uniphier_smp_trampoline_dest_end); | |
144 | ||
145 | uniphier_cache_l2_set_locked_ways(0); | |
146 | } | |
147 | ||
148 | static int __init uniphier_smp_enable_scu(void) | |
149 | { | |
150 | unsigned long scu_base_phys = 0; | |
151 | void __iomem *scu_base; | |
152 | ||
ba56a987 MY |
153 | if (scu_a9_has_base()) |
154 | scu_base_phys = scu_a9_get_base(); | |
155 | ||
156 | if (!scu_base_phys) { | |
157 | pr_err("failed to get scu base\n"); | |
b1e4006a | 158 | return -ENODEV; |
ba56a987 MY |
159 | } |
160 | ||
161 | scu_base = ioremap(scu_base_phys, SZ_128); | |
162 | if (!scu_base) { | |
b1e4006a MY |
163 | pr_err("failed to map scu base\n"); |
164 | return -ENOMEM; | |
ba56a987 MY |
165 | } |
166 | ||
167 | scu_enable(scu_base); | |
168 | iounmap(scu_base); | |
169 | ||
b1e4006a MY |
170 | return 0; |
171 | } | |
172 | ||
173 | static void __init uniphier_smp_prepare_cpus(unsigned int max_cpus) | |
174 | { | |
175 | static cpumask_t only_cpu_0 = { CPU_BITS_CPU0 }; | |
176 | int ret; | |
177 | ||
178 | ret = uniphier_smp_prepare_trampoline(max_cpus); | |
179 | if (ret) | |
180 | goto err; | |
181 | ||
182 | ret = uniphier_smp_enable_scu(); | |
183 | if (ret) | |
184 | goto err; | |
185 | ||
ba56a987 MY |
186 | return; |
187 | err: | |
188 | pr_warn("disabling SMP\n"); | |
189 | init_cpu_present(&only_cpu_0); | |
b1e4006a | 190 | uniphier_smp_unprepare_trampoline(); |
ba56a987 MY |
191 | } |
192 | ||
b1e4006a MY |
193 | static int __init uniphier_smp_boot_secondary(unsigned int cpu, |
194 | struct task_struct *idle) | |
ba56a987 | 195 | { |
b1e4006a MY |
196 | if (WARN_ON_ONCE(!uniphier_smp_rom_boot_rsv2)) |
197 | return -EFAULT; | |
ba56a987 | 198 | |
b1e4006a MY |
199 | writel(cpu, uniphier_smp_rom_boot_rsv2); |
200 | readl(uniphier_smp_rom_boot_rsv2); /* relax */ | |
ba56a987 | 201 | |
b1e4006a MY |
202 | asm("sev"); /* wake up secondary CPUs sleeping in the trampoline */ |
203 | ||
204 | if (cpu == uniphier_smp_max_cpus - 1) { | |
205 | /* clean up resources if this is the last CPU */ | |
206 | uniphier_smp_unprepare_trampoline(); | |
207 | } | |
ba56a987 | 208 | |
b1e4006a | 209 | return 0; |
ba56a987 MY |
210 | } |
211 | ||
75305275 | 212 | static const struct smp_operations uniphier_smp_ops __initconst = { |
ba56a987 | 213 | .smp_prepare_cpus = uniphier_smp_prepare_cpus, |
b1e4006a | 214 | .smp_boot_secondary = uniphier_smp_boot_secondary, |
ba56a987 MY |
215 | }; |
216 | CPU_METHOD_OF_DECLARE(uniphier_smp, "socionext,uniphier-smp", | |
217 | &uniphier_smp_ops); |