1/*
2 * plat smp support for CSR Marco dual-core SMP SoCs
3 *
4 * Copyright (c) 2012 Cambridge Silicon Radio Limited, a CSR plc group company.
5 *
6 * Licensed under GPLv2 or later.
7 */
8
9#include <linux/init.h>
10#include <linux/smp.h>
11#include <linux/delay.h>
12#include <linux/of.h>
13#include <linux/of_address.h>
14#include <asm/page.h>
15#include <asm/mach/map.h>
16#include <asm/smp_plat.h>
17#include <asm/smp_scu.h>
18#include <asm/cacheflush.h>
19#include <asm/cputype.h>
20
21#include "common.h"
22
23static void __iomem *clk_base;
24
25static DEFINE_SPINLOCK(boot_lock);
26
27static void sirfsoc_secondary_init(unsigned int cpu)
28{
29	/*
30	 * let the primary processor know we're out of the
31	 * pen, then head off into the C entry point
32	 */
33	pen_release = -1;
34	smp_wmb();
35
36	/*
37	 * Synchronise with the boot thread.
38	 */
39	spin_lock(&boot_lock);
40	spin_unlock(&boot_lock);
41}
42
43static const struct of_device_id clk_ids[]  = {
44	{ .compatible = "sirf,atlas7-clkc" },
45	{},
46};
47
48static int sirfsoc_boot_secondary(unsigned int cpu, struct task_struct *idle)
49{
50	unsigned long timeout;
51	struct device_node *np;
52
53	np = of_find_matching_node(NULL, clk_ids);
54	if (!np)
55		return -ENODEV;
56
57	clk_base = of_iomap(np, 0);
58	if (!clk_base)
59		return -ENOMEM;
60
61	/*
62	 * write the address of secondary startup into the clkc register
63	 * at offset 0x2bC, then write the magic number 0x3CAF5D62 to the
64	 * clkc register at offset 0x2b8, which is what boot rom code is
65	 * waiting for. This would wake up the secondary core from WFE
66	 */
67#define SIRFSOC_CPU1_JUMPADDR_OFFSET 0x2bc
68	__raw_writel(virt_to_phys(sirfsoc_secondary_startup),
69		clk_base + SIRFSOC_CPU1_JUMPADDR_OFFSET);
70
71#define SIRFSOC_CPU1_WAKEMAGIC_OFFSET 0x2b8
72	__raw_writel(0x3CAF5D62,
73		clk_base + SIRFSOC_CPU1_WAKEMAGIC_OFFSET);
74
75	/* make sure write buffer is drained */
76	mb();
77
78	spin_lock(&boot_lock);
79
80	/*
81	 * The secondary processor is waiting to be released from
82	 * the holding pen - release it, then wait for it to flag
83	 * that it has been released by resetting pen_release.
84	 *
85	 * Note that "pen_release" is the hardware CPU ID, whereas
86	 * "cpu" is Linux's internal ID.
87	 */
88	pen_release = cpu_logical_map(cpu);
89	sync_cache_w(&pen_release);
90
91	/*
92	 * Send the secondary CPU SEV, thereby causing the boot monitor to read
93	 * the JUMPADDR and WAKEMAGIC, and branch to the address found there.
94	 */
95	dsb_sev();
96
97	timeout = jiffies + (1 * HZ);
98	while (time_before(jiffies, timeout)) {
99		smp_rmb();
100		if (pen_release == -1)
101			break;
102
103		udelay(10);
104	}
105
106	/*
107	 * now the secondary core is starting up let it run its
108	 * calibrations, then wait for it to finish
109	 */
110	spin_unlock(&boot_lock);
111
112	return pen_release != -1 ? -ENOSYS : 0;
113}
114
115struct smp_operations sirfsoc_smp_ops __initdata = {
116	.smp_secondary_init     = sirfsoc_secondary_init,
117	.smp_boot_secondary     = sirfsoc_boot_secondary,
118#ifdef CONFIG_HOTPLUG_CPU
119	.cpu_die                = sirfsoc_cpu_die,
120#endif
121};
122