diff options
Diffstat (limited to 'arch/arm/mach-omap2/powerdomain.c')
-rw-r--r-- | arch/arm/mach-omap2/powerdomain.c | 232 |
1 files changed, 180 insertions, 52 deletions
diff --git a/arch/arm/mach-omap2/powerdomain.c b/arch/arm/mach-omap2/powerdomain.c index 36a69189b083..43ac20c15f4f 100644 --- a/arch/arm/mach-omap2/powerdomain.c +++ b/arch/arm/mach-omap2/powerdomain.c | |||
@@ -19,6 +19,7 @@ | |||
19 | #include <linux/list.h> | 19 | #include <linux/list.h> |
20 | #include <linux/errno.h> | 20 | #include <linux/errno.h> |
21 | #include <linux/string.h> | 21 | #include <linux/string.h> |
22 | #include <linux/spinlock.h> | ||
22 | #include <trace/events/power.h> | 23 | #include <trace/events/power.h> |
23 | 24 | ||
24 | #include "cm2xxx_3xxx.h" | 25 | #include "cm2xxx_3xxx.h" |
@@ -42,6 +43,16 @@ enum { | |||
42 | PWRDM_STATE_PREV, | 43 | PWRDM_STATE_PREV, |
43 | }; | 44 | }; |
44 | 45 | ||
46 | /* | ||
47 | * Types of sleep_switch used internally in omap_set_pwrdm_state() | ||
48 | * and its associated static functions | ||
49 | * | ||
50 | * XXX Better documentation is needed here | ||
51 | */ | ||
52 | #define ALREADYACTIVE_SWITCH 0 | ||
53 | #define FORCEWAKEUP_SWITCH 1 | ||
54 | #define LOWPOWERSTATE_SWITCH 2 | ||
55 | #define ERROR_SWITCH 3 | ||
45 | 56 | ||
46 | /* pwrdm_list contains all registered struct powerdomains */ | 57 | /* pwrdm_list contains all registered struct powerdomains */ |
47 | static LIST_HEAD(pwrdm_list); | 58 | static LIST_HEAD(pwrdm_list); |
@@ -101,6 +112,7 @@ static int _pwrdm_register(struct powerdomain *pwrdm) | |||
101 | pwrdm->voltdm.ptr = voltdm; | 112 | pwrdm->voltdm.ptr = voltdm; |
102 | INIT_LIST_HEAD(&pwrdm->voltdm_node); | 113 | INIT_LIST_HEAD(&pwrdm->voltdm_node); |
103 | voltdm_add_pwrdm(voltdm, pwrdm); | 114 | voltdm_add_pwrdm(voltdm, pwrdm); |
115 | spin_lock_init(&pwrdm->_lock); | ||
104 | 116 | ||
105 | list_add(&pwrdm->node, &pwrdm_list); | 117 | list_add(&pwrdm->node, &pwrdm_list); |
106 | 118 | ||
@@ -112,7 +124,7 @@ static int _pwrdm_register(struct powerdomain *pwrdm) | |||
112 | for (i = 0; i < pwrdm->banks; i++) | 124 | for (i = 0; i < pwrdm->banks; i++) |
113 | pwrdm->ret_mem_off_counter[i] = 0; | 125 | pwrdm->ret_mem_off_counter[i] = 0; |
114 | 126 | ||
115 | pwrdm_wait_transition(pwrdm); | 127 | arch_pwrdm->pwrdm_wait_transition(pwrdm); |
116 | pwrdm->state = pwrdm_read_pwrst(pwrdm); | 128 | pwrdm->state = pwrdm_read_pwrst(pwrdm); |
117 | pwrdm->state_counter[pwrdm->state] = 1; | 129 | pwrdm->state_counter[pwrdm->state] = 1; |
118 | 130 | ||
@@ -143,7 +155,7 @@ static void _update_logic_membank_counters(struct powerdomain *pwrdm) | |||
143 | static int _pwrdm_state_switch(struct powerdomain *pwrdm, int flag) | 155 | static int _pwrdm_state_switch(struct powerdomain *pwrdm, int flag) |
144 | { | 156 | { |
145 | 157 | ||
146 | int prev, state, trace_state = 0; | 158 | int prev, next, state, trace_state = 0; |
147 | 159 | ||
148 | if (pwrdm == NULL) | 160 | if (pwrdm == NULL) |
149 | return -EINVAL; | 161 | return -EINVAL; |
@@ -164,9 +176,10 @@ static int _pwrdm_state_switch(struct powerdomain *pwrdm, int flag) | |||
164 | * If the power domain did not hit the desired state, | 176 | * If the power domain did not hit the desired state, |
165 | * generate a trace event with both the desired and hit states | 177 | * generate a trace event with both the desired and hit states |
166 | */ | 178 | */ |
167 | if (state != prev) { | 179 | next = pwrdm_read_next_pwrst(pwrdm); |
180 | if (next != prev) { | ||
168 | trace_state = (PWRDM_TRACE_STATES_FLAG | | 181 | trace_state = (PWRDM_TRACE_STATES_FLAG | |
169 | ((state & OMAP_POWERSTATE_MASK) << 8) | | 182 | ((next & OMAP_POWERSTATE_MASK) << 8) | |
170 | ((prev & OMAP_POWERSTATE_MASK) << 0)); | 183 | ((prev & OMAP_POWERSTATE_MASK) << 0)); |
171 | trace_power_domain_target(pwrdm->name, trace_state, | 184 | trace_power_domain_target(pwrdm->name, trace_state, |
172 | smp_processor_id()); | 185 | smp_processor_id()); |
@@ -199,6 +212,80 @@ static int _pwrdm_post_transition_cb(struct powerdomain *pwrdm, void *unused) | |||
199 | return 0; | 212 | return 0; |
200 | } | 213 | } |
201 | 214 | ||
215 | /** | ||
216 | * _pwrdm_save_clkdm_state_and_activate - prepare for power state change | ||
217 | * @pwrdm: struct powerdomain * to operate on | ||
218 | * @curr_pwrst: current power state of @pwrdm | ||
219 | * @pwrst: power state to switch to | ||
220 | * @hwsup: ptr to a bool to return whether the clkdm is hardware-supervised | ||
221 | * | ||
222 | * Determine whether the powerdomain needs to be turned on before | ||
223 | * attempting to switch power states. Called by | ||
224 | * omap_set_pwrdm_state(). NOTE that if the powerdomain contains | ||
225 | * multiple clockdomains, this code assumes that the first clockdomain | ||
226 | * supports software-supervised wakeup mode - potentially a problem. | ||
227 | * Returns the power state switch mode currently in use (see the | ||
228 | * "Types of sleep_switch" comment above). | ||
229 | */ | ||
230 | static u8 _pwrdm_save_clkdm_state_and_activate(struct powerdomain *pwrdm, | ||
231 | u8 curr_pwrst, u8 pwrst, | ||
232 | bool *hwsup) | ||
233 | { | ||
234 | u8 sleep_switch; | ||
235 | |||
236 | if (curr_pwrst < 0) { | ||
237 | WARN_ON(1); | ||
238 | sleep_switch = ERROR_SWITCH; | ||
239 | } else if (curr_pwrst < PWRDM_POWER_ON) { | ||
240 | if (curr_pwrst > pwrst && | ||
241 | pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE && | ||
242 | arch_pwrdm->pwrdm_set_lowpwrstchange) { | ||
243 | sleep_switch = LOWPOWERSTATE_SWITCH; | ||
244 | } else { | ||
245 | *hwsup = clkdm_in_hwsup(pwrdm->pwrdm_clkdms[0]); | ||
246 | clkdm_wakeup_nolock(pwrdm->pwrdm_clkdms[0]); | ||
247 | sleep_switch = FORCEWAKEUP_SWITCH; | ||
248 | } | ||
249 | } else { | ||
250 | sleep_switch = ALREADYACTIVE_SWITCH; | ||
251 | } | ||
252 | |||
253 | return sleep_switch; | ||
254 | } | ||
255 | |||
256 | /** | ||
257 | * _pwrdm_restore_clkdm_state - restore the clkdm hwsup state after pwrst change | ||
258 | * @pwrdm: struct powerdomain * to operate on | ||
259 | * @sleep_switch: return value from _pwrdm_save_clkdm_state_and_activate() | ||
260 | * @hwsup: should @pwrdm's first clockdomain be set to hardware-supervised mode? | ||
261 | * | ||
262 | * Restore the clockdomain state perturbed by | ||
263 | * _pwrdm_save_clkdm_state_and_activate(), and call the power state | ||
264 | * bookkeeping code. Called by omap_set_pwrdm_state(). NOTE that if | ||
265 | * the powerdomain contains multiple clockdomains, this assumes that | ||
266 | * the first associated clockdomain supports either | ||
267 | * hardware-supervised idle control in the register, or | ||
268 | * software-supervised sleep. No return value. | ||
269 | */ | ||
270 | static void _pwrdm_restore_clkdm_state(struct powerdomain *pwrdm, | ||
271 | u8 sleep_switch, bool hwsup) | ||
272 | { | ||
273 | switch (sleep_switch) { | ||
274 | case FORCEWAKEUP_SWITCH: | ||
275 | if (hwsup) | ||
276 | clkdm_allow_idle_nolock(pwrdm->pwrdm_clkdms[0]); | ||
277 | else | ||
278 | clkdm_sleep_nolock(pwrdm->pwrdm_clkdms[0]); | ||
279 | break; | ||
280 | case LOWPOWERSTATE_SWITCH: | ||
281 | if (pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE && | ||
282 | arch_pwrdm->pwrdm_set_lowpwrstchange) | ||
283 | arch_pwrdm->pwrdm_set_lowpwrstchange(pwrdm); | ||
284 | pwrdm_state_switch_nolock(pwrdm); | ||
285 | break; | ||
286 | } | ||
287 | } | ||
288 | |||
202 | /* Public functions */ | 289 | /* Public functions */ |
203 | 290 | ||
204 | /** | 291 | /** |
@@ -275,6 +362,30 @@ int pwrdm_complete_init(void) | |||
275 | } | 362 | } |
276 | 363 | ||
277 | /** | 364 | /** |
365 | * pwrdm_lock - acquire a Linux spinlock on a powerdomain | ||
366 | * @pwrdm: struct powerdomain * to lock | ||
367 | * | ||
368 | * Acquire the powerdomain spinlock on @pwrdm. No return value. | ||
369 | */ | ||
370 | void pwrdm_lock(struct powerdomain *pwrdm) | ||
371 | __acquires(&pwrdm->_lock) | ||
372 | { | ||
373 | spin_lock_irqsave(&pwrdm->_lock, pwrdm->_lock_flags); | ||
374 | } | ||
375 | |||
376 | /** | ||
377 | * pwrdm_unlock - release a Linux spinlock on a powerdomain | ||
378 | * @pwrdm: struct powerdomain * to unlock | ||
379 | * | ||
380 | * Release the powerdomain spinlock on @pwrdm. No return value. | ||
381 | */ | ||
382 | void pwrdm_unlock(struct powerdomain *pwrdm) | ||
383 | __releases(&pwrdm->_lock) | ||
384 | { | ||
385 | spin_unlock_irqrestore(&pwrdm->_lock, pwrdm->_lock_flags); | ||
386 | } | ||
387 | |||
388 | /** | ||
278 | * pwrdm_lookup - look up a powerdomain by name, return a pointer | 389 | * pwrdm_lookup - look up a powerdomain by name, return a pointer |
279 | * @name: name of powerdomain | 390 | * @name: name of powerdomain |
280 | * | 391 | * |
@@ -920,65 +1031,27 @@ bool pwrdm_has_hdwr_sar(struct powerdomain *pwrdm) | |||
920 | return (pwrdm && pwrdm->flags & PWRDM_HAS_HDWR_SAR) ? 1 : 0; | 1031 | return (pwrdm && pwrdm->flags & PWRDM_HAS_HDWR_SAR) ? 1 : 0; |
921 | } | 1032 | } |
922 | 1033 | ||
923 | /** | 1034 | int pwrdm_state_switch_nolock(struct powerdomain *pwrdm) |
924 | * pwrdm_set_lowpwrstchange - Request a low power state change | ||
925 | * @pwrdm: struct powerdomain * | ||
926 | * | ||
927 | * Allows a powerdomain to transtion to a lower power sleep state | ||
928 | * from an existing sleep state without waking up the powerdomain. | ||
929 | * Returns -EINVAL if the powerdomain pointer is null or if the | ||
930 | * powerdomain does not support LOWPOWERSTATECHANGE, or returns 0 | ||
931 | * upon success. | ||
932 | */ | ||
933 | int pwrdm_set_lowpwrstchange(struct powerdomain *pwrdm) | ||
934 | { | ||
935 | int ret = -EINVAL; | ||
936 | |||
937 | if (!pwrdm) | ||
938 | return -EINVAL; | ||
939 | |||
940 | if (!(pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE)) | ||
941 | return -EINVAL; | ||
942 | |||
943 | pr_debug("powerdomain: %s: setting LOWPOWERSTATECHANGE bit\n", | ||
944 | pwrdm->name); | ||
945 | |||
946 | if (arch_pwrdm && arch_pwrdm->pwrdm_set_lowpwrstchange) | ||
947 | ret = arch_pwrdm->pwrdm_set_lowpwrstchange(pwrdm); | ||
948 | |||
949 | return ret; | ||
950 | } | ||
951 | |||
952 | /** | ||
953 | * pwrdm_wait_transition - wait for powerdomain power transition to finish | ||
954 | * @pwrdm: struct powerdomain * to wait for | ||
955 | * | ||
956 | * If the powerdomain @pwrdm is in the process of a state transition, | ||
957 | * spin until it completes the power transition, or until an iteration | ||
958 | * bailout value is reached. Returns -EINVAL if the powerdomain | ||
959 | * pointer is null, -EAGAIN if the bailout value was reached, or | ||
960 | * returns 0 upon success. | ||
961 | */ | ||
962 | int pwrdm_wait_transition(struct powerdomain *pwrdm) | ||
963 | { | 1035 | { |
964 | int ret = -EINVAL; | 1036 | int ret; |
965 | 1037 | ||
966 | if (!pwrdm) | 1038 | if (!pwrdm || !arch_pwrdm) |
967 | return -EINVAL; | 1039 | return -EINVAL; |
968 | 1040 | ||
969 | if (arch_pwrdm && arch_pwrdm->pwrdm_wait_transition) | 1041 | ret = arch_pwrdm->pwrdm_wait_transition(pwrdm); |
970 | ret = arch_pwrdm->pwrdm_wait_transition(pwrdm); | 1042 | if (!ret) |
1043 | ret = _pwrdm_state_switch(pwrdm, PWRDM_STATE_NOW); | ||
971 | 1044 | ||
972 | return ret; | 1045 | return ret; |
973 | } | 1046 | } |
974 | 1047 | ||
975 | int pwrdm_state_switch(struct powerdomain *pwrdm) | 1048 | int __deprecated pwrdm_state_switch(struct powerdomain *pwrdm) |
976 | { | 1049 | { |
977 | int ret; | 1050 | int ret; |
978 | 1051 | ||
979 | ret = pwrdm_wait_transition(pwrdm); | 1052 | pwrdm_lock(pwrdm); |
980 | if (!ret) | 1053 | ret = pwrdm_state_switch_nolock(pwrdm); |
981 | ret = _pwrdm_state_switch(pwrdm, PWRDM_STATE_NOW); | 1054 | pwrdm_unlock(pwrdm); |
982 | 1055 | ||
983 | return ret; | 1056 | return ret; |
984 | } | 1057 | } |
@@ -1004,6 +1077,61 @@ int pwrdm_post_transition(struct powerdomain *pwrdm) | |||
1004 | } | 1077 | } |
1005 | 1078 | ||
1006 | /** | 1079 | /** |
1080 | * omap_set_pwrdm_state - change a powerdomain's current power state | ||
1081 | * @pwrdm: struct powerdomain * to change the power state of | ||
1082 | * @pwrst: power state to change to | ||
1083 | * | ||
1084 | * Change the current hardware power state of the powerdomain | ||
1085 | * represented by @pwrdm to the power state represented by @pwrst. | ||
1086 | * Returns -EINVAL if @pwrdm is null or invalid or if the | ||
1087 | * powerdomain's current power state could not be read, or returns 0 | ||
1088 | * upon success or if @pwrdm does not support @pwrst or any | ||
1089 | * lower-power state. XXX Should not return 0 if the @pwrdm does not | ||
1090 | * support @pwrst or any lower-power state: this should be an error. | ||
1091 | */ | ||
1092 | int omap_set_pwrdm_state(struct powerdomain *pwrdm, u8 pwrst) | ||
1093 | { | ||
1094 | u8 curr_pwrst, next_pwrst, sleep_switch; | ||
1095 | int ret = 0; | ||
1096 | bool hwsup = false; | ||
1097 | |||
1098 | if (!pwrdm || IS_ERR(pwrdm)) | ||
1099 | return -EINVAL; | ||
1100 | |||
1101 | while (!(pwrdm->pwrsts & (1 << pwrst))) { | ||
1102 | if (pwrst == PWRDM_POWER_OFF) | ||
1103 | return ret; | ||
1104 | pwrst--; | ||
1105 | } | ||
1106 | |||
1107 | pwrdm_lock(pwrdm); | ||
1108 | |||
1109 | curr_pwrst = pwrdm_read_pwrst(pwrdm); | ||
1110 | next_pwrst = pwrdm_read_next_pwrst(pwrdm); | ||
1111 | if (curr_pwrst == pwrst && next_pwrst == pwrst) | ||
1112 | goto osps_out; | ||
1113 | |||
1114 | sleep_switch = _pwrdm_save_clkdm_state_and_activate(pwrdm, curr_pwrst, | ||
1115 | pwrst, &hwsup); | ||
1116 | if (sleep_switch == ERROR_SWITCH) { | ||
1117 | ret = -EINVAL; | ||
1118 | goto osps_out; | ||
1119 | } | ||
1120 | |||
1121 | ret = pwrdm_set_next_pwrst(pwrdm, pwrst); | ||
1122 | if (ret) | ||
1123 | pr_err("%s: unable to set power state of powerdomain: %s\n", | ||
1124 | __func__, pwrdm->name); | ||
1125 | |||
1126 | _pwrdm_restore_clkdm_state(pwrdm, sleep_switch, hwsup); | ||
1127 | |||
1128 | osps_out: | ||
1129 | pwrdm_unlock(pwrdm); | ||
1130 | |||
1131 | return ret; | ||
1132 | } | ||
1133 | |||
1134 | /** | ||
1007 | * pwrdm_get_context_loss_count - get powerdomain's context loss count | 1135 | * pwrdm_get_context_loss_count - get powerdomain's context loss count |
1008 | * @pwrdm: struct powerdomain * to wait for | 1136 | * @pwrdm: struct powerdomain * to wait for |
1009 | * | 1137 | * |