diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /arch/arm/mach-exynos4/platsmp.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'arch/arm/mach-exynos4/platsmp.c')
-rw-r--r-- | arch/arm/mach-exynos4/platsmp.c | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos4/platsmp.c b/arch/arm/mach-exynos4/platsmp.c new file mode 100644 index 00000000000..0c90896ad9a --- /dev/null +++ b/arch/arm/mach-exynos4/platsmp.c | |||
@@ -0,0 +1,220 @@ | |||
1 | /* linux/arch/arm/mach-exynos4/platsmp.c | ||
2 | * | ||
3 | * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. | ||
4 | * http://www.samsung.com | ||
5 | * | ||
6 | * Cloned from linux/arch/arm/mach-vexpress/platsmp.c | ||
7 | * | ||
8 | * Copyright (C) 2002 ARM Ltd. | ||
9 | * All Rights Reserved | ||
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/errno.h> | ||
18 | #include <linux/delay.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/jiffies.h> | ||
21 | #include <linux/smp.h> | ||
22 | #include <linux/io.h> | ||
23 | |||
24 | #include <asm/cacheflush.h> | ||
25 | #include <asm/hardware/gic.h> | ||
26 | #include <asm/smp_scu.h> | ||
27 | #include <asm/unified.h> | ||
28 | |||
29 | #include <mach/hardware.h> | ||
30 | #include <mach/regs-clock.h> | ||
31 | #include <mach/regs-pmu.h> | ||
32 | |||
33 | extern void exynos4_secondary_startup(void); | ||
34 | |||
35 | #define CPU1_BOOT_REG S5P_VA_SYSRAM | ||
36 | |||
37 | /* | ||
38 | * control for which core is the next to come out of the secondary | ||
39 | * boot "holding pen" | ||
40 | */ | ||
41 | |||
42 | volatile int __cpuinitdata pen_release = -1; | ||
43 | |||
44 | /* | ||
45 | * Write pen_release in a way that is guaranteed to be visible to all | ||
46 | * observers, irrespective of whether they're taking part in coherency | ||
47 | * or not. This is necessary for the hotplug code to work reliably. | ||
48 | */ | ||
49 | static void write_pen_release(int val) | ||
50 | { | ||
51 | pen_release = val; | ||
52 | smp_wmb(); | ||
53 | __cpuc_flush_dcache_area((void *)&pen_release, sizeof(pen_release)); | ||
54 | outer_clean_range(__pa(&pen_release), __pa(&pen_release + 1)); | ||
55 | } | ||
56 | |||
57 | static void __iomem *scu_base_addr(void) | ||
58 | { | ||
59 | return (void __iomem *)(S5P_VA_SCU); | ||
60 | } | ||
61 | |||
62 | static DEFINE_SPINLOCK(boot_lock); | ||
63 | |||
64 | static void __cpuinit exynos4_gic_secondary_init(void) | ||
65 | { | ||
66 | void __iomem *dist_base = S5P_VA_GIC_DIST + | ||
67 | (EXYNOS4_GIC_BANK_OFFSET * smp_processor_id()); | ||
68 | void __iomem *cpu_base = S5P_VA_GIC_CPU + | ||
69 | (EXYNOS4_GIC_BANK_OFFSET * smp_processor_id()); | ||
70 | int i; | ||
71 | |||
72 | /* | ||
73 | * Deal with the banked PPI and SGI interrupts - disable all | ||
74 | * PPI interrupts, ensure all SGI interrupts are enabled. | ||
75 | */ | ||
76 | __raw_writel(0xffff0000, dist_base + GIC_DIST_ENABLE_CLEAR); | ||
77 | __raw_writel(0x0000ffff, dist_base + GIC_DIST_ENABLE_SET); | ||
78 | |||
79 | /* | ||
80 | * Set priority on PPI and SGI interrupts | ||
81 | */ | ||
82 | for (i = 0; i < 32; i += 4) | ||
83 | __raw_writel(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4 / 4); | ||
84 | |||
85 | __raw_writel(0xf0, cpu_base + GIC_CPU_PRIMASK); | ||
86 | __raw_writel(1, cpu_base + GIC_CPU_CTRL); | ||
87 | } | ||
88 | |||
89 | void __cpuinit platform_secondary_init(unsigned int cpu) | ||
90 | { | ||
91 | /* | ||
92 | * if any interrupts are already enabled for the primary | ||
93 | * core (e.g. timer irq), then they will not have been enabled | ||
94 | * for us: do so | ||
95 | */ | ||
96 | exynos4_gic_secondary_init(); | ||
97 | |||
98 | /* | ||
99 | * let the primary processor know we're out of the | ||
100 | * pen, then head off into the C entry point | ||
101 | */ | ||
102 | write_pen_release(-1); | ||
103 | |||
104 | /* | ||
105 | * Synchronise with the boot thread. | ||
106 | */ | ||
107 | spin_lock(&boot_lock); | ||
108 | spin_unlock(&boot_lock); | ||
109 | |||
110 | set_cpu_online(cpu, true); | ||
111 | } | ||
112 | |||
113 | int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle) | ||
114 | { | ||
115 | unsigned long timeout; | ||
116 | |||
117 | /* | ||
118 | * Set synchronisation state between this boot processor | ||
119 | * and the secondary one | ||
120 | */ | ||
121 | spin_lock(&boot_lock); | ||
122 | |||
123 | /* | ||
124 | * The secondary processor is waiting to be released from | ||
125 | * the holding pen - release it, then wait for it to flag | ||
126 | * that it has been released by resetting pen_release. | ||
127 | * | ||
128 | * Note that "pen_release" is the hardware CPU ID, whereas | ||
129 | * "cpu" is Linux's internal ID. | ||
130 | */ | ||
131 | write_pen_release(cpu); | ||
132 | |||
133 | if (!(__raw_readl(S5P_ARM_CORE1_STATUS) & S5P_CORE_LOCAL_PWR_EN)) { | ||
134 | __raw_writel(S5P_CORE_LOCAL_PWR_EN, | ||
135 | S5P_ARM_CORE1_CONFIGURATION); | ||
136 | |||
137 | timeout = 10; | ||
138 | |||
139 | /* wait max 10 ms until cpu1 is on */ | ||
140 | while ((__raw_readl(S5P_ARM_CORE1_STATUS) | ||
141 | & S5P_CORE_LOCAL_PWR_EN) != S5P_CORE_LOCAL_PWR_EN) { | ||
142 | if (timeout-- == 0) | ||
143 | break; | ||
144 | |||
145 | mdelay(1); | ||
146 | } | ||
147 | |||
148 | if (timeout == 0) { | ||
149 | printk(KERN_ERR "cpu1 power enable failed"); | ||
150 | spin_unlock(&boot_lock); | ||
151 | return -ETIMEDOUT; | ||
152 | } | ||
153 | } | ||
154 | /* | ||
155 | * Send the secondary CPU a soft interrupt, thereby causing | ||
156 | * the boot monitor to read the system wide flags register, | ||
157 | * and branch to the address found there. | ||
158 | */ | ||
159 | |||
160 | timeout = jiffies + (1 * HZ); | ||
161 | while (time_before(jiffies, timeout)) { | ||
162 | smp_rmb(); | ||
163 | |||
164 | __raw_writel(BSYM(virt_to_phys(exynos4_secondary_startup)), | ||
165 | CPU1_BOOT_REG); | ||
166 | gic_raise_softirq(cpumask_of(cpu), 1); | ||
167 | |||
168 | if (pen_release == -1) | ||
169 | break; | ||
170 | |||
171 | udelay(10); | ||
172 | } | ||
173 | |||
174 | /* | ||
175 | * now the secondary core is starting up let it run its | ||
176 | * calibrations, then wait for it to finish | ||
177 | */ | ||
178 | spin_unlock(&boot_lock); | ||
179 | |||
180 | return pen_release != -1 ? -ENOSYS : 0; | ||
181 | } | ||
182 | |||
183 | /* | ||
184 | * Initialise the CPU possible map early - this describes the CPUs | ||
185 | * which may be present or become present in the system. | ||
186 | */ | ||
187 | |||
188 | void __init smp_init_cpus(void) | ||
189 | { | ||
190 | void __iomem *scu_base = scu_base_addr(); | ||
191 | unsigned int i, ncores; | ||
192 | |||
193 | ncores = scu_base ? scu_get_core_count(scu_base) : 1; | ||
194 | |||
195 | /* sanity check */ | ||
196 | if (ncores > nr_cpu_ids) { | ||
197 | pr_warn("SMP: %u cores greater than maximum (%u), clipping\n", | ||
198 | ncores, nr_cpu_ids); | ||
199 | ncores = nr_cpu_ids; | ||
200 | } | ||
201 | |||
202 | for (i = 0; i < ncores; i++) | ||
203 | set_cpu_possible(i, true); | ||
204 | |||
205 | set_smp_cross_call(gic_raise_softirq); | ||
206 | } | ||
207 | |||
208 | void __init platform_smp_prepare_cpus(unsigned int max_cpus) | ||
209 | { | ||
210 | |||
211 | scu_enable(scu_base_addr()); | ||
212 | |||
213 | /* | ||
214 | * Write the address of secondary startup into the | ||
215 | * system-wide flags register. The boot monitor waits | ||
216 | * until it receives a soft interrupt, and then the | ||
217 | * secondary CPU branches to this address. | ||
218 | */ | ||
219 | __raw_writel(BSYM(virt_to_phys(exynos4_secondary_startup)), S5P_VA_SYSRAM); | ||
220 | } | ||