staging: drm/omap: defer unpin until scanout completes
authorRob Clark <rob@ti.com>
Mon, 5 Mar 2012 16:48:35 +0000 (10:48 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 7 Mar 2012 21:38:07 +0000 (13:38 -0800)
When flipping, defer unpinning until scanout completes, as indicated
by the appropriate END_WIN irq.

This also re-organizes things a bit, in replacing omap_fb_{pin,unpin}
with omap_fb_replace(), to make it easier to add support for scanout
synchronized DMM refill mode (flipping by just reprogramming DMM
synchronized with DSS scanout).

Signed-off-by: Rob Clark <rob@ti.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/omapdrm/omap_drv.h
drivers/staging/omapdrm/omap_fb.c
drivers/staging/omapdrm/omap_plane.c

index a84547c2464b48d7efca502c84b75cffc4d9f9bd..fe4766e27b9eba8dd094f4955551d3b83e410ac3 100644 (file)
@@ -101,8 +101,9 @@ struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev,
 struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev,
                struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos);
 struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb, int p);
-int omap_framebuffer_pin(struct drm_framebuffer *fb);
-void omap_framebuffer_unpin(struct drm_framebuffer *fb);
+int omap_framebuffer_replace(struct drm_framebuffer *a,
+               struct drm_framebuffer *b, void *arg,
+               void (*unpin)(void *arg, struct drm_gem_object *bo));
 void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, int x, int y,
                struct omap_overlay_info *info);
 struct drm_connector *omap_framebuffer_get_next_connector(
index 08e2e356b58019571a0c3720cb5ea47f06081a8a..fcb248f4cb0c480c8d6efa3a7cf6b37b570df1bb 100644 (file)
@@ -137,41 +137,6 @@ static const struct drm_framebuffer_funcs omap_framebuffer_funcs = {
        .dirty = omap_framebuffer_dirty,
 };
 
-/* pins buffer in preparation for scanout */
-int omap_framebuffer_pin(struct drm_framebuffer *fb)
-{
-       struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb);
-       int ret, i, n = drm_format_num_planes(omap_fb->format->pixel_format);
-
-       for (i = 0; i < n; i++) {
-               struct plane *plane = &omap_fb->planes[i];
-               ret = omap_gem_get_paddr(plane->bo, &plane->paddr, true);
-               if (ret)
-                       goto fail;
-       }
-
-       return 0;
-
-fail:
-       while (--i > 0) {
-               struct plane *plane = &omap_fb->planes[i];
-               omap_gem_put_paddr(plane->bo);
-       }
-       return ret;
-}
-
-/* releases buffer when done with scanout */
-void omap_framebuffer_unpin(struct drm_framebuffer *fb)
-{
-       struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb);
-       int i, n = drm_format_num_planes(omap_fb->format->pixel_format);
-
-       for (i = 0; i < n; i++) {
-               struct plane *plane = &omap_fb->planes[i];
-               omap_gem_put_paddr(plane->bo);
-       }
-}
-
 /* update ovl info for scanout, handles cases of multi-planar fb's, etc.
  */
 void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, int x, int y,
@@ -201,6 +166,55 @@ void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, int x, int y,
        }
 }
 
+/* Call for unpin 'a' (if not NULL), and pin 'b' (if not NULL).  Although
+ * buffers to unpin are just just pushed to the unpin fifo so that the
+ * caller can defer unpin until vblank.
+ *
+ * Note if this fails (ie. something went very wrong!), all buffers are
+ * unpinned, and the caller disables the overlay.  We could have tried
+ * to revert back to the previous set of pinned buffers but if things are
+ * hosed there is no guarantee that would succeed.
+ */
+int omap_framebuffer_replace(struct drm_framebuffer *a,
+               struct drm_framebuffer *b, void *arg,
+               void (*unpin)(void *arg, struct drm_gem_object *bo))
+{
+       int ret = 0, i, na, nb;
+       struct omap_framebuffer *ofba = to_omap_framebuffer(a);
+       struct omap_framebuffer *ofbb = to_omap_framebuffer(b);
+
+       na = a ? drm_format_num_planes(a->pixel_format) : 0;
+       nb = b ? drm_format_num_planes(b->pixel_format) : 0;
+
+       for (i = 0; i < max(na, nb); i++) {
+               struct plane *pa, *pb;
+
+               pa = (i < na) ? &ofba->planes[i] : NULL;
+               pb = (i < nb) ? &ofbb->planes[i] : NULL;
+
+               if (pa) {
+                       unpin(arg, pa->bo);
+                       pa->paddr = 0;
+               }
+
+               if (pb && !ret)
+                       ret = omap_gem_get_paddr(pb->bo, &pb->paddr, true);
+       }
+
+       if (ret) {
+               /* something went wrong.. unpin what has been pinned */
+               for (i = 0; i < nb; i++) {
+                       struct plane *pb = &ofba->planes[i];
+                       if (pb->paddr) {
+                               unpin(arg, pb->bo);
+                               pb->paddr = 0;
+                       }
+               }
+       }
+
+       return ret;
+}
+
 struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb, int p)
 {
        struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb);
