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