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