index c5625e3461bb14649ad8e5c613c4b7e97090eb35..55ddc580fe68037dfec3b2430f2d9d50f9ee22e1 100644 (file)
@@ -17,6 +17,8 @@
  * this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <linux/kfifo.h>
+
 #include "omap_drv.h"
 
 /* some hackery because omapdss has an 'enum omap_plane' (which would be
@@ -46,8 +48,57 @@ struct omap_plane {
 
        uint32_t nformats;
        uint32_t formats[32];
+
+       /* for synchronizing access to unpins fifo */
+       struct mutex unpin_mutex;
+
+       /* set of bo's pending unpin until next END_WIN irq */
+       DECLARE_KFIFO_PTR(unpin_fifo, struct drm_gem_object *);
+       int num_unpins, pending_num_unpins;
+
+       /* for deferred unpin when we need to wait for scanout complete irq */
+       struct work_struct work;
+};
+
+/* map from ovl->id to the irq we are interested in for scanout-done */
+static const uint32_t id2irq[] = {
+               [OMAP_DSS_GFX]    = DISPC_IRQ_GFX_END_WIN,
+               [OMAP_DSS_VIDEO1] = DISPC_IRQ_VID1_END_WIN,
+               [OMAP_DSS_VIDEO2] = DISPC_IRQ_VID2_END_WIN,
+               [OMAP_DSS_VIDEO3] = DISPC_IRQ_VID3_END_WIN,
 };
 
+static void dispc_isr(void *arg, uint32_t mask)
+{
+       struct drm_plane *plane = arg;
+       struct omap_plane *omap_plane = to_omap_plane(plane);
+       struct omap_drm_private *priv = plane->dev->dev_private;
+
+       omap_dispc_unregister_isr(dispc_isr, plane,
+                       id2irq[omap_plane->ovl->id]);
+
+       queue_work(priv->wq, &omap_plane->work);
+}
+
+static void unpin_worker(struct work_struct *work)
+{
+       struct omap_plane *omap_plane =
+                       container_of(work, struct omap_plane, work);
+
+       mutex_lock(&omap_plane->unpin_mutex);
+       DBG("unpinning %d of %d", omap_plane->num_unpins,
+                       omap_plane->num_unpins + omap_plane->pending_num_unpins);
+       while (omap_plane->num_unpins > 0) {
+               struct drm_gem_object *bo = NULL;
+               int ret = kfifo_get(&omap_plane->unpin_fifo, &bo);
+               WARN_ON(!ret);
+               omap_gem_put_paddr(bo);
+               drm_gem_object_unreference_unlocked(bo);
+               omap_plane->num_unpins--;
+       }
+       mutex_unlock(&omap_plane->unpin_mutex);
+}
+
 /* push changes down to dss2 */
 static int commit(struct drm_plane *plane)
 {
@@ -73,6 +124,11 @@ static int commit(struct drm_plane *plane)
                return ret;
        }
 
+       mutex_lock(&omap_plane->unpin_mutex);
+       omap_plane->num_unpins += omap_plane->pending_num_unpins;
+       omap_plane->pending_num_unpins = 0;
+       mutex_unlock(&omap_plane->unpin_mutex);
+
        /* our encoder doesn't necessarily get a commit() after this, in
         * particular in the dpms() and mode_set_base() cases, so force the
         * manager to update:
@@ -85,8 +141,29 @@ static int commit(struct drm_plane *plane)
                        dev_err(dev->dev, "could not apply settings\n");
                        return ret;
                }
+
+               /*
+                * NOTE: really this should be atomic w/ mgr->apply() but
+                * omapdss does not expose such an API
+                */
+               if (omap_plane->num_unpins > 0) {
+                       ret = omap_dispc_register_isr(dispc_isr,
+                               plane, id2irq[ovl->id]);
+               }
+
+               /*
+                * omapdss has upper limit on # of registered irq handlers,
+                * which we shouldn't hit.. but if we do the limit should
+                * be raised or bad things happen:
+                */
+               WARN_ON(ret == -EBUSY);
+
+       } else {
+               struct omap_drm_private *priv = dev->dev_private;
+               queue_work(priv->wq, &omap_plane->work);
        }
 
