diff options
Diffstat (limited to 'drivers/gpu/drm/amd/amdgpu/amdgpu_display.c')
| -rw-r--r-- | drivers/gpu/drm/amd/amdgpu/amdgpu_display.c | 108 | 
1 files changed, 79 insertions, 29 deletions
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c index e173a5a02f0d..5580d3420c3a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c @@ -73,6 +73,8 @@ static void amdgpu_flip_work_func(struct work_struct *__work)  	struct drm_crtc *crtc = &amdgpuCrtc->base;  	unsigned long flags;  	unsigned i; +	int vpos, hpos, stat, min_udelay; +	struct drm_vblank_crtc *vblank = &crtc->dev->vblank[work->crtc_id];  	amdgpu_flip_wait_fence(adev, &work->excl);  	for (i = 0; i < work->shared_count; ++i) @@ -81,6 +83,41 @@ static void amdgpu_flip_work_func(struct work_struct *__work)  	/* We borrow the event spin lock for protecting flip_status */  	spin_lock_irqsave(&crtc->dev->event_lock, flags); +	/* If this happens to execute within the "virtually extended" vblank +	 * interval before the start of the real vblank interval then it needs +	 * to delay programming the mmio flip until the real vblank is entered. +	 * This prevents completing a flip too early due to the way we fudge +	 * our vblank counter and vblank timestamps in order to work around the +	 * problem that the hw fires vblank interrupts before actual start of +	 * vblank (when line buffer refilling is done for a frame). It +	 * complements the fudging logic in amdgpu_get_crtc_scanoutpos() for +	 * timestamping and amdgpu_get_vblank_counter_kms() for vblank counts. +	 * +	 * In practice this won't execute very often unless on very fast +	 * machines because the time window for this to happen is very small. +	 */ +	for (;;) { +		/* GET_DISTANCE_TO_VBLANKSTART returns distance to real vblank +		 * start in hpos, and to the "fudged earlier" vblank start in +		 * vpos. +		 */ +		stat = amdgpu_get_crtc_scanoutpos(adev->ddev, work->crtc_id, +						  GET_DISTANCE_TO_VBLANKSTART, +						  &vpos, &hpos, NULL, NULL, +						  &crtc->hwmode); + +		if ((stat & (DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_ACCURATE)) != +		    (DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_ACCURATE) || +		    !(vpos >= 0 && hpos <= 0)) +			break; + +		/* Sleep at least until estimated real start of hw vblank */ +		spin_unlock_irqrestore(&crtc->dev->event_lock, flags); +		min_udelay = (-hpos + 1) * max(vblank->linedur_ns / 1000, 5); +		usleep_range(min_udelay, 2 * min_udelay); +		spin_lock_irqsave(&crtc->dev->event_lock, flags); +	}; +  	/* do the flip (mmio) */  	adev->mode_info.funcs->page_flip(adev, work->crtc_id, work->base);  	/* set the flip status */ @@ -109,7 +146,7 @@ static void amdgpu_unpin_work_func(struct work_struct *__work)  	} else  		DRM_ERROR("failed to reserve buffer after flip\n"); -	drm_gem_object_unreference_unlocked(&work->old_rbo->gem_base); +	amdgpu_bo_unref(&work->old_rbo);  	kfree(work->shared);  	kfree(work);  } @@ -148,8 +185,8 @@ int amdgpu_crtc_page_flip(struct drm_crtc *crtc,  	obj = old_amdgpu_fb->obj;  	/* take a reference to the old object */ -	drm_gem_object_reference(obj);  	work->old_rbo = gem_to_amdgpu_bo(obj); +	amdgpu_bo_ref(work->old_rbo);  	new_amdgpu_fb = to_amdgpu_framebuffer(fb);  	obj = new_amdgpu_fb->obj; @@ -222,7 +259,7 @@ pflip_cleanup:  	amdgpu_bo_unreserve(new_rbo);  cleanup: -	drm_gem_object_unreference_unlocked(&work->old_rbo->gem_base); +	amdgpu_bo_unref(&work->old_rbo);  	fence_put(work->excl);  	for (i = 0; i < work->shared_count; ++i)  		fence_put(work->shared[i]); @@ -712,6 +749,15 @@ bool amdgpu_crtc_scaling_mode_fixup(struct drm_crtc *crtc,   * \param dev Device to query.   * \param pipe Crtc to query.   * \param flags Flags from caller (DRM_CALLED_FROM_VBLIRQ or 0). + *              For driver internal use only also supports these flags: + * + *              USE_REAL_VBLANKSTART to use the real start of vblank instead + *              of a fudged earlier start of vblank. + * + *              GET_DISTANCE_TO_VBLANKSTART to return distance to the + *              fudged earlier start of vblank in *vpos and the distance + *              to true start of vblank in *hpos. + *   * \param *vpos Location where vertical scanout position should be stored.   * \param *hpos Location where horizontal scanout position should go.   * \param *stime Target location for timestamp taken immediately before @@ -776,10 +822,40 @@ int amdgpu_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe,  		vbl_end = 0;  	} +	/* Called from driver internal vblank counter query code? */ +	if (flags & GET_DISTANCE_TO_VBLANKSTART) { +	    /* Caller wants distance from real vbl_start in *hpos */ +	    *hpos = *vpos - vbl_start; +	} + +	/* Fudge vblank to start a few scanlines earlier to handle the +	 * problem that vblank irqs fire a few scanlines before start +	 * of vblank. Some driver internal callers need the true vblank +	 * start to be used and signal this via the USE_REAL_VBLANKSTART flag. +	 * +	 * The cause of the "early" vblank irq is that the irq is triggered +	 * by the line buffer logic when the line buffer read position enters +	 * the vblank, whereas our crtc scanout position naturally lags the +	 * line buffer read position. +	 */ +	if (!(flags & USE_REAL_VBLANKSTART)) +		vbl_start -= adev->mode_info.crtcs[pipe]->lb_vblank_lead_lines; +  	/* Test scanout position against vblank region. */  	if ((*vpos < vbl_start) && (*vpos >= vbl_end))  		in_vbl = false; +	/* In vblank? */ +	if (in_vbl) +	    ret |= DRM_SCANOUTPOS_IN_VBLANK; + +	/* Called from driver internal vblank counter query code? */ +	if (flags & GET_DISTANCE_TO_VBLANKSTART) { +		/* Caller wants distance from fudged earlier vbl_start */ +		*vpos -= vbl_start; +		return ret; +	} +  	/* Check if inside vblank area and apply corrective offsets:  	 * vpos will then be >=0 in video scanout area, but negative  	 * within vblank area, counting down the number of lines until @@ -795,32 +871,6 @@ int amdgpu_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe,  	/* Correct for shifted end of vbl at vbl_end. */  	*vpos = *vpos - vbl_end; -	/* In vblank? */ -	if (in_vbl) -		ret |= DRM_SCANOUTPOS_IN_VBLANK; - -	/* Is vpos outside nominal vblank area, but less than -	 * 1/100 of a frame height away from start of vblank? -	 * If so, assume this isn't a massively delayed vblank -	 * interrupt, but a vblank interrupt that fired a few -	 * microseconds before true start of vblank. Compensate -	 * by adding a full frame duration to the final timestamp. -	 * Happens, e.g., on ATI R500, R600. -	 * -	 * We only do this if DRM_CALLED_FROM_VBLIRQ. -	 */ -	if ((flags & DRM_CALLED_FROM_VBLIRQ) && !in_vbl) { -		vbl_start = mode->crtc_vdisplay; -		vtotal = mode->crtc_vtotal; - -		if (vbl_start - *vpos < vtotal / 100) { -			*vpos -= vtotal; - -			/* Signal this correction as "applied". */ -			ret |= 0x8; -		} -	} -  	return ret;  }  | 
