OMAPDSS: add new vsync and line getter ioctls
authorGrazvydas Ignotas <notasas@gmail.com>
Thu, 25 Feb 2016 23:10:19 +0000 (01:10 +0200)
committerGrazvydas Ignotas <notasas@gmail.com>
Sun, 28 Feb 2016 01:55:13 +0000 (03:55 +0200)
OMAPFB_WAITFORVSYNC_FRAME has an int arg which is the current frame
number (both an input and output). The ioctl only waits if the passed
arg matches the current frame number, otherwise it instantly returns.
It always updates the arg to the current frame number on return.

This can be used for a reliable adaptive vsync implementation where
vsync wait will only happen if the program renders a frame faster
than it's displayed.

OMAPFB_GET_LINE_STATUS reads the line number from OMAP's DISPC hardware.
Note that the number is 0 while blanking (VFP, vsync, VBP).
When the display is off, it seem to always return 2047.

drivers/video/omap2/dss/dispc.c
drivers/video/omap2/omapfb/omapfb-ioctl.c
include/linux/omapfb.h
include/video/omapdss.h

index c10fdcb..c86bd06 100644 (file)
@@ -117,6 +117,11 @@ static struct {
        u32 error_irqs;
        struct work_struct error_work;
 
+       u32 frame_counter;
+       u32 fc_last_use;
+       bool fc_isr_registered;
+       struct completion *fc_complete[4];
+
        bool            ctx_valid;
        u32             ctx[DISPC_SZ_REGS / sizeof(u32)];
 
@@ -3352,6 +3357,120 @@ int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask,
        return 0;
 }
 
+static void dispc_irq_vsync_on_frame_handler(void *data, u32 mask)
+{
+       struct completion *completion;
+       unsigned int i;
+       u32 diff;
+       int ret;
+
+       spin_lock(&dispc.irq_lock);
+
+       dispc.frame_counter++;
+
+       diff = dispc.frame_counter - dispc.fc_last_use;
+       if (diff > 5 * 60 && dispc.fc_isr_registered) {
+               ret = omap_dispc_unregister_isr_unlocked(
+                       dispc_irq_vsync_on_frame_handler,
+                       data, DISPC_IRQ_VSYNC);
+               if (ret == 0)
+                       dispc.fc_isr_registered = false;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(dispc.fc_complete); i++) {
+               completion = xchg(&dispc.fc_complete[i], NULL);
+               if (completion != NULL)
+                       complete(completion);
+       }
+
+       spin_unlock(&dispc.irq_lock);
+}
+
+int omap_dispc_wait_for_vsync_on_frame(u32 *frame,
+       unsigned long timeout, bool force)
+{
+       DECLARE_COMPLETION_ONSTACK(completion);
+       bool need_to_wait = force;
+       unsigned long flags;
+       unsigned int i;
+       long time;
+       int ret;
+
+       spin_lock_irqsave(&dispc.irq_lock, flags);
+
+       if (!dispc.fc_isr_registered) {
+               ret = omap_dispc_register_isr_unlocked(
+                       dispc_irq_vsync_on_frame_handler,
+                       NULL, DISPC_IRQ_VSYNC);
+               if (ret)
+                       goto out_unlock;
+               dispc.fc_isr_registered = true;
+       }
+       else {
+               need_to_wait |= *frame == dispc.frame_counter;
+       }
+       dispc.fc_last_use = dispc.frame_counter;
+
+       if (need_to_wait) {
+               for (i = 0; i < ARRAY_SIZE(dispc.fc_complete); i++) {
+                       if (dispc.fc_complete[i] == NULL) {
+                               dispc.fc_complete[i] = &completion;
+                               break;
+                       }
+               }
+               if (i == ARRAY_SIZE(dispc.fc_complete)) {
+                       ret = -EBUSY;
+                       goto out_unlock;
+               }
+       }
+
+       spin_unlock_irqrestore(&dispc.irq_lock, flags);
+
+       ret = 0;
+       if (need_to_wait) {
+               time = wait_for_completion_interruptible_timeout(
+                               &completion, msecs_to_jiffies(17 * 2));
+               if (time == 0)
+                       ret = -ETIMEDOUT;
+               else if (time < 0)
+                       ret = time;
+       }
+       if (ret != 0) {
+               spin_lock(&dispc.irq_lock);
+
+               for (i = 0; i < ARRAY_SIZE(dispc.fc_complete); i++) {
+                       if (dispc.fc_complete[i] == &completion) {
+                               dispc.fc_complete[i] = NULL;
+                               break;
+                       }
+               }
+
+               spin_unlock(&dispc.irq_lock);
+       }
+
+       *frame = dispc.frame_counter;
+       return ret;
+
+out_unlock:
+       spin_unlock_irqrestore(&dispc.irq_lock, flags);
+       return ret;
+}
+
+int omap_dispc_get_line_status(void)
+{
+       int r;
+
+       r = dispc_runtime_get();
+       if (r < 0)
+               return r;
+
+       r = dispc_read_reg(DISPC_LINE_STATUS);
+
+       dispc_runtime_put();
+
+       return r;
+}
+
 #ifdef CONFIG_OMAP2_DSS_FAKE_VSYNC
 void dispc_fake_vsync_irq(void)
 {
index 233aab0..6355dfe 100644 (file)
@@ -591,6 +591,70 @@ static int omapfb_wait_for_go(struct fb_info *fbi)
        return r;
 }
 
