@@ -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(
@@ -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);
@@ -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);