summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-bcm/platsmp.c
blob: 9e3f275934eb412f14947bf40ae2cec0f785bcc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/*
 * Copyright (C) 2014-2015 Broadcom Corporation
 * Copyright 2014 Linaro Limited
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation version 2.
 *
 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
 * kind, whether express or implied; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/sched.h>
#include <linux/sched/clock.h>
#include <linux/smp.h>

#include <asm/cacheflush.h>
#include <asm/smp.h>
#include <asm/smp_plat.h>
#include <asm/smp_scu.h>

/* Size of mapped Cortex A9 SCU address space */
#define CORTEX_A9_SCU_SIZE	0x58

#define SECONDARY_TIMEOUT_NS	NSEC_PER_MSEC	/* 1 msec (in nanoseconds) */
#define BOOT_ADDR_CPUID_MASK	0x3

/* Name of device node property defining secondary boot register location */
#define OF_SECONDARY_BOOT	"secondary-boot-reg"
#define MPIDR_CPUID_BITMASK	0x3

/*
 * Enable the Cortex A9 Snoop Control Unit
 *
 * By the time this is called we already know there are multiple
 * cores present.  We assume we're running on a Cortex A9 processor,
 * so any trouble getting the base address register or getting the
 * SCU base is a problem.
 *
 * Return 0 if successful or an error code otherwise.
 */
static int __init scu_a9_enable(void)
{
	unsigned long config_base;
	void __iomem *scu_base;

	if (!scu_a9_has_base()) {
		pr_err("no configuration base address register!\n");
		return -ENXIO;
	}

	/* Config base address register value is zero for uniprocessor */
	config_base = scu_a9_get_base();
	if (!config_base) {
		pr_err("hardware reports only one core\n");
		return -ENOENT;
	}

	scu_base = ioremap((phys_addr_t)config_base, CORTEX_A9_SCU_SIZE);
	if (!scu_base) {
		pr_err("failed to remap config base (%lu/%u) for SCU\n",
			config_base, CORTEX_A9_SCU_SIZE);
		return -ENOMEM;
	}

	scu_enable(scu_base);

	iounmap(scu_base);	/* That's the last we'll need of this */

	return 0;
}

static u32 secondary_boot_addr_for(unsigned int cpu)
{
	u32 secondary_boot_addr = 0;
	struct device_node *cpu_node = of_get_cpu_node(cpu, NULL);

        if (!cpu_node) {
		pr_err("Failed to find device tree node for CPU%u\n", cpu);
		return 0;
	}

	if (of_property_read_u32(cpu_node,
				 OF_SECONDARY_BOOT,
				 &secondary_boot_addr))
		pr_err("required secondary boot register not specified for CPU%u\n",
			cpu);

	of_node_put(cpu_node);

	return secondary_boot_addr;
}

static int nsp_write_lut(unsigned int cpu)
{
	void __iomem *sku_rom_lut;
	phys_addr_t secondary_startup_phy;
	const u32 secondary_boot_addr = secondary_boot_addr_for(cpu);

	if (!secondary_boot_addr)
		return -EINVAL;

	sku_rom_lut = ioremap_nocache((phys_addr_t)secondary_boot_addr,
				      sizeof(phys_addr_t));
	if (!sku_rom_lut) {
		pr_warn("unable to ioremap SKU-ROM LUT register for cpu %u\n", cpu);
		return -ENOMEM;
	}

	secondary_startup_phy = __pa_symbol(secondary_startup);
	BUG_ON(secondary_startup_phy > (phys_addr_t)U32_MAX);

	writel_relaxed(secondary_startup_phy, sku_rom_lut);

	/* Ensure the write is visible to the secondary core */
	smp_wmb();

	iounmap(sku_rom_lut);

	return 0;
}

static void __init bcm_smp_prepare_cpus(unsigned int max_cpus)
{
	const cpumask_t only_cpu_0 = { CPU_BITS_CPU0 };

	/* Enable the SCU on Cortex A9 based SoCs */
	if (scu_a9_enable()) {
		/* Update the CPU present map to reflect uniprocessor mode */
		pr_warn("failed to enable A9 SCU - disabling SMP\n");
		init_cpu_present(&only_cpu_0);
	}
}

/*
 * The ROM code has the secondary cores looping, waiting for an event.
 * When an event occurs each core examines the bottom two bits of the
 * secondary boot register.  When a core finds those bits contain its
 * own core id, it performs initialization, including computing its boot
 * address by clearing the boot register value's bottom two bits.  The
 * core signals that it is beginning its execution by writing its boot
 * address back to the secondary boot register, and finally jumps to
 * that address.
 *
 * So to start a core executing we need to:
 * - Encode the (hardware) CPU id with the bottom bits of the secondary
 *   start address.
 * - Write that value into the secondary boot register.
 * - Generate an event to wake up the secondary CPU(s).
 * - Wait for the secondary boot register to be re-written, which
 *   indicates the secondary core has started.
 */
