Commit | Line | Data |
---|---|---|
b2b9762f SS |
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 |