Merge commit '31d9adca82ce65e5c99d045b5fd917c702b6fce3' into tmp
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / arch / arm / mach-omap2 / powerdomain.c
index dea62a9aad0751d77796b5c3fbbba513c9ae6f02..43ac20c15f4ff1d08990b4c1b9fa5d9db1137047 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/list.h>
 #include <linux/errno.h>
 #include <linux/string.h>
+#include <linux/spinlock.h>
 #include <trace/events/power.h>
 
 #include "cm2xxx_3xxx.h"
@@ -42,6 +43,16 @@ enum {
        PWRDM_STATE_PREV,
 };
 
+/*
+ * Types of sleep_switch used internally in omap_set_pwrdm_state()
+ * and its associated static functions
+ *
+ * XXX Better documentation is needed here
+ */
+#define ALREADYACTIVE_SWITCH           0
+#define FORCEWAKEUP_SWITCH             1
+#define LOWPOWERSTATE_SWITCH           2
+#define ERROR_SWITCH                   3
 
 /* pwrdm_list contains all registered struct powerdomains */
 static LIST_HEAD(pwrdm_list);
@@ -101,6 +112,7 @@ static int _pwrdm_register(struct powerdomain *pwrdm)
        pwrdm->voltdm.ptr = voltdm;
        INIT_LIST_HEAD(&pwrdm->voltdm_node);
        voltdm_add_pwrdm(voltdm, pwrdm);
+       spin_lock_init(&pwrdm->_lock);
 
        list_add(&pwrdm->node, &pwrdm_list);
 
@@ -112,7 +124,7 @@ static int _pwrdm_register(struct powerdomain *pwrdm)
        for (i = 0; i < pwrdm->banks; i++)
                pwrdm->ret_mem_off_counter[i] = 0;
 
-       pwrdm_wait_transition(pwrdm);
+       arch_pwrdm->pwrdm_wait_transition(pwrdm);
        pwrdm->state = pwrdm_read_pwrst(pwrdm);
        pwrdm->state_counter[pwrdm->state] = 1;
 
