misc: nct1008: change overheat enable message
[linux-3.10.git] / drivers / misc / tegra-throughput.c
1 /*
2  * drivers/misc/throughput.c
3  *
4  * Copyright (c) 2012-2013, 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/debugfs.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 <linux/notifier.h>
31 #include <linux/tegra-throughput.h>
32 #include <mach/dc.h>
33
34 #define DEFAULT_SYNC_RATE 60000 /* 60 Hz */
35
36 static unsigned int target_frame_time;
37 static ktime_t last_flip;
38 static spinlock_t lock;
39
40 #define EMA_PERIOD  8
41
42 static int frame_time_sum_init = 1;
43 static long frame_time_sum; /* used for fps EMA */
44
45 static struct work_struct work;
46 static int throughput_hint;
47
48 static int sync_rate;
49 static int throughput_active_app_count;
50
51 BLOCKING_NOTIFIER_HEAD(throughput_notifier_list);
52 EXPORT_SYMBOL(throughput_notifier_list);
53
54 static void set_throughput_hint(struct work_struct *work)
55 {
56         /* notify throughput hint clients here */
57         blocking_notifier_call_chain(&throughput_notifier_list,
58                                      throughput_hint, NULL);
59 }
60
61 int tegra_throughput_get_hint(void)
62 {
63         return throughput_hint;
64 }
65 EXPORT_SYMBOL(tegra_throughput_get_hint);
66
67 static void throughput_flip_callback(void)
68 {
69         long timediff;
70         ktime_t now;
71
72         now = ktime_get();
73
74         if (last_flip.tv64 != 0) {
75                 timediff = (long) ktime_us_delta(now, last_flip);
76
77                 if (timediff <= 0) {
78                         pr_warn("%s: flips %lld nsec apart\n",
79                                 __func__, now.tv64 - last_flip.tv64);
80                         last_flip = now;
81                         return;
82                 }
83
84                 throughput_hint =
85                         ((int) target_frame_time * 1000) / timediff;
86
87                 /* only deliver throughput hints when a single app is active */
88                 if (throughput_active_app_count == 1 && !work_pending(&work))
89                         schedule_work(&work);
90
91                 if (frame_time_sum_init) {
92                         frame_time_sum = timediff * EMA_PERIOD;
93                         frame_time_sum_init = 0;
94                 } else {
95                         int t = (frame_time_sum / EMA_PERIOD) *
96                                 (EMA_PERIOD - 1);
97                         frame_time_sum = t + timediff;
98                 }
99         }
100
101         last_flip = now;
102 }
103
104 static void reset_target_frame_time(void)
105 {
106         if (sync_rate == 0) {
107                 sync_rate = tegra_dc_get_panel_sync_rate();
108
109                 if (sync_rate == 0)
110                         sync_rate = DEFAULT_SYNC_RATE;
111         }
112
113         target_frame_time = (unsigned int) (1000000000 / sync_rate);
114
115         pr_debug("%s: panel sync rate %d, target frame time %u\n",
116                 __func__, sync_rate, target_frame_time);
117 }
118
119 static int throughput_open(struct inode *inode, struct file *file)
120 {
121         spin_lock(&lock);
122
123         throughput_active_app_count++;
124         frame_time_sum_init = 1;
125
126         spin_unlock(&lock);
127
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         spin_lock(&lock);
137
138         throughput_active_app_count--;
139         frame_time_sum_init = 1;
140
141         if (throughput_active_app_count == 1)
142                 reset_target_frame_time();
143
144         spin_unlock(&lock);
145
146         pr_debug("throughput_release node %p file %p\n", inode, file);
147
148         return 0;
149 }
150
151 static int throughput_set_target_fps(unsigned long arg)
152 {
153         pr_debug("%s: target fps %lu requested\n", __func__, arg);
154
155         if (throughput_active_app_count != 1) {
156                 pr_debug("%s: %d active apps, disabling fps usage\n",
157                         __func__, throughput_active_app_count);
158                 return 0;
159         }
160
161         if (arg == 0)
162                 reset_target_frame_time();
163         else
164                 target_frame_time = (unsigned int) (1000000 / arg);
165
166         return 0;
167 }
168
169 static long
170 throughput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
171 {
172         int err = 0;
173
174         if ((_IOC_TYPE(cmd) != TEGRA_THROUGHPUT_MAGIC) ||
175                 (_IOC_NR(cmd) == 0) ||
176                 (_IOC_NR(cmd) > TEGRA_THROUGHPUT_IOCTL_MAXNR))
177                 return -EFAULT;
178
179         switch (cmd) {
180         case TEGRA_THROUGHPUT_IOCTL_TARGET_FPS:
181                 pr_debug("%s: TEGRA_THROUGHPUT_IOCTL_TARGET_FPS %lu\n",
182                         __func__, arg);
183                 err = throughput_set_target_fps(arg);
184                 break;
185
186         default:
187                 err = -ENOTTY;
188         }
189
190         return err;
191 }
192
193 static const struct file_operations throughput_user_fops = {
194         .owner                  = THIS_MODULE,
195         .open                   = throughput_open,
196         .release                = throughput_release,
197         .unlocked_ioctl         = throughput_ioctl,
198 };
199
200 #define TEGRA_THROUGHPUT_MINOR 1
201
202 static struct miscdevice throughput_miscdev = {
203         .minor = TEGRA_THROUGHPUT_MINOR,
204         .name  = "tegra-throughput",
205         .fops  = &throughput_user_fops,
206         .mode  = 0666,
207 };
208
209 static ssize_t show_fps(struct kobject *kobj,
210         struct attribute *attr, char *buf)
211 {
212         int frame_time_avg;
213         ktime_t now;
214         long timediff;
215         int fps = 0;
216
217         if (frame_time_sum_init)
218                 goto DONE;
219
220         now = ktime_get();
221         timediff = (long) ktime_us_delta(now, last_flip);
222         if (timediff > 1000000)
223                 goto DONE;
224
225         frame_time_avg = frame_time_sum / EMA_PERIOD;
226         fps = frame_time_avg > 0 ? 1000000 / frame_time_avg : 0;
227
228 DONE:
229         return sprintf(buf, "%d\n", fps);
230 }
231
232 static struct global_attr fps_attr = __ATTR(fps, 0444,
233                 show_fps, NULL);
234
235 int __init throughput_init_miscdev(void)
236 {
237         int ret;
238
239         pr_debug("%s: initializing\n", __func__);
240
241         spin_lock_init(&lock);
242         INIT_WORK(&work, set_throughput_hint);
243
244         ret = misc_register(&throughput_miscdev);
245         if (ret) {
246                 pr_err("can\'t reigster throughput miscdev"
247                        " (minor %d err %d)\n", TEGRA_THROUGHPUT_MINOR, ret);
248                 return ret;
249         }
250
251         ret = sysfs_create_file(&throughput_miscdev.this_device->kobj,
252                 &fps_attr.attr);
253         if (ret)
254                 pr_err("%s: error %d creating sysfs node\n", __func__, ret);
255
256         tegra_dc_set_flip_callback(throughput_flip_callback);
257
258         return 0;
259 }
260
261 module_init(throughput_init_miscdev);
262
263 void __exit throughput_exit_miscdev(void)
264 {
265         pr_debug("%s: exiting\n", __func__);
266
267         tegra_dc_unset_flip_callback();
268
269         cancel_work_sync(&work);
270
271         sysfs_remove_file(&throughput_miscdev.this_device->kobj, &fps_attr.attr);
272
273         misc_deregister(&throughput_miscdev);
274 }
275
276 module_exit(throughput_exit_miscdev);
277