Merge branch 'master' into for-2.6.35
[safe/jmp/linux-2.6] / drivers / gpu / drm / i915 / intel_overlay.c
index 3f6f3a3..6d524a1 100644 (file)
@@ -172,7 +172,7 @@ struct overlay_registers {
 #define OFC_UPDATE             0x1
 
 #define OVERLAY_NONPHYSICAL(dev) (IS_G33(dev) || IS_I965G(dev))
-#define OVERLAY_EXISTS(dev) (!IS_G4X(dev) && !IS_IGDNG(dev))
+#define OVERLAY_EXISTS(dev) (!IS_G4X(dev) && !IS_IRONLAKE(dev) && !IS_GEN6(dev))
 
 
 static struct overlay_registers *intel_overlay_map_regs_atomic(struct intel_overlay *overlay)
@@ -199,16 +199,11 @@ static struct overlay_registers *intel_overlay_map_regs_atomic(struct intel_over
 
 static void intel_overlay_unmap_regs_atomic(struct intel_overlay *overlay)
 {
-       struct drm_device *dev = overlay->dev;
-        drm_i915_private_t *dev_priv = dev->dev_private;
-
        if (OVERLAY_NONPHYSICAL(overlay->dev))
                io_mapping_unmap_atomic(overlay->virt_addr);
 
        overlay->virt_addr = NULL;
 
-       I915_READ(OVADD); /* flush wc cashes */
-
        return;
 }
 
@@ -222,24 +217,26 @@ static int intel_overlay_on(struct intel_overlay *overlay)
 
        BUG_ON(overlay->active);
 
-       BEGIN_LP_RING(6);
-       OUT_RING(MI_FLUSH);
-       OUT_RING(MI_NOOP);
+       overlay->active = 1;
+       overlay->hw_wedged = NEEDS_WAIT_FOR_FLIP;
+
+       BEGIN_LP_RING(4);
        OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_ON);
        OUT_RING(overlay->flip_addr | OFC_UPDATE);
        OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP);
        OUT_RING(MI_NOOP);
        ADVANCE_LP_RING();
 
-       ret = i915_lp_ring_sync(dev);
-       if (ret != 0) {
-               DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n");
-               overlay->hw_wedged = 1;
-               return 0;
-       }
+       overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+       if (overlay->last_flip_req == 0)
+               return -ENOMEM;
 
-       overlay->active = 1;
+       ret = i915_do_wait_request(dev, overlay->last_flip_req, 1);
+       if (ret != 0)
+               return ret;
 
+       overlay->hw_wedged = 0;
+       overlay->last_flip_req = 0;
        return 0;
 }
 
@@ -251,7 +248,6 @@ static void intel_overlay_continue(struct intel_overlay *overlay,
         drm_i915_private_t *dev_priv = dev->dev_private;
        u32 flip_addr = overlay->flip_addr;
        u32 tmp;
-       int ret;
        RING_LOCALS;
 
        BUG_ON(!overlay->active);
@@ -264,27 +260,52 @@ static void intel_overlay_continue(struct intel_overlay *overlay,
        if (tmp & (1 << 17))
                DRM_DEBUG("overlay underrun, DOVSTA: %x\n", tmp);
 
-       BEGIN_LP_RING(6);
-       OUT_RING(MI_FLUSH);
-       OUT_RING(MI_NOOP);
+       BEGIN_LP_RING(2);
        OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_CONTINUE);
        OUT_RING(flip_addr);
-        OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP);
-        OUT_RING(MI_NOOP);
         ADVANCE_LP_RING();
 
