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