diff options
-rw-r--r-- | arch/arm/mach-tegra/include/mach/dc.h | 5 | ||||
-rw-r--r-- | drivers/misc/Kconfig | 7 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/tegra-throughput.c | 231 | ||||
-rw-r--r-- | drivers/video/tegra/dc/dc_priv.h | 2 | ||||
-rw-r--r-- | drivers/video/tegra/dc/ext/dev.c | 29 | ||||
-rw-r--r-- | drivers/video/tegra/dc/mode.c | 13 | ||||
-rw-r--r-- | include/linux/throughput_ioctl.h | 39 |
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) */ + |