2015-04-20 20:55:21 +00:00
|
|
|
/*
|
|
|
|
* Copyright 2007-8 Advanced Micro Devices, Inc.
|
|
|
|
* Copyright 2008 Red Hat Inc.
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
|
|
* to deal in the Software without restriction, including without limitation
|
|
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
|
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
|
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
*
|
|
|
|
* Authors: Dave Airlie
|
|
|
|
* Alex Deucher
|
|
|
|
*/
|
|
|
|
#include <drm/drmP.h>
|
|
|
|
#include <drm/amdgpu_drm.h>
|
|
|
|
#include "amdgpu.h"
|
|
|
|
#include "amdgpu_i2c.h"
|
|
|
|
#include "atom.h"
|
|
|
|
#include "amdgpu_connectors.h"
|
2017-10-26 16:06:23 +00:00
|
|
|
#include "amdgpu_display.h"
|
2015-04-20 20:55:21 +00:00
|
|
|
#include <asm/div64.h>
|
|
|
|
|
|
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include <drm/drm_crtc_helper.h>
|
|
|
|
#include <drm/drm_edid.h>
|
2018-03-30 14:11:38 +00:00
|
|
|
#include <drm/drm_gem_framebuffer_helper.h>
|
2017-12-05 18:24:55 +00:00
|
|
|
#include <drm/drm_fb_helper.h>
|
2015-04-20 20:55:21 +00:00
|
|
|
|
2018-01-18 22:47:29 +00:00
|
|
|
static void amdgpu_display_flip_callback(struct dma_fence *f,
|
|
|
|
struct dma_fence_cb *cb)
|
2015-08-11 15:29:52 +00:00
|
|
|
{
|
2016-02-11 14:48:30 +00:00
|
|
|
struct amdgpu_flip_work *work =
|
|
|
|
container_of(cb, struct amdgpu_flip_work, cb);
|
2015-08-11 15:29:52 +00:00
|
|
|
|
2016-10-25 12:00:45 +00:00
|
|
|
dma_fence_put(f);
|
2016-08-04 03:39:37 +00:00
|
|
|
schedule_work(&work->flip_work.work);
|
2016-02-11 14:48:30 +00:00
|
|
|
}
|
2015-08-11 15:29:52 +00:00
|
|
|
|
2018-01-18 22:47:29 +00:00
|
|
|
static bool amdgpu_display_flip_handle_fence(struct amdgpu_flip_work *work,
|
|
|
|
struct dma_fence **f)
|
2016-02-11 14:48:30 +00:00
|
|
|
{
|
2016-10-25 12:00:45 +00:00
|
|
|
struct dma_fence *fence= *f;
|
2016-02-11 14:48:30 +00:00
|
|
|
|
|
|
|
if (fence == NULL)
|
|
|
|
return false;
|
2015-08-11 15:29:52 +00:00
|
|
|
|
|
|
|
*f = NULL;
|
2016-02-11 14:48:30 +00:00
|
|
|
|
2018-01-18 22:47:29 +00:00
|
|
|
if (!dma_fence_add_callback(fence, &work->cb,
|
|
|
|
amdgpu_display_flip_callback))
|
2016-02-11 14:48:30 +00:00
|
|
|
return true;
|
|
|
|
|
2016-10-25 12:00:45 +00:00
|
|
|
dma_fence_put(fence);
|
2016-02-11 14:48:30 +00:00
|
|
|
return false;
|
2015-08-11 15:29:52 +00:00
|
|
|
}
|
2015-04-20 20:55:21 +00:00
|
|
|
|
2018-01-18 22:47:29 +00:00
|
|
|
static void amdgpu_display_flip_work_func(struct work_struct *__work)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
2016-08-04 03:39:37 +00:00
|
|
|
struct delayed_work *delayed_work =
|
|
|
|
container_of(__work, struct delayed_work, work);
|
2015-04-20 20:55:21 +00:00
|
|
|
struct amdgpu_flip_work *work =
|
2016-08-04 03:39:37 +00:00
|
|
|
container_of(delayed_work, struct amdgpu_flip_work, flip_work);
|
2015-04-20 20:55:21 +00:00
|
|
|
struct amdgpu_device *adev = work->adev;
|
2016-10-21 20:36:12 +00:00
|
|
|
struct amdgpu_crtc *amdgpu_crtc = adev->mode_info.crtcs[work->crtc_id];
|
2015-04-20 20:55:21 +00:00
|
|
|
|
2016-10-21 20:36:12 +00:00
|
|
|
struct drm_crtc *crtc = &amdgpu_crtc->base;
|
2015-04-20 20:55:21 +00:00
|
|
|
unsigned long flags;
|
2016-08-04 03:39:37 +00:00
|
|
|
unsigned i;
|
|
|
|
int vpos, hpos;
|
2015-04-20 20:55:21 +00:00
|
|
|
|
2018-01-18 22:47:29 +00:00
|
|
|
if (amdgpu_display_flip_handle_fence(work, &work->excl))
|
2016-02-11 14:48:30 +00:00
|
|
|
return;
|
|
|
|
|
2015-08-11 15:29:52 +00:00
|
|
|
for (i = 0; i < work->shared_count; ++i)
|
2018-01-18 22:47:29 +00:00
|
|
|
if (amdgpu_display_flip_handle_fence(work, &work->shared[i]))
|
2016-02-11 14:48:30 +00:00
|
|
|
return;
|
2015-04-20 20:55:21 +00:00
|
|
|
|
2016-08-04 03:39:37 +00:00
|
|
|
/* Wait until we're out of the vertical blank period before the one
|
|
|
|
* targeted by the flip
|
drm/amdgpu: Fixup hw vblank counter/ts for new drm_update_vblank_count() (v3)
commit 4dfd6486 "drm: Use vblank timestamps to guesstimate how many
vblanks were missed" introduced in Linux 4.4-rc1 makes the drm core
more fragile to drivers which don't update hw vblank counters and
vblank timestamps in sync with firing of the vblank irq and
essentially at leading edge of vblank.
This exposed a problem with radeon-kms/amdgpu-kms which do not
satisfy above requirements:
The vblank irq fires a few scanlines before start of vblank, but
programmed pageflips complete at start of vblank and
vblank timestamps update at start of vblank, whereas the
hw vblank counter increments only later, at start of vsync.
This leads to problems like off by one errors for vblank counter
updates, vblank counters apparently going backwards or vblank
timestamps apparently having time going backwards. The net result
is stuttering of graphics in games, or little hangs, as well as
total failure of timing sensitive applications.
See bug #93147 for an example of the regression on Linux 4.4-rc:
https://bugs.freedesktop.org/show_bug.cgi?id=93147
This patch tries to align all above events better from the
viewpoint of the drm core / of external callers to fix the problem:
1. The apparent start of vblank is shifted a few scanlines earlier,
so the vblank irq now always happens after start of this extended
vblank interval and thereby drm_update_vblank_count() always samples
the updated vblank count and timestamp of the new vblank interval.
To achieve this, the reporting of scanout positions by
radeon_get_crtc_scanoutpos() now operates as if the vblank starts
radeon_crtc->lb_vblank_lead_lines before the real start of the hw
vblank interval. This means that the vblank timestamps which are based
on these scanout positions will now update at this earlier start of
vblank.
2. The driver->get_vblank_counter() function will bump the returned
vblank count as read from the hw by +1 if the query happens after
the shifted earlier start of the vblank, but before the real hw increment
at start of vsync, so the counter appears to increment at start of vblank
in sync with the timestamp update.
3. Calls from vblank irq-context and regular non-irq calls are now
treated identical, always simulating the shifted vblank start, to
avoid inconsistent results for queries happening from vblank irq vs.
happening from drm_vblank_enable() or vblank_disable_fn().
4. The radeon_flip_work_func will delay mmio programming a pageflip until
the start of the real vblank iff it happens to execute inside the shifted
earlier start of the vblank, so pageflips now also appear to execute at
start of the shifted vblank, in sync with vblank counter and timestamp
updates. This to avoid some races between updates of vblank count and
timestamps that are used for swap scheduling and pageflip execution which
could cause pageflips to execute before the scheduled target vblank.
The lb_vblank_lead_lines "fudge" value is calculated as the size of
the display controllers line buffer in scanlines for the given video
mode: Vblank irq's are triggered by the line buffer logic when the line
buffer refill for a video frame ends, ie. when the line buffer source read
position enters the hw vblank. This means that a vblank irq could fire at
most as many scanlines before the current reported scanout position of the
crtc timing generator as the number of scanlines the line buffer can
maximally hold for a given video mode.
This patch has been successfully tested on a RV730 card with DCE-3 display
engine and on a evergreen card with DCE-4 display engine, in single-display
and dual-display configuration, with different video modes.
A similar patch is needed for amdgpu-kms to fix the same problem.
Limitations:
- Maybe replace the udelay() in the flip_work_func() by a suitable
usleep_range() for a bit better efficiency? Will try that.
- Line buffer sizes in pixels are hard-coded on < DCE-4 to a value
i just guessed to be high enough to work ok, lacking info on the true
sizes atm.
Probably fixes: fdo#93147
Port of Mario's radeon fix to amdgpu.
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(v1) Reviewed-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v2) Refine amdgpu_flip_work_func() for better efficiency.
In amdgpu_flip_work_func, replace the busy waiting udelay(5)
with event lock held by a more performance and energy efficient
usleep_range() until at least predicted true start of hw vblank,
with some slack for scheduler happiness. Release the event lock
during waits to not delay other outputs in doing their stuff, as
the waiting can last up to 200 usecs in some cases.
Also small fix to code comment and formatting in that function.
(v2) Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v3) Fix crash in crtc disabled case
2015-12-03 17:31:56 +00:00
|
|
|
*/
|
2016-10-21 20:36:12 +00:00
|
|
|
if (amdgpu_crtc->enabled &&
|
2018-01-19 20:53:16 +00:00
|
|
|
(amdgpu_display_get_crtc_scanoutpos(adev->ddev, work->crtc_id, 0,
|
|
|
|
&vpos, &hpos, NULL, NULL,
|
|
|
|
&crtc->hwmode)
|
2016-08-04 03:39:37 +00:00
|
|
|
& (DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_IN_VBLANK)) ==
|
|
|
|
(DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_IN_VBLANK) &&
|
|
|
|
(int)(work->target_vblank -
|
2016-10-21 20:36:12 +00:00
|
|
|
amdgpu_get_vblank_counter_kms(adev->ddev, amdgpu_crtc->crtc_id)) > 0) {
|
2016-08-04 03:39:37 +00:00
|
|
|
schedule_delayed_work(&work->flip_work, usecs_to_jiffies(1000));
|
|
|
|
return;
|
2016-07-12 00:17:51 +00:00
|
|
|
}
|
drm/amdgpu: Fixup hw vblank counter/ts for new drm_update_vblank_count() (v3)
commit 4dfd6486 "drm: Use vblank timestamps to guesstimate how many
vblanks were missed" introduced in Linux 4.4-rc1 makes the drm core
more fragile to drivers which don't update hw vblank counters and
vblank timestamps in sync with firing of the vblank irq and
essentially at leading edge of vblank.
This exposed a problem with radeon-kms/amdgpu-kms which do not
satisfy above requirements:
The vblank irq fires a few scanlines before start of vblank, but
programmed pageflips complete at start of vblank and
vblank timestamps update at start of vblank, whereas the
hw vblank counter increments only later, at start of vsync.
This leads to problems like off by one errors for vblank counter
updates, vblank counters apparently going backwards or vblank
timestamps apparently having time going backwards. The net result
is stuttering of graphics in games, or little hangs, as well as
total failure of timing sensitive applications.
See bug #93147 for an example of the regression on Linux 4.4-rc:
https://bugs.freedesktop.org/show_bug.cgi?id=93147
This patch tries to align all above events better from the
viewpoint of the drm core / of external callers to fix the problem:
1. The apparent start of vblank is shifted a few scanlines earlier,
so the vblank irq now always happens after start of this extended
vblank interval and thereby drm_update_vblank_count() always samples
the updated vblank count and timestamp of the new vblank interval.
To achieve this, the reporting of scanout positions by
radeon_get_crtc_scanoutpos() now operates as if the vblank starts
radeon_crtc->lb_vblank_lead_lines before the real start of the hw
vblank interval. This means that the vblank timestamps which are based
on these scanout positions will now update at this earlier start of
vblank.
2. The driver->get_vblank_counter() function will bump the returned
vblank count as read from the hw by +1 if the query happens after
the shifted earlier start of the vblank, but before the real hw increment
at start of vsync, so the counter appears to increment at start of vblank
in sync with the timestamp update.
3. Calls from vblank irq-context and regular non-irq calls are now
treated identical, always simulating the shifted vblank start, to
avoid inconsistent results for queries happening from vblank irq vs.
happening from drm_vblank_enable() or vblank_disable_fn().
4. The radeon_flip_work_func will delay mmio programming a pageflip until
the start of the real vblank iff it happens to execute inside the shifted
earlier start of the vblank, so pageflips now also appear to execute at
start of the shifted vblank, in sync with vblank counter and timestamp
updates. This to avoid some races between updates of vblank count and
timestamps that are used for swap scheduling and pageflip execution which
could cause pageflips to execute before the scheduled target vblank.
The lb_vblank_lead_lines "fudge" value is calculated as the size of
the display controllers line buffer in scanlines for the given video
mode: Vblank irq's are triggered by the line buffer logic when the line
buffer refill for a video frame ends, ie. when the line buffer source read
position enters the hw vblank. This means that a vblank irq could fire at
most as many scanlines before the current reported scanout position of the
crtc timing generator as the number of scanlines the line buffer can
maximally hold for a given video mode.
This patch has been successfully tested on a RV730 card with DCE-3 display
engine and on a evergreen card with DCE-4 display engine, in single-display
and dual-display configuration, with different video modes.
A similar patch is needed for amdgpu-kms to fix the same problem.
Limitations:
- Maybe replace the udelay() in the flip_work_func() by a suitable
usleep_range() for a bit better efficiency? Will try that.
- Line buffer sizes in pixels are hard-coded on < DCE-4 to a value
i just guessed to be high enough to work ok, lacking info on the true
sizes atm.
Probably fixes: fdo#93147
Port of Mario's radeon fix to amdgpu.
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(v1) Reviewed-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v2) Refine amdgpu_flip_work_func() for better efficiency.
In amdgpu_flip_work_func, replace the busy waiting udelay(5)
with event lock held by a more performance and energy efficient
usleep_range() until at least predicted true start of hw vblank,
with some slack for scheduler happiness. Release the event lock
during waits to not delay other outputs in doing their stuff, as
the waiting can last up to 200 usecs in some cases.
Also small fix to code comment and formatting in that function.
(v2) Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v3) Fix crash in crtc disabled case
2015-12-03 17:31:56 +00:00
|
|
|
|
2016-08-04 03:39:37 +00:00
|
|
|
/* We borrow the event spin lock for protecting flip_status */
|
|
|
|
spin_lock_irqsave(&crtc->dev->event_lock, flags);
|
2016-02-19 01:06:39 +00:00
|
|
|
|
2016-03-30 21:34:27 +00:00
|
|
|
/* Do the flip (mmio) */
|
2016-05-05 20:03:57 +00:00
|
|
|
adev->mode_info.funcs->page_flip(adev, work->crtc_id, work->base, work->async);
|
2016-03-30 21:34:27 +00:00
|
|
|
|
|
|
|
/* Set the flip status */
|
2016-10-21 20:36:12 +00:00
|
|
|
amdgpu_crtc->pflip_status = AMDGPU_FLIP_SUBMITTED;
|
2015-04-20 20:55:21 +00:00
|
|
|
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
|
2015-10-20 19:02:03 +00:00
|
|
|
|
2016-03-30 21:34:27 +00:00
|
|
|
|
|
|
|
DRM_DEBUG_DRIVER("crtc:%d[%p], pflip_stat:AMDGPU_FLIP_SUBMITTED, work: %p,\n",
|
2016-10-21 20:36:12 +00:00
|
|
|
amdgpu_crtc->crtc_id, amdgpu_crtc, work);
|
2016-03-30 21:34:27 +00:00
|
|
|
|
2015-04-20 20:55:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle unpin events outside the interrupt handler proper.
|
|
|
|
*/
|
2018-01-18 22:47:29 +00:00
|
|
|
static void amdgpu_display_unpin_work_func(struct work_struct *__work)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
struct amdgpu_flip_work *work =
|
|
|
|
container_of(__work, struct amdgpu_flip_work, unpin_work);
|
|
|
|
int r;
|
|
|
|
|
|
|
|
/* unpin of the old buffer */
|
2017-04-28 08:28:14 +00:00
|
|
|
r = amdgpu_bo_reserve(work->old_abo, true);
|
2015-04-20 20:55:21 +00:00
|
|
|
if (likely(r == 0)) {
|
2016-09-15 13:06:50 +00:00
|
|
|
r = amdgpu_bo_unpin(work->old_abo);
|
2015-04-20 20:55:21 +00:00
|
|
|
if (unlikely(r != 0)) {
|
|
|
|
DRM_ERROR("failed to unpin buffer after flip\n");
|
|
|
|
}
|
2016-09-15 13:06:50 +00:00
|
|
|
amdgpu_bo_unreserve(work->old_abo);
|
2015-04-20 20:55:21 +00:00
|
|
|
} else
|
|
|
|
DRM_ERROR("failed to reserve buffer after flip\n");
|
|
|
|
|
2016-09-15 13:06:50 +00:00
|
|
|
amdgpu_bo_unref(&work->old_abo);
|
2015-08-11 15:29:52 +00:00
|
|
|
kfree(work->shared);
|
2015-04-20 20:55:21 +00:00
|
|
|
kfree(work);
|
|
|
|
}
|
|
|
|
|
2018-01-19 16:22:59 +00:00
|
|
|
int amdgpu_display_crtc_page_flip_target(struct drm_crtc *crtc,
|
|
|
|
struct drm_framebuffer *fb,
|
|
|
|
struct drm_pending_vblank_event *event,
|
|
|
|
uint32_t page_flip_flags, uint32_t target,
|
|
|
|
struct drm_modeset_acquire_ctx *ctx)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct drm_gem_object *obj;
|
|
|
|
struct amdgpu_flip_work *work;
|
2016-09-15 13:06:50 +00:00
|
|
|
struct amdgpu_bo *new_abo;
|
2015-04-20 20:55:21 +00:00
|
|
|
unsigned long flags;
|
|
|
|
u64 tiling_flags;
|
|
|
|
u64 base;
|
2017-09-13 19:17:19 +00:00
|
|
|
int i, r;
|
2015-04-20 20:55:21 +00:00
|
|
|
|
|
|
|
work = kzalloc(sizeof *work, GFP_KERNEL);
|
|
|
|
if (work == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2018-01-18 22:47:29 +00:00
|
|
|
INIT_DELAYED_WORK(&work->flip_work, amdgpu_display_flip_work_func);
|
|
|
|
INIT_WORK(&work->unpin_work, amdgpu_display_unpin_work_func);
|
2015-04-20 20:55:21 +00:00
|
|
|
|
|
|
|
work->event = event;
|
|
|
|
work->adev = adev;
|
|
|
|
work->crtc_id = amdgpu_crtc->crtc_id;
|
2016-05-05 20:03:57 +00:00
|
|
|
work->async = (page_flip_flags & DRM_MODE_PAGE_FLIP_ASYNC) != 0;
|
2015-04-20 20:55:21 +00:00
|
|
|
|
|
|
|
/* schedule unpin of the old buffer */
|
2018-03-30 14:11:38 +00:00
|
|
|
obj = crtc->primary->fb->obj[0];
|
2015-04-20 20:55:21 +00:00
|
|
|
|
|
|
|
/* take a reference to the old object */
|
2016-09-15 13:06:50 +00:00
|
|
|
work->old_abo = gem_to_amdgpu_bo(obj);
|
|
|
|
amdgpu_bo_ref(work->old_abo);
|
2015-04-20 20:55:21 +00:00
|
|
|
|
2018-03-30 14:11:38 +00:00
|
|
|
obj = fb->obj[0];
|
2016-09-15 13:06:50 +00:00
|
|
|
new_abo = gem_to_amdgpu_bo(obj);
|
2015-04-20 20:55:21 +00:00
|
|
|
|
|
|
|
/* pin the new buffer */
|
2016-09-15 13:06:50 +00:00
|
|
|
r = amdgpu_bo_reserve(new_abo, false);
|
2015-04-20 20:55:21 +00:00
|
|
|
if (unlikely(r != 0)) {
|
2016-09-15 13:06:50 +00:00
|
|
|
DRM_ERROR("failed to reserve new abo buffer before flip\n");
|
2015-04-20 20:55:21 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2017-10-26 16:06:23 +00:00
|
|
|
r = amdgpu_bo_pin(new_abo, amdgpu_display_framebuffer_domains(adev), &base);
|
2015-04-20 20:55:21 +00:00
|
|
|
if (unlikely(r != 0)) {
|
2016-09-15 13:06:50 +00:00
|
|
|
DRM_ERROR("failed to pin new abo buffer before flip\n");
|
2016-06-24 08:30:08 +00:00
|
|
|
goto unreserve;
|
2015-04-20 20:55:21 +00:00
|
|
|
}
|
|
|
|
|
2016-09-15 13:06:50 +00:00
|
|
|
r = reservation_object_get_fences_rcu(new_abo->tbo.resv, &work->excl,
|
2015-08-11 15:29:52 +00:00
|
|
|
&work->shared_count,
|
|
|
|
&work->shared);
|
|
|
|
if (unlikely(r != 0)) {
|
|
|
|
DRM_ERROR("failed to get fences for buffer\n");
|
2016-06-24 08:30:08 +00:00
|
|
|
goto unpin;
|
2015-08-11 15:29:52 +00:00
|
|
|
}
|
|
|
|
|
2016-09-15 13:06:50 +00:00
|
|
|
amdgpu_bo_get_tiling_flags(new_abo, &tiling_flags);
|
|
|
|
amdgpu_bo_unreserve(new_abo);
|
2015-04-20 20:55:21 +00:00
|
|
|
|
|
|
|
work->base = base;
|
2018-02-03 05:12:56 +00:00
|
|
|
work->target_vblank = target - (uint32_t)drm_crtc_vblank_count(crtc) +
|
2016-08-04 03:39:37 +00:00
|
|
|
amdgpu_get_vblank_counter_kms(dev, work->crtc_id);
|
2015-04-20 20:55:21 +00:00
|
|
|
|
|
|
|
/* we borrow the event spin lock for protecting flip_wrok */
|
|
|
|
spin_lock_irqsave(&crtc->dev->event_lock, flags);
|
|
|
|
if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_NONE) {
|
|
|
|
DRM_DEBUG_DRIVER("flip queue: crtc already busy\n");
|
|
|
|
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
|
|
|
|
r = -EBUSY;
|
2016-08-04 03:39:37 +00:00
|
|
|
goto pflip_cleanup;
|
2015-04-20 20:55:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
amdgpu_crtc->pflip_status = AMDGPU_FLIP_PENDING;
|
|
|
|
amdgpu_crtc->pflip_works = work;
|
|
|
|
|
2017-09-13 19:17:19 +00:00
|
|
|
|
|
|
|
DRM_DEBUG_DRIVER("crtc:%d[%p], pflip_stat:AMDGPU_FLIP_PENDING, work: %p,\n",
|
|
|
|
amdgpu_crtc->crtc_id, amdgpu_crtc, work);
|
2015-04-20 20:55:21 +00:00
|
|
|
/* update crtc fb */
|
|
|
|
crtc->primary->fb = fb;
|
|
|
|
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
|
2018-01-18 22:47:29 +00:00
|
|
|
amdgpu_display_flip_work_func(&work->flip_work.work);
|
2017-09-13 19:17:19 +00:00
|
|
|
return 0;
|
2015-04-20 20:55:21 +00:00
|
|
|
|
2017-09-13 19:17:19 +00:00
|
|
|
pflip_cleanup:
|
|
|
|
if (unlikely(amdgpu_bo_reserve(new_abo, false) != 0)) {
|
|
|
|
DRM_ERROR("failed to reserve new abo in error path\n");
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
unpin:
|
|
|
|
if (unlikely(amdgpu_bo_unpin(new_abo) != 0)) {
|
|
|
|
DRM_ERROR("failed to unpin new abo in error path\n");
|
|
|
|
}
|
|
|
|
unreserve:
|
|
|
|
amdgpu_bo_unreserve(new_abo);
|
2016-12-05 20:15:33 +00:00
|
|
|
|
2017-09-13 19:17:19 +00:00
|
|
|
cleanup:
|
|
|
|
amdgpu_bo_unref(&work->old_abo);
|
|
|
|
dma_fence_put(work->excl);
|
|
|
|
for (i = 0; i < work->shared_count; ++i)
|
|
|
|
dma_fence_put(work->shared[i]);
|
|
|
|
kfree(work->shared);
|
|
|
|
kfree(work);
|
2017-09-13 14:03:31 +00:00
|
|
|
|
2017-09-13 19:17:19 +00:00
|
|
|
return r;
|
2015-04-20 20:55:21 +00:00
|
|
|
}
|
|
|
|
|
2018-01-19 16:53:31 +00:00
|
|
|
int amdgpu_display_crtc_set_config(struct drm_mode_set *set,
|
|
|
|
struct drm_modeset_acquire_ctx *ctx)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
struct drm_device *dev;
|
|
|
|
struct amdgpu_device *adev;
|
|
|
|
struct drm_crtc *crtc;
|
|
|
|
bool active = false;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!set || !set->crtc)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
dev = set->crtc->dev;
|
|
|
|
|
|
|
|
ret = pm_runtime_get_sync(dev->dev);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2017-03-22 21:50:57 +00:00
|
|
|
ret = drm_crtc_helper_set_config(set, ctx);
|
2015-04-20 20:55:21 +00:00
|
|
|
|
|
|
|
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
|
|
|
|
if (crtc->enabled)
|
|
|
|
active = true;
|
|
|
|
|
|
|
|
pm_runtime_mark_last_busy(dev->dev);
|
|
|
|
|
|
|
|
adev = dev->dev_private;
|
|
|
|
/* if we have active crtcs and we don't have a power ref,
|
|
|
|
take the current one */
|
|
|
|
if (active && !adev->have_disp_power_ref) {
|
|
|
|
adev->have_disp_power_ref = true;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
/* if we have no active crtcs, then drop the power ref
|
|
|
|
we got before */
|
|
|
|
if (!active && adev->have_disp_power_ref) {
|
|
|
|
pm_runtime_put_autosuspend(dev->dev);
|
|
|
|
adev->have_disp_power_ref = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* drop the power reference we got coming in here */
|
|
|
|
pm_runtime_put_autosuspend(dev->dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-08-08 03:30:50 +00:00
|
|
|
static const char *encoder_names[41] = {
|
2015-04-20 20:55:21 +00:00
|
|
|
"NONE",
|
|
|
|
"INTERNAL_LVDS",
|
|
|
|
"INTERNAL_TMDS1",
|
|
|
|
"INTERNAL_TMDS2",
|
|
|
|
"INTERNAL_DAC1",
|
|
|
|
"INTERNAL_DAC2",
|
|
|
|
"INTERNAL_SDVOA",
|
|
|
|
"INTERNAL_SDVOB",
|
|
|
|
"SI170B",
|
|
|
|
"CH7303",
|
|
|
|
"CH7301",
|
|
|
|
"INTERNAL_DVO1",
|
|
|
|
"EXTERNAL_SDVOA",
|
|
|
|
"EXTERNAL_SDVOB",
|
|
|
|
"TITFP513",
|
|
|
|
"INTERNAL_LVTM1",
|
|
|
|
"VT1623",
|
|
|
|
"HDMI_SI1930",
|
|
|
|
"HDMI_INTERNAL",
|
|
|
|
"INTERNAL_KLDSCP_TMDS1",
|
|
|
|
"INTERNAL_KLDSCP_DVO1",
|
|
|
|
"INTERNAL_KLDSCP_DAC1",
|
|
|
|
"INTERNAL_KLDSCP_DAC2",
|
|
|
|
"SI178",
|
|
|
|
"MVPU_FPGA",
|
|
|
|
"INTERNAL_DDI",
|
|
|
|
"VT1625",
|
|
|
|
"HDMI_SI1932",
|
|
|
|
"DP_AN9801",
|
|
|
|
"DP_DP501",
|
|
|
|
"INTERNAL_UNIPHY",
|
|
|
|
"INTERNAL_KLDSCP_LVTMA",
|
|
|
|
"INTERNAL_UNIPHY1",
|
|
|
|
"INTERNAL_UNIPHY2",
|
|
|
|
"NUTMEG",
|
|
|
|
"TRAVIS",
|
|
|
|
"INTERNAL_VCE",
|
|
|
|
"INTERNAL_UNIPHY3",
|
2016-08-08 03:30:50 +00:00
|
|
|
"HDMI_ANX9805",
|
|
|
|
"INTERNAL_AMCLK",
|
|
|
|
"VIRTUAL",
|
2015-04-20 20:55:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const char *hpd_names[6] = {
|
|
|
|
"HPD1",
|
|
|
|
"HPD2",
|
|
|
|
"HPD3",
|
|
|
|
"HPD4",
|
|
|
|
"HPD5",
|
|
|
|
"HPD6",
|
|
|
|
};
|
|
|
|
|
2018-01-19 17:02:45 +00:00
|
|
|
void amdgpu_display_print_display_setup(struct drm_device *dev)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
struct drm_connector *connector;
|
|
|
|
struct amdgpu_connector *amdgpu_connector;
|
|
|
|
struct drm_encoder *encoder;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
|
|
uint32_t devices;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
DRM_INFO("AMDGPU Display Connectors\n");
|
|
|
|
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
|
|
|
amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
DRM_INFO("Connector %d:\n", i);
|
|
|
|
DRM_INFO(" %s\n", connector->name);
|
|
|
|
if (amdgpu_connector->hpd.hpd != AMDGPU_HPD_NONE)
|
|
|
|
DRM_INFO(" %s\n", hpd_names[amdgpu_connector->hpd.hpd]);
|
|
|
|
if (amdgpu_connector->ddc_bus) {
|
|
|
|
DRM_INFO(" DDC: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
|
|
|
|
amdgpu_connector->ddc_bus->rec.mask_clk_reg,
|
|
|
|
amdgpu_connector->ddc_bus->rec.mask_data_reg,
|
|
|
|
amdgpu_connector->ddc_bus->rec.a_clk_reg,
|
|
|
|
amdgpu_connector->ddc_bus->rec.a_data_reg,
|
|
|
|
amdgpu_connector->ddc_bus->rec.en_clk_reg,
|
|
|
|
amdgpu_connector->ddc_bus->rec.en_data_reg,
|
|
|
|
amdgpu_connector->ddc_bus->rec.y_clk_reg,
|
|
|
|
amdgpu_connector->ddc_bus->rec.y_data_reg);
|
|
|
|
if (amdgpu_connector->router.ddc_valid)
|
|
|
|
DRM_INFO(" DDC Router 0x%x/0x%x\n",
|
|
|
|
amdgpu_connector->router.ddc_mux_control_pin,
|
|
|
|
amdgpu_connector->router.ddc_mux_state);
|
|
|
|
if (amdgpu_connector->router.cd_valid)
|
|
|
|
DRM_INFO(" Clock/Data Router 0x%x/0x%x\n",
|
|
|
|
amdgpu_connector->router.cd_mux_control_pin,
|
|
|
|
amdgpu_connector->router.cd_mux_state);
|
|
|
|
} else {
|
|
|
|
if (connector->connector_type == DRM_MODE_CONNECTOR_VGA ||
|
|
|
|
connector->connector_type == DRM_MODE_CONNECTOR_DVII ||
|
|
|
|
connector->connector_type == DRM_MODE_CONNECTOR_DVID ||
|
|
|
|
connector->connector_type == DRM_MODE_CONNECTOR_DVIA ||
|
|
|
|
connector->connector_type == DRM_MODE_CONNECTOR_HDMIA ||
|
|
|
|
connector->connector_type == DRM_MODE_CONNECTOR_HDMIB)
|
|
|
|
DRM_INFO(" DDC: no ddc bus - possible BIOS bug - please report to xorg-driver-ati@lists.x.org\n");
|
|
|
|
}
|
|
|
|
DRM_INFO(" Encoders:\n");
|
|
|
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
devices = amdgpu_encoder->devices & amdgpu_connector->devices;
|
|
|
|
if (devices) {
|
|
|
|
if (devices & ATOM_DEVICE_CRT1_SUPPORT)
|
|
|
|
DRM_INFO(" CRT1: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_CRT2_SUPPORT)
|
|
|
|
DRM_INFO(" CRT2: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_LCD1_SUPPORT)
|
|
|
|
DRM_INFO(" LCD1: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_DFP1_SUPPORT)
|
|
|
|
DRM_INFO(" DFP1: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_DFP2_SUPPORT)
|
|
|
|
DRM_INFO(" DFP2: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_DFP3_SUPPORT)
|
|
|
|
DRM_INFO(" DFP3: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_DFP4_SUPPORT)
|
|
|
|
DRM_INFO(" DFP4: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_DFP5_SUPPORT)
|
|
|
|
DRM_INFO(" DFP5: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_DFP6_SUPPORT)
|
|
|
|
DRM_INFO(" DFP6: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_TV1_SUPPORT)
|
|
|
|
DRM_INFO(" TV1: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
if (devices & ATOM_DEVICE_CV_SUPPORT)
|
|
|
|
DRM_INFO(" CV: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-19 17:10:52 +00:00
|
|
|
* amdgpu_display_ddc_probe
|
2015-04-20 20:55:21 +00:00
|
|
|
*
|
|
|
|
*/
|
2018-01-19 17:10:52 +00:00
|
|
|
bool amdgpu_display_ddc_probe(struct amdgpu_connector *amdgpu_connector,
|
|
|
|
bool use_aux)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
u8 out = 0x0;
|
|
|
|
u8 buf[8];
|
|
|
|
int ret;
|
|
|
|
struct i2c_msg msgs[] = {
|
|
|
|
{
|
|
|
|
.addr = DDC_ADDR,
|
|
|
|
.flags = 0,
|
|
|
|
.len = 1,
|
|
|
|
.buf = &out,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.addr = DDC_ADDR,
|
|
|
|
.flags = I2C_M_RD,
|
|
|
|
.len = 8,
|
|
|
|
.buf = buf,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/* on hw with routers, select right port */
|
|
|
|
if (amdgpu_connector->router.ddc_valid)
|
|
|
|
amdgpu_i2c_router_select_ddc_port(amdgpu_connector);
|
|
|
|
|
|
|
|
if (use_aux) {
|
|
|
|
ret = i2c_transfer(&amdgpu_connector->ddc_bus->aux.ddc, msgs, 2);
|
|
|
|
} else {
|
|
|
|
ret = i2c_transfer(&amdgpu_connector->ddc_bus->adapter, msgs, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret != 2)
|
|
|
|
/* Couldn't find an accessible DDC on this connector */
|
|
|
|
return false;
|
|
|
|
/* Probe also for valid EDID header
|
|
|
|
* EDID header starts with:
|
|
|
|
* 0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00.
|
|
|
|
* Only the first 6 bytes must be valid as
|
|
|
|
* drm_edid_block_valid() can fix the last 2 bytes */
|
|
|
|
if (drm_edid_header_is_valid(buf) < 6) {
|
|
|
|
/* Couldn't find an accessible EDID on this
|
|
|
|
* connector */
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_framebuffer_funcs amdgpu_fb_funcs = {
|
2018-03-30 14:11:38 +00:00
|
|
|
.destroy = drm_gem_fb_destroy,
|
|
|
|
.create_handle = drm_gem_fb_create_handle,
|
2015-04-20 20:55:21 +00:00
|
|
|
};
|
|
|
|
|
2017-10-26 16:06:23 +00:00
|
|
|
uint32_t amdgpu_display_framebuffer_domains(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
uint32_t domain = AMDGPU_GEM_DOMAIN_VRAM;
|
|
|
|
|
2018-01-12 19:56:49 +00:00
|
|
|
#if defined(CONFIG_DRM_AMD_DC)
|
2017-10-26 16:06:23 +00:00
|
|
|
if (adev->asic_type >= CHIP_CARRIZO && adev->asic_type < CHIP_RAVEN &&
|
2018-01-12 19:56:49 +00:00
|
|
|
adev->flags & AMD_IS_APU &&
|
|
|
|
amdgpu_device_asic_has_dc_support(adev->asic_type))
|
2017-10-26 16:06:23 +00:00
|
|
|
domain |= AMDGPU_GEM_DOMAIN_GTT;
|
2018-01-12 19:56:49 +00:00
|
|
|
#endif
|
2017-10-26 16:06:23 +00:00
|
|
|
|
|
|
|
return domain;
|
|
|
|
}
|
|
|
|
|
2018-01-19 17:17:42 +00:00
|
|
|
int amdgpu_display_framebuffer_init(struct drm_device *dev,
|
|
|
|
struct amdgpu_framebuffer *rfb,
|
|
|
|
const struct drm_mode_fb_cmd2 *mode_cmd,
|
|
|
|
struct drm_gem_object *obj)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
int ret;
|
2018-03-30 14:11:38 +00:00
|
|
|
rfb->base.obj[0] = obj;
|
drm: Pass 'dev' to drm_helper_mode_fill_fb_struct()
Pass the drm_device to drm_helper_mode_fill_fb_struct() so that we can
populate fb->dev early. Will make it easier to use the fb before we
register it.
@@
identifier fb, mode_cmd;
@@
void drm_helper_mode_fill_fb_struct(
+ struct drm_device *dev,
struct drm_framebuffer *fb,
const struct drm_mode_fb_cmd2 *mode_cmd
);
@@
identifier fb, mode_cmd;
@@
void drm_helper_mode_fill_fb_struct(
+ struct drm_device *dev,
struct drm_framebuffer *fb,
const struct drm_mode_fb_cmd2 *mode_cmd
)
{ ... }
@@
function func;
identifier dev;
expression E1, E2;
@@
func(struct drm_device *dev, ...)
{
...
drm_helper_mode_fill_fb_struct(
+ dev,
E1, E2);
...
}
@@
expression E1, E2;
@@
drm_helper_mode_fill_fb_struct(
+ dev,
E1, E2);
v2: Rerun spatch due to code changes
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Alex Deucher <alexander.deucher@amd.com>
Link: http://patchwork.freedesktop.org/patch/msgid/1481748539-18283-1-git-send-email-ville.syrjala@linux.intel.com
2016-12-14 20:48:59 +00:00
|
|
|
drm_helper_mode_fill_fb_struct(dev, &rfb->base, mode_cmd);
|
2015-04-20 20:55:21 +00:00
|
|
|
ret = drm_framebuffer_init(dev, &rfb->base, &amdgpu_fb_funcs);
|
|
|
|
if (ret) {
|
2018-03-30 14:11:38 +00:00
|
|
|
rfb->base.obj[0] = NULL;
|
2015-04-20 20:55:21 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-01-12 14:07:38 +00:00
|
|
|
struct drm_framebuffer *
|
2018-01-19 17:24:39 +00:00
|
|
|
amdgpu_display_user_framebuffer_create(struct drm_device *dev,
|
|
|
|
struct drm_file *file_priv,
|
|
|
|
const struct drm_mode_fb_cmd2 *mode_cmd)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
struct drm_gem_object *obj;
|
|
|
|
struct amdgpu_framebuffer *amdgpu_fb;
|
|
|
|
int ret;
|
|
|
|
|
2016-05-09 10:04:54 +00:00
|
|
|
obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[0]);
|
2015-04-20 20:55:21 +00:00
|
|
|
if (obj == NULL) {
|
|
|
|
dev_err(&dev->pdev->dev, "No GEM object associated to handle 0x%08X, "
|
|
|
|
"can't create framebuffer\n", mode_cmd->handles[0]);
|
|
|
|
return ERR_PTR(-ENOENT);
|
|
|
|
}
|
|
|
|
|
2017-03-29 04:02:11 +00:00
|
|
|
/* Handle is imported dma-buf, so cannot be migrated to VRAM for scanout */
|
|
|
|
if (obj->import_attach) {
|
|
|
|
DRM_DEBUG_KMS("Cannot create framebuffer from imported dma_buf\n");
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
}
|
|
|
|
|
2015-04-20 20:55:21 +00:00
|
|
|
amdgpu_fb = kzalloc(sizeof(*amdgpu_fb), GFP_KERNEL);
|
|
|
|
if (amdgpu_fb == NULL) {
|
2017-08-03 11:58:16 +00:00
|
|
|
drm_gem_object_put_unlocked(obj);
|
2015-04-20 20:55:21 +00:00
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
}
|
|
|
|
|
2018-01-19 17:17:42 +00:00
|
|
|
ret = amdgpu_display_framebuffer_init(dev, amdgpu_fb, mode_cmd, obj);
|
2015-04-20 20:55:21 +00:00
|
|
|
if (ret) {
|
|
|
|
kfree(amdgpu_fb);
|
2017-08-03 11:58:16 +00:00
|
|
|
drm_gem_object_put_unlocked(obj);
|
2015-04-20 20:55:21 +00:00
|
|
|
return ERR_PTR(ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
return &amdgpu_fb->base;
|
|
|
|
}
|
|
|
|
|
|
|
|
const struct drm_mode_config_funcs amdgpu_mode_funcs = {
|
2018-01-19 17:24:39 +00:00
|
|
|
.fb_create = amdgpu_display_user_framebuffer_create,
|
2017-12-05 18:24:55 +00:00
|
|
|
.output_poll_changed = drm_fb_helper_output_poll_changed,
|
2015-04-20 20:55:21 +00:00
|
|
|
};
|
|
|
|
|
2016-04-10 14:29:59 +00:00
|
|
|
static const struct drm_prop_enum_list amdgpu_underscan_enum_list[] =
|
2015-04-20 20:55:21 +00:00
|
|
|
{ { UNDERSCAN_OFF, "off" },
|
|
|
|
{ UNDERSCAN_ON, "on" },
|
|
|
|
{ UNDERSCAN_AUTO, "auto" },
|
|
|
|
};
|
|
|
|
|
2016-04-10 14:29:59 +00:00
|
|
|
static const struct drm_prop_enum_list amdgpu_audio_enum_list[] =
|
2015-04-20 20:55:21 +00:00
|
|
|
{ { AMDGPU_AUDIO_DISABLE, "off" },
|
|
|
|
{ AMDGPU_AUDIO_ENABLE, "on" },
|
|
|
|
{ AMDGPU_AUDIO_AUTO, "auto" },
|
|
|
|
};
|
|
|
|
|
|
|
|
/* XXX support different dither options? spatial, temporal, both, etc. */
|
2016-04-10 14:29:59 +00:00
|
|
|
static const struct drm_prop_enum_list amdgpu_dither_enum_list[] =
|
2015-04-20 20:55:21 +00:00
|
|
|
{ { AMDGPU_FMT_DITHER_DISABLE, "off" },
|
|
|
|
{ AMDGPU_FMT_DITHER_ENABLE, "on" },
|
|
|
|
};
|
|
|
|
|
2018-01-19 17:47:40 +00:00
|
|
|
int amdgpu_display_modeset_create_props(struct amdgpu_device *adev)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
int sz;
|
|
|
|
|
2016-12-14 20:52:45 +00:00
|
|
|
adev->mode_info.coherent_mode_property =
|
|
|
|
drm_property_create_range(adev->ddev, 0 , "coherent", 0, 1);
|
|
|
|
if (!adev->mode_info.coherent_mode_property)
|
|
|
|
return -ENOMEM;
|
2015-04-20 20:55:21 +00:00
|
|
|
|
|
|
|
adev->mode_info.load_detect_property =
|
|
|
|
drm_property_create_range(adev->ddev, 0, "load detection", 0, 1);
|
|
|
|
if (!adev->mode_info.load_detect_property)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
drm_mode_create_scaling_mode_property(adev->ddev);
|
|
|
|
|
|
|
|
sz = ARRAY_SIZE(amdgpu_underscan_enum_list);
|
|
|
|
adev->mode_info.underscan_property =
|
|
|
|
drm_property_create_enum(adev->ddev, 0,
|
|
|
|
"underscan",
|
|
|
|
amdgpu_underscan_enum_list, sz);
|
|
|
|
|
|
|
|
adev->mode_info.underscan_hborder_property =
|
|
|
|
drm_property_create_range(adev->ddev, 0,
|
|
|
|
"underscan hborder", 0, 128);
|
|
|
|
if (!adev->mode_info.underscan_hborder_property)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
adev->mode_info.underscan_vborder_property =
|
|
|
|
drm_property_create_range(adev->ddev, 0,
|
|
|
|
"underscan vborder", 0, 128);
|
|
|
|
if (!adev->mode_info.underscan_vborder_property)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
sz = ARRAY_SIZE(amdgpu_audio_enum_list);
|
|
|
|
adev->mode_info.audio_property =
|
|
|
|
drm_property_create_enum(adev->ddev, 0,
|
|
|
|
"audio",
|
|
|
|
amdgpu_audio_enum_list, sz);
|
|
|
|
|
|
|
|
sz = ARRAY_SIZE(amdgpu_dither_enum_list);
|
|
|
|
adev->mode_info.dither_property =
|
|
|
|
drm_property_create_enum(adev->ddev, 0,
|
|
|
|
"dither",
|
|
|
|
amdgpu_dither_enum_list, sz);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-01-19 20:28:27 +00:00
|
|
|
void amdgpu_display_update_priority(struct amdgpu_device *adev)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
/* adjustment options for the display watermarks */
|
|
|
|
if ((amdgpu_disp_priority == 0) || (amdgpu_disp_priority > 2))
|
|
|
|
adev->mode_info.disp_priority = 0;
|
|
|
|
else
|
|
|
|
adev->mode_info.disp_priority = amdgpu_disp_priority;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-01-18 22:47:29 +00:00
|
|
|
static bool amdgpu_display_is_hdtv_mode(const struct drm_display_mode *mode)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
/* try and guess if this is a tv or a monitor */
|
|
|
|
if ((mode->vdisplay == 480 && mode->hdisplay == 720) || /* 480p */
|
|
|
|
(mode->vdisplay == 576) || /* 576p */
|
|
|
|
(mode->vdisplay == 720) || /* 720p */
|
|
|
|
(mode->vdisplay == 1080)) /* 1080p */
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-19 20:36:43 +00:00
|
|
|
bool amdgpu_display_crtc_scaling_mode_fixup(struct drm_crtc *crtc,
|
|
|
|
const struct drm_display_mode *mode,
|
|
|
|
struct drm_display_mode *adjusted_mode)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct drm_encoder *encoder;
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
|
|
struct drm_connector *connector;
|
|
|
|
struct amdgpu_connector *amdgpu_connector;
|
|
|
|
u32 src_v = 1, dst_v = 1;
|
|
|
|
u32 src_h = 1, dst_h = 1;
|
|
|
|
|
|
|
|
amdgpu_crtc->h_border = 0;
|
|
|
|
amdgpu_crtc->v_border = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
|
|
|
if (encoder->crtc != crtc)
|
|
|
|
continue;
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
connector = amdgpu_get_connector_for_encoder(encoder);
|
|
|
|
amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
|
|
|
|
/* set scaling */
|
|
|
|
if (amdgpu_encoder->rmx_type == RMX_OFF)
|
|
|
|
amdgpu_crtc->rmx_type = RMX_OFF;
|
|
|
|
else if (mode->hdisplay < amdgpu_encoder->native_mode.hdisplay ||
|
|
|
|
mode->vdisplay < amdgpu_encoder->native_mode.vdisplay)
|
|
|
|
amdgpu_crtc->rmx_type = amdgpu_encoder->rmx_type;
|
|
|
|
else
|
|
|
|
amdgpu_crtc->rmx_type = RMX_OFF;
|
|
|
|
/* copy native mode */
|
|
|
|
memcpy(&amdgpu_crtc->native_mode,
|
|
|
|
&amdgpu_encoder->native_mode,
|
|
|
|
sizeof(struct drm_display_mode));
|
|
|
|
src_v = crtc->mode.vdisplay;
|
|
|
|
dst_v = amdgpu_crtc->native_mode.vdisplay;
|
|
|
|
src_h = crtc->mode.hdisplay;
|
|
|
|
dst_h = amdgpu_crtc->native_mode.hdisplay;
|
|
|
|
|
|
|
|
/* fix up for overscan on hdmi */
|
|
|
|
if ((!(mode->flags & DRM_MODE_FLAG_INTERLACE)) &&
|
|
|
|
((amdgpu_encoder->underscan_type == UNDERSCAN_ON) ||
|
|
|
|
((amdgpu_encoder->underscan_type == UNDERSCAN_AUTO) &&
|
|
|
|
drm_detect_hdmi_monitor(amdgpu_connector_edid(connector)) &&
|
2018-01-18 22:47:29 +00:00
|
|
|
amdgpu_display_is_hdtv_mode(mode)))) {
|
2015-04-20 20:55:21 +00:00
|
|
|
if (amdgpu_encoder->underscan_hborder != 0)
|
|
|
|
amdgpu_crtc->h_border = amdgpu_encoder->underscan_hborder;
|
|
|
|
else
|
|
|
|
amdgpu_crtc->h_border = (mode->hdisplay >> 5) + 16;
|
|
|
|
if (amdgpu_encoder->underscan_vborder != 0)
|
|
|
|
amdgpu_crtc->v_border = amdgpu_encoder->underscan_vborder;
|
|
|
|
else
|
|
|
|
amdgpu_crtc->v_border = (mode->vdisplay >> 5) + 16;
|
|
|
|
amdgpu_crtc->rmx_type = RMX_FULL;
|
|
|
|
src_v = crtc->mode.vdisplay;
|
|
|
|
dst_v = crtc->mode.vdisplay - (amdgpu_crtc->v_border * 2);
|
|
|
|
src_h = crtc->mode.hdisplay;
|
|
|
|
dst_h = crtc->mode.hdisplay - (amdgpu_crtc->h_border * 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (amdgpu_crtc->rmx_type != RMX_OFF) {
|
|
|
|
fixed20_12 a, b;
|
|
|
|
a.full = dfixed_const(src_v);
|
|
|
|
b.full = dfixed_const(dst_v);
|
|
|
|
amdgpu_crtc->vsc.full = dfixed_div(a, b);
|
|
|
|
a.full = dfixed_const(src_h);
|
|
|
|
b.full = dfixed_const(dst_h);
|
|
|
|
amdgpu_crtc->hsc.full = dfixed_div(a, b);
|
|
|
|
} else {
|
|
|
|
amdgpu_crtc->vsc.full = dfixed_const(1);
|
|
|
|
amdgpu_crtc->hsc.full = dfixed_const(1);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Retrieve current video scanout position of crtc on a given gpu, and
|
|
|
|
* an optional accurate timestamp of when query happened.
|
|
|
|
*
|
|
|
|
* \param dev Device to query.
|
2015-09-24 16:35:31 +00:00
|
|
|
* \param pipe Crtc to query.
|
2015-04-20 20:55:21 +00:00
|
|
|
* \param flags Flags from caller (DRM_CALLED_FROM_VBLIRQ or 0).
|
drm/amdgpu: Fixup hw vblank counter/ts for new drm_update_vblank_count() (v3)
commit 4dfd6486 "drm: Use vblank timestamps to guesstimate how many
vblanks were missed" introduced in Linux 4.4-rc1 makes the drm core
more fragile to drivers which don't update hw vblank counters and
vblank timestamps in sync with firing of the vblank irq and
essentially at leading edge of vblank.
This exposed a problem with radeon-kms/amdgpu-kms which do not
satisfy above requirements:
The vblank irq fires a few scanlines before start of vblank, but
programmed pageflips complete at start of vblank and
vblank timestamps update at start of vblank, whereas the
hw vblank counter increments only later, at start of vsync.
This leads to problems like off by one errors for vblank counter
updates, vblank counters apparently going backwards or vblank
timestamps apparently having time going backwards. The net result
is stuttering of graphics in games, or little hangs, as well as
total failure of timing sensitive applications.
See bug #93147 for an example of the regression on Linux 4.4-rc:
https://bugs.freedesktop.org/show_bug.cgi?id=93147
This patch tries to align all above events better from the
viewpoint of the drm core / of external callers to fix the problem:
1. The apparent start of vblank is shifted a few scanlines earlier,
so the vblank irq now always happens after start of this extended
vblank interval and thereby drm_update_vblank_count() always samples
the updated vblank count and timestamp of the new vblank interval.
To achieve this, the reporting of scanout positions by
radeon_get_crtc_scanoutpos() now operates as if the vblank starts
radeon_crtc->lb_vblank_lead_lines before the real start of the hw
vblank interval. This means that the vblank timestamps which are based
on these scanout positions will now update at this earlier start of
vblank.
2. The driver->get_vblank_counter() function will bump the returned
vblank count as read from the hw by +1 if the query happens after
the shifted earlier start of the vblank, but before the real hw increment
at start of vsync, so the counter appears to increment at start of vblank
in sync with the timestamp update.
3. Calls from vblank irq-context and regular non-irq calls are now
treated identical, always simulating the shifted vblank start, to
avoid inconsistent results for queries happening from vblank irq vs.
happening from drm_vblank_enable() or vblank_disable_fn().
4. The radeon_flip_work_func will delay mmio programming a pageflip until
the start of the real vblank iff it happens to execute inside the shifted
earlier start of the vblank, so pageflips now also appear to execute at
start of the shifted vblank, in sync with vblank counter and timestamp
updates. This to avoid some races between updates of vblank count and
timestamps that are used for swap scheduling and pageflip execution which
could cause pageflips to execute before the scheduled target vblank.
The lb_vblank_lead_lines "fudge" value is calculated as the size of
the display controllers line buffer in scanlines for the given video
mode: Vblank irq's are triggered by the line buffer logic when the line
buffer refill for a video frame ends, ie. when the line buffer source read
position enters the hw vblank. This means that a vblank irq could fire at
most as many scanlines before the current reported scanout position of the
crtc timing generator as the number of scanlines the line buffer can
maximally hold for a given video mode.
This patch has been successfully tested on a RV730 card with DCE-3 display
engine and on a evergreen card with DCE-4 display engine, in single-display
and dual-display configuration, with different video modes.
A similar patch is needed for amdgpu-kms to fix the same problem.
Limitations:
- Maybe replace the udelay() in the flip_work_func() by a suitable
usleep_range() for a bit better efficiency? Will try that.
- Line buffer sizes in pixels are hard-coded on < DCE-4 to a value
i just guessed to be high enough to work ok, lacking info on the true
sizes atm.
Probably fixes: fdo#93147
Port of Mario's radeon fix to amdgpu.
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(v1) Reviewed-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v2) Refine amdgpu_flip_work_func() for better efficiency.
In amdgpu_flip_work_func, replace the busy waiting udelay(5)
with event lock held by a more performance and energy efficient
usleep_range() until at least predicted true start of hw vblank,
with some slack for scheduler happiness. Release the event lock
during waits to not delay other outputs in doing their stuff, as
the waiting can last up to 200 usecs in some cases.
Also small fix to code comment and formatting in that function.
(v2) Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v3) Fix crash in crtc disabled case
2015-12-03 17:31:56 +00:00
|
|
|
* 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.
|
|
|
|
*
|
2015-04-20 20:55:21 +00:00
|
|
|
* \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
|
|
|
|
* scanout position query. Can be NULL to skip timestamp.
|
|
|
|
* \param *etime Target location for timestamp taken immediately after
|
|
|
|
* scanout position query. Can be NULL to skip timestamp.
|
|
|
|
*
|
|
|
|
* Returns vpos as a positive number while in active scanout area.
|
|
|
|
* Returns vpos as a negative number inside vblank, counting the number
|
|
|
|
* of scanlines to go until end of vblank, e.g., -1 means "one scanline
|
|
|
|
* until start of active scanout / end of vblank."
|
|
|
|
*
|
|
|
|
* \return Flags, or'ed together as follows:
|
|
|
|
*
|
|
|
|
* DRM_SCANOUTPOS_VALID = Query successful.
|
|
|
|
* DRM_SCANOUTPOS_INVBL = Inside vblank.
|
|
|
|
* DRM_SCANOUTPOS_ACCURATE = Returned position is accurate. A lack of
|
|
|
|
* this flag means that returned position may be offset by a constant but
|
|
|
|
* unknown small number of scanlines wrt. real scanout position.
|
|
|
|
*
|
|
|
|
*/
|
2018-01-19 20:53:16 +00:00
|
|
|
int amdgpu_display_get_crtc_scanoutpos(struct drm_device *dev,
|
|
|
|
unsigned int pipe, unsigned int flags, int *vpos,
|
|
|
|
int *hpos, ktime_t *stime, ktime_t *etime,
|
|
|
|
const struct drm_display_mode *mode)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
u32 vbl = 0, position = 0;
|
|
|
|
int vbl_start, vbl_end, vtotal, ret = 0;
|
|
|
|
bool in_vbl = true;
|
|
|
|
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
|
|
|
|
/* preempt_disable_rt() should go right here in PREEMPT_RT patchset. */
|
|
|
|
|
|
|
|
/* Get optional system timestamp before query. */
|
|
|
|
if (stime)
|
|
|
|
*stime = ktime_get();
|
|
|
|
|
2015-09-24 16:35:31 +00:00
|
|
|
if (amdgpu_display_page_flip_get_scanoutpos(adev, pipe, &vbl, &position) == 0)
|
2015-04-20 20:55:21 +00:00
|
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
|
|
|
|
|
|
/* Get optional system timestamp after query. */
|
|
|
|
if (etime)
|
|
|
|
*etime = ktime_get();
|
|
|
|
|
|
|
|
/* preempt_enable_rt() should go right here in PREEMPT_RT patchset. */
|
|
|
|
|
|
|
|
/* Decode into vertical and horizontal scanout position. */
|
|
|
|
*vpos = position & 0x1fff;
|
|
|
|
*hpos = (position >> 16) & 0x1fff;
|
|
|
|
|
|
|
|
/* Valid vblank area boundaries from gpu retrieved? */
|
|
|
|
if (vbl > 0) {
|
|
|
|
/* Yes: Decode. */
|
|
|
|
ret |= DRM_SCANOUTPOS_ACCURATE;
|
|
|
|
vbl_start = vbl & 0x1fff;
|
|
|
|
vbl_end = (vbl >> 16) & 0x1fff;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* No: Fake something reasonable which gives at least ok results. */
|
2015-09-14 19:43:44 +00:00
|
|
|
vbl_start = mode->crtc_vdisplay;
|
2015-04-20 20:55:21 +00:00
|
|
|
vbl_end = 0;
|
|
|
|
}
|
|
|
|
|
drm/amdgpu: Fixup hw vblank counter/ts for new drm_update_vblank_count() (v3)
commit 4dfd6486 "drm: Use vblank timestamps to guesstimate how many
vblanks were missed" introduced in Linux 4.4-rc1 makes the drm core
more fragile to drivers which don't update hw vblank counters and
vblank timestamps in sync with firing of the vblank irq and
essentially at leading edge of vblank.
This exposed a problem with radeon-kms/amdgpu-kms which do not
satisfy above requirements:
The vblank irq fires a few scanlines before start of vblank, but
programmed pageflips complete at start of vblank and
vblank timestamps update at start of vblank, whereas the
hw vblank counter increments only later, at start of vsync.
This leads to problems like off by one errors for vblank counter
updates, vblank counters apparently going backwards or vblank
timestamps apparently having time going backwards. The net result
is stuttering of graphics in games, or little hangs, as well as
total failure of timing sensitive applications.
See bug #93147 for an example of the regression on Linux 4.4-rc:
https://bugs.freedesktop.org/show_bug.cgi?id=93147
This patch tries to align all above events better from the
viewpoint of the drm core / of external callers to fix the problem:
1. The apparent start of vblank is shifted a few scanlines earlier,
so the vblank irq now always happens after start of this extended
vblank interval and thereby drm_update_vblank_count() always samples
the updated vblank count and timestamp of the new vblank interval.
To achieve this, the reporting of scanout positions by
radeon_get_crtc_scanoutpos() now operates as if the vblank starts
radeon_crtc->lb_vblank_lead_lines before the real start of the hw
vblank interval. This means that the vblank timestamps which are based
on these scanout positions will now update at this earlier start of
vblank.
2. The driver->get_vblank_counter() function will bump the returned
vblank count as read from the hw by +1 if the query happens after
the shifted earlier start of the vblank, but before the real hw increment
at start of vsync, so the counter appears to increment at start of vblank
in sync with the timestamp update.
3. Calls from vblank irq-context and regular non-irq calls are now
treated identical, always simulating the shifted vblank start, to
avoid inconsistent results for queries happening from vblank irq vs.
happening from drm_vblank_enable() or vblank_disable_fn().
4. The radeon_flip_work_func will delay mmio programming a pageflip until
the start of the real vblank iff it happens to execute inside the shifted
earlier start of the vblank, so pageflips now also appear to execute at
start of the shifted vblank, in sync with vblank counter and timestamp
updates. This to avoid some races between updates of vblank count and
timestamps that are used for swap scheduling and pageflip execution which
could cause pageflips to execute before the scheduled target vblank.
The lb_vblank_lead_lines "fudge" value is calculated as the size of
the display controllers line buffer in scanlines for the given video
mode: Vblank irq's are triggered by the line buffer logic when the line
buffer refill for a video frame ends, ie. when the line buffer source read
position enters the hw vblank. This means that a vblank irq could fire at
most as many scanlines before the current reported scanout position of the
crtc timing generator as the number of scanlines the line buffer can
maximally hold for a given video mode.
This patch has been successfully tested on a RV730 card with DCE-3 display
engine and on a evergreen card with DCE-4 display engine, in single-display
and dual-display configuration, with different video modes.
A similar patch is needed for amdgpu-kms to fix the same problem.
Limitations:
- Maybe replace the udelay() in the flip_work_func() by a suitable
usleep_range() for a bit better efficiency? Will try that.
- Line buffer sizes in pixels are hard-coded on < DCE-4 to a value
i just guessed to be high enough to work ok, lacking info on the true
sizes atm.
Probably fixes: fdo#93147
Port of Mario's radeon fix to amdgpu.
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(v1) Reviewed-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v2) Refine amdgpu_flip_work_func() for better efficiency.
In amdgpu_flip_work_func, replace the busy waiting udelay(5)
with event lock held by a more performance and energy efficient
usleep_range() until at least predicted true start of hw vblank,
with some slack for scheduler happiness. Release the event lock
during waits to not delay other outputs in doing their stuff, as
the waiting can last up to 200 usecs in some cases.
Also small fix to code comment and formatting in that function.
(v2) Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v3) Fix crash in crtc disabled case
2015-12-03 17:31:56 +00:00
|
|
|
/* 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;
|
|
|
|
|
2015-04-20 20:55:21 +00:00
|
|
|
/* Test scanout position against vblank region. */
|
|
|
|
if ((*vpos < vbl_start) && (*vpos >= vbl_end))
|
|
|
|
in_vbl = false;
|
|
|
|
|
drm/amdgpu: Fixup hw vblank counter/ts for new drm_update_vblank_count() (v3)
commit 4dfd6486 "drm: Use vblank timestamps to guesstimate how many
vblanks were missed" introduced in Linux 4.4-rc1 makes the drm core
more fragile to drivers which don't update hw vblank counters and
vblank timestamps in sync with firing of the vblank irq and
essentially at leading edge of vblank.
This exposed a problem with radeon-kms/amdgpu-kms which do not
satisfy above requirements:
The vblank irq fires a few scanlines before start of vblank, but
programmed pageflips complete at start of vblank and
vblank timestamps update at start of vblank, whereas the
hw vblank counter increments only later, at start of vsync.
This leads to problems like off by one errors for vblank counter
updates, vblank counters apparently going backwards or vblank
timestamps apparently having time going backwards. The net result
is stuttering of graphics in games, or little hangs, as well as
total failure of timing sensitive applications.
See bug #93147 for an example of the regression on Linux 4.4-rc:
https://bugs.freedesktop.org/show_bug.cgi?id=93147
This patch tries to align all above events better from the
viewpoint of the drm core / of external callers to fix the problem:
1. The apparent start of vblank is shifted a few scanlines earlier,
so the vblank irq now always happens after start of this extended
vblank interval and thereby drm_update_vblank_count() always samples
the updated vblank count and timestamp of the new vblank interval.
To achieve this, the reporting of scanout positions by
radeon_get_crtc_scanoutpos() now operates as if the vblank starts
radeon_crtc->lb_vblank_lead_lines before the real start of the hw
vblank interval. This means that the vblank timestamps which are based
on these scanout positions will now update at this earlier start of
vblank.
2. The driver->get_vblank_counter() function will bump the returned
vblank count as read from the hw by +1 if the query happens after
the shifted earlier start of the vblank, but before the real hw increment
at start of vsync, so the counter appears to increment at start of vblank
in sync with the timestamp update.
3. Calls from vblank irq-context and regular non-irq calls are now
treated identical, always simulating the shifted vblank start, to
avoid inconsistent results for queries happening from vblank irq vs.
happening from drm_vblank_enable() or vblank_disable_fn().
4. The radeon_flip_work_func will delay mmio programming a pageflip until
the start of the real vblank iff it happens to execute inside the shifted
earlier start of the vblank, so pageflips now also appear to execute at
start of the shifted vblank, in sync with vblank counter and timestamp
updates. This to avoid some races between updates of vblank count and
timestamps that are used for swap scheduling and pageflip execution which
could cause pageflips to execute before the scheduled target vblank.
The lb_vblank_lead_lines "fudge" value is calculated as the size of
the display controllers line buffer in scanlines for the given video
mode: Vblank irq's are triggered by the line buffer logic when the line
buffer refill for a video frame ends, ie. when the line buffer source read
position enters the hw vblank. This means that a vblank irq could fire at
most as many scanlines before the current reported scanout position of the
crtc timing generator as the number of scanlines the line buffer can
maximally hold for a given video mode.
This patch has been successfully tested on a RV730 card with DCE-3 display
engine and on a evergreen card with DCE-4 display engine, in single-display
and dual-display configuration, with different video modes.
A similar patch is needed for amdgpu-kms to fix the same problem.
Limitations:
- Maybe replace the udelay() in the flip_work_func() by a suitable
usleep_range() for a bit better efficiency? Will try that.
- Line buffer sizes in pixels are hard-coded on < DCE-4 to a value
i just guessed to be high enough to work ok, lacking info on the true
sizes atm.
Probably fixes: fdo#93147
Port of Mario's radeon fix to amdgpu.
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(v1) Reviewed-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v2) Refine amdgpu_flip_work_func() for better efficiency.
In amdgpu_flip_work_func, replace the busy waiting udelay(5)
with event lock held by a more performance and energy efficient
usleep_range() until at least predicted true start of hw vblank,
with some slack for scheduler happiness. Release the event lock
during waits to not delay other outputs in doing their stuff, as
the waiting can last up to 200 usecs in some cases.
Also small fix to code comment and formatting in that function.
(v2) Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
(v3) Fix crash in crtc disabled case
2015-12-03 17:31:56 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2015-04-20 20:55:21 +00:00
|
|
|
/* 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
|
|
|
|
* start of scanout.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Inside "upper part" of vblank area? Apply corrective offset if so: */
|
|
|
|
if (in_vbl && (*vpos >= vbl_start)) {
|
2015-09-14 19:43:44 +00:00
|
|
|
vtotal = mode->crtc_vtotal;
|
2015-04-20 20:55:21 +00:00
|
|
|
*vpos = *vpos - vtotal;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Correct for shifted end of vbl at vbl_end. */
|
|
|
|
*vpos = *vpos - vbl_end;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-01-19 21:06:41 +00:00
|
|
|
int amdgpu_display_crtc_idx_to_irq_type(struct amdgpu_device *adev, int crtc)
|
2015-04-20 20:55:21 +00:00
|
|
|
{
|
|
|
|
if (crtc < 0 || crtc >= adev->mode_info.num_crtc)
|
|
|
|
return AMDGPU_CRTC_IRQ_NONE;
|
|
|
|
|
|
|
|
switch (crtc) {
|
|
|
|
case 0:
|
|
|
|
return AMDGPU_CRTC_IRQ_VBLANK1;
|
|
|
|
case 1:
|
|
|
|
return AMDGPU_CRTC_IRQ_VBLANK2;
|
|
|
|
case 2:
|
|
|
|
return AMDGPU_CRTC_IRQ_VBLANK3;
|
|
|
|
case 3:
|
|
|
|
return AMDGPU_CRTC_IRQ_VBLANK4;
|
|
|
|
case 4:
|
|
|
|
return AMDGPU_CRTC_IRQ_VBLANK5;
|
|
|
|
case 5:
|
|
|
|
return AMDGPU_CRTC_IRQ_VBLANK6;
|
|
|
|
default:
|
|
|
|
return AMDGPU_CRTC_IRQ_NONE;
|
|
|
|
}
|
|
|
|
}
|