misc: tegra-throughput: fix fps reading
[linux-2.6.git] / drivers / misc / tegra-throughput.c
1 /*
2  * drivers/misc/throughput.c
3  *
4  * Copyright (C) 2012-2013, NVIDIA CORPORATION. All rights reserved.
5  *
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.
9  *
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
13  * more details.
14  *
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.
18  */
19
20 #include <linux/kthread.h>
21 #include <linux/ktime.h>
22 #include <linux/miscdevice.h>
23 #include <linux/fs.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>
30 #include <mach/dc.h>
31
32 #define DEFAULT_SYNC_RATE 60000 /* 60 Hz */
33
34 static unsigned int target_frame_time;
35 static ktime_t last_flip;
36 static spinlock_t lock;
37
38 #define EMA_PERIOD  8
39
40 static int frame_time_sum_init = 1;
41 static long frame_time_sum; /* used for fps EMA */
42
43 static struct work_struct work;
44 static int throughput_hint;
45
46 static int sync_rate;
47 static int throughput_active_app_count;
48
49 static void set_throughput_hint(struct work_struct *work)
50 {
51         /* notify throughput hint clients here */
52         nvhost_scale3d_set_throughput_hint(throughput_hint);
53 }
54
55 static void throughput_flip_callback(void)
56 {
57         long timediff;
58         ktime_t now;
59
60         now = ktime_get();
61
62         if (last_flip.tv64 != 0) {
63                 timediff = (long) ktime_us_delta(now, last_flip);
64
65                 if (timediff <= 0) {
66                         pr_warn("%s: flips %lld nsec apart\n",
67                                 __func__, now.tv64 - last_flip.tv64);
68                         return;
69                 }
70
71                 throughput_hint =
72                         ((int) target_frame_time * 1000) / timediff;
73
74                 /* only deliver throughput hints when a single app is active */
75                 if (throughput_active_app_count == 1 && !work_pending(&work))
76                         schedule_work(&work);
77
78                 if (frame_time_sum_init) {
79                         frame_time_sum = timediff * EMA_PERIOD;
80                         frame_time_sum_init = 0;
81                 } else {
82                         int t = (frame_time_sum / EMA_PERIOD) *
83                                 (EMA_PERIOD - 1);
84                         frame_time_sum = t + timediff;
85                 }
86         }
87
88         last_flip = now;
89
90         return NOTIFY_OK;
91 }
92
93 static void reset_target_frame_time(void)
94 {
95         if (sync_rate == 0) {
96                 sync_rate = tegra_dc_get_panel_sync_rate();
97
98                 if (sync_rate == 0)
99                         sync_rate = DEFAULT_SYNC_RATE;
100         }
101
102         target_frame_time = (unsigned int) (1000000000 / sync_rate);
103
104         pr_debug("%s: panel sync rate %d, target frame time %u\n",
105                 __func__, sync_rate, target_frame_time);
106 }
107
108 static int throughput_open(struct inode *inode, struct file *file)
109 {
110         spin_lock(&lock);
111
112         throughput_active_app_count++;
113         frame_time_sum_init = 1;
114
115         spin_unlock(&lock);
116
117
118         pr_debug("throughput_open node %p file %p\n", inode, file);
119
120         return 0;
121 }
122
123 static int throughput_release(struct inode *inode, struct file *file)
124 {
125         spin_lock(&lock);
126
127         throughput_active_app_count--;
128         frame_time_sum_init = 1;
129
130         if (throughput_active_app_count == 1)
131                 reset_target_frame_time();
132
133         spin_unlock(&lock);
134
135         pr_debug("throughput_release node %p file %p\n", inode, file);
136
137         return 0;
138 }
139
140 static int throughput_set_target_fps(unsigned long arg)
141 {
142         pr_debug("%s: target fps %lu requested\n", __func__, arg);
143
144         if (throughput_active_app_count != 1) {
145                 pr_debug("%s: %d active apps, disabling fps usage\n",
146                         __func__, throughput_active_app_count);
147                 return 0;
148         }
149
150         if (arg == 0)
151                 reset_target_frame_time();
152         else
153                 target_frame_time = (unsigned int) (1000000 / arg);
154
155         return 0;
156 }
157
158 static long
159 throughput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
160 {
161         int err = 0;
162
163         if ((_IOC_TYPE(cmd) != TEGRA_THROUGHPUT_MAGIC) ||
164                 (_IOC_NR(cmd) == 0) ||
165                 (_IOC_NR(cmd) > TEGRA_THROUGHPUT_IOCTL_MAXNR))
166                 return -EFAULT;
167
168         switch (cmd) {
169         case TEGRA_THROUGHPUT_IOCTL_TARGET_FPS:
170                 pr_debug("%s: TEGRA_THROUGHPUT_IOCTL_TARGET_FPS %lu\n",
171                         __func__, arg);
172                 err = throughput_set_target_fps(arg);
173                 break;
174
175         default:
176                 err = -ENOTTY;
177         }
178
179         return err;
180 }
181
182 static const struct file_operations throughput_user_fops = {
183         .owner                  = THIS_MODULE,
184         .open                   = throughput_open,
185         .release                = throughput_release,
186         .unlocked_ioctl         = throughput_ioctl,
187 };
188
189 #define TEGRA_THROUGHPUT_MINOR 1
190
191 static struct miscdevice throughput_miscdev = {
192         .minor = TEGRA_THROUGHPUT_MINOR,
193         .name  = "tegra-throughput",
194         .fops  = &throughput_user_fops,
195         .mode  = 0666,
196 };
197
198 static int fps_show(struct seq_file *s, void *unused)
199 {
200         int frame_time_avg;
201         int fps;
202         ktime_t now;
203         long timediff;
204
205         fps = 0;
206         if (frame_time_sum_init)
207                 goto DONE;
208
209         now = ktime_get();
210         timediff = (long) ktime_us_delta(now, last_flip);
211         if (timediff > 1000000)
212                 goto DONE;
213
214         frame_time_avg = frame_time_sum / EMA_PERIOD;
215         fps = frame_time_avg > 0 ? 1000000 / frame_time_avg : 0;
216
217 DONE:
218         seq_printf(s, "%d\n", fps);
219         return 0;
220 }
221
222 static int fps_open(struct inode *inode, struct file *file)
223 {
224         return single_open(file, fps_show, inode->i_private);
225 }
226
227 static const struct file_operations fps_fops = {
228         .open           = fps_open,
229         .read           = seq_read,
230         .llseek         = seq_lseek,
231         .release        = single_release,
232 };
233
234 int __init throughput_init_miscdev(void)
235 {
236         int ret;
237
238         pr_debug("%s: initializing\n", __func__);
239
240         spin_lock_init(&lock);
241         INIT_WORK(&work, set_throughput_hint);
242
243         ret = misc_register(&throughput_miscdev);
244         if (ret) {
245                 pr_err("can\'t reigster throughput miscdev"
246                        " (minor %d err %d)\n", TEGRA_THROUGHPUT_MINOR, ret);
247                 return ret;
248         }
249
250         debugfs_create_file("fps", 0444, NULL, NULL, &fps_fops);
251
252         tegra_dc_set_flip_callback(throughput_flip_callback);
253
254         return 0;
255 }
256
257 module_init(throughput_init_miscdev);
258
259 void __exit throughput_exit_miscdev(void)
260 {
261         pr_debug("%s: exiting\n", __func__);
262
263         tegra_dc_unset_flip_callback();
264
265         cancel_work_sync(&work);
266
267         misc_deregister(&throughput_miscdev);
268 }
269
270 module_exit(throughput_exit_miscdev);
271