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