From patchwork Thu Oct 25 18:05:50 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Ville Syrjala X-Patchwork-Id: 1646791 Return-Path: X-Original-To: patchwork-dri-devel@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by patchwork2.kernel.org (Postfix) with ESMTP id C3466DF2AB for ; Thu, 25 Oct 2012 18:40:41 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 4B5239ED2F for ; Thu, 25 Oct 2012 11:40:41 -0700 (PDT) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mga02.intel.com (mga02.intel.com [134.134.136.20]) by gabe.freedesktop.org (Postfix) with ESMTP id 09BD0A0A2A for ; Thu, 25 Oct 2012 11:08:29 -0700 (PDT) Received: from orsmga002.jf.intel.com ([10.7.209.21]) by orsmga101.jf.intel.com with ESMTP; 25 Oct 2012 11:08:29 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="4.80,648,1344236400"; d="scan'208";a="232545228" Received: from stinkbox.fi.intel.com (HELO stinkbox) ([10.237.72.168]) by orsmga002.jf.intel.com with SMTP; 25 Oct 2012 11:08:27 -0700 Received: by stinkbox (sSMTP sendmail emulation); Thu, 25 Oct 2012 21:08:26 +0300 From: ville.syrjala@linux.intel.com To: dri-devel@lists.freedesktop.org Subject: [PATCH 47/51] drm/i915: Add atomic page flip support Date: Thu, 25 Oct 2012 21:05:50 +0300 Message-Id: <1351188354-24233-48-git-send-email-ville.syrjala@linux.intel.com> X-Mailer: git-send-email 1.7.8.6 In-Reply-To: <1351188354-24233-1-git-send-email-ville.syrjala@linux.intel.com> References: <1351188354-24233-1-git-send-email-ville.syrjala@linux.intel.com> MIME-Version: 1.0 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dri-devel-bounces+patchwork-dri-devel=patchwork.kernel.org@lists.freedesktop.org Errors-To: dri-devel-bounces+patchwork-dri-devel=patchwork.kernel.org@lists.freedesktop.org From: Ville Syrjälä Add support for the DRM_MODE_ATOMIC_NONBLOCK flag. The drm_flip helper provides the necessary logic to track the progress of the flips. drm_flip is driven by a few extra calls from the interrupt handling and crtc_disable code paths. Since the hardware doesn't provide inter-plane synchronization, some extra software magic is required to avoid flips for multiple planes ending up on the wrong sides of the vblank leading edge. Signed-off-by: Ville Syrjälä --- drivers/gpu/drm/i915/i915_dma.c | 3 + drivers/gpu/drm/i915/i915_drv.h | 4 + drivers/gpu/drm/i915/i915_irq.c | 16 +- drivers/gpu/drm/i915/intel_atomic.c | 622 +++++++++++++++++++++++++++++++++- drivers/gpu/drm/i915/intel_display.c | 2 + drivers/gpu/drm/i915/intel_drv.h | 7 + 6 files changed, 637 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c index acc4317..33999f1 100644 --- a/drivers/gpu/drm/i915/i915_dma.c +++ b/drivers/gpu/drm/i915/i915_dma.c @@ -1794,6 +1794,8 @@ int i915_driver_open(struct drm_device *dev, struct drm_file *file) idr_init(&file_priv->context_idr); + INIT_LIST_HEAD(&file_priv->pending_flips); + return 0; } @@ -1834,6 +1836,7 @@ void i915_driver_preclose(struct drm_device * dev, struct drm_file *file_priv) { i915_gem_context_close(dev, file_priv); i915_gem_release(dev, file_priv); + intel_atomic_free_events(dev, file_priv); } void i915_driver_postclose(struct drm_device *dev, struct drm_file *file) diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 302b6e6..51b36c1 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -882,6 +883,8 @@ typedef struct drm_i915_private { struct work_struct parity_error_work; bool hw_contexts_disabled; uint32_t hw_context_size; + + struct drm_flip_driver flip_driver; } drm_i915_private_t; /* Iterate over initialised rings */ @@ -1100,6 +1103,7 @@ struct drm_i915_file_private { struct list_head request_list; } mm; struct idr context_idr; + struct list_head pending_flips; }; #define INTEL_INFO(dev) (((struct drm_i915_private *) (dev)->dev_private)->info) diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 32e1bda..ee22e97 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -563,8 +563,10 @@ static irqreturn_t valleyview_irq_handler(DRM_IRQ_ARGS) spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); for_each_pipe(pipe) { - if (pipe_stats[pipe] & PIPE_VBLANK_INTERRUPT_STATUS) + if (pipe_stats[pipe] & PIPE_VBLANK_INTERRUPT_STATUS) { drm_handle_vblank(dev, pipe); + intel_atomic_handle_vblank(dev, pipe); + } if (pipe_stats[pipe] & PLANE_FLIPDONE_INT_STATUS_VLV) { intel_prepare_page_flip(dev, pipe); @@ -697,8 +699,10 @@ static irqreturn_t ivybridge_irq_handler(DRM_IRQ_ARGS) intel_opregion_gse_intr(dev); for (i = 0; i < 3; i++) { - if (de_iir & (DE_PIPEA_VBLANK_IVB << (5 * i))) + if (de_iir & (DE_PIPEA_VBLANK_IVB << (5 * i))) { drm_handle_vblank(dev, i); + intel_atomic_handle_vblank(dev, i); + } if (de_iir & (DE_PLANEA_FLIP_DONE_IVB << (5 * i))) { intel_prepare_page_flip(dev, i); intel_finish_page_flip_plane(dev, i); @@ -784,11 +788,15 @@ static irqreturn_t ironlake_irq_handler(DRM_IRQ_ARGS) if (de_iir & DE_GSE) intel_opregion_gse_intr(dev); - if (de_iir & DE_PIPEA_VBLANK) + if (de_iir & DE_PIPEA_VBLANK) { drm_handle_vblank(dev, 0); + intel_atomic_handle_vblank(dev, 0); + } - if (de_iir & DE_PIPEB_VBLANK) + if (de_iir & DE_PIPEB_VBLANK) { drm_handle_vblank(dev, 1); + intel_atomic_handle_vblank(dev, 1); + } if (de_iir & DE_PLANEA_FLIP_DONE) { intel_prepare_page_flip(dev, 0); diff --git a/drivers/gpu/drm/i915/intel_atomic.c b/drivers/gpu/drm/i915/intel_atomic.c index 3adb140..238a843 100644 --- a/drivers/gpu/drm/i915/intel_atomic.c +++ b/drivers/gpu/drm/i915/intel_atomic.c @@ -26,6 +26,7 @@ #include #include +#include #include "intel_drv.h" @@ -47,6 +48,20 @@ static struct drm_property *prop_cursor_y; static struct drm_property *prop_cursor_w; static struct drm_property *prop_cursor_h; +struct intel_flip { + struct drm_flip base; + u32 vbl_count; + bool vblank_ref; + bool has_cursor; + struct drm_crtc *crtc; + struct drm_plane *plane; + struct drm_i915_gem_object *old_bo; + struct drm_i915_gem_object *old_cursor_bo; + struct drm_pending_atomic_event *event; + uint32_t old_fb_id; + struct list_head pending_head; +}; + struct intel_plane_state { struct drm_plane *plane; struct intel_plane_coords coords; @@ -54,6 +69,7 @@ struct intel_plane_state { bool pinned; bool changed; struct drm_pending_atomic_event *event; + struct intel_flip *flip; struct { struct drm_crtc *crtc; @@ -76,6 +92,7 @@ struct intel_crtc_state { unsigned long encoders_bitmask; bool changed; struct drm_pending_atomic_event *event; + struct intel_flip *flip; struct { bool enabled; @@ -966,6 +983,22 @@ static void free_event(struct drm_pending_atomic_event *e) kfree(e); } +void intel_atomic_free_events(struct drm_device *dev, struct drm_file *file) +{ + struct drm_i915_file_private *file_priv = file->driver_priv; + struct intel_flip *intel_flip, *next; + + spin_lock_irq(&dev->event_lock); + + list_for_each_entry_safe(intel_flip, next, &file_priv->pending_flips, pending_head) { + free_event(intel_flip->event); + intel_flip->event = NULL; + list_del_init(&intel_flip->pending_head); + } + + spin_unlock_irq(&dev->event_lock); +} + static void queue_event(struct drm_device *dev, struct drm_crtc *crtc, struct drm_pending_atomic_event *e) { @@ -1533,6 +1566,60 @@ static void update_crtc(struct drm_device *dev, } } +static void atomic_pipe_commit(struct drm_device *dev, + struct intel_atomic_state *state, + int pipe); + +static int apply_nonblocking(struct drm_device *dev, struct intel_atomic_state *s) +{ + struct intel_crtc *intel_crtc; + int i; + + for (i = 0; i < dev->mode_config.num_crtc; i++) { + struct intel_crtc_state *st = &s->crtc[i]; + struct intel_crtc *intel_crtc = to_intel_crtc(st->crtc); + struct drm_i915_gem_object *obj; + + if (!st->old.fb) + continue; + + obj = to_intel_framebuffer(st->old.fb)->obj; + + /* Only one bit per plane in pending_flips */ + if (atomic_read(&obj->pending_flip) & (1 << intel_crtc->plane)) + return -EBUSY; + } + + for (i = 0; i < dev->mode_config.num_plane; i++) { + struct intel_plane_state *st = &s->plane[i]; + struct intel_plane *intel_plane = to_intel_plane(st->plane); + struct drm_i915_gem_object *obj; + + if (!st->old.fb) + continue; + + obj = to_intel_framebuffer(st->old.fb)->obj; + + if (!st->old.fb) + continue; + + obj = to_intel_framebuffer(st->old.fb)->obj; + + /* Only one bit per plane in pending_flips */ + if (atomic_read(&obj->pending_flip) & (1 << (16 + intel_plane->pipe))) + return -EBUSY; + } + + list_for_each_entry(intel_crtc, &dev->mode_config.crtc_list, base.head) + atomic_pipe_commit(dev, s, intel_crtc->pipe); + + /* don't restore the old state in end() */ + s->dirty = false; + s->restore_state = false; + + return 0; +} + static int alloc_flip_data(struct drm_device *dev, struct intel_atomic_state *s) { int i; @@ -1551,6 +1638,13 @@ static int alloc_flip_data(struct drm_device *dev, struct intel_atomic_state *s) st->event = e; } + + if (!st->fb_dirty && !st->mode_dirty && !st->cursor_dirty) + continue; + + st->flip = kzalloc(sizeof *st->flip, GFP_KERNEL); + if (!st->flip) + return -ENOMEM; } @@ -1568,6 +1662,13 @@ static int alloc_flip_data(struct drm_device *dev, struct intel_atomic_state *s) st->event = e; } + + if (!st->dirty) + continue; + + st->flip = kzalloc(sizeof *st->flip, GFP_KERNEL); + if (!st->flip) + return -ENOMEM; } return 0; @@ -1586,6 +1687,9 @@ static void free_flip_data(struct drm_device *dev, struct intel_atomic_state *s) spin_unlock_irq(&dev->event_lock); st->event = NULL; } + + kfree(st->flip); + st->flip = NULL; } for (i = 0; i < dev->mode_config.num_plane; i++) { @@ -1597,6 +1701,9 @@ static void free_flip_data(struct drm_device *dev, struct intel_atomic_state *s) spin_unlock_irq(&dev->event_lock); st->event = NULL; } + + kfree(st->flip); + st->flip = NULL; } } @@ -1605,9 +1712,6 @@ static int intel_atomic_commit(struct drm_device *dev, void *state) struct intel_atomic_state *s = state; int ret; - if (s->flags & DRM_MODE_ATOMIC_NONBLOCK) - return -ENOSYS; - if (!s->dirty) return 0; @@ -1623,17 +1727,27 @@ static int intel_atomic_commit(struct drm_device *dev, void *state) if (ret) return ret; - /* apply in a blocking manner */ - ret = apply_config(dev, s); - if (ret) { - unpin_cursors(dev, s); - unpin_fbs(dev, s); - s->restore_hw = true; - return ret; - } + /* try to apply in a non blocking manner */ + if (s->flags & DRM_MODE_ATOMIC_NONBLOCK) { + ret = apply_nonblocking(dev, s); + if (ret) { + unpin_cursors(dev, s); + unpin_fbs(dev, s); + return ret; + } + } else { + /* apply in a blocking manner */ + ret = apply_config(dev, s); + if (ret) { + unpin_cursors(dev, s); + unpin_fbs(dev, s); + s->restore_hw = true; + return ret; + } - unpin_old_cursors(dev, s); - unpin_old_fbs(dev, s); + unpin_old_cursors(dev, s); + unpin_old_fbs(dev, s); + } /* * Either we took the blocking code path, or perhaps the state of @@ -1703,6 +1817,9 @@ static struct { { &prop_cursor_y, "CURSOR_Y", INT_MIN, INT_MAX }, }; +static void intel_flip_init(struct drm_device *dev); +static void intel_flip_fini(struct drm_device *dev); + int intel_atomic_init(struct drm_device *dev) { struct drm_crtc *crtc; @@ -1776,6 +1893,8 @@ int intel_atomic_init(struct drm_device *dev) dev->driver->atomic_funcs = &intel_atomic_funcs; + intel_flip_init(dev); + return 0; out: @@ -1792,6 +1911,8 @@ void intel_atomic_fini(struct drm_device *dev) { struct drm_crtc *crtc; + intel_flip_fini(dev); + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { drm_property_destroy_blob(dev, crtc->mode_blob); drm_property_destroy_blob(dev, crtc->connector_ids_blob); @@ -1812,3 +1933,478 @@ void intel_atomic_fini(struct drm_device *dev) drm_property_destroy(dev, prop_src_y); drm_property_destroy(dev, prop_src_x); } + +enum { + /* somwehat arbitrary value */ + INTEL_VBL_CNT_TIMEOUT = 5, +}; + +static void intel_flip_complete(struct drm_flip *flip) +{ + struct intel_flip *intel_flip = + container_of(flip, struct intel_flip, base); + struct drm_device *dev = intel_flip->crtc->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_crtc *crtc = intel_flip->crtc; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); + int pipe = intel_crtc->pipe; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + + if (intel_flip->event) { + list_del_init(&intel_flip->pending_head); + intel_flip->event->event.old_fb_id = intel_flip->old_fb_id; + queue_event(dev, crtc, intel_flip->event); + } + + spin_unlock_irqrestore(&dev->event_lock, flags); + + if (intel_flip->vblank_ref) + drm_vblank_put(dev, pipe); + + /* Possibly allow rendering to old_bo again */ + if (intel_flip->old_bo) { + if (intel_flip->plane) { + struct intel_plane *intel_plane = to_intel_plane(intel_flip->plane); + atomic_clear_mask(1 << (16 + intel_plane->pipe), &intel_flip->old_bo->pending_flip.counter); + } else + atomic_clear_mask(1 << intel_crtc->plane, &intel_flip->old_bo->pending_flip.counter); + + if (atomic_read(&intel_flip->old_bo->pending_flip) == 0) + wake_up(&dev_priv->pending_flip_queue); + } +} + +static void intel_flip_finish(struct drm_flip *flip) +{ + struct intel_flip *intel_flip = + container_of(flip, struct intel_flip, base); + struct drm_device *dev = intel_flip->crtc->dev; + + if (intel_flip->old_bo) { + mutex_lock(&dev->struct_mutex); + + intel_unpin_fb_obj(intel_flip->old_bo); + + drm_gem_object_unreference(&intel_flip->old_bo->base); + + mutex_unlock(&dev->struct_mutex); + } + + if (intel_flip->old_cursor_bo) + intel_crtc_cursor_bo_unref(intel_flip->crtc, intel_flip->old_cursor_bo); +} + +static void intel_flip_cleanup(struct drm_flip *flip) +{ + struct intel_flip *intel_flip = + container_of(flip, struct intel_flip, base); + + kfree(intel_flip); +} + +static void intel_flip_driver_flush(struct drm_flip_driver *driver) +{ + struct drm_i915_private *dev_priv = + container_of(driver, struct drm_i915_private, flip_driver); + + /* Flush posted writes */ + I915_READ(PIPEDSL(PIPE_A)); +} + +static bool intel_have_new_frmcount(struct drm_device *dev) +{ + return IS_G4X(dev) || INTEL_INFO(dev)->gen >= 5; +} + +static u32 get_vbl_count(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); + int pipe = intel_crtc->pipe; + + if (intel_have_new_frmcount(dev)) { + return I915_READ(PIPE_FRMCOUNT_GM45(pipe)); + } else { + u32 high, low1, low2, dsl; + unsigned int timeout = 0; + + /* + * FIXME check where the frame counter increments, and if + * it happens in the middle of some line, take appropriate + * measures to get a sensible reading. + */ + + /* All reads must be satisfied during the same frame */ + do { + low1 = I915_READ(PIPEFRAMEPIXEL(pipe)) >> PIPE_FRAME_LOW_SHIFT; + high = I915_READ(PIPEFRAME(pipe)) << 8; + dsl = I915_READ(PIPEDSL(pipe)); + low2 = I915_READ(PIPEFRAMEPIXEL(pipe)) >> PIPE_FRAME_LOW_SHIFT; + } while (low1 != low2 && timeout++ < INTEL_VBL_CNT_TIMEOUT); + + if (timeout >= INTEL_VBL_CNT_TIMEOUT) + dev_warn(dev->dev, + "Timed out while determining VBL count for pipe %d\n", pipe); + + return ((high | low2) + + ((dsl >= crtc->hwmode.crtc_vdisplay) && + (dsl < crtc->hwmode.crtc_vtotal - 1))) & 0xffffff; + } +} + +static unsigned int usecs_to_scanlines(struct drm_crtc *crtc, + unsigned int usecs) +{ + /* paranoia */ + if (!crtc->hwmode.crtc_htotal) + return 1; + + return DIV_ROUND_UP(usecs * crtc->hwmode.clock, + 1000 * crtc->hwmode.crtc_htotal); +} + +static void intel_pipe_vblank_evade(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); + int pipe = intel_crtc->pipe; + /* FIXME needs to be calibrated sensibly */ + u32 min = crtc->hwmode.crtc_vdisplay - usecs_to_scanlines(crtc, 50); + u32 max = crtc->hwmode.crtc_vdisplay - 1; + long timeout = msecs_to_jiffies(3); + u32 val; + + bool vblank_ref = drm_vblank_get(dev, pipe) == 0; + + intel_crtc->vbl_received = false; + + val = I915_READ(PIPEDSL(pipe)); + + while (val >= min && val <= max && timeout > 0) { + local_irq_enable(); + + timeout = wait_event_timeout(intel_crtc->vbl_wait, + intel_crtc->vbl_received, + timeout); + + local_irq_disable(); + + intel_crtc->vbl_received = false; + + val = I915_READ(PIPEDSL(pipe)); + } + + if (vblank_ref) + drm_vblank_put(dev, pipe); + + if (val >= min && val <= max) + dev_warn(dev->dev, + "Page flipping close to vblank start (DSL=%u, VBL=%u)\n", + val, crtc->hwmode.crtc_vdisplay); +} + +static bool vbl_count_after_eq_new(u32 a, u32 b) +{ + return !((a - b) & 0x80000000); +} + +static bool vbl_count_after_eq(u32 a, u32 b) +{ + return !((a - b) & 0x800000); +} + +static bool intel_vbl_check(struct drm_flip *pending_flip, u32 vbl_count) +{ + struct intel_flip *old_intel_flip = + container_of(pending_flip, struct intel_flip, base); + struct drm_device *dev = old_intel_flip->crtc->dev; + + if (intel_have_new_frmcount(dev)) + return vbl_count_after_eq_new(vbl_count, old_intel_flip->vbl_count); + else + return vbl_count_after_eq(vbl_count, old_intel_flip->vbl_count); +} + +static void intel_flip_prepare(struct drm_flip *flip) +{ + struct intel_flip *intel_flip = + container_of(flip, struct intel_flip, base); + + if (intel_flip->plane) { + struct drm_plane *plane = intel_flip->plane; + struct intel_plane *intel_plane = to_intel_plane(plane); + + intel_plane->prepare(plane); + } +} + +static bool intel_flip_flip(struct drm_flip *flip, + struct drm_flip *pending_flip) +{ + struct intel_flip *intel_flip = container_of(flip, struct intel_flip, base); + struct drm_crtc *crtc = intel_flip->crtc; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); + struct drm_device *dev = crtc->dev; + int pipe = intel_crtc->pipe; + u32 vbl_count; + + intel_flip->vblank_ref = drm_vblank_get(dev, pipe) == 0; + + vbl_count = get_vbl_count(crtc); + + /* arm all the double buffer registers */ + if (intel_flip->plane) { + struct drm_plane *plane = intel_flip->plane; + struct intel_plane *intel_plane = to_intel_plane(plane); + + intel_plane->commit(plane); + } else { + struct drm_i915_private *dev_priv = dev->dev_private; + + dev_priv->display.commit_plane(crtc); + } + + if (intel_flip->has_cursor) + intel_crtc_cursor_commit(crtc, + intel_crtc->cursor_handle, + intel_crtc->cursor_width, + intel_crtc->cursor_height, + intel_crtc->cursor_bo, + intel_crtc->cursor_addr); + + /* This flip will happen on the next vblank */ + if (intel_have_new_frmcount(dev)) + intel_flip->vbl_count = vbl_count + 1; + else + intel_flip->vbl_count = (vbl_count + 1) & 0xffffff; + + if (pending_flip) { + struct intel_flip *old_intel_flip = + container_of(pending_flip, struct intel_flip, base); + bool flipped = intel_vbl_check(pending_flip, vbl_count); + + if (!flipped) { + swap(intel_flip->old_fb_id, old_intel_flip->old_fb_id); + swap(intel_flip->old_bo, old_intel_flip->old_bo); + swap(intel_flip->old_cursor_bo, old_intel_flip->old_cursor_bo); + } + + return flipped; + } + + return false; +} + +static bool intel_flip_vblank(struct drm_flip *pending_flip) +{ + struct intel_flip *old_intel_flip = + container_of(pending_flip, struct intel_flip, base); + u32 vbl_count = get_vbl_count(old_intel_flip->crtc); + + return intel_vbl_check(pending_flip, vbl_count); +} + +static const struct drm_flip_helper_funcs intel_flip_funcs = { + .prepare = intel_flip_prepare, + .flip = intel_flip_flip, + .vblank = intel_flip_vblank, + .complete = intel_flip_complete, + .finish = intel_flip_finish, + .cleanup = intel_flip_cleanup, +}; + +static const struct drm_flip_driver_funcs intel_flip_driver_funcs = { + .flush = intel_flip_driver_flush, +}; + +static void intel_flip_init(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_crtc *intel_crtc; + struct intel_plane *intel_plane; + + drm_flip_driver_init(&dev_priv->flip_driver, &intel_flip_driver_funcs); + + list_for_each_entry(intel_crtc, &dev->mode_config.crtc_list, base.head) { + init_waitqueue_head(&intel_crtc->vbl_wait); + + drm_flip_helper_init(&intel_crtc->flip_helper, + &dev_priv->flip_driver, &intel_flip_funcs); + } + + list_for_each_entry(intel_plane, &dev->mode_config.plane_list, base.head) + drm_flip_helper_init(&intel_plane->flip_helper, + &dev_priv->flip_driver, &intel_flip_funcs); +} + +static void intel_flip_fini(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_crtc *intel_crtc; + struct intel_plane *intel_plane; + + list_for_each_entry(intel_plane, &dev->mode_config.plane_list, base.head) + drm_flip_helper_fini(&intel_plane->flip_helper); + + list_for_each_entry(intel_crtc, &dev->mode_config.crtc_list, base.head) + drm_flip_helper_fini(&intel_crtc->flip_helper); + + drm_flip_driver_fini(&dev_priv->flip_driver); +} + +static void atomic_pipe_commit(struct drm_device *dev, + struct intel_atomic_state *state, + int pipe) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_i915_file_private *file_priv = state->file->driver_priv; + LIST_HEAD(flips); + int i; + bool pipe_enabled = to_intel_crtc(intel_get_crtc_for_pipe(dev, pipe))->active; + + for (i = 0; i < dev->mode_config.num_crtc; i++) { + struct intel_crtc_state *st = &state->crtc[i]; + struct drm_crtc *crtc = st->crtc; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); + struct intel_flip *intel_flip; + + if (!st->fb_dirty && !st->cursor_dirty) + continue; + + if (intel_crtc->pipe != pipe) + continue; + + intel_flip = st->flip; + st->flip = NULL; + + drm_flip_init(&intel_flip->base, &intel_crtc->flip_helper); + + if (st->event) { + intel_flip->event = st->event; + st->event = NULL; + /* need to keep track of it in case process exits */ + spin_lock_irq(&dev->event_lock); + list_add_tail(&intel_flip->pending_head, + &file_priv->pending_flips); + spin_unlock_irq(&dev->event_lock); + } + + intel_flip->crtc = crtc; + + /* should already be checked so can't fail */ + /* FIXME refactor the failing parts? */ + dev_priv->display.calc_plane(crtc, crtc->fb, crtc->x, crtc->y); + + if (st->cursor_dirty) { + intel_flip->has_cursor = true; + intel_flip->old_cursor_bo = st->old.cursor_bo; + } + + if (st->old.fb) { + intel_flip->old_fb_id = st->old.fb->base.id; + intel_flip->old_bo = to_intel_framebuffer(st->old.fb)->obj; + + mutex_lock(&dev->struct_mutex); + drm_gem_object_reference(&intel_flip->old_bo->base); + mutex_unlock(&dev->struct_mutex); + + /* + * Block clients from rendering to the new back buffer until + * the flip occurs and the object is no longer visible. + */ + atomic_set_mask(1 << intel_crtc->plane, + &intel_flip->old_bo->pending_flip.counter); + } + + list_add_tail(&intel_flip->base.list, &flips); + } + + for (i = 0; i < dev->mode_config.num_plane; i++) { + struct intel_plane_state *st = &state->plane[i]; + struct drm_plane *plane = st->plane; + struct intel_plane *intel_plane = to_intel_plane(plane); + struct intel_flip *intel_flip; + + if (!st->dirty) + continue; + + if (intel_plane->pipe != pipe) + continue; + + intel_flip = st->flip; + st->flip = NULL; + + drm_flip_init(&intel_flip->base, &intel_plane->flip_helper); + + if (st->event) { + intel_flip->event = st->event; + st->event = NULL; + /* need to keep track of it in case process exits */ + spin_lock_irq(&dev->event_lock); + list_add_tail(&intel_flip->pending_head, + &file_priv->pending_flips); + spin_unlock_irq(&dev->event_lock); + } + + intel_flip->crtc = intel_get_crtc_for_pipe(dev, pipe); + intel_flip->plane = plane; + + intel_plane->calc(plane, plane->fb, &st->coords); + + if (st->old.fb) { + intel_flip->old_fb_id = st->old.fb->base.id; + intel_flip->old_bo = to_intel_framebuffer(st->old.fb)->obj; + + mutex_lock(&dev->struct_mutex); + drm_gem_object_reference(&intel_flip->old_bo->base); + mutex_unlock(&dev->struct_mutex); + + /* + * Block clients from rendering to the new back buffer until + * the flip occurs and the object is no longer visible. + */ + atomic_set_mask(1 << (16 + intel_plane->pipe), + &intel_flip->old_bo->pending_flip.counter); + } + + list_add_tail(&intel_flip->base.list, &flips); + } + + if (list_empty(&flips)) + return; + + if (!pipe_enabled) { + drm_flip_driver_complete_flips(&dev_priv->flip_driver, &flips); + return; + } + + drm_flip_driver_prepare_flips(&dev_priv->flip_driver, &flips); + + local_irq_disable(); + + intel_pipe_vblank_evade(intel_get_crtc_for_pipe(dev, pipe)); + + drm_flip_driver_schedule_flips(&dev_priv->flip_driver, &flips); + + local_irq_enable(); +} + +void intel_atomic_handle_vblank(struct drm_device *dev, int pipe) +{ + struct intel_crtc *intel_crtc = to_intel_crtc(intel_get_crtc_for_pipe(dev, pipe)); + struct intel_plane *intel_plane; + + intel_crtc->vbl_received = true; + wake_up(&intel_crtc->vbl_wait); + + drm_flip_helper_vblank(&intel_crtc->flip_helper); + + list_for_each_entry(intel_plane, &dev->mode_config.plane_list, base.head) { + if (intel_plane->pipe == pipe) + drm_flip_helper_vblank(&intel_plane->flip_helper); + } +} diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index ac46281..756cf94 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -3357,6 +3357,7 @@ static void ironlake_crtc_disable(struct drm_crtc *crtc) encoder->disable(encoder); intel_crtc_wait_for_pending_flips(crtc); + drm_flip_helper_clear(&intel_crtc->flip_helper); drm_vblank_off(dev, pipe); intel_crtc_update_cursor(crtc, false); @@ -3493,6 +3494,7 @@ static void i9xx_crtc_disable(struct drm_crtc *crtc) /* Give the overlay scaler a chance to disable if it's on this pipe */ intel_crtc_wait_for_pending_flips(crtc); + drm_flip_helper_clear(&intel_crtc->flip_helper); drm_vblank_off(dev, pipe); intel_crtc_dpms_overlay(intel_crtc, false); intel_crtc_update_cursor(crtc, false); diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index 616d5d3..4784c44 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -32,6 +32,7 @@ #include #include #include +#include #define _wait_for(COND, MS, W) ({ \ unsigned long timeout__ = jiffies + msecs_to_jiffies(MS); \ @@ -227,6 +228,9 @@ struct intel_crtc { /* We can share PLLs across outputs if the timings match */ struct intel_pch_pll *pch_pll; struct intel_plane_regs primary_regs; + struct drm_flip_helper flip_helper; + wait_queue_head_t vbl_wait; + bool vbl_received; }; struct intel_plane_coords { @@ -257,6 +261,7 @@ struct intel_plane { void (*prepare)(struct drm_plane *plane); void (*commit)(struct drm_plane *plane); struct intel_plane_regs regs; + struct drm_flip_helper flip_helper; }; struct intel_watermark_params { @@ -629,5 +634,7 @@ extern bool intel_crtc_mode_fixup(struct drm_crtc *crtc, extern int intel_atomic_init(struct drm_device *dev); extern void intel_atomic_fini(struct drm_device *dev); +extern void intel_atomic_free_events(struct drm_device *dev, struct drm_file *file); +extern void intel_atomic_handle_vblank(struct drm_device *dev, int pipe); #endif /* __INTEL_DRV_H__ */