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
15#define pr_fmt(fmt)		"uniphier: " fmt
16
17#include <linux/init.h>
18#include <linux/io.h>
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>
26#include <asm/smp.h>
27#include <asm/smp_scu.h>
28
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 */
33#define UNIPHIER_SBC_ROM_BOOT_RSV2	0x1208
34
35static void __iomem *uniphier_smp_rom_boot_rsv2;
36static unsigned int uniphier_smp_max_cpus;
37
38extern char uniphier_smp_trampoline;
39extern char uniphier_smp_trampoline_jump;
40extern char uniphier_smp_trampoline_poll_addr;
41extern 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 */
48static const phys_addr_t uniphier_smp_trampoline_dest_end = SECTION_SIZE;
49static phys_addr_t uniphier_smp_trampoline_dest;
50
51static int __init uniphier_smp_copy_trampoline(phys_addr_t poll_addr)
52{
53	size_t trmp_size;
54	static void __iomem *trmp_base;
55
56	if (!uniphier_cache_l2_is_enabled()) {
57		pr_warn("outer cache is needed for SMP, but not enabled\n");
58		return -ENODEV;
59	}
60
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
94static 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
101	np = of_find_compatible_node(NULL, NULL,
102				"socionext,uniphier-system-bus-controller");
103	ret = of_address_to_resource(np, 1, &res);
104	if (ret) {
105		pr_err("failed to get resource of system-bus-controller\n");
106		return ret;
107	}
108
109	rom_rsv2_phys = res.start + UNIPHIER_SBC_ROM_BOOT_RSV2;
110
111	ret = uniphier_smp_copy_trampoline(rom_rsv2_phys);
112	if (ret)
113		return ret;
114
115	uniphier_smp_rom_boot_rsv2 = ioremap(rom_rsv2_phys, sizeof(SZ_4));
116	if (!uniphier_smp_rom_boot_rsv2) {
117		pr_err("failed to map ROM_BOOT_RSV2 register\n");
118		return -ENOMEM;
119	}
120
121	writel(uniphier_smp_trampoline_dest, uniphier_smp_rom_boot_rsv2);
122	asm("sev"); /* Bring up all secondary CPUs to the trampoline code */
123
124	uniphier_smp_max_cpus = max_cpus;	/* save for later use */
125
126	return 0;
127}
128
129static void __init uniphier_smp_unprepare_trampoline(void)
130{
131	iounmap(uniphier_smp_rom_boot_rsv2);
132
133	if (uniphier_smp_trampoline_dest)
134		outer_inv_range(uniphier_smp_trampoline_dest,
135				uniphier_smp_trampoline_dest_end);
136
137	uniphier_cache_l2_set_locked_ways(0);
138}
139
140static int __init uniphier_smp_enable_scu(void)
141{
142	unsigned long scu_base_phys = 0;
143	void __iomem *scu_base;
144
145	if (scu_a9_has_base())
146		scu_base_phys = scu_a9_get_base();
147
148	if (!scu_base_phys) {
149		pr_err("failed to get scu base\n");
150		return -ENODEV;
151	}
152
153	scu_base = ioremap(scu_base_phys, SZ_128);
154	if (!scu_base) {
155		pr_err("failed to map scu base\n");
156		return -ENOMEM;
157	}
158
159	scu_enable(scu_base);
160	iounmap(scu_base);
161
162	return 0;
163}
164
165static void __init uniphier_smp_prepare_cpus(unsigned int max_cpus)
166{
167	static cpumask_t only_cpu_0 = { CPU_BITS_CPU0 };
168	int ret;
169
170	ret = uniphier_smp_prepare_trampoline(max_cpus);
171	if (ret)
172		goto err;
173
174	ret = uniphier_smp_enable_scu();
175	if (ret)
176		goto err;
177
178	return;
179err:
180	pr_warn("disabling SMP\n");
181	init_cpu_present(&only_cpu_0);
182	uniphier_smp_unprepare_trampoline();
183}
184
185static int __init uniphier_smp_boot_secondary(unsigned int cpu,
186					      struct task_struct *idle)
187{
188	if (WARN_ON_ONCE(!uniphier_smp_rom_boot_rsv2))
189		return -EFAULT;
190
191	writel(cpu, uniphier_smp_rom_boot_rsv2);
192	readl(uniphier_smp_rom_boot_rsv2); /* relax */
193
194	asm("sev"); /* wake up secondary CPUs sleeping in the trampoline */
195
196	if (cpu == uniphier_smp_max_cpus - 1) {
197		/* clean up resources if this is the last CPU */
198		uniphier_smp_unprepare_trampoline();
199	}
200
201	return 0;
202}
203
204static struct smp_operations uniphier_smp_ops __initdata = {
205	.smp_prepare_cpus	= uniphier_smp_prepare_cpus,
206	.smp_boot_secondary	= uniphier_smp_boot_secondary,
207};
208CPU_METHOD_OF_DECLARE(uniphier_smp, "socionext,uniphier-smp",
209		      &uniphier_smp_ops);
210