From 00943e97e1fcda39c59f503e36f161b99feaf1f7 Mon Sep 17 00:00:00 2001 From: Grazvydas Ignotas Date: Fri, 26 Feb 2016 01:10:19 +0200 Subject: [PATCH] OMAPDSS: add new vsync and line getter ioctls 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 | 119 ++++++++++++++++++++++ drivers/video/omap2/omapfb/omapfb-ioctl.c | 87 ++++++++++++++++ include/linux/omapfb.h | 3 + include/video/omapdss.h | 3 + 4 files changed, 212 insertions(+) diff --git a/drivers/video/omap2/dss/dispc.c b/drivers/video/omap2/dss/dispc.c index c10fdcbb2c6a..c86bd0653902 100644 --- a/drivers/video/omap2/dss/dispc.c +++ b/drivers/video/omap2/dss/dispc.c @@ -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) { diff --git a/drivers/video/omap2/omapfb/omapfb-ioctl.c b/drivers/video/omap2/omapfb/omapfb-ioctl.c index 233aab08270c..6355dfeb3d8b 100644 --- a/drivers/video/omap2/omapfb/omapfb-ioctl.c +++ b/drivers/video/omap2/omapfb/omapfb-ioctl.c @@ -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: diff --git a/include/linux/omapfb.h b/include/linux/omapfb.h index c0b018790f07..1f260fda82c6 100644 --- a/include/linux/omapfb.h +++ b/include/linux/omapfb.h @@ -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 diff --git a/include/video/omapdss.h b/include/video/omapdss.h index 1ec95490cc00..50b10cae7f25 100644 --- a/include/video/omapdss.h +++ b/include/video/omapdss.h @@ -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) -- 2.47.3