refactoring tdm vblank 91/86391/3
authorBoram Park <boram1288.park@samsung.com>
Tue, 30 Aug 2016 07:02:54 +0000 (16:02 +0900)
committerBoram Park <boram1288.park@samsung.com>
Fri, 2 Sep 2016 04:52:48 +0000 (13:52 +0900)
Change-Id: Id688bf2108f54cff797c96f67a8f5ed31ab23ba2

src/tdm_private.h
src/tdm_server.c
src/tdm_vblank.c

index 8678e25..44af51f 100644 (file)
@@ -600,6 +600,8 @@ tdm_error
 tdm_vblank_set_offset(tdm_vblank *vblank, int offset);
 tdm_error
 tdm_vblank_set_enable_fake(tdm_vblank *vblank, unsigned int enable_fake);
+unsigned int
+tdm_vblank_get_enable_fake(tdm_vblank *vblank);
 tdm_error
 tdm_vblank_wait(tdm_vblank *vblank, unsigned int req_sec, unsigned int req_usec, unsigned int interval, tdm_vblank_handler func, void *user_data);
 
index 90f46ad..9e88787 100644 (file)
@@ -262,11 +262,16 @@ _tdm_server_vblank_cb_wait_vblank(struct wl_client *client, struct wl_resource *
                TDM_INFO("req_id(%d) wait", req_id);
 
        ret = tdm_vblank_wait(vblank_info->vblank, req_sec, req_usec, interval, _tdm_server_cb_vblank, wait_info);
+
+       if (!tdm_vblank_get_enable_fake(vblank_info->vblank) && ret == TDM_ERROR_DPMS_OFF)
+               goto wait_failed;
+
        TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, wait_failed);
 
        return;
 wait_failed:
        wl_tdm_vblank_send_done(vblank_info->resource, req_id, 0, 0, 0, ret);
