video: tegra: dc: Add timestamp support
Raghavendra VK [Sat, 4 Aug 2012 04:25:43 +0000 (21:25 -0700)]
bug 1021221

Change-Id: Ifbe007de5bdeafaa15a0b3f2a138086045eba160
Signed-off-by: Raghavendra VK <rvk@nvidia.com>
Reviewed-on: http://git-master/r/118179
(cherry picked from commit 74be8d4e7210d7bcea0d55565a7cbb06d6cc960e)
Reviewed-on: http://git-master/r/121087
Reviewed-by: Jon Mayo <jmayo@nvidia.com>
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Robert Morell <rmorell@nvidia.com>

arch/arm/mach-tegra/include/mach/dc.h
drivers/video/tegra/dc/dc.c
drivers/video/tegra/dc/dc_priv.h
drivers/video/tegra/dc/ext/dev.c
drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h
drivers/video/tegra/dc/mode.c
drivers/video/tegra/dc/window.c

index b60a913..3483fe9 100644 (file)
@@ -539,6 +539,9 @@ void tegra_dc_incr_syncpt_min(struct tegra_dc *dc, int i, u32 val);
  */
 int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n);
 int tegra_dc_sync_windows(struct tegra_dc_win *windows[], int n);
+int tegra_dc_config_frame_end_intr(struct tegra_dc *dc, bool enable);
+bool tegra_dc_is_within_n_vsync(struct tegra_dc *dc, s64 ts);
+bool tegra_dc_does_vsync_separate(struct tegra_dc *dc, s64 new_ts, s64 old_ts);
 
 int tegra_dc_set_mode(struct tegra_dc *dc, const struct tegra_dc_mode *mode);
 struct fb_videomode;
index db97ed3..de865e4 100644 (file)
@@ -1045,6 +1045,10 @@ static void tegra_dc_continuous_irq(struct tegra_dc *dc, unsigned long status)
                queue_work(system_freezable_wq, &dc->vblank_work);
 
        if (status & FRAME_END_INT) {
+               struct timespec tm = CURRENT_TIME;
+               dc->frame_end_timestamp = timespec_to_ns(&tm);
+               wake_up(&dc->timestamp_wq);
+
                /* Mark the frame_end as complete. */
                if (!completion_done(&dc->frame_end_complete))
                        complete(&dc->frame_end_complete);
@@ -1052,6 +1056,20 @@ static void tegra_dc_continuous_irq(struct tegra_dc *dc, unsigned long status)
                tegra_dc_trigger_windows(dc);
        }
 }
+
+/* XXX: Not sure if we limit look ahead to 1 frame */
+bool tegra_dc_is_within_n_vsync(struct tegra_dc *dc, s64 ts)
+{
+       return ((ts - dc->frame_end_timestamp) < dc->frametime_ns);
+}
+
+bool tegra_dc_does_vsync_separate(struct tegra_dc *dc, s64 new_ts, s64 old_ts)
+{
+       return (((new_ts - old_ts) > dc->frametime_ns)
+               || (div_s64((new_ts - dc->frame_end_timestamp), dc->frametime_ns)
+                       != div_s64((old_ts - dc->frame_end_timestamp),
+                               dc->frametime_ns)));
+}
 #endif
 
 static irqreturn_t tegra_dc_irq(int irq, void *ptr)
