|
@@ -30,6 +30,8 @@
|
|
|
#include "nouveau_fb.h"
|
|
|
#include "nouveau_fbcon.h"
|
|
|
#include "nouveau_hw.h"
|
|
|
+#include "nouveau_crtc.h"
|
|
|
+#include "nouveau_dma.h"
|
|
|
|
|
|
static void
|
|
|
nouveau_user_framebuffer_destroy(struct drm_framebuffer *drm_fb)
|
|
@@ -131,3 +133,181 @@ nouveau_vblank_disable(struct drm_device *dev, int crtc)
|
|
|
else
|
|
|
NVWriteCRTC(dev, crtc, NV_PCRTC_INTR_EN_0, 0);
|
|
|
}
|
|
|
+
|
|
|
+static int
|
|
|
+nouveau_page_flip_reserve(struct nouveau_bo *old_bo,
|
|
|
+ struct nouveau_bo *new_bo)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = nouveau_bo_pin(new_bo, TTM_PL_FLAG_VRAM);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ ret = ttm_bo_reserve(&new_bo->bo, false, false, false, 0);
|
|
|
+ if (ret)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ ret = ttm_bo_reserve(&old_bo->bo, false, false, false, 0);
|
|
|
+ if (ret)
|
|
|
+ goto fail_unreserve;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+fail_unreserve:
|
|
|
+ ttm_bo_unreserve(&new_bo->bo);
|
|
|
+fail:
|
|
|
+ nouveau_bo_unpin(new_bo);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+nouveau_page_flip_unreserve(struct nouveau_bo *old_bo,
|
|
|
+ struct nouveau_bo *new_bo,
|
|
|
+ struct nouveau_fence *fence)
|
|
|
+{
|
|
|
+ nouveau_bo_fence(new_bo, fence);
|
|
|
+ ttm_bo_unreserve(&new_bo->bo);
|
|
|
+
|
|
|
+ nouveau_bo_fence(old_bo, fence);
|
|
|
+ ttm_bo_unreserve(&old_bo->bo);
|
|
|
+
|
|
|
+ nouveau_bo_unpin(old_bo);
|
|
|
+}
|
|
|
+
|
|
|
+static int
|
|
|
+nouveau_page_flip_emit(struct nouveau_channel *chan,
|
|
|
+ struct nouveau_bo *old_bo,
|
|
|
+ struct nouveau_bo *new_bo,
|
|
|
+ struct nouveau_page_flip_state *s,
|
|
|
+ struct nouveau_fence **pfence)
|
|
|
+{
|
|
|
+ struct drm_device *dev = chan->dev;
|
|
|
+ unsigned long flags;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Queue it to the pending list */
|
|
|
+ spin_lock_irqsave(&dev->event_lock, flags);
|
|
|
+ list_add_tail(&s->head, &chan->nvsw.flip);
|
|
|
+ spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
+
|
|
|
+ /* Synchronize with the old framebuffer */
|
|
|
+ ret = nouveau_fence_sync(old_bo->bo.sync_obj, chan);
|
|
|
+ if (ret)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ /* Emit the pageflip */
|
|
|
+ ret = RING_SPACE(chan, 2);
|
|
|
+ if (ret)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ BEGIN_RING(chan, NvSubSw, NV_SW_PAGE_FLIP, 1);
|
|
|
+ OUT_RING(chan, 0);
|
|
|
+ FIRE_RING(chan);
|
|
|
+
|
|
|
+ ret = nouveau_fence_new(chan, pfence, true);
|
|
|
+ if (ret)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+fail:
|
|
|
+ spin_lock_irqsave(&dev->event_lock, flags);
|
|
|
+ list_del(&s->head);
|
|
|
+ spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
|
|
|
+ struct drm_pending_vblank_event *event)
|
|
|
+{
|
|
|
+ struct drm_device *dev = crtc->dev;
|
|
|
+ struct drm_nouveau_private *dev_priv = dev->dev_private;
|
|
|
+ struct nouveau_bo *old_bo = nouveau_framebuffer(crtc->fb)->nvbo;
|
|
|
+ struct nouveau_bo *new_bo = nouveau_framebuffer(fb)->nvbo;
|
|
|
+ struct nouveau_page_flip_state *s;
|
|
|
+ struct nouveau_channel *chan;
|
|
|
+ struct nouveau_fence *fence;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (dev_priv->engine.graph.accel_blocked)
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ s = kzalloc(sizeof(*s), GFP_KERNEL);
|
|
|
+ if (!s)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ /* Don't let the buffers go away while we flip */
|
|
|
+ ret = nouveau_page_flip_reserve(old_bo, new_bo);
|
|
|
+ if (ret)
|
|
|
+ goto fail_free;
|
|
|
+
|
|
|
+ /* Initialize a page flip struct */
|
|
|
+ *s = (struct nouveau_page_flip_state)
|
|
|
+ { { }, s->event, nouveau_crtc(crtc)->index,
|
|
|
+ fb->bits_per_pixel, fb->pitch, crtc->x, crtc->y,
|
|
|
+ new_bo->bo.offset };
|
|
|
+
|
|
|
+ /* Choose the channel the flip will be handled in */
|
|
|
+ chan = nouveau_fence_channel(new_bo->bo.sync_obj);
|
|
|
+ if (!chan)
|
|
|
+ chan = nouveau_channel_get_unlocked(dev_priv->channel);
|
|
|
+ mutex_lock(&chan->mutex);
|
|
|
+
|
|
|
+ /* Emit a page flip */
|
|
|
+ ret = nouveau_page_flip_emit(chan, old_bo, new_bo, s, &fence);
|
|
|
+ nouveau_channel_put(&chan);
|
|
|
+ if (ret)
|
|
|
+ goto fail_unreserve;
|
|
|
+
|
|
|
+ /* Update the crtc struct and cleanup */
|
|
|
+ crtc->fb = fb;
|
|
|
+
|
|
|
+ nouveau_page_flip_unreserve(old_bo, new_bo, fence);
|
|
|
+ nouveau_fence_unref(&fence);
|
|
|
+ return 0;
|
|
|
+
|
|
|
+fail_unreserve:
|
|
|
+ nouveau_page_flip_unreserve(old_bo, new_bo, NULL);
|
|
|
+fail_free:
|
|
|
+ kfree(s);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+nouveau_finish_page_flip(struct nouveau_channel *chan,
|
|
|
+ struct nouveau_page_flip_state *ps)
|
|
|
+{
|
|
|
+ struct drm_device *dev = chan->dev;
|
|
|
+ struct nouveau_page_flip_state *s;
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&dev->event_lock, flags);
|
|
|
+
|
|
|
+ if (list_empty(&chan->nvsw.flip)) {
|
|
|
+ NV_ERROR(dev, "Unexpected pageflip in channel %d.\n", chan->id);
|
|
|
+ spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ s = list_first_entry(&chan->nvsw.flip,
|
|
|
+ struct nouveau_page_flip_state, head);
|
|
|
+ if (s->event) {
|
|
|
+ struct drm_pending_vblank_event *e = s->event;
|
|
|
+ struct timeval now;
|
|
|
+
|
|
|
+ do_gettimeofday(&now);
|
|
|
+ e->event.sequence = 0;
|
|
|
+ e->event.tv_sec = now.tv_sec;
|
|
|
+ e->event.tv_usec = now.tv_usec;
|
|
|
+ list_add_tail(&e->base.link, &e->base.file_priv->event_list);
|
|
|
+ wake_up_interruptible(&e->base.file_priv->event_wait);
|
|
|
+ }
|
|
|
+
|
|
|
+ list_del(&s->head);
|
|
|
+ *ps = *s;
|
|
|
+ kfree(s);
|
|
|
+
|
|
|
+ spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
+ return 0;
|
|
|
+}
|