2 * drivers/misc/throughput.c
4 * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, version 2 of the License.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include <linux/kthread.h>
21 #include <linux/ktime.h>
22 #include <linux/miscdevice.h>
24 #include <linux/debugfs.h>
25 #include <linux/init.h>
26 #include <linux/spinlock.h>
27 #include <linux/throughput_ioctl.h>
28 #include <linux/module.h>
29 #include <linux/nvhost.h>
32 #define DEFAULT_SYNC_RATE 60000 /* 60 Hz */
34 static unsigned int target_frame_time;
35 static ktime_t last_flip;
36 static spinlock_t lock;
40 static int frame_time_sum_init = 1;
41 static long frame_time_sum; /* used for fps EMA */
43 static struct work_struct work;
44 static int throughput_hint;
47 static int throughput_active_app_count;
49 static void set_throughput_hint(struct work_struct *work)
51 /* notify throughput hint clients here */
52 nvhost_scale3d_set_throughput_hint(throughput_hint);
55 static void throughput_flip_callback(void)
62 if (last_flip.tv64 != 0) {
63 timediff = (long) ktime_us_delta(now, last_flip);
66 pr_warn("%s: flips %lld nsec apart\n",
67 __func__, now.tv64 - last_flip.tv64);
73 ((int) target_frame_time * 1000) / timediff;
75 /* only deliver throughput hints when a single app is active */
76 if (throughput_active_app_count == 1 && !work_pending(&work))
79 if (frame_time_sum_init) {
80 frame_time_sum = timediff * EMA_PERIOD;
81 frame_time_sum_init = 0;
83 int t = (frame_time_sum / EMA_PERIOD) *
85 frame_time_sum = t + timediff;
92 static void reset_target_frame_time(void)
95 sync_rate = tegra_dc_get_panel_sync_rate();
98 sync_rate = DEFAULT_SYNC_RATE;
101 target_frame_time = (unsigned int) (1000000000 / sync_rate);
103 pr_debug("%s: panel sync rate %d, target frame time %u\n",
104 __func__, sync_rate, target_frame_time);
107 static int throughput_open(struct inode *inode, struct file *file)
111 throughput_active_app_count++;
112 frame_time_sum_init = 1;
117 pr_debug("throughput_open node %p file %p\n", inode, file);
122 static int throughput_release(struct inode *inode, struct file *file)
126 throughput_active_app_count--;
127 frame_time_sum_init = 1;
129 if (throughput_active_app_count == 1)
130 reset_target_frame_time();
134 pr_debug("throughput_release node %p file %p\n", inode, file);
139 static int throughput_set_target_fps(unsigned long arg)
141 pr_debug("%s: target fps %lu requested\n", __func__, arg);
143 if (throughput_active_app_count != 1) {
144 pr_debug("%s: %d active apps, disabling fps usage\n",
145 __func__, throughput_active_app_count);
150 reset_target_frame_time();
152 target_frame_time = (unsigned int) (1000000 / arg);
158 throughput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
162 if ((_IOC_TYPE(cmd) != TEGRA_THROUGHPUT_MAGIC) ||
163 (_IOC_NR(cmd) == 0) ||
164 (_IOC_NR(cmd) > TEGRA_THROUGHPUT_IOCTL_MAXNR))
168 case TEGRA_THROUGHPUT_IOCTL_TARGET_FPS:
169 pr_debug("%s: TEGRA_THROUGHPUT_IOCTL_TARGET_FPS %lu\n",
171 err = throughput_set_target_fps(arg);
181 static const struct file_operations throughput_user_fops = {
182 .owner = THIS_MODULE,
183 .open = throughput_open,
184 .release = throughput_release,
185 .unlocked_ioctl = throughput_ioctl,
188 #define TEGRA_THROUGHPUT_MINOR 1
190 static struct miscdevice throughput_miscdev = {
191 .minor = TEGRA_THROUGHPUT_MINOR,
192 .name = "tegra-throughput",
193 .fops = &throughput_user_fops,
197 static ssize_t show_fps(struct kobject *kobj,
198 struct attribute *attr, char *buf)
205 if (frame_time_sum_init)
209 timediff = (long) ktime_us_delta(now, last_flip);
210 if (timediff > 1000000)
213 frame_time_avg = frame_time_sum / EMA_PERIOD;
214 fps = frame_time_avg > 0 ? 1000000 / frame_time_avg : 0;
217 return sprintf(buf, "%d\n", fps);
220 static struct global_attr fps_attr = __ATTR(fps, 0444,
223 int __init throughput_init_miscdev(void)
227 pr_debug("%s: initializing\n", __func__);
229 spin_lock_init(&lock);
230 INIT_WORK(&work, set_throughput_hint);
232 ret = misc_register(&throughput_miscdev);
234 pr_err("can\'t reigster throughput miscdev"
235 " (minor %d err %d)\n", TEGRA_THROUGHPUT_MINOR, ret);
239 ret = sysfs_create_file(&throughput_miscdev.this_device->kobj,
242 pr_err("%s: error %d creating sysfs node\n", __func__, ret);
244 tegra_dc_set_flip_callback(throughput_flip_callback);
249 module_init(throughput_init_miscdev);
251 void __exit throughput_exit_miscdev(void)
253 pr_debug("%s: exiting\n", __func__);
255 tegra_dc_unset_flip_callback();
257 cancel_work_sync(&work);
259 sysfs_remove_file(&throughput_miscdev.this_device->kobj, &fps_attr.attr);
261 misc_deregister(&throughput_miscdev);
264 module_exit(throughput_exit_miscdev);