msm: kgsl: Add idle_stats based pwrscale policy

Add a pwrscale policy to transmit idle statistics
to userspace via the MSM idle_stats_device core.

Signed-off-by: Lucille Sylvester <lsylvest@codeaurora.org>
diff --git a/drivers/gpu/msm/Makefile b/drivers/gpu/msm/Makefile
index 514e4f8..5b36c62 100644
--- a/drivers/gpu/msm/Makefile
+++ b/drivers/gpu/msm/Makefile
@@ -11,6 +11,7 @@
 msm_kgsl_core-$(CONFIG_MSM_KGSL_CFF_DUMP) += kgsl_cffdump.o
 msm_kgsl_core-$(CONFIG_MSM_KGSL_DRM) += kgsl_drm.o
 msm_kgsl_core-$(CONFIG_MSM_SCM) += kgsl_pwrscale_trustzone.o
+msm_kgsl_core-$(CONFIG_MSM_SLEEP_STATS) += kgsl_pwrscale_idlestats.o
 
 msm_adreno-y += \
 	adreno_ringbuffer.o \
diff --git a/drivers/gpu/msm/kgsl_pwrscale.c b/drivers/gpu/msm/kgsl_pwrscale.c
index 37e5d2d..0bf874d 100644
--- a/drivers/gpu/msm/kgsl_pwrscale.c
+++ b/drivers/gpu/msm/kgsl_pwrscale.c
@@ -42,6 +42,9 @@
 #ifdef CONFIG_MSM_SCM
 	&kgsl_pwrscale_policy_tz,
 #endif
+#ifdef CONFIG_MSM_SLEEP_STATS
+	&kgsl_pwrscale_policy_idlestats,
+#endif
 	NULL
 };
 
@@ -268,6 +271,7 @@
 	sysfs_remove_group(&pwrscale->kobj, attr_group);
 	kobject_del(&pwrscale->kobj);
 	kobject_put(&pwrscale->kobj);
+	pwrscale->kobj.state_initialized = 0;
 }
 
 static void _kgsl_pwrscale_detach_policy(struct kgsl_device *device)
diff --git a/drivers/gpu/msm/kgsl_pwrscale.h b/drivers/gpu/msm/kgsl_pwrscale.h
index 2046f78..b4f831e 100644
--- a/drivers/gpu/msm/kgsl_pwrscale.h
+++ b/drivers/gpu/msm/kgsl_pwrscale.h
@@ -53,6 +53,7 @@
 		__ATTR(_name, _mode, _show, _store)
 
 extern struct kgsl_pwrscale_policy kgsl_pwrscale_policy_tz;
+extern struct kgsl_pwrscale_policy kgsl_pwrscale_policy_idlestats;
 
 int kgsl_pwrscale_init(struct kgsl_device *device);
 void kgsl_pwrscale_close(struct kgsl_device *device);
