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