misc: tegra-throughput: move fps node to sysfs
[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
91 static void reset_target_frame_time(void)
92 {
93         if (sync_rate == 0) {
94                 sync_rate = tegra_dc_get_panel_sync_rate();
95
96                 if (sync_rate == 0)
97                         sync_rate = DEFAULT_SYNC_RATE;
98         }
99
100         target_frame_time = (unsigned int) (1000000000 / sync_rate);
101
102         pr_debug("%s: panel sync rate %d, target frame time %u\n",
103                 __func__, sync_rate, target_frame_time);
104 }
105
106 static int throughput_open(struct inode *inode, struct file *file)
107 {
108         spin_lock(&lock);
109
110         throughput_active_app_count++;
111         frame_time_sum_init = 1;
112
113         spin_unlock(&lock);
114
115
116         pr_debug("throughput_open node %p file %p\n", inode, file);
117
118         return 0;
119 }
120
121 static int throughput_release(struct inode *inode, struct file *file)
122 {
123         spin_lock(&lock);
124
125         throughput_active_app_count--;
126         frame_time_sum_init = 1;
127
128         if (throughput_active_app_count == 1)
129                 reset_target_frame_time();
130
131         spin_unlock(&lock);
132
133         pr_debug("throughput_release node %p file %p\n", inode, file);
134
135         return 0;
136 }
137
138 static int throughput_set_target_fps(unsigned long arg)
139 {
140         pr_debug("%s: target fps %lu requested\n", __func__, arg);
141
142         if (throughput_active_app_count != 1) {
143                 pr_debug("%s: %d active apps, disabling fps usage\n",
144                         __func__, throughput_active_app_count);
145                 return 0;
146         }
147
148         if (arg == 0)
149                 reset_target_frame_time();
150         else
151                 target_frame_time = (unsigned int) (1000000 / arg);
152
153         return 0;
154 }
155
156 static long
157 throughput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
158 {
159         int err = 0;
160
161         if ((_IOC_TYPE(cmd) != TEGRA_THROUGHPUT_MAGIC) ||
162                 (_IOC_NR(cmd) == 0) ||
163                 (_IOC_NR(cmd) > TEGRA_THROUGHPUT_IOCTL_MAXNR))
164                 return -EFAULT;
165
166         switch (cmd) {
167         case TEGRA_THROUGHPUT_IOCTL_TARGET_FPS:
168                 pr_debug("%s: TEGRA_THROUGHPUT_IOCTL_TARGET_FPS %lu\n",
169                         __func__, arg);
170                 err = throughput_set_target_fps(arg);
171                 break;
172
173         default:
174                 err = -ENOTTY;
175         }
176
177         return err;
178 }
179
180 static const struct file_operations throughput_user_fops = {
181         .owner                  = THIS_MODULE,
182         .open                   = throughput_open,
183         .release                = throughput_release,
184         .unlocked_ioctl         = throughput_ioctl,
185 };
186
187 #define TEGRA_THROUGHPUT_MINOR 1
188
189 static struct miscdevice throughput_miscdev = {
190         .minor = TEGRA_THROUGHPUT_MINOR,
191         .name  = "tegra-throughput",
192         .fops  = &throughput_user_fops,
193         .mode  = 0666,
194 };
195
196 static ssize_t show_fps(struct kobject *kobj,
197         struct attribute *attr, char *buf)
198 {
199         int frame_time_avg;
200         ktime_t now;
201         long timediff;
202         int fps = 0;
203
204         if (frame_time_sum_init)
205                 goto DONE;
206
207         now = ktime_get();
208         timediff = (long) ktime_us_delta(now, last_flip);
209         if (timediff > 1000000)
210                 goto DONE;
211
212         frame_time_avg = frame_time_sum / EMA_PERIOD;
213         fps = frame_time_avg > 0 ? 1000000 / frame_time_avg : 0;
214
215 DONE:
216         return sprintf(buf, "%d\n", fps);
217 }
218
219 static struct global_attr fps_attr = __ATTR(fps, 0444,
220                 show_fps, NULL);
221
222 int __init throughput_init_miscdev(void)
223 {
224         int ret;
225
226         pr_debug("%s: initializing\n", __func__);
227
228         spin_lock_init(&lock);
229         INIT_WORK(&work, set_throughput_hint);
230
231         ret = misc_register(&throughput_miscdev);
232         if (ret) {
233                 pr_err("can\'t reigster throughput miscdev"
234                        " (minor %d err %d)\n", TEGRA_THROUGHPUT_MINOR, ret);
235                 return ret;
236         }
237
238         ret = sysfs_create_file(&throughput_miscdev.this_device->kobj,
239                 &fps_attr.attr);
240         if (ret)
241                 pr_err("%s: error %d creating sysfs node\n", __func__, ret);
242
243         tegra_dc_set_flip_callback(throughput_flip_callback);
244
245         return 0;
246 }
247
248 module_init(throughput_init_miscdev);
249
250 void __exit throughput_exit_miscdev(void)
251 {
252         pr_debug("%s: exiting\n", __func__);
253
254         tegra_dc_unset_flip_callback();
255
256         cancel_work_sync(&work);
257
258         sysfs_remove_file(&throughput_miscdev.this_device->kobj, &fps_attr.attr);
259
260         misc_deregister(&throughput_miscdev);
261 }
262
263 module_exit(throughput_exit_miscdev);
264