support the tdm dynamic fps and the dpms on/off event
[platform/core/uifw/libtdm.git] / src / tdm_vblank.c
1 /**************************************************************************
2  *
3  * libtdm
4  *
5  * Copyright 2015 Samsung Electronics co., Ltd. All Rights Reserved.
6  *
7  * Contact: Eunchul Kim <chulspro.kim@samsung.com>,
8  *          JinYoung Jeon <jy0.jeon@samsung.com>,
9  *          Taeheon Kim <th908.kim@samsung.com>,
10  *          YoungJun Cho <yj44.cho@samsung.com>,
11  *          SooChan Lim <sc1.lim@samsung.com>,
12  *          Boram Park <sc1.lim@samsung.com>
13  *
14  * Permission is hereby granted, free of charge, to any person obtaining a
15  * copy of this software and associated documentation files (the
16  * "Software"), to deal in the Software without restriction, including
17  * without limitation the rights to use, copy, modify, merge, publish,
18  * distribute, sub license, and/or sell copies of the Software, and to
19  * permit persons to whom the Software is furnished to do so, subject to
20  * the following conditions:
21  *
22  * The above copyright notice and this permission notice (including the
23  * next paragraph) shall be included in all copies or substantial portions
24  * of the Software.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
27  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
29  * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR
30  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
31  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
32  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33  *
34 **************************************************************************/
35
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
39
40 #include "tdm.h"
41 #include "tdm_private.h"
42 #include "tdm_list.h"
43
44 /* CAUTION:
45  * - tdm vblank doesn't care about thread things.
46  * - DO NOT use the TDM internal functions here.
47  *     However, the internal function which does lock/unlock the mutex of
48  *     private_display in itself can be called.
49  * - DO NOT use the tdm_private_display structure here.
50  */
51
52 /* TDM vblank
53  * - aligned by HW vblank
54  * - use a tdm_event_loop_source object only.
55  */
56
57 #define VER(fmt,arg...)   TDM_ERR("[%p] "fmt, private_vblank, ##arg)
58 #define VWR(fmt,arg...)   TDM_WRN("[%p] "fmt, private_vblank, ##arg)
59 #define VIN(fmt,arg...)   TDM_INFO("[%p] "fmt, private_vblank, ##arg)
60 #define VDB(fmt,arg...)   TDM_DBG("[%p] "fmt, private_vblank, ##arg)
61
62 typedef struct _tdm_vblank_wait_info tdm_vblank_wait_info;
63
64 typedef struct _tdm_private_vblank {
65         struct list_head link;
66
67         tdm_display *dpy;
68         tdm_output *output;
69         tdm_output_dpms dpms;
70         unsigned int vrefresh;
71
72         unsigned int check_HW_or_SW;
73         unsigned int fps;
74         int offset;
75         unsigned int enable_fake;
76
77         double vblank_gap;
78         unsigned int last_seq;
79         unsigned int last_tv_sec;
80         unsigned int last_tv_usec;
81
82         /* for HW */
83         double HW_vblank_gap;
84         unsigned int HW_enable;
85         unsigned int HW_quotient;
86         struct list_head HW_wait_list;
87
88         /* for SW */
89         tdm_event_loop_source *SW_timer;
90         struct list_head SW_pending_wait_list;
91         struct list_head SW_wait_list;
92 #if 0
93         tdm_vblank_wait_info *SW_align_wait;
94         double SW_align_offset;
95         unsigned int SW_align_sec;
96         unsigned int SW_align_usec;
97 #endif
98 } tdm_private_vblank;
99
100 struct _tdm_vblank_wait_info {
101         struct list_head link;
102         struct list_head valid_link;
103
104         unsigned int stamp;
105
106         unsigned int req_sec;
107         unsigned int req_usec;
108         unsigned int interval;
109
110         tdm_vblank_handler func;
111         void *user_data;
112         tdm_private_vblank *private_vblank;
113
114         /* target_sec can be 0 when last_tv_sec is 0 because we can't calculate
115          * target_sec without last_tv_sec. So we have to call tdm_output_wait_vblank
116          * to fill last_tv_sec at the first time.
117          */
118         unsigned int target_sec;
119         unsigned int target_usec;
120         unsigned int target_seq;
121         int target_hw_interval;
122 };
123
124 static struct list_head vblank_list;
125 static struct list_head valid_wait_list;
126 static unsigned int vblank_list_inited;
127 static unsigned int stamp = 0;
128
129 static tdm_error _tdm_vblank_wait_SW(tdm_vblank_wait_info *wait_info);
130 #if 0
131 static void _tdm_vblank_sw_timer_align(tdm_private_vblank *private_vblank);
132 #endif
133
134 #if 0
135 static void
136 _print_list(struct list_head *list)
137 {
138         tdm_vblank_wait_info *w = NULL;
139
140         LIST_FOR_EACH_ENTRY(w, list, link) {
141                 printf(" %d", w->interval);
142         }
143         printf("\n");
144 }
145 #endif
146
147 static inline unsigned int
148 _tdm_vblank_check_valid(tdm_vblank_wait_info *wait_info)
149 {
150         tdm_vblank_wait_info *w = NULL;
151
152         if (!wait_info)
153                 return 0;
154
155         LIST_FOR_EACH_ENTRY(w, &valid_wait_list, valid_link) {
156                 if (w->stamp == wait_info->stamp)
157                         return 1;
158         }
159
160         return 0;
161 }
162
163 static inline unsigned int
164 _tdm_vblank_find_wait(tdm_vblank_wait_info *wait_info, struct list_head *list)
165 {
166         tdm_vblank_wait_info *w = NULL;
167
168         if (!wait_info)
169                 return 0;
170
171         LIST_FOR_EACH_ENTRY(w, list, link) {
172                 if (w->stamp == wait_info->stamp)
173                         return 1;
174         }
175
176         return 0;
177 }
178
179 static inline void
180 _tdm_vblank_insert_wait(tdm_vblank_wait_info *wait_info, struct list_head *list)
181 {
182         tdm_vblank_wait_info *w = NULL;
183
184         LIST_FOR_EACH_ENTRY_REV(w, list, link) {
185                 /* If last_tv_sec == 0, we can't calculate target_sec. */
186                 if (wait_info->target_sec == 0) {
187                         if (w->interval > wait_info->interval)
188                                 continue;
189                         list = &w->link;
190                         break;
191                 } else {
192                         if (w->target_sec > wait_info->target_sec)
193                                 continue;
194                         if (w->target_usec > wait_info->target_usec)
195                                 continue;
196                         list = &w->link;
197                 }
198                 break;
199         }
200
201         LIST_ADDTAIL(&wait_info->link, list->next);
202 }
203
204 static void
205 _tdm_vblank_change_to_SW(tdm_private_vblank *private_vblank)
206 {
207         tdm_vblank_wait_info *w = NULL, *ww = NULL;
208
209         VIN("Change to SW");
210
211         LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->HW_wait_list, link) {
212                 LIST_DEL(&w->link);
213                 _tdm_vblank_wait_SW(w);
214         }
215 }
216
217 static void
218 _tdm_vblank_free_HW_wait(tdm_private_vblank *private_vblank, tdm_error error, unsigned int call_cb)
219 {
220         tdm_vblank_wait_info *w = NULL, *ww = NULL;
221
222         LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->HW_wait_list, link) {
223                 LIST_DEL(&w->link);
224                 LIST_DEL(&w->valid_link);
225
226                 if (call_cb && w->func)
227                         w->func(private_vblank, error, 0, 0, 0, w->user_data);
228
229                 free(w);
230         }
231 }
232
233 static void
234 _tdm_vblank_cb_output_change(tdm_output *output, tdm_output_change_type type,
235                                                          tdm_value value, void *user_data)
236 {
237         tdm_private_vblank *private_vblank = user_data;
238
239         TDM_RETURN_IF_FAIL(private_vblank != NULL);
240
241         switch (type) {
242         case TDM_OUTPUT_CHANGE_DPMS:
243                 if (private_vblank->dpms == value.u32)
244                         break;
245                 VIN("dpms %s", tdm_dpms_str(value.u32));
246                 private_vblank->dpms = value.u32;
247                 private_vblank->check_HW_or_SW = 1;
248                 if (private_vblank->dpms != TDM_OUTPUT_DPMS_ON) {
249 #if 0
250                         if (private_vblank->SW_align_wait) {
251                                 LIST_DEL(&private_vblank->SW_align_wait->valid_link);
252                                 free(private_vblank->SW_align_wait);
253                                 private_vblank->SW_align_wait = NULL;
254                         }
255 #endif
256
257                         if (private_vblank->enable_fake)
258                                 _tdm_vblank_change_to_SW(private_vblank);
259                         else
260                                 _tdm_vblank_free_HW_wait(private_vblank, TDM_ERROR_DPMS_OFF, 1);
261                 }
262                 break;
263         case TDM_OUTPUT_CHANGE_CONNECTION:
264                 VIN("output %s", tdm_status_str(value.u32));
265                 if (value.u32 == TDM_OUTPUT_CONN_STATUS_DISCONNECTED)
266                         _tdm_vblank_free_HW_wait(private_vblank, 0, 0);
267                 break;
268         default:
269                 break;
270         }
271 }
272
273 tdm_vblank*
274 tdm_vblank_create(tdm_display *dpy, tdm_output *output, tdm_error *error)
275 {
276         tdm_private_vblank *private_vblank;
277         const tdm_output_mode *mode = NULL;
278         tdm_output_dpms dpms = TDM_OUTPUT_DPMS_ON;
279         tdm_error ret;
280
281         TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(dpy != NULL, TDM_ERROR_INVALID_PARAMETER, NULL);
282         TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(output != NULL, TDM_ERROR_INVALID_PARAMETER, NULL);
283
284         if (error)
285                 *error = TDM_ERROR_NONE;
286
287         if (!vblank_list_inited) {
288                 LIST_INITHEAD(&vblank_list);
289                 LIST_INITHEAD(&valid_wait_list);
290                 vblank_list_inited = 1;
291         }
292
293         tdm_output_get_mode(output, &mode);
294         if (!mode) {
295                 if (error)
296                         *error = TDM_ERROR_OPERATION_FAILED;
297                 TDM_ERR("no mode");
298                 return NULL;
299         }
300
301         tdm_output_get_dpms(output, &dpms);
302
303         private_vblank = calloc(1, sizeof *private_vblank);
304         if (!private_vblank) {
305                 if (error)
306                         *error = TDM_ERROR_OUT_OF_MEMORY;
307                 VER("alloc failed");
308                 return NULL;
309         }
310
311         tdm_output_add_change_handler(output, _tdm_vblank_cb_output_change, private_vblank);
312
313         private_vblank->dpy = dpy;
314         private_vblank->output = output;
315         private_vblank->dpms = dpms;
316         private_vblank->vrefresh = mode->vrefresh;
317         private_vblank->HW_vblank_gap = (double)1000000 / private_vblank->vrefresh;
318
319         private_vblank->check_HW_or_SW = 1;
320         private_vblank->fps = mode->vrefresh;
321
322         LIST_INITHEAD(&private_vblank->HW_wait_list);
323
324         LIST_INITHEAD(&private_vblank->SW_pending_wait_list);
325         LIST_INITHEAD(&private_vblank->SW_wait_list);
326
327         LIST_ADD(&private_vblank->link, &vblank_list);
328
329         VDB("created. vrefresh(%d) dpms(%d)",
330                 private_vblank->vrefresh, private_vblank->dpms);
331
332         return (tdm_vblank*)private_vblank;
333 }
334
335 void
336 tdm_vblank_destroy(tdm_vblank *vblank)
337 {
338         tdm_private_vblank *private_vblank = vblank;
339         tdm_vblank_wait_info *w = NULL, *ww = NULL;
340
341         if (!private_vblank)
342                 return;
343
344         LIST_DEL(&private_vblank->link);
345
346 #if 0
347         if (private_vblank->SW_align_wait) {
348                 LIST_DEL(&private_vblank->SW_align_wait->valid_link);
349                 free(private_vblank->SW_align_wait);
350         }
351 #endif
352
353         if (private_vblank->SW_timer) {
354                 if (tdm_display_lock(private_vblank->dpy) == TDM_ERROR_NONE) {
355                         tdm_event_loop_source_remove(private_vblank->SW_timer);
356                         tdm_display_unlock(private_vblank->dpy);
357                 }
358         }
359
360         tdm_output_remove_change_handler(private_vblank->output,
361                                                                          _tdm_vblank_cb_output_change, private_vblank);
362
363         _tdm_vblank_free_HW_wait(private_vblank, 0, 0);
364
365         LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->SW_pending_wait_list, link) {
366                 LIST_DEL(&w->link);
367                 LIST_DEL(&w->valid_link);
368                 free(w);
369         }
370
371         LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->SW_wait_list, link) {
372                 LIST_DEL(&w->link);
373                 LIST_DEL(&w->valid_link);
374                 free(w);
375         }
376
377         VIN("destroyed");
378
379         free(private_vblank);
380 }
381
382 tdm_error
383 tdm_vblank_set_fps(tdm_vblank *vblank, unsigned int fps)
384 {
385         tdm_private_vblank *private_vblank = vblank;
386
387         TDM_RETURN_VAL_IF_FAIL(private_vblank != NULL, TDM_ERROR_INVALID_PARAMETER);
388         TDM_RETURN_VAL_IF_FAIL(fps > 0, TDM_ERROR_INVALID_PARAMETER);
389
390         if (private_vblank->fps == fps)
391                 return TDM_ERROR_NONE;
392
393         private_vblank->fps = fps;
394         private_vblank->check_HW_or_SW = 1;
395
396         VDB("fps(%d)", private_vblank->fps);
397
398         return TDM_ERROR_NONE;
399 }
400
401 tdm_error
402 tdm_vblank_set_offset(tdm_vblank *vblank, int offset)
403 {
404         tdm_private_vblank *private_vblank = vblank;
405
406         TDM_RETURN_VAL_IF_FAIL(private_vblank != NULL, TDM_ERROR_INVALID_PARAMETER);
407
408         if (private_vblank->offset == offset)
409                 return TDM_ERROR_NONE;
410
411         private_vblank->offset = offset;
412         private_vblank->check_HW_or_SW = 1;
413
414         VDB("offset(%d)", private_vblank->offset);
415
416         return TDM_ERROR_NONE;
417 }
418
419 tdm_error
420 tdm_vblank_set_enable_fake(tdm_vblank *vblank, unsigned int enable_fake)
421 {
422         tdm_private_vblank *private_vblank = vblank;
423
424         TDM_RETURN_VAL_IF_FAIL(private_vblank != NULL, TDM_ERROR_INVALID_PARAMETER);
425
426         if (private_vblank->enable_fake == enable_fake)
427                 return TDM_ERROR_NONE;
428
429         private_vblank->enable_fake = enable_fake;
430
431         VDB("enable_fake(%d)", private_vblank->enable_fake);
432
433         return TDM_ERROR_NONE;
434 }
435
436 static void
437 _tdm_vblank_cb_vblank_HW(tdm_output *output, unsigned int sequence,
438                                                  unsigned int tv_sec, unsigned int tv_usec,
439                                                  void *user_data)
440 {
441         tdm_vblank_wait_info *wait_info = user_data;
442         tdm_private_vblank *private_vblank;
443
444         if (!_tdm_vblank_check_valid(wait_info)) {
445                 TDM_DBG("can't find wait(%p) from valid_wait_list", wait_info);
446                 return;
447         }
448
449         private_vblank = wait_info->private_vblank;
450         TDM_RETURN_IF_FAIL(private_vblank != NULL);
451
452         if (!_tdm_vblank_find_wait(wait_info, &private_vblank->HW_wait_list)) {
453                 VDB("can't find wait(%p)", wait_info);
454                 return;
455         }
456
457         LIST_DEL(&wait_info->link);
458         LIST_DEL(&wait_info->valid_link);
459
460         private_vblank->last_seq = wait_info->target_seq;
461         private_vblank->last_tv_sec = tv_sec;
462         private_vblank->last_tv_usec = tv_usec;
463
464         if (wait_info->func)
465                 wait_info->func(private_vblank, TDM_ERROR_NONE, private_vblank->last_seq,
466                                                 tv_sec, tv_usec, wait_info->user_data);
467
468         VDB("wait(%p) done", wait_info);
469
470         free(wait_info);
471 }
472
473 static tdm_error
474 _tdm_vblank_wait_HW(tdm_vblank_wait_info *wait_info)
475 {
476         tdm_private_vblank *private_vblank = wait_info->private_vblank;
477         tdm_error ret;
478
479         TDM_RETURN_VAL_IF_FAIL(wait_info->target_hw_interval > 0, TDM_ERROR_OPERATION_FAILED);
480
481         _tdm_vblank_insert_wait(wait_info, &private_vblank->HW_wait_list);
482
483         ret = tdm_output_wait_vblank(private_vblank->output, wait_info->target_hw_interval, 0,
484                                                                  _tdm_vblank_cb_vblank_HW, wait_info);
485
486         if (ret != TDM_ERROR_NONE) {
487                 VER("wait(%p) failed", wait_info);
488                 LIST_DEL(&wait_info->link);
489                 return ret;
490         }
491
492         VDB("wait(%p) waiting", wait_info);
493
494         return TDM_ERROR_NONE;
495 }
496
497 static tdm_error
498 _tdm_vblank_cb_vblank_SW(void *user_data)
499 {
500         tdm_private_vblank *private_vblank = user_data;
501         tdm_vblank_wait_info *first_wait_info = NULL, *w = NULL, *ww = NULL;
502
503         TDM_RETURN_VAL_IF_FAIL(private_vblank != NULL, TDM_ERROR_OPERATION_FAILED);
504
505         first_wait_info = container_of(private_vblank->SW_wait_list.next, first_wait_info, link);
506         TDM_RETURN_VAL_IF_FAIL(first_wait_info != NULL, TDM_ERROR_OPERATION_FAILED);
507
508         VDB("wait(%p) done", first_wait_info);
509
510         private_vblank->last_seq = first_wait_info->target_seq;
511         private_vblank->last_tv_sec = first_wait_info->target_sec;
512         private_vblank->last_tv_usec = first_wait_info->target_usec;
513
514         LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->SW_wait_list, link) {
515                 if (w->target_sec != first_wait_info->target_sec ||
516                         w->target_usec != first_wait_info->target_usec)
517                         break;
518
519                 LIST_DEL(&w->link);
520                 LIST_DEL(&w->valid_link);
521
522                 if (w->func)
523                         w->func(private_vblank, TDM_ERROR_NONE, w->target_seq,
524                                         w->target_sec, w->target_usec,
525                                         w->user_data);
526
527                 free(w);
528         }
529
530         return TDM_ERROR_NONE;
531 }
532
533 static void
534 _tdm_vblank_cb_vblank_SW_first(tdm_output *output, unsigned int sequence,
535                                                            unsigned int tv_sec, unsigned int tv_usec,
536                                                            void *user_data)
537 {
538         tdm_vblank_wait_info *wait_info = user_data;
539         tdm_private_vblank *private_vblank;
540         tdm_vblank_wait_info *w = NULL, *ww = NULL;
541         unsigned int min_interval = 0;
542         unsigned long last;
543
544         if (!_tdm_vblank_check_valid(wait_info))
545                 return;
546
547         private_vblank = wait_info->private_vblank;
548         TDM_RETURN_IF_FAIL(private_vblank != NULL);
549
550         w = container_of((&private_vblank->SW_pending_wait_list)->next, w, link);
551         TDM_RETURN_IF_FAIL(w != NULL);
552
553         VDB("wait(%p) done", w);
554
555         min_interval = w->interval;
556
557         last = (unsigned long)tv_sec * 1000000 + tv_usec;
558         last -= private_vblank->offset * 1000;
559
560         private_vblank->last_seq = min_interval;
561         private_vblank->last_tv_sec = last / 1000000;
562         private_vblank->last_tv_usec = last % 1000000;
563
564         LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->SW_pending_wait_list, link) {
565                 if (w->interval == min_interval) {
566                         LIST_DEL(&w->link);
567                         LIST_DEL(&w->valid_link);
568
569                         if (w->func)
570                                 w->func(private_vblank, TDM_ERROR_NONE, private_vblank->last_seq,
571                                                 tv_sec, tv_usec, w->user_data);
572                         free(w);
573                 } else {
574                         LIST_DEL(&w->link);
575                         w->interval -= min_interval;
576                         _tdm_vblank_wait_SW(w);
577                 }
578         }
579 }
580
581 static tdm_error
582 _tdm_vblank_sw_timer_update(tdm_private_vblank *private_vblank)
583 {
584         tdm_vblank_wait_info *first_wait_info = NULL;
585         unsigned long curr, target;
586         int ms_delay;
587         tdm_error ret;
588
589         first_wait_info = container_of(private_vblank->SW_wait_list.next, first_wait_info, link);
590         curr = tdm_helper_get_time_in_micros();
591         target = first_wait_info->target_sec * 1000000 + first_wait_info->target_usec;
592
593         if (target < curr)
594                 ms_delay = 1;
595         else
596                 ms_delay = ceil((double)(target - curr) / 1000);
597
598         if (ms_delay < 1)
599                 ms_delay = 1;
600
601         VDB("wait(%p) curr(%4lu) target(%4lu) ms_delay(%d)",
602                 first_wait_info, curr, target, ms_delay);
603
604         ret = tdm_display_lock(private_vblank->dpy);
605         TDM_RETURN_VAL_IF_FAIL(ret == TDM_ERROR_NONE, ret);
606
607         if (!private_vblank->SW_timer) {
608                 private_vblank->SW_timer =
609                         tdm_event_loop_add_timer_handler(private_vblank->dpy,
610                                                                                          _tdm_vblank_cb_vblank_SW,
611                                                                                          private_vblank,
612                                                                                          &ret);
613                 if (!private_vblank->SW_timer) {
614                         tdm_display_unlock(private_vblank->dpy);
615                         VER("couldn't add timer");
616                         return ret;
617                 }
618                 VIN("Use SW vblank");
619         }
620
621         ret = tdm_event_loop_source_timer_update(private_vblank->SW_timer, ms_delay);
622         if (ret != TDM_ERROR_NONE) {
623                 tdm_display_unlock(private_vblank->dpy);
624                 VER("couldn't update timer");
625                 return ret;
626         }
627
628         tdm_display_unlock(private_vblank->dpy);
629
630         return TDM_ERROR_NONE;
631 }
632
633 #if 0
634 static void
635 _tdm_vblank_cb_vblank_align(tdm_output *output, unsigned int sequence,
636                                                         unsigned int tv_sec, unsigned int tv_usec,
637                                                         void *user_data)
638 {
639         tdm_vblank_wait_info *align_info = user_data;
640         tdm_private_vblank *private_vblank;
641         unsigned int diff_sec, diff_usec;
642
643         if (!_tdm_vblank_check_valid(align_info))
644                 return;
645
646         private_vblank = align_info->private_vblank;
647         TDM_RETURN_IF_FAIL(private_vblank != NULL);
648
649         private_vblank->SW_align_wait = NULL;
650         private_vblank->SW_align_sec = tv_sec;
651         private_vblank->SW_align_usec = tv_usec;
652
653         LIST_DEL(&align_info->valid_link);
654
655         if (tv_usec > align_info->req_usec) {
656                 diff_usec = tv_usec - align_info->req_usec;
657                 diff_sec = tv_sec - align_info->req_sec;
658         } else {
659                 diff_usec = 1000000 + tv_usec - align_info->req_usec;
660                 diff_sec = tv_sec - align_info->req_sec - 1;
661         }
662
663         private_vblank->SW_align_offset = (double)(1000000 - diff_sec * 1000000 - diff_usec) / private_vblank->vrefresh;
664
665         free(align_info);
666
667         /* align vblank continously only if non HW and DPMS on */
668         if (!private_vblank->HW_enable && private_vblank->dpms == TDM_OUTPUT_DPMS_ON)
669                 _tdm_vblank_sw_timer_align(private_vblank);
670 }
671
672 static void
673 _tdm_vblank_sw_timer_align(tdm_private_vblank *private_vblank)
674 {
675         tdm_vblank_wait_info *align_info;
676         unsigned long curr;
677         tdm_error ret;
678
679         if (private_vblank->SW_align_wait)
680                 return;
681
682         TDM_RETURN_IF_FAIL(private_vblank->dpms == TDM_OUTPUT_DPMS_ON);
683
684         align_info = calloc(1, sizeof *align_info);
685         if (!align_info) {
686                 VER("alloc failed");
687                 return;
688         }
689
690         LIST_ADDTAIL(&align_info->valid_link, &valid_wait_list);
691         align_info->stamp = ++stamp;
692         align_info->private_vblank = private_vblank;
693
694         curr = tdm_helper_get_time_in_micros();
695         align_info->req_sec = curr / 1000000;
696         align_info->req_usec = curr % 1000000;
697
698         ret = tdm_output_wait_vblank(private_vblank->output, private_vblank->vrefresh, 0,
699                                                                  _tdm_vblank_cb_vblank_align, align_info);
700         if (ret != TDM_ERROR_NONE) {
701                 LIST_DEL(&align_info->valid_link);
702                 free(align_info);
703                 return;
704         }
705
706         private_vblank->SW_align_wait = align_info;
707 }
708 #endif
709
710 static tdm_error
711 _tdm_vblank_wait_SW(tdm_vblank_wait_info *wait_info)
712 {
713         tdm_private_vblank *private_vblank = wait_info->private_vblank;
714         tdm_error ret;
715
716         if (private_vblank->last_tv_sec == 0 && private_vblank->dpms == TDM_OUTPUT_DPMS_ON) {
717                 unsigned int do_wait = LIST_IS_EMPTY(&private_vblank->SW_pending_wait_list);
718                 _tdm_vblank_insert_wait(wait_info, &private_vblank->SW_pending_wait_list);
719                 if (do_wait) {
720                         ret = tdm_output_wait_vblank(private_vblank->output, 1, 0,
721                                                                                  _tdm_vblank_cb_vblank_SW_first, wait_info);
722                         if (ret != TDM_ERROR_NONE) {
723                                 LIST_DEL(&wait_info->link);
724                                 return ret;
725                         }
726                         VDB("wait(%p) waiting", wait_info);
727                 }
728                 return TDM_ERROR_NONE;
729         }
730
731         TDM_RETURN_VAL_IF_FAIL(wait_info->target_sec > 0, TDM_ERROR_OPERATION_FAILED);
732
733         _tdm_vblank_insert_wait(wait_info, &private_vblank->SW_wait_list);
734
735         ret = _tdm_vblank_sw_timer_update(private_vblank);
736         if (ret != TDM_ERROR_NONE) {
737                 VER("couldn't update sw timer");
738                 return ret;
739         }
740
741         return TDM_ERROR_NONE;
742 }
743
744 static void
745 _tdm_vblank_calculate_target(tdm_vblank_wait_info *wait_info)
746 {
747         tdm_private_vblank *private_vblank = wait_info->private_vblank;
748         unsigned long last, prev, req, curr, target;
749         unsigned int skip = 0;
750
751         curr = tdm_helper_get_time_in_micros();
752
753         if (!private_vblank->HW_enable) {
754                 if (private_vblank->last_tv_sec == 0) {
755                         /* If last == 0 and DPMS == on, we will use HW vblank to sync with HW vblank. */
756                         if (private_vblank->dpms == TDM_OUTPUT_DPMS_ON) {
757                                 return;
758                         } else {
759                                 private_vblank->last_tv_sec = curr / 1000000;
760                                 private_vblank->last_tv_usec = curr % 1000000;
761                         }
762                 }
763         }
764
765         /* last can be 0 when HW enable. But it doesn't matter if HW enable. */
766         if (!private_vblank->HW_enable)
767                 TDM_RETURN_IF_FAIL(private_vblank->last_tv_sec != 0);
768
769         last = (unsigned long)private_vblank->last_tv_sec * 1000000 + private_vblank->last_tv_usec;
770         req = (unsigned long)wait_info->req_sec * 1000000 + wait_info->req_usec;
771         skip = (unsigned int)((req - last) / private_vblank->vblank_gap);
772         prev = last + skip * private_vblank->vblank_gap;
773
774         if (private_vblank->last_seq == 0)
775                 skip = 0;
776
777         skip += wait_info->interval;
778
779         if (private_vblank->HW_enable) {
780                 unsigned int hw_skip = (unsigned int)((curr - prev) / private_vblank->HW_vblank_gap);
781
782                 wait_info->target_hw_interval = wait_info->interval * private_vblank->HW_quotient;
783                 wait_info->target_hw_interval -= hw_skip;
784
785                 if (wait_info->target_hw_interval < 1)
786                         wait_info->target_hw_interval = 1;
787
788                 target = prev + wait_info->target_hw_interval * private_vblank->HW_vblank_gap;
789         } else {
790                 target = prev + (unsigned long)(private_vblank->vblank_gap * wait_info->interval);
791
792                 while (target < curr) {
793                         target += (unsigned long)private_vblank->vblank_gap;
794                         skip++;
795                 }
796         }
797
798         VDB("target_seq(%d) last_seq(%d) skip(%d)",
799                 wait_info->target_seq, private_vblank->last_seq, skip);
800
801 #if 0
802         target -= (private_vblank->SW_align_offset * skip * private_vblank->HW_quotient);
803 #endif
804
805         wait_info->target_seq = private_vblank->last_seq + skip;
806         wait_info->target_sec = target / 1000000;
807         wait_info->target_usec = target % 1000000;
808
809         VDB("wait(%p) last(%4lu) req(%4lu) prev(%4lu) curr(%4lu) skip(%d) hw_interval(%d) target(%4lu,%4lu)",
810                 wait_info, last, req - last, prev - last, curr - last,
811                 skip, wait_info->target_hw_interval, target, target - last);
812 }
813
814 tdm_error
815 tdm_vblank_wait(tdm_vblank *vblank, unsigned int req_sec, unsigned int req_usec,
816                                 unsigned int interval, tdm_vblank_handler func, void *user_data)
817 {
818         tdm_private_vblank *private_vblank = vblank;
819         tdm_vblank_wait_info *wait_info;
820         tdm_error ret;
821
822         TDM_RETURN_VAL_IF_FAIL(private_vblank != NULL, TDM_ERROR_INVALID_PARAMETER);
823         TDM_RETURN_VAL_IF_FAIL(func != NULL, TDM_ERROR_INVALID_PARAMETER);
824
825         if (private_vblank->dpms != TDM_OUTPUT_DPMS_ON && !private_vblank->enable_fake) {
826                 VIN("can't wait a vblank because of DPMS off");
827                 return TDM_ERROR_DPMS_OFF;
828         }
829
830 #if 0
831         if (!private_vblank->SW_align_wait && private_vblank->dpms == TDM_OUTPUT_DPMS_ON)
832                 _tdm_vblank_sw_timer_align(private_vblank);
833 #endif
834
835         if (private_vblank->check_HW_or_SW) {
836                 private_vblank->check_HW_or_SW = 0;
837                 private_vblank->vblank_gap = (double)1000000 / private_vblank->fps;
838                 private_vblank->HW_quotient = private_vblank->vrefresh / private_vblank->fps;
839
840                 if (private_vblank->dpms == TDM_OUTPUT_DPMS_ON &&
841                         !(private_vblank->vrefresh % private_vblank->fps)) {
842                         private_vblank->HW_enable = 1;
843                         VIN("Use HW vblank");
844                 } else {
845                         private_vblank->HW_enable = 0;
846                         VIN("Use SW vblank");
847                 }
848         }
849
850         wait_info = calloc(1, sizeof *wait_info);
851         if (!wait_info) {
852                 VER("alloc failed");
853                 return TDM_ERROR_OUT_OF_MEMORY;
854         }
855
856         LIST_ADDTAIL(&wait_info->valid_link, &valid_wait_list);
857         wait_info->stamp = ++stamp;
858         wait_info->req_sec = req_sec;
859         wait_info->req_usec = req_usec;
860         wait_info->interval = interval;
861         wait_info->func = func;
862         wait_info->user_data = user_data;
863         wait_info->private_vblank = private_vblank;
864
865         _tdm_vblank_calculate_target(wait_info);
866
867         if (private_vblank->HW_enable)
868                 ret = _tdm_vblank_wait_HW(wait_info);
869         else
870                 ret = _tdm_vblank_wait_SW(wait_info);
871
872         if (ret != TDM_ERROR_NONE) {
873                 LIST_DEL(&wait_info->valid_link);
874                 free(wait_info);
875                 return ret;
876         }
877
878         return TDM_ERROR_NONE;
879 }