+
        if (ovl->is_enabled(ovl)) {
                omap_framebuffer_flush(plane->fb, info->pos_x, info->pos_y,
                                info->out_width, info->out_height);
@@ -139,21 +216,48 @@ static void update_manager(struct drm_plane *plane)
        }
 }
 
+static void unpin(void *arg, struct drm_gem_object *bo)
+{
+       struct drm_plane *plane = arg;
+       struct omap_plane *omap_plane = to_omap_plane(plane);
+
+       if (kfifo_put(&omap_plane->unpin_fifo,
+                       (const struct drm_gem_object **)&bo)) {
+               omap_plane->pending_num_unpins++;
+               /* also hold a ref so it isn't free'd while pinned */
+               drm_gem_object_reference(bo);
+       } else {
+               dev_err(plane->dev->dev, "unpin fifo full!\n");
+               omap_gem_put_paddr(bo);
+       }
+}
+
 /* update which fb (if any) is pinned for scanout */
 static int update_pin(struct drm_plane *plane, struct drm_framebuffer *fb)
 {
        struct omap_plane *omap_plane = to_omap_plane(plane);
-       int ret = 0;
+       struct drm_framebuffer *pinned_fb = omap_plane->pinned_fb;
+
+       if (pinned_fb != fb) {
+               int ret;
+
+               DBG("%p -> %p", pinned_fb, fb);
+
+               mutex_lock(&omap_plane->unpin_mutex);
+               ret = omap_framebuffer_replace(pinned_fb, fb, plane, unpin);
+               mutex_unlock(&omap_plane->unpin_mutex);
+
+               if (ret) {
+                       dev_err(plane->dev->dev, "could not swap %p -> %p\n",
+                                       omap_plane->pinned_fb, fb);
+                       omap_plane->pinned_fb = NULL;
+                       return ret;
+               }
 
-       if (omap_plane->pinned_fb != fb) {
-               if (omap_plane->pinned_fb)
-                       omap_framebuffer_unpin(omap_plane->pinned_fb);
                omap_plane->pinned_fb = fb;
-               if (fb)
-                       ret = omap_framebuffer_pin(fb);
        }
 
-       return ret;
+       return 0;
 }
 
 /* update parameters that are dependent on the framebuffer dimensions and
@@ -243,6 +347,8 @@ static void omap_plane_destroy(struct drm_plane *plane)
        DBG("%s", omap_plane->ovl->name);
        omap_plane_disable(plane);
        drm_plane_cleanup(plane);
+       WARN_ON(omap_plane->pending_num_unpins + omap_plane->num_unpins > 0);
+       kfifo_free(&omap_plane->unpin_fifo);
        kfree(omap_plane);
 }
 
@@ -260,8 +366,10 @@ int omap_plane_dpms(struct drm_plane *plane, int mode)
                if (!r)
                        r = ovl->enable(ovl);
        } else {
+               struct omap_drm_private *priv = plane->dev->dev_private;
                r = ovl->disable(ovl);
                update_pin(plane, NULL);
+               queue_work(priv->wq, &omap_plane->work);
        }
 
        return r;
@@ -280,16 +388,30 @@ struct drm_plane *omap_plane_init(struct drm_device *dev,
 {
        struct drm_plane *plane = NULL;
        struct omap_plane *omap_plane;
+       int ret;
 
        DBG("%s: possible_crtcs=%08x, priv=%d", ovl->name,
                        possible_crtcs, priv);
 
+       /* friendly reminder to update table for future hw: */
+       WARN_ON(ovl->id >= ARRAY_SIZE(id2irq));
+
        omap_plane = kzalloc(sizeof(*omap_plane), GFP_KERNEL);
        if (!omap_plane) {
                dev_err(dev->dev, "could not allocate plane\n");
                goto fail;
        }
 
+       mutex_init(&omap_plane->unpin_mutex);
+
+       ret = kfifo_alloc(&omap_plane->unpin_fifo, 16, GFP_KERNEL);
+       if (ret) {
+               dev_err(dev->dev, "could not allocate unpin FIFO\n");
+               goto fail;
+       }
+
+       INIT_WORK(&omap_plane->work, unpin_worker);
+
        omap_plane->nformats = omap_framebuffer_get_formats(
                        omap_plane->formats, ARRAY_SIZE(omap_plane->formats),
                        ovl->supported_modes);