-       /* run in lockstep with the hw for easier testing */
-       ret = i915_lp_ring_sync(dev);
-       if (ret != 0) {
-               DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n");
-               overlay->hw_wedged = 1;
-       }
+       overlay->last_flip_req = i915_add_request(dev, NULL, 0);
 }
 
 static int intel_overlay_wait_flip(struct intel_overlay *overlay)
 {
-       /* don't overcomplicate things for now with asynchronous operations
-        * see comment above */
+       struct drm_device *dev = overlay->dev;
+        drm_i915_private_t *dev_priv = dev->dev_private;
+       int ret;
+       u32 tmp;
+       RING_LOCALS;
+
+       if (overlay->last_flip_req != 0) {
+               ret = i915_do_wait_request(dev, overlay->last_flip_req, 1);
+               if (ret == 0) {
+                       overlay->last_flip_req = 0;
+
+                       tmp = I915_READ(ISR);
+
+                       if (!(tmp & I915_OVERLAY_PLANE_FLIP_PENDING_INTERRUPT))
+                               return 0;
+               }
+       }
+
+       /* synchronous slowpath */
+       overlay->hw_wedged = RELEASE_OLD_VID;
+
+       BEGIN_LP_RING(2);
+        OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP);
+        OUT_RING(MI_NOOP);
+        ADVANCE_LP_RING();
+
+       overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+       if (overlay->last_flip_req == 0)
+               return -ENOMEM;
+
+       ret = i915_do_wait_request(dev, overlay->last_flip_req, 1);
+       if (ret != 0)
+               return ret;
+
+       overlay->hw_wedged = 0;
+       overlay->last_flip_req = 0;
        return 0;
 }
 
@@ -306,58 +327,146 @@ static int intel_overlay_off(struct intel_overlay *overlay)
        flip_addr |= OFC_UPDATE;
 
        /* wait for overlay to go idle */
-       BEGIN_LP_RING(6);
-       OUT_RING(MI_FLUSH);
-       OUT_RING(MI_NOOP);
+       overlay->hw_wedged = SWITCH_OFF_STAGE_1;
+
+       BEGIN_LP_RING(4);
        OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_CONTINUE);
        OUT_RING(flip_addr);
         OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP);
         OUT_RING(MI_NOOP);
         ADVANCE_LP_RING();
 
-       ret = i915_lp_ring_sync(dev);
-       if (ret != 0) {
-               DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n");
-               overlay->hw_wedged = 1;
+       overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+       if (overlay->last_flip_req == 0)
+               return -ENOMEM;
+
+       ret = i915_do_wait_request(dev, overlay->last_flip_req, 1);
+       if (ret != 0)
                return ret;
-       }
 
        /* turn overlay off */
-       /* this is not done in userspace!
-       BEGIN_LP_RING(6);
-        OUT_RING(MI_FLUSH);
-        OUT_RING(MI_NOOP);
+       overlay->hw_wedged = SWITCH_OFF_STAGE_2;
+
+       BEGIN_LP_RING(4);
         OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_OFF);
        OUT_RING(flip_addr);
         OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP);
         OUT_RING(MI_NOOP);
        ADVANCE_LP_RING();
 
-       ret = i915_lp_ring_sync(dev);
-       if (ret != 0) {
-               DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n");
-               overlay->hw_wedged = 1;
+       overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+       if (overlay->last_flip_req == 0)
+               return -ENOMEM;
+
+       ret = i915_do_wait_request(dev, overlay->last_flip_req, 1);
+       if (ret != 0)
                return ret;
-       }*/
 
+       overlay->hw_wedged = 0;
+       overlay->last_flip_req = 0;
+       return ret;
+}
+
+static void intel_overlay_off_tail(struct intel_overlay *overlay)
+{
+       struct drm_gem_object *obj;
+
+       /* never have the overlay hw on without showing a frame */
+       BUG_ON(!overlay->vid_bo);
+       obj = overlay->vid_bo->obj;
+
+       i915_gem_object_unpin(obj);
+       drm_gem_object_unreference(obj);
+       overlay->vid_bo = NULL;
+
+       overlay->crtc->overlay = NULL;
+       overlay->crtc = NULL;
        overlay->active = 0;
+}
 
