Branch data Line data Source code
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 : : #include <linux/arm-cci.h>
20 : :
21 : : #include <asm/mcpm.h>
22 : : #include <asm/proc-fns.h>
23 : : #include <asm/cacheflush.h>
24 : : #include <asm/cputype.h>
25 : : #include <asm/cp15.h>
26 : : #include <asm/psci.h>
27 : :
28 : :
29 : : #define RST_HOLD0 0x0
30 : : #define RST_HOLD1 0x4
31 : : #define SYS_SWRESET 0x8
32 : : #define RST_STAT0 0xc
33 : : #define RST_STAT1 0x10
34 : : #define EAG_CFG_R 0x20
35 : : #define EAG_CFG_W 0x24
36 : : #define KFC_CFG_R 0x28
37 : : #define KFC_CFG_W 0x2c
38 : : #define DCS_CFG_R 0x30
39 : :
40 : : /*
41 : : * We can't use regular spinlocks. In the switcher case, it is possible
42 : : * for an outbound CPU to call power_down() while its inbound counterpart
43 : : * is already live using the same logical CPU number which trips lockdep
44 : : * debugging.
45 : : */
46 : : static arch_spinlock_t dcscb_lock = __ARCH_SPIN_LOCK_UNLOCKED;
47 : :
48 : : static void __iomem *dcscb_base;
49 : : static int dcscb_use_count[4][2];
50 : : static int dcscb_allcpus_mask[2];
51 : :
52 : 0 : static int dcscb_power_up(unsigned int cpu, unsigned int cluster)
53 : : {
54 : 0 : unsigned int rst_hold, cpumask = (1 << cpu);
55 : 0 : unsigned int all_mask = dcscb_allcpus_mask[cluster];
56 : :
57 : : pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
58 [ # # ]: 0 : if (cpu >= 4 || cluster >= 2)
59 : : return -EINVAL;
60 : :
61 : : /*
62 : : * Since this is called with IRQs enabled, and no arch_spin_lock_irq
63 : : * variant exists, we need to disable IRQs manually here.
64 : : */
65 : : local_irq_disable();
66 : : arch_spin_lock(&dcscb_lock);
67 : :
68 : 0 : dcscb_use_count[cpu][cluster]++;
69 [ # # ]: 0 : if (dcscb_use_count[cpu][cluster] == 1) {
70 : 0 : rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4);
71 [ # # ]: 0 : if (rst_hold & (1 << 8)) {
72 : : /* remove cluster reset and add individual CPU's reset */
73 : 0 : rst_hold &= ~(1 << 8);
74 : 0 : rst_hold |= all_mask;
75 : : }
76 : 0 : rst_hold &= ~(cpumask | (cpumask << 4));
77 : 0 : writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4);
78 [ # # ]: 0 : } else if (dcscb_use_count[cpu][cluster] != 2) {
79 : : /*
80 : : * The only possible values are:
81 : : * 0 = CPU down
82 : : * 1 = CPU (still) up
83 : : * 2 = CPU requested to be up before it had a chance
84 : : * to actually make itself down.
85 : : * Any other value is a bug.
86 : : */
87 : 0 : BUG();
88 : : }
89 : :
90 : : arch_spin_unlock(&dcscb_lock);
91 : : local_irq_enable();
92 : :
93 : 0 : return 0;
94 : : }
95 : :
96 : 0 : static void dcscb_power_down(void)
97 : : {
98 : : unsigned int mpidr, cpu, cluster, rst_hold, cpumask, all_mask;
99 : : bool last_man = false, skip_wfi = false;
100 : :
101 : : mpidr = read_cpuid_mpidr();
102 : 0 : cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
103 : 0 : cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
104 : 0 : cpumask = (1 << cpu);
105 : 0 : all_mask = dcscb_allcpus_mask[cluster];
106 : :
107 : : pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
108 [ # # ]: 0 : BUG_ON(cpu >= 4 || cluster >= 2);
109 : :
110 : 0 : __mcpm_cpu_going_down(cpu, cluster);
111 : :
112 : : arch_spin_lock(&dcscb_lock);
113 [ # # ]: 0 : BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP);
114 : 0 : dcscb_use_count[cpu][cluster]--;
115 [ # # ]: 0 : if (dcscb_use_count[cpu][cluster] == 0) {
116 : 0 : rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4);
117 : 0 : rst_hold |= cpumask;
118 [ # # ]: 0 : if (((rst_hold | (rst_hold >> 4)) & all_mask) == all_mask) {
119 : 0 : rst_hold |= (1 << 8);
120 : : last_man = true;
121 : : }
122 : 0 : writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4);
123 [ # # ]: 0 : } else if (dcscb_use_count[cpu][cluster] == 1) {
124 : : /*
125 : : * A power_up request went ahead of us.
126 : : * Even if we do not want to shut this CPU down,
127 : : * the caller expects a certain state as if the WFI
128 : : * was aborted. So let's continue with cache cleaning.
129 : : */
130 : : skip_wfi = true;
131 : : } else
132 : 0 : BUG();
133 : :
134 [ # # ][ # # ]: 0 : if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) {
135 : : arch_spin_unlock(&dcscb_lock);
136 : :
137 : : /* Flush all cache levels for this cluster. */
138 : 0 : v7_exit_coherency_flush(all);
139 : :
140 : : /*
141 : : * This is a harmless no-op. On platforms with a real
142 : : * outer cache this might either be needed or not,
143 : : * depending on where the outer cache sits.
144 : : */
145 : : outer_flush_all();
146 : :
147 : : /*
148 : : * Disable cluster-level coherency by masking
149 : : * incoming snoops and DVM messages:
150 : : */
151 : 0 : cci_disable_port_by_cpu(mpidr);
152 : :
153 : 0 : __mcpm_outbound_leave_critical(cluster, CLUSTER_DOWN);
154 : : } else {
155 : : arch_spin_unlock(&dcscb_lock);
156 : :
157 : : /* Disable and flush the local CPU cache. */
158 : 0 : v7_exit_coherency_flush(louis);
159 : : }
160 : :
161 : 0 : __mcpm_cpu_down(cpu, cluster);
162 : :
163 : : /* Now we are prepared for power-down, do it: */
164 : 0 : dsb();
165 [ # # ]: 0 : if (!skip_wfi)
166 : 0 : wfi();
167 : :
168 : : /* Not dead at this point? Let our caller cope. */
169 : 0 : }
170 : :
171 : : static const struct mcpm_platform_ops dcscb_power_ops = {
172 : : .power_up = dcscb_power_up,
173 : : .power_down = dcscb_power_down,
174 : : };
175 : :
176 : 0 : static void __init dcscb_usage_count_init(void)
177 : : {
178 : : unsigned int mpidr, cpu, cluster;
179 : :
180 : : mpidr = read_cpuid_mpidr();
181 : 0 : cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
182 : 0 : cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
183 : :
184 : : pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
185 [ # # ]: 0 : BUG_ON(cpu >= 4 || cluster >= 2);
186 : 0 : dcscb_use_count[cpu][cluster] = 1;
187 : 0 : }
188 : :
189 : : extern void dcscb_power_up_setup(unsigned int affinity_level);
190 : :
191 : 0 : static int __init dcscb_init(void)
192 : : {
193 : : struct device_node *node;
194 : : unsigned int cfg;
195 : : int ret;
196 : :
197 : 0 : ret = psci_probe();
198 [ # # ]: 0 : if (!ret) {
199 : : pr_debug("psci found. Aborting native init\n");
200 : : return -ENODEV;
201 : : }
202 : :
203 [ # # ]: 0 : if (!cci_probed())
204 : : return -ENODEV;
205 : :
206 : 0 : node = of_find_compatible_node(NULL, NULL, "arm,rtsm,dcscb");
207 [ # # ]: 0 : if (!node)
208 : : return -ENODEV;
209 : 0 : dcscb_base = of_iomap(node, 0);
210 [ # # ]: 0 : if (!dcscb_base)
211 : : return -EADDRNOTAVAIL;
212 : 0 : cfg = readl_relaxed(dcscb_base + DCS_CFG_R);
213 : 0 : dcscb_allcpus_mask[0] = (1 << (((cfg >> 16) >> (0 << 2)) & 0xf)) - 1;
214 : 0 : dcscb_allcpus_mask[1] = (1 << (((cfg >> 16) >> (1 << 2)) & 0xf)) - 1;
215 : 0 : dcscb_usage_count_init();
216 : :
217 : 0 : ret = mcpm_platform_register(&dcscb_power_ops);
218 [ # # ]: 0 : if (!ret)
219 : 0 : ret = mcpm_sync_init(dcscb_power_up_setup);
220 [ # # ]: 0 : if (ret) {
221 : 0 : iounmap(dcscb_base);
222 : 0 : return ret;
223 : : }
224 : :
225 : 0 : pr_info("VExpress DCSCB support installed\n");
226 : :
227 : : /*
228 : : * Future entries into the kernel can now go
229 : : * through the cluster entry vectors.
230 : : */
231 : 0 : vexpress_flags_set(virt_to_phys(mcpm_entry_point));
232 : :
233 : 0 : return 0;
234 : : }
235 : :
236 : : early_initcall(dcscb_init);
|