--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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