static int kona_boot_secondary(unsigned int cpu, struct task_struct *idle)
{
	void __iomem *boot_reg;
	phys_addr_t boot_func;
	u64 start_clock;
	u32 cpu_id;
	u32 boot_val;
	bool timeout = false;
	const u32 secondary_boot_addr = secondary_boot_addr_for(cpu);

	cpu_id = cpu_logical_map(cpu);
	if (cpu_id & ~BOOT_ADDR_CPUID_MASK) {
		pr_err("bad cpu id (%u > %u)\n", cpu_id, BOOT_ADDR_CPUID_MASK);
		return -EINVAL;
	}

	if (!secondary_boot_addr)
		return -EINVAL;

	boot_reg = ioremap_nocache((phys_addr_t)secondary_boot_addr,
				   sizeof(phys_addr_t));
	if (!boot_reg) {
		pr_err("unable to map boot register for cpu %u\n", cpu_id);
		return -ENOMEM;
	}

	/*
	 * Secondary cores will start in secondary_startup(),
	 * defined in "arch/arm/kernel/head.S"
	 */
	boot_func = __pa_symbol(secondary_startup);
	BUG_ON(boot_func & BOOT_ADDR_CPUID_MASK);
	BUG_ON(boot_func > (phys_addr_t)U32_MAX);

	/* The core to start is encoded in the low bits */
	boot_val = (u32)boot_func | cpu_id;
	writel_relaxed(boot_val, boot_reg);

	sev();

	/* The low bits will be cleared once the core has started */
	start_clock = local_clock();
	while (!timeout && readl_relaxed(boot_reg) == boot_val)
		timeout = local_clock() - start_clock > SECONDARY_TIMEOUT_NS;

	iounmap(boot_reg);

	if (!timeout)
		return 0;

	pr_err("timeout waiting for cpu %u to start\n", cpu_id);

	return -ENXIO;
}

/* Cluster Dormant Control command to bring CPU into a running state */
#define CDC_CMD			6
#define CDC_CMD_OFFSET		0
#define CDC_CMD_REG(cpu)	(CDC_CMD_OFFSET + 4*(cpu))

/*
 * BCM23550 has a Cluster Dormant Control block that keeps the core in
 * idle state. A command needs to be sent to the block to bring the CPU
 * into running state.
 */
static int bcm23550_boot_secondary(unsigned int cpu, struct task_struct *idle)
{
	void __iomem *cdc_base;
	struct device_node *dn;
	char *name;
	int ret;

	/* Make sure a CDC node exists before booting the
	 * secondary core.
	 */
	name = "brcm,bcm23550-cdc";
	dn = of_find_compatible_node(NULL, NULL, name);
	if (!dn) {
		pr_err("unable to find cdc node\n");
		return -ENODEV;
	}

	cdc_base = of_iomap(dn, 0);
	of_node_put(dn);

	if (!cdc_base) {
		pr_err("unable to remap cdc base register\n");
		return -ENOMEM;
	}

	/* Boot the secondary core */
	ret = kona_boot_secondary(cpu, idle);
	if (ret)
		goto out;

	/* Bring this CPU to RUN state so that nIRQ nFIQ
	 * signals are unblocked.
	 */
	writel_relaxed(CDC_CMD, cdc_base + CDC_CMD_REG(cpu));

out:
	iounmap(cdc_base);

	return ret;
}

static int nsp_boot_secondary(unsigned int cpu, struct task_struct *idle)
{
	int ret;

	/*
	 * After wake up, secondary core branches to the startup
	 * address programmed at SKU ROM LUT location.
	 */
	ret = nsp_write_lut(cpu);
	if (ret) {
		pr_err("unable to write startup addr to SKU ROM LUT\n");
		goto out;
	}

	/* Send a CPU wakeup interrupt to the secondary core */
	arch_send_wakeup_ipi_mask(cpumask_of(cpu));

out:
	return ret;
}

static const struct smp_operations kona_smp_ops __initconst = {
	.smp_prepare_cpus	= bcm_smp_prepare_cpus,
	.smp_boot_secondary	= kona_boot_secondary,
};
CPU_METHOD_OF_DECLARE(bcm_smp_bcm281xx, "brcm,bcm11351-cpu-method",
			&kona_smp_ops);

static const struct smp_operations bcm23550_smp_ops __initconst = {
	.smp_boot_secondary	= bcm23550_boot_secondary,
};
CPU_METHOD_OF_DECLARE(bcm_smp_bcm23550, "brcm,bcm23550",
			&bcm23550_smp_ops);

static const struct smp_operations nsp_smp_ops __initconst = {
	.smp_prepare_cpus	= bcm_smp_prepare_cpus,
	.smp_boot_secondary	= nsp_boot_secondary,
};
CPU_METHOD_OF_DECLARE(bcm_smp_nsp, "brcm,bcm-nsp-smp", &nsp_smp_ops);