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.c b/drivers/gpu/msm/kgsl.c
index c6fceaa..e84d473 100644
--- a/drivers/gpu/msm/kgsl.c
+++ b/drivers/gpu/msm/kgsl.c
@@ -348,6 +348,12 @@
 			KGSL_PWR_WARN(device, "state -> SUSPEND, device %d\n",
 				device->id);
 			break;
+		case KGSL_STATE_SLUMBER:
+			INIT_COMPLETION(device->hwaccess_gate);
+			device->state = KGSL_STATE_SUSPEND;
+			KGSL_PWR_WARN(device, "state -> SUSPEND, device %d\n",
+				device->id);
+			break;
 		default:
 			KGSL_PWR_ERR(device, "suspend fail, device %d\n",
 					device->id);
@@ -374,28 +380,16 @@
 	KGSL_PWR_WARN(device, "resume start\n");
 	mutex_lock(&device->mutex);
 	if (device->state == KGSL_STATE_SUSPEND) {
-		device->requested_state = KGSL_STATE_ACTIVE;
-		kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_NOMINAL);
-		status = device->ftbl->start(device, 0);
-		if (status == 0) {
-			device->state = KGSL_STATE_ACTIVE;
-			KGSL_PWR_WARN(device,
-					"state -> ACTIVE, device %d\n",
-					device->id);
-		} else {
-			KGSL_PWR_ERR(device,
-					"resume failed, device %d\n",
-					device->id);
-			device->state = KGSL_STATE_INIT;
-			goto end;
-		}
+		device->state = KGSL_STATE_SLUMBER;
+		status = 0;
+		KGSL_PWR_WARN(device,
+				"state -> SLUMBER, device %d\n",
+				device->id);
 		complete_all(&device->hwaccess_gate);
 	}
 	device->requested_state = KGSL_STATE_NONE;
 
-end:
 	mutex_unlock(&device->mutex);
-	kgsl_check_idle(device);
 	KGSL_PWR_WARN(device, "resume end\n");
 	return status;
 }
@@ -436,9 +430,12 @@
 {
 	struct kgsl_device *device = container_of(h,
 					struct kgsl_device, display_off);
+	KGSL_PWR_WARN(device, "early suspend start\n");
 	mutex_lock(&device->mutex);
-	kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_NOMINAL);
+	device->requested_state = KGSL_STATE_SLUMBER;
+	kgsl_pwrctrl_sleep(device);
 	mutex_unlock(&device->mutex);
+	KGSL_PWR_WARN(device, "early suspend end\n");
 }
 EXPORT_SYMBOL(kgsl_early_suspend_driver);
 
@@ -461,9 +458,13 @@
 {
 	struct kgsl_device *device = container_of(h,
 					struct kgsl_device, display_off);
+	KGSL_PWR_WARN(device, "late resume start\n");
 	mutex_lock(&device->mutex);
-	kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_TURBO);
+	kgsl_pwrctrl_wake(device);
+	device->pwrctrl.restore_slumber = 0;
 	mutex_unlock(&device->mutex);
+	kgsl_check_idle(device);
+	KGSL_PWR_WARN(device, "late resume end\n");
 }
 EXPORT_SYMBOL(kgsl_late_resume_driver);
 
diff --git a/drivers/gpu/msm/kgsl_device.h b/drivers/gpu/msm/kgsl_device.h
index e261b84..6ae9258 100644
--- a/drivers/gpu/msm/kgsl_device.h
+++ b/drivers/gpu/msm/kgsl_device.h
@@ -46,6 +46,7 @@
 #define KGSL_STATE_SUSPEND	0x00000010
 #define KGSL_STATE_HUNG		0x00000020
 #define KGSL_STATE_DUMP_AND_RECOVER	0x00000040
+#define KGSL_STATE_SLUMBER	0x00000080
 
 #define KGSL_GRAPHICS_MEMORY_LOW_WATERMARK  0x1000000
 
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);
diff --git a/drivers/gpu/msm/kgsl_pwrctrl.h b/drivers/gpu/msm/kgsl_pwrctrl.h
index 127a19b..17dab76 100644
--- a/drivers/gpu/msm/kgsl_pwrctrl.h
+++ b/drivers/gpu/msm/kgsl_pwrctrl.h
@@ -56,6 +56,7 @@
 	const char *src_clk_name;
 	s64 time;
 	struct kgsl_busy busy;
+	unsigned int restore_slumber;
 };
 
 void kgsl_pwrctrl_clk(struct kgsl_device *device, int state);