summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-tegra/include/mach/dc.h5
-rw-r--r--drivers/misc/Kconfig7
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/tegra-throughput.c231
-rw-r--r--drivers/video/tegra/dc/dc_priv.h2
-rw-r--r--drivers/video/tegra/dc/ext/dev.c29
-rw-r--r--drivers/video/tegra/dc/mode.c13
-rw-r--r--include/linux/throughput_ioctl.h39
8 files changed, 326 insertions, 1 deletions
diff --git a/arch/arm/mach-tegra/include/mach/dc.h b/arch/arm/mach-tegra/include/mach/dc.h
index 161e37616497..47210c1cd87b 100644
--- a/arch/arm/mach-tegra/include/mach/dc.h
+++ b/arch/arm/mach-tegra/include/mach/dc.h
@@ -25,6 +25,7 @@
#include <linux/pm.h>
#include <linux/types.h>
#include <drm/drm_fixed.h>
+#include <linux/notifier.h>
#define TEGRA_MAX_DC 2
#define DC_N_WINDOWS 3
@@ -584,4 +585,8 @@ struct tegra_dc_edid {
struct tegra_dc_edid *tegra_dc_get_edid(struct tegra_dc *dc);
void tegra_dc_put_edid(struct tegra_dc_edid *edid);
+int tegra_dc_register_flip_notifier(struct notifier_block *nb);
+int tegra_dc_unregister_flip_notifier(struct notifier_block *nb);
+int tegra_dc_get_panel_sync_rate(void);
+
#endif
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index dcf345e23487..0d6210e50e43 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -568,6 +568,13 @@ config THERM_EST
---help---
Thermal driver which estimates temperature based of other sensors.
+config TEGRA_THROUGHPUT
+ bool "Device node to set throughput target"
+ depends on TEGRA_DC && TEGRA_DC_EXTENSIONS
+ default y
+ ---help---
+ Dev node /dev/tegra-throughput used to set a throughput target.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index d9172c99bf6d..39d1427ae549 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -60,3 +60,4 @@ obj-$(CONFIG_TEGRA_BB_SUPPORT) += tegra-baseband/
obj-$(CONFIG_TEGRA_CEC_SUPPORT) += tegra-cec/
obj-$(CONFIG_MAX1749_VIBRATOR) += max1749.o
obj-$(CONFIG_THERM_EST) += therm_est.o
+obj-$(CONFIG_TEGRA_THROUGHPUT) += tegra-throughput.o
diff --git a/drivers/misc/tegra-throughput.c b/drivers/misc/tegra-throughput.c
new file mode 100644
index 000000000000..c909a81d0882
--- /dev/null
+++ b/drivers/misc/tegra-throughput.c
@@ -0,0 +1,231 @@
+/*
+ * 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/notifier.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/throughput_ioctl.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 int throughput_flip_notifier(struct notifier_block *nb,
+ unsigned long val,
+ void *data)
+{
+ /* only register flips when a single display is active */
+ if (val != 1 || multiple_app_disable)
+ return NOTIFY_DONE;
+ else {
+ long timediff;
+ ktime_t now;
+ int throughput_hint;
+
+ 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;
+
+ throughput_hint =
+ ((int) target_frame_time * 100)/last_frame_time;
+
+ /* notify throughput hint clients here */
+ }
+ last_flip = now;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block throughput_flip_nb = {
+ .notifier_call = throughput_flip_notifier,
+};
+
+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 notifier_initialized;
+
+static int throughput_open(struct inode *inode, struct file *file)
+{
+ if (!notifier_initialized) {
+ tegra_dc_register_flip_notifier(&throughput_flip_nb);
+ notifier_initialized = 1;
+ }
+
+ spin_lock(&lock);
+
+ 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--;
+ spin_unlock(&lock);
+
+ if (throughput_active_app_count == 0) {
+ reset_target_frame_time();
+ multiple_app_disable = 0;
+ tegra_dc_unregister_flip_notifier(&throughput_flip_nb);
+ notifier_initialized = 0;
+ }
+
+
+ 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);
+
+ 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__);
+
+ misc_deregister(&throughput_miscdev);
+}
+
+module_exit(throughput_exit_miscdev);
+
diff --git a/drivers/video/tegra/dc/dc_priv.h b/drivers/video/tegra/dc/dc_priv.h
index 332c80f9cbb6..759d64da7052 100644
--- a/drivers/video/tegra/dc/dc_priv.h
+++ b/drivers/video/tegra/dc/dc_priv.h
@@ -372,7 +372,7 @@ void tegra_dc_disable_crc(struct tegra_dc *dc);
void tegra_dc_set_out_pin_polars(struct tegra_dc *dc,
const struct tegra_dc_out_pin *pins,
const unsigned int n_pins);
-/* defined in dc.c, used in bandwidth.c */
+/* defined in dc.c, used in bandwidth.c and ext/dev.c */
unsigned int tegra_dc_has_multiple_dc(void);
/* defined in dc.c, used in dsi.c */
diff --git a/drivers/video/tegra/dc/ext/dev.c b/drivers/video/tegra/dc/ext/dev.c
index f9c76f8f0d0d..92e42ce32ac2 100644
--- a/drivers/video/tegra/dc/ext/dev.c
+++ b/drivers/video/tegra/dc/ext/dev.c
@@ -274,6 +274,32 @@ static int tegra_dc_ext_set_windowattr(struct tegra_dc_ext *ext,
return 0;
}
+static struct srcu_notifier_head tegra_dc_flip_notifier_list;
+static bool init_tegra_dc_flip_notifier_list_called;
+static int __init init_tegra_dc_flip_notifier_list(void)
+{
+ srcu_init_notifier_head(&tegra_dc_flip_notifier_list);
+ init_tegra_dc_flip_notifier_list_called = true;
+ return 0;
+}
+
+pure_initcall(init_tegra_dc_flip_notifier_list);
+
+int tegra_dc_register_flip_notifier(struct notifier_block *nb)
+{
+ WARN_ON(!init_tegra_dc_flip_notifier_list_called);
+
+ return srcu_notifier_chain_register(
+ &tegra_dc_flip_notifier_list, nb);
+}
+EXPORT_SYMBOL(tegra_dc_register_flip_notifier);
+
+int tegra_dc_unregister_flip_notifier(struct notifier_block *nb)
+{
+ return srcu_notifier_chain_unregister(&tegra_dc_flip_notifier_list, nb);
+}
+EXPORT_SYMBOL(tegra_dc_unregister_flip_notifier);
+
static void tegra_dc_ext_flip_worker(struct work_struct *work)
{
struct tegra_dc_ext_flip_data *data =
@@ -327,6 +353,9 @@ static void tegra_dc_ext_flip_worker(struct work_struct *work)
tegra_dc_update_windows(wins, nr_win);
/* TODO: implement swapinterval here */
tegra_dc_sync_windows(wins, nr_win);
+ if (!tegra_dc_has_multiple_dc())
+ srcu_notifier_call_chain(&tegra_dc_flip_notifier_list,
+ 1UL, NULL);
}
for (i = 0; i < DC_N_WINDOWS; i++) {
diff --git a/drivers/video/tegra/dc/mode.c b/drivers/video/tegra/dc/mode.c
index 49cc5f5abd53..be909691b957 100644
--- a/drivers/video/tegra/dc/mode.c
+++ b/drivers/video/tegra/dc/mode.c
@@ -247,10 +247,23 @@ int tegra_dc_program_mode(struct tegra_dc *dc, struct tegra_dc_mode *mode)
return 0;
}
+static int panel_sync_rate;
+
+int tegra_dc_get_panel_sync_rate(void)
+{
+ return panel_sync_rate;
+}
+EXPORT_SYMBOL(tegra_dc_get_panel_sync_rate);
+
int tegra_dc_set_mode(struct tegra_dc *dc, const struct tegra_dc_mode *mode)
{
memcpy(&dc->mode, mode, sizeof(dc->mode));
+ if (dc->out->type == TEGRA_DC_OUT_RGB)
+ panel_sync_rate = tegra_dc_calc_refresh(mode);
+ else if (dc->out->type == TEGRA_DC_OUT_DSI)
+ panel_sync_rate = dc->out->dsi->rated_refresh_rate * 1000;
+
print_mode(dc, mode, __func__);
return 0;
diff --git a/include/linux/throughput_ioctl.h b/include/linux/throughput_ioctl.h
new file mode 100644
index 000000000000..96e57399b2e9
--- /dev/null
+++ b/include/linux/throughput_ioctl.h
@@ -0,0 +1,39 @@
+/*
+ * include/linux/throughput_ioctl.h
+ *
+ * ioctl declarations for throughput miscdev
+ *
+ * Copyright (c) 2012, NVIDIA Corporation.
+ *
+ * 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.
+ */
+
+#ifndef __TEGRA_THROUGHPUT_IOCTL_H
+#define __TEGRA_THROUGHPUT_IOCTL_H
+
+#include <linux/ioctl.h>
+
+#define TEGRA_THROUGHPUT_MAGIC 'g'
+
+struct tegra_throughput_target_fps_args {
+ __u32 target_fps;
+};
+
+#define TEGRA_THROUGHPUT_IOCTL_TARGET_FPS \
+ _IOW(TEGRA_THROUGHPUT_MAGIC, 1, struct tegra_throughput_target_fps_args)
+#define TEGRA_THROUGHPUT_IOCTL_MAXNR \
+ (_IOC_NR(TEGRA_THROUGHPUT_IOCTL_TARGET_FPS))
+
+#endif /* !defined(__TEGRA_THROUGHPUT_IOCTL_H) */
+