msm: kgsl: suspend device when the display does off
This change saves leakage current when the display is
off. In scenerios where a user is listening to an mp3
this feature stops the GPU as soon as the display goes
off. The GPU is started again when the display comes
back. The feature also avoids the GPU resuming when a
email sync happens while the device is suspended.
The change also disables NAP when the display is off.
It wakes 3d core to process user space requests when in
slumber
Change-Id: I65d17e937079a27b14d08be0a975d7ecf80b18ab
CRs-fixed: 316579
Signed-off-by: Suman Tatiraju <sumant@codeaurora.org>
diff --git a/drivers/gpu/msm/kgsl_pwrctrl.c b/drivers/gpu/msm/kgsl_pwrctrl.c
index 343a39a..2aff1ff 100644
--- a/drivers/gpu/msm/kgsl_pwrctrl.c
+++ b/drivers/gpu/msm/kgsl_pwrctrl.c
@@ -583,7 +583,8 @@
mutex_lock(&device->mutex);
if (device->state & (KGSL_STATE_ACTIVE | KGSL_STATE_NAP)) {
- if (device->requested_state != KGSL_STATE_SLEEP)
+ if ((device->requested_state != KGSL_STATE_SLEEP) &&
+ (device->requested_state != KGSL_STATE_SLUMBER))
kgsl_pwrscale_idle(device);
if (kgsl_pwrctrl_sleep(device) != 0) {
@@ -621,7 +622,8 @@
void kgsl_pre_hwaccess(struct kgsl_device *device)
{
BUG_ON(!mutex_is_locked(&device->mutex));
- if (device->state & (KGSL_STATE_SLEEP | KGSL_STATE_NAP))
+ if (device->state & (KGSL_STATE_SLEEP | KGSL_STATE_NAP |
+ KGSL_STATE_SLUMBER))
kgsl_pwrctrl_wake(device);
}
EXPORT_SYMBOL(kgsl_pre_hwaccess);
@@ -633,14 +635,46 @@
mutex_unlock(&device->mutex);
wait_for_completion(&device->hwaccess_gate);
mutex_lock(&device->mutex);
- }
- if (device->state == KGSL_STATE_DUMP_AND_RECOVER) {
+ } else if (device->state == KGSL_STATE_DUMP_AND_RECOVER) {
mutex_unlock(&device->mutex);
wait_for_completion(&device->recovery_gate);
mutex_lock(&device->mutex);
- }
- }
+ } else if (device->state == KGSL_STATE_SLUMBER)
+ kgsl_pwrctrl_wake(device);
+}
+static int
+_slumber(struct kgsl_device *device)
+{
+ int status = -EINVAL;
+ if (!device)
+ return -EINVAL;
+ KGSL_PWR_WARN(device, "Slumber start\n");
+
+ device->requested_state = KGSL_STATE_SLUMBER;
+ del_timer(&device->idle_timer);
+ switch (device->state) {
+ case KGSL_STATE_ACTIVE:
+ /* Wait for the device to become idle */
+ device->ftbl->idle(device, KGSL_TIMEOUT_DEFAULT);
+ case KGSL_STATE_NAP:
+ case KGSL_STATE_SLEEP:
+ device->ftbl->suspend_context(device);
+ device->ftbl->stop(device);
+ device->state = KGSL_STATE_SLUMBER;
+ device->pwrctrl.restore_slumber = 1;
+ KGSL_PWR_WARN(device, "state -> SLUMBER, device %d\n",
+ device->id);
+ break;
+ default:
+ break;
+ }
+ status = 0;
+ /* Don't set requested state to NONE
+ It's done in kgsl_pwrctrl_sleep*/
+ KGSL_PWR_WARN(device, "Done going to slumber\n");
+ return status;
+}
/******************************************************************/
/* Caller must hold the device mutex. */
@@ -650,18 +684,32 @@
KGSL_PWR_INFO(device, "sleep device %d\n", device->id);
/* Work through the legal state transitions */
- if (device->requested_state == KGSL_STATE_NAP) {
- if (device->ftbl->isidle(device))
+ if ((device->requested_state == KGSL_STATE_NAP)) {
+ if (device->pwrctrl.restore_slumber) {
+ device->requested_state = KGSL_STATE_NONE;
+ return 0;
+ } else if (device->ftbl->isidle(device))
goto nap;
} else if (device->requested_state == KGSL_STATE_SLEEP) {
if (device->state == KGSL_STATE_NAP ||
- device->ftbl->isidle(device))
- goto sleep;
+ device->ftbl->isidle(device)) {
+ if (!device->pwrctrl.restore_slumber)
+ goto sleep;
+ else
+ goto slumber;
+ }
+ } else if (device->requested_state == KGSL_STATE_SLUMBER) {
+ if (device->ftbl->isidle(device))
+ goto slumber;
}
device->requested_state = KGSL_STATE_NONE;
return -EBUSY;
+
+slumber:
+ _slumber(device);
+
sleep:
kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF);
kgsl_pwrctrl_axi(device, KGSL_PWRFLAGS_OFF);
@@ -693,6 +741,24 @@
}
EXPORT_SYMBOL(kgsl_pwrctrl_sleep);
+static int
+_wake_from_slumber(struct kgsl_device *device)
+{
+ int status = -EINVAL;
+ if (!device)
+ return -EINVAL;
+
+ KGSL_PWR_WARN(device, "wake from slumber start\n");
+
+ device->requested_state = KGSL_STATE_ACTIVE;
+ kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_NOMINAL);
+ status = device->ftbl->start(device, 0);
+ device->requested_state = KGSL_STATE_NONE;
+
+ KGSL_PWR_WARN(device, "Done waking from slumber\n");
+ return status;
+}
+
/******************************************************************/
/* Caller must hold the device mutex. */
void kgsl_pwrctrl_wake(struct kgsl_device *device)
@@ -700,6 +766,9 @@
if (device->state == KGSL_STATE_SUSPEND)
return;
+ if (device->state == KGSL_STATE_SLUMBER)
+ _wake_from_slumber(device);
+
if (device->state != KGSL_STATE_NAP) {
kgsl_pwrctrl_axi(device, KGSL_PWRFLAGS_ON);
kgsl_pwrscale_wake(device);