forked from Minki/linux
3514521ccb
A recent attempt to fix a ref count leak in
amdgpu_display_crtc_set_config() turned out to be doing too much and
"fixed" an intended decrease as if it were a leak. Undo that part to
restore the proper balance. This is the very nature of this function
to increase or decrease the power reference count depending on the
situation.
Consequences of this bug is that the power reference would
eventually get down to 0 while the display was still in use,
resulting in that display switching off unexpectedly.
Signed-off-by: Jean Delvare <jdelvare@suse.de>
Fixes: e008fa6fb4
("drm/amdgpu: fix ref count leak in amdgpu_display_crtc_set_config")
Cc: stable@vger.kernel.org
Cc: Navid Emamdoost <navid.emamdoost@gmail.com>
Cc: Alex Deucher <alexander.deucher@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
941 lines
28 KiB
C
941 lines
28 KiB
C
/*
|
|
* 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/amdgpu_drm.h>
|
|
#include "amdgpu.h"
|
|
#include "amdgpu_i2c.h"
|
|
#include "atom.h"
|
|
#include "amdgpu_connectors.h"
|
|
#include "amdgpu_display.h"
|
|
#include <asm/div64.h>
|
|
|
|
#include <linux/pci.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_gem_framebuffer_helper.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <drm/drm_vblank.h>
|
|
|
|
static void amdgpu_display_flip_callback(struct dma_fence *f,
|
|
struct dma_fence_cb *cb)
|
|
{
|
|
struct amdgpu_flip_work *work =
|
|
container_of(cb, struct amdgpu_flip_work, cb);
|
|
|
|
dma_fence_put(f);
|
|
schedule_work(&work->flip_work.work);
|
|
}
|
|
|
|
static bool amdgpu_display_flip_handle_fence(struct amdgpu_flip_work *work,
|
|
struct dma_fence **f)
|
|
{
|
|
struct dma_fence *fence= *f;
|
|
|
|
if (fence == NULL)
|
|
return false;
|
|
|
|
*f = NULL;
|
|
|
|
if (!dma_fence_add_callback(fence, &work->cb,
|
|
amdgpu_display_flip_callback))
|
|
return true;
|
|
|
|
dma_fence_put(fence);
|
|
return false;
|
|
}
|
|
|
|
static void amdgpu_display_flip_work_func(struct work_struct *__work)
|
|
{
|
|
struct delayed_work *delayed_work =
|
|
container_of(__work, struct delayed_work, work);
|
|
struct amdgpu_flip_work *work =
|
|
container_of(delayed_work, struct amdgpu_flip_work, flip_work);
|
|
struct amdgpu_device *adev = work->adev;
|
|
struct amdgpu_crtc *amdgpu_crtc = adev->mode_info.crtcs[work->crtc_id];
|
|
|
|
struct drm_crtc *crtc = &amdgpu_crtc->base;
|
|
unsigned long flags;
|
|
unsigned i;
|
|
int vpos, hpos;
|
|
|
|
if (amdgpu_display_flip_handle_fence(work, &work->excl))
|
|
return;
|
|
|
|
for (i = 0; i < work->shared_count; ++i)
|
|
if (amdgpu_display_flip_handle_fence(work, &work->shared[i]))
|
|
return;
|
|
|
|
/* Wait until we're out of the vertical blank period before the one
|
|
* targeted by the flip
|
|
*/
|
|
if (amdgpu_crtc->enabled &&
|
|
(amdgpu_display_get_crtc_scanoutpos(adev_to_drm(adev), work->crtc_id, 0,
|
|
&vpos, &hpos, NULL, NULL,
|
|
&crtc->hwmode)
|
|
& (DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_IN_VBLANK)) ==
|
|
(DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_IN_VBLANK) &&
|
|
(int)(work->target_vblank -
|
|
amdgpu_get_vblank_counter_kms(crtc)) > 0) {
|
|
schedule_delayed_work(&work->flip_work, usecs_to_jiffies(1000));
|
|
return;
|
|
}
|
|
|
|
/* We borrow the event spin lock for protecting flip_status */
|
|
spin_lock_irqsave(&crtc->dev->event_lock, flags);
|
|
|
|
/* Do the flip (mmio) */
|
|
adev->mode_info.funcs->page_flip(adev, work->crtc_id, work->base, work->async);
|
|
|
|
/* Set the flip status */
|
|
amdgpu_crtc->pflip_status = AMDGPU_FLIP_SUBMITTED;
|
|
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
|
|
|
|
|
|
DRM_DEBUG_DRIVER("crtc:%d[%p], pflip_stat:AMDGPU_FLIP_SUBMITTED, work: %p,\n",
|
|
amdgpu_crtc->crtc_id, amdgpu_crtc, work);
|
|
|
|
}
|
|
|
|
/*
|
|
* Handle unpin events outside the interrupt handler proper.
|
|
*/
|
|
static void amdgpu_display_unpin_work_func(struct work_struct *__work)
|
|
{
|
|
struct amdgpu_flip_work *work =
|
|
container_of(__work, struct amdgpu_flip_work, unpin_work);
|
|
int r;
|
|
|
|
/* unpin of the old buffer */
|
|
r = amdgpu_bo_reserve(work->old_abo, true);
|
|
if (likely(r == 0)) {
|
|
r = amdgpu_bo_unpin(work->old_abo);
|
|
if (unlikely(r != 0)) {
|
|
DRM_ERROR("failed to unpin buffer after flip\n");
|
|
}
|
|
amdgpu_bo_unreserve(work->old_abo);
|
|
} else
|
|
DRM_ERROR("failed to reserve buffer after flip\n");
|
|
|
|
amdgpu_bo_unref(&work->old_abo);
|
|
kfree(work->shared);
|
|
kfree(work);
|
|
}
|
|
|
|
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)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
struct amdgpu_device *adev = drm_to_adev(dev);
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
struct drm_gem_object *obj;
|
|
struct amdgpu_flip_work *work;
|
|
struct amdgpu_bo *new_abo;
|
|
unsigned long flags;
|
|
u64 tiling_flags;
|
|
int i, r;
|
|
|
|
work = kzalloc(sizeof *work, GFP_KERNEL);
|
|
if (work == NULL)
|
|
return -ENOMEM;
|
|
|
|
INIT_DELAYED_WORK(&work->flip_work, amdgpu_display_flip_work_func);
|
|
INIT_WORK(&work->unpin_work, amdgpu_display_unpin_work_func);
|
|
|
|
work->event = event;
|
|
work->adev = adev;
|
|
work->crtc_id = amdgpu_crtc->crtc_id;
|
|
work->async = (page_flip_flags & DRM_MODE_PAGE_FLIP_ASYNC) != 0;
|
|
|
|
/* schedule unpin of the old buffer */
|
|
obj = crtc->primary->fb->obj[0];
|
|
|
|
/* take a reference to the old object */
|
|
work->old_abo = gem_to_amdgpu_bo(obj);
|
|
amdgpu_bo_ref(work->old_abo);
|
|
|
|
obj = fb->obj[0];
|
|
new_abo = gem_to_amdgpu_bo(obj);
|
|
|
|
/* pin the new buffer */
|
|
r = amdgpu_bo_reserve(new_abo, false);
|
|
if (unlikely(r != 0)) {
|
|
DRM_ERROR("failed to reserve new abo buffer before flip\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!adev->enable_virtual_display) {
|
|
r = amdgpu_bo_pin(new_abo,
|
|
amdgpu_display_supported_domains(adev, new_abo->flags));
|
|
if (unlikely(r != 0)) {
|
|
DRM_ERROR("failed to pin new abo buffer before flip\n");
|
|
goto unreserve;
|
|
}
|
|
}
|
|
|
|
r = amdgpu_ttm_alloc_gart(&new_abo->tbo);
|
|
if (unlikely(r != 0)) {
|
|
DRM_ERROR("%p bind failed\n", new_abo);
|
|
goto unpin;
|
|
}
|
|
|
|
r = dma_resv_get_fences_rcu(new_abo->tbo.base.resv, &work->excl,
|
|
&work->shared_count,
|
|
&work->shared);
|
|
if (unlikely(r != 0)) {
|
|
DRM_ERROR("failed to get fences for buffer\n");
|
|
goto unpin;
|
|
}
|
|
|
|
amdgpu_bo_get_tiling_flags(new_abo, &tiling_flags);
|
|
amdgpu_bo_unreserve(new_abo);
|
|
|
|
if (!adev->enable_virtual_display)
|
|
work->base = amdgpu_bo_gpu_offset(new_abo);
|
|
work->target_vblank = target - (uint32_t)drm_crtc_vblank_count(crtc) +
|
|
amdgpu_get_vblank_counter_kms(crtc);
|
|
|
|
/* 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;
|
|
goto pflip_cleanup;
|
|
}
|
|
|
|
amdgpu_crtc->pflip_status = AMDGPU_FLIP_PENDING;
|
|
amdgpu_crtc->pflip_works = work;
|
|
|
|
|
|
DRM_DEBUG_DRIVER("crtc:%d[%p], pflip_stat:AMDGPU_FLIP_PENDING, work: %p,\n",
|
|
amdgpu_crtc->crtc_id, amdgpu_crtc, work);
|
|
/* update crtc fb */
|
|
crtc->primary->fb = fb;
|
|
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
|
|
amdgpu_display_flip_work_func(&work->flip_work.work);
|
|
return 0;
|
|
|
|
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 (!adev->enable_virtual_display)
|
|
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);
|
|
|
|
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);
|
|
|
|
return r;
|
|
}
|
|
|
|
int amdgpu_display_crtc_set_config(struct drm_mode_set *set,
|
|
struct drm_modeset_acquire_ctx *ctx)
|
|
{
|
|
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)
|
|
goto out;
|
|
|
|
ret = drm_crtc_helper_set_config(set, ctx);
|
|
|
|
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
|
|
if (crtc->enabled)
|
|
active = true;
|
|
|
|
pm_runtime_mark_last_busy(dev->dev);
|
|
|
|
adev = drm_to_adev(dev);
|
|
/* 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;
|
|
}
|
|
|
|
out:
|
|
/* drop the power reference we got coming in here */
|
|
pm_runtime_put_autosuspend(dev->dev);
|
|
return ret;
|
|
}
|
|
|
|
static const char *encoder_names[41] = {
|
|
"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",
|
|
"HDMI_ANX9805",
|
|
"INTERNAL_AMCLK",
|
|
"VIRTUAL",
|
|
};
|
|
|
|
static const char *hpd_names[6] = {
|
|
"HPD1",
|
|
"HPD2",
|
|
"HPD3",
|
|
"HPD4",
|
|
"HPD5",
|
|
"HPD6",
|
|
};
|
|
|
|
void amdgpu_display_print_display_setup(struct drm_device *dev)
|
|
{
|
|
struct drm_connector *connector;
|
|
struct amdgpu_connector *amdgpu_connector;
|
|
struct drm_encoder *encoder;
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
struct drm_connector_list_iter iter;
|
|
uint32_t devices;
|
|
int i = 0;
|
|
|
|
drm_connector_list_iter_begin(dev, &iter);
|
|
DRM_INFO("AMDGPU Display Connectors\n");
|
|
drm_for_each_connector_iter(connector, &iter) {
|
|
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++;
|
|
}
|
|
drm_connector_list_iter_end(&iter);
|
|
}
|
|
|
|
/**
|
|
* amdgpu_display_ddc_probe
|
|
*
|
|
*/
|
|
bool amdgpu_display_ddc_probe(struct amdgpu_connector *amdgpu_connector,
|
|
bool use_aux)
|
|
{
|
|
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 = {
|
|
.destroy = drm_gem_fb_destroy,
|
|
.create_handle = drm_gem_fb_create_handle,
|
|
};
|
|
|
|
uint32_t amdgpu_display_supported_domains(struct amdgpu_device *adev,
|
|
uint64_t bo_flags)
|
|
{
|
|
uint32_t domain = AMDGPU_GEM_DOMAIN_VRAM;
|
|
|
|
#if defined(CONFIG_DRM_AMD_DC)
|
|
/*
|
|
* if amdgpu_bo_support_uswc returns false it means that USWC mappings
|
|
* is not supported for this board. But this mapping is required
|
|
* to avoid hang caused by placement of scanout BO in GTT on certain
|
|
* APUs. So force the BO placement to VRAM in case this architecture
|
|
* will not allow USWC mappings.
|
|
* Also, don't allow GTT domain if the BO doens't have USWC falg set.
|
|
*/
|
|
if ((bo_flags & AMDGPU_GEM_CREATE_CPU_GTT_USWC) &&
|
|
amdgpu_bo_support_uswc(bo_flags) &&
|
|
amdgpu_device_asic_has_dc_support(adev->asic_type)) {
|
|
switch (adev->asic_type) {
|
|
case CHIP_CARRIZO:
|
|
case CHIP_STONEY:
|
|
domain |= AMDGPU_GEM_DOMAIN_GTT;
|
|
break;
|
|
case CHIP_RAVEN:
|
|
/* enable S/G on PCO and RV2 */
|
|
if ((adev->apu_flags & AMD_APU_IS_RAVEN2) ||
|
|
(adev->apu_flags & AMD_APU_IS_PICASSO))
|
|
domain |= AMDGPU_GEM_DOMAIN_GTT;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return domain;
|
|
}
|
|
|
|
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)
|
|
{
|
|
int ret;
|
|
rfb->base.obj[0] = obj;
|
|
drm_helper_mode_fill_fb_struct(dev, &rfb->base, mode_cmd);
|
|
ret = drm_framebuffer_init(dev, &rfb->base, &amdgpu_fb_funcs);
|
|
if (ret) {
|
|
rfb->base.obj[0] = NULL;
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct drm_framebuffer *
|
|
amdgpu_display_user_framebuffer_create(struct drm_device *dev,
|
|
struct drm_file *file_priv,
|
|
const struct drm_mode_fb_cmd2 *mode_cmd)
|
|
{
|
|
struct drm_gem_object *obj;
|
|
struct amdgpu_framebuffer *amdgpu_fb;
|
|
int ret;
|
|
|
|
obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[0]);
|
|
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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
amdgpu_fb = kzalloc(sizeof(*amdgpu_fb), GFP_KERNEL);
|
|
if (amdgpu_fb == NULL) {
|
|
drm_gem_object_put(obj);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
ret = amdgpu_display_framebuffer_init(dev, amdgpu_fb, mode_cmd, obj);
|
|
if (ret) {
|
|
kfree(amdgpu_fb);
|
|
drm_gem_object_put(obj);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
return &amdgpu_fb->base;
|
|
}
|
|
|
|
const struct drm_mode_config_funcs amdgpu_mode_funcs = {
|
|
.fb_create = amdgpu_display_user_framebuffer_create,
|
|
.output_poll_changed = drm_fb_helper_output_poll_changed,
|
|
};
|
|
|
|
static const struct drm_prop_enum_list amdgpu_underscan_enum_list[] =
|
|
{ { UNDERSCAN_OFF, "off" },
|
|
{ UNDERSCAN_ON, "on" },
|
|
{ UNDERSCAN_AUTO, "auto" },
|
|
};
|
|
|
|
static const struct drm_prop_enum_list amdgpu_audio_enum_list[] =
|
|
{ { AMDGPU_AUDIO_DISABLE, "off" },
|
|
{ AMDGPU_AUDIO_ENABLE, "on" },
|
|
{ AMDGPU_AUDIO_AUTO, "auto" },
|
|
};
|
|
|
|
/* XXX support different dither options? spatial, temporal, both, etc. */
|
|
static const struct drm_prop_enum_list amdgpu_dither_enum_list[] =
|
|
{ { AMDGPU_FMT_DITHER_DISABLE, "off" },
|
|
{ AMDGPU_FMT_DITHER_ENABLE, "on" },
|
|
};
|
|
|
|
int amdgpu_display_modeset_create_props(struct amdgpu_device *adev)
|
|
{
|
|
int sz;
|
|
|
|
adev->mode_info.coherent_mode_property =
|
|
drm_property_create_range(adev_to_drm(adev), 0, "coherent", 0, 1);
|
|
if (!adev->mode_info.coherent_mode_property)
|
|
return -ENOMEM;
|
|
|
|
adev->mode_info.load_detect_property =
|
|
drm_property_create_range(adev_to_drm(adev), 0, "load detection", 0, 1);
|
|
if (!adev->mode_info.load_detect_property)
|
|
return -ENOMEM;
|
|
|
|
drm_mode_create_scaling_mode_property(adev_to_drm(adev));
|
|
|
|
sz = ARRAY_SIZE(amdgpu_underscan_enum_list);
|
|
adev->mode_info.underscan_property =
|
|
drm_property_create_enum(adev_to_drm(adev), 0,
|
|
"underscan",
|
|
amdgpu_underscan_enum_list, sz);
|
|
|
|
adev->mode_info.underscan_hborder_property =
|
|
drm_property_create_range(adev_to_drm(adev), 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_to_drm(adev), 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_to_drm(adev), 0,
|
|
"audio",
|
|
amdgpu_audio_enum_list, sz);
|
|
|
|
sz = ARRAY_SIZE(amdgpu_dither_enum_list);
|
|
adev->mode_info.dither_property =
|
|
drm_property_create_enum(adev_to_drm(adev), 0,
|
|
"dither",
|
|
amdgpu_dither_enum_list, sz);
|
|
|
|
if (amdgpu_device_has_dc_support(adev)) {
|
|
adev->mode_info.abm_level_property =
|
|
drm_property_create_range(adev_to_drm(adev), 0,
|
|
"abm level", 0, 4);
|
|
if (!adev->mode_info.abm_level_property)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void amdgpu_display_update_priority(struct amdgpu_device *adev)
|
|
{
|
|
/* 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;
|
|
|
|
}
|
|
|
|
static bool amdgpu_display_is_hdtv_mode(const struct drm_display_mode *mode)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
bool amdgpu_display_crtc_scaling_mode_fixup(struct drm_crtc *crtc,
|
|
const struct drm_display_mode *mode,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
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;
|
|
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);
|
|
|
|
/* 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)) &&
|
|
amdgpu_display_is_hdtv_mode(mode)))) {
|
|
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.
|
|
* \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
|
|
* 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.
|
|
*
|
|
*/
|
|
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)
|
|
{
|
|
u32 vbl = 0, position = 0;
|
|
int vbl_start, vbl_end, vtotal, ret = 0;
|
|
bool in_vbl = true;
|
|
|
|
struct amdgpu_device *adev = drm_to_adev(dev);
|
|
|
|
/* preempt_disable_rt() should go right here in PREEMPT_RT patchset. */
|
|
|
|
/* Get optional system timestamp before query. */
|
|
if (stime)
|
|
*stime = ktime_get();
|
|
|
|
if (amdgpu_display_page_flip_get_scanoutpos(adev, pipe, &vbl, &position) == 0)
|
|
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. */
|
|
vbl_start = mode->crtc_vdisplay;
|
|
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
|
|
* start of scanout.
|
|
*/
|
|
|
|
/* Inside "upper part" of vblank area? Apply corrective offset if so: */
|
|
if (in_vbl && (*vpos >= vbl_start)) {
|
|
vtotal = mode->crtc_vtotal;
|
|
|
|
/* With variable refresh rate displays the vpos can exceed
|
|
* the vtotal value. Clamp to 0 to return -vbl_end instead
|
|
* of guessing the remaining number of lines until scanout.
|
|
*/
|
|
*vpos = (*vpos < vtotal) ? (*vpos - vtotal) : 0;
|
|
}
|
|
|
|
/* Correct for shifted end of vbl at vbl_end. */
|
|
*vpos = *vpos - vbl_end;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int amdgpu_display_crtc_idx_to_irq_type(struct amdgpu_device *adev, int crtc)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
bool amdgpu_crtc_get_scanout_position(struct drm_crtc *crtc,
|
|
bool in_vblank_irq, int *vpos,
|
|
int *hpos, ktime_t *stime, ktime_t *etime,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
unsigned int pipe = crtc->index;
|
|
|
|
return amdgpu_display_get_crtc_scanoutpos(dev, pipe, 0, vpos, hpos,
|
|
stime, etime, mode);
|
|
}
|