diff options
-rw-r--r-- | arch/arm/mach-vexpress/Kconfig | 12 | ||||
-rw-r--r-- | arch/arm/mach-vexpress/Makefile | 3 | ||||
-rw-r--r-- | arch/arm/mach-vexpress/spc.c | 262 | ||||
-rw-r--r-- | arch/arm/mach-vexpress/spc.h | 2 | ||||
-rw-r--r-- | arch/arm/mach-vexpress/tc2_pm.c | 7 |
5 files changed, 281 insertions, 5 deletions
diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 365795447804..c77170c04fd0 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig | |||
@@ -66,10 +66,22 @@ config ARCH_VEXPRESS_DCSCB | |||
66 | This is needed to provide CPU and cluster power management | 66 | This is needed to provide CPU and cluster power management |
67 | on RTSM implementing big.LITTLE. | 67 | on RTSM implementing big.LITTLE. |
68 | 68 | ||
69 | config ARCH_VEXPRESS_SPC | ||
70 | bool "Versatile Express Serial Power Controller (SPC)" | ||
71 | select ARCH_HAS_CPUFREQ | ||
72 | select ARCH_HAS_OPP | ||
73 | select PM_OPP | ||
74 | help | ||
75 | The TC2 (A15x2 A7x3) versatile express core tile integrates a logic | ||
76 | block called Serial Power Controller (SPC) that provides the interface | ||
77 | between the dual cluster test-chip and the M3 microcontroller that | ||
78 | carries out power management. | ||
79 | |||
69 | config ARCH_VEXPRESS_TC2_PM | 80 | config ARCH_VEXPRESS_TC2_PM |
70 | bool "Versatile Express TC2 power management" | 81 | bool "Versatile Express TC2 power management" |
71 | depends on MCPM | 82 | depends on MCPM |
72 | select ARM_CCI | 83 | select ARM_CCI |
84 | select ARCH_VEXPRESS_SPC | ||
73 | help | 85 | help |
74 | Support for CPU and cluster power management on Versatile Express | 86 | Support for CPU and cluster power management on Versatile Express |
75 | with a TC2 (A15x2 A7x3) big.LITTLE core tile. | 87 | with a TC2 (A15x2 A7x3) big.LITTLE core tile. |
diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 505e64ab3eae..0997e0b7494c 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile | |||
@@ -8,7 +8,8 @@ obj-y := v2m.o | |||
8 | obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o | 8 | obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o |
9 | obj-$(CONFIG_ARCH_VEXPRESS_DCSCB) += dcscb.o dcscb_setup.o | 9 | obj-$(CONFIG_ARCH_VEXPRESS_DCSCB) += dcscb.o dcscb_setup.o |
10 | CFLAGS_dcscb.o += -march=armv7-a | 10 | CFLAGS_dcscb.o += -march=armv7-a |
11 | obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += tc2_pm.o spc.o | 11 | obj-$(CONFIG_ARCH_VEXPRESS_SPC) += spc.o |
12 | obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += tc2_pm.o | ||
12 | CFLAGS_tc2_pm.o += -march=armv7-a | 13 | CFLAGS_tc2_pm.o += -march=armv7-a |
13 | obj-$(CONFIG_SMP) += platsmp.o | 14 | obj-$(CONFIG_SMP) += platsmp.o |
14 | obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o | 15 | obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o |
diff --git a/arch/arm/mach-vexpress/spc.c b/arch/arm/mach-vexpress/spc.c index eefb029197ca..de0ca3615c36 100644 --- a/arch/arm/mach-vexpress/spc.c +++ b/arch/arm/mach-vexpress/spc.c | |||
@@ -17,14 +17,27 @@ | |||
17 | * GNU General Public License for more details. | 17 | * GNU General Public License for more details. |
18 | */ | 18 | */ |
19 | 19 | ||
20 | #include <linux/delay.h> | ||
20 | #include <linux/err.h> | 21 | #include <linux/err.h> |
22 | #include <linux/interrupt.h> | ||
21 | #include <linux/io.h> | 23 | #include <linux/io.h> |
24 | #include <linux/pm_opp.h> | ||
22 | #include <linux/slab.h> | 25 | #include <linux/slab.h> |
26 | #include <linux/semaphore.h> | ||
23 | 27 | ||
24 | #include <asm/cacheflush.h> | 28 | #include <asm/cacheflush.h> |
25 | 29 | ||
26 | #define SPCLOG "vexpress-spc: " | 30 | #define SPCLOG "vexpress-spc: " |
27 | 31 | ||
32 | #define PERF_LVL_A15 0x00 | ||
33 | #define PERF_REQ_A15 0x04 | ||
34 | #define PERF_LVL_A7 0x08 | ||
35 | #define PERF_REQ_A7 0x0c | ||
36 | #define COMMS 0x10 | ||
37 | #define COMMS_REQ 0x14 | ||
38 | #define PWC_STATUS 0x18 | ||
39 | #define PWC_FLAG 0x1c | ||
40 | |||
28 | /* SPC wake-up IRQs status and mask */ | 41 | /* SPC wake-up IRQs status and mask */ |
29 | #define WAKE_INT_MASK 0x24 | 42 | #define WAKE_INT_MASK 0x24 |
30 | #define WAKE_INT_RAW 0x28 | 43 | #define WAKE_INT_RAW 0x28 |
@@ -36,12 +49,45 @@ | |||
36 | #define A15_BX_ADDR0 0x68 | 49 | #define A15_BX_ADDR0 0x68 |
37 | #define A7_BX_ADDR0 0x78 | 50 | #define A7_BX_ADDR0 0x78 |
38 | 51 | ||
52 | /* SPC system config interface registers */ | ||
53 | #define SYSCFG_WDATA 0x70 | ||
54 | #define SYSCFG_RDATA 0x74 | ||
55 | |||
56 | /* A15/A7 OPP virtual register base */ | ||
57 | #define A15_PERFVAL_BASE 0xC10 | ||
58 | #define A7_PERFVAL_BASE 0xC30 | ||
59 | |||
60 | /* Config interface control bits */ | ||
61 | #define SYSCFG_START (1 << 31) | ||
62 | #define SYSCFG_SCC (6 << 20) | ||
63 | #define SYSCFG_STAT (14 << 20) | ||
64 | |||
39 | /* wake-up interrupt masks */ | 65 | /* wake-up interrupt masks */ |
40 | #define GBL_WAKEUP_INT_MSK (0x3 << 10) | 66 | #define GBL_WAKEUP_INT_MSK (0x3 << 10) |
41 | 67 | ||
42 | /* TC2 static dual-cluster configuration */ | 68 | /* TC2 static dual-cluster configuration */ |
43 | #define MAX_CLUSTERS 2 | 69 | #define MAX_CLUSTERS 2 |
44 | 70 | ||
71 | /* | ||
72 | * Even though the SPC takes max 3-5 ms to complete any OPP/COMMS | ||
73 | * operation, the operation could start just before jiffie is about | ||
74 | * to be incremented. So setting timeout value of 20ms = 2jiffies@100Hz | ||
75 | */ | ||
76 | #define TIMEOUT_US 20000 | ||
77 | |||
78 | #define MAX_OPPS 8 | ||
79 | #define CA15_DVFS 0 | ||
80 | #define CA7_DVFS 1 | ||
81 | #define SPC_SYS_CFG 2 | ||
82 | #define STAT_COMPLETE(type) ((1 << 0) << (type << 2)) | ||
83 | #define STAT_ERR(type) ((1 << 1) << (type << 2)) | ||
84 | #define RESPONSE_MASK(type) (STAT_COMPLETE(type) | STAT_ERR(type)) | ||
85 | |||
86 | struct ve_spc_opp { | ||
87 | unsigned long freq; | ||
88 | unsigned long u_volt; | ||
89 | }; | ||
90 | |||
45 | struct ve_spc_drvdata { | 91 | struct ve_spc_drvdata { |
46 | void __iomem *baseaddr; | 92 | void __iomem *baseaddr; |
47 | /* | 93 | /* |
@@ -49,6 +95,12 @@ struct ve_spc_drvdata { | |||
49 | * It corresponds to A15 processors MPIDR[15:8] bitfield | 95 | * It corresponds to A15 processors MPIDR[15:8] bitfield |
50 | */ | 96 | */ |
51 | u32 a15_clusid; | 97 | u32 a15_clusid; |
98 | uint32_t cur_rsp_mask; | ||
99 | uint32_t cur_rsp_stat; | ||
100 | struct semaphore sem; | ||
101 | struct completion done; | ||
102 | struct ve_spc_opp *opps[MAX_CLUSTERS]; | ||
103 | int num_opps[MAX_CLUSTERS]; | ||
52 | }; | 104 | }; |
53 | 105 | ||
54 | static struct ve_spc_drvdata *info; | 106 | static struct ve_spc_drvdata *info; |
@@ -157,8 +209,197 @@ void ve_spc_powerdown(u32 cluster, bool enable) | |||
157 | writel_relaxed(enable, info->baseaddr + pwdrn_reg); | 209 | writel_relaxed(enable, info->baseaddr + pwdrn_reg); |
158 | } | 210 | } |
159 | 211 | ||
160 | int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid) | 212 | static int ve_spc_get_performance(int cluster, u32 *freq) |
213 | { | ||
214 | struct ve_spc_opp *opps = info->opps[cluster]; | ||
215 | u32 perf_cfg_reg = 0; | ||
216 | u32 perf; | ||
217 | |||
218 | perf_cfg_reg = cluster_is_a15(cluster) ? PERF_LVL_A15 : PERF_LVL_A7; | ||
219 | |||
220 | perf = readl_relaxed(info->baseaddr + perf_cfg_reg); | ||
221 | if (perf >= info->num_opps[cluster]) | ||
222 | return -EINVAL; | ||
223 | |||
224 | opps += perf; | ||
225 | *freq = opps->freq; | ||
226 | |||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | /* find closest match to given frequency in OPP table */ | ||
231 | static int ve_spc_round_performance(int cluster, u32 freq) | ||
232 | { | ||
233 | int idx, max_opp = info->num_opps[cluster]; | ||
234 | struct ve_spc_opp *opps = info->opps[cluster]; | ||
235 | u32 fmin = 0, fmax = ~0, ftmp; | ||
236 | |||
237 | freq /= 1000; /* OPP entries in kHz */ | ||
238 | for (idx = 0; idx < max_opp; idx++, opps++) { | ||
239 | ftmp = opps->freq; | ||
240 | if (ftmp >= freq) { | ||
241 | if (ftmp <= fmax) | ||
242 | fmax = ftmp; | ||
243 | } else { | ||
244 | if (ftmp >= fmin) | ||
245 | fmin = ftmp; | ||
246 | } | ||
247 | } | ||
248 | if (fmax != ~0) | ||
249 | return fmax * 1000; | ||
250 | else | ||
251 | return fmin * 1000; | ||
252 | } | ||
253 | |||
254 | static int ve_spc_find_performance_index(int cluster, u32 freq) | ||
255 | { | ||
256 | int idx, max_opp = info->num_opps[cluster]; | ||
257 | struct ve_spc_opp *opps = info->opps[cluster]; | ||
258 | |||
259 | for (idx = 0; idx < max_opp; idx++, opps++) | ||
260 | if (opps->freq == freq) | ||
261 | break; | ||
262 | return (idx == max_opp) ? -EINVAL : idx; | ||
263 | } | ||
264 | |||
265 | static int ve_spc_waitforcompletion(int req_type) | ||
266 | { | ||
267 | int ret = wait_for_completion_interruptible_timeout( | ||
268 | &info->done, usecs_to_jiffies(TIMEOUT_US)); | ||
269 | if (ret == 0) | ||
270 | ret = -ETIMEDOUT; | ||
271 | else if (ret > 0) | ||
272 | ret = info->cur_rsp_stat & STAT_COMPLETE(req_type) ? 0 : -EIO; | ||
273 | return ret; | ||
274 | } | ||
275 | |||
276 | static int ve_spc_set_performance(int cluster, u32 freq) | ||
277 | { | ||
278 | u32 perf_cfg_reg, perf_stat_reg; | ||
279 | int ret, perf, req_type; | ||
280 | |||
281 | if (cluster_is_a15(cluster)) { | ||
282 | req_type = CA15_DVFS; | ||
283 | perf_cfg_reg = PERF_LVL_A15; | ||
284 | perf_stat_reg = PERF_REQ_A15; | ||
285 | } else { | ||
286 | req_type = CA7_DVFS; | ||
287 | perf_cfg_reg = PERF_LVL_A7; | ||
288 | perf_stat_reg = PERF_REQ_A7; | ||
289 | } | ||
290 | |||
291 | perf = ve_spc_find_performance_index(cluster, freq); | ||
292 | |||
293 | if (perf < 0) | ||
294 | return perf; | ||
295 | |||
296 | if (down_timeout(&info->sem, usecs_to_jiffies(TIMEOUT_US))) | ||
297 | return -ETIME; | ||
298 | |||
299 | init_completion(&info->done); | ||
300 | info->cur_rsp_mask = RESPONSE_MASK(req_type); | ||
301 | |||
302 | writel(perf, info->baseaddr + perf_cfg_reg); | ||
303 | ret = ve_spc_waitforcompletion(req_type); | ||
304 | |||
305 | info->cur_rsp_mask = 0; | ||
306 | up(&info->sem); | ||
307 | |||
308 | return ret; | ||
309 | } | ||
310 | |||
311 | static int ve_spc_read_sys_cfg(int func, int offset, uint32_t *data) | ||
312 | { | ||
313 | int ret; | ||
314 | |||
315 | if (down_timeout(&info->sem, usecs_to_jiffies(TIMEOUT_US))) | ||
316 | return -ETIME; | ||
317 | |||
318 | init_completion(&info->done); | ||
319 | info->cur_rsp_mask = RESPONSE_MASK(SPC_SYS_CFG); | ||
320 | |||
321 | /* Set the control value */ | ||
322 | writel(SYSCFG_START | func | offset >> 2, info->baseaddr + COMMS); | ||
323 | ret = ve_spc_waitforcompletion(SPC_SYS_CFG); | ||
324 | |||
325 | if (ret == 0) | ||
326 | *data = readl(info->baseaddr + SYSCFG_RDATA); | ||
327 | |||
328 | info->cur_rsp_mask = 0; | ||
329 | up(&info->sem); | ||
330 | |||
331 | return ret; | ||
332 | } | ||
333 | |||
334 | static irqreturn_t ve_spc_irq_handler(int irq, void *data) | ||
335 | { | ||
336 | struct ve_spc_drvdata *drv_data = data; | ||
337 | uint32_t status = readl_relaxed(drv_data->baseaddr + PWC_STATUS); | ||
338 | |||
339 | if (info->cur_rsp_mask & status) { | ||
340 | info->cur_rsp_stat = status; | ||
341 | complete(&drv_data->done); | ||
342 | } | ||
343 | |||
344 | return IRQ_HANDLED; | ||
345 | } | ||
346 | |||
347 | /* | ||
348 | * +--------------------------+ | ||
349 | * | 31 20 | 19 0 | | ||
350 | * +--------------------------+ | ||
351 | * | u_volt | freq(kHz) | | ||
352 | * +--------------------------+ | ||
353 | */ | ||
354 | #define MULT_FACTOR 20 | ||
355 | #define VOLT_SHIFT 20 | ||
356 | #define FREQ_MASK (0xFFFFF) | ||
357 | static int ve_spc_populate_opps(uint32_t cluster) | ||
358 | { | ||
359 | uint32_t data = 0, off, ret, idx; | ||
360 | struct ve_spc_opp *opps; | ||
361 | |||
362 | opps = kzalloc(sizeof(*opps) * MAX_OPPS, GFP_KERNEL); | ||
363 | if (!opps) | ||
364 | return -ENOMEM; | ||
365 | |||
366 | info->opps[cluster] = opps; | ||
367 | |||
368 | off = cluster_is_a15(cluster) ? A15_PERFVAL_BASE : A7_PERFVAL_BASE; | ||
369 | for (idx = 0; idx < MAX_OPPS; idx++, off += 4, opps++) { | ||
370 | ret = ve_spc_read_sys_cfg(SYSCFG_SCC, off, &data); | ||
371 | if (!ret) { | ||
372 | opps->freq = (data & FREQ_MASK) * MULT_FACTOR; | ||
373 | opps->u_volt = data >> VOLT_SHIFT; | ||
374 | } else { | ||
375 | break; | ||
376 | } | ||
377 | } | ||
378 | info->num_opps[cluster] = idx; | ||
379 | |||
380 | return ret; | ||
381 | } | ||
382 | |||
383 | static int ve_init_opp_table(struct device *cpu_dev) | ||
384 | { | ||
385 | int cluster = topology_physical_package_id(cpu_dev->id); | ||
386 | int idx, ret = 0, max_opp = info->num_opps[cluster]; | ||
387 | struct ve_spc_opp *opps = info->opps[cluster]; | ||
388 | |||
389 | for (idx = 0; idx < max_opp; idx++, opps++) { | ||
390 | ret = dev_pm_opp_add(cpu_dev, opps->freq * 1000, opps->u_volt); | ||
391 | if (ret) { | ||
392 | dev_warn(cpu_dev, "failed to add opp %lu %lu\n", | ||
393 | opps->freq, opps->u_volt); | ||
394 | return ret; | ||
395 | } | ||
396 | } | ||
397 | return ret; | ||
398 | } | ||
399 | |||
400 | int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid, int irq) | ||
161 | { | 401 | { |
402 | int ret; | ||
162 | info = kzalloc(sizeof(*info), GFP_KERNEL); | 403 | info = kzalloc(sizeof(*info), GFP_KERNEL); |
163 | if (!info) { | 404 | if (!info) { |
164 | pr_err(SPCLOG "unable to allocate mem\n"); | 405 | pr_err(SPCLOG "unable to allocate mem\n"); |
@@ -168,6 +409,25 @@ int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid) | |||
168 | info->baseaddr = baseaddr; | 409 | info->baseaddr = baseaddr; |
169 | info->a15_clusid = a15_clusid; | 410 | info->a15_clusid = a15_clusid; |
170 | 411 | ||
412 | if (irq <= 0) { | ||
413 | pr_err(SPCLOG "Invalid IRQ %d\n", irq); | ||
414 | kfree(info); | ||
415 | return -EINVAL; | ||
416 | } | ||
417 | |||
418 | init_completion(&info->done); | ||
419 | |||
420 | readl_relaxed(info->baseaddr + PWC_STATUS); | ||
421 | |||
422 | ret = request_irq(irq, ve_spc_irq_handler, IRQF_TRIGGER_HIGH | ||
423 | | IRQF_ONESHOT, "vexpress-spc", info); | ||
424 | if (ret) { | ||
425 | pr_err(SPCLOG "IRQ %d request failed\n", irq); | ||
426 | kfree(info); | ||
427 | return -ENODEV; | ||
428 | } | ||
429 | |||
430 | sema_init(&info->sem, 1); | ||
171 | /* | 431 | /* |
172 | * Multi-cluster systems may need this data when non-coherent, during | 432 | * Multi-cluster systems may need this data when non-coherent, during |
173 | * cluster power-up/power-down. Make sure driver info reaches main | 433 | * cluster power-up/power-down. Make sure driver info reaches main |
diff --git a/arch/arm/mach-vexpress/spc.h b/arch/arm/mach-vexpress/spc.h index 5f7e4a446a17..dbd44c3720f9 100644 --- a/arch/arm/mach-vexpress/spc.h +++ b/arch/arm/mach-vexpress/spc.h | |||
@@ -15,7 +15,7 @@ | |||
15 | #ifndef __SPC_H_ | 15 | #ifndef __SPC_H_ |
16 | #define __SPC_H_ | 16 | #define __SPC_H_ |
17 | 17 | ||
18 | int __init ve_spc_init(void __iomem *base, u32 a15_clusid); | 18 | int __init ve_spc_init(void __iomem *base, u32 a15_clusid, int irq); |
19 | void ve_spc_global_wakeup_irq(bool set); | 19 | void ve_spc_global_wakeup_irq(bool set); |
20 | void ve_spc_cpu_wakeup_irq(u32 cluster, u32 cpu, bool set); | 20 | void ve_spc_cpu_wakeup_irq(u32 cluster, u32 cpu, bool set); |
21 | void ve_spc_set_resume_addr(u32 cluster, u32 cpu, u32 addr); | 21 | void ve_spc_set_resume_addr(u32 cluster, u32 cpu, u32 addr); |
diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index e6eb48192912..d38130aba464 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c | |||
@@ -16,6 +16,7 @@ | |||
16 | #include <linux/io.h> | 16 | #include <linux/io.h> |
17 | #include <linux/kernel.h> | 17 | #include <linux/kernel.h> |
18 | #include <linux/of_address.h> | 18 | #include <linux/of_address.h> |
19 | #include <linux/of_irq.h> | ||
19 | #include <linux/spinlock.h> | 20 | #include <linux/spinlock.h> |
20 | #include <linux/errno.h> | 21 | #include <linux/errno.h> |
21 | #include <linux/irqchip/arm-gic.h> | 22 | #include <linux/irqchip/arm-gic.h> |
@@ -311,7 +312,7 @@ static void __naked tc2_pm_power_up_setup(unsigned int affinity_level) | |||
311 | 312 | ||
312 | static int __init tc2_pm_init(void) | 313 | static int __init tc2_pm_init(void) |
313 | { | 314 | { |
314 | int ret; | 315 | int ret, irq; |
315 | void __iomem *scc; | 316 | void __iomem *scc; |
316 | u32 a15_cluster_id, a7_cluster_id, sys_info; | 317 | u32 a15_cluster_id, a7_cluster_id, sys_info; |
317 | struct device_node *np; | 318 | struct device_node *np; |
@@ -336,13 +337,15 @@ static int __init tc2_pm_init(void) | |||
336 | tc2_nr_cpus[a15_cluster_id] = (sys_info >> 16) & 0xf; | 337 | tc2_nr_cpus[a15_cluster_id] = (sys_info >> 16) & 0xf; |
337 | tc2_nr_cpus[a7_cluster_id] = (sys_info >> 20) & 0xf; | 338 | tc2_nr_cpus[a7_cluster_id] = (sys_info >> 20) & 0xf; |
338 | 339 | ||
340 | irq = irq_of_parse_and_map(np, 0); | ||
341 | |||
339 | /* | 342 | /* |
340 | * A subset of the SCC registers is also used to communicate | 343 | * A subset of the SCC registers is also used to communicate |
341 | * with the SPC (power controller). We need to be able to | 344 | * with the SPC (power controller). We need to be able to |
342 | * drive it very early in the boot process to power up | 345 | * drive it very early in the boot process to power up |
343 | * processors, so we initialize the SPC driver here. | 346 | * processors, so we initialize the SPC driver here. |
344 | */ | 347 | */ |
345 | ret = ve_spc_init(scc + SPC_BASE, a15_cluster_id); | 348 | ret = ve_spc_init(scc + SPC_BASE, a15_cluster_id, irq); |
346 | if (ret) | 349 | if (ret) |
347 | return ret; | 350 | return ret; |
348 | 351 | ||