msm: Add idle_stats_device infrastructure

Add a core infrastructure for transmitting idle stats to user
space for use by other devices in the system such as the GPU.

Signed-off-by: Lucille Sylvester <lsylvest@codeaurora.org>
diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index f51cd5b..26268f7 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -248,7 +248,7 @@
 endif
 endif
 
-obj-$(CONFIG_MSM_SLEEP_STATS) += msm_rq_stats.o idle_stats.o
+obj-$(CONFIG_MSM_SLEEP_STATS) += msm_rq_stats.o idle_stats.o idle_stats_device.o
 obj-$(CONFIG_MSM_SHOW_RESUME_IRQ) += msm_show_resume_irq.o
 obj-$(CONFIG_BT_MSM_PINTEST)  += btpintest.o
 obj-$(CONFIG_MSM_FAKE_BATTERY) += fish_battery.o
diff --git a/arch/arm/mach-msm/idle_stats_device.c b/arch/arm/mach-msm/idle_stats_device.c
new file mode 100644
index 0000000..7c48f5a
--- /dev/null
+++ b/arch/arm/mach-msm/idle_stats_device.c
@@ -0,0 +1,352 @@
+/* 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/sched.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <mach/idle_stats_device.h>
+
+DEFINE_MUTEX(device_list_lock);
+LIST_HEAD(device_list);
+
+static ktime_t us_to_ktime(__u32 us)
+{
+	return ns_to_ktime((u64)us * NSEC_PER_USEC);
+}
+
+static struct msm_idle_stats_device *_device_from_minor(unsigned int minor)
+{
+	struct msm_idle_stats_device *device, *ret = NULL;
+
+
+	mutex_lock(&device_list_lock);
+	list_for_each_entry(device, &device_list, list) {
+		if (minor == device->miscdev.minor) {
+			ret = device;
+			break;
+		}
+	}
+	mutex_unlock(&device_list_lock);
+	return ret;
+}
+
+static void update_event
+	(struct msm_idle_stats_device *device, __u32 event)
+{
+	__u32 wake_up = !device->stats->event;
+
+	device->stats->event |= event;
+	if (wake_up)
+		wake_up_interruptible(&device->wait);
+}
+
+static enum hrtimer_restart msm_idle_stats_busy_timer(struct hrtimer *timer)
+{
+	struct msm_idle_stats_device *device =
+		container_of(timer, struct msm_idle_stats_device, busy_timer);
+
+
+	/* This is the only case that the event is modified without a device
+	 * lock. However, since the timer is cancelled in the other cases we are
+	 * assured that we have exclusive access to the event at this time.
+	 */
+	hrtimer_set_expires(&device->busy_timer, us_to_ktime(0));
+	update_event(device, MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED);
+	return HRTIMER_NORESTART;
+}
+
+static void start_busy_timer(struct msm_idle_stats_device *device,
+						     ktime_t relative_time)
+{
+	hrtimer_cancel(&device->busy_timer);
+	hrtimer_set_expires(&device->busy_timer, us_to_ktime(0));
+	if (!((device->stats->event &
+		   MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED) ||
+	      (device->stats->event & MSM_IDLE_STATS_EVENT_COLLECTION_FULL))) {
+		if (ktime_to_us(relative_time) > 0) {
+			hrtimer_start(&device->busy_timer,
+						  relative_time,
+						  HRTIMER_MODE_REL);
+		}
+	}
+}
+
+static unsigned int msm_idle_stats_device_poll(struct file *file,
+						poll_table *wait)
+{
+	struct msm_idle_stats_device *device = file->private_data;
+	unsigned int mask = 0;
+
+	poll_wait(file, &device->wait, wait);
+	if (device->stats->event)
+		mask = POLLIN | POLLRDNORM;
+	return mask;
+}
+
+static void msm_idle_stats_add_sample(struct msm_idle_stats_device *device,
+		struct msm_idle_pulse *pulse)
+{
+	hrtimer_cancel(&device->busy_timer);
+	hrtimer_set_expires(&device->busy_timer, us_to_ktime(0));
+	if (device->stats->nr_collected >= MSM_IDLE_STATS_NR_MAX_INTERVALS)
+		return;
+
+	device->stats->pulse_chain[device->stats->nr_collected] = *pulse;
+	device->stats->nr_collected++;
+
+	if (device->stats->nr_collected == MSM_IDLE_STATS_NR_MAX_INTERVALS) {
+		update_event(device, MSM_IDLE_STATS_EVENT_COLLECTION_FULL);
+	} else if (device->stats->nr_collected ==
+				((MSM_IDLE_STATS_NR_MAX_INTERVALS * 3) / 4)) {
+		update_event(device,
+			MSM_IDLE_STATS_EVENT_COLLECTION_NEARLY_FULL);
+	}
+}
+
+static long ioctl_read_stats(struct msm_idle_stats_device *device,
+		unsigned long arg)
+{
+	int remaining;
+	int requested;
+	struct msm_idle_pulse pulse;
+	struct msm_idle_read_stats *stats;
+	__s64 remaining_time =
+		ktime_to_us(hrtimer_get_remaining(&device->busy_timer));
+
+	device->get_sample(device, &pulse);
+	spin_lock(&device->lock);
+	hrtimer_cancel(&device->busy_timer);
+	stats = device->stats;
+	if (stats == &device->stats_vector[0])
+		device->stats = &device->stats_vector[1];
+	else
+		device->stats = &device->stats_vector[0];
+	device->stats->event = (stats->event &
+			MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED);
+	device->stats->nr_collected = 0;
+	spin_unlock(&device->lock);
+	if (stats->nr_collected >= MSM_IDLE_STATS_NR_MAX_INTERVALS) {
+		stats->nr_collected = MSM_IDLE_STATS_NR_MAX_INTERVALS;
+	} else {
+	    stats->pulse_chain[stats->nr_collected] = pulse;
+	    stats->nr_collected++;
+	    if (stats->nr_collected == MSM_IDLE_STATS_NR_MAX_INTERVALS)
+			stats->event |= MSM_IDLE_STATS_EVENT_COLLECTION_FULL;
+	    else if (stats->nr_collected ==
+				 ((MSM_IDLE_STATS_NR_MAX_INTERVALS * 3) / 4))
+			stats->event |=
+				MSM_IDLE_STATS_EVENT_COLLECTION_NEARLY_FULL;
+	}
+	if (remaining_time < 0) {
+		stats->busy_timer_remaining = 0;
+	} else {
+	    stats->busy_timer_remaining = remaining_time;
+		if ((__s64)stats->busy_timer_remaining != remaining_time)
+			stats->busy_timer_remaining = -1;
+	}
+	stats->return_timestamp = ktime_to_us(ktime_get());
+	requested =
+		((sizeof(*stats) - sizeof(stats->pulse_chain)) +
+		 (sizeof(stats->pulse_chain[0]) * stats->nr_collected));
+	remaining = copy_to_user((void __user *)arg, stats, requested);
+	if (remaining > 0)
+		return -EFAULT;
+
+	return 0;
+}
+
+static long ioctl_write_stats(struct msm_idle_stats_device *device,
+		unsigned long arg)
+{
+	struct msm_idle_write_stats stats;
+	int remaining;
+	int ret = 0;
+
+	remaining = copy_from_user(&stats,  (void __user *) arg, sizeof(stats));
+	if (remaining > 0) {
+		ret = -EFAULT;
+	} else {
+	    spin_lock(&device->lock);
+	    device->busy_timer_interval = us_to_ktime(stats.next_busy_timer);
+	    if (ktime_to_us(device->idle_start) == 0)
+			start_busy_timer(device, us_to_ktime(stats.busy_timer));
+	    spin_unlock(&device->lock);
+	}
+	return ret;
+}
+
+void msm_idle_stats_prepare_idle_start(struct msm_idle_stats_device *device)
+{
+	spin_lock(&device->lock);
+	hrtimer_cancel(&device->busy_timer);
+	spin_unlock(&device->lock);
+}
+EXPORT_SYMBOL(msm_idle_stats_prepare_idle_start);
+
+void msm_idle_stats_abort_idle_start(struct msm_idle_stats_device *device)
+{
+	spin_lock(&device->lock);
+	if (ktime_to_us(hrtimer_get_expires(&device->busy_timer)) > 0)
+		hrtimer_restart(&device->busy_timer);
+	spin_unlock(&device->lock);
+}
+EXPORT_SYMBOL(msm_idle_stats_abort_idle_start);
+
+void msm_idle_stats_idle_start(struct msm_idle_stats_device *device)
+{
+	spin_lock(&device->lock);
+	hrtimer_cancel(&device->busy_timer);
+	device->idle_start = ktime_get();
+	if (ktime_to_us(hrtimer_get_expires(&device->busy_timer)) > 0) {
+		device->remaining_time =
+				hrtimer_get_remaining(&device->busy_timer);
+		if (ktime_to_us(device->remaining_time) <= 0)
+			device->remaining_time = us_to_ktime(1);
+	} else {
+		device->remaining_time = us_to_ktime(0);
+	}
+	spin_unlock(&device->lock);
+}
+EXPORT_SYMBOL(msm_idle_stats_idle_start);
+
+void msm_idle_stats_idle_end(struct msm_idle_stats_device *device,
+				struct msm_idle_pulse *pulse)
+{
+	spin_lock(&device->lock);
+	if (ktime_to_us(device->idle_start) != 0) {
+		device->idle_start = us_to_ktime(0);
+	    msm_idle_stats_add_sample(device, pulse);
+		if (device->stats->event &
+			MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED) {
+			device->stats->event &=
+				~MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED;
+			update_event(device,
+				MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED_RESET);
+		} else if (ktime_to_us(device->busy_timer_interval) > 0) {
+			ktime_t busy_timer = device->busy_timer_interval;
+			if ((pulse->wait_interval > 0) &&
+				(ktime_to_us(device->remaining_time) > 0) &&
+				(ktime_to_us(device->remaining_time) <
+				 ktime_to_us(busy_timer)))
+				busy_timer = device->remaining_time;
+		    start_busy_timer(device, busy_timer);
+	    }
+	}
+	spin_unlock(&device->lock);
+}
+EXPORT_SYMBOL(msm_idle_stats_idle_end);
+
+static long msm_idle_stats_device_ioctl(struct file *file, unsigned int cmd,
+		unsigned long arg)
+{
+	struct msm_idle_stats_device *device = file->private_data;
+	int ret;
+
+	switch (cmd) {
+	case MSM_IDLE_STATS_IOC_READ_STATS:
+		ret = ioctl_read_stats(device, arg);
+		break;
+	case MSM_IDLE_STATS_IOC_WRITE_STATS:
+		ret = ioctl_write_stats(device, arg);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int msm_idle_stats_device_release
+			  (struct inode *inode, struct file *filep)
+{
+	return 0;
+}
+
+static int msm_idle_stats_device_open(struct inode *inode, struct file *filep)
+{
+	struct msm_idle_stats_device *device;
+
+
+	device = _device_from_minor(iminor(inode));
+
+	if (device == NULL)
+		return -EPERM;
+
+	filep->private_data = device;
+	return 0;
+}
+
+static const struct file_operations msm_idle_stats_fops = {
+	.open = msm_idle_stats_device_open,
+	.release = msm_idle_stats_device_release,
+	.unlocked_ioctl = msm_idle_stats_device_ioctl,
+	.poll = msm_idle_stats_device_poll,
+};
+
+int msm_idle_stats_register_device(struct msm_idle_stats_device *device)
+{
+	int ret = -ENOMEM;
+
+	spin_lock_init(&device->lock);
+	init_waitqueue_head(&device->wait);
+	hrtimer_init(&device->busy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	device->busy_timer.function = msm_idle_stats_busy_timer;
+
+	device->stats_vector[0].event         = 0;
+	device->stats_vector[0].nr_collected  = 0;
+	device->stats_vector[1].event         = 0;
+	device->stats_vector[1].nr_collected  = 0;
+	device->stats = &device->stats_vector[0];
+	device->busy_timer_interval = us_to_ktime(0);
+
+	mutex_lock(&device_list_lock);
+	list_add(&device->list, &device_list);
+	mutex_unlock(&device_list_lock);
+
+	device->miscdev.minor = MISC_DYNAMIC_MINOR;
+	device->miscdev.name = device->name;
+	device->miscdev.fops = &msm_idle_stats_fops;
+
+	ret = misc_register(&device->miscdev);
+
+	if (ret)
+		goto err_list;
+
+	return ret;
+
+err_list:
+	mutex_lock(&device_list_lock);
+	list_del(&device->list);
+	mutex_unlock(&device_list_lock);
+	return ret;
+}
+EXPORT_SYMBOL(msm_idle_stats_register_device);
+
+int msm_idle_stats_deregister_device(struct msm_idle_stats_device *device)
+{
+	if (device == NULL)
+		return 0;
+
+	mutex_lock(&device_list_lock);
+	spin_lock(&device->lock);
+	hrtimer_cancel(&device->busy_timer);
+	list_del(&device->list);
+	spin_unlock(&device->lock);
+	mutex_unlock(&device_list_lock);
+
+	return misc_deregister(&device->miscdev);
+}
+EXPORT_SYMBOL(msm_idle_stats_deregister_device);
diff --git a/arch/arm/mach-msm/include/mach/idle_stats_device.h b/arch/arm/mach-msm/include/mach/idle_stats_device.h
new file mode 100644
index 0000000..0538e19
--- /dev/null
+++ b/arch/arm/mach-msm/include/mach/idle_stats_device.h
@@ -0,0 +1,106 @@
+/* Copyright (c) 2010, 2011 Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of Code Aurora Forum, Inc. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __ARCH_ARM_MACH_MSM_IDLE_STATS_DEVICE_H
+#define __ARCH_ARM_MACH_MSM_IDLE_STATS_DEVICE_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define MSM_IDLE_STATS_EVENT_NONE                     0
+#define MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED       1
+#define MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED_RESET 2
+#define MSM_IDLE_STATS_EVENT_COLLECTION_NEARLY_FULL   4
+#define MSM_IDLE_STATS_EVENT_COLLECTION_FULL          8
+
+/*
+ * All time, timer, and time interval values are in units of
+ * microseconds unless stated otherwise.
+ */
+#define MSM_IDLE_STATS_NR_MAX_INTERVALS 200
+
+struct msm_idle_pulse {
+	__s64 busy_start_time;
+	__u32 busy_interval;
+	__u32 wait_interval;
+};
+
+struct msm_idle_read_stats {
+	__u32 event;
+	__s64 return_timestamp;
+	__u32 busy_timer_remaining;
+	__u32 nr_collected;
+	struct msm_idle_pulse pulse_chain[MSM_IDLE_STATS_NR_MAX_INTERVALS];
+};
+
+struct msm_idle_write_stats {
+	__u32 busy_timer;
+	__u32 next_busy_timer;
+};
+
+#define MSM_IDLE_STATS_IOC_MAGIC  0xD8
+#define MSM_IDLE_STATS_IOC_READ_STATS  \
+		_IOWR(MSM_IDLE_STATS_IOC_MAGIC, 1, struct msm_idle_read_stats)
+#define MSM_IDLE_STATS_IOC_WRITE_STATS  \
+		_IOWR(MSM_IDLE_STATS_IOC_MAGIC, 2, struct msm_idle_write_stats)
+
+#ifdef __KERNEL__
+#include <linux/hrtimer.h>
+#include <linux/mutex.h>
+#include <linux/miscdevice.h>
+
+struct msm_idle_stats_device {
+	const char *name;
+	void (*get_sample)(struct msm_idle_stats_device *device,
+		struct msm_idle_pulse *pulse);
+
+	struct miscdevice miscdev;
+	spinlock_t lock;
+	wait_queue_head_t wait;
+	struct list_head list;
+	struct hrtimer busy_timer;
+	ktime_t busy_timer_interval;
+	ktime_t idle_start;
+	ktime_t remaining_time;
+
+	struct msm_idle_read_stats *stats;
+	struct msm_idle_read_stats stats_vector[2];
+};
+
+int msm_idle_stats_register_device(struct msm_idle_stats_device *device);
+int msm_idle_stats_deregister_device(struct msm_idle_stats_device *device);
+void msm_idle_stats_prepare_idle_start(struct msm_idle_stats_device *device);
+void msm_idle_stats_abort_idle_start(struct msm_idle_stats_device *device);
+void msm_idle_stats_idle_start(struct msm_idle_stats_device *device);
+void msm_idle_stats_idle_end(struct msm_idle_stats_device *device,
+				struct msm_idle_pulse *pulse);
+#endif
+
+#endif  /* __ARCH_ARM_MACH_MSM_IDLE_STATS_DEVICE_H */
+