]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - arch/arm/mach-vexpress/spc.c
ARM: vexpress/TC2: add Serial Power Controller (SPC) support
[karo-tx-linux.git] / arch / arm / mach-vexpress / spc.c
1 /*
2  * Versatile Express Serial Power Controller (SPC) support
3  *
4  * Copyright (C) 2013 ARM Ltd.
5  *
6  * Authors: Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com>
7  *          Achin Gupta           <achin.gupta@arm.com>
8  *          Lorenzo Pieralisi     <lorenzo.pieralisi@arm.com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License version 2 as
12  * published by the Free Software Foundation.
13  *
14  * This program is distributed "as is" WITHOUT ANY WARRANTY of any
15  * kind, whether express or implied; without even the implied warranty
16  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  */
19
20 #include <linux/err.h>
21 #include <linux/io.h>
22 #include <linux/slab.h>
23
24 #include <asm/cacheflush.h>
25
26 #define SPCLOG "vexpress-spc: "
27
28 /* SPC wake-up IRQs status and mask */
29 #define WAKE_INT_MASK           0x24
30 #define WAKE_INT_RAW            0x28
31 #define WAKE_INT_STAT           0x2c
32 /* SPC power down registers */
33 #define A15_PWRDN_EN            0x30
34 #define A7_PWRDN_EN             0x34
35 /* SPC per-CPU mailboxes */
36 #define A15_BX_ADDR0            0x68
37 #define A7_BX_ADDR0             0x78
38
39 /* wake-up interrupt masks */
40 #define GBL_WAKEUP_INT_MSK      (0x3 << 10)
41
42 /* TC2 static dual-cluster configuration */
43 #define MAX_CLUSTERS            2
44
45 struct ve_spc_drvdata {
46         void __iomem *baseaddr;
47         /*
48          * A15s cluster identifier
49          * It corresponds to A15 processors MPIDR[15:8] bitfield
50          */
51         u32 a15_clusid;
52 };
53
54 static struct ve_spc_drvdata *info;
55
56 static inline bool cluster_is_a15(u32 cluster)
57 {
58         return cluster == info->a15_clusid;
59 }
60
61 /**
62  * ve_spc_global_wakeup_irq()
63  *
64  * Function to set/clear global wakeup IRQs. Not protected by locking since
65  * it might be used in code paths where normal cacheable locks are not
66  * working. Locking must be provided by the caller to ensure atomicity.
67  *
68  * @set: if true, global wake-up IRQs are set, if false they are cleared
69  */
70 void ve_spc_global_wakeup_irq(bool set)
71 {
72         u32 reg;
73
74         reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK);
75
76         if (set)
77                 reg |= GBL_WAKEUP_INT_MSK;
78         else
79                 reg &= ~GBL_WAKEUP_INT_MSK;
80
81         writel_relaxed(reg, info->baseaddr + WAKE_INT_MASK);
82 }
83
84 /**
85  * ve_spc_cpu_wakeup_irq()
86  *
87  * Function to set/clear per-CPU wake-up IRQs. Not protected by locking since
88  * it might be used in code paths where normal cacheable locks are not
89  * working. Locking must be provided by the caller to ensure atomicity.
90  *
91  * @cluster: mpidr[15:8] bitfield describing cluster affinity level
92  * @cpu: mpidr[7:0] bitfield describing cpu affinity level
93  * @set: if true, wake-up IRQs are set, if false they are cleared
94  */
95 void ve_spc_cpu_wakeup_irq(u32 cluster, u32 cpu, bool set)
96 {
97         u32 mask, reg;
98
99         if (cluster >= MAX_CLUSTERS)
100                 return;
101
102         mask = 1 << cpu;
103
104         if (!cluster_is_a15(cluster))
105                 mask <<= 4;
106
107         reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK);
108
109         if (set)
110                 reg |= mask;
111         else
112                 reg &= ~mask;
113
114         writel_relaxed(reg, info->baseaddr + WAKE_INT_MASK);
115 }
116
117 /**
118  * ve_spc_set_resume_addr() - set the jump address used for warm boot
119  *
120  * @cluster: mpidr[15:8] bitfield describing cluster affinity level
121  * @cpu: mpidr[7:0] bitfield describing cpu affinity level
122  * @addr: physical resume address
123  */
124 void ve_spc_set_resume_addr(u32 cluster, u32 cpu, u32 addr)
125 {
126         void __iomem *baseaddr;
127
128         if (cluster >= MAX_CLUSTERS)
129                 return;
130
131         if (cluster_is_a15(cluster))
132                 baseaddr = info->baseaddr + A15_BX_ADDR0 + (cpu << 2);
133         else
134                 baseaddr = info->baseaddr + A7_BX_ADDR0 + (cpu << 2);
135
136         writel_relaxed(addr, baseaddr);
137 }
138
139 /**
140  * ve_spc_powerdown()
141  *
142  * Function to enable/disable cluster powerdown. Not protected by locking
143  * since it might be used in code paths where normal cacheable locks are not
144  * working. Locking must be provided by the caller to ensure atomicity.
145  *
146  * @cluster: mpidr[15:8] bitfield describing cluster affinity level
147  * @enable: if true enables powerdown, if false disables it
148  */
149 void ve_spc_powerdown(u32 cluster, bool enable)
150 {
151         u32 pwdrn_reg;
152
153         if (cluster >= MAX_CLUSTERS)
154                 return;
155
156         pwdrn_reg = cluster_is_a15(cluster) ? A15_PWRDN_EN : A7_PWRDN_EN;
157         writel_relaxed(enable, info->baseaddr + pwdrn_reg);
158 }
159
160 int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid)
161 {
162         info = kzalloc(sizeof(*info), GFP_KERNEL);
163         if (!info) {
164                 pr_err(SPCLOG "unable to allocate mem\n");
165                 return -ENOMEM;
166         }
167
168         info->baseaddr = baseaddr;
169         info->a15_clusid = a15_clusid;
170
171         /*
172          * Multi-cluster systems may need this data when non-coherent, during
173          * cluster power-up/power-down. Make sure driver info reaches main
174          * memory.
175          */
176         sync_cache_w(info);
177         sync_cache_w(&info);
178
179         return 0;
180 }