gfx: drv: Introduce drm_flip helper class
authorVille Syrjälä <ville.syrjala@linux.intel.com>
Wed, 15 Feb 2012 13:02:34 +0000 (15:02 +0200)
committerMarkus Lehtonen <markus.lehtonen@linux.intel.com>
Tue, 3 Jul 2012 09:29:59 +0000 (12:29 +0300)
The drm_flip mechanism can be used to implement robust page flipping
support, and also to synchronize the flips on multiple hardware scanout
engines (eg. CRTCs and overlays).

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Reviewed-by: Imre Deak <imre.deak@intel.com>
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
drivers/staging/mrst/Makefile
drivers/staging/mrst/drv/drm_flip.c [new file with mode: 0644]
drivers/staging/mrst/drv/drm_flip.h [new file with mode: 0644]

index 21a214e..9f9c024 100644 (file)
@@ -160,6 +160,7 @@ medfield_gfx-y += \
        $(FBDEVDIR)/mrstlfb_linux.o
 
 medfield_gfx-y += \
+       $(DRMDRVDIR)/drm_flip.o \
        $(DRMDRVDIR)/fp_trig.o \
        $(DRMDRVDIR)/mdfld_dsi_dbi.o \
        $(DRMDRVDIR)/mdfld_dsi_dpi.o \