@@ -1759,6 +1777,7 @@ static int tegra_dc_probe(struct nvhost_device *ndev,
        mutex_init(&dc->one_shot_lock);
        init_completion(&dc->frame_end_complete);
        init_waitqueue_head(&dc->wq);
+       init_waitqueue_head(&dc->timestamp_wq);
 #ifdef CONFIG_ARCH_TEGRA_2x_SOC
        INIT_WORK(&dc->reset_work, tegra_dc_reset_worker);
 #endif
index 759d64d..75c3a2a 100644 (file)
@@ -113,12 +113,14 @@ struct tegra_dc {
        void                            *out_data;
 
        struct tegra_dc_mode            mode;
+       s64                             frametime_ns;
 
        struct tegra_dc_win             windows[DC_N_WINDOWS];
        struct tegra_dc_blend           blend;
        int                             n_windows;
 
        wait_queue_head_t               wq;
+       wait_queue_head_t               timestamp_wq;
 
        struct mutex                    lock;
        struct mutex                    one_shot_lock;
@@ -163,6 +165,7 @@ struct tegra_dc {
        struct delayed_work             underflow_work;
        u32                             one_shot_delay_ms;
        struct delayed_work             one_shot_work;
+       s64                             frame_end_timestamp;
 };
 
 #define print_mode_info(dc, mode) do {                                 \
index 8ec015c..88273e2 100644 (file)
@@ -56,6 +56,7 @@ struct tegra_dc_ext_flip_data {
        struct tegra_dc_ext             *ext;
        struct work_struct              work;
        struct tegra_dc_ext_flip_win    win[DC_N_WINDOWS];
+       struct list_head                timestamp_node;
 };
 
 int tegra_dc_ext_get_num_outputs(void)
@@ -207,6 +208,7 @@ static int tegra_dc_ext_set_windowattr(struct tegra_dc_ext *ext,
 {
        int err = 0;
        struct tegra_dc_ext_win *ext_win = &ext->win[win->idx];
+       s64 timestamp_ns;
 
        if (flip_win->handle[TEGRA_DC_Y] == NULL) {
                win->flags = 0;
@@ -270,8 +272,20 @@ static int tegra_dc_ext_set_windowattr(struct tegra_dc_ext *ext,
                                msecs_to_jiffies(500), NULL);
        }
 
-
-       return 0;
+#ifndef CONFIG_TEGRA_SIMULATION_PLATFORM
+       timestamp_ns = timespec_to_ns(&flip_win->attr.timestamp);
+
+       if (timestamp_ns) {
+               /* XXX: Should timestamping be overridden by "no_vsync" flag */
+               tegra_dc_config_frame_end_intr(win->dc, true);
+               trace_printk("%s:Before timestamp wait\n", win->dc->ndev->name);
+               err = wait_event_interruptible(win->dc->timestamp_wq,
+                               tegra_dc_is_within_n_vsync(win->dc, timestamp_ns));
+               trace_printk("%s:After timestamp wait\n", win->dc->ndev->name);
+               tegra_dc_config_frame_end_intr(win->dc, false);
+       }
+#endif
+       return err;
 }
 
 static void (*flip_callback)(void);
@@ -323,9 +337,11 @@ static void tegra_dc_ext_flip_worker(struct work_struct *work)
 
        for (i = 0; i < DC_N_WINDOWS; i++) {
                struct tegra_dc_ext_flip_win *flip_win = &data->win[i];
-               int index = flip_win->attr.index;
+               int j = 0, index = flip_win->attr.index;
                struct tegra_dc_win *win;
                struct tegra_dc_ext_win *ext_win;
+               struct tegra_dc_ext_flip_data *temp = NULL;
+               s64 head_timestamp = 0;
 
                if (index < 0)
                        continue;
@@ -337,6 +353,31 @@ static void tegra_dc_ext_flip_worker(struct work_struct *work)
                        (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_CURSOR))
                        skip_flip = true;
 
+               mutex_lock(&ext_win->queue_lock);
+               list_for_each_entry(temp, &ext_win->timestamp_queue,
+                               timestamp_node) {
+                       if (j == 0) {
+                               if (unlikely(temp != data))
+                                       dev_err(&win->dc->ndev->dev,
+                                                       "work queue did NOT dequeue head!!!");
+                               else
+                                       head_timestamp =
+                                               timespec_to_ns(&flip_win->attr.timestamp);
+                       } else {
+                               s64 timestamp =
+                                       timespec_to_ns(&temp->win[i].attr.timestamp);
+
+                               skip_flip = !tegra_dc_does_vsync_separate(ext->dc,
+                                               timestamp, head_timestamp);
+                               /* Look ahead only one flip */
+                               break;
+                       }
+                       j++;
+               }
+               if (!list_empty(&ext_win->timestamp_queue))
+                       list_del(&data->timestamp_node);
+               mutex_unlock(&ext_win->queue_lock);
+
                if (win->flags & TEGRA_WIN_FLAG_ENABLED) {
                        int j;
                        for (j = 0; j < TEGRA_DC_NUM_PLANES; j++) {
@@ -368,17 +409,17 @@ static void tegra_dc_ext_flip_worker(struct work_struct *work)
                                flip_callback();
                        spin_unlock(&flip_callback_lock);
                }
-       }
 
-       for (i = 0; i < DC_N_WINDOWS; i++) {
-               struct tegra_dc_ext_flip_win *flip_win = &data->win[i];
-               int index = flip_win->attr.index;
+               for (i = 0; i < DC_N_WINDOWS; i++) {
+                       struct tegra_dc_ext_flip_win *flip_win = &data->win[i];
+                       int index = flip_win->attr.index;
 
-               if (index < 0)
-                       continue;
+                       if (index < 0)
+                               continue;
 
-               tegra_dc_incr_syncpt_min(ext->dc, index,
-                       flip_win->syncpt_max);
+                       tegra_dc_incr_syncpt_min(ext->dc, index,
+                                       flip_win->syncpt_max);
+               }
        }
 
        /* unpin and deref previous front buffers */
@@ -490,6 +531,7 @@ static int tegra_dc_ext_flip(struct tegra_dc_ext_user *user,
        struct tegra_dc_ext_flip_data *data;
        int work_index = -1;
        int i, ret = 0;
+       bool has_timestamp = false;
 
 #ifdef CONFIG_ANDROID
        int index_check[DC_N_WINDOWS] = {0, };
@@ -530,6 +572,8 @@ static int tegra_dc_ext_flip(struct tegra_dc_ext_user *user,
                int index = args->win[i].index;
 
                memcpy(&flip_win->attr, &args->win[i], sizeof(flip_win->attr));
+               if (timespec_to_ns(&flip_win->attr.timestamp))
+                       has_timestamp = true;
 
                if (index < 0)
                        continue;
@@ -604,6 +648,11 @@ static int tegra_dc_ext_flip(struct tegra_dc_ext_user *user,
                ret = -EINVAL;
                goto unlock;
        }
+       if (has_timestamp) {
+               mutex_lock(&ext->win[work_index].queue_lock);
+               list_add_tail(&data->timestamp_node, &ext->win[work_index].timestamp_queue);
+               mutex_unlock(&ext->win[work_index].queue_lock);
+       }
        queue_work(ext->win[work_index].flip_wq, &data->work);
 
        unlock_windows_for_flip(user, args);
@@ -944,6 +993,8 @@ static int tegra_dc_ext_setup_windows(struct tegra_dc_ext *ext)
                }
 
                mutex_init(&win->lock);
+               mutex_init(&win->queue_lock);
+               INIT_LIST_HEAD(&win->timestamp_queue);
        }
 
        return 0;
index f68c7d5..ef7361d 100644 (file)
@@ -58,6 +58,10 @@ struct tegra_dc_ext_win {
        struct workqueue_struct *flip_wq;
 
        atomic_t                nr_pending_flips;
+
+       struct mutex            queue_lock;
+
+       struct list_head        timestamp_queue;
 };
 
 struct tegra_dc_ext {
index be90969..45cfe8d 100644 (file)
@@ -137,6 +137,16 @@ static bool check_ref_to_sync(struct tegra_dc_mode *mode)
        return true;
 }
 
+static s64 calc_frametime_ns(const struct tegra_dc_mode *m)
+{
+       long h_total, v_total;
+       h_total = m->h_active + m->h_front_porch + m->h_back_porch +
+               m->h_sync_width;
+       v_total = m->v_active + m->v_front_porch + m->v_back_porch +
+               m->v_sync_width;
+       return (s64)(div_s64(((s64)h_total * v_total * 1000000000ULL), m->pclk));
+}
+
 /* return in 1000ths of a Hertz */
 int tegra_dc_calc_refresh(const struct tegra_dc_mode *m)
 {
@@ -265,6 +275,7 @@ int tegra_dc_set_mode(struct tegra_dc *dc, const struct tegra_dc_mode *mode)
                panel_sync_rate = dc->out->dsi->rated_refresh_rate * 1000;
 
        print_mode(dc, mode, __func__);
+       dc->frametime_ns = calc_frametime_ns(mode);
 
        return 0;
 }
index 0b4350d..cd91fab 100644 (file)
@@ -24,6 +24,7 @@
 #include "dc_priv.h"
 
 static int no_vsync;
+static atomic_t frame_end_ref = ATOMIC_INIT(0);
 
 module_param_named(no_vsync, no_vsync, int, S_IRUGO | S_IWUSR);
 
@@ -40,6 +41,17 @@ static bool tegra_dc_windows_are_clean(struct tegra_dc_win *windows[],
        return true;
 }
 
+int tegra_dc_config_frame_end_intr(struct tegra_dc *dc, bool enable)
+{
+       tegra_dc_writel(dc, FRAME_END_INT, DC_CMD_INT_STATUS);
+       if (enable) {
+               atomic_inc(&frame_end_ref);
+               tegra_dc_unmask_interrupt(dc, FRAME_END_INT);
+       } else if (!atomic_dec_return(&frame_end_ref))
+               tegra_dc_mask_interrupt(dc, FRAME_END_INT);
+       return 0;
+}
+
 static int get_topmost_window(u32 *depths, unsigned long *wins)
 {
        int idx, best = -1;
@@ -406,8 +418,9 @@ int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n)
                        FRAME_END_INT | V_BLANK_INT | ALL_UF_INT);
        } else {
                clear_bit(V_BLANK_FLIP, &dc->vblank_ref_count);
-               tegra_dc_mask_interrupt(dc,
-                       FRAME_END_INT | V_BLANK_INT | ALL_UF_INT);
+               tegra_dc_mask_interrupt(dc, V_BLANK_INT | ALL_UF_INT);
+               if (!atomic_read(&frame_end_ref))
+                       tegra_dc_mask_interrupt(dc, FRAME_END_INT);
        }
 
        if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
@@ -456,7 +469,8 @@ void tegra_dc_trigger_windows(struct tegra_dc *dc)
        }
 
        if (!dirty) {
-               if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE))
+               if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
+                       && !atomic_read(&frame_end_ref))
                        tegra_dc_mask_interrupt(dc, FRAME_END_INT);
        }