summaryrefslogtreecommitdiff
path: root/drivers/misc/tegra-throughput.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/tegra-throughput.c')
-rw-r--r--drivers/misc/tegra-throughput.c242
1 files changed, 242 insertions, 0 deletions
diff --git a/drivers/misc/tegra-throughput.c b/drivers/misc/tegra-throughput.c
new file mode 100644
index 000000000000..d491ef8c06cf
--- /dev/null
+++ b/drivers/misc/tegra-throughput.c
@@ -0,0 +1,242 @@
+/*
+ * drivers/misc/throughput.c
+ *
+ * Copyright (C) 2012, NVIDIA CORPORATION. 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 as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/kthread.h>
+#include <linux/ktime.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/throughput_ioctl.h>
+#include <linux/nvhost.h>
+#include <mach/dc.h>
+
+#define DEFAULT_SYNC_RATE 60000 /* 60 Hz */
+
+static unsigned short target_frame_time;
+static unsigned short last_frame_time;
+static ktime_t last_flip;
+static unsigned int multiple_app_disable;
+static spinlock_t lock;
+
+static struct work_struct work;
+static int throughput_hint;
+
+static void set_throughput_hint(struct work_struct *work)
+{
+ /* notify throughput hint clients here */
+ nvhost_scale3d_set_throughput_hint(throughput_hint);
+}
+
+static int throughput_flip_callback(void)
+{
+ long timediff;
+ ktime_t now;
+
+ /* only register flips when a single app is active */
+ if (multiple_app_disable)
+ return NOTIFY_DONE;
+
+ now = ktime_get();
+ if (last_flip.tv64 != 0) {
+ timediff = (long) ktime_us_delta(now, last_flip);
+ if (timediff > (long) USHRT_MAX)
+ last_frame_time = USHRT_MAX;
+ else
+ last_frame_time = (unsigned short) timediff;
+
+ if (last_frame_time == 0) {
+ pr_warn("%s: flips %lld nsec apart\n",
+ __func__, now.tv64 - last_flip.tv64);
+ return NOTIFY_DONE;
+ }
+
+ throughput_hint =
+ ((int) target_frame_time * 1000) / last_frame_time;
+
+ if (!work_pending(&work))
+ schedule_work(&work);
+ }
+ last_flip = now;
+
+ return NOTIFY_OK;
+}
+
+static int sync_rate;
+static int throughput_active_app_count;
+
+static void reset_target_frame_time(void)
+{
+ if (sync_rate == 0) {
+ sync_rate = tegra_dc_get_panel_sync_rate();
+
+ if (sync_rate == 0)
+ sync_rate = DEFAULT_SYNC_RATE;
+ }
+
+ target_frame_time = (unsigned short) (1000000000 / sync_rate);
+
+ pr_debug("%s: panel sync rate %d, target frame time %u\n",
+ __func__, sync_rate, target_frame_time);
+}
+
+static int callback_initialized;
+
+static int throughput_open(struct inode *inode, struct file *file)
+{
+ spin_lock(&lock);
+
+ if (!callback_initialized) {
+ callback_initialized = 1;
+ tegra_dc_set_flip_callback(throughput_flip_callback);
+ }
+
+ throughput_active_app_count++;
+ if (throughput_active_app_count > 1)
+ multiple_app_disable = 1;
+
+ spin_unlock(&lock);
+
+
+ pr_debug("throughput_open node %p file %p\n", inode, file);
+
+ return 0;
+}
+
+static int throughput_release(struct inode *inode, struct file *file)
+{
+ spin_lock(&lock);
+
+ throughput_active_app_count--;
+ if (throughput_active_app_count == 0) {
+ reset_target_frame_time();
+ multiple_app_disable = 0;
+ callback_initialized = 0;
+ tegra_dc_unset_flip_callback();
+ }
+
+ spin_unlock(&lock);
+
+ pr_debug("throughput_release node %p file %p\n", inode, file);
+
+ return 0;
+}
+
+static int throughput_set_target_fps(unsigned long arg)
+{
+ int disable;
+
+ pr_debug("%s: target fps %lu requested\n", __func__, arg);
+
+ disable = multiple_app_disable;
+
+ if (disable) {
+ pr_debug("%s: %d active apps, disabling fps usage\n",
+ __func__, throughput_active_app_count);
+ return 0;
+ }
+
+ if (arg == 0)
+ reset_target_frame_time();
+ else {
+ unsigned long frame_time = (1000000 / arg);
+
+ if (frame_time > USHRT_MAX)
+ frame_time = USHRT_MAX;
+
+ target_frame_time = (unsigned short) frame_time;
+ }
+
+ return 0;
+}
+
+static long
+throughput_ioctl(struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ int err = 0;
+
+ if ((_IOC_TYPE(cmd) != TEGRA_THROUGHPUT_MAGIC) ||
+ (_IOC_NR(cmd) == 0) ||
+ (_IOC_NR(cmd) > TEGRA_THROUGHPUT_IOCTL_MAXNR))
+ return -EFAULT;
+
+ switch (cmd) {
+ case TEGRA_THROUGHPUT_IOCTL_TARGET_FPS:
+ pr_debug("%s: TEGRA_THROUGHPUT_IOCTL_TARGET_FPS %lu\n",
+ __func__, arg);
+ err = throughput_set_target_fps(arg);
+ break;
+
+ default:
+ err = -ENOTTY;
+ }
+
+ return err;
+}
+
+static const struct file_operations throughput_user_fops = {
+ .owner = THIS_MODULE,
+ .open = throughput_open,
+ .release = throughput_release,
+ .unlocked_ioctl = throughput_ioctl,
+};
+
+#define TEGRA_THROUGHPUT_MINOR 1
+
+static struct miscdevice throughput_miscdev = {
+ .minor = TEGRA_THROUGHPUT_MINOR,
+ .name = "tegra-throughput",
+ .fops = &throughput_user_fops,
+ .mode = 0666,
+};
+
+int __init throughput_init_miscdev(void)
+{
+ int ret;
+
+ pr_debug("%s: initializing\n", __func__);
+
+ spin_lock_init(&lock);
+ INIT_WORK(&work, set_throughput_hint);
+
+ ret = misc_register(&throughput_miscdev);
+ if (ret) {
+ pr_err("can\'t reigster throughput miscdev"
+ " (minor %d err %d)\n", TEGRA_THROUGHPUT_MINOR, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+module_init(throughput_init_miscdev);
+
+void __exit throughput_exit_miscdev(void)
+{
+ pr_debug("%s: exiting\n", __func__);
+
+ cancel_work_sync(&work);
+
+ misc_deregister(&throughput_miscdev);
+}
+
+module_exit(throughput_exit_miscdev);
+