diff --git a/drivers/gpu/msm/kgsl_pwrscale_idlestats.c b/drivers/gpu/msm/kgsl_pwrscale_idlestats.c
new file mode 100644
index 0000000..7b4f2a5
--- /dev/null
+++ b/drivers/gpu/msm/kgsl_pwrscale_idlestats.c
@@ -0,0 +1,134 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <mach/idle_stats_device.h>
+
+#include "kgsl.h"
+#include "kgsl_pwrscale.h"
+#include "kgsl_device.h"
+
+struct idlestats_priv {
+	char name[32];
+	struct msm_idle_stats_device idledev;
+	struct kgsl_device *device;
+	struct msm_idle_pulse pulse;
+};
+
+static void idlestats_get_sample(struct msm_idle_stats_device *idledev,
+	struct msm_idle_pulse *pulse)
+{
+	struct kgsl_power_stats stats;
+	struct idlestats_priv *priv = container_of(idledev,
+		struct idlestats_priv, idledev);
+	struct kgsl_device *device = priv->device;
+	struct kgsl_pwrctrl *pwr = &device->pwrctrl;
+
+	mutex_lock(&device->mutex);
+	/* If the GPU is asleep, don't wake it up - assume that we
+	   are idle */
+
+	if (!(device->state & (KGSL_STATE_SLEEP | KGSL_STATE_NAP))) {
+		device->ftbl->power_stats(device, &stats);
+		pulse->busy_start_time = pwr->time - stats.busy_time;
+		pulse->busy_interval = stats.busy_time;
+	} else {
+		pulse->busy_start_time = pwr->time;
+		pulse->busy_interval = 0;
+	}
+	pulse->wait_interval = 0;
+	mutex_unlock(&device->mutex);
+}
+
+static void idlestats_busy(struct kgsl_device *device,
+			struct kgsl_pwrscale *pwrscale)
+{
+	struct idlestats_priv *priv = pwrscale->priv;
+	if (priv->pulse.busy_start_time != 0)
+		msm_idle_stats_idle_end(&priv->idledev, &priv->pulse);
+	priv->pulse.busy_start_time = ktime_to_us(ktime_get());
+}
+
+static void idlestats_idle(struct kgsl_device *device,
+			struct kgsl_pwrscale *pwrscale)
+{
+	struct kgsl_power_stats stats;
+	struct idlestats_priv *priv = pwrscale->priv;
+
+	/* This is called from within a mutex protected function, so
+	   no additional locking required */
+	device->ftbl->power_stats(device, &stats);
+
+	/* If total_time is zero, then we don't have
+	   any interesting statistics to store */
+	if (stats.total_time == 0) {
+		priv->pulse.busy_start_time = 0;
+		return;
+	}
+
+	priv->pulse.busy_interval   = stats.busy_time;
+	priv->pulse.wait_interval   = 0;
+	msm_idle_stats_idle_start(&priv->idledev);
+}
+
+static int idlestats_init(struct kgsl_device *device,
+		     struct kgsl_pwrscale *pwrscale)
+{
+	struct idlestats_priv *priv;
+	int ret;
+
+	priv = pwrscale->priv = kzalloc(sizeof(struct idlestats_priv),
+		GFP_KERNEL);
+	if (pwrscale->priv == NULL)
+		return -ENOMEM;
+
+	snprintf(priv->name, sizeof(priv->name), "idle_stats_%s",
+		 device->name);
+
+	priv->device = device;
+
+	priv->idledev.name = (const char *) priv->name;
+	priv->idledev.get_sample = idlestats_get_sample;
+
+	ret = msm_idle_stats_register_device(&priv->idledev);
+
+	if (ret) {
+		kfree(pwrscale->priv);
+		pwrscale->priv = NULL;
+	}
+
+	return ret;
+}
+
+static void idlestats_close(struct kgsl_device *device,
+		      struct kgsl_pwrscale *pwrscale)
+{
+	struct idlestats_priv *priv = pwrscale->priv;
+
+	if (pwrscale->priv == NULL)
+		return;
+
+	msm_idle_stats_deregister_device(&priv->idledev);
+
+	kfree(pwrscale->priv);
+	pwrscale->priv = NULL;
+}
+
+struct kgsl_pwrscale_policy kgsl_pwrscale_policy_idlestats = {
+	.name = "idlestats",
+	.init = idlestats_init,
+	.idle = idlestats_idle,
+	.busy = idlestats_busy,
+	.close = idlestats_close
+};
diff --git a/drivers/gpu/msm/z180.c b/drivers/gpu/msm/z180.c
index 27004af..a8aff37 100644
--- a/drivers/gpu/msm/z180.c
+++ b/drivers/gpu/msm/z180.c
@@ -522,6 +522,8 @@
 	if (status)
 		goto error_close_ringbuffer;
 
+	kgsl_pwrscale_init(device);
+
 	return status;
 
 error_close_ringbuffer:
@@ -537,6 +539,7 @@
 
 	device = (struct kgsl_device *)pdev->id_entry->driver_data;
 
+	kgsl_pwrscale_close(device);
 	kgsl_device_platform_remove(device);
 
 	z180_ringbuffer_close(device);
@@ -861,8 +864,19 @@
 static void z180_power_stats(struct kgsl_device *device,
 			    struct kgsl_power_stats *stats)
 {
-	stats->total_time = 0;
-	stats->busy_time = 0;
+	struct kgsl_pwrctrl *pwr = &device->pwrctrl;
+
+	if (pwr->time == 0) {
+		pwr->time = ktime_to_us(ktime_get());
+		stats->total_time = 0;
+		stats->busy_time = 0;
+	} else {
+		s64 tmp;
+		tmp = ktime_to_us(ktime_get());
+		stats->total_time = tmp - pwr->time;
+		stats->busy_time = tmp - pwr->time;
+		pwr->time = tmp;
+	}
 }
 
 static void z180_irqctrl(struct kgsl_device *device, int state)