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