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