diff options
author | Nicolas Pitre <nicolas.pitre@linaro.org> | 2012-05-02 20:56:52 -0400 |
---|---|---|
committer | Nicolas Pitre <nicolas.pitre@linaro.org> | 2013-05-29 15:50:34 -0400 |
commit | 1e904e1bf6f1285cc2dd5696c44b7cf78cda643f (patch) | |
tree | 960d3d6b28b2a6097da4806b1059f55d4fa263ad /arch/arm/mach-vexpress/dcscb.c | |
parent | bbc8d77db655be61a21d7623428c46c578a866d3 (diff) |
ARM: vexpress: introduce DCSCB support
This adds basic CPU and cluster reset controls on RTSM for the
A15x4-A7x4 model configuration using the Dual Cluster System
Configuration Block (DCSCB).
The cache coherency interconnect (CCI) is not handled yet.
Signed-off-by: Nicolas Pitre <nico@linaro.org>
Reviewed-by: Santosh Shilimkar <santosh.shilimkar@ti.com>
Acked-by: Pawel Moll <pawel.moll@arm.com>
Diffstat (limited to 'arch/arm/mach-vexpress/dcscb.c')
-rw-r--r-- | arch/arm/mach-vexpress/dcscb.c | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/arch/arm/mach-vexpress/dcscb.c b/arch/arm/mach-vexpress/dcscb.c new file mode 100644 index 000000000000..2ca4bbce530c --- /dev/null +++ b/arch/arm/mach-vexpress/dcscb.c | |||
@@ -0,0 +1,164 @@ | |||
1 | /* | ||
2 | * arch/arm/mach-vexpress/dcscb.c - Dual Cluster System Configuration Block | ||
3 | * | ||
4 | * Created by: Nicolas Pitre, May 2012 | ||
5 | * Copyright: (C) 2012-2013 Linaro Limited | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 as | ||
9 | * published by the Free Software Foundation. | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/io.h> | ||
15 | #include <linux/spinlock.h> | ||
16 | #include <linux/errno.h> | ||
17 | #include <linux/of_address.h> | ||
18 | #include <linux/vexpress.h> | ||
19 | |||
20 | #include <asm/mcpm.h> | ||
21 | #include <asm/proc-fns.h> | ||
22 | #include <asm/cacheflush.h> | ||
23 | #include <asm/cputype.h> | ||
24 | #include <asm/cp15.h> | ||
25 | |||
26 | |||
27 | #define RST_HOLD0 0x0 | ||
28 | #define RST_HOLD1 0x4 | ||
29 | #define SYS_SWRESET 0x8 | ||
30 | #define RST_STAT0 0xc | ||
31 | #define RST_STAT1 0x10 | ||
32 | #define EAG_CFG_R 0x20 | ||
33 | #define EAG_CFG_W 0x24 | ||
34 | #define KFC_CFG_R 0x28 | ||
35 | #define KFC_CFG_W 0x2c | ||
36 | #define DCS_CFG_R 0x30 | ||
37 | |||
38 | /* | ||
39 | * We can't use regular spinlocks. In the switcher case, it is possible | ||
40 | * for an outbound CPU to call power_down() while its inbound counterpart | ||
41 | * is already live using the same logical CPU number which trips lockdep | ||
42 | * debugging. | ||
43 | */ | ||
44 | static arch_spinlock_t dcscb_lock = __ARCH_SPIN_LOCK_UNLOCKED; | ||
45 | |||
46 | static void __iomem *dcscb_base; | ||
47 | |||
48 | static int dcscb_power_up(unsigned int cpu, unsigned int cluster) | ||
49 | { | ||
50 | unsigned int rst_hold, cpumask = (1 << cpu); | ||
51 | |||
52 | pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); | ||
53 | if (cpu >= 4 || cluster >= 2) | ||
54 | return -EINVAL; | ||
55 | |||
56 | /* | ||
57 | * Since this is called with IRQs enabled, and no arch_spin_lock_irq | ||
58 | * variant exists, we need to disable IRQs manually here. | ||
59 | */ | ||
60 | local_irq_disable(); | ||
61 | arch_spin_lock(&dcscb_lock); | ||
62 | |||
63 | rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); | ||
64 | if (rst_hold & (1 << 8)) { | ||
65 | /* remove cluster reset and add individual CPU's reset */ | ||
66 | rst_hold &= ~(1 << 8); | ||
67 | rst_hold |= 0xf; | ||
68 | } | ||
69 | rst_hold &= ~(cpumask | (cpumask << 4)); | ||
70 | writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); | ||
71 | |||
72 | arch_spin_unlock(&dcscb_lock); | ||
73 | local_irq_enable(); | ||
74 | |||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | static void dcscb_power_down(void) | ||
79 | { | ||
80 | unsigned int mpidr, cpu, cluster, rst_hold, cpumask, last_man; | ||
81 | |||
82 | mpidr = read_cpuid_mpidr(); | ||
83 | cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); | ||
84 | cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); | ||
85 | cpumask = (1 << cpu); | ||
86 | |||
87 | pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); | ||
88 | BUG_ON(cpu >= 4 || cluster >= 2); | ||
89 | |||
90 | arch_spin_lock(&dcscb_lock); | ||
91 | rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); | ||
92 | rst_hold |= cpumask; | ||
93 | if (((rst_hold | (rst_hold >> 4)) & 0xf) == 0xf) | ||
94 | rst_hold |= (1 << 8); | ||
95 | writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); | ||
96 | arch_spin_unlock(&dcscb_lock); | ||
97 | last_man = (rst_hold & (1 << 8)); | ||
98 | |||
99 | /* | ||
100 | * Now let's clean our L1 cache and shut ourself down. | ||
101 | * If we're the last CPU in this cluster then clean L2 too. | ||
102 | */ | ||
103 | |||
104 | /* | ||
105 | * A15/A7 can hit in the cache with SCTLR.C=0, so we don't need | ||
106 | * a preliminary flush here for those CPUs. At least, that's | ||
107 | * the theory -- without the extra flush, Linux explodes on | ||
108 | * RTSM (to be investigated).. | ||
109 | */ | ||
110 | flush_cache_louis(); | ||
111 | set_cr(get_cr() & ~CR_C); | ||
112 | |||
113 | if (!last_man) { | ||
114 | flush_cache_louis(); | ||
115 | } else { | ||
116 | flush_cache_all(); | ||
117 | outer_flush_all(); | ||
118 | } | ||
119 | |||
120 | /* Disable local coherency by clearing the ACTLR "SMP" bit: */ | ||
121 | set_auxcr(get_auxcr() & ~(1 << 6)); | ||
122 | |||
123 | /* Now we are prepared for power-down, do it: */ | ||
124 | dsb(); | ||
125 | wfi(); | ||
126 | |||
127 | /* Not dead at this point? Let our caller cope. */ | ||
128 | } | ||
129 | |||
130 | static const struct mcpm_platform_ops dcscb_power_ops = { | ||
131 | .power_up = dcscb_power_up, | ||
132 | .power_down = dcscb_power_down, | ||
133 | }; | ||
134 | |||
135 | static int __init dcscb_init(void) | ||
136 | { | ||
137 | struct device_node *node; | ||
138 | int ret; | ||
139 | |||
140 | node = of_find_compatible_node(NULL, NULL, "arm,rtsm,dcscb"); | ||
141 | if (!node) | ||
142 | return -ENODEV; | ||
143 | dcscb_base = of_iomap(node, 0); | ||
144 | if (!dcscb_base) | ||
145 | return -EADDRNOTAVAIL; | ||
146 | |||
147 | ret = mcpm_platform_register(&dcscb_power_ops); | ||
148 | if (ret) { | ||
149 | iounmap(dcscb_base); | ||
150 | return ret; | ||
151 | } | ||
152 | |||
153 | pr_info("VExpress DCSCB support installed\n"); | ||
154 | |||
155 | /* | ||
156 | * Future entries into the kernel can now go | ||
157 | * through the cluster entry vectors. | ||
158 | */ | ||
159 | vexpress_flags_set(virt_to_phys(mcpm_entry_point)); | ||
160 | |||
161 | return 0; | ||
162 | } | ||
163 | |||
164 | early_initcall(dcscb_init); | ||