diff --git a/drivers/staging/mrst/drv/drm_flip.c b/drivers/staging/mrst/drv/drm_flip.c
new file mode 100644 (file)
index 0000000..aa1f3ad
--- /dev/null
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2012 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ * Ville Syrjälä <ville.syrjala@linux.intel.com>
+ */
+
+#include "drm_flip.h"
+
+static void drm_flip_driver_cleanup(struct work_struct *work)
+{
+       struct drm_flip *flip, *next;
+       struct drm_flip_driver *driver =
+               container_of(work, struct drm_flip_driver, cleanup_work);
+       LIST_HEAD(list);
+
+       spin_lock_irq(&driver->lock);
+
+       list_cut_position(&list,
+                         &driver->cleanup_list,
+                         driver->cleanup_list.prev);
+
+       spin_unlock_irq(&driver->lock);
+
+       if (list_empty(&list))
+               return;
+
+       list_for_each_entry_safe(flip, next, &list, list) {
+               struct drm_flip_helper *helper = flip->helper;
+
+               WARN_ON(!flip->finished);
+
+               helper->funcs->cleanup(flip);
+       }
+}
+
+static void drm_flip_driver_finish(struct work_struct *work)
+{
+       struct drm_flip *flip, *next;
+       struct drm_flip_driver *driver =
+               container_of(work, struct drm_flip_driver, finish_work);
+       LIST_HEAD(list);
+       bool need_cleanup = false;
+
+       spin_lock_irq(&driver->lock);
+
+       list_cut_position(&list,
+                         &driver->finish_list,
+                         driver->finish_list.prev);
+
+       spin_unlock_irq(&driver->lock);
+
+       if (list_empty(&list))
+               return;
+
+       list_for_each_entry_safe(flip, next, &list, list) {
+               struct drm_flip_helper *helper = flip->helper;
+
+               helper->funcs->finish(flip);
+
+               spin_lock_irq(&driver->lock);
+
+               flip->finished = true;
+
+               /*
+                * It's possible that drm_flip_set_scanout() was called after we
+                * pulled this flip from finish_list, in which case the flip
+                * could be in need of cleanup, but not on cleanup_list.
+                */
+               if (flip == helper->scanout_flip) {
+                       list_del_init(&flip->list);
+               } else {
+                       need_cleanup = true;
+                       list_move_tail(&flip->list, &driver->cleanup_list);
+               }
+
+               spin_unlock_irq(&driver->lock);
+       }
+
+       if (need_cleanup)
+               queue_work(driver->wq, &driver->cleanup_work);
+}
+
+static bool drm_flip_set_scanout(struct drm_flip_helper *helper,
+                                struct drm_flip *flip)
+{
+       struct drm_flip_driver *driver = helper->driver;
+       struct drm_flip *old = helper->scanout_flip;
+
+       helper->scanout_flip = flip;
+
+       if (old && old->finished)
+               list_move_tail(&old->list, &driver->cleanup_list);
+
+       return old != NULL;
+}
+
+static bool drm_flip_complete(struct drm_flip *flip)
+{
+       struct drm_flip_helper *helper = flip->helper;
+       struct drm_flip_driver *driver = helper->driver;
+       bool need_cleanup = false;
+
+       helper->funcs->complete(flip);
+
+       if (flip->flipped) {
+               if (drm_flip_set_scanout(helper, flip))
+                       need_cleanup = true;
+       }
+
+       list_add_tail(&flip->list, &driver->finish_list);
+
+       return need_cleanup;
+}
+
+void drm_flip_helper_init(struct drm_flip_helper *helper,
+                         struct drm_flip_driver *driver,
+                         const struct drm_flip_helper_funcs *funcs)
+{
+       helper->pending_flip = NULL;
+       helper->scanout_flip = NULL;
+       helper->driver = driver;
+       helper->funcs = funcs;
+}
+
+void drm_flip_helper_fini(struct drm_flip_helper *helper)
+{
+       unsigned long flags;
+       struct drm_flip_driver *driver = helper->driver;
+       struct drm_flip *pending_flip;
+       bool need_finish = false;
+       bool need_cleanup = false;
+
+       spin_lock_irqsave(&driver->lock, flags);
+
+       pending_flip = helper->pending_flip;
+
+       if (pending_flip) {
+               need_finish = true;
+
+               if (drm_flip_complete(pending_flip))
+                       need_cleanup = true;
+
+               helper->pending_flip = NULL;
+       }
+
+       if (drm_flip_set_scanout(helper, NULL))
+               need_cleanup = true;
+
+       spin_unlock_irqrestore(&driver->lock, flags);
+
+       if (need_finish)
+               queue_work(driver->wq, &driver->finish_work);
+
+       if (need_cleanup)
+               queue_work(driver->wq, &driver->cleanup_work);
+}
+
+void drm_flip_helper_vblank(struct drm_flip_helper *helper)
+{
+       struct drm_flip_driver *driver = helper->driver;
+       struct drm_flip *pending_flip;
+       unsigned long flags;
+       bool need_finish = false;
+       bool need_cleanup = false;
+
+       spin_lock_irqsave(&driver->lock, flags);
+
+       pending_flip = helper->pending_flip;
+
+       if (pending_flip) {
+               BUG_ON(pending_flip->helper != helper);
+
+               if (helper->funcs->vblank(pending_flip))
+                       pending_flip->flipped = true;
+
+               if (pending_flip->flipped) {
+                       need_finish = true;
+
+                       if (drm_flip_complete(pending_flip))
+                               need_cleanup = true;
+
+                       helper->pending_flip = NULL;
+               }
+       }
+
+       spin_unlock_irqrestore(&driver->lock, flags);
+
+       if (need_finish)
+               queue_work(driver->wq, &driver->finish_work);
+
+       if (need_cleanup)
+               queue_work(driver->wq, &driver->cleanup_work);
+}
+
+void drm_flip_driver_init(struct drm_flip_driver *driver,
+                         const struct drm_flip_driver_funcs *funcs)
+{
+       spin_lock_init(&driver->lock);
+
+       INIT_LIST_HEAD(&driver->finish_list);
+       INIT_LIST_HEAD(&driver->cleanup_list);
+
+       INIT_WORK(&driver->finish_work, drm_flip_driver_finish);
+       INIT_WORK(&driver->cleanup_work, drm_flip_driver_cleanup);
+
+       driver->funcs = funcs;
+
+       driver->wq = create_singlethread_workqueue("drm_flip");
+}
+
+void drm_flip_driver_fini(struct drm_flip_driver *driver)
+{
+       flush_work_sync(&driver->finish_work);
+       flush_work_sync(&driver->cleanup_work);
+
+       destroy_workqueue(driver->wq);
+
+       /* All the scheduled flips should be cleaned up by now. */
+       WARN_ON(!list_empty(&driver->finish_list));
+       WARN_ON(!list_empty(&driver->cleanup_list));
+}
+
+void drm_flip_driver_schedule_flips(struct drm_flip_driver *driver,
+                                   struct list_head *flips)
+{
+       unsigned long flags;
+       struct drm_flip *flip, *next;
+       bool need_finish = false;
+       bool need_cleanup = false;
+
+       spin_lock_irqsave(&driver->lock, flags);
+
+       list_for_each_entry(flip, flips, list) {
+               struct drm_flip_helper *helper = flip->helper;
+               struct drm_flip *pending_flip = helper->pending_flip;
+
+               if (helper->funcs->flip(flip, pending_flip))
+                       pending_flip->flipped = true;
+       }
+
+       if (driver->funcs->flush)
+               driver->funcs->flush(driver);
+
+       /* Complete all flips that got overridden */
+       list_for_each_entry_safe(flip, next, flips, list) {
+               struct drm_flip_helper *helper = flip->helper;
+               struct drm_flip *pending_flip = helper->pending_flip;
+
+               BUG_ON(helper->driver != driver);
+
+               if (pending_flip) {
+                       BUG_ON(pending_flip->helper != helper);
+
+                       need_finish = true;
+
+                       if (drm_flip_complete(pending_flip))
+                               need_cleanup = true;
+               }
+
+               list_del_init(&flip->list);
+               helper->pending_flip = flip;
+       }
+
+       spin_unlock_irqrestore(&driver->lock, flags);
+
+       if (need_finish)
+               queue_work(driver->wq, &driver->finish_work);
+
+       if (need_cleanup)
+               queue_work(driver->wq, &driver->cleanup_work);
+}
+
+void drm_flip_driver_prepare_flips(struct drm_flip_driver *driver,
+                                  struct list_head *flips)
+{
+       struct drm_flip *flip;
+
+       list_for_each_entry(flip, flips, list) {
+               struct drm_flip_helper *helper = flip->helper;
+
+               if (helper->funcs->prepare)
+                       helper->funcs->prepare(flip);
+       }
+}
+
+void drm_flip_driver_complete_flips(struct drm_flip_driver *driver,
+                                   struct list_head *flips)
+{
+       unsigned long flags;
+       struct drm_flip *flip, *next;
+       bool need_finish = false;
+       bool need_cleanup = false;
+
+       spin_lock_irqsave(&driver->lock, flags);
+
+       /* first complete all pending flips */
+       list_for_each_entry(flip, flips, list) {
+               struct drm_flip_helper *helper = flip->helper;
+               struct drm_flip *pending_flip = helper->pending_flip;
+
+               BUG_ON(helper->driver != driver);
+
+               if (pending_flip) {
+                       BUG_ON(pending_flip->helper != helper);
+
+                       need_finish = true;
+
+                       if (drm_flip_complete(pending_flip))
+                               need_cleanup = true;
+
+                       helper->pending_flip = NULL;
+               }
+       }
+
+       /* then complete all new flips as well */
+       list_for_each_entry_safe(flip, next, flips, list) {
+               list_del_init(&flip->list);
+
+               /*
+                * This is the flip that gets scanned out
+                * next time the hardware is fired up.
+                */
+               flip->flipped = true;
+
+               need_finish = true;
+
+               if (drm_flip_complete(flip))
+                       need_cleanup = true;
+       }
+
+       spin_unlock_irqrestore(&driver->lock, flags);
+
+       if (need_finish)
+               queue_work(driver->wq, &driver->finish_work);
+
+       if (need_cleanup)
+               queue_work(driver->wq, &driver->cleanup_work);
+}
+
+void drm_flip_init(struct drm_flip *flip,
+                  struct drm_flip_helper *helper)
+{
+       flip->helper = helper;
+       flip->flipped = false;
+       flip->finished = false;
+       INIT_LIST_HEAD(&flip->list);
+}
diff --git a/drivers/staging/mrst/drv/drm_flip.h b/drivers/staging/mrst/drv/drm_flip.h
new file mode 100644 (file)
index 0000000..8e70a88
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2012 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and iated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ * Ville Syrjälä <ville.syrjala@linux.intel.com>
+ */
+
+#ifndef DRM_FLIP_H
+#define DRM_FLIP_H
+
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+struct drm_flip;
+struct drm_flip_helper;
+struct drm_flip_driver;
+
+/* Driver callbacks for drm_flip_driver */
+struct drm_flip_driver_funcs {
+       /*
+        * Optional callback, called after drm_flip_driver_schedule_flips()
+        * has called drm_flip_helper::flip() for all the provided flips.
+        * Can be used to:
+        * - commit the flips atomically to the hardware, if the
+        *   hardware provides some mechanism to do that.
+        * - flush posted writes to make sure all the flips have reached
+        *   the hardware
+        * Called with drm_flip_driver::lock held.
+        */
+       void (*flush)(struct drm_flip_driver *driver);
+};
+
+/*
+ * The driver needs one drm_flip_driver to
+ * coordinates the drm_flip mechanism.
+ */
+struct drm_flip_driver {
+       /* protects drm_flip_driver, drm_flip_helper, and drm_flip internals. */
+       spinlock_t lock;
+
+       /* list of drm_flips waiting to be finished, protected by 'lock' */
+       struct list_head finish_list;
+
+       /* list of drm_flips waiting to be cleaned up, protected by 'lock' */
+       struct list_head cleanup_list;
+
+       /* work used to finish the drm_flips */
+       struct work_struct finish_work;
+
+       /* work used to clean up the drm_flips */
+       struct work_struct cleanup_work;
+
+       /* driver provided callback functions */
+       const struct drm_flip_driver_funcs *funcs;
+
+       /* work queue for finish_work and cleanup_work */
+       struct workqueue_struct *wq;
+};
+
+/* Driver callbacks for drm_flip_helper */
+struct drm_flip_helper_funcs {
+       /*
+        * Optional function to perform heavy but non-timing
+        * critial preparations for the flip.
+        * Called from drm_flip_driver_prepare_flips() with
+        * no extra locks being held.
+        */
+       void (*prepare)(struct drm_flip *flip);
+       /*
+        * Instruct the hardware to flip on the next vblank.
+        * Must return true, iff pending_flip exists, and has
+        * actually flipped (ie. now being scanned out).
+        * Otherwise must return false.
+        * Called with drm_flip_driver::lock held.
+        */
+       bool (*flip)(struct drm_flip *flip,
+                    struct drm_flip *pending_flip);
+       /*
+        * Called from drm_flip_helper_vblank() if
+        * pending_flip exists. Must return true, iff
+        * pending_flip has actually flipped (ie. now
+        * being scanned out). Otherwise must return false.
+        * Called with drm_flip_driver::lock held.
+        */
+       bool (*vblank)(struct drm_flip *pending_flip);
+
+       /*
+        * The flip has just occured, or it got overwritten
+        * by a more recent flip. If the flip occured, it is
+        * now being scanned out, otherwise it is scheduled
+        * for cleanup.
+        * Can be called from drm_flip_driver_schedule_flips(),
+        * drm_flip_driver_complete_flips(), or from
+        * drm_flip_helper_vblank().
+        * Called with drm_flip_driver::lock held.
+        */
+       void (*complete)(struct drm_flip *flip);
+
+       /*
+        * Perform finishing steps on the flip. Called from a workqueue
+        * soon after the flip has completed. The flip's buffer may be
+        * actively scanned out.
+        * Called with no locks being held.
+        */
+       void (*finish)(struct drm_flip *flip);
+
+       /*
+        * Perform final cleanup on the flip. Called from a workqueue
+        * after the flip's buffer is no longer being scanned out.
+        * Called with no locks being held.
+        */
+       void (*cleanup)(struct drm_flip *flip);
+
+};
+
+/*
+ * The driver needs one drm_flip_helper for each scanout engine it
+ * wants to operate through the drm_flip mechanism.
+ */
+struct drm_flip_helper {
+       /* drm_flip from the previous drm_flip_schedule() call */
+       struct drm_flip *pending_flip;
+       /* drm_flip whose buffer is being scanned out */
+       struct drm_flip *scanout_flip;
+       /* associated drm_flip_driver */
+       struct drm_flip_driver *driver;
+       /* driver provided callback functions */
+       const struct drm_flip_helper_funcs *funcs;
+};
+
+/*
+ * This structure represents a single page flip operation.
+ */
+struct drm_flip {
+       /* associated drm_flip_helper */
+       struct drm_flip_helper *helper;
+       /* has this flip occured? */
+       bool flipped;
+       /* has the finish work been executed for this flip? */
+       bool finished;
+       /* used to keep this flip on various lists */
+       struct list_head list;
+};
+
+/*
+ * Initialize the flip driver.
+ */
+void drm_flip_driver_init(struct drm_flip_driver *driver,
+                         const struct drm_flip_driver_funcs *funcs);
+
+/*
+ * Finalize the flip driver. This will block until all the
+ * pending finish and cleanup work has been completed.
+ */
+void drm_flip_driver_fini(struct drm_flip_driver *driver);
+
+/*
+ * Initialize flip helper.
+ */
+void drm_flip_helper_init(struct drm_flip_helper *helper,
+                         struct drm_flip_driver *driver,
+                         const struct drm_flip_helper_funcs *funcs);
+
+/*
+ * Finalize flip helper. This will forcefully complete the
+ * helper's pending flip (if any).
+ */
+void drm_flip_helper_fini(struct drm_flip_helper *helper);
+
+/*
+ * Call this from the driver's vblank handler for the scanout engine
+ * associated with this helper.
+ */
+void drm_flip_helper_vblank(struct drm_flip_helper *helper);
+
+/*
+ * This will call drm_flip_helper::prepare() (if provided) for all the
+ * drm_flips on the list. The purpose is to perform any non-timing critical
+ * preparation steps for the flips before taking locks or disabling interrupts.
+ */
+void drm_flip_driver_prepare_flips(struct drm_flip_driver *driver,
+                                  struct list_head *flips);
+
+/*
+ * Schedule the flips on the list to occur on the next vblank.
+ *
+ * This will call drm_flip_helper::flip() for all the drm_flips on the list.
+ * It will then call drm_flip_driver::flush(), after which it will complete
+ * any pending_flip that got overridden by the new flips.
+ *
+ * Unless the hardware provides some mechanism to synchronize the flips, the
+ * time spent until drm_flip_driver::flush() is timing critical and the driver
+ * must somehow make sure it can complete the operation in a seemingly atomic
+ * fashion.
+ */
+void drm_flip_driver_schedule_flips(struct drm_flip_driver *driver,
+                                   struct list_head *flips);
+
+/*
+ * This will complete any pending_flip and also all the flips
+ * on the provided list (in that order).
+ *
+ * Call this instead of drm_flip_driver_schedule_flips()
+ * eg. if the hardware powered down, and you just want to keep
+ * the drm_flip mechanim's state consistent w/o waking up the
+ * hardware.
+ */
+void drm_flip_driver_complete_flips(struct drm_flip_driver *driver,
+                                   struct list_head *flips);
+
+/*
+ * Initialize the flip structure members.
+ */
+void drm_flip_init(struct drm_flip *flip,
+                  struct drm_flip_helper *helper);
+
+#endif