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