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