drm/radeon/kms: add pageflip ioctl support (v3)

This adds support for dri2 pageflipping.

v2: precision updates from Mario Kleiner.
v3: Multihead fixes from Mario Kleiner; missing crtc offset
    add note about update pending bit on pre-avivo chips

Signed-off-by: Alex Deucher <alexdeucher@gmail.com>
Signed-off-by: Mario Kleiner <mario.kleiner@tuebingen.mpg.de>
Signed-off-by: Dave Airlie <airlied@redhat.com>
This commit is contained in:
Alex Deucher
2010-11-21 10:59:01 -05:00
committed by Dave Airlie
parent f5a8020903
commit 6f34be50bd
17 changed files with 897 additions and 193 deletions

View File

@@ -183,12 +183,273 @@ static void radeon_crtc_destroy(struct drm_crtc *crtc)
kfree(radeon_crtc);
}
/*
* Handle unpin events outside the interrupt handler proper.
*/
static void radeon_unpin_work_func(struct work_struct *__work)
{
struct radeon_unpin_work *work =
container_of(__work, struct radeon_unpin_work, work);
int r;
/* unpin of the old buffer */
r = radeon_bo_reserve(work->old_rbo, false);
if (likely(r == 0)) {
r = radeon_bo_unpin(work->old_rbo);
if (unlikely(r != 0)) {
DRM_ERROR("failed to unpin buffer after flip\n");
}
radeon_bo_unreserve(work->old_rbo);
} else
DRM_ERROR("failed to reserve buffer after flip\n");
kfree(work);
}
void radeon_crtc_handle_flip(struct radeon_device *rdev, int crtc_id)
{
struct radeon_crtc *radeon_crtc = rdev->mode_info.crtcs[crtc_id];
struct radeon_unpin_work *work;
struct drm_pending_vblank_event *e;
struct timeval now;
unsigned long flags;
u32 update_pending;
int vpos, hpos;
spin_lock_irqsave(&rdev->ddev->event_lock, flags);
work = radeon_crtc->unpin_work;
if (work == NULL ||
!radeon_fence_signaled(work->fence)) {
spin_unlock_irqrestore(&rdev->ddev->event_lock, flags);
return;
}
/* New pageflip, or just completion of a previous one? */
if (!radeon_crtc->deferred_flip_completion) {
/* do the flip (mmio) */
update_pending = radeon_page_flip(rdev, crtc_id, work->new_crtc_base);
} else {
/* This is just a completion of a flip queued in crtc
* at last invocation. Make sure we go directly to
* completion routine.
*/
update_pending = 0;
radeon_crtc->deferred_flip_completion = 0;
}
/* Has the pageflip already completed in crtc, or is it certain
* to complete in this vblank?
*/
if (update_pending &&
(DRM_SCANOUTPOS_VALID & radeon_get_crtc_scanoutpos(rdev->ddev, crtc_id,
&vpos, &hpos)) &&
(vpos >=0) &&
(vpos < (99 * rdev->mode_info.crtcs[crtc_id]->base.hwmode.crtc_vdisplay)/100)) {
/* crtc didn't flip in this target vblank interval,
* but flip is pending in crtc. It will complete it
* in next vblank interval, so complete the flip at
* next vblank irq.
*/
radeon_crtc->deferred_flip_completion = 1;
spin_unlock_irqrestore(&rdev->ddev->event_lock, flags);
return;
}
/* Pageflip (will be) certainly completed in this vblank. Clean up. */
radeon_crtc->unpin_work = NULL;
/* wakeup userspace */
if (work->event) {
e = work->event;
do_gettimeofday(&now);
e->event.sequence = drm_vblank_count(rdev->ddev, radeon_crtc->crtc_id);
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);
}
spin_unlock_irqrestore(&rdev->ddev->event_lock, flags);
drm_vblank_put(rdev->ddev, radeon_crtc->crtc_id);
radeon_fence_unref(&work->fence);
radeon_post_page_flip(work->rdev, work->crtc_id);
schedule_work(&work->work);
}
static int radeon_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event)
{
struct drm_device *dev = crtc->dev;
struct radeon_device *rdev = dev->dev_private;
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
struct radeon_framebuffer *old_radeon_fb;
struct radeon_framebuffer *new_radeon_fb;
struct drm_gem_object *obj;
struct radeon_bo *rbo;
struct radeon_fence *fence;
struct radeon_unpin_work *work;
unsigned long flags;
u32 tiling_flags, pitch_pixels;
u64 base;
int r;
work = kzalloc(sizeof *work, GFP_KERNEL);
if (work == NULL)
return -ENOMEM;
r = radeon_fence_create(rdev, &fence);
if (unlikely(r != 0)) {
kfree(work);
DRM_ERROR("flip queue: failed to create fence.\n");
return -ENOMEM;
}
work->event = event;
work->rdev = rdev;
work->crtc_id = radeon_crtc->crtc_id;
work->fence = radeon_fence_ref(fence);
old_radeon_fb = to_radeon_framebuffer(crtc->fb);
new_radeon_fb = to_radeon_framebuffer(fb);
/* schedule unpin of the old buffer */
obj = old_radeon_fb->obj;
rbo = obj->driver_private;
work->old_rbo = rbo;
INIT_WORK(&work->work, radeon_unpin_work_func);
/* We borrow the event spin lock for protecting unpin_work */
spin_lock_irqsave(&dev->event_lock, flags);
if (radeon_crtc->unpin_work) {
spin_unlock_irqrestore(&dev->event_lock, flags);
kfree(work);
radeon_fence_unref(&fence);
DRM_DEBUG_DRIVER("flip queue: crtc already busy\n");
return -EBUSY;
}
radeon_crtc->unpin_work = work;
radeon_crtc->deferred_flip_completion = 0;
spin_unlock_irqrestore(&dev->event_lock, flags);
/* pin the new buffer */
obj = new_radeon_fb->obj;
rbo = obj->driver_private;
DRM_DEBUG_DRIVER("flip-ioctl() cur_fbo = %p, cur_bbo = %p\n",
work->old_rbo, rbo);
r = radeon_bo_reserve(rbo, false);
if (unlikely(r != 0)) {
DRM_ERROR("failed to reserve new rbo buffer before flip\n");
goto pflip_cleanup;
}
r = radeon_bo_pin(rbo, RADEON_GEM_DOMAIN_VRAM, &base);
if (unlikely(r != 0)) {
radeon_bo_unreserve(rbo);
r = -EINVAL;
DRM_ERROR("failed to pin new rbo buffer before flip\n");
goto pflip_cleanup;
}
radeon_bo_get_tiling_flags(rbo, &tiling_flags, NULL);
radeon_bo_unreserve(rbo);
if (!ASIC_IS_AVIVO(rdev)) {
/* crtc offset is from display base addr not FB location */
base -= radeon_crtc->legacy_display_base_addr;
pitch_pixels = fb->pitch / (fb->bits_per_pixel / 8);
if (tiling_flags & RADEON_TILING_MACRO) {
if (ASIC_IS_R300(rdev)) {
base &= ~0x7ff;
} else {
int byteshift = fb->bits_per_pixel >> 4;
int tile_addr = (((crtc->y >> 3) * pitch_pixels + crtc->x) >> (8 - byteshift)) << 11;
base += tile_addr + ((crtc->x << byteshift) % 256) + ((crtc->y % 8) << 8);
}
} else {
int offset = crtc->y * pitch_pixels + crtc->x;
switch (fb->bits_per_pixel) {
case 8:
default:
offset *= 1;
break;
case 15:
case 16:
offset *= 2;
break;
case 24:
offset *= 3;
break;
case 32:
offset *= 4;
break;
}
base += offset;
}
base &= ~7;
}
spin_lock_irqsave(&dev->event_lock, flags);
work->new_crtc_base = base;
spin_unlock_irqrestore(&dev->event_lock, flags);
/* update crtc fb */
crtc->fb = fb;
r = drm_vblank_get(dev, radeon_crtc->crtc_id);
if (r) {
DRM_ERROR("failed to get vblank before flip\n");
goto pflip_cleanup1;
}
/* 32 ought to cover us */
r = radeon_ring_lock(rdev, 32);
if (r) {
DRM_ERROR("failed to lock the ring before flip\n");
goto pflip_cleanup2;
}
/* emit the fence */
radeon_fence_emit(rdev, fence);
/* set the proper interrupt */
radeon_pre_page_flip(rdev, radeon_crtc->crtc_id);
/* fire the ring */
radeon_ring_unlock_commit(rdev);
return 0;
pflip_cleanup2:
drm_vblank_put(dev, radeon_crtc->crtc_id);
pflip_cleanup1:
r = radeon_bo_reserve(rbo, false);
if (unlikely(r != 0)) {
DRM_ERROR("failed to reserve new rbo in error path\n");
goto pflip_cleanup;
}
r = radeon_bo_unpin(rbo);
if (unlikely(r != 0)) {
radeon_bo_unreserve(rbo);
r = -EINVAL;
DRM_ERROR("failed to unpin new rbo in error path\n");
goto pflip_cleanup;
}
radeon_bo_unreserve(rbo);
pflip_cleanup:
spin_lock_irqsave(&dev->event_lock, flags);
radeon_crtc->unpin_work = NULL;
spin_unlock_irqrestore(&dev->event_lock, flags);
radeon_fence_unref(&fence);
kfree(work);
return r;
}
static const struct drm_crtc_funcs radeon_crtc_funcs = {
.cursor_set = radeon_crtc_cursor_set,
.cursor_move = radeon_crtc_cursor_move,
.gamma_set = radeon_crtc_gamma_set,
.set_config = drm_crtc_helper_set_config,
.destroy = radeon_crtc_destroy,
.page_flip = radeon_crtc_page_flip,
};
static void radeon_crtc_init(struct drm_device *dev, int index)