aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-bcm/bcm63xx_smp.c
blob: 641e1f8fcf5e7c9b92bc205f5a2414a7f67c1a2a (plain) (blame)
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
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Broadcom BCM63138 DSL SoCs SMP support code
 *
 * Copyright (C) 2015, Broadcom Corporation
 */

#include <linux/delay.h>
#include <linux/init.h>
#include <linux/smp.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>

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

#include "bcm63xx_smp.h"

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

/*
 * 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;
	unsigned int i, ncores;

	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);

	ncores = scu_base ? scu_get_core_count(scu_base) : 1;

	if (ncores > nr_cpu_ids) {
		pr_warn("SMP: %u cores greater than maximum (%u), clipping\n",
				ncores, nr_cpu_ids);
		ncores = nr_cpu_ids;
	}

	/* The BCM63138 SoC has two Cortex-A9 CPUs, CPU0 features a complete
	 * and fully functional VFP unit that can be used, but CPU1 does not.
	 * Since we will not be able to trap kernel-mode NEON to force
	 * migration to CPU0, just do not advertise VFP support at all.
	 *
	 * This will make vfp_init bail out and do not attempt to use VFP at
	 * all, for kernel-mode NEON, we do not want to introduce any
	 * conditionals in hot-paths, so we just restrict the system to UP.
	 */
#ifdef CONFIG_VFP
	if (ncores > 1) {
		pr_warn("SMP: secondary CPUs lack VFP unit, disabling VFP\n");
		vfp_disable();

#ifdef CONFIG_KERNEL_MODE_NEON
		WARN(1, "SMP: kernel-mode NEON enabled, restricting to UP\n");
		ncores = 1;
#endif
	}
#endif

	for (i = 0; i < ncores; i++)
		set_cpu_possible(i, true);

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

	return 0;
}

static const struct of_device_id bcm63138_bootlut_ids[] = {
	{ .compatible = "brcm,bcm63138-bootlut", },
	{ /* sentinel */ },
};

#define BOOTLUT_RESET_VECT	0x20

static int bcm63138_smp_boot_secondary(unsigned int cpu,
				       struct task_struct *idle)
{
	void __iomem *bootlut_base;
	struct device_node *dn;
	int ret = 0;
	u32 val;

	dn = of_find_matching_node(NULL, bcm63138_bootlut_ids);
	if (!dn) {
		pr_err("SMP: unable to find bcm63138 boot LUT node\n");
		return -ENODEV;
	}

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

	if (!bootlut_base) {
		pr_err("SMP: unable to remap boot LUT base register\n");
		return -ENOMEM;
	}

	/* Locate the secondary CPU node */
	dn = of_get_cpu_node(cpu, NULL);
	if (!dn) {
		pr_err("SMP: failed to locate secondary CPU%d node\n", cpu);
		ret = -ENODEV;
		goto out;
	}

	/* Write the secondary init routine to the BootLUT reset vector */
	val = __pa_symbol(secondary_startup);
	writel_relaxed(val, bootlut_base + BOOTLUT_RESET_VECT);

	/* Power up the core, will jump straight to its reset vector when we
	 * return
	 */
	ret = bcm63xx_pmb_power_on_cpu(dn);
	of_node_put(dn);
	if (ret)
		goto out;
out:
	iounmap(bootlut_base);

	return ret;
}

static void __init bcm63138_smp_prepare_cpus(unsigned int max_cpus)
{
	int ret;

	ret = scu_a9_enable();
	if (ret) {
		pr_warn("SMP: Cortex-A9 SCU setup failed\n");
		return;
	}
}

static const struct smp_operations bcm63138_smp_ops __initconst = {
	.smp_prepare_cpus	= bcm63138_smp_prepare_cpus,
	.smp_boot_secondary	= bcm63138_smp_boot_secondary,
};

CPU_METHOD_OF_DECLARE(bcm63138_smp, "brcm,bcm63138", &bcm63138_smp_ops);