+static int omapfb_do_vsync(struct fb_info *fbi,
+       struct omap_dss_device *display, u32 *frame, bool force)
+{
+       unsigned long timeout = usecs_to_jiffies(16667 * 2);
+       struct omapfb_info *ofbi = FB2OFB(fbi);
+       static u32 frame_tv;
+       bool is_tv = false;
+       u32 frame_dummy = 0;
+       int i, r;
+
+       if (frame == NULL)
+               frame = &frame_dummy;
+
+       /* try to find the first enabled overlay+display pair */
+       for (i = 0; i < ofbi->num_overlays; i++) {
+               struct omap_overlay_manager *manager;
+
+               if (!ofbi->overlays[i]->info.enabled)
+                       continue;
+
+               manager = ofbi->overlays[i]->manager;
+               if (!manager)
+                       continue;
+
+               if (manager->device->state
+                   == OMAP_DSS_DISPLAY_ACTIVE)
+               {
+                       display = manager->device;
+                       break;
+               }
+       }
+
+       if (display->type == OMAP_DISPLAY_TYPE_VENC)
+               is_tv = true;
+
+       r = dispc_runtime_get();
+       if (r)
+               return r;
+
+       /* this is unsafe pandora hack, but should work as fb
+        * is compiled in (no worry about rmmod) and there
+        * is no way to rm fb instances at runtime */
+       unlock_fb_info(fbi);
+
+       if (is_tv) {
+               u32 irq = DISPC_IRQ_EVSYNC_ODD | DISPC_IRQ_EVSYNC_EVEN;
+               r = omap_dispc_wait_for_irq_interruptible_timeout(irq,
+                       timeout);
+               /* there is no real frame counter for TV */
+               *frame = ++frame_tv;
+       }
+       else {
+               r = omap_dispc_wait_for_vsync_on_frame(frame,
+                       timeout, force);
+       }
+
+       if (!lock_fb_info(fbi))
+               printk(KERN_ERR "omapfb: lock_fb_info failed\n");
+
+       dispc_runtime_put();
+
+       return r;
+}
+
 int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg)
 {
        struct omapfb_info *ofbi = FB2OFB(fbi);
@@ -612,6 +676,7 @@ int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg)
                struct omapfb_tearsync_info     tearsync_info;
                struct omapfb_display_info      display_info;
                u32                             crt;
+               u32                             frame;
        } p;
 
        int r = 0;
@@ -801,6 +866,28 @@ int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg)
                r = omapfb_wait_for_go(fbi);
                break;
 
+       case OMAPFB_WAITFORVSYNC_FRAME:
+               if (get_user(p.frame, (__u32 __user *)arg)) {
+                       r = -EFAULT;
+                       break;
+               }
+               r = omapfb_do_vsync(fbi, display, &p.frame, false);
+               /* report the frame # regardless */
+               if (copy_to_user((void __user *)arg, &p.frame,
+                                sizeof(p.frame)))
+                       r = -EFAULT;
+               break;
+
+       case OMAPFB_GET_LINE_STATUS:
+               r = omap_dispc_get_line_status();
+               if (r < 0)
+                       break;
+               if (copy_to_user((void __user *)arg, &r, sizeof(r)))
+                       r = -EFAULT;
+               else
+                       r = 0;
+               break;
+
        /* LCD and CTRL tests do the same thing for backward
         * compatibility */
        case OMAPFB_LCD_TEST:
index c0b0187..1f260fd 100644 (file)
@@ -59,6 +59,9 @@
 #define OMAPFB_SET_TEARSYNC    OMAP_IOW(62, struct omapfb_tearsync_info)
 #define OMAPFB_GET_DISPLAY_INFO        OMAP_IOR(63, struct omapfb_display_info)
 
+#define OMAPFB_WAITFORVSYNC_FRAME OMAP_IOWR(70, int)
+#define OMAPFB_GET_LINE_STATUS OMAP_IOR(71, int)
+
 #define OMAPFB_CAPS_GENERIC_MASK       0x00000fff
 #define OMAPFB_CAPS_LCDC_MASK          0x00fff000
 #define OMAPFB_CAPS_PANEL_MASK         0xff000000
index 1ec9549..50b10ca 100644 (file)
@@ -661,6 +661,9 @@ int omap_dispc_unregister_isr(omap_dispc_isr_t isr, void *arg, u32 mask);
 int omap_dispc_wait_for_irq_timeout(u32 irqmask, unsigned long timeout);
 int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask,
                unsigned long timeout);
+int omap_dispc_wait_for_vsync_on_frame(u32 *frame,
+               unsigned long timeout, bool force);
+int omap_dispc_get_line_status(void);
 
 #define to_dss_driver(x) container_of((x), struct omap_dss_driver, driver)
 #define to_dss_device(x) container_of((x), struct omap_dss_device, dev)