aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLina Iyer <lina.iyer@linaro.org>2015-04-09 15:20:41 -0400
committerKumar Gala <galak@codeaurora.org>2015-04-27 17:35:06 -0400
commit7ce75bb2c05ef6949ab0b93633e052f46855690d (patch)
tree81c73c93025ccdf39da87dc9a5eb4d2971482125
parentb787f68c36d49bb1d9236f403813641efa74a031 (diff)
ARM: qcom: Add Subsystem Power Manager (SPM) driver
SPM is a hardware block that controls the peripheral logic surrounding the application cores (cpu/l$). When the core executes WFI instruction, the SPM takes over the putting the core in low power state as configured. The wake up for the SPM is an interrupt at the GIC, which then completes the rest of low power mode sequence and brings the core out of low power mode. The SPM has a set of control registers that configure the SPMs individually based on the type of the core and the runtime conditions. SPM is a finite state machine block to which a sequence is provided and it interprets the bytes and executes them in sequence. Each low power mode that the core can enter into is provided to the SPM as a sequence. Configure the SPM to set the core (cpu or L2) into its low power mode, the index of the first command in the sequence is set in the SPM_CTL register. When the core executes ARM wfi instruction, it triggers the SPM state machine to start executing from that index. The SPM state machine waits until the interrupt occurs and starts executing the rest of the sequence until it hits the end of the sequence. The end of the sequence jumps the core out of its low power mode. Add support for an idle driver to set up the SPM to place the core in Standby or Standalone power collapse mode when the core is idle. Based on work by: Mahesh Sivasubramanian <msivasub@codeaurora.org>, Ai Li <ali@codeaurora.org>, Praveen Chidambaram <pchidamb@codeaurora.org> Original tree available at - git://codeaurora.org/quic/la/kernel/msm-3.10.git Cc: Stephen Boyd <sboyd@codeaurora.org> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Kevin Hilman <khilman@linaro.org> Cc: Daniel Lezcano <daniel.lezcano@linaro.org> Signed-off-by: Lina Iyer <lina.iyer@linaro.org> Reviewed-by: Stephen Boyd <sboyd@codeaurora.org> Tested-by: Kevin Hilman <khilman@linaro.org> Acked-by: Kumar Gala <galak@codeaurora.org> Acked-by: Kevin Hilman <khilman@linaro.org> Signed-off-by: Kumar Gala <galak@codeaurora.org>
-rw-r--r--drivers/soc/qcom/Kconfig7
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/spm.c385
3 files changed, 393 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 460b2dba109c..5eea374c8fa6 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -10,3 +10,10 @@ config QCOM_GSBI
10 functions for connecting the underlying serial UART, SPI, and I2C 10 functions for connecting the underlying serial UART, SPI, and I2C
11 devices to the output pins. 11 devices to the output pins.
12 12
13config QCOM_PM
14 bool "Qualcomm Power Management"
15 depends on ARCH_QCOM && !ARM64
16 help
17 QCOM Platform specific power driver to manage cores and L2 low power
18 modes. It interface with various system drivers to put the cores in
19 low power modes.
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 438901257ac1..931d385386c5 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1 +1,2 @@
1obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o 1obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
2obj-$(CONFIG_QCOM_PM) += spm.o
diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
new file mode 100644
index 000000000000..b562af816c0a
--- /dev/null
+++ b/drivers/soc/qcom/spm.c
@@ -0,0 +1,385 @@
1/*
2 * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
3 * Copyright (c) 2014,2015, Linaro Ltd.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 and
7 * only version 2 as published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15#include <linux/module.h>
16#include <linux/kernel.h>
17#include <linux/init.h>
18#include <linux/io.h>
19#include <linux/slab.h>
20#include <linux/of.h>
21#include <linux/of_address.h>
22#include <linux/of_device.h>
23#include <linux/err.h>
24#include <linux/platform_device.h>
25#include <linux/cpuidle.h>
26#include <linux/cpu_pm.h>
27#include <linux/qcom_scm.h>
28
29#include <asm/cpuidle.h>
30#include <asm/proc-fns.h>
31#include <asm/suspend.h>
32
33#define MAX_PMIC_DATA 2
34#define MAX_SEQ_DATA 64
35#define SPM_CTL_INDEX 0x7f
36#define SPM_CTL_INDEX_SHIFT 4
37#define SPM_CTL_EN BIT(0)
38
39enum pm_sleep_mode {
40 PM_SLEEP_MODE_STBY,
41 PM_SLEEP_MODE_RET,
42 PM_SLEEP_MODE_SPC,
43 PM_SLEEP_MODE_PC,
44 PM_SLEEP_MODE_NR,
45};
46
47enum spm_reg {
48 SPM_REG_CFG,
49 SPM_REG_SPM_CTL,
50 SPM_REG_DLY,
51 SPM_REG_PMIC_DLY,
52 SPM_REG_PMIC_DATA_0,
53 SPM_REG_PMIC_DATA_1,
54 SPM_REG_VCTL,
55 SPM_REG_SEQ_ENTRY,
56 SPM_REG_SPM_STS,
57 SPM_REG_PMIC_STS,
58 SPM_REG_NR,
59};
60
61struct spm_reg_data {
62 const u8 *reg_offset;
63 u32 spm_cfg;
64 u32 spm_dly;
65 u32 pmic_dly;
66 u32 pmic_data[MAX_PMIC_DATA];
67 u8 seq[MAX_SEQ_DATA];
68 u8 start_index[PM_SLEEP_MODE_NR];
69};
70
71struct spm_driver_data {
72 void __iomem *reg_base;
73 const struct spm_reg_data *reg_data;
74};
75
76static const u8 spm_reg_offset_v2_1[SPM_REG_NR] = {
77 [SPM_REG_CFG] = 0x08,
78 [SPM_REG_SPM_CTL] = 0x30,
79 [SPM_REG_DLY] = 0x34,
80 [SPM_REG_SEQ_ENTRY] = 0x80,
81};
82
83/* SPM register data for 8974, 8084 */
84static const struct spm_reg_data spm_reg_8974_8084_cpu = {
85 .reg_offset = spm_reg_offset_v2_1,
86 .spm_cfg = 0x1,
87 .spm_dly = 0x3C102800,
88 .seq = { 0x03, 0x0B, 0x0F, 0x00, 0x20, 0x80, 0x10, 0xE8, 0x5B, 0x03,
89 0x3B, 0xE8, 0x5B, 0x82, 0x10, 0x0B, 0x30, 0x06, 0x26, 0x30,
90 0x0F },
91 .start_index[PM_SLEEP_MODE_STBY] = 0,
92 .start_index[PM_SLEEP_MODE_SPC] = 3,
93};
94
95static const u8 spm_reg_offset_v1_1[SPM_REG_NR] = {
96 [SPM_REG_CFG] = 0x08,
97 [SPM_REG_SPM_CTL] = 0x20,
98 [SPM_REG_PMIC_DLY] = 0x24,
99 [SPM_REG_PMIC_DATA_0] = 0x28,
100 [SPM_REG_PMIC_DATA_1] = 0x2C,
101 [SPM_REG_SEQ_ENTRY] = 0x80,
102};
103
104/* SPM register data for 8064 */
105static const struct spm_reg_data spm_reg_8064_cpu = {
106 .reg_offset = spm_reg_offset_v1_1,
107 .spm_cfg = 0x1F,
108 .pmic_dly = 0x02020004,
109 .pmic_data[0] = 0x0084009C,
110 .pmic_data[1] = 0x00A4001C,
111 .seq = { 0x03, 0x0F, 0x00, 0x24, 0x54, 0x10, 0x09, 0x03, 0x01,
112 0x10, 0x54, 0x30, 0x0C, 0x24, 0x30, 0x0F },
113 .start_index[PM_SLEEP_MODE_STBY] = 0,
114 .start_index[PM_SLEEP_MODE_SPC] = 2,
115};
116
117static DEFINE_PER_CPU(struct spm_driver_data *, cpu_spm_drv);
118
119typedef int (*idle_fn)(int);
120static DEFINE_PER_CPU(idle_fn*, qcom_idle_ops);
121
122static inline void spm_register_write(struct spm_driver_data *drv,
123 enum spm_reg reg, u32 val)
124{
125 if (drv->reg_data->reg_offset[reg])
126 writel_relaxed(val, drv->reg_base +
127 drv->reg_data->reg_offset[reg]);
128}
129
130/* Ensure a guaranteed write, before return */
131static inline void spm_register_write_sync(struct spm_driver_data *drv,
132 enum spm_reg reg, u32 val)
133{
134 u32 ret;
135
136 if (!drv->reg_data->reg_offset[reg])
137 return;
138
139 do {
140 writel_relaxed(val, drv->reg_base +
141 drv->reg_data->reg_offset[reg]);
142 ret = readl_relaxed(drv->reg_base +
143 drv->reg_data->reg_offset[reg]);
144 if (ret == val)
145 break;
146 cpu_relax();
147 } while (1);
148}
149
150static inline u32 spm_register_read(struct spm_driver_data *drv,
151 enum spm_reg reg)
152{
153 return readl_relaxed(drv->reg_base + drv->reg_data->reg_offset[reg]);
154}
155
156static void spm_set_low_power_mode(struct spm_driver_data *drv,
157 enum pm_sleep_mode mode)
158{
159 u32 start_index;
160 u32 ctl_val;
161
162 start_index = drv->reg_data->start_index[mode];
163
164 ctl_val = spm_register_read(drv, SPM_REG_SPM_CTL);
165 ctl_val &= ~(SPM_CTL_INDEX << SPM_CTL_INDEX_SHIFT);
166 ctl_val |= start_index << SPM_CTL_INDEX_SHIFT;
167 ctl_val |= SPM_CTL_EN;
168 spm_register_write_sync(drv, SPM_REG_SPM_CTL, ctl_val);
169}
170
171static int qcom_pm_collapse(unsigned long int unused)
172{
173 qcom_scm_cpu_power_down(QCOM_SCM_CPU_PWR_DOWN_L2_ON);
174
175 /*
176 * Returns here only if there was a pending interrupt and we did not
177 * power down as a result.
178 */
179 return -1;
180}
181
182static int qcom_cpu_spc(int cpu)
183{
184 int ret;
185 struct spm_driver_data *drv = per_cpu(cpu_spm_drv, cpu);
186
187 spm_set_low_power_mode(drv, PM_SLEEP_MODE_SPC);
188 ret = cpu_suspend(0, qcom_pm_collapse);
189 /*
190 * ARM common code executes WFI without calling into our driver and
191 * if the SPM mode is not reset, then we may accidently power down the
192 * cpu when we intended only to gate the cpu clock.
193 * Ensure the state is set to standby before returning.
194 */
195 spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY);
196
197 return ret;
198}
199
200static int qcom_idle_enter(int cpu, unsigned long index)
201{
202 return per_cpu(qcom_idle_ops, cpu)[index](cpu);
203}
204
205static const struct of_device_id qcom_idle_state_match[] __initconst = {
206 { .compatible = "qcom,idle-state-spc", .data = qcom_cpu_spc },
207 { },
208};
209
210static int __init qcom_cpuidle_init(struct device_node *cpu_node, int cpu)
211{
212 const struct of_device_id *match_id;
213 struct device_node *state_node;
214 int i;
215 int state_count = 1;
216 idle_fn idle_fns[CPUIDLE_STATE_MAX];
217 idle_fn *fns;
218 cpumask_t mask;
219 bool use_scm_power_down = false;
220
221 for (i = 0; ; i++) {
222 state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i);
223 if (!state_node)
224 break;
225
226 if (!of_device_is_available(state_node))
227 continue;
228
229 if (i == CPUIDLE_STATE_MAX) {
230 pr_warn("%s: cpuidle states reached max possible\n",
231 __func__);
232 break;
233 }
234
235 match_id = of_match_node(qcom_idle_state_match, state_node);
236 if (!match_id)
237 return -ENODEV;
238
239 idle_fns[state_count] = match_id->data;
240
241 /* Check if any of the states allow power down */
242 if (match_id->data == qcom_cpu_spc)
243 use_scm_power_down = true;
244
245 state_count++;
246 }
247
248 if (state_count == 1)
249 goto check_spm;
250
251 fns = devm_kcalloc(get_cpu_device(cpu), state_count, sizeof(*fns),
252 GFP_KERNEL);
253 if (!fns)
254 return -ENOMEM;
255
256 for (i = 1; i < state_count; i++)
257 fns[i] = idle_fns[i];
258
259 if (use_scm_power_down) {
260 /* We have atleast one power down mode */
261 cpumask_clear(&mask);
262 cpumask_set_cpu(cpu, &mask);
263 qcom_scm_set_warm_boot_addr(cpu_resume, &mask);
264 }
265
266 per_cpu(qcom_idle_ops, cpu) = fns;
267
268 /*
269 * SPM probe for the cpu should have happened by now, if the
270 * SPM device does not exist, return -ENXIO to indicate that the
271 * cpu does not support idle states.
272 */
273check_spm:
274 return per_cpu(cpu_spm_drv, cpu) ? 0 : -ENXIO;
275}
276
277static struct cpuidle_ops qcom_cpuidle_ops __initdata = {
278 .suspend = qcom_idle_enter,
279 .init = qcom_cpuidle_init,
280};
281
282CPUIDLE_METHOD_OF_DECLARE(qcom_idle_v1, "qcom,kpss-acc-v1", &qcom_cpuidle_ops);
283CPUIDLE_METHOD_OF_DECLARE(qcom_idle_v2, "qcom,kpss-acc-v2", &qcom_cpuidle_ops);
284
285static struct spm_driver_data *spm_get_drv(struct platform_device *pdev,
286 int *spm_cpu)
287{
288 struct spm_driver_data *drv = NULL;
289 struct device_node *cpu_node, *saw_node;
290 int cpu;
291 bool found;
292
293 for_each_possible_cpu(cpu) {
294 cpu_node = of_cpu_device_node_get(cpu);
295 if (!cpu_node)
296 continue;
297 saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0);
298 found = (saw_node == pdev->dev.of_node);
299 of_node_put(saw_node);
300 of_node_put(cpu_node);
301 if (found)
302 break;
303 }
304
305 if (found) {
306 drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
307 if (drv)
308 *spm_cpu = cpu;
309 }
310
311 return drv;
312}
313
314static const struct of_device_id spm_match_table[] = {
315 { .compatible = "qcom,msm8974-saw2-v2.1-cpu",
316 .data = &spm_reg_8974_8084_cpu },
317 { .compatible = "qcom,apq8084-saw2-v2.1-cpu",
318 .data = &spm_reg_8974_8084_cpu },
319 { .compatible = "qcom,apq8064-saw2-v1.1-cpu",
320 .data = &spm_reg_8064_cpu },
321 { },
322};
323
324static int spm_dev_probe(struct platform_device *pdev)
325{
326 struct spm_driver_data *drv;
327 struct resource *res;
328 const struct of_device_id *match_id;
329 void __iomem *addr;
330 int cpu;
331
332 drv = spm_get_drv(pdev, &cpu);
333 if (!drv)
334 return -EINVAL;
335
336 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
337 drv->reg_base = devm_ioremap_resource(&pdev->dev, res);
338 if (IS_ERR(drv->reg_base))
339 return PTR_ERR(drv->reg_base);
340
341 match_id = of_match_node(spm_match_table, pdev->dev.of_node);
342 if (!match_id)
343 return -ENODEV;
344
345 drv->reg_data = match_id->data;
346
347 /* Write the SPM sequences first.. */
348 addr = drv->reg_base + drv->reg_data->reg_offset[SPM_REG_SEQ_ENTRY];
349 __iowrite32_copy(addr, drv->reg_data->seq,
350 ARRAY_SIZE(drv->reg_data->seq) / 4);
351
352 /*
353 * ..and then the control registers.
354 * On some SoC if the control registers are written first and if the
355 * CPU was held in reset, the reset signal could trigger the SPM state
356 * machine, before the sequences are completely written.
357 */
358 spm_register_write(drv, SPM_REG_CFG, drv->reg_data->spm_cfg);
359 spm_register_write(drv, SPM_REG_DLY, drv->reg_data->spm_dly);
360 spm_register_write(drv, SPM_REG_PMIC_DLY, drv->reg_data->pmic_dly);
361 spm_register_write(drv, SPM_REG_PMIC_DATA_0,
362 drv->reg_data->pmic_data[0]);
363 spm_register_write(drv, SPM_REG_PMIC_DATA_1,
364 drv->reg_data->pmic_data[1]);
365
366 /* Set up Standby as the default low power mode */
367 spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY);
368
369 per_cpu(cpu_spm_drv, cpu) = drv;
370
371 return 0;
372}
373
374static struct platform_driver spm_driver = {
375 .probe = spm_dev_probe,
376 .driver = {
377 .name = "saw",
378 .of_match_table = spm_match_table,
379 },
380};
381module_platform_driver(spm_driver);
382
383MODULE_LICENSE("GPL v2");
384MODULE_DESCRIPTION("SAW power controller driver");
385MODULE_ALIAS("platform:saw");