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