-       return ret;
+/* recover from an interruption due to a signal
+ * We have to be careful not to repeat work forever an make forward progess. */
+int intel_overlay_recover_from_interrupt(struct intel_overlay *overlay,
+                                        int interruptible)
+{
+       struct drm_device *dev = overlay->dev;
+        drm_i915_private_t *dev_priv = dev->dev_private;
+       struct drm_gem_object *obj;
+       u32 flip_addr;
+       int ret;
+       RING_LOCALS;
+
+       if (overlay->hw_wedged == HW_WEDGED)
+               return -EIO;
+
+       if (overlay->last_flip_req == 0) {
+               overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+               if (overlay->last_flip_req == 0)
+                       return -ENOMEM;
+       }
+
+       ret = i915_do_wait_request(dev, overlay->last_flip_req, interruptible);
+       if (ret != 0)
+               return ret;
+
+       switch (overlay->hw_wedged) {
+               case RELEASE_OLD_VID:
+                       obj = overlay->old_vid_bo->obj;
+                       i915_gem_object_unpin(obj);
+                       drm_gem_object_unreference(obj);
+                       overlay->old_vid_bo = NULL;
+                       break;
+               case SWITCH_OFF_STAGE_1:
+                       flip_addr = overlay->flip_addr;
+                       flip_addr |= OFC_UPDATE;
+
+                       overlay->hw_wedged = SWITCH_OFF_STAGE_2;
+
+                       BEGIN_LP_RING(4);
+                       OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_OFF);
+                       OUT_RING(flip_addr);
+                       OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP);
+                       OUT_RING(MI_NOOP);
+                       ADVANCE_LP_RING();
+
+                       overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+                       if (overlay->last_flip_req == 0)
+                               return -ENOMEM;
+
+                       ret = i915_do_wait_request(dev, overlay->last_flip_req,
+                                       interruptible);
+                       if (ret != 0)
+                               return ret;
+
+               case SWITCH_OFF_STAGE_2:
+                       intel_overlay_off_tail(overlay);
+                       break;
+               default:
+                       BUG_ON(overlay->hw_wedged != NEEDS_WAIT_FOR_FLIP);
+       }
+
+       overlay->hw_wedged = 0;
+       overlay->last_flip_req = 0;
+       return 0;
 }
 