+       destroy_wait(wait_info);
 }
 
 static const struct wl_tdm_vblank_interface tdm_vblank_implementation = {
index 53554fa..e63c14c 100644 (file)
 #define VIN(fmt, arg...)   TDM_INFO("[%p] "fmt, private_vblank, ##arg)
 #define VDB(fmt, arg...)   TDM_DBG("[%p] "fmt, private_vblank, ##arg)
 
+typedef enum {
+       VBLANK_TYPE_SW,
+       VBLANK_TYPE_SW_FAKE,
+       VBLANK_TYPE_HW,
+       VBLANK_TYPE_HW_SW,
+} tdm_vblank_wait_type;
+
 typedef struct _tdm_vblank_wait_info tdm_vblank_wait_info;
 
 typedef struct _tdm_private_vblank {
@@ -73,26 +80,20 @@ typedef struct _tdm_private_vblank {
        unsigned int enable_fake;
 
        double vblank_gap;
+       unsigned int quotient;
+
        unsigned int last_seq;
        unsigned int last_tv_sec;
        unsigned int last_tv_usec;
 
        /* for HW */
        double HW_vblank_gap;
-       unsigned int HW_enable;
-       unsigned int HW_quotient;
        struct list_head HW_wait_list;
+       unsigned int HW_seq_margin;
 
        /* for SW */
        tdm_event_loop_source *SW_timer;
-       struct list_head SW_pending_wait_list;
        struct list_head SW_wait_list;
-#if 0
-       tdm_vblank_wait_info *SW_align_wait;
-       double SW_align_offset;
-       unsigned int SW_align_sec;
-       unsigned int SW_align_usec;
-#endif
 } tdm_private_vblank;
 
 struct _tdm_vblank_wait_info {
@@ -109,14 +110,11 @@ struct _tdm_vblank_wait_info {
        void *user_data;
        tdm_private_vblank *private_vblank;
 
-       /* target_sec can be 0 when last_tv_sec is 0 because we can't calculate
-        * target_sec without last_tv_sec. So we have to call tdm_output_wait_vblank
-        * to fill last_tv_sec at the first time.
-        */
+       tdm_vblank_wait_type type;
+
        unsigned int target_sec;
        unsigned int target_usec;
        unsigned int target_seq;
-       int target_hw_interval;
 };
 
 static struct list_head vblank_list;
@@ -124,10 +122,8 @@ static struct list_head valid_wait_list;
 static unsigned int vblank_list_inited;
 static unsigned int stamp = 0;
 
+static tdm_error _tdm_vblank_cb_vblank_SW(void *user_data);
 static tdm_error _tdm_vblank_wait_SW(tdm_vblank_wait_info *wait_info);
-#if 0
-static void _tdm_vblank_sw_timer_align(tdm_private_vblank *private_vblank);
-#endif
 
 #if 0
 static void
@@ -186,18 +182,19 @@ _tdm_vblank_insert_wait(tdm_vblank_wait_info *wait_info, struct list_head *list)
        }
 
        LIST_FOR_EACH_ENTRY(w, list, link) {
-               /* If last_tv_sec == 0, we can't calculate target_sec. */
-               if (wait_info->target_sec == 0) {
-                       if (w->interval <= wait_info->interval) {
+               if (wait_info->type == VBLANK_TYPE_SW) {
+                       if (wait_info->target_sec == 0)
+                               TDM_NEVER_GET_HERE();
+                       if (w->target_sec < wait_info->target_sec) {
                                found = w;
                                continue;
                        }
-               } else {
-                       if (w->target_sec < wait_info->target_sec) {
+                       if (w->target_sec == wait_info->target_sec && w->target_usec <= wait_info->target_usec) {
                                found = w;
                                continue;
                        }
-                       if (w->target_sec == wait_info->target_sec && w->target_usec <= wait_info->target_usec) {
+               } else {
+                       if (w->interval <= wait_info->interval) {
                                found = w;
                                continue;
                        }
@@ -215,10 +212,9 @@ _tdm_vblank_change_to_SW(tdm_private_vblank *private_vblank)
 {
        tdm_vblank_wait_info *w = NULL, *ww = NULL;
 
-       VIN("Change to SW");
-
        LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->HW_wait_list, link) {
                LIST_DEL(&w->link);
+               w->type = VBLANK_TYPE_SW_FAKE;
                _tdm_vblank_wait_SW(w);
        }
 }
@@ -255,14 +251,6 @@ _tdm_vblank_cb_output_change(tdm_output *output, tdm_output_change_type type,
                private_vblank->dpms = value.u32;
                private_vblank->check_HW_or_SW = 1;
                if (private_vblank->dpms != TDM_OUTPUT_DPMS_ON) {
-#if 0
-                       if (private_vblank->SW_align_wait) {
-                               LIST_DEL(&private_vblank->SW_align_wait->valid_link);
-                               free(private_vblank->SW_align_wait);
-                               private_vblank->SW_align_wait = NULL;
-                       }
-#endif
-
                        if (private_vblank->enable_fake)
                                _tdm_vblank_change_to_SW(private_vblank);
                        else
@@ -329,8 +317,6 @@ tdm_vblank_create(tdm_display *dpy, tdm_output *output, tdm_error *error)
        private_vblank->fps = mode->vrefresh;
 
        LIST_INITHEAD(&private_vblank->HW_wait_list);
-
-       LIST_INITHEAD(&private_vblank->SW_pending_wait_list);
        LIST_INITHEAD(&private_vblank->SW_wait_list);
 
        LIST_ADD(&private_vblank->link, &vblank_list);
@@ -353,13 +339,6 @@ tdm_vblank_destroy(tdm_vblank *vblank)
 
        LIST_DEL(&private_vblank->link);
 
-#if 0
-       if (private_vblank->SW_align_wait) {
-               LIST_DEL(&private_vblank->SW_align_wait->valid_link);
-               free(private_vblank->SW_align_wait);
-       }
-#endif
-
        if (private_vblank->SW_timer) {
                tdm_display_lock(private_vblank->dpy);
                tdm_event_loop_source_remove(private_vblank->SW_timer);
@@ -371,12 +350,6 @@ tdm_vblank_destroy(tdm_vblank *vblank)
 
        _tdm_vblank_free_HW_wait(private_vblank, 0, 0);
 
-       LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->SW_pending_wait_list, link) {
-               LIST_DEL(&w->link);
-               LIST_DEL(&w->valid_link);
-               free(w);
-       }
-
        LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->SW_wait_list, link) {
                LIST_DEL(&w->link);
                LIST_DEL(&w->valid_link);
@@ -446,163 +419,14 @@ tdm_vblank_set_enable_fake(tdm_vblank *vblank, unsigned int enable_fake)
        return TDM_ERROR_NONE;
 }
 
-static void
-_tdm_vblank_cb_vblank_HW(tdm_output *output, unsigned int sequence,
-                                                unsigned int tv_sec, unsigned int tv_usec,
-                                                void *user_data)
-{
-       tdm_vblank_wait_info *wait_info = user_data;
-       tdm_private_vblank *private_vblank;
-
-       if (!_tdm_vblank_check_valid(wait_info)) {
-               TDM_DBG("can't find wait(%p) from valid_wait_list", wait_info);
-               return;
-       }
-
-       private_vblank = wait_info->private_vblank;
-       TDM_RETURN_IF_FAIL(private_vblank != NULL);
-
-       if (!_tdm_vblank_find_wait(wait_info, &private_vblank->HW_wait_list)) {
-               VDB("can't find wait(%p)", wait_info);
-               return;
-       }
-
-       LIST_DEL(&wait_info->link);
-       LIST_DEL(&wait_info->valid_link);
-
-       private_vblank->last_seq = wait_info->target_seq;
-       private_vblank->last_tv_sec = tv_sec;
-       private_vblank->last_tv_usec = tv_usec;
-
-       if (wait_info->func)
-               wait_info->func(private_vblank, TDM_ERROR_NONE, private_vblank->last_seq,
-                                               tv_sec, tv_usec, wait_info->user_data);
-
-       if (tdm_debug_module & TDM_DEBUG_VBLANK)
-               VIN("wait(%p) done", wait_info);
-
-       free(wait_info);
-}
-
-static tdm_error
-_tdm_vblank_wait_HW(tdm_vblank_wait_info *wait_info)
-{
-       tdm_private_vblank *private_vblank = wait_info->private_vblank;
-       tdm_error ret;
-
-       TDM_RETURN_VAL_IF_FAIL(wait_info->target_hw_interval > 0, TDM_ERROR_OPERATION_FAILED);
-
-       _tdm_vblank_insert_wait(wait_info, &private_vblank->HW_wait_list);
-
-       ret = tdm_output_wait_vblank(private_vblank->output, wait_info->target_hw_interval, 0,
-                                                                _tdm_vblank_cb_vblank_HW, wait_info);
-
-       if (ret != TDM_ERROR_NONE) {
-               VWR("wait(%p) failed", wait_info);
-               LIST_DEL(&wait_info->link);
-               return ret;
-       }
-
-       if (tdm_debug_module & TDM_DEBUG_VBLANK)
-               VIN("wait(%p) waiting", wait_info);
-
-       return TDM_ERROR_NONE;
-}
-
-static tdm_error
-_tdm_vblank_cb_vblank_SW(void *user_data)
+unsigned int
+tdm_vblank_get_enable_fake(tdm_vblank *vblank)
 {
-       tdm_private_vblank *private_vblank = user_data;
-       tdm_vblank_wait_info *first_wait_info = NULL, *w = NULL, *ww = NULL;
-
-       TDM_RETURN_VAL_IF_FAIL(private_vblank != NULL, TDM_ERROR_OPERATION_FAILED);
-
-       if (LIST_IS_EMPTY(&private_vblank->SW_wait_list)) {
-               VER("no wait_info");
-               return TDM_ERROR_OPERATION_FAILED;
-       }
-
-       first_wait_info = container_of(private_vblank->SW_wait_list.next, first_wait_info, link);
-       TDM_RETURN_VAL_IF_FAIL(first_wait_info != NULL, TDM_ERROR_OPERATION_FAILED);
-
-       if (tdm_debug_module & TDM_DEBUG_VBLANK)
-               VIN("wait(%p) done", first_wait_info);
-
-       private_vblank->last_seq = first_wait_info->target_seq;
-       private_vblank->last_tv_sec = first_wait_info->target_sec;
-       private_vblank->last_tv_usec = first_wait_info->target_usec;
-
-       LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->SW_wait_list, link) {
-               if (w->target_sec != first_wait_info->target_sec ||
-                       w->target_usec != first_wait_info->target_usec)
-                       break;
-
-               LIST_DEL(&w->link);
-               LIST_DEL(&w->valid_link);
-
-               if (w->func)
-                       w->func(private_vblank, TDM_ERROR_NONE, w->target_seq,
-                                       w->target_sec, w->target_usec,
-                                       w->user_data);
-
-               free(w);
-       }
-
-       return TDM_ERROR_NONE;
-}
-
-static void
-_tdm_vblank_cb_vblank_SW_first(tdm_output *output, unsigned int sequence,
-                                                          unsigned int tv_sec, unsigned int tv_usec,
-                                                          void *user_data)
-{
-       tdm_vblank_wait_info *wait_info = user_data;
-       tdm_private_vblank *private_vblank;
-       tdm_vblank_wait_info *w = NULL, *ww = NULL;
-       unsigned int min_interval = 0;
-       unsigned long last;
-
-       if (!_tdm_vblank_check_valid(wait_info))
-               return;
-
-       private_vblank = wait_info->private_vblank;
-       TDM_RETURN_IF_FAIL(private_vblank != NULL);
-
-       if (LIST_IS_EMPTY(&private_vblank->SW_pending_wait_list)) {
-               VER("no wait_info");
-               return;
-       }
-
-       w = container_of((&private_vblank->SW_pending_wait_list)->next, w, link);
-       TDM_RETURN_IF_FAIL(w != NULL);
-
-       if (tdm_debug_module & TDM_DEBUG_VBLANK)
-               VIN("wait(%p) done", w);
-
-       min_interval = w->interval;
-
-       last = (unsigned long)tv_sec * 1000000 + tv_usec;
-       last -= private_vblank->offset * 1000;
-
-       private_vblank->last_seq = min_interval;
-       private_vblank->last_tv_sec = last / 1000000;
-       private_vblank->last_tv_usec = last % 1000000;
+       tdm_private_vblank *private_vblank = vblank;
 
-       LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->SW_pending_wait_list, link) {
-               if (w->interval == min_interval) {
-                       LIST_DEL(&w->link);
-                       LIST_DEL(&w->valid_link);
+       TDM_RETURN_VAL_IF_FAIL(private_vblank != NULL, 0);
 
-                       if (w->func)
-                               w->func(private_vblank, TDM_ERROR_NONE, private_vblank->last_seq,
-                                               tv_sec, tv_usec, w->user_data);
-                       free(w);
-               } else {
-                       LIST_DEL(&w->link);
-                       w->interval -= min_interval;
-                       _tdm_vblank_wait_SW(w);
-               }
-       }
+       return private_vblank->enable_fake;
 }
 
 static tdm_error
@@ -622,6 +446,7 @@ _tdm_vblank_sw_timer_update(tdm_private_vblank *private_vblank)
        curr = tdm_helper_get_time_in_micros();
        target = first_wait_info->target_sec * 1000000 + first_wait_info->target_usec;
 
+       /* ms_delay should be more that 1 */
        if (target < curr)
                ms_delay = 1;
        else
@@ -662,194 +487,238 @@ _tdm_vblank_sw_timer_update(tdm_private_vblank *private_vblank)
        return TDM_ERROR_NONE;
 }
 
-#if 0
 static void
-_tdm_vblank_cb_vblank_align(tdm_output *output, unsigned int sequence,
-                                                       unsigned int tv_sec, unsigned int tv_usec,
-                                                       void *user_data)
+_tdm_vblank_cb_vblank_HW(tdm_output *output, unsigned int sequence,
+                                                unsigned int tv_sec, unsigned int tv_usec,
+                                                void *user_data)
 {
-       tdm_vblank_wait_info *align_info = user_data;
+       tdm_vblank_wait_info *wait_info = user_data;
        tdm_private_vblank *private_vblank;
-       unsigned int diff_sec, diff_usec;
 
-       if (!_tdm_vblank_check_valid(align_info))
+       if (!_tdm_vblank_check_valid(wait_info)) {
+               TDM_DBG("can't find wait(%p) from valid_wait_list", wait_info);
                return;
+       }
 
-       private_vblank = align_info->private_vblank;
+       private_vblank = wait_info->private_vblank;
        TDM_RETURN_IF_FAIL(private_vblank != NULL);
 
-       private_vblank->SW_align_wait = NULL;
-       private_vblank->SW_align_sec = tv_sec;
-       private_vblank->SW_align_usec = tv_usec;
+       if (!_tdm_vblank_find_wait(wait_info, &private_vblank->HW_wait_list)) {
+               VDB("can't find wait(%p)", wait_info);
+               return;
+       }
 
-       LIST_DEL(&align_info->valid_link);
+       /* sequence is the relative value of fps. If fps = 10, sequence should be
+        * increased by 10 during 1 second.
+        */
+       sequence /= private_vblank->quotient;
 
-       if (tv_usec > align_info->req_usec) {
-               diff_usec = tv_usec - align_info->req_usec;
-               diff_sec = tv_sec - align_info->req_sec;
-       } else {
-               diff_usec = 1000000 + tv_usec - align_info->req_usec;
-               diff_sec = tv_sec - align_info->req_sec - 1;
+       /* If VBLANK_TYPE_SW_FAKE, HW sequeuce can become less than SW sequeuce.
+        * so we will correct it with HW_seq_margin.
+        */
+       if (private_vblank->last_seq > sequence) {
+               unsigned long last, tv;
+               last = (unsigned long)private_vblank->last_tv_sec * 1000000 + private_vblank->last_tv_usec;
+               tv   = (unsigned long)tv_sec * 1000000 + tv_usec;
+               private_vblank->HW_seq_margin = ((tv - last) / (unsigned long)private_vblank->vblank_gap) + 1;
+               private_vblank->HW_seq_margin += private_vblank->last_seq - sequence;
        }
 
-       private_vblank->SW_align_offset = (double)(1000000 - diff_sec * 1000000 - diff_usec) / private_vblank->vrefresh;
+       sequence += private_vblank->HW_seq_margin;
 
-       free(align_info);
+       if (wait_info->type == VBLANK_TYPE_HW_SW) {
+               unsigned long target;
+               tdm_error ret;
 
-       /* align vblank continously only if non HW and DPMS on */
-       if (!private_vblank->HW_enable && private_vblank->dpms == TDM_OUTPUT_DPMS_ON)
-               _tdm_vblank_sw_timer_align(private_vblank);
-}
+               LIST_DEL(&wait_info->link);
 
-static void
-_tdm_vblank_sw_timer_align(tdm_private_vblank *private_vblank)
-{
-       tdm_vblank_wait_info *align_info;
-       unsigned long curr;
-       tdm_error ret;
+               target = (unsigned long)tv_sec * 1000000 + tv_usec;
+               target += (private_vblank->offset * 1000);
 
-       if (private_vblank->SW_align_wait)
-               return;
+               wait_info->target_seq = sequence;
+               wait_info->target_sec = target / 1000000;
+               wait_info->target_usec = target % 1000000;
 
-       TDM_RETURN_IF_FAIL(private_vblank->dpms == TDM_OUTPUT_DPMS_ON);
+               _tdm_vblank_insert_wait(wait_info, &private_vblank->SW_wait_list);
 
-       align_info = calloc(1, sizeof *align_info);
-       if (!align_info) {
-               VER("alloc failed");
-               return;
+               ret = _tdm_vblank_sw_timer_update(private_vblank);
+
+               /* wait_info will be freed in _tdm_vblank_cb_vblank_SW() */
+               if (ret == TDM_ERROR_NONE) {
+                       if (tdm_debug_module & TDM_DEBUG_VBLANK)
+                               VIN("wait(%p) SW timer", wait_info);
+                       return;
+               }
+
+               VWR("couldn't update sw timer");
        }
 
-       LIST_ADDTAIL(&align_info->valid_link, &valid_wait_list);
-       align_info->stamp = ++stamp;
-       align_info->private_vblank = private_vblank;
+       if (tdm_debug_module & TDM_DEBUG_VBLANK)
+               VIN("wait(%p) sequence(%u) done", wait_info, sequence);
 
-       curr = tdm_helper_get_time_in_micros();
-       align_info->req_sec = curr / 1000000;
-       align_info->req_usec = curr % 1000000;
+       private_vblank->last_seq = sequence;
+       private_vblank->last_tv_sec = tv_sec;
+       private_vblank->last_tv_usec = tv_usec;
 
-       ret = tdm_output_wait_vblank(private_vblank->output, private_vblank->vrefresh, 0,
-                                                                _tdm_vblank_cb_vblank_align, align_info);
-       if (ret != TDM_ERROR_NONE) {
-               LIST_DEL(&align_info->valid_link);
-               free(align_info);
-               return;
-       }
+       if (wait_info->func)
+               wait_info->func(private_vblank, TDM_ERROR_NONE, sequence,
+                                               tv_sec, tv_usec, wait_info->user_data);
 
-       private_vblank->SW_align_wait = align_info;
+       LIST_DEL(&wait_info->link);
+       LIST_DEL(&wait_info->valid_link);
+       free(wait_info);
 }
-#endif
 
 static tdm_error
-_tdm_vblank_wait_SW(tdm_vblank_wait_info *wait_info)
+_tdm_vblank_wait_HW(tdm_vblank_wait_info *wait_info)
 {
        tdm_private_vblank *private_vblank = wait_info->private_vblank;
+       int hw_interval;
        tdm_error ret;
 
-       if (private_vblank->last_tv_sec == 0 && private_vblank->dpms == TDM_OUTPUT_DPMS_ON) {
-               unsigned int do_wait = LIST_IS_EMPTY(&private_vblank->SW_pending_wait_list);
-               _tdm_vblank_insert_wait(wait_info, &private_vblank->SW_pending_wait_list);
-               if (do_wait) {
-                       ret = tdm_output_wait_vblank(private_vblank->output, 1, 0,
-                                                                                _tdm_vblank_cb_vblank_SW_first, wait_info);
-                       if (ret == TDM_ERROR_DPMS_OFF) {
-                               TDM_WRN("use SW");
-                               goto use_sw;
-                       }
-                       if (ret != TDM_ERROR_NONE) {
-                               LIST_DEL(&wait_info->link);
-                               return ret;
-                       }
-                       if (tdm_debug_module & TDM_DEBUG_VBLANK)
-                               VIN("wait(%p) waiting", wait_info);
-               }
-               return TDM_ERROR_NONE;
+       _tdm_vblank_insert_wait(wait_info, &private_vblank->HW_wait_list);
+
+       hw_interval = wait_info->interval * private_vblank->quotient;
+
+       if (private_vblank->last_tv_sec != 0) {
+               unsigned long last, prev, req, curr;
+               unsigned int skip = 0;
+               unsigned int hw_skip;
+
+               last = (unsigned long)private_vblank->last_tv_sec * 1000000 + private_vblank->last_tv_usec;
+               req = (unsigned long)wait_info->req_sec * 1000000 + wait_info->req_usec;
+
+               skip = (unsigned int)((req - last) / private_vblank->vblank_gap);
+               prev = last + skip * private_vblank->vblank_gap;
+
+               curr = tdm_helper_get_time_in_micros();
+               hw_skip = (unsigned int)((curr - prev) / private_vblank->HW_vblank_gap);
+
+               hw_interval -= hw_skip;
+
+               if (tdm_debug_module & TDM_DEBUG_VBLANK)
+                       VIN("wait(%p) last(%4lu) req(%4lu) prev(%4lu) curr(%4lu) skip(%d) hw_interval(%d)",
+                               wait_info, last, req - last, prev - last, curr - last,
+                               skip, hw_interval);
        }
 
-use_sw:
-       TDM_RETURN_VAL_IF_FAIL(wait_info->target_sec > 0, TDM_ERROR_OPERATION_FAILED);
+       if (hw_interval < 1)
+               hw_interval = 1;
 
-       _tdm_vblank_insert_wait(wait_info, &private_vblank->SW_wait_list);
+       ret = tdm_output_wait_vblank(private_vblank->output, hw_interval, 0,
+                                                                _tdm_vblank_cb_vblank_HW, wait_info);
 
-       ret = _tdm_vblank_sw_timer_update(private_vblank);
        if (ret != TDM_ERROR_NONE) {
                LIST_DEL(&wait_info->link);
-               VER("couldn't update sw timer");
                return ret;
        }
 
+       if (tdm_debug_module & TDM_DEBUG_VBLANK)
+               VIN("wait(%p) waiting", wait_info);
+
        return TDM_ERROR_NONE;
 }
 
-static void
-_tdm_vblank_calculate_target(tdm_vblank_wait_info *wait_info)
+static tdm_error
+_tdm_vblank_cb_vblank_SW(void *user_data)
 {
-       tdm_private_vblank *private_vblank = wait_info->private_vblank;
-       unsigned long last, prev, req, curr, target;
-       unsigned int skip = 0;
+       tdm_private_vblank *private_vblank = user_data;
+       tdm_vblank_wait_info *first_wait_info = NULL, *w = NULL, *ww = NULL;
 
-       curr = tdm_helper_get_time_in_micros();
+       TDM_RETURN_VAL_IF_FAIL(private_vblank != NULL, TDM_ERROR_OPERATION_FAILED);
 
-       if (!private_vblank->HW_enable) {
-               if (private_vblank->last_tv_sec == 0) {
-                       /* If last == 0 and DPMS == on, we will use HW vblank to sync with HW vblank. */
-                       if (private_vblank->dpms == TDM_OUTPUT_DPMS_ON) {
-                               return;
-                       } else {
-                               private_vblank->last_tv_sec = curr / 1000000;
-                               private_vblank->last_tv_usec = curr % 1000000;
-                       }
-               }
+       if (LIST_IS_EMPTY(&private_vblank->SW_wait_list)) {
+               VER("no wait_info");
+               return TDM_ERROR_OPERATION_FAILED;
        }
 
-       /* last can be 0 when HW enable. But it doesn't matter if HW enable. */
-       if (!private_vblank->HW_enable)
-               TDM_RETURN_IF_FAIL(private_vblank->last_tv_sec != 0);
+       first_wait_info = container_of(private_vblank->SW_wait_list.next, first_wait_info, link);
+       TDM_RETURN_VAL_IF_FAIL(first_wait_info != NULL, TDM_ERROR_OPERATION_FAILED);
+
+       if (tdm_debug_module & TDM_DEBUG_VBLANK)
+               VIN("wait(%p) sequence(%u) done", first_wait_info, first_wait_info->target_seq);
+
+       private_vblank->last_seq = first_wait_info->target_seq;
+       private_vblank->last_tv_sec = first_wait_info->target_sec;
+       private_vblank->last_tv_usec = first_wait_info->target_usec;
+
+       LIST_FOR_EACH_ENTRY_SAFE(w, ww, &private_vblank->SW_wait_list, link) {
+               if (w->target_sec != first_wait_info->target_sec ||
+                       w->target_usec != first_wait_info->target_usec)
+                       break;
+
+               LIST_DEL(&w->link);
+               LIST_DEL(&w->valid_link);
+
+               if (w->func)
+                       w->func(private_vblank, TDM_ERROR_NONE, w->target_seq,
+                                       w->target_sec, w->target_usec,
+                                       w->user_data);
 
-       last = (unsigned long)private_vblank->last_tv_sec * 1000000 + private_vblank->last_tv_usec;
-       req = (unsigned long)wait_info->req_sec * 1000000 + wait_info->req_usec;
-       skip = (unsigned int)((req - last) / private_vblank->vblank_gap);
-       prev = last + skip * private_vblank->vblank_gap;
+               free(w);
+       }
 
-       if (private_vblank->last_seq == 0)
-               skip = 0;
+       return TDM_ERROR_NONE;
+}
 
-       skip += wait_info->interval;
+static tdm_error
+_tdm_vblank_wait_SW(tdm_vblank_wait_info *wait_info)
+{
+       tdm_private_vblank *private_vblank = wait_info->private_vblank;
+       tdm_error ret;
 
-       if (private_vblank->HW_enable) {
-               unsigned int hw_skip = (unsigned int)((curr - prev) / private_vblank->HW_vblank_gap);
+       if (private_vblank->last_tv_sec == 0) {
+               unsigned long curr = tdm_helper_get_time_in_micros();
 
-               wait_info->target_hw_interval = wait_info->interval * private_vblank->HW_quotient;
-               wait_info->target_hw_interval -= hw_skip;
+               /* SW vblank starts from now. SW vblank doesn't need to be aligned with HW vblank. */
+               private_vblank->last_seq = 0;
+               private_vblank->last_tv_sec = curr / 1000000;
+               private_vblank->last_tv_usec = curr % 1000000;
 
-               if (wait_info->target_hw_interval < 1)
-                       wait_info->target_hw_interval = 1;
+               /* +1 ms to call the handler ASAP at the first. no matter for SW timer. */
+               curr += 1000;
 
-               target = prev + wait_info->target_hw_interval * private_vblank->HW_vblank_gap;
+               wait_info->target_seq = 1;
+               wait_info->target_sec = curr / 1000000;
+               wait_info->target_usec = curr % 1000000;
        } else {
+               unsigned long last, prev, req, curr, target;
+               unsigned int skip;
+
+               last = (unsigned long)private_vblank->last_tv_sec * 1000000 + private_vblank->last_tv_usec;
+               req = (unsigned long)wait_info->req_sec * 1000000 + wait_info->req_usec;
+
+               skip = (unsigned int)((req - last) / private_vblank->vblank_gap);
+               prev = last + skip * private_vblank->vblank_gap;
+
+               curr = tdm_helper_get_time_in_micros();
                target = prev + (unsigned long)(private_vblank->vblank_gap * wait_info->interval);
 
-               while (target < curr) {
+               while (target < curr)
                        target += (unsigned long)private_vblank->vblank_gap;
-                       skip++;
-               }
-       }
 
-       if (tdm_debug_module & TDM_DEBUG_VBLANK)
-               VIN("target_seq(%d) last_seq(%d) skip(%d)",
-                       wait_info->target_seq, private_vblank->last_seq, skip);
+               wait_info->target_seq = private_vblank->last_seq;
+               wait_info->target_seq += (target - last) / (unsigned long)private_vblank->vblank_gap;
 
-#if 0
-       target -= (private_vblank->SW_align_offset * skip * private_vblank->HW_quotient);
-#endif
+               wait_info->target_sec = target / 1000000;
+               wait_info->target_usec = target % 1000000;
 
-       wait_info->target_seq = private_vblank->last_seq + skip;
-       wait_info->target_sec = target / 1000000;
-       wait_info->target_usec = target % 1000000;
+               if (tdm_debug_module & TDM_DEBUG_VBLANK)
+                       VIN("wait(%p) last(%4lu) req(%4lu) prev(%4lu) curr(%4lu) target(%4lu,%4lu)",
+                               wait_info, last, req - last, prev - last, curr - last,
+                               target, target - last);
+       }
 
-       if (tdm_debug_module & TDM_DEBUG_VBLANK)
-               VIN("wait(%p) last(%4lu) req(%4lu) prev(%4lu) curr(%4lu) skip(%d) hw_interval(%d) target(%4lu,%4lu)",
-                       wait_info, last, req - last, prev - last, curr - last,
-                       skip, wait_info->target_hw_interval, target, target - last);
+       _tdm_vblank_insert_wait(wait_info, &private_vblank->SW_wait_list);
+
+       ret = _tdm_vblank_sw_timer_update(private_vblank);
+       if (ret != TDM_ERROR_NONE) {
+               LIST_DEL(&wait_info->link);
+               VER("couldn't update sw timer");
+               return ret;
+       }
+
+       return TDM_ERROR_NONE;
 }
 
 tdm_error
@@ -868,26 +737,6 @@ tdm_vblank_wait(tdm_vblank *vblank, unsigned int req_sec, unsigned int req_usec,
                return TDM_ERROR_DPMS_OFF;
        }
 
-#if 0
-       if (!private_vblank->SW_align_wait && private_vblank->dpms == TDM_OUTPUT_DPMS_ON)
-               _tdm_vblank_sw_timer_align(private_vblank);
-#endif
-
-       if (private_vblank->check_HW_or_SW) {
-               private_vblank->check_HW_or_SW = 0;
-               private_vblank->vblank_gap = (double)1000000 / private_vblank->fps;
-               private_vblank->HW_quotient = private_vblank->vrefresh / private_vblank->fps;
-
-               if (private_vblank->dpms == TDM_OUTPUT_DPMS_ON &&
-                       !(private_vblank->vrefresh % private_vblank->fps)) {
-                       private_vblank->HW_enable = 1;
-                       VIN("Use HW vblank");
-               } else {
-                       private_vblank->HW_enable = 0;
-                       VIN("Use SW vblank");
-               }
-       }
-
        wait_info = calloc(1, sizeof *wait_info);
        if (!wait_info) {
                VER("alloc failed");
@@ -904,16 +753,34 @@ tdm_vblank_wait(tdm_vblank *vblank, unsigned int req_sec, unsigned int req_usec,
        wait_info->user_data = user_data;
        wait_info->private_vblank = private_vblank;
 
-       _tdm_vblank_calculate_target(wait_info);
+       if (private_vblank->check_HW_or_SW) {
+               private_vblank->check_HW_or_SW = 0;
+               private_vblank->vblank_gap = (double)1000000 / private_vblank->fps;
+               private_vblank->quotient = private_vblank->vrefresh / private_vblank->fps;
+       }
+
+       /* 1) if fps != factor of vrefresh, SW timer
+        * 2) if fps == factor of vrefresh && dpms == off, SW timer (Fake HW vblank)
+        * 2) if fps == factor of vrefresh && dpms == on && offset == 0, HW vblank
+        * 3) if fps == factor of vrefresh && dpms == on && offset != 0, HW vblank + SW timer
+        * In case of 1), we really don't need to align with HW vblank.
+        */
+       if (private_vblank->vrefresh % private_vblank->fps)
+               wait_info->type = VBLANK_TYPE_SW;
+       else if (private_vblank->dpms == TDM_OUTPUT_DPMS_OFF)
+               wait_info->type = VBLANK_TYPE_SW_FAKE;
+       else if (private_vblank->offset == 0)
+               wait_info->type = VBLANK_TYPE_HW;
+       else
+               wait_info->type = VBLANK_TYPE_HW_SW;
 
-       if (private_vblank->HW_enable) {
+       if (wait_info->type == VBLANK_TYPE_SW || wait_info->type == VBLANK_TYPE_SW_FAKE)
+               ret = _tdm_vblank_wait_SW(wait_info);
+       else {
                ret = _tdm_vblank_wait_HW(wait_info);
-               if (ret == TDM_ERROR_DPMS_OFF) {
-                       TDM_WRN("try to use SW");
+               if (ret == TDM_ERROR_DPMS_OFF)
                        ret = _tdm_vblank_wait_SW(wait_info);
-               }
-       } else
-               ret = _tdm_vblank_wait_SW(wait_info);
+       }
 
        if (ret != TDM_ERROR_NONE) {
                LIST_DEL(&wait_info->link);