13b1c47178de9b40cd744c9f9cbcccfbbc9ea1da
[linux-2.6.git] / drivers / misc / tegra-throughput.c
1 /*
2  * drivers/misc/throughput.c
3  *
4  * Copyright (C) 2012, 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/notifier.h>
23 #include <linux/miscdevice.h>
24 #include <linux/fs.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 static struct work_struct work;
41 static int throughput_hint;
42
43 static void set_throughput_hint(struct work_struct *work)
44 {
45         /* notify throughput hint clients here */
46         nvhost_scale3d_set_throughput_hint(throughput_hint);
47 }
48
49 static int throughput_flip_notifier(struct notifier_block *nb,
50                                              unsigned long val,
51                                              void *data)
52 {
53         /* only register flips when a single display is active */
54         if (val != 1 || multiple_app_disable)
55                 return NOTIFY_DONE;
56         else {
57                 long timediff;
58                 ktime_t now;
59
60                 now = ktime_get();
61                 if (last_flip.tv64 != 0) {
62                         timediff = (long) ktime_us_delta(now, last_flip);
63                         if (timediff > (long) USHRT_MAX)
64                                 last_frame_time = USHRT_MAX;
65                         else
66                                 last_frame_time = (unsigned short) timediff;
67
68                         if (last_frame_time == 0) {
69                                 pr_warn("%s: notifications %lld nsec apart\n",
70                                         __func__, now.tv64 - last_flip.tv64);
71                                 return NOTIFY_DONE;
72                         }
73
74                         throughput_hint =
75                                 ((int) target_frame_time * 100)/last_frame_time;
76
77                         if (!work_pending(&work))
78                                 schedule_work(&work);
79                 }
80                 last_flip = now;
81         }
82
83         return NOTIFY_OK;
84 }
85
86 static struct notifier_block throughput_flip_nb = {
87         .notifier_call = throughput_flip_notifier,
88 };
89
90 static int sync_rate;
91 static int throughput_active_app_count;
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 short) (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 notifier_initialized;
109
110 static int throughput_open(struct inode *inode, struct file *file)
111 {
112         int need_init = 0;
113
114         spin_lock(&lock);
115
116         if (!notifier_initialized) {
117                 notifier_initialized = 1;
118                 need_init = 1;
119         }
120
121         throughput_active_app_count++;
122         if (throughput_active_app_count > 1)
123                 multiple_app_disable = 1;
124
125         spin_unlock(&lock);
126
127         if (need_init)
128                 tegra_dc_register_flip_notifier(&throughput_flip_nb);
129
130         pr_debug("throughput_open node %p file %p\n", inode, file);
131
132         return 0;
133 }
134
135 static int throughput_release(struct inode *inode, struct file *file)
136 {
137         int need_deinit = 0;
138
139         spin_lock(&lock);
140
141         throughput_active_app_count--;
142         if (throughput_active_app_count == 0) {
143                 reset_target_frame_time();
144                 multiple_app_disable = 0;
145                 notifier_initialized = 0;
146                 need_deinit = 1;
147         }
148
149         spin_unlock(&lock);
150
151         if (need_deinit)
152                 tegra_dc_unregister_flip_notifier(&throughput_flip_nb);
153
154         pr_debug("throughput_release node %p file %p\n", inode, file);
155
156         return 0;
157 }
158
159 static int throughput_set_target_fps(unsigned long arg)
160 {
161         int disable;
162
163         pr_debug("%s: target fps %lu requested\n", __func__, arg);
164
165         disable = multiple_app_disable;
166
167         if (disable) {
168                 pr_debug("%s: %d active apps, disabling fps usage\n",
169                         __func__, throughput_active_app_count);
170                 return 0;
171         }
172
173         if (arg == 0)
174                 reset_target_frame_time();
175         else {
176                 unsigned long frame_time = (1000000 / arg);
177
178                 if (frame_time > USHRT_MAX)
179                         frame_time = USHRT_MAX;
180
181                 target_frame_time = (unsigned short) frame_time;
182         }
183
184         return 0;
185 }
186
187 static long
188 throughput_ioctl(struct file *file,
189                           unsigned int cmd,
190                           unsigned long arg)
191 {
192         int err = 0;
193
194         if ((_IOC_TYPE(cmd) != TEGRA_THROUGHPUT_MAGIC) ||
195                 (_IOC_NR(cmd) == 0) ||
196                 (_IOC_NR(cmd) > TEGRA_THROUGHPUT_IOCTL_MAXNR))
197                 return -EFAULT;
198
199         switch (cmd) {
200         case TEGRA_THROUGHPUT_IOCTL_TARGET_FPS:
201                 pr_debug("%s: TEGRA_THROUGHPUT_IOCTL_TARGET_FPS %lu\n",
202                         __func__, arg);
203                 err = throughput_set_target_fps(arg);
204                 break;
205
206         default:
207                 err = -ENOTTY;
208         }
209
210         return err;
211 }
212
213 static const struct file_operations throughput_user_fops = {
214         .owner                  = THIS_MODULE,
215         .open                   = throughput_open,
216         .release                = throughput_release,
217         .unlocked_ioctl         = throughput_ioctl,
218 };
219
220 #define TEGRA_THROUGHPUT_MINOR 1
221
222 static struct miscdevice throughput_miscdev = {
223         .minor  = TEGRA_THROUGHPUT_MINOR,
224         .name   = "tegra-throughput",
225         .fops   = &throughput_user_fops,
226         .mode   = 0666,
227 };
228
229 int __init throughput_init_miscdev(void)
230 {
231         int ret;
232
233         pr_debug("%s: initializing\n", __func__);
234
235         spin_lock_init(&lock);
236         INIT_WORK(&work, set_throughput_hint);
237
238         ret = misc_register(&throughput_miscdev);
239         if (ret) {
240                 pr_err("can\'t reigster throughput miscdev"
241                        " (minor %d err %d)\n", TEGRA_THROUGHPUT_MINOR, ret);
242                 return ret;
243         }
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         cancel_work_sync(&work);
255
256         misc_deregister(&throughput_miscdev);
257 }
258
259 module_exit(throughput_exit_miscdev);
260