-/* wait for pending overlay flip and release old frame */
+/* Wait for pending overlay flip and release old frame.
+ * Needs to be called before the overlay register are changed
+ * via intel_overlay_(un)map_regs_atomic */
 static int intel_overlay_release_old_vid(struct intel_overlay *overlay)
 {
        int ret;
        struct drm_gem_object *obj;
 
+       /* only wait if there is actually an old frame to release to
+        * guarantee forward progress */
+       if (!overlay->old_vid_bo)
+               return 0;
+
        ret = intel_overlay_wait_flip(overlay);
        if (ret != 0)
                return ret;
 
-       if (!overlay->old_vid_bo)
-               return 0;
-
        obj = overlay->old_vid_bo->obj;
        i915_gem_object_unpin(obj);
        drm_gem_object_unreference(obj);
@@ -615,16 +724,13 @@ int intel_overlay_do_put_image(struct intel_overlay *overlay,
        int ret, tmp_width;
        struct overlay_registers *regs;
        bool scale_changed = false;
-       struct drm_i915_gem_object *bo_priv = new_bo->driver_private;
+       struct drm_i915_gem_object *bo_priv = to_intel_bo(new_bo);
        struct drm_device *dev = overlay->dev;
 
        BUG_ON(!mutex_is_locked(&dev->struct_mutex));
        BUG_ON(!mutex_is_locked(&dev->mode_config.mutex));
        BUG_ON(!overlay);
 
-       if (overlay->hw_wedged)
-               return -EBUSY;
-
        ret = intel_overlay_release_old_vid(overlay);
        if (ret != 0)
                return ret;
@@ -703,7 +809,7 @@ int intel_overlay_do_put_image(struct intel_overlay *overlay,
        intel_overlay_continue(overlay, scale_changed);
 
        overlay->old_vid_bo = overlay->vid_bo;
-       overlay->vid_bo = new_bo->driver_private;
+       overlay->vid_bo = to_intel_bo(new_bo);
 
        return 0;
 
@@ -716,18 +822,20 @@ int intel_overlay_switch_off(struct intel_overlay *overlay)
 {
        int ret;
        struct overlay_registers *regs;
-       struct drm_gem_object *obj;
        struct drm_device *dev = overlay->dev;
 
        BUG_ON(!mutex_is_locked(&dev->struct_mutex));
        BUG_ON(!mutex_is_locked(&dev->mode_config.mutex));
 
+       if (overlay->hw_wedged) {
+               ret = intel_overlay_recover_from_interrupt(overlay, 1);
+               if (ret != 0)
+                       return ret;
+       }
+
        if (!overlay->active)
                return 0;
 
-       if (overlay->hw_wedged)
-               return -EBUSY;
-
        ret = intel_overlay_release_old_vid(overlay);
        if (ret != 0)
                return ret;
@@ -737,16 +845,10 @@ int intel_overlay_switch_off(struct intel_overlay *overlay)
        intel_overlay_unmap_regs_atomic(overlay);
 
        ret = intel_overlay_off(overlay);
-       /* never have the overlay hw on without showing a frame */
-       BUG_ON(!overlay->vid_bo);
-       obj = overlay->vid_bo->obj;
-
-       i915_gem_object_unpin(obj);
-       drm_gem_object_unreference(obj);
-       overlay->vid_bo = NULL;
+       if (ret != 0)
+               return ret;
 
-       overlay->crtc->overlay = NULL;
-       overlay->crtc = NULL;
+       intel_overlay_off_tail(overlay);
 
        return 0;
 }
@@ -966,18 +1068,28 @@ int intel_overlay_put_image(struct drm_device *dev, void *data,
 
        drmmode_obj = drm_mode_object_find(dev, put_image_rec->crtc_id,
                         DRM_MODE_OBJECT_CRTC);
-       if (!drmmode_obj)
-               return -ENOENT;
+       if (!drmmode_obj) {
+               ret = -ENOENT;
+               goto out_free;
+       }
        crtc = to_intel_crtc(obj_to_crtc(drmmode_obj));
 
        new_bo = drm_gem_object_lookup(dev, file_priv,
                        put_image_rec->bo_handle);
-       if (!new_bo)
-               return -ENOENT;
+       if (!new_bo) {
+               ret = -ENOENT;
+               goto out_free;
+       }
 
        mutex_lock(&dev->mode_config.mutex);
        mutex_lock(&dev->struct_mutex);
 
+       if (overlay->hw_wedged) {
+               ret = intel_overlay_recover_from_interrupt(overlay, 1);
+               if (ret != 0)
+                       goto out_unlock;
+       }
+
        if (overlay->crtc != crtc) {
                struct drm_display_mode *mode = &crtc->base.mode;
                ret = intel_overlay_switch_off(overlay);
@@ -1056,7 +1168,8 @@ int intel_overlay_put_image(struct drm_device *dev, void *data,
 out_unlock:
        mutex_unlock(&dev->struct_mutex);
        mutex_unlock(&dev->mode_config.mutex);
-       drm_gem_object_unreference(new_bo);
+       drm_gem_object_unreference_unlocked(new_bo);
+out_free:
        kfree(params);
 
        return ret;
@@ -1231,7 +1344,7 @@ void intel_setup_overlay(struct drm_device *dev)
        reg_bo = drm_gem_object_alloc(dev, PAGE_SIZE);
        if (!reg_bo)
                goto out_free;
-       overlay->reg_bo = reg_bo->driver_private;
+       overlay->reg_bo = to_intel_bo(reg_bo);
 
        if (OVERLAY_NONPHYSICAL(dev)) {
                ret = i915_gem_object_pin(reg_bo, PAGE_SIZE);