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