@@ -143,7 +155,7 @@ static void _update_logic_membank_counters(struct powerdomain *pwrdm)
 static int _pwrdm_state_switch(struct powerdomain *pwrdm, int flag)
 {
 
-       int prev, state, trace_state = 0;
+       int prev, next, state, trace_state = 0;
 
        if (pwrdm == NULL)
                return -EINVAL;
@@ -164,9 +176,10 @@ static int _pwrdm_state_switch(struct powerdomain *pwrdm, int flag)
                 * If the power domain did not hit the desired state,
                 * generate a trace event with both the desired and hit states
                 */
-               if (state != prev) {
+               next = pwrdm_read_next_pwrst(pwrdm);
+               if (next != prev) {
                        trace_state = (PWRDM_TRACE_STATES_FLAG |
-                                      ((state & OMAP_POWERSTATE_MASK) << 8) |
+                                      ((next & OMAP_POWERSTATE_MASK) << 8) |
                                       ((prev & OMAP_POWERSTATE_MASK) << 0));
                        trace_power_domain_target(pwrdm->name, trace_state,
                                                  smp_processor_id());
@@ -199,6 +212,80 @@ static int _pwrdm_post_transition_cb(struct powerdomain *pwrdm, void *unused)
        return 0;
 }
 
+/**
+ * _pwrdm_save_clkdm_state_and_activate - prepare for power state change
+ * @pwrdm: struct powerdomain * to operate on
+ * @curr_pwrst: current power state of @pwrdm
+ * @pwrst: power state to switch to
+ * @hwsup: ptr to a bool to return whether the clkdm is hardware-supervised
+ *
+ * Determine whether the powerdomain needs to be turned on before
+ * attempting to switch power states.  Called by
+ * omap_set_pwrdm_state().  NOTE that if the powerdomain contains
+ * multiple clockdomains, this code assumes that the first clockdomain
+ * supports software-supervised wakeup mode - potentially a problem.
+ * Returns the power state switch mode currently in use (see the
+ * "Types of sleep_switch" comment above).
+ */
+static u8 _pwrdm_save_clkdm_state_and_activate(struct powerdomain *pwrdm,
+                                              u8 curr_pwrst, u8 pwrst,
+                                              bool *hwsup)
+{
+       u8 sleep_switch;
+
+       if (curr_pwrst < 0) {
+               WARN_ON(1);
+               sleep_switch = ERROR_SWITCH;
+       } else if (curr_pwrst < PWRDM_POWER_ON) {
+               if (curr_pwrst > pwrst &&
+                   pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE &&
+                   arch_pwrdm->pwrdm_set_lowpwrstchange) {
+                       sleep_switch = LOWPOWERSTATE_SWITCH;
+               } else {
+                       *hwsup = clkdm_in_hwsup(pwrdm->pwrdm_clkdms[0]);
+                       clkdm_wakeup_nolock(pwrdm->pwrdm_clkdms[0]);
+                       sleep_switch = FORCEWAKEUP_SWITCH;
+               }
+       } else {
+               sleep_switch = ALREADYACTIVE_SWITCH;
+       }
+
+       return sleep_switch;
+}
+
+/**
+ * _pwrdm_restore_clkdm_state - restore the clkdm hwsup state after pwrst change
+ * @pwrdm: struct powerdomain * to operate on
+ * @sleep_switch: return value from _pwrdm_save_clkdm_state_and_activate()
+ * @hwsup: should @pwrdm's first clockdomain be set to hardware-supervised mode?
+ *
+ * Restore the clockdomain state perturbed by
+ * _pwrdm_save_clkdm_state_and_activate(), and call the power state
+ * bookkeeping code.  Called by omap_set_pwrdm_state().  NOTE that if
+ * the powerdomain contains multiple clockdomains, this assumes that
+ * the first associated clockdomain supports either
+ * hardware-supervised idle control in the register, or
+ * software-supervised sleep.  No return value.
+ */
+static void _pwrdm_restore_clkdm_state(struct powerdomain *pwrdm,
+                                      u8 sleep_switch, bool hwsup)
+{
+       switch (sleep_switch) {
+       case FORCEWAKEUP_SWITCH:
+               if (hwsup)
+                       clkdm_allow_idle_nolock(pwrdm->pwrdm_clkdms[0]);
+               else
+                       clkdm_sleep_nolock(pwrdm->pwrdm_clkdms[0]);
+               break;
+       case LOWPOWERSTATE_SWITCH:
+               if (pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE &&
+                   arch_pwrdm->pwrdm_set_lowpwrstchange)
+                       arch_pwrdm->pwrdm_set_lowpwrstchange(pwrdm);
+               pwrdm_state_switch_nolock(pwrdm);
+               break;
+       }
+}
+
 /* Public functions */
 
 /**
@@ -274,6 +361,30 @@ int pwrdm_complete_init(void)
        return 0;
 }
 
+/**
+ * pwrdm_lock - acquire a Linux spinlock on a powerdomain
+ * @pwrdm: struct powerdomain * to lock
+ *
+ * Acquire the powerdomain spinlock on @pwrdm.  No return value.
+ */
+void pwrdm_lock(struct powerdomain *pwrdm)
+       __acquires(&pwrdm->_lock)
+{
+       spin_lock_irqsave(&pwrdm->_lock, pwrdm->_lock_flags);
+}
+
+/**
+ * pwrdm_unlock - release a Linux spinlock on a powerdomain
+ * @pwrdm: struct powerdomain * to unlock
+ *
+ * Release the powerdomain spinlock on @pwrdm.  No return value.
+ */
+void pwrdm_unlock(struct powerdomain *pwrdm)
+       __releases(&pwrdm->_lock)
+{
+       spin_unlock_irqrestore(&pwrdm->_lock, pwrdm->_lock_flags);
+}
+
 /**
  * pwrdm_lookup - look up a powerdomain by name, return a pointer
  * @name: name of powerdomain
@@ -920,65 +1031,27 @@ bool pwrdm_has_hdwr_sar(struct powerdomain *pwrdm)
        return (pwrdm && pwrdm->flags & PWRDM_HAS_HDWR_SAR) ? 1 : 0;
 }
 
-/**
- * pwrdm_set_lowpwrstchange - Request a low power state change
- * @pwrdm: struct powerdomain *
- *
- * Allows a powerdomain to transtion to a lower power sleep state
- * from an existing sleep state without waking up the powerdomain.
- * Returns -EINVAL if the powerdomain pointer is null or if the
- * powerdomain does not support LOWPOWERSTATECHANGE, or returns 0
- * upon success.
- */
-int pwrdm_set_lowpwrstchange(struct powerdomain *pwrdm)
+int pwrdm_state_switch_nolock(struct powerdomain *pwrdm)
 {
-       int ret = -EINVAL;
-
-       if (!pwrdm)
-               return -EINVAL;
-
-       if (!(pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE))
-               return -EINVAL;
-
-       pr_debug("powerdomain: %s: setting LOWPOWERSTATECHANGE bit\n",
-                pwrdm->name);
-
-       if (arch_pwrdm && arch_pwrdm->pwrdm_set_lowpwrstchange)
-               ret = arch_pwrdm->pwrdm_set_lowpwrstchange(pwrdm);
-
-       return ret;
-}
-
-/**
- * pwrdm_wait_transition - wait for powerdomain power transition to finish
- * @pwrdm: struct powerdomain * to wait for
- *
- * If the powerdomain @pwrdm is in the process of a state transition,
- * spin until it completes the power transition, or until an iteration
- * bailout value is reached. Returns -EINVAL if the powerdomain
- * pointer is null, -EAGAIN if the bailout value was reached, or
- * returns 0 upon success.
- */
-int pwrdm_wait_transition(struct powerdomain *pwrdm)
-{
-       int ret = -EINVAL;
+       int ret;
 
-       if (!pwrdm)
+       if (!pwrdm || !arch_pwrdm)
                return -EINVAL;
 
-       if (arch_pwrdm && arch_pwrdm->pwrdm_wait_transition)
-               ret = arch_pwrdm->pwrdm_wait_transition(pwrdm);
+       ret = arch_pwrdm->pwrdm_wait_transition(pwrdm);
+       if (!ret)
+               ret = _pwrdm_state_switch(pwrdm, PWRDM_STATE_NOW);
 
        return ret;
 }
 
-int pwrdm_state_switch(struct powerdomain *pwrdm)
+int __deprecated pwrdm_state_switch(struct powerdomain *pwrdm)
 {
        int ret;
 
-       ret = pwrdm_wait_transition(pwrdm);
-       if (!ret)
-               ret = _pwrdm_state_switch(pwrdm, PWRDM_STATE_NOW);
+       pwrdm_lock(pwrdm);
+       ret = pwrdm_state_switch_nolock(pwrdm);
+       pwrdm_unlock(pwrdm);
 
        return ret;
 }
@@ -1003,6 +1076,61 @@ int pwrdm_post_transition(struct powerdomain *pwrdm)
        return 0;
 }
 
+/**
+ * omap_set_pwrdm_state - change a powerdomain's current power state
+ * @pwrdm: struct powerdomain * to change the power state of
+ * @pwrst: power state to change to
+ *
+ * Change the current hardware power state of the powerdomain
+ * represented by @pwrdm to the power state represented by @pwrst.
+ * Returns -EINVAL if @pwrdm is null or invalid or if the
+ * powerdomain's current power state could not be read, or returns 0
+ * upon success or if @pwrdm does not support @pwrst or any
+ * lower-power state.  XXX Should not return 0 if the @pwrdm does not
+ * support @pwrst or any lower-power state: this should be an error.
+ */
+int omap_set_pwrdm_state(struct powerdomain *pwrdm, u8 pwrst)
+{
+       u8 curr_pwrst, next_pwrst, sleep_switch;
+       int ret = 0;
+       bool hwsup = false;
+
+       if (!pwrdm || IS_ERR(pwrdm))
+               return -EINVAL;
+
+       while (!(pwrdm->pwrsts & (1 << pwrst))) {
+               if (pwrst == PWRDM_POWER_OFF)
+                       return ret;
+               pwrst--;
+       }
+
+       pwrdm_lock(pwrdm);
+
+       curr_pwrst = pwrdm_read_pwrst(pwrdm);
+       next_pwrst = pwrdm_read_next_pwrst(pwrdm);
+       if (curr_pwrst == pwrst && next_pwrst == pwrst)
+               goto osps_out;
+
+       sleep_switch = _pwrdm_save_clkdm_state_and_activate(pwrdm, curr_pwrst,
+                                                           pwrst, &hwsup);
+       if (sleep_switch == ERROR_SWITCH) {
+               ret = -EINVAL;
+               goto osps_out;
+       }
+
+       ret = pwrdm_set_next_pwrst(pwrdm, pwrst);
+       if (ret)
+               pr_err("%s: unable to set power state of powerdomain: %s\n",
+                      __func__, pwrdm->name);
+
+       _pwrdm_restore_clkdm_state(pwrdm, sleep_switch, hwsup);
+
+osps_out:
+       pwrdm_unlock(pwrdm);
+
+       return ret;
+}
+
 /**
  * pwrdm_get_context_loss_count - get powerdomain's context loss count
  * @pwrdm: struct powerdomain * to wait for
@@ -1054,7 +1182,7 @@ bool pwrdm_can_ever_lose_context(struct powerdomain *pwrdm)
 {
        int i;
 
-       if (IS_ERR_OR_NULL(pwrdm)) {
+       if (!pwrdm) {
                pr_debug("powerdomain: %s: invalid powerdomain pointer\n",
                         __func__);
                return 1;