diff options
Diffstat (limited to 'arch/arm/mach-omap2/omap-mpuss-lowpower.c')
-rw-r--r-- | arch/arm/mach-omap2/omap-mpuss-lowpower.c | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/arch/arm/mach-omap2/omap-mpuss-lowpower.c b/arch/arm/mach-omap2/omap-mpuss-lowpower.c new file mode 100644 index 000000000000..867fee51e42c --- /dev/null +++ b/arch/arm/mach-omap2/omap-mpuss-lowpower.c | |||
@@ -0,0 +1,248 @@ | |||
1 | /* | ||
2 | * OMAP MPUSS low power code | ||
3 | * | ||
4 | * Copyright (C) 2011 Texas Instruments, Inc. | ||
5 | * Santosh Shilimkar <santosh.shilimkar@ti.com> | ||
6 | * | ||
7 | * OMAP4430 MPUSS mainly consists of dual Cortex-A9 with per-CPU | ||
8 | * Local timer and Watchdog, GIC, SCU, PL310 L2 cache controller, | ||
9 | * CPU0 and CPU1 LPRM modules. | ||
10 | * CPU0, CPU1 and MPUSS each have there own power domain and | ||
11 | * hence multiple low power combinations of MPUSS are possible. | ||
12 | * | ||
13 | * The CPU0 and CPU1 can't support Closed switch Retention (CSWR) | ||
14 | * because the mode is not supported by hw constraints of dormant | ||
15 | * mode. While waking up from the dormant mode, a reset signal | ||
16 | * to the Cortex-A9 processor must be asserted by the external | ||
17 | * power controller. | ||
18 | * | ||
19 | * With architectural inputs and hardware recommendations, only | ||
20 | * below modes are supported from power gain vs latency point of view. | ||
21 | * | ||
22 | * CPU0 CPU1 MPUSS | ||
23 | * ---------------------------------------------- | ||
24 | * ON ON ON | ||
25 | * ON(Inactive) OFF ON(Inactive) | ||
26 | * OFF OFF CSWR | ||
27 | * OFF OFF OSWR (*TBD) | ||
28 | * OFF OFF OFF* (*TBD) | ||
29 | * ---------------------------------------------- | ||
30 | * | ||
31 | * Note: CPU0 is the master core and it is the last CPU to go down | ||
32 | * and first to wake-up when MPUSS low power states are excercised | ||
33 | * | ||
34 | * | ||
35 | * This program is free software; you can redistribute it and/or modify | ||
36 | * it under the terms of the GNU General Public License version 2 as | ||
37 | * published by the Free Software Foundation. | ||
38 | */ | ||
39 | |||
40 | #include <linux/kernel.h> | ||
41 | #include <linux/io.h> | ||
42 | #include <linux/errno.h> | ||
43 | #include <linux/linkage.h> | ||
44 | #include <linux/smp.h> | ||
45 | |||
46 | #include <asm/cacheflush.h> | ||
47 | #include <asm/tlbflush.h> | ||
48 | #include <asm/smp_scu.h> | ||
49 | #include <asm/system.h> | ||
50 | #include <asm/pgalloc.h> | ||
51 | #include <asm/suspend.h> | ||
52 | |||
53 | #include <plat/omap44xx.h> | ||
54 | |||
55 | #include "common.h" | ||
56 | #include "omap4-sar-layout.h" | ||
57 | #include "pm.h" | ||
58 | #include "powerdomain.h" | ||
59 | |||
60 | #ifdef CONFIG_SMP | ||
61 | |||
62 | struct omap4_cpu_pm_info { | ||
63 | struct powerdomain *pwrdm; | ||
64 | void __iomem *scu_sar_addr; | ||
65 | void __iomem *wkup_sar_addr; | ||
66 | }; | ||
67 | |||
68 | static DEFINE_PER_CPU(struct omap4_cpu_pm_info, omap4_pm_info); | ||
69 | |||
70 | /* | ||
71 | * Program the wakeup routine address for the CPU0 and CPU1 | ||
72 | * used for OFF or DORMANT wakeup. | ||
73 | */ | ||
74 | static inline void set_cpu_wakeup_addr(unsigned int cpu_id, u32 addr) | ||
75 | { | ||
76 | struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); | ||
77 | |||
78 | __raw_writel(addr, pm_info->wkup_sar_addr); | ||
79 | } | ||
80 | |||
81 | /* | ||
82 | * Set the CPUx powerdomain's previous power state | ||
83 | */ | ||
84 | static inline void set_cpu_next_pwrst(unsigned int cpu_id, | ||
85 | unsigned int power_state) | ||
86 | { | ||
87 | struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); | ||
88 | |||
89 | pwrdm_set_next_pwrst(pm_info->pwrdm, power_state); | ||
90 | } | ||
91 | |||
92 | /* | ||
93 | * Read CPU's previous power state | ||
94 | */ | ||
95 | static inline unsigned int read_cpu_prev_pwrst(unsigned int cpu_id) | ||
96 | { | ||
97 | struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); | ||
98 | |||
99 | return pwrdm_read_prev_pwrst(pm_info->pwrdm); | ||
100 | } | ||
101 | |||
102 | /* | ||
103 | * Clear the CPUx powerdomain's previous power state | ||
104 | */ | ||
105 | static inline void clear_cpu_prev_pwrst(unsigned int cpu_id) | ||
106 | { | ||
107 | struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); | ||
108 | |||
109 | pwrdm_clear_all_prev_pwrst(pm_info->pwrdm); | ||
110 | } | ||
111 | |||
112 | /* | ||
113 | * Store the SCU power status value to scratchpad memory | ||
114 | */ | ||
115 | static void scu_pwrst_prepare(unsigned int cpu_id, unsigned int cpu_state) | ||
116 | { | ||
117 | struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); | ||
118 | u32 scu_pwr_st; | ||
119 | |||
120 | switch (cpu_state) { | ||
121 | case PWRDM_POWER_RET: | ||
122 | scu_pwr_st = SCU_PM_DORMANT; | ||
123 | break; | ||
124 | case PWRDM_POWER_OFF: | ||
125 | scu_pwr_st = SCU_PM_POWEROFF; | ||
126 | break; | ||
127 | case PWRDM_POWER_ON: | ||
128 | case PWRDM_POWER_INACTIVE: | ||
129 | default: | ||
130 | scu_pwr_st = SCU_PM_NORMAL; | ||
131 | break; | ||
132 | } | ||
133 | |||
134 | __raw_writel(scu_pwr_st, pm_info->scu_sar_addr); | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * omap4_enter_lowpower: OMAP4 MPUSS Low Power Entry Function | ||
139 | * The purpose of this function is to manage low power programming | ||
140 | * of OMAP4 MPUSS subsystem | ||
141 | * @cpu : CPU ID | ||
142 | * @power_state: Low power state. | ||
143 | */ | ||
144 | int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state) | ||
145 | { | ||
146 | unsigned int save_state = 0; | ||
147 | unsigned int wakeup_cpu; | ||
148 | |||
149 | if (omap_rev() == OMAP4430_REV_ES1_0) | ||
150 | return -ENXIO; | ||
151 | |||
152 | switch (power_state) { | ||
153 | case PWRDM_POWER_ON: | ||
154 | case PWRDM_POWER_INACTIVE: | ||
155 | save_state = 0; | ||
156 | break; | ||
157 | case PWRDM_POWER_OFF: | ||
158 | save_state = 1; | ||
159 | break; | ||
160 | case PWRDM_POWER_RET: | ||
161 | default: | ||
162 | /* | ||
163 | * CPUx CSWR is invalid hardware state. Also CPUx OSWR | ||
164 | * doesn't make much scense, since logic is lost and $L1 | ||
165 | * needs to be cleaned because of coherency. This makes | ||
166 | * CPUx OSWR equivalent to CPUX OFF and hence not supported | ||
167 | */ | ||
168 | WARN_ON(1); | ||
169 | return -ENXIO; | ||
170 | } | ||
171 | |||
172 | clear_cpu_prev_pwrst(cpu); | ||
173 | set_cpu_next_pwrst(cpu, power_state); | ||
174 | set_cpu_wakeup_addr(cpu, virt_to_phys(omap4_cpu_resume)); | ||
175 | scu_pwrst_prepare(cpu, power_state); | ||
176 | |||
177 | /* | ||
178 | * Call low level function with targeted low power state. | ||
179 | */ | ||
180 | cpu_suspend(save_state, omap4_finish_suspend); | ||
181 | |||
182 | /* | ||
183 | * Restore the CPUx power state to ON otherwise CPUx | ||
184 | * power domain can transitions to programmed low power | ||
185 | * state while doing WFI outside the low powe code. On | ||
186 | * secure devices, CPUx does WFI which can result in | ||
187 | * domain transition | ||
188 | */ | ||
189 | wakeup_cpu = smp_processor_id(); | ||
190 | set_cpu_next_pwrst(wakeup_cpu, PWRDM_POWER_ON); | ||
191 | |||
192 | return 0; | ||
193 | } | ||
194 | |||
195 | /* | ||
196 | * Initialise OMAP4 MPUSS | ||
197 | */ | ||
198 | int __init omap4_mpuss_init(void) | ||
199 | { | ||
200 | struct omap4_cpu_pm_info *pm_info; | ||
201 | void __iomem *sar_base = omap4_get_sar_ram_base(); | ||
202 | |||
203 | if (omap_rev() == OMAP4430_REV_ES1_0) { | ||
204 | WARN(1, "Power Management not supported on OMAP4430 ES1.0\n"); | ||
205 | return -ENODEV; | ||
206 | } | ||
207 | |||
208 | /* Initilaise per CPU PM information */ | ||
209 | pm_info = &per_cpu(omap4_pm_info, 0x0); | ||
210 | pm_info->scu_sar_addr = sar_base + SCU_OFFSET0; | ||
211 | pm_info->wkup_sar_addr = sar_base + CPU0_WAKEUP_NS_PA_ADDR_OFFSET; | ||
212 | pm_info->pwrdm = pwrdm_lookup("cpu0_pwrdm"); | ||
213 | if (!pm_info->pwrdm) { | ||
214 | pr_err("Lookup failed for CPU0 pwrdm\n"); | ||
215 | return -ENODEV; | ||
216 | } | ||
217 | |||
218 | /* Clear CPU previous power domain state */ | ||
219 | pwrdm_clear_all_prev_pwrst(pm_info->pwrdm); | ||
220 | |||
221 | /* Initialise CPU0 power domain state to ON */ | ||
222 | pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON); | ||
223 | |||
224 | pm_info = &per_cpu(omap4_pm_info, 0x1); | ||
225 | pm_info->scu_sar_addr = sar_base + SCU_OFFSET1; | ||
226 | pm_info->wkup_sar_addr = sar_base + CPU1_WAKEUP_NS_PA_ADDR_OFFSET; | ||
227 | pm_info->pwrdm = pwrdm_lookup("cpu1_pwrdm"); | ||
228 | if (!pm_info->pwrdm) { | ||
229 | pr_err("Lookup failed for CPU1 pwrdm\n"); | ||
230 | return -ENODEV; | ||
231 | } | ||
232 | |||
233 | /* Clear CPU previous power domain state */ | ||
234 | pwrdm_clear_all_prev_pwrst(pm_info->pwrdm); | ||
235 | |||
236 | /* Initialise CPU1 power domain state to ON */ | ||
237 | pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON); | ||
238 | |||
239 | /* Save device type on scratchpad for low level code to use */ | ||
240 | if (omap_type() != OMAP2_DEVICE_TYPE_GP) | ||
241 | __raw_writel(1, sar_base + OMAP_TYPE_OFFSET); | ||
242 | else | ||
243 | __raw_writel(0, sar_base + OMAP_TYPE_OFFSET); | ||
244 | |||
245 | return 0; | ||
246 | } | ||
247 | |||
248 | #endif | ||