2015-04-20 21:31:14 +00:00
|
|
|
/*
|
|
|
|
* Copyright 2014 Advanced Micro Devices, 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.
|
|
|
|
*
|
|
|
|
*/
|
2017-04-24 04:50:21 +00:00
|
|
|
#include <drm/drmP.h>
|
2015-04-20 21:31:14 +00:00
|
|
|
#include "amdgpu.h"
|
|
|
|
#include "amdgpu_pm.h"
|
|
|
|
#include "amdgpu_i2c.h"
|
|
|
|
#include "vid.h"
|
|
|
|
#include "atom.h"
|
|
|
|
#include "amdgpu_atombios.h"
|
|
|
|
#include "atombios_crtc.h"
|
|
|
|
#include "atombios_encoders.h"
|
|
|
|
#include "amdgpu_pll.h"
|
|
|
|
#include "amdgpu_connectors.h"
|
2016-10-22 08:48:25 +00:00
|
|
|
#include "dce_v10_0.h"
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
#include "dce/dce_10_0_d.h"
|
|
|
|
#include "dce/dce_10_0_sh_mask.h"
|
|
|
|
#include "dce/dce_10_0_enum.h"
|
|
|
|
#include "oss/oss_3_0_d.h"
|
|
|
|
#include "oss/oss_3_0_sh_mask.h"
|
|
|
|
#include "gmc/gmc_8_1_d.h"
|
|
|
|
#include "gmc/gmc_8_1_sh_mask.h"
|
|
|
|
|
|
|
|
static void dce_v10_0_set_display_funcs(struct amdgpu_device *adev);
|
|
|
|
static void dce_v10_0_set_irq_funcs(struct amdgpu_device *adev);
|
|
|
|
|
|
|
|
static const u32 crtc_offsets[] =
|
|
|
|
{
|
|
|
|
CRTC0_REGISTER_OFFSET,
|
|
|
|
CRTC1_REGISTER_OFFSET,
|
|
|
|
CRTC2_REGISTER_OFFSET,
|
|
|
|
CRTC3_REGISTER_OFFSET,
|
|
|
|
CRTC4_REGISTER_OFFSET,
|
|
|
|
CRTC5_REGISTER_OFFSET,
|
|
|
|
CRTC6_REGISTER_OFFSET
|
|
|
|
};
|
|
|
|
|
|
|
|
static const u32 hpd_offsets[] =
|
|
|
|
{
|
|
|
|
HPD0_REGISTER_OFFSET,
|
|
|
|
HPD1_REGISTER_OFFSET,
|
|
|
|
HPD2_REGISTER_OFFSET,
|
|
|
|
HPD3_REGISTER_OFFSET,
|
|
|
|
HPD4_REGISTER_OFFSET,
|
|
|
|
HPD5_REGISTER_OFFSET
|
|
|
|
};
|
|
|
|
|
|
|
|
static const uint32_t dig_offsets[] = {
|
|
|
|
DIG0_REGISTER_OFFSET,
|
|
|
|
DIG1_REGISTER_OFFSET,
|
|
|
|
DIG2_REGISTER_OFFSET,
|
|
|
|
DIG3_REGISTER_OFFSET,
|
|
|
|
DIG4_REGISTER_OFFSET,
|
|
|
|
DIG5_REGISTER_OFFSET,
|
|
|
|
DIG6_REGISTER_OFFSET
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct {
|
|
|
|
uint32_t reg;
|
|
|
|
uint32_t vblank;
|
|
|
|
uint32_t vline;
|
|
|
|
uint32_t hpd;
|
|
|
|
|
|
|
|
} interrupt_status_offsets[] = { {
|
|
|
|
.reg = mmDISP_INTERRUPT_STATUS,
|
|
|
|
.vblank = DISP_INTERRUPT_STATUS__LB_D1_VBLANK_INTERRUPT_MASK,
|
|
|
|
.vline = DISP_INTERRUPT_STATUS__LB_D1_VLINE_INTERRUPT_MASK,
|
|
|
|
.hpd = DISP_INTERRUPT_STATUS__DC_HPD1_INTERRUPT_MASK
|
|
|
|
}, {
|
|
|
|
.reg = mmDISP_INTERRUPT_STATUS_CONTINUE,
|
|
|
|
.vblank = DISP_INTERRUPT_STATUS_CONTINUE__LB_D2_VBLANK_INTERRUPT_MASK,
|
|
|
|
.vline = DISP_INTERRUPT_STATUS_CONTINUE__LB_D2_VLINE_INTERRUPT_MASK,
|
|
|
|
.hpd = DISP_INTERRUPT_STATUS_CONTINUE__DC_HPD2_INTERRUPT_MASK
|
|
|
|
}, {
|
|
|
|
.reg = mmDISP_INTERRUPT_STATUS_CONTINUE2,
|
|
|
|
.vblank = DISP_INTERRUPT_STATUS_CONTINUE2__LB_D3_VBLANK_INTERRUPT_MASK,
|
|
|
|
.vline = DISP_INTERRUPT_STATUS_CONTINUE2__LB_D3_VLINE_INTERRUPT_MASK,
|
|
|
|
.hpd = DISP_INTERRUPT_STATUS_CONTINUE2__DC_HPD3_INTERRUPT_MASK
|
|
|
|
}, {
|
|
|
|
.reg = mmDISP_INTERRUPT_STATUS_CONTINUE3,
|
|
|
|
.vblank = DISP_INTERRUPT_STATUS_CONTINUE3__LB_D4_VBLANK_INTERRUPT_MASK,
|
|
|
|
.vline = DISP_INTERRUPT_STATUS_CONTINUE3__LB_D4_VLINE_INTERRUPT_MASK,
|
|
|
|
.hpd = DISP_INTERRUPT_STATUS_CONTINUE3__DC_HPD4_INTERRUPT_MASK
|
|
|
|
}, {
|
|
|
|
.reg = mmDISP_INTERRUPT_STATUS_CONTINUE4,
|
|
|
|
.vblank = DISP_INTERRUPT_STATUS_CONTINUE4__LB_D5_VBLANK_INTERRUPT_MASK,
|
|
|
|
.vline = DISP_INTERRUPT_STATUS_CONTINUE4__LB_D5_VLINE_INTERRUPT_MASK,
|
|
|
|
.hpd = DISP_INTERRUPT_STATUS_CONTINUE4__DC_HPD5_INTERRUPT_MASK
|
|
|
|
}, {
|
|
|
|
.reg = mmDISP_INTERRUPT_STATUS_CONTINUE5,
|
|
|
|
.vblank = DISP_INTERRUPT_STATUS_CONTINUE5__LB_D6_VBLANK_INTERRUPT_MASK,
|
|
|
|
.vline = DISP_INTERRUPT_STATUS_CONTINUE5__LB_D6_VLINE_INTERRUPT_MASK,
|
|
|
|
.hpd = DISP_INTERRUPT_STATUS_CONTINUE5__DC_HPD6_INTERRUPT_MASK
|
|
|
|
} };
|
|
|
|
|
|
|
|
static const u32 golden_settings_tonga_a11[] =
|
|
|
|
{
|
|
|
|
mmDCI_CLK_CNTL, 0x00000080, 0x00000000,
|
|
|
|
mmFBC_DEBUG_COMP, 0x000000f0, 0x00000070,
|
|
|
|
mmFBC_MISC, 0x1f311fff, 0x12300000,
|
|
|
|
mmHDMI_CONTROL, 0x31000111, 0x00000011,
|
|
|
|
};
|
|
|
|
|
2015-06-09 17:51:25 +00:00
|
|
|
static const u32 tonga_mgcg_cgcg_init[] =
|
|
|
|
{
|
|
|
|
mmXDMA_CLOCK_GATING_CNTL, 0xffffffff, 0x00000100,
|
|
|
|
mmXDMA_MEM_POWER_CNTL, 0x00000101, 0x00000000,
|
|
|
|
};
|
|
|
|
|
2015-07-07 17:28:20 +00:00
|
|
|
static const u32 golden_settings_fiji_a10[] =
|
|
|
|
{
|
|
|
|
mmDCI_CLK_CNTL, 0x00000080, 0x00000000,
|
|
|
|
mmFBC_DEBUG_COMP, 0x000000f0, 0x00000070,
|
|
|
|
mmFBC_MISC, 0x1f311fff, 0x12300000,
|
|
|
|
mmHDMI_CONTROL, 0x31000111, 0x00000011,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const u32 fiji_mgcg_cgcg_init[] =
|
|
|
|
{
|
|
|
|
mmXDMA_CLOCK_GATING_CNTL, 0xffffffff, 0x00000100,
|
|
|
|
mmXDMA_MEM_POWER_CNTL, 0x00000101, 0x00000000,
|
|
|
|
};
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
static void dce_v10_0_init_golden_registers(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
switch (adev->asic_type) {
|
2015-07-07 17:28:20 +00:00
|
|
|
case CHIP_FIJI:
|
2017-12-14 21:20:19 +00:00
|
|
|
amdgpu_device_program_register_sequence(adev,
|
|
|
|
fiji_mgcg_cgcg_init,
|
|
|
|
ARRAY_SIZE(fiji_mgcg_cgcg_init));
|
|
|
|
amdgpu_device_program_register_sequence(adev,
|
|
|
|
golden_settings_fiji_a10,
|
|
|
|
ARRAY_SIZE(golden_settings_fiji_a10));
|
2015-07-07 17:28:20 +00:00
|
|
|
break;
|
2015-04-20 21:31:14 +00:00
|
|
|
case CHIP_TONGA:
|
2017-12-14 21:20:19 +00:00
|
|
|
amdgpu_device_program_register_sequence(adev,
|
|
|
|
tonga_mgcg_cgcg_init,
|
|
|
|
ARRAY_SIZE(tonga_mgcg_cgcg_init));
|
|
|
|
amdgpu_device_program_register_sequence(adev,
|
|
|
|
golden_settings_tonga_a11,
|
|
|
|
ARRAY_SIZE(golden_settings_tonga_a11));
|
2015-04-20 21:31:14 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 dce_v10_0_audio_endpt_rreg(struct amdgpu_device *adev,
|
|
|
|
u32 block_offset, u32 reg)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
u32 r;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&adev->audio_endpt_idx_lock, flags);
|
|
|
|
WREG32(mmAZALIA_F0_CODEC_ENDPOINT_INDEX + block_offset, reg);
|
|
|
|
r = RREG32(mmAZALIA_F0_CODEC_ENDPOINT_DATA + block_offset);
|
|
|
|
spin_unlock_irqrestore(&adev->audio_endpt_idx_lock, flags);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_audio_endpt_wreg(struct amdgpu_device *adev,
|
|
|
|
u32 block_offset, u32 reg, u32 v)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&adev->audio_endpt_idx_lock, flags);
|
|
|
|
WREG32(mmAZALIA_F0_CODEC_ENDPOINT_INDEX + block_offset, reg);
|
|
|
|
WREG32(mmAZALIA_F0_CODEC_ENDPOINT_DATA + block_offset, v);
|
|
|
|
spin_unlock_irqrestore(&adev->audio_endpt_idx_lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool dce_v10_0_is_in_vblank(struct amdgpu_device *adev, int crtc)
|
|
|
|
{
|
|
|
|
if (RREG32(mmCRTC_STATUS + crtc_offsets[crtc]) &
|
|
|
|
CRTC_V_BLANK_START_END__CRTC_V_BLANK_START_MASK)
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool dce_v10_0_is_counter_moving(struct amdgpu_device *adev, int crtc)
|
|
|
|
{
|
|
|
|
u32 pos1, pos2;
|
|
|
|
|
|
|
|
pos1 = RREG32(mmCRTC_STATUS_POSITION + crtc_offsets[crtc]);
|
|
|
|
pos2 = RREG32(mmCRTC_STATUS_POSITION + crtc_offsets[crtc]);
|
|
|
|
|
|
|
|
if (pos1 != pos2)
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_vblank_wait - vblank wait asic callback.
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
* @crtc: crtc to wait for vblank on
|
|
|
|
*
|
|
|
|
* Wait for vblank on the requested crtc (evergreen+).
|
|
|
|
*/
|
|
|
|
static void dce_v10_0_vblank_wait(struct amdgpu_device *adev, int crtc)
|
|
|
|
{
|
2016-09-22 14:01:00 +00:00
|
|
|
unsigned i = 100;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
if (crtc >= adev->mode_info.num_crtc)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!(RREG32(mmCRTC_CONTROL + crtc_offsets[crtc]) & CRTC_CONTROL__CRTC_MASTER_EN_MASK))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* depending on when we hit vblank, we may be close to active; if so,
|
|
|
|
* wait for another frame.
|
|
|
|
*/
|
|
|
|
while (dce_v10_0_is_in_vblank(adev, crtc)) {
|
2016-09-22 14:01:00 +00:00
|
|
|
if (i++ == 100) {
|
|
|
|
i = 0;
|
2015-04-20 21:31:14 +00:00
|
|
|
if (!dce_v10_0_is_counter_moving(adev, crtc))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!dce_v10_0_is_in_vblank(adev, crtc)) {
|
2016-09-22 14:01:00 +00:00
|
|
|
if (i++ == 100) {
|
|
|
|
i = 0;
|
2015-04-20 21:31:14 +00:00
|
|
|
if (!dce_v10_0_is_counter_moving(adev, crtc))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 dce_v10_0_vblank_get_counter(struct amdgpu_device *adev, int crtc)
|
|
|
|
{
|
|
|
|
if (crtc >= adev->mode_info.num_crtc)
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
return RREG32(mmCRTC_STATUS_FRAME_COUNT + crtc_offsets[crtc]);
|
|
|
|
}
|
|
|
|
|
2015-10-08 08:48:04 +00:00
|
|
|
static void dce_v10_0_pageflip_interrupt_init(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
/* Enable pflip interrupts */
|
|
|
|
for (i = 0; i < adev->mode_info.num_crtc; i++)
|
|
|
|
amdgpu_irq_get(adev, &adev->pageflip_irq, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_pageflip_interrupt_fini(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
/* Disable pflip interrupts */
|
|
|
|
for (i = 0; i < adev->mode_info.num_crtc; i++)
|
|
|
|
amdgpu_irq_put(adev, &adev->pageflip_irq, i);
|
|
|
|
}
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
/**
|
|
|
|
* dce_v10_0_page_flip - pageflip callback.
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
* @crtc_id: crtc to cleanup pageflip on
|
|
|
|
* @crtc_base: new address of the crtc (GPU MC address)
|
|
|
|
*
|
2015-10-22 18:16:57 +00:00
|
|
|
* Triggers the actual pageflip by updating the primary
|
|
|
|
* surface base address.
|
2015-04-20 21:31:14 +00:00
|
|
|
*/
|
|
|
|
static void dce_v10_0_page_flip(struct amdgpu_device *adev,
|
2016-05-05 20:03:57 +00:00
|
|
|
int crtc_id, u64 crtc_base, bool async)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = adev->mode_info.crtcs[crtc_id];
|
2016-05-05 20:03:57 +00:00
|
|
|
u32 tmp;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
2016-05-05 20:03:57 +00:00
|
|
|
/* flip at hsync for async, default is vsync */
|
|
|
|
tmp = RREG32(mmGRPH_FLIP_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, GRPH_FLIP_CONTROL,
|
|
|
|
GRPH_SURFACE_UPDATE_H_RETRACE_EN, async ? 1 : 0);
|
|
|
|
WREG32(mmGRPH_FLIP_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
2015-10-22 18:16:57 +00:00
|
|
|
/* update the primary scanout address */
|
2015-04-20 21:31:14 +00:00
|
|
|
WREG32(mmGRPH_PRIMARY_SURFACE_ADDRESS_HIGH + amdgpu_crtc->crtc_offset,
|
|
|
|
upper_32_bits(crtc_base));
|
2015-10-22 18:16:57 +00:00
|
|
|
/* writing to the low address triggers the update */
|
2015-04-20 21:31:14 +00:00
|
|
|
WREG32(mmGRPH_PRIMARY_SURFACE_ADDRESS + amdgpu_crtc->crtc_offset,
|
|
|
|
lower_32_bits(crtc_base));
|
2015-10-22 18:16:57 +00:00
|
|
|
/* post the write */
|
|
|
|
RREG32(mmGRPH_PRIMARY_SURFACE_ADDRESS + amdgpu_crtc->crtc_offset);
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc,
|
|
|
|
u32 *vbl, u32 *position)
|
|
|
|
{
|
|
|
|
if ((crtc < 0) || (crtc >= adev->mode_info.num_crtc))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
*vbl = RREG32(mmCRTC_V_BLANK_START_END + crtc_offsets[crtc]);
|
|
|
|
*position = RREG32(mmCRTC_STATUS_POSITION + crtc_offsets[crtc]);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_hpd_sense - hpd sense callback.
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
* @hpd: hpd (hotplug detect) pin
|
|
|
|
*
|
|
|
|
* Checks if a digital monitor is connected (evergreen+).
|
|
|
|
* Returns true if connected, false if not connected.
|
|
|
|
*/
|
|
|
|
static bool dce_v10_0_hpd_sense(struct amdgpu_device *adev,
|
|
|
|
enum amdgpu_hpd_id hpd)
|
|
|
|
{
|
|
|
|
bool connected = false;
|
|
|
|
|
2016-09-28 17:44:00 +00:00
|
|
|
if (hpd >= adev->mode_info.num_hpd)
|
2015-04-20 21:31:14 +00:00
|
|
|
return connected;
|
|
|
|
|
2016-09-28 17:44:00 +00:00
|
|
|
if (RREG32(mmDC_HPD_INT_STATUS + hpd_offsets[hpd]) &
|
2015-04-20 21:31:14 +00:00
|
|
|
DC_HPD_INT_STATUS__DC_HPD_SENSE_MASK)
|
|
|
|
connected = true;
|
|
|
|
|
|
|
|
return connected;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_hpd_set_polarity - hpd set polarity callback.
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
* @hpd: hpd (hotplug detect) pin
|
|
|
|
*
|
|
|
|
* Set the polarity of the hpd pin (evergreen+).
|
|
|
|
*/
|
|
|
|
static void dce_v10_0_hpd_set_polarity(struct amdgpu_device *adev,
|
|
|
|
enum amdgpu_hpd_id hpd)
|
|
|
|
{
|
|
|
|
u32 tmp;
|
|
|
|
bool connected = dce_v10_0_hpd_sense(adev, hpd);
|
|
|
|
|
2016-09-28 17:44:00 +00:00
|
|
|
if (hpd >= adev->mode_info.num_hpd)
|
2015-04-20 21:31:14 +00:00
|
|
|
return;
|
|
|
|
|
2016-09-28 17:44:00 +00:00
|
|
|
tmp = RREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[hpd]);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (connected)
|
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_INT_CONTROL, DC_HPD_INT_POLARITY, 0);
|
|
|
|
else
|
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_INT_CONTROL, DC_HPD_INT_POLARITY, 1);
|
2016-09-28 17:44:00 +00:00
|
|
|
WREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[hpd], tmp);
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_hpd_init - hpd setup callback.
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
*
|
|
|
|
* Setup the hpd pins used by the card (evergreen+).
|
|
|
|
* Enable the pin, set the polarity, and enable the hpd interrupts.
|
|
|
|
*/
|
|
|
|
static void dce_v10_0_hpd_init(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = adev->ddev;
|
|
|
|
struct drm_connector *connector;
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
|
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
|
2016-09-28 17:44:00 +00:00
|
|
|
if (amdgpu_connector->hpd.hpd >= adev->mode_info.num_hpd)
|
2015-04-20 21:31:14 +00:00
|
|
|
continue;
|
|
|
|
|
2016-09-28 16:41:50 +00:00
|
|
|
if (connector->connector_type == DRM_MODE_CONNECTOR_eDP ||
|
|
|
|
connector->connector_type == DRM_MODE_CONNECTOR_LVDS) {
|
|
|
|
/* don't try to enable hpd on eDP or LVDS avoid breaking the
|
|
|
|
* aux dp channel on imac and help (but not completely fix)
|
|
|
|
* https://bugzilla.redhat.com/show_bug.cgi?id=726143
|
|
|
|
* also avoid interrupt storms during dpms.
|
|
|
|
*/
|
2016-09-28 17:44:00 +00:00
|
|
|
tmp = RREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[amdgpu_connector->hpd.hpd]);
|
2016-09-28 16:41:50 +00:00
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_INT_CONTROL, DC_HPD_INT_EN, 0);
|
2016-09-28 17:44:00 +00:00
|
|
|
WREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[amdgpu_connector->hpd.hpd], tmp);
|
2016-09-28 16:41:50 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-09-28 17:44:00 +00:00
|
|
|
tmp = RREG32(mmDC_HPD_CONTROL + hpd_offsets[amdgpu_connector->hpd.hpd]);
|
2015-04-20 21:31:14 +00:00
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_CONTROL, DC_HPD_EN, 1);
|
2016-09-28 17:44:00 +00:00
|
|
|
WREG32(mmDC_HPD_CONTROL + hpd_offsets[amdgpu_connector->hpd.hpd], tmp);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
2016-09-28 17:44:00 +00:00
|
|
|
tmp = RREG32(mmDC_HPD_TOGGLE_FILT_CNTL + hpd_offsets[amdgpu_connector->hpd.hpd]);
|
2015-04-20 21:31:14 +00:00
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_TOGGLE_FILT_CNTL,
|
|
|
|
DC_HPD_CONNECT_INT_DELAY,
|
|
|
|
AMDGPU_HPD_CONNECT_INT_DELAY_IN_MS);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_TOGGLE_FILT_CNTL,
|
|
|
|
DC_HPD_DISCONNECT_INT_DELAY,
|
|
|
|
AMDGPU_HPD_DISCONNECT_INT_DELAY_IN_MS);
|
2016-09-28 17:44:00 +00:00
|
|
|
WREG32(mmDC_HPD_TOGGLE_FILT_CNTL + hpd_offsets[amdgpu_connector->hpd.hpd], tmp);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
dce_v10_0_hpd_set_polarity(adev, amdgpu_connector->hpd.hpd);
|
|
|
|
amdgpu_irq_get(adev, &adev->hpd_irq,
|
|
|
|
amdgpu_connector->hpd.hpd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_hpd_fini - hpd tear down callback.
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
*
|
|
|
|
* Tear down the hpd pins used by the card (evergreen+).
|
|
|
|
* Disable the hpd interrupts.
|
|
|
|
*/
|
|
|
|
static void dce_v10_0_hpd_fini(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = adev->ddev;
|
|
|
|
struct drm_connector *connector;
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
|
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
|
2016-09-28 17:44:00 +00:00
|
|
|
if (amdgpu_connector->hpd.hpd >= adev->mode_info.num_hpd)
|
2015-04-20 21:31:14 +00:00
|
|
|
continue;
|
|
|
|
|
2016-09-28 17:44:00 +00:00
|
|
|
tmp = RREG32(mmDC_HPD_CONTROL + hpd_offsets[amdgpu_connector->hpd.hpd]);
|
2015-04-20 21:31:14 +00:00
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_CONTROL, DC_HPD_EN, 0);
|
2016-09-28 17:44:00 +00:00
|
|
|
WREG32(mmDC_HPD_CONTROL + hpd_offsets[amdgpu_connector->hpd.hpd], tmp);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
amdgpu_irq_put(adev, &adev->hpd_irq,
|
|
|
|
amdgpu_connector->hpd.hpd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 dce_v10_0_hpd_get_gpio_reg(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
return mmDC_GPIO_HPD_A;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool dce_v10_0_is_display_hung(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
u32 crtc_hung = 0;
|
|
|
|
u32 crtc_status[6];
|
|
|
|
u32 i, j, tmp;
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.num_crtc; i++) {
|
|
|
|
tmp = RREG32(mmCRTC_CONTROL + crtc_offsets[i]);
|
|
|
|
if (REG_GET_FIELD(tmp, CRTC_CONTROL, CRTC_MASTER_EN)) {
|
|
|
|
crtc_status[i] = RREG32(mmCRTC_STATUS_HV_COUNT + crtc_offsets[i]);
|
|
|
|
crtc_hung |= (1 << i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (j = 0; j < 10; j++) {
|
|
|
|
for (i = 0; i < adev->mode_info.num_crtc; i++) {
|
|
|
|
if (crtc_hung & (1 << i)) {
|
|
|
|
tmp = RREG32(mmCRTC_STATUS_HV_COUNT + crtc_offsets[i]);
|
|
|
|
if (tmp != crtc_status[i])
|
|
|
|
crtc_hung &= ~(1 << i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (crtc_hung == 0)
|
|
|
|
return false;
|
|
|
|
udelay(100);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_set_vga_render_state(struct amdgpu_device *adev,
|
|
|
|
bool render)
|
|
|
|
{
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
/* Lockout access through VGA aperture*/
|
|
|
|
tmp = RREG32(mmVGA_HDP_CONTROL);
|
|
|
|
if (render)
|
|
|
|
tmp = REG_SET_FIELD(tmp, VGA_HDP_CONTROL, VGA_MEMORY_DISABLE, 0);
|
|
|
|
else
|
|
|
|
tmp = REG_SET_FIELD(tmp, VGA_HDP_CONTROL, VGA_MEMORY_DISABLE, 1);
|
|
|
|
WREG32(mmVGA_HDP_CONTROL, tmp);
|
|
|
|
|
|
|
|
/* disable VGA render */
|
|
|
|
tmp = RREG32(mmVGA_RENDER_CONTROL);
|
|
|
|
if (render)
|
|
|
|
tmp = REG_SET_FIELD(tmp, VGA_RENDER_CONTROL, VGA_VSTATUS_CNTL, 1);
|
|
|
|
else
|
|
|
|
tmp = REG_SET_FIELD(tmp, VGA_RENDER_CONTROL, VGA_VSTATUS_CNTL, 0);
|
|
|
|
WREG32(mmVGA_RENDER_CONTROL, tmp);
|
|
|
|
}
|
|
|
|
|
2016-08-08 03:33:11 +00:00
|
|
|
static int dce_v10_0_get_num_crtc(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
int num_crtc = 0;
|
|
|
|
|
|
|
|
switch (adev->asic_type) {
|
|
|
|
case CHIP_FIJI:
|
|
|
|
case CHIP_TONGA:
|
|
|
|
num_crtc = 6;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
num_crtc = 0;
|
|
|
|
}
|
|
|
|
return num_crtc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void dce_v10_0_disable_dce(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
/*Disable VGA render and enabled crtc, if has DCE engine*/
|
|
|
|
if (amdgpu_atombios_has_dce_engine_info(adev)) {
|
|
|
|
u32 tmp;
|
|
|
|
int crtc_enabled, i;
|
|
|
|
|
|
|
|
dce_v10_0_set_vga_render_state(adev, false);
|
|
|
|
|
|
|
|
/*Disable crtc*/
|
|
|
|
for (i = 0; i < dce_v10_0_get_num_crtc(adev); i++) {
|
|
|
|
crtc_enabled = REG_GET_FIELD(RREG32(mmCRTC_CONTROL + crtc_offsets[i]),
|
|
|
|
CRTC_CONTROL, CRTC_MASTER_EN);
|
|
|
|
if (crtc_enabled) {
|
|
|
|
WREG32(mmCRTC_UPDATE_LOCK + crtc_offsets[i], 1);
|
|
|
|
tmp = RREG32(mmCRTC_CONTROL + crtc_offsets[i]);
|
|
|
|
tmp = REG_SET_FIELD(tmp, CRTC_CONTROL, CRTC_MASTER_EN, 0);
|
|
|
|
WREG32(mmCRTC_CONTROL + crtc_offsets[i], tmp);
|
|
|
|
WREG32(mmCRTC_UPDATE_LOCK + crtc_offsets[i], 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
static void dce_v10_0_program_fmt(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = encoder->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(encoder->crtc);
|
|
|
|
struct drm_connector *connector = amdgpu_get_connector_for_encoder(encoder);
|
|
|
|
int bpc = 0;
|
|
|
|
u32 tmp = 0;
|
|
|
|
enum amdgpu_connector_dither dither = AMDGPU_FMT_DITHER_DISABLE;
|
|
|
|
|
|
|
|
if (connector) {
|
|
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
bpc = amdgpu_connector_get_monitor_bpc(connector);
|
|
|
|
dither = amdgpu_connector->dither;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* LVDS/eDP FMT is set up by atom */
|
|
|
|
if (amdgpu_encoder->devices & ATOM_DEVICE_LCD_SUPPORT)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* not needed for analog */
|
|
|
|
if ((amdgpu_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1) ||
|
|
|
|
(amdgpu_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (bpc == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (bpc) {
|
|
|
|
case 6:
|
|
|
|
if (dither == AMDGPU_FMT_DITHER_ENABLE) {
|
|
|
|
/* XXX sort out optimal dither settings */
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_FRAME_RANDOM_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_HIGHPASS_RANDOM_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_SPATIAL_DITHER_EN, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_SPATIAL_DITHER_DEPTH, 0);
|
|
|
|
} else {
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_TRUNCATE_EN, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_TRUNCATE_DEPTH, 0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
if (dither == AMDGPU_FMT_DITHER_ENABLE) {
|
|
|
|
/* XXX sort out optimal dither settings */
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_FRAME_RANDOM_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_HIGHPASS_RANDOM_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_RGB_RANDOM_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_SPATIAL_DITHER_EN, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_SPATIAL_DITHER_DEPTH, 1);
|
|
|
|
} else {
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_TRUNCATE_EN, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_TRUNCATE_DEPTH, 1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
if (dither == AMDGPU_FMT_DITHER_ENABLE) {
|
|
|
|
/* XXX sort out optimal dither settings */
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_FRAME_RANDOM_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_HIGHPASS_RANDOM_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_RGB_RANDOM_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_SPATIAL_DITHER_EN, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_SPATIAL_DITHER_DEPTH, 2);
|
|
|
|
} else {
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_TRUNCATE_EN, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, FMT_BIT_DEPTH_CONTROL, FMT_TRUNCATE_DEPTH, 2);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* not needed */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
WREG32(mmFMT_BIT_DEPTH_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* display watermark setup */
|
|
|
|
/**
|
|
|
|
* dce_v10_0_line_buffer_adjust - Set up the line buffer
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
* @amdgpu_crtc: the selected display controller
|
|
|
|
* @mode: the current display mode on the selected display
|
|
|
|
* controller
|
|
|
|
*
|
|
|
|
* Setup up the line buffer allocation for
|
|
|
|
* the selected display controller (CIK).
|
|
|
|
* Returns the line buffer size in pixels.
|
|
|
|
*/
|
|
|
|
static u32 dce_v10_0_line_buffer_adjust(struct amdgpu_device *adev,
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc,
|
|
|
|
struct drm_display_mode *mode)
|
|
|
|
{
|
|
|
|
u32 tmp, buffer_alloc, i, mem_cfg;
|
|
|
|
u32 pipe_offset = amdgpu_crtc->crtc_id;
|
|
|
|
/*
|
|
|
|
* Line Buffer Setup
|
|
|
|
* There are 6 line buffers, one for each display controllers.
|
|
|
|
* There are 3 partitions per LB. Select the number of partitions
|
|
|
|
* to enable based on the display width. For display widths larger
|
|
|
|
* than 4096, you need use to use 2 display controllers and combine
|
|
|
|
* them using the stereo blender.
|
|
|
|
*/
|
|
|
|
if (amdgpu_crtc->base.enabled && mode) {
|
|
|
|
if (mode->crtc_hdisplay < 1920) {
|
|
|
|
mem_cfg = 1;
|
|
|
|
buffer_alloc = 2;
|
|
|
|
} else if (mode->crtc_hdisplay < 2560) {
|
|
|
|
mem_cfg = 2;
|
|
|
|
buffer_alloc = 2;
|
|
|
|
} else if (mode->crtc_hdisplay < 4096) {
|
|
|
|
mem_cfg = 0;
|
2015-07-22 03:29:01 +00:00
|
|
|
buffer_alloc = (adev->flags & AMD_IS_APU) ? 2 : 4;
|
2015-04-20 21:31:14 +00:00
|
|
|
} else {
|
|
|
|
DRM_DEBUG_KMS("Mode too big for LB!\n");
|
|
|
|
mem_cfg = 0;
|
2015-07-22 03:29:01 +00:00
|
|
|
buffer_alloc = (adev->flags & AMD_IS_APU) ? 2 : 4;
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mem_cfg = 1;
|
|
|
|
buffer_alloc = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp = RREG32(mmLB_MEMORY_CTRL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, LB_MEMORY_CTRL, LB_MEMORY_CONFIG, mem_cfg);
|
|
|
|
WREG32(mmLB_MEMORY_CTRL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmPIPE0_DMIF_BUFFER_CONTROL + pipe_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, PIPE0_DMIF_BUFFER_CONTROL, DMIF_BUFFERS_ALLOCATED, buffer_alloc);
|
|
|
|
WREG32(mmPIPE0_DMIF_BUFFER_CONTROL + pipe_offset, tmp);
|
|
|
|
|
|
|
|
for (i = 0; i < adev->usec_timeout; i++) {
|
|
|
|
tmp = RREG32(mmPIPE0_DMIF_BUFFER_CONTROL + pipe_offset);
|
|
|
|
if (REG_GET_FIELD(tmp, PIPE0_DMIF_BUFFER_CONTROL, DMIF_BUFFERS_ALLOCATION_COMPLETED))
|
|
|
|
break;
|
|
|
|
udelay(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (amdgpu_crtc->base.enabled && mode) {
|
|
|
|
switch (mem_cfg) {
|
|
|
|
case 0:
|
|
|
|
default:
|
|
|
|
return 4096 * 2;
|
|
|
|
case 1:
|
|
|
|
return 1920 * 2;
|
|
|
|
case 2:
|
|
|
|
return 2560 * 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* controller not enabled, so no lb used */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* cik_get_number_of_dram_channels - get the number of dram channels
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
*
|
|
|
|
* Look up the number of video ram channels (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns the number of dram channels
|
|
|
|
*/
|
|
|
|
static u32 cik_get_number_of_dram_channels(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
u32 tmp = RREG32(mmMC_SHARED_CHMAP);
|
|
|
|
|
|
|
|
switch (REG_GET_FIELD(tmp, MC_SHARED_CHMAP, NOOFCHAN)) {
|
|
|
|
case 0:
|
|
|
|
default:
|
|
|
|
return 1;
|
|
|
|
case 1:
|
|
|
|
return 2;
|
|
|
|
case 2:
|
|
|
|
return 4;
|
|
|
|
case 3:
|
|
|
|
return 8;
|
|
|
|
case 4:
|
|
|
|
return 3;
|
|
|
|
case 5:
|
|
|
|
return 6;
|
|
|
|
case 6:
|
|
|
|
return 10;
|
|
|
|
case 7:
|
|
|
|
return 12;
|
|
|
|
case 8:
|
|
|
|
return 16;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dce10_wm_params {
|
|
|
|
u32 dram_channels; /* number of dram channels */
|
|
|
|
u32 yclk; /* bandwidth per dram data pin in kHz */
|
|
|
|
u32 sclk; /* engine clock in kHz */
|
|
|
|
u32 disp_clk; /* display clock in kHz */
|
|
|
|
u32 src_width; /* viewport width */
|
|
|
|
u32 active_time; /* active display time in ns */
|
|
|
|
u32 blank_time; /* blank time in ns */
|
|
|
|
bool interlaced; /* mode is interlaced */
|
|
|
|
fixed20_12 vsc; /* vertical scale ratio */
|
|
|
|
u32 num_heads; /* number of active crtcs */
|
|
|
|
u32 bytes_per_pixel; /* bytes per pixel display + overlay */
|
|
|
|
u32 lb_size; /* line buffer allocated to pipe */
|
|
|
|
u32 vtaps; /* vertical scaler taps */
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_dram_bandwidth - get the dram bandwidth
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Calculate the raw dram bandwidth (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns the dram bandwidth in MBytes/s
|
|
|
|
*/
|
|
|
|
static u32 dce_v10_0_dram_bandwidth(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
/* Calculate raw DRAM Bandwidth */
|
|
|
|
fixed20_12 dram_efficiency; /* 0.7 */
|
|
|
|
fixed20_12 yclk, dram_channels, bandwidth;
|
|
|
|
fixed20_12 a;
|
|
|
|
|
|
|
|
a.full = dfixed_const(1000);
|
|
|
|
yclk.full = dfixed_const(wm->yclk);
|
|
|
|
yclk.full = dfixed_div(yclk, a);
|
|
|
|
dram_channels.full = dfixed_const(wm->dram_channels * 4);
|
|
|
|
a.full = dfixed_const(10);
|
|
|
|
dram_efficiency.full = dfixed_const(7);
|
|
|
|
dram_efficiency.full = dfixed_div(dram_efficiency, a);
|
|
|
|
bandwidth.full = dfixed_mul(dram_channels, yclk);
|
|
|
|
bandwidth.full = dfixed_mul(bandwidth, dram_efficiency);
|
|
|
|
|
|
|
|
return dfixed_trunc(bandwidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_dram_bandwidth_for_display - get the dram bandwidth for display
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Calculate the dram bandwidth used for display (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns the dram bandwidth for display in MBytes/s
|
|
|
|
*/
|
|
|
|
static u32 dce_v10_0_dram_bandwidth_for_display(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
/* Calculate DRAM Bandwidth and the part allocated to display. */
|
|
|
|
fixed20_12 disp_dram_allocation; /* 0.3 to 0.7 */
|
|
|
|
fixed20_12 yclk, dram_channels, bandwidth;
|
|
|
|
fixed20_12 a;
|
|
|
|
|
|
|
|
a.full = dfixed_const(1000);
|
|
|
|
yclk.full = dfixed_const(wm->yclk);
|
|
|
|
yclk.full = dfixed_div(yclk, a);
|
|
|
|
dram_channels.full = dfixed_const(wm->dram_channels * 4);
|
|
|
|
a.full = dfixed_const(10);
|
|
|
|
disp_dram_allocation.full = dfixed_const(3); /* XXX worse case value 0.3 */
|
|
|
|
disp_dram_allocation.full = dfixed_div(disp_dram_allocation, a);
|
|
|
|
bandwidth.full = dfixed_mul(dram_channels, yclk);
|
|
|
|
bandwidth.full = dfixed_mul(bandwidth, disp_dram_allocation);
|
|
|
|
|
|
|
|
return dfixed_trunc(bandwidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_data_return_bandwidth - get the data return bandwidth
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Calculate the data return bandwidth used for display (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns the data return bandwidth in MBytes/s
|
|
|
|
*/
|
|
|
|
static u32 dce_v10_0_data_return_bandwidth(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
/* Calculate the display Data return Bandwidth */
|
|
|
|
fixed20_12 return_efficiency; /* 0.8 */
|
|
|
|
fixed20_12 sclk, bandwidth;
|
|
|
|
fixed20_12 a;
|
|
|
|
|
|
|
|
a.full = dfixed_const(1000);
|
|
|
|
sclk.full = dfixed_const(wm->sclk);
|
|
|
|
sclk.full = dfixed_div(sclk, a);
|
|
|
|
a.full = dfixed_const(10);
|
|
|
|
return_efficiency.full = dfixed_const(8);
|
|
|
|
return_efficiency.full = dfixed_div(return_efficiency, a);
|
|
|
|
a.full = dfixed_const(32);
|
|
|
|
bandwidth.full = dfixed_mul(a, sclk);
|
|
|
|
bandwidth.full = dfixed_mul(bandwidth, return_efficiency);
|
|
|
|
|
|
|
|
return dfixed_trunc(bandwidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_dmif_request_bandwidth - get the dmif bandwidth
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Calculate the dmif bandwidth used for display (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns the dmif bandwidth in MBytes/s
|
|
|
|
*/
|
|
|
|
static u32 dce_v10_0_dmif_request_bandwidth(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
/* Calculate the DMIF Request Bandwidth */
|
|
|
|
fixed20_12 disp_clk_request_efficiency; /* 0.8 */
|
|
|
|
fixed20_12 disp_clk, bandwidth;
|
|
|
|
fixed20_12 a, b;
|
|
|
|
|
|
|
|
a.full = dfixed_const(1000);
|
|
|
|
disp_clk.full = dfixed_const(wm->disp_clk);
|
|
|
|
disp_clk.full = dfixed_div(disp_clk, a);
|
|
|
|
a.full = dfixed_const(32);
|
|
|
|
b.full = dfixed_mul(a, disp_clk);
|
|
|
|
|
|
|
|
a.full = dfixed_const(10);
|
|
|
|
disp_clk_request_efficiency.full = dfixed_const(8);
|
|
|
|
disp_clk_request_efficiency.full = dfixed_div(disp_clk_request_efficiency, a);
|
|
|
|
|
|
|
|
bandwidth.full = dfixed_mul(b, disp_clk_request_efficiency);
|
|
|
|
|
|
|
|
return dfixed_trunc(bandwidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_available_bandwidth - get the min available bandwidth
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Calculate the min available bandwidth used for display (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns the min available bandwidth in MBytes/s
|
|
|
|
*/
|
|
|
|
static u32 dce_v10_0_available_bandwidth(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
/* Calculate the Available bandwidth. Display can use this temporarily but not in average. */
|
|
|
|
u32 dram_bandwidth = dce_v10_0_dram_bandwidth(wm);
|
|
|
|
u32 data_return_bandwidth = dce_v10_0_data_return_bandwidth(wm);
|
|
|
|
u32 dmif_req_bandwidth = dce_v10_0_dmif_request_bandwidth(wm);
|
|
|
|
|
|
|
|
return min(dram_bandwidth, min(data_return_bandwidth, dmif_req_bandwidth));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_average_bandwidth - get the average available bandwidth
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Calculate the average available bandwidth used for display (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns the average available bandwidth in MBytes/s
|
|
|
|
*/
|
|
|
|
static u32 dce_v10_0_average_bandwidth(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
/* Calculate the display mode Average Bandwidth
|
|
|
|
* DisplayMode should contain the source and destination dimensions,
|
|
|
|
* timing, etc.
|
|
|
|
*/
|
|
|
|
fixed20_12 bpp;
|
|
|
|
fixed20_12 line_time;
|
|
|
|
fixed20_12 src_width;
|
|
|
|
fixed20_12 bandwidth;
|
|
|
|
fixed20_12 a;
|
|
|
|
|
|
|
|
a.full = dfixed_const(1000);
|
|
|
|
line_time.full = dfixed_const(wm->active_time + wm->blank_time);
|
|
|
|
line_time.full = dfixed_div(line_time, a);
|
|
|
|
bpp.full = dfixed_const(wm->bytes_per_pixel);
|
|
|
|
src_width.full = dfixed_const(wm->src_width);
|
|
|
|
bandwidth.full = dfixed_mul(src_width, bpp);
|
|
|
|
bandwidth.full = dfixed_mul(bandwidth, wm->vsc);
|
|
|
|
bandwidth.full = dfixed_div(bandwidth, line_time);
|
|
|
|
|
|
|
|
return dfixed_trunc(bandwidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_latency_watermark - get the latency watermark
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Calculate the latency watermark (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns the latency watermark in ns
|
|
|
|
*/
|
|
|
|
static u32 dce_v10_0_latency_watermark(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
/* First calculate the latency in ns */
|
|
|
|
u32 mc_latency = 2000; /* 2000 ns. */
|
|
|
|
u32 available_bandwidth = dce_v10_0_available_bandwidth(wm);
|
|
|
|
u32 worst_chunk_return_time = (512 * 8 * 1000) / available_bandwidth;
|
|
|
|
u32 cursor_line_pair_return_time = (128 * 4 * 1000) / available_bandwidth;
|
|
|
|
u32 dc_latency = 40000000 / wm->disp_clk; /* dc pipe latency */
|
|
|
|
u32 other_heads_data_return_time = ((wm->num_heads + 1) * worst_chunk_return_time) +
|
|
|
|
(wm->num_heads * cursor_line_pair_return_time);
|
|
|
|
u32 latency = mc_latency + other_heads_data_return_time + dc_latency;
|
|
|
|
u32 max_src_lines_per_dst_line, lb_fill_bw, line_fill_time;
|
|
|
|
u32 tmp, dmif_size = 12288;
|
|
|
|
fixed20_12 a, b, c;
|
|
|
|
|
|
|
|
if (wm->num_heads == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
a.full = dfixed_const(2);
|
|
|
|
b.full = dfixed_const(1);
|
|
|
|
if ((wm->vsc.full > a.full) ||
|
|
|
|
((wm->vsc.full > b.full) && (wm->vtaps >= 3)) ||
|
|
|
|
(wm->vtaps >= 5) ||
|
|
|
|
((wm->vsc.full >= a.full) && wm->interlaced))
|
|
|
|
max_src_lines_per_dst_line = 4;
|
|
|
|
else
|
|
|
|
max_src_lines_per_dst_line = 2;
|
|
|
|
|
|
|
|
a.full = dfixed_const(available_bandwidth);
|
|
|
|
b.full = dfixed_const(wm->num_heads);
|
|
|
|
a.full = dfixed_div(a, b);
|
2017-03-29 20:09:12 +00:00
|
|
|
tmp = div_u64((u64) dmif_size * (u64) wm->disp_clk, mc_latency + 512);
|
|
|
|
tmp = min(dfixed_trunc(a), tmp);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
2017-03-29 20:09:12 +00:00
|
|
|
lb_fill_bw = min(tmp, wm->disp_clk * wm->bytes_per_pixel / 1000);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
a.full = dfixed_const(max_src_lines_per_dst_line * wm->src_width * wm->bytes_per_pixel);
|
|
|
|
b.full = dfixed_const(1000);
|
|
|
|
c.full = dfixed_const(lb_fill_bw);
|
|
|
|
b.full = dfixed_div(c, b);
|
|
|
|
a.full = dfixed_div(a, b);
|
|
|
|
line_fill_time = dfixed_trunc(a);
|
|
|
|
|
|
|
|
if (line_fill_time < wm->active_time)
|
|
|
|
return latency;
|
|
|
|
else
|
|
|
|
return latency + (line_fill_time - wm->active_time);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_average_bandwidth_vs_dram_bandwidth_for_display - check
|
|
|
|
* average and available dram bandwidth
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Check if the display average bandwidth fits in the display
|
|
|
|
* dram bandwidth (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns true if the display fits, false if not.
|
|
|
|
*/
|
|
|
|
static bool dce_v10_0_average_bandwidth_vs_dram_bandwidth_for_display(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
if (dce_v10_0_average_bandwidth(wm) <=
|
|
|
|
(dce_v10_0_dram_bandwidth_for_display(wm) / wm->num_heads))
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_average_bandwidth_vs_available_bandwidth - check
|
|
|
|
* average and available bandwidth
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Check if the display average bandwidth fits in the display
|
|
|
|
* available bandwidth (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns true if the display fits, false if not.
|
|
|
|
*/
|
|
|
|
static bool dce_v10_0_average_bandwidth_vs_available_bandwidth(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
if (dce_v10_0_average_bandwidth(wm) <=
|
|
|
|
(dce_v10_0_available_bandwidth(wm) / wm->num_heads))
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_check_latency_hiding - check latency hiding
|
|
|
|
*
|
|
|
|
* @wm: watermark calculation data
|
|
|
|
*
|
|
|
|
* Check latency hiding (CIK).
|
|
|
|
* Used for display watermark bandwidth calculations
|
|
|
|
* Returns true if the display fits, false if not.
|
|
|
|
*/
|
|
|
|
static bool dce_v10_0_check_latency_hiding(struct dce10_wm_params *wm)
|
|
|
|
{
|
|
|
|
u32 lb_partitions = wm->lb_size / wm->src_width;
|
|
|
|
u32 line_time = wm->active_time + wm->blank_time;
|
|
|
|
u32 latency_tolerant_lines;
|
|
|
|
u32 latency_hiding;
|
|
|
|
fixed20_12 a;
|
|
|
|
|
|
|
|
a.full = dfixed_const(1);
|
|
|
|
if (wm->vsc.full > a.full)
|
|
|
|
latency_tolerant_lines = 1;
|
|
|
|
else {
|
|
|
|
if (lb_partitions <= (wm->vtaps + 1))
|
|
|
|
latency_tolerant_lines = 1;
|
|
|
|
else
|
|
|
|
latency_tolerant_lines = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
latency_hiding = (latency_tolerant_lines * line_time + wm->blank_time);
|
|
|
|
|
|
|
|
if (dce_v10_0_latency_watermark(wm) <= latency_hiding)
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_program_watermarks - program display watermarks
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
* @amdgpu_crtc: the selected display controller
|
|
|
|
* @lb_size: line buffer size
|
|
|
|
* @num_heads: number of display controllers in use
|
|
|
|
*
|
|
|
|
* Calculate and program the display watermarks for the
|
|
|
|
* selected display controller (CIK).
|
|
|
|
*/
|
|
|
|
static void dce_v10_0_program_watermarks(struct amdgpu_device *adev,
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc,
|
|
|
|
u32 lb_size, u32 num_heads)
|
|
|
|
{
|
|
|
|
struct drm_display_mode *mode = &amdgpu_crtc->base.mode;
|
|
|
|
struct dce10_wm_params wm_low, wm_high;
|
2017-03-29 20:09:11 +00:00
|
|
|
u32 active_time;
|
2015-04-20 21:31:14 +00:00
|
|
|
u32 line_time = 0;
|
|
|
|
u32 latency_watermark_a = 0, latency_watermark_b = 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
|
|
|
u32 tmp, wm_mask, lb_vblank_lead_lines = 0;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
if (amdgpu_crtc->base.enabled && num_heads && mode) {
|
2017-06-13 05:17:10 +00:00
|
|
|
active_time = (u32) div_u64((u64)mode->crtc_hdisplay * 1000000,
|
|
|
|
(u32)mode->clock);
|
|
|
|
line_time = (u32) div_u64((u64)mode->crtc_htotal * 1000000,
|
|
|
|
(u32)mode->clock);
|
|
|
|
line_time = min(line_time, (u32)65535);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
/* watermark for high clocks */
|
|
|
|
if (adev->pm.dpm_enabled) {
|
|
|
|
wm_high.yclk =
|
|
|
|
amdgpu_dpm_get_mclk(adev, false) * 10;
|
|
|
|
wm_high.sclk =
|
|
|
|
amdgpu_dpm_get_sclk(adev, false) * 10;
|
|
|
|
} else {
|
|
|
|
wm_high.yclk = adev->pm.current_mclk * 10;
|
|
|
|
wm_high.sclk = adev->pm.current_sclk * 10;
|
|
|
|
}
|
|
|
|
|
|
|
|
wm_high.disp_clk = mode->clock;
|
|
|
|
wm_high.src_width = mode->crtc_hdisplay;
|
2017-03-29 20:09:11 +00:00
|
|
|
wm_high.active_time = active_time;
|
2015-04-20 21:31:14 +00:00
|
|
|
wm_high.blank_time = line_time - wm_high.active_time;
|
|
|
|
wm_high.interlaced = false;
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
|
|
|
wm_high.interlaced = true;
|
|
|
|
wm_high.vsc = amdgpu_crtc->vsc;
|
|
|
|
wm_high.vtaps = 1;
|
|
|
|
if (amdgpu_crtc->rmx_type != RMX_OFF)
|
|
|
|
wm_high.vtaps = 2;
|
|
|
|
wm_high.bytes_per_pixel = 4; /* XXX: get this from fb config */
|
|
|
|
wm_high.lb_size = lb_size;
|
|
|
|
wm_high.dram_channels = cik_get_number_of_dram_channels(adev);
|
|
|
|
wm_high.num_heads = num_heads;
|
|
|
|
|
|
|
|
/* set for high clocks */
|
|
|
|
latency_watermark_a = min(dce_v10_0_latency_watermark(&wm_high), (u32)65535);
|
|
|
|
|
|
|
|
/* possibly force display priority to high */
|
|
|
|
/* should really do this at mode validation time... */
|
|
|
|
if (!dce_v10_0_average_bandwidth_vs_dram_bandwidth_for_display(&wm_high) ||
|
|
|
|
!dce_v10_0_average_bandwidth_vs_available_bandwidth(&wm_high) ||
|
|
|
|
!dce_v10_0_check_latency_hiding(&wm_high) ||
|
|
|
|
(adev->mode_info.disp_priority == 2)) {
|
|
|
|
DRM_DEBUG_KMS("force priority to high\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* watermark for low clocks */
|
|
|
|
if (adev->pm.dpm_enabled) {
|
|
|
|
wm_low.yclk =
|
|
|
|
amdgpu_dpm_get_mclk(adev, true) * 10;
|
|
|
|
wm_low.sclk =
|
|
|
|
amdgpu_dpm_get_sclk(adev, true) * 10;
|
|
|
|
} else {
|
|
|
|
wm_low.yclk = adev->pm.current_mclk * 10;
|
|
|
|
wm_low.sclk = adev->pm.current_sclk * 10;
|
|
|
|
}
|
|
|
|
|
|
|
|
wm_low.disp_clk = mode->clock;
|
|
|
|
wm_low.src_width = mode->crtc_hdisplay;
|
2017-03-29 20:09:11 +00:00
|
|
|
wm_low.active_time = active_time;
|
2015-04-20 21:31:14 +00:00
|
|
|
wm_low.blank_time = line_time - wm_low.active_time;
|
|
|
|
wm_low.interlaced = false;
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
|
|
|
wm_low.interlaced = true;
|
|
|
|
wm_low.vsc = amdgpu_crtc->vsc;
|
|
|
|
wm_low.vtaps = 1;
|
|
|
|
if (amdgpu_crtc->rmx_type != RMX_OFF)
|
|
|
|
wm_low.vtaps = 2;
|
|
|
|
wm_low.bytes_per_pixel = 4; /* XXX: get this from fb config */
|
|
|
|
wm_low.lb_size = lb_size;
|
|
|
|
wm_low.dram_channels = cik_get_number_of_dram_channels(adev);
|
|
|
|
wm_low.num_heads = num_heads;
|
|
|
|
|
|
|
|
/* set for low clocks */
|
|
|
|
latency_watermark_b = min(dce_v10_0_latency_watermark(&wm_low), (u32)65535);
|
|
|
|
|
|
|
|
/* possibly force display priority to high */
|
|
|
|
/* should really do this at mode validation time... */
|
|
|
|
if (!dce_v10_0_average_bandwidth_vs_dram_bandwidth_for_display(&wm_low) ||
|
|
|
|
!dce_v10_0_average_bandwidth_vs_available_bandwidth(&wm_low) ||
|
|
|
|
!dce_v10_0_check_latency_hiding(&wm_low) ||
|
|
|
|
(adev->mode_info.disp_priority == 2)) {
|
|
|
|
DRM_DEBUG_KMS("force priority to high\n");
|
|
|
|
}
|
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
|
|
|
lb_vblank_lead_lines = DIV_ROUND_UP(lb_size, mode->crtc_hdisplay);
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* select wm A */
|
|
|
|
wm_mask = RREG32(mmDPG_WATERMARK_MASK_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(wm_mask, DPG_WATERMARK_MASK_CONTROL, URGENCY_WATERMARK_MASK, 1);
|
|
|
|
WREG32(mmDPG_WATERMARK_MASK_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
tmp = RREG32(mmDPG_PIPE_URGENCY_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DPG_PIPE_URGENCY_CONTROL, URGENCY_LOW_WATERMARK, latency_watermark_a);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DPG_PIPE_URGENCY_CONTROL, URGENCY_HIGH_WATERMARK, line_time);
|
|
|
|
WREG32(mmDPG_PIPE_URGENCY_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
/* select wm B */
|
|
|
|
tmp = REG_SET_FIELD(wm_mask, DPG_WATERMARK_MASK_CONTROL, URGENCY_WATERMARK_MASK, 2);
|
|
|
|
WREG32(mmDPG_WATERMARK_MASK_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
tmp = RREG32(mmDPG_PIPE_URGENCY_CONTROL + amdgpu_crtc->crtc_offset);
|
2015-09-03 14:19:03 +00:00
|
|
|
tmp = REG_SET_FIELD(tmp, DPG_PIPE_URGENCY_CONTROL, URGENCY_LOW_WATERMARK, latency_watermark_b);
|
2015-04-20 21:31:14 +00:00
|
|
|
tmp = REG_SET_FIELD(tmp, DPG_PIPE_URGENCY_CONTROL, URGENCY_HIGH_WATERMARK, line_time);
|
|
|
|
WREG32(mmDPG_PIPE_URGENCY_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
/* restore original selection */
|
|
|
|
WREG32(mmDPG_WATERMARK_MASK_CONTROL + amdgpu_crtc->crtc_offset, wm_mask);
|
|
|
|
|
|
|
|
/* save values for DPM */
|
|
|
|
amdgpu_crtc->line_time = line_time;
|
|
|
|
amdgpu_crtc->wm_high = latency_watermark_a;
|
|
|
|
amdgpu_crtc->wm_low = latency_watermark_b;
|
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
|
|
|
/* Save number of lines the linebuffer leads before the scanout */
|
|
|
|
amdgpu_crtc->lb_vblank_lead_lines = lb_vblank_lead_lines;
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_bandwidth_update - program display watermarks
|
|
|
|
*
|
|
|
|
* @adev: amdgpu_device pointer
|
|
|
|
*
|
|
|
|
* Calculate and program the display watermarks and line
|
|
|
|
* buffer allocation (CIK).
|
|
|
|
*/
|
|
|
|
static void dce_v10_0_bandwidth_update(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
struct drm_display_mode *mode = NULL;
|
|
|
|
u32 num_heads = 0, lb_size;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
amdgpu_update_display_priority(adev);
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.num_crtc; i++) {
|
|
|
|
if (adev->mode_info.crtcs[i]->base.enabled)
|
|
|
|
num_heads++;
|
|
|
|
}
|
|
|
|
for (i = 0; i < adev->mode_info.num_crtc; i++) {
|
|
|
|
mode = &adev->mode_info.crtcs[i]->base.mode;
|
|
|
|
lb_size = dce_v10_0_line_buffer_adjust(adev, adev->mode_info.crtcs[i], mode);
|
|
|
|
dce_v10_0_program_watermarks(adev, adev->mode_info.crtcs[i],
|
|
|
|
lb_size, num_heads);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_audio_get_connected_pins(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
u32 offset, tmp;
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.audio.num_pins; i++) {
|
|
|
|
offset = adev->mode_info.audio.pin[i].offset;
|
|
|
|
tmp = RREG32_AUDIO_ENDPT(offset,
|
|
|
|
ixAZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_CONFIGURATION_DEFAULT);
|
|
|
|
if (((tmp &
|
|
|
|
AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_CONFIGURATION_DEFAULT__PORT_CONNECTIVITY_MASK) >>
|
|
|
|
AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_CONFIGURATION_DEFAULT__PORT_CONNECTIVITY__SHIFT) == 1)
|
|
|
|
adev->mode_info.audio.pin[i].connected = false;
|
|
|
|
else
|
|
|
|
adev->mode_info.audio.pin[i].connected = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct amdgpu_audio_pin *dce_v10_0_audio_get_pin(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
dce_v10_0_audio_get_connected_pins(adev);
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.audio.num_pins; i++) {
|
|
|
|
if (adev->mode_info.audio.pin[i].connected)
|
|
|
|
return &adev->mode_info.audio.pin[i];
|
|
|
|
}
|
|
|
|
DRM_ERROR("No connected audio pins found!\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_afmt_audio_select_pin(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct amdgpu_device *adev = encoder->dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
if (!dig || !dig->afmt || !dig->afmt->pin)
|
|
|
|
return;
|
|
|
|
|
|
|
|
tmp = RREG32(mmAFMT_AUDIO_SRC_CONTROL + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_AUDIO_SRC_CONTROL, AFMT_AUDIO_SRC_SELECT, dig->afmt->pin->id);
|
|
|
|
WREG32(mmAFMT_AUDIO_SRC_CONTROL + dig->afmt->offset, tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_audio_write_latency_fields(struct drm_encoder *encoder,
|
|
|
|
struct drm_display_mode *mode)
|
|
|
|
{
|
|
|
|
struct amdgpu_device *adev = encoder->dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
struct drm_connector *connector;
|
|
|
|
struct amdgpu_connector *amdgpu_connector = NULL;
|
|
|
|
u32 tmp;
|
|
|
|
int interlace = 0;
|
|
|
|
|
|
|
|
if (!dig || !dig->afmt || !dig->afmt->pin)
|
|
|
|
return;
|
|
|
|
|
|
|
|
list_for_each_entry(connector, &encoder->dev->mode_config.connector_list, head) {
|
|
|
|
if (connector->encoder == encoder) {
|
|
|
|
amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!amdgpu_connector) {
|
|
|
|
DRM_ERROR("Couldn't find encoder's connector\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
|
|
|
interlace = 1;
|
|
|
|
if (connector->latency_present[interlace]) {
|
|
|
|
tmp = REG_SET_FIELD(0, AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC,
|
|
|
|
VIDEO_LIPSYNC, connector->video_latency[interlace]);
|
|
|
|
tmp = REG_SET_FIELD(0, AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC,
|
|
|
|
AUDIO_LIPSYNC, connector->audio_latency[interlace]);
|
|
|
|
} else {
|
|
|
|
tmp = REG_SET_FIELD(0, AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC,
|
|
|
|
VIDEO_LIPSYNC, 0);
|
|
|
|
tmp = REG_SET_FIELD(0, AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC,
|
|
|
|
AUDIO_LIPSYNC, 0);
|
|
|
|
}
|
|
|
|
WREG32_AUDIO_ENDPT(dig->afmt->pin->offset,
|
|
|
|
ixAZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC, tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_audio_write_speaker_allocation(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct amdgpu_device *adev = encoder->dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
struct drm_connector *connector;
|
|
|
|
struct amdgpu_connector *amdgpu_connector = NULL;
|
|
|
|
u32 tmp;
|
|
|
|
u8 *sadb = NULL;
|
|
|
|
int sad_count;
|
|
|
|
|
|
|
|
if (!dig || !dig->afmt || !dig->afmt->pin)
|
|
|
|
return;
|
|
|
|
|
|
|
|
list_for_each_entry(connector, &encoder->dev->mode_config.connector_list, head) {
|
|
|
|
if (connector->encoder == encoder) {
|
|
|
|
amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!amdgpu_connector) {
|
|
|
|
DRM_ERROR("Couldn't find encoder's connector\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sad_count = drm_edid_to_speaker_allocation(amdgpu_connector_edid(connector), &sadb);
|
|
|
|
if (sad_count < 0) {
|
|
|
|
DRM_ERROR("Couldn't read Speaker Allocation Data Block: %d\n", sad_count);
|
|
|
|
sad_count = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* program the speaker allocation */
|
|
|
|
tmp = RREG32_AUDIO_ENDPT(dig->afmt->pin->offset,
|
|
|
|
ixAZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER,
|
|
|
|
DP_CONNECTION, 0);
|
|
|
|
/* set HDMI mode */
|
|
|
|
tmp = REG_SET_FIELD(tmp, AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER,
|
|
|
|
HDMI_CONNECTION, 1);
|
|
|
|
if (sad_count)
|
|
|
|
tmp = REG_SET_FIELD(tmp, AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER,
|
|
|
|
SPEAKER_ALLOCATION, sadb[0]);
|
|
|
|
else
|
|
|
|
tmp = REG_SET_FIELD(tmp, AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER,
|
|
|
|
SPEAKER_ALLOCATION, 5); /* stereo */
|
|
|
|
WREG32_AUDIO_ENDPT(dig->afmt->pin->offset,
|
|
|
|
ixAZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, tmp);
|
|
|
|
|
|
|
|
kfree(sadb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_audio_write_sad_regs(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct amdgpu_device *adev = encoder->dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
struct drm_connector *connector;
|
|
|
|
struct amdgpu_connector *amdgpu_connector = NULL;
|
|
|
|
struct cea_sad *sads;
|
|
|
|
int i, sad_count;
|
|
|
|
|
|
|
|
static const u16 eld_reg_to_type[][2] = {
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0, HDMI_AUDIO_CODING_TYPE_PCM },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR1, HDMI_AUDIO_CODING_TYPE_AC3 },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR2, HDMI_AUDIO_CODING_TYPE_MPEG1 },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR3, HDMI_AUDIO_CODING_TYPE_MP3 },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR4, HDMI_AUDIO_CODING_TYPE_MPEG2 },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR5, HDMI_AUDIO_CODING_TYPE_AAC_LC },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR6, HDMI_AUDIO_CODING_TYPE_DTS },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR7, HDMI_AUDIO_CODING_TYPE_ATRAC },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR9, HDMI_AUDIO_CODING_TYPE_EAC3 },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR10, HDMI_AUDIO_CODING_TYPE_DTS_HD },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR11, HDMI_AUDIO_CODING_TYPE_MLP },
|
|
|
|
{ ixAZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR13, HDMI_AUDIO_CODING_TYPE_WMA_PRO },
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!dig || !dig->afmt || !dig->afmt->pin)
|
|
|
|
return;
|
|
|
|
|
|
|
|
list_for_each_entry(connector, &encoder->dev->mode_config.connector_list, head) {
|
|
|
|
if (connector->encoder == encoder) {
|
|
|
|
amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!amdgpu_connector) {
|
|
|
|
DRM_ERROR("Couldn't find encoder's connector\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sad_count = drm_edid_to_sad(amdgpu_connector_edid(connector), &sads);
|
|
|
|
if (sad_count <= 0) {
|
|
|
|
DRM_ERROR("Couldn't read SADs: %d\n", sad_count);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
BUG_ON(!sads);
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(eld_reg_to_type); i++) {
|
|
|
|
u32 tmp = 0;
|
|
|
|
u8 stereo_freqs = 0;
|
|
|
|
int max_channels = -1;
|
|
|
|
int j;
|
|
|
|
|
|
|
|
for (j = 0; j < sad_count; j++) {
|
|
|
|
struct cea_sad *sad = &sads[j];
|
|
|
|
|
|
|
|
if (sad->format == eld_reg_to_type[i][1]) {
|
|
|
|
if (sad->channels > max_channels) {
|
|
|
|
tmp = REG_SET_FIELD(tmp, AZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0,
|
|
|
|
MAX_CHANNELS, sad->channels);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0,
|
|
|
|
DESCRIPTOR_BYTE_2, sad->byte2);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0,
|
|
|
|
SUPPORTED_FREQUENCIES, sad->freq);
|
|
|
|
max_channels = sad->channels;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sad->format == HDMI_AUDIO_CODING_TYPE_PCM)
|
|
|
|
stereo_freqs |= sad->freq;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp = REG_SET_FIELD(tmp, AZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0,
|
|
|
|
SUPPORTED_FREQUENCIES_STEREO, stereo_freqs);
|
|
|
|
WREG32_AUDIO_ENDPT(dig->afmt->pin->offset, eld_reg_to_type[i][0], tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(sads);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_audio_enable(struct amdgpu_device *adev,
|
|
|
|
struct amdgpu_audio_pin *pin,
|
|
|
|
bool enable)
|
|
|
|
{
|
|
|
|
if (!pin)
|
|
|
|
return;
|
|
|
|
|
|
|
|
WREG32_AUDIO_ENDPT(pin->offset, ixAZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL,
|
|
|
|
enable ? AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL__AUDIO_ENABLED_MASK : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const u32 pin_offsets[] =
|
|
|
|
{
|
|
|
|
AUD0_REGISTER_OFFSET,
|
|
|
|
AUD1_REGISTER_OFFSET,
|
|
|
|
AUD2_REGISTER_OFFSET,
|
|
|
|
AUD3_REGISTER_OFFSET,
|
|
|
|
AUD4_REGISTER_OFFSET,
|
|
|
|
AUD5_REGISTER_OFFSET,
|
|
|
|
AUD6_REGISTER_OFFSET,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int dce_v10_0_audio_init(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!amdgpu_audio)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
adev->mode_info.audio.enabled = true;
|
|
|
|
|
|
|
|
adev->mode_info.audio.num_pins = 7;
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.audio.num_pins; i++) {
|
|
|
|
adev->mode_info.audio.pin[i].channels = -1;
|
|
|
|
adev->mode_info.audio.pin[i].rate = -1;
|
|
|
|
adev->mode_info.audio.pin[i].bits_per_sample = -1;
|
|
|
|
adev->mode_info.audio.pin[i].status_bits = 0;
|
|
|
|
adev->mode_info.audio.pin[i].category_code = 0;
|
|
|
|
adev->mode_info.audio.pin[i].connected = false;
|
|
|
|
adev->mode_info.audio.pin[i].offset = pin_offsets[i];
|
|
|
|
adev->mode_info.audio.pin[i].id = i;
|
|
|
|
/* disable audio. it will be set up later */
|
|
|
|
/* XXX remove once we switch to ip funcs */
|
|
|
|
dce_v10_0_audio_enable(adev, &adev->mode_info.audio.pin[i], false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_audio_fini(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2016-03-03 14:20:10 +00:00
|
|
|
if (!amdgpu_audio)
|
|
|
|
return;
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
if (!adev->mode_info.audio.enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.audio.num_pins; i++)
|
|
|
|
dce_v10_0_audio_enable(adev, &adev->mode_info.audio.pin[i], false);
|
|
|
|
|
|
|
|
adev->mode_info.audio.enabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* update the N and CTS parameters for a given pixel clock rate
|
|
|
|
*/
|
|
|
|
static void dce_v10_0_afmt_update_ACR(struct drm_encoder *encoder, uint32_t clock)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = encoder->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_afmt_acr acr = amdgpu_afmt_acr(clock);
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_ACR_32_0 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_ACR_32_0, HDMI_ACR_CTS_32, acr.cts_32khz);
|
|
|
|
WREG32(mmHDMI_ACR_32_0 + dig->afmt->offset, tmp);
|
|
|
|
tmp = RREG32(mmHDMI_ACR_32_1 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_ACR_32_1, HDMI_ACR_N_32, acr.n_32khz);
|
|
|
|
WREG32(mmHDMI_ACR_32_1 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_ACR_44_0 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_ACR_44_0, HDMI_ACR_CTS_44, acr.cts_44_1khz);
|
|
|
|
WREG32(mmHDMI_ACR_44_0 + dig->afmt->offset, tmp);
|
|
|
|
tmp = RREG32(mmHDMI_ACR_44_1 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_ACR_44_1, HDMI_ACR_N_44, acr.n_44_1khz);
|
|
|
|
WREG32(mmHDMI_ACR_44_1 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_ACR_48_0 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_ACR_48_0, HDMI_ACR_CTS_48, acr.cts_48khz);
|
|
|
|
WREG32(mmHDMI_ACR_48_0 + dig->afmt->offset, tmp);
|
|
|
|
tmp = RREG32(mmHDMI_ACR_48_1 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_ACR_48_1, HDMI_ACR_N_48, acr.n_48khz);
|
|
|
|
WREG32(mmHDMI_ACR_48_1 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* build a HDMI Video Info Frame
|
|
|
|
*/
|
|
|
|
static void dce_v10_0_afmt_update_avi_infoframe(struct drm_encoder *encoder,
|
|
|
|
void *buffer, size_t size)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = encoder->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
uint8_t *frame = buffer + 3;
|
|
|
|
uint8_t *header = buffer;
|
|
|
|
|
|
|
|
WREG32(mmAFMT_AVI_INFO0 + dig->afmt->offset,
|
|
|
|
frame[0x0] | (frame[0x1] << 8) | (frame[0x2] << 16) | (frame[0x3] << 24));
|
|
|
|
WREG32(mmAFMT_AVI_INFO1 + dig->afmt->offset,
|
|
|
|
frame[0x4] | (frame[0x5] << 8) | (frame[0x6] << 16) | (frame[0x7] << 24));
|
|
|
|
WREG32(mmAFMT_AVI_INFO2 + dig->afmt->offset,
|
|
|
|
frame[0x8] | (frame[0x9] << 8) | (frame[0xA] << 16) | (frame[0xB] << 24));
|
|
|
|
WREG32(mmAFMT_AVI_INFO3 + dig->afmt->offset,
|
|
|
|
frame[0xC] | (frame[0xD] << 8) | (header[1] << 24));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_audio_set_dto(struct drm_encoder *encoder, u32 clock)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = encoder->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(encoder->crtc);
|
|
|
|
u32 dto_phase = 24 * 1000;
|
|
|
|
u32 dto_modulo = clock;
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
if (!dig || !dig->afmt)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* XXX two dtos; generally use dto0 for hdmi */
|
|
|
|
/* Express [24MHz / target pixel clock] as an exact rational
|
|
|
|
* number (coefficient of two integer numbers. DCCG_AUDIO_DTOx_PHASE
|
|
|
|
* is the numerator, DCCG_AUDIO_DTOx_MODULE is the denominator
|
|
|
|
*/
|
|
|
|
tmp = RREG32(mmDCCG_AUDIO_DTO_SOURCE);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DCCG_AUDIO_DTO_SOURCE, DCCG_AUDIO_DTO0_SOURCE_SEL,
|
|
|
|
amdgpu_crtc->crtc_id);
|
|
|
|
WREG32(mmDCCG_AUDIO_DTO_SOURCE, tmp);
|
|
|
|
WREG32(mmDCCG_AUDIO_DTO0_PHASE, dto_phase);
|
|
|
|
WREG32(mmDCCG_AUDIO_DTO0_MODULE, dto_modulo);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* update the info frames with the data from the current display mode
|
|
|
|
*/
|
|
|
|
static void dce_v10_0_afmt_setmode(struct drm_encoder *encoder,
|
|
|
|
struct drm_display_mode *mode)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = encoder->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
struct drm_connector *connector = amdgpu_get_connector_for_encoder(encoder);
|
|
|
|
u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE];
|
|
|
|
struct hdmi_avi_infoframe frame;
|
|
|
|
ssize_t err;
|
|
|
|
u32 tmp;
|
|
|
|
int bpc = 8;
|
|
|
|
|
|
|
|
if (!dig || !dig->afmt)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Silent, r600_hdmi_enable will raise WARN for us */
|
|
|
|
if (!dig->afmt->enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* hdmi deep color mode general control packets setup, if bpc > 8 */
|
|
|
|
if (encoder->crtc) {
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(encoder->crtc);
|
|
|
|
bpc = amdgpu_crtc->bpc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* disable audio prior to setting up hw */
|
|
|
|
dig->afmt->pin = dce_v10_0_audio_get_pin(adev);
|
|
|
|
dce_v10_0_audio_enable(adev, dig->afmt->pin, false);
|
|
|
|
|
|
|
|
dce_v10_0_audio_set_dto(encoder, mode->clock);
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_VBI_PACKET_CONTROL + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_VBI_PACKET_CONTROL, HDMI_NULL_SEND, 1);
|
|
|
|
WREG32(mmHDMI_VBI_PACKET_CONTROL + dig->afmt->offset, tmp); /* send null packets when required */
|
|
|
|
|
|
|
|
WREG32(mmAFMT_AUDIO_CRC_CONTROL + dig->afmt->offset, 0x1000);
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_CONTROL + dig->afmt->offset);
|
|
|
|
switch (bpc) {
|
|
|
|
case 0:
|
|
|
|
case 6:
|
|
|
|
case 8:
|
|
|
|
case 16:
|
|
|
|
default:
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_CONTROL, HDMI_DEEP_COLOR_ENABLE, 0);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_CONTROL, HDMI_DEEP_COLOR_DEPTH, 0);
|
|
|
|
DRM_DEBUG("%s: Disabling hdmi deep color for %d bpc.\n",
|
|
|
|
connector->name, bpc);
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_CONTROL, HDMI_DEEP_COLOR_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_CONTROL, HDMI_DEEP_COLOR_DEPTH, 1);
|
|
|
|
DRM_DEBUG("%s: Enabling hdmi deep color 30 for 10 bpc.\n",
|
|
|
|
connector->name);
|
|
|
|
break;
|
|
|
|
case 12:
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_CONTROL, HDMI_DEEP_COLOR_ENABLE, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_CONTROL, HDMI_DEEP_COLOR_DEPTH, 2);
|
|
|
|
DRM_DEBUG("%s: Enabling hdmi deep color 36 for 12 bpc.\n",
|
|
|
|
connector->name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
WREG32(mmHDMI_CONTROL + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_VBI_PACKET_CONTROL + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_VBI_PACKET_CONTROL, HDMI_NULL_SEND, 1); /* send null packets when required */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_VBI_PACKET_CONTROL, HDMI_GC_SEND, 1); /* send general control packets */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_VBI_PACKET_CONTROL, HDMI_GC_CONT, 1); /* send general control packets every frame */
|
|
|
|
WREG32(mmHDMI_VBI_PACKET_CONTROL + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_INFOFRAME_CONTROL0 + dig->afmt->offset);
|
|
|
|
/* enable audio info frames (frames won't be set until audio is enabled) */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_INFOFRAME_CONTROL0, HDMI_AUDIO_INFO_SEND, 1);
|
|
|
|
/* required for audio info values to be updated */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_INFOFRAME_CONTROL0, HDMI_AUDIO_INFO_CONT, 1);
|
|
|
|
WREG32(mmHDMI_INFOFRAME_CONTROL0 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmAFMT_INFOFRAME_CONTROL0 + dig->afmt->offset);
|
|
|
|
/* required for audio info values to be updated */
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_INFOFRAME_CONTROL0, AFMT_AUDIO_INFO_UPDATE, 1);
|
|
|
|
WREG32(mmAFMT_INFOFRAME_CONTROL0 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_INFOFRAME_CONTROL1 + dig->afmt->offset);
|
|
|
|
/* anything other than 0 */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_INFOFRAME_CONTROL1, HDMI_AUDIO_INFO_LINE, 2);
|
|
|
|
WREG32(mmHDMI_INFOFRAME_CONTROL1 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
WREG32(mmHDMI_GC + dig->afmt->offset, 0); /* unset HDMI_GC_AVMUTE */
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_AUDIO_PACKET_CONTROL + dig->afmt->offset);
|
|
|
|
/* set the default audio delay */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_AUDIO_PACKET_CONTROL, HDMI_AUDIO_DELAY_EN, 1);
|
|
|
|
/* should be suffient for all audio modes and small enough for all hblanks */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_AUDIO_PACKET_CONTROL, HDMI_AUDIO_PACKETS_PER_LINE, 3);
|
|
|
|
WREG32(mmHDMI_AUDIO_PACKET_CONTROL + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmAFMT_AUDIO_PACKET_CONTROL + dig->afmt->offset);
|
|
|
|
/* allow 60958 channel status fields to be updated */
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_AUDIO_PACKET_CONTROL, AFMT_60958_CS_UPDATE, 1);
|
|
|
|
WREG32(mmAFMT_AUDIO_PACKET_CONTROL + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_ACR_PACKET_CONTROL + dig->afmt->offset);
|
|
|
|
if (bpc > 8)
|
|
|
|
/* clear SW CTS value */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_ACR_PACKET_CONTROL, HDMI_ACR_SOURCE, 0);
|
|
|
|
else
|
|
|
|
/* select SW CTS value */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_ACR_PACKET_CONTROL, HDMI_ACR_SOURCE, 1);
|
|
|
|
/* allow hw to sent ACR packets when required */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_ACR_PACKET_CONTROL, HDMI_ACR_AUTO_SEND, 1);
|
|
|
|
WREG32(mmHDMI_ACR_PACKET_CONTROL + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
dce_v10_0_afmt_update_ACR(encoder, mode->clock);
|
|
|
|
|
|
|
|
tmp = RREG32(mmAFMT_60958_0 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_60958_0, AFMT_60958_CS_CHANNEL_NUMBER_L, 1);
|
|
|
|
WREG32(mmAFMT_60958_0 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmAFMT_60958_1 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_60958_1, AFMT_60958_CS_CHANNEL_NUMBER_R, 2);
|
|
|
|
WREG32(mmAFMT_60958_1 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmAFMT_60958_2 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_60958_2, AFMT_60958_CS_CHANNEL_NUMBER_2, 3);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_60958_2, AFMT_60958_CS_CHANNEL_NUMBER_3, 4);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_60958_2, AFMT_60958_CS_CHANNEL_NUMBER_4, 5);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_60958_2, AFMT_60958_CS_CHANNEL_NUMBER_5, 6);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_60958_2, AFMT_60958_CS_CHANNEL_NUMBER_6, 7);
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_60958_2, AFMT_60958_CS_CHANNEL_NUMBER_7, 8);
|
|
|
|
WREG32(mmAFMT_60958_2 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
dce_v10_0_audio_write_speaker_allocation(encoder);
|
|
|
|
|
|
|
|
WREG32(mmAFMT_AUDIO_PACKET_CONTROL2 + dig->afmt->offset,
|
|
|
|
(0xff << AFMT_AUDIO_PACKET_CONTROL2__AFMT_AUDIO_CHANNEL_ENABLE__SHIFT));
|
|
|
|
|
|
|
|
dce_v10_0_afmt_audio_select_pin(encoder);
|
|
|
|
dce_v10_0_audio_write_sad_regs(encoder);
|
|
|
|
dce_v10_0_audio_write_latency_fields(encoder, mode);
|
|
|
|
|
drm: handle HDMI 2.0 VICs in AVI info-frames
HDMI 1.4b support the CEA video modes as per range of CEA-861-D (VIC 1-64).
For any other mode, the VIC filed in AVI infoframes should be 0.
HDMI 2.0 sinks, support video modes range as per CEA-861-F spec, which is
extended to (VIC 1-107).
This patch adds a bool input variable, which indicates if the connected
sink is a HDMI 2.0 sink or not. This will make sure that we don't pass a
HDMI 2.0 VIC to a HDMI 1.4 sink.
This patch touches all drm drivers, who are callers of this function
drm_hdmi_avi_infoframe_from_display_mode but to make sure there is
no change in current behavior, is_hdmi2 is kept as false.
In case of I915 driver, this patch:
- checks if the connected display is HDMI 2.0.
- HDMI infoframes carry one of this two type of information:
- VIC for 4K modes for HDMI 1.4 sinks
- S3D information for S3D modes
As CEA-861-F has already defined VICs for 4K videomodes, this
patch doesn't allow sending HDMI infoframes for HDMI 2.0 sinks,
until the mode is 3D.
Cc: Ville Syrjala <ville.syrjala@linux.intel.com>
Cc: Jose Abreu <jose.abreu@synopsys.com>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Alex Deucher <alexander.deucher@amd.com>
Cc: Daniel Vetter <daniel.vetter@intel.com>
PS: This patch touches a few lines in few files, which were
already above 80 char, so checkpatch gives 80 char warning again.
- gpu/drm/omapdrm/omap_encoder.c
- gpu/drm/i915/intel_sdvo.c
V2: Rebase, Added r-b from Andrzej
V3: Addressed review comment from Ville:
- Do not send VICs in both AVI-IF and HDMI-IF
send only one of it.
V4: Rebase
V5: Added r-b from Neil.
Addressed review comments from Ville
- Do not block HDMI vendor IF, instead check for VIC while
handling AVI infoframes
V6: Rebase
V7: Rebase
Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Signed-off-by: Shashank Sharma <shashank.sharma@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/1499960000-9232-2-git-send-email-shashank.sharma@intel.com
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
2017-07-13 15:33:07 +00:00
|
|
|
err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, false);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (err < 0) {
|
|
|
|
DRM_ERROR("failed to setup AVI infoframe: %zd\n", err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
|
|
|
|
if (err < 0) {
|
|
|
|
DRM_ERROR("failed to pack AVI infoframe: %zd\n", err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dce_v10_0_afmt_update_avi_infoframe(encoder, buffer, sizeof(buffer));
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_INFOFRAME_CONTROL0 + dig->afmt->offset);
|
|
|
|
/* enable AVI info frames */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_INFOFRAME_CONTROL0, HDMI_AVI_INFO_SEND, 1);
|
|
|
|
/* required for audio info values to be updated */
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_INFOFRAME_CONTROL0, HDMI_AVI_INFO_CONT, 1);
|
|
|
|
WREG32(mmHDMI_INFOFRAME_CONTROL0 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmHDMI_INFOFRAME_CONTROL1 + dig->afmt->offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, HDMI_INFOFRAME_CONTROL1, HDMI_AVI_INFO_LINE, 2);
|
|
|
|
WREG32(mmHDMI_INFOFRAME_CONTROL1 + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmAFMT_AUDIO_PACKET_CONTROL + dig->afmt->offset);
|
|
|
|
/* send audio packets */
|
|
|
|
tmp = REG_SET_FIELD(tmp, AFMT_AUDIO_PACKET_CONTROL, AFMT_AUDIO_SAMPLE_SEND, 1);
|
|
|
|
WREG32(mmAFMT_AUDIO_PACKET_CONTROL + dig->afmt->offset, tmp);
|
|
|
|
|
|
|
|
WREG32(mmAFMT_RAMP_CONTROL0 + dig->afmt->offset, 0x00FFFFFF);
|
|
|
|
WREG32(mmAFMT_RAMP_CONTROL1 + dig->afmt->offset, 0x007FFFFF);
|
|
|
|
WREG32(mmAFMT_RAMP_CONTROL2 + dig->afmt->offset, 0x00000001);
|
|
|
|
WREG32(mmAFMT_RAMP_CONTROL3 + dig->afmt->offset, 0x00000001);
|
|
|
|
|
|
|
|
/* enable audio after to setting up hw */
|
|
|
|
dce_v10_0_audio_enable(adev, dig->afmt->pin, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_afmt_enable(struct drm_encoder *encoder, bool enable)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = encoder->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
|
|
|
|
if (!dig || !dig->afmt)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Silent, r600_hdmi_enable will raise WARN for us */
|
|
|
|
if (enable && dig->afmt->enabled)
|
|
|
|
return;
|
|
|
|
if (!enable && !dig->afmt->enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!enable && dig->afmt->pin) {
|
|
|
|
dce_v10_0_audio_enable(adev, dig->afmt->pin, false);
|
|
|
|
dig->afmt->pin = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
dig->afmt->enabled = enable;
|
|
|
|
|
|
|
|
DRM_DEBUG("%sabling AFMT interface @ 0x%04X for encoder 0x%x\n",
|
|
|
|
enable ? "En" : "Dis", dig->afmt->offset, amdgpu_encoder->encoder_id);
|
|
|
|
}
|
|
|
|
|
2016-03-03 14:23:48 +00:00
|
|
|
static int dce_v10_0_afmt_init(struct amdgpu_device *adev)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.num_dig; i++)
|
|
|
|
adev->mode_info.afmt[i] = NULL;
|
|
|
|
|
|
|
|
/* DCE10 has audio blocks tied to DIG encoders */
|
|
|
|
for (i = 0; i < adev->mode_info.num_dig; i++) {
|
|
|
|
adev->mode_info.afmt[i] = kzalloc(sizeof(struct amdgpu_afmt), GFP_KERNEL);
|
|
|
|
if (adev->mode_info.afmt[i]) {
|
|
|
|
adev->mode_info.afmt[i]->offset = dig_offsets[i];
|
|
|
|
adev->mode_info.afmt[i]->id = i;
|
2016-03-03 14:23:48 +00:00
|
|
|
} else {
|
|
|
|
int j;
|
|
|
|
for (j = 0; j < i; j++) {
|
|
|
|
kfree(adev->mode_info.afmt[j]);
|
|
|
|
adev->mode_info.afmt[j] = NULL;
|
|
|
|
}
|
|
|
|
return -ENOMEM;
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
}
|
2016-03-03 14:23:48 +00:00
|
|
|
return 0;
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_afmt_fini(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.num_dig; i++) {
|
|
|
|
kfree(adev->mode_info.afmt[i]);
|
|
|
|
adev->mode_info.afmt[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const u32 vga_control_regs[6] =
|
|
|
|
{
|
|
|
|
mmD1VGA_CONTROL,
|
|
|
|
mmD2VGA_CONTROL,
|
|
|
|
mmD3VGA_CONTROL,
|
|
|
|
mmD4VGA_CONTROL,
|
|
|
|
mmD5VGA_CONTROL,
|
|
|
|
mmD6VGA_CONTROL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void dce_v10_0_vga_enable(struct drm_crtc *crtc, bool enable)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
u32 vga_control;
|
|
|
|
|
|
|
|
vga_control = RREG32(vga_control_regs[amdgpu_crtc->crtc_id]) & ~1;
|
|
|
|
if (enable)
|
|
|
|
WREG32(vga_control_regs[amdgpu_crtc->crtc_id], vga_control | 1);
|
|
|
|
else
|
|
|
|
WREG32(vga_control_regs[amdgpu_crtc->crtc_id], vga_control);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_grph_enable(struct drm_crtc *crtc, bool enable)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
|
|
|
|
if (enable)
|
|
|
|
WREG32(mmGRPH_ENABLE + amdgpu_crtc->crtc_offset, 1);
|
|
|
|
else
|
|
|
|
WREG32(mmGRPH_ENABLE + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_crtc_do_set_base(struct drm_crtc *crtc,
|
|
|
|
struct drm_framebuffer *fb,
|
|
|
|
int x, int y, int atomic)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_framebuffer *amdgpu_fb;
|
|
|
|
struct drm_framebuffer *target_fb;
|
|
|
|
struct drm_gem_object *obj;
|
2016-09-15 13:06:50 +00:00
|
|
|
struct amdgpu_bo *abo;
|
2015-04-20 21:31:14 +00:00
|
|
|
uint64_t fb_location, tiling_flags;
|
|
|
|
uint32_t fb_format, fb_pitch_pixels;
|
|
|
|
u32 fb_swap = REG_SET_FIELD(0, GRPH_SWAP_CNTL, GRPH_ENDIAN_SWAP, ENDIAN_NONE);
|
2015-05-14 21:48:26 +00:00
|
|
|
u32 pipe_config;
|
2015-04-20 21:31:14 +00:00
|
|
|
u32 tmp, viewport_w, viewport_h;
|
|
|
|
int r;
|
|
|
|
bool bypass_lut = false;
|
2016-11-12 01:12:56 +00:00
|
|
|
struct drm_format_name_buf format_name;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
/* no fb bound */
|
|
|
|
if (!atomic && !crtc->primary->fb) {
|
|
|
|
DRM_DEBUG_KMS("No FB bound\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (atomic) {
|
|
|
|
amdgpu_fb = to_amdgpu_framebuffer(fb);
|
|
|
|
target_fb = fb;
|
2016-03-03 14:25:05 +00:00
|
|
|
} else {
|
2015-04-20 21:31:14 +00:00
|
|
|
amdgpu_fb = to_amdgpu_framebuffer(crtc->primary->fb);
|
|
|
|
target_fb = crtc->primary->fb;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If atomic, assume fb object is pinned & idle & fenced and
|
|
|
|
* just update base pointers
|
|
|
|
*/
|
|
|
|
obj = amdgpu_fb->obj;
|
2016-09-15 13:06:50 +00:00
|
|
|
abo = gem_to_amdgpu_bo(obj);
|
|
|
|
r = amdgpu_bo_reserve(abo, false);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (unlikely(r != 0))
|
|
|
|
return r;
|
|
|
|
|
2016-03-03 14:25:05 +00:00
|
|
|
if (atomic) {
|
2016-09-15 13:06:50 +00:00
|
|
|
fb_location = amdgpu_bo_gpu_offset(abo);
|
2016-03-03 14:25:05 +00:00
|
|
|
} else {
|
2016-09-15 13:06:50 +00:00
|
|
|
r = amdgpu_bo_pin(abo, AMDGPU_GEM_DOMAIN_VRAM, &fb_location);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (unlikely(r != 0)) {
|
2016-09-15 13:06:50 +00:00
|
|
|
amdgpu_bo_unreserve(abo);
|
2015-04-20 21:31:14 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-15 13:06:50 +00:00
|
|
|
amdgpu_bo_get_tiling_flags(abo, &tiling_flags);
|
|
|
|
amdgpu_bo_unreserve(abo);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
2015-05-14 21:48:26 +00:00
|
|
|
pipe_config = AMDGPU_TILING_GET(tiling_flags, PIPE_CONFIG);
|
|
|
|
|
2016-12-14 21:32:55 +00:00
|
|
|
switch (target_fb->format->format) {
|
2015-04-20 21:31:14 +00:00
|
|
|
case DRM_FORMAT_C8:
|
|
|
|
fb_format = REG_SET_FIELD(0, GRPH_CONTROL, GRPH_DEPTH, 0);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_FORMAT, 0);
|
|
|
|
break;
|
|
|
|
case DRM_FORMAT_XRGB4444:
|
|
|
|
case DRM_FORMAT_ARGB4444:
|
|
|
|
fb_format = REG_SET_FIELD(0, GRPH_CONTROL, GRPH_DEPTH, 1);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_FORMAT, 2);
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
|
|
fb_swap = REG_SET_FIELD(fb_swap, GRPH_SWAP_CNTL, GRPH_ENDIAN_SWAP,
|
|
|
|
ENDIAN_8IN16);
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
case DRM_FORMAT_XRGB1555:
|
|
|
|
case DRM_FORMAT_ARGB1555:
|
|
|
|
fb_format = REG_SET_FIELD(0, GRPH_CONTROL, GRPH_DEPTH, 1);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_FORMAT, 0);
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
|
|
fb_swap = REG_SET_FIELD(fb_swap, GRPH_SWAP_CNTL, GRPH_ENDIAN_SWAP,
|
|
|
|
ENDIAN_8IN16);
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
case DRM_FORMAT_BGRX5551:
|
|
|
|
case DRM_FORMAT_BGRA5551:
|
|
|
|
fb_format = REG_SET_FIELD(0, GRPH_CONTROL, GRPH_DEPTH, 1);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_FORMAT, 5);
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
|
|
fb_swap = REG_SET_FIELD(fb_swap, GRPH_SWAP_CNTL, GRPH_ENDIAN_SWAP,
|
|
|
|
ENDIAN_8IN16);
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
case DRM_FORMAT_RGB565:
|
|
|
|
fb_format = REG_SET_FIELD(0, GRPH_CONTROL, GRPH_DEPTH, 1);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_FORMAT, 1);
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
|
|
fb_swap = REG_SET_FIELD(fb_swap, GRPH_SWAP_CNTL, GRPH_ENDIAN_SWAP,
|
|
|
|
ENDIAN_8IN16);
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
case DRM_FORMAT_XRGB8888:
|
|
|
|
case DRM_FORMAT_ARGB8888:
|
|
|
|
fb_format = REG_SET_FIELD(0, GRPH_CONTROL, GRPH_DEPTH, 2);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_FORMAT, 0);
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
|
|
fb_swap = REG_SET_FIELD(fb_swap, GRPH_SWAP_CNTL, GRPH_ENDIAN_SWAP,
|
|
|
|
ENDIAN_8IN32);
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
case DRM_FORMAT_XRGB2101010:
|
|
|
|
case DRM_FORMAT_ARGB2101010:
|
|
|
|
fb_format = REG_SET_FIELD(0, GRPH_CONTROL, GRPH_DEPTH, 2);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_FORMAT, 1);
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
|
|
fb_swap = REG_SET_FIELD(fb_swap, GRPH_SWAP_CNTL, GRPH_ENDIAN_SWAP,
|
|
|
|
ENDIAN_8IN32);
|
|
|
|
#endif
|
|
|
|
/* Greater 8 bpc fb needs to bypass hw-lut to retain precision */
|
|
|
|
bypass_lut = true;
|
|
|
|
break;
|
|
|
|
case DRM_FORMAT_BGRX1010102:
|
|
|
|
case DRM_FORMAT_BGRA1010102:
|
|
|
|
fb_format = REG_SET_FIELD(0, GRPH_CONTROL, GRPH_DEPTH, 2);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_FORMAT, 4);
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
|
|
fb_swap = REG_SET_FIELD(fb_swap, GRPH_SWAP_CNTL, GRPH_ENDIAN_SWAP,
|
|
|
|
ENDIAN_8IN32);
|
|
|
|
#endif
|
|
|
|
/* Greater 8 bpc fb needs to bypass hw-lut to retain precision */
|
|
|
|
bypass_lut = true;
|
|
|
|
break;
|
|
|
|
default:
|
2016-11-12 01:12:56 +00:00
|
|
|
DRM_ERROR("Unsupported screen format %s\n",
|
2016-12-14 21:32:55 +00:00
|
|
|
drm_get_format_name(target_fb->format->format, &format_name));
|
2015-04-20 21:31:14 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-05-14 21:48:26 +00:00
|
|
|
if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) == ARRAY_2D_TILED_THIN1) {
|
|
|
|
unsigned bankw, bankh, mtaspect, tile_split, num_banks;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
2015-05-14 21:48:26 +00:00
|
|
|
bankw = AMDGPU_TILING_GET(tiling_flags, BANK_WIDTH);
|
|
|
|
bankh = AMDGPU_TILING_GET(tiling_flags, BANK_HEIGHT);
|
|
|
|
mtaspect = AMDGPU_TILING_GET(tiling_flags, MACRO_TILE_ASPECT);
|
|
|
|
tile_split = AMDGPU_TILING_GET(tiling_flags, TILE_SPLIT);
|
|
|
|
num_banks = AMDGPU_TILING_GET(tiling_flags, NUM_BANKS);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_NUM_BANKS, num_banks);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_ARRAY_MODE,
|
|
|
|
ARRAY_2D_TILED_THIN1);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_TILE_SPLIT,
|
|
|
|
tile_split);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_BANK_WIDTH, bankw);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_BANK_HEIGHT, bankh);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_MACRO_TILE_ASPECT,
|
|
|
|
mtaspect);
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_MICRO_TILE_MODE,
|
|
|
|
ADDR_SURF_MICRO_TILING_DISPLAY);
|
2015-05-14 21:48:26 +00:00
|
|
|
} else if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) == ARRAY_1D_TILED_THIN1) {
|
2015-04-20 21:31:14 +00:00
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_ARRAY_MODE,
|
|
|
|
ARRAY_1D_TILED_THIN1);
|
|
|
|
}
|
|
|
|
|
|
|
|
fb_format = REG_SET_FIELD(fb_format, GRPH_CONTROL, GRPH_PIPE_CONFIG,
|
|
|
|
pipe_config);
|
|
|
|
|
|
|
|
dce_v10_0_vga_enable(crtc, false);
|
|
|
|
|
2016-05-05 20:03:57 +00:00
|
|
|
/* Make sure surface address is updated at vertical blank rather than
|
|
|
|
* horizontal blank
|
|
|
|
*/
|
|
|
|
tmp = RREG32(mmGRPH_FLIP_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, GRPH_FLIP_CONTROL,
|
|
|
|
GRPH_SURFACE_UPDATE_H_RETRACE_EN, 0);
|
|
|
|
WREG32(mmGRPH_FLIP_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
WREG32(mmGRPH_PRIMARY_SURFACE_ADDRESS_HIGH + amdgpu_crtc->crtc_offset,
|
|
|
|
upper_32_bits(fb_location));
|
|
|
|
WREG32(mmGRPH_SECONDARY_SURFACE_ADDRESS_HIGH + amdgpu_crtc->crtc_offset,
|
|
|
|
upper_32_bits(fb_location));
|
|
|
|
WREG32(mmGRPH_PRIMARY_SURFACE_ADDRESS + amdgpu_crtc->crtc_offset,
|
|
|
|
(u32)fb_location & GRPH_PRIMARY_SURFACE_ADDRESS__GRPH_PRIMARY_SURFACE_ADDRESS_MASK);
|
|
|
|
WREG32(mmGRPH_SECONDARY_SURFACE_ADDRESS + amdgpu_crtc->crtc_offset,
|
|
|
|
(u32) fb_location & GRPH_SECONDARY_SURFACE_ADDRESS__GRPH_SECONDARY_SURFACE_ADDRESS_MASK);
|
|
|
|
WREG32(mmGRPH_CONTROL + amdgpu_crtc->crtc_offset, fb_format);
|
|
|
|
WREG32(mmGRPH_SWAP_CNTL + amdgpu_crtc->crtc_offset, fb_swap);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The LUT only has 256 slots for indexing by a 8 bpc fb. Bypass the LUT
|
|
|
|
* for > 8 bpc scanout to avoid truncation of fb indices to 8 msb's, to
|
|
|
|
* retain the full precision throughout the pipeline.
|
|
|
|
*/
|
|
|
|
tmp = RREG32(mmGRPH_LUT_10BIT_BYPASS + amdgpu_crtc->crtc_offset);
|
|
|
|
if (bypass_lut)
|
|
|
|
tmp = REG_SET_FIELD(tmp, GRPH_LUT_10BIT_BYPASS, GRPH_LUT_10BIT_BYPASS_EN, 1);
|
|
|
|
else
|
|
|
|
tmp = REG_SET_FIELD(tmp, GRPH_LUT_10BIT_BYPASS, GRPH_LUT_10BIT_BYPASS_EN, 0);
|
|
|
|
WREG32(mmGRPH_LUT_10BIT_BYPASS + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
if (bypass_lut)
|
|
|
|
DRM_DEBUG_KMS("Bypassing hardware LUT due to 10 bit fb scanout.\n");
|
|
|
|
|
|
|
|
WREG32(mmGRPH_SURFACE_OFFSET_X + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
WREG32(mmGRPH_SURFACE_OFFSET_Y + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
WREG32(mmGRPH_X_START + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
WREG32(mmGRPH_Y_START + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
WREG32(mmGRPH_X_END + amdgpu_crtc->crtc_offset, target_fb->width);
|
|
|
|
WREG32(mmGRPH_Y_END + amdgpu_crtc->crtc_offset, target_fb->height);
|
|
|
|
|
2016-12-14 21:32:20 +00:00
|
|
|
fb_pitch_pixels = target_fb->pitches[0] / target_fb->format->cpp[0];
|
2015-04-20 21:31:14 +00:00
|
|
|
WREG32(mmGRPH_PITCH + amdgpu_crtc->crtc_offset, fb_pitch_pixels);
|
|
|
|
|
|
|
|
dce_v10_0_grph_enable(crtc, true);
|
|
|
|
|
|
|
|
WREG32(mmLB_DESKTOP_HEIGHT + amdgpu_crtc->crtc_offset,
|
|
|
|
target_fb->height);
|
|
|
|
|
|
|
|
x &= ~3;
|
|
|
|
y &= ~1;
|
|
|
|
WREG32(mmVIEWPORT_START + amdgpu_crtc->crtc_offset,
|
|
|
|
(x << 16) | y);
|
|
|
|
viewport_w = crtc->mode.hdisplay;
|
|
|
|
viewport_h = (crtc->mode.vdisplay + 1) & ~1;
|
|
|
|
WREG32(mmVIEWPORT_SIZE + amdgpu_crtc->crtc_offset,
|
|
|
|
(viewport_w << 16) | viewport_h);
|
|
|
|
|
2016-08-04 03:39:38 +00:00
|
|
|
/* set pageflip to happen anywhere in vblank interval */
|
|
|
|
WREG32(mmMASTER_UPDATE_MODE + amdgpu_crtc->crtc_offset, 0);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
if (!atomic && fb && fb != crtc->primary->fb) {
|
|
|
|
amdgpu_fb = to_amdgpu_framebuffer(fb);
|
2016-09-15 13:06:50 +00:00
|
|
|
abo = gem_to_amdgpu_bo(amdgpu_fb->obj);
|
2017-04-28 08:28:14 +00:00
|
|
|
r = amdgpu_bo_reserve(abo, true);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (unlikely(r != 0))
|
|
|
|
return r;
|
2016-09-15 13:06:50 +00:00
|
|
|
amdgpu_bo_unpin(abo);
|
|
|
|
amdgpu_bo_unreserve(abo);
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Bytes per pixel may have changed */
|
|
|
|
dce_v10_0_bandwidth_update(adev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_set_interleave(struct drm_crtc *crtc,
|
|
|
|
struct drm_display_mode *mode)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
tmp = RREG32(mmLB_DATA_FORMAT + amdgpu_crtc->crtc_offset);
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
|
|
|
tmp = REG_SET_FIELD(tmp, LB_DATA_FORMAT, INTERLEAVE_EN, 1);
|
|
|
|
else
|
|
|
|
tmp = REG_SET_FIELD(tmp, LB_DATA_FORMAT, INTERLEAVE_EN, 0);
|
|
|
|
WREG32(mmLB_DATA_FORMAT + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_crtc_load_lut(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
2017-07-13 16:25:28 +00:00
|
|
|
u16 *r, *g, *b;
|
2015-04-20 21:31:14 +00:00
|
|
|
int i;
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
DRM_DEBUG_KMS("%d\n", amdgpu_crtc->crtc_id);
|
|
|
|
|
|
|
|
tmp = RREG32(mmINPUT_CSC_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, INPUT_CSC_CONTROL, INPUT_CSC_GRPH_MODE, 0);
|
|
|
|
tmp = REG_SET_FIELD(tmp, INPUT_CSC_CONTROL, INPUT_CSC_OVL_MODE, 0);
|
|
|
|
WREG32(mmINPUT_CSC_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmPRESCALE_GRPH_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, PRESCALE_GRPH_CONTROL, GRPH_PRESCALE_BYPASS, 1);
|
|
|
|
WREG32(mmPRESCALE_GRPH_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmPRESCALE_OVL_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, PRESCALE_OVL_CONTROL, OVL_PRESCALE_BYPASS, 1);
|
|
|
|
WREG32(mmPRESCALE_OVL_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmINPUT_GAMMA_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, INPUT_GAMMA_CONTROL, GRPH_INPUT_GAMMA_MODE, 0);
|
|
|
|
tmp = REG_SET_FIELD(tmp, INPUT_GAMMA_CONTROL, OVL_INPUT_GAMMA_MODE, 0);
|
|
|
|
WREG32(mmINPUT_GAMMA_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
WREG32(mmDC_LUT_CONTROL + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
|
|
|
|
WREG32(mmDC_LUT_BLACK_OFFSET_BLUE + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
WREG32(mmDC_LUT_BLACK_OFFSET_GREEN + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
WREG32(mmDC_LUT_BLACK_OFFSET_RED + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
|
|
|
|
WREG32(mmDC_LUT_WHITE_OFFSET_BLUE + amdgpu_crtc->crtc_offset, 0xffff);
|
|
|
|
WREG32(mmDC_LUT_WHITE_OFFSET_GREEN + amdgpu_crtc->crtc_offset, 0xffff);
|
|
|
|
WREG32(mmDC_LUT_WHITE_OFFSET_RED + amdgpu_crtc->crtc_offset, 0xffff);
|
|
|
|
|
|
|
|
WREG32(mmDC_LUT_RW_MODE + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
WREG32(mmDC_LUT_WRITE_EN_MASK + amdgpu_crtc->crtc_offset, 0x00000007);
|
|
|
|
|
|
|
|
WREG32(mmDC_LUT_RW_INDEX + amdgpu_crtc->crtc_offset, 0);
|
2017-07-13 16:25:28 +00:00
|
|
|
r = crtc->gamma_store;
|
|
|
|
g = r + crtc->gamma_size;
|
|
|
|
b = g + crtc->gamma_size;
|
2015-04-20 21:31:14 +00:00
|
|
|
for (i = 0; i < 256; i++) {
|
|
|
|
WREG32(mmDC_LUT_30_COLOR + amdgpu_crtc->crtc_offset,
|
2017-07-13 16:25:28 +00:00
|
|
|
((*r++ & 0xffc0) << 14) |
|
|
|
|
((*g++ & 0xffc0) << 4) |
|
|
|
|
(*b++ >> 6));
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tmp = RREG32(mmDEGAMMA_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DEGAMMA_CONTROL, GRPH_DEGAMMA_MODE, 0);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DEGAMMA_CONTROL, OVL_DEGAMMA_MODE, 0);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DEGAMMA_CONTROL, CURSOR_DEGAMMA_MODE, 0);
|
|
|
|
WREG32(mmDEGAMMA_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmGAMUT_REMAP_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, GAMUT_REMAP_CONTROL, GRPH_GAMUT_REMAP_MODE, 0);
|
|
|
|
tmp = REG_SET_FIELD(tmp, GAMUT_REMAP_CONTROL, OVL_GAMUT_REMAP_MODE, 0);
|
|
|
|
WREG32(mmGAMUT_REMAP_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmREGAMMA_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, REGAMMA_CONTROL, GRPH_REGAMMA_MODE, 0);
|
|
|
|
tmp = REG_SET_FIELD(tmp, REGAMMA_CONTROL, OVL_REGAMMA_MODE, 0);
|
|
|
|
WREG32(mmREGAMMA_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
tmp = RREG32(mmOUTPUT_CSC_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, OUTPUT_CSC_CONTROL, OUTPUT_CSC_GRPH_MODE, 0);
|
|
|
|
tmp = REG_SET_FIELD(tmp, OUTPUT_CSC_CONTROL, OUTPUT_CSC_OVL_MODE, 0);
|
|
|
|
WREG32(mmOUTPUT_CSC_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
|
|
|
|
/* XXX match this to the depth of the crtc fmt block, move to modeset? */
|
|
|
|
WREG32(mmDENORM_CONTROL + amdgpu_crtc->crtc_offset, 0);
|
|
|
|
/* XXX this only needs to be programmed once per crtc at startup,
|
|
|
|
* not sure where the best place for it is
|
|
|
|
*/
|
|
|
|
tmp = RREG32(mmALPHA_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, ALPHA_CONTROL, CURSOR_ALPHA_BLND_ENA, 1);
|
|
|
|
WREG32(mmALPHA_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_pick_dig_encoder(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
|
|
|
|
switch (amdgpu_encoder->encoder_id) {
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
|
|
|
|
if (dig->linkb)
|
|
|
|
return 1;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
|
|
|
|
if (dig->linkb)
|
|
|
|
return 3;
|
|
|
|
else
|
|
|
|
return 2;
|
|
|
|
break;
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
|
|
|
|
if (dig->linkb)
|
|
|
|
return 5;
|
|
|
|
else
|
|
|
|
return 4;
|
|
|
|
break;
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY3:
|
|
|
|
return 6;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DRM_ERROR("invalid encoder_id: 0x%x\n", amdgpu_encoder->encoder_id);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dce_v10_0_pick_pll - Allocate a PPLL for use by the crtc.
|
|
|
|
*
|
|
|
|
* @crtc: drm crtc
|
|
|
|
*
|
|
|
|
* Returns the PPLL (Pixel PLL) to be used by the crtc. For DP monitors
|
|
|
|
* a single PPLL can be used for all DP crtcs/encoders. For non-DP
|
|
|
|
* monitors a dedicated PPLL must be used. If a particular board has
|
|
|
|
* an external DP PLL, return ATOM_PPLL_INVALID to skip PLL programming
|
|
|
|
* as there is no need to program the PLL itself. If we are not able to
|
|
|
|
* allocate a PLL, return ATOM_PPLL_INVALID to skip PLL programming to
|
|
|
|
* avoid messing up an existing monitor.
|
|
|
|
*
|
|
|
|
* Asic specific PLL information
|
|
|
|
*
|
|
|
|
* DCE 10.x
|
|
|
|
* Tonga
|
|
|
|
* - PPLL1, PPLL2 are available for all UNIPHY (both DP and non-DP)
|
|
|
|
* CI
|
|
|
|
* - PPLL0, PPLL1, PPLL2 are available for all UNIPHY (both DP and non-DP) and DAC
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static u32 dce_v10_0_pick_pll(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
u32 pll_in_use;
|
|
|
|
int pll;
|
|
|
|
|
|
|
|
if (ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(amdgpu_crtc->encoder))) {
|
|
|
|
if (adev->clock.dp_extclk)
|
|
|
|
/* skip PPLL programming if using ext clock */
|
|
|
|
return ATOM_PPLL_INVALID;
|
|
|
|
else {
|
|
|
|
/* use the same PPLL for all DP monitors */
|
|
|
|
pll = amdgpu_pll_get_shared_dp_ppll(crtc);
|
|
|
|
if (pll != ATOM_PPLL_INVALID)
|
|
|
|
return pll;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* use the same PPLL for all monitors with the same clock */
|
|
|
|
pll = amdgpu_pll_get_shared_nondp_ppll(crtc);
|
|
|
|
if (pll != ATOM_PPLL_INVALID)
|
|
|
|
return pll;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* DCE10 has PPLL0, PPLL1, and PPLL2 */
|
|
|
|
pll_in_use = amdgpu_pll_get_use_mask(crtc);
|
|
|
|
if (!(pll_in_use & (1 << ATOM_PPLL2)))
|
|
|
|
return ATOM_PPLL2;
|
|
|
|
if (!(pll_in_use & (1 << ATOM_PPLL1)))
|
|
|
|
return ATOM_PPLL1;
|
|
|
|
if (!(pll_in_use & (1 << ATOM_PPLL0)))
|
|
|
|
return ATOM_PPLL0;
|
|
|
|
DRM_ERROR("unable to allocate a PPLL\n");
|
|
|
|
return ATOM_PPLL_INVALID;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_lock_cursor(struct drm_crtc *crtc, bool lock)
|
|
|
|
{
|
|
|
|
struct amdgpu_device *adev = crtc->dev->dev_private;
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
uint32_t cur_lock;
|
|
|
|
|
|
|
|
cur_lock = RREG32(mmCUR_UPDATE + amdgpu_crtc->crtc_offset);
|
|
|
|
if (lock)
|
|
|
|
cur_lock = REG_SET_FIELD(cur_lock, CUR_UPDATE, CURSOR_UPDATE_LOCK, 1);
|
|
|
|
else
|
|
|
|
cur_lock = REG_SET_FIELD(cur_lock, CUR_UPDATE, CURSOR_UPDATE_LOCK, 0);
|
|
|
|
WREG32(mmCUR_UPDATE + amdgpu_crtc->crtc_offset, cur_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_hide_cursor(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct amdgpu_device *adev = crtc->dev->dev_private;
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
tmp = RREG32_IDX(mmCUR_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, CUR_CONTROL, CURSOR_EN, 0);
|
|
|
|
WREG32_IDX(mmCUR_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_show_cursor(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct amdgpu_device *adev = crtc->dev->dev_private;
|
|
|
|
u32 tmp;
|
|
|
|
|
2015-09-25 13:51:59 +00:00
|
|
|
WREG32(mmCUR_SURFACE_ADDRESS_HIGH + amdgpu_crtc->crtc_offset,
|
|
|
|
upper_32_bits(amdgpu_crtc->cursor_addr));
|
|
|
|
WREG32(mmCUR_SURFACE_ADDRESS + amdgpu_crtc->crtc_offset,
|
|
|
|
lower_32_bits(amdgpu_crtc->cursor_addr));
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
tmp = RREG32_IDX(mmCUR_CONTROL + amdgpu_crtc->crtc_offset);
|
|
|
|
tmp = REG_SET_FIELD(tmp, CUR_CONTROL, CURSOR_EN, 1);
|
|
|
|
tmp = REG_SET_FIELD(tmp, CUR_CONTROL, CURSOR_MODE, 2);
|
|
|
|
WREG32_IDX(mmCUR_CONTROL + amdgpu_crtc->crtc_offset, tmp);
|
|
|
|
}
|
|
|
|
|
2015-09-24 21:29:44 +00:00
|
|
|
static int dce_v10_0_cursor_move_locked(struct drm_crtc *crtc,
|
|
|
|
int x, int y)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct amdgpu_device *adev = crtc->dev->dev_private;
|
|
|
|
int xorigin = 0, yorigin = 0;
|
|
|
|
|
2016-10-27 08:11:43 +00:00
|
|
|
amdgpu_crtc->cursor_x = x;
|
|
|
|
amdgpu_crtc->cursor_y = y;
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
/* avivo cursor are offset into the total surface */
|
|
|
|
x += crtc->x;
|
|
|
|
y += crtc->y;
|
|
|
|
DRM_DEBUG("x %d y %d c->x %d c->y %d\n", x, y, crtc->x, crtc->y);
|
|
|
|
|
|
|
|
if (x < 0) {
|
|
|
|
xorigin = min(-x, amdgpu_crtc->max_cursor_width - 1);
|
|
|
|
x = 0;
|
|
|
|
}
|
|
|
|
if (y < 0) {
|
|
|
|
yorigin = min(-y, amdgpu_crtc->max_cursor_height - 1);
|
|
|
|
y = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
WREG32(mmCUR_POSITION + amdgpu_crtc->crtc_offset, (x << 16) | y);
|
|
|
|
WREG32(mmCUR_HOT_SPOT + amdgpu_crtc->crtc_offset, (xorigin << 16) | yorigin);
|
2017-01-11 09:27:25 +00:00
|
|
|
WREG32(mmCUR_SIZE + amdgpu_crtc->crtc_offset,
|
|
|
|
((amdgpu_crtc->cursor_width - 1) << 16) | (amdgpu_crtc->cursor_height - 1));
|
2015-09-24 21:29:44 +00:00
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-09-24 21:29:44 +00:00
|
|
|
static int dce_v10_0_crtc_cursor_move(struct drm_crtc *crtc,
|
|
|
|
int x, int y)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
dce_v10_0_lock_cursor(crtc, true);
|
|
|
|
ret = dce_v10_0_cursor_move_locked(crtc, x, y);
|
|
|
|
dce_v10_0_lock_cursor(crtc, false);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_crtc_cursor_set2(struct drm_crtc *crtc,
|
|
|
|
struct drm_file *file_priv,
|
|
|
|
uint32_t handle,
|
|
|
|
uint32_t width,
|
|
|
|
uint32_t height,
|
|
|
|
int32_t hot_x,
|
|
|
|
int32_t hot_y)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct drm_gem_object *obj;
|
2015-10-08 15:25:38 +00:00
|
|
|
struct amdgpu_bo *aobj;
|
2015-04-20 21:31:14 +00:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!handle) {
|
|
|
|
/* turn off cursor */
|
|
|
|
dce_v10_0_hide_cursor(crtc);
|
|
|
|
obj = NULL;
|
|
|
|
goto unpin;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((width > amdgpu_crtc->max_cursor_width) ||
|
|
|
|
(height > amdgpu_crtc->max_cursor_height)) {
|
|
|
|
DRM_ERROR("bad cursor width or height %d x %d\n", width, height);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-05-09 10:04:54 +00:00
|
|
|
obj = drm_gem_object_lookup(file_priv, handle);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (!obj) {
|
|
|
|
DRM_ERROR("Cannot find cursor object %x for crtc %d\n", handle, amdgpu_crtc->crtc_id);
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
|
2015-10-08 15:25:38 +00:00
|
|
|
aobj = gem_to_amdgpu_bo(obj);
|
|
|
|
ret = amdgpu_bo_reserve(aobj, false);
|
|
|
|
if (ret != 0) {
|
2017-08-03 11:58:16 +00:00
|
|
|
drm_gem_object_put_unlocked(obj);
|
2015-10-08 15:25:38 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = amdgpu_bo_pin(aobj, AMDGPU_GEM_DOMAIN_VRAM, &amdgpu_crtc->cursor_addr);
|
|
|
|
amdgpu_bo_unreserve(aobj);
|
|
|
|
if (ret) {
|
|
|
|
DRM_ERROR("Failed to pin new cursor BO (%d)\n", ret);
|
2017-08-03 11:58:16 +00:00
|
|
|
drm_gem_object_put_unlocked(obj);
|
2015-10-08 15:25:38 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
dce_v10_0_lock_cursor(crtc, true);
|
2015-09-24 21:46:19 +00:00
|
|
|
|
2017-01-11 09:27:25 +00:00
|
|
|
if (width != amdgpu_crtc->cursor_width ||
|
|
|
|
height != amdgpu_crtc->cursor_height ||
|
|
|
|
hot_x != amdgpu_crtc->cursor_hot_x ||
|
2015-09-24 21:46:19 +00:00
|
|
|
hot_y != amdgpu_crtc->cursor_hot_y) {
|
|
|
|
int x, y;
|
|
|
|
|
|
|
|
x = amdgpu_crtc->cursor_x + amdgpu_crtc->cursor_hot_x - hot_x;
|
|
|
|
y = amdgpu_crtc->cursor_y + amdgpu_crtc->cursor_hot_y - hot_y;
|
|
|
|
|
|
|
|
dce_v10_0_cursor_move_locked(crtc, x, y);
|
|
|
|
|
2016-10-27 08:44:07 +00:00
|
|
|
amdgpu_crtc->cursor_width = width;
|
|
|
|
amdgpu_crtc->cursor_height = height;
|
2017-01-11 09:27:25 +00:00
|
|
|
amdgpu_crtc->cursor_hot_x = hot_x;
|
|
|
|
amdgpu_crtc->cursor_hot_y = hot_y;
|
2016-10-27 08:44:07 +00:00
|
|
|
}
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
dce_v10_0_show_cursor(crtc);
|
|
|
|
dce_v10_0_lock_cursor(crtc, false);
|
|
|
|
|
|
|
|
unpin:
|
|
|
|
if (amdgpu_crtc->cursor_bo) {
|
2015-09-24 21:43:09 +00:00
|
|
|
struct amdgpu_bo *aobj = gem_to_amdgpu_bo(amdgpu_crtc->cursor_bo);
|
2017-04-28 08:28:14 +00:00
|
|
|
ret = amdgpu_bo_reserve(aobj, true);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (likely(ret == 0)) {
|
2015-09-24 21:43:09 +00:00
|
|
|
amdgpu_bo_unpin(aobj);
|
|
|
|
amdgpu_bo_unreserve(aobj);
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
2017-08-03 11:58:16 +00:00
|
|
|
drm_gem_object_put_unlocked(amdgpu_crtc->cursor_bo);
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
amdgpu_crtc->cursor_bo = obj;
|
|
|
|
return 0;
|
2015-09-24 21:43:09 +00:00
|
|
|
}
|
2015-04-20 21:31:14 +00:00
|
|
|
|
2015-09-24 21:43:09 +00:00
|
|
|
static void dce_v10_0_cursor_reset(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
|
|
|
|
if (amdgpu_crtc->cursor_bo) {
|
|
|
|
dce_v10_0_lock_cursor(crtc, true);
|
|
|
|
|
|
|
|
dce_v10_0_cursor_move_locked(crtc, amdgpu_crtc->cursor_x,
|
|
|
|
amdgpu_crtc->cursor_y);
|
|
|
|
|
2015-10-08 15:25:38 +00:00
|
|
|
dce_v10_0_show_cursor(crtc);
|
2015-09-24 21:43:09 +00:00
|
|
|
|
|
|
|
dce_v10_0_lock_cursor(crtc, false);
|
|
|
|
}
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
2016-06-07 10:49:30 +00:00
|
|
|
static int dce_v10_0_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green,
|
2017-04-03 08:33:01 +00:00
|
|
|
u16 *blue, uint32_t size,
|
|
|
|
struct drm_modeset_acquire_ctx *ctx)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
dce_v10_0_crtc_load_lut(crtc);
|
2016-06-07 10:49:30 +00:00
|
|
|
|
|
|
|
return 0;
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_crtc_destroy(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
|
|
|
|
drm_crtc_cleanup(crtc);
|
|
|
|
kfree(amdgpu_crtc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_crtc_funcs dce_v10_0_crtc_funcs = {
|
2015-09-24 21:29:44 +00:00
|
|
|
.cursor_set2 = dce_v10_0_crtc_cursor_set2,
|
2015-04-20 21:31:14 +00:00
|
|
|
.cursor_move = dce_v10_0_crtc_cursor_move,
|
|
|
|
.gamma_set = dce_v10_0_crtc_gamma_set,
|
2018-01-19 16:53:31 +00:00
|
|
|
.set_config = amdgpu_display_crtc_set_config,
|
2015-04-20 21:31:14 +00:00
|
|
|
.destroy = dce_v10_0_crtc_destroy,
|
2018-01-19 16:22:59 +00:00
|
|
|
.page_flip_target = amdgpu_display_crtc_page_flip_target,
|
2015-04-20 21:31:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static void dce_v10_0_crtc_dpms(struct drm_crtc *crtc, int mode)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
2015-07-17 02:20:18 +00:00
|
|
|
unsigned type;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case DRM_MODE_DPMS_ON:
|
|
|
|
amdgpu_crtc->enabled = true;
|
|
|
|
amdgpu_atombios_crtc_enable(crtc, ATOM_ENABLE);
|
|
|
|
dce_v10_0_vga_enable(crtc, true);
|
|
|
|
amdgpu_atombios_crtc_blank(crtc, ATOM_DISABLE);
|
|
|
|
dce_v10_0_vga_enable(crtc, false);
|
2015-10-08 08:48:04 +00:00
|
|
|
/* Make sure VBLANK and PFLIP interrupts are still enabled */
|
2015-07-17 02:20:18 +00:00
|
|
|
type = amdgpu_crtc_idx_to_irq_type(adev, amdgpu_crtc->crtc_id);
|
|
|
|
amdgpu_irq_update(adev, &adev->crtc_irq, type);
|
2015-10-08 08:48:04 +00:00
|
|
|
amdgpu_irq_update(adev, &adev->pageflip_irq, type);
|
2016-06-07 14:07:58 +00:00
|
|
|
drm_crtc_vblank_on(crtc);
|
2015-04-20 21:31:14 +00:00
|
|
|
dce_v10_0_crtc_load_lut(crtc);
|
|
|
|
break;
|
|
|
|
case DRM_MODE_DPMS_STANDBY:
|
|
|
|
case DRM_MODE_DPMS_SUSPEND:
|
|
|
|
case DRM_MODE_DPMS_OFF:
|
2016-06-07 14:07:58 +00:00
|
|
|
drm_crtc_vblank_off(crtc);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (amdgpu_crtc->enabled) {
|
|
|
|
dce_v10_0_vga_enable(crtc, true);
|
|
|
|
amdgpu_atombios_crtc_blank(crtc, ATOM_ENABLE);
|
|
|
|
dce_v10_0_vga_enable(crtc, false);
|
|
|
|
}
|
|
|
|
amdgpu_atombios_crtc_enable(crtc, ATOM_DISABLE);
|
|
|
|
amdgpu_crtc->enabled = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* adjust pm to dpms */
|
|
|
|
amdgpu_pm_compute_clocks(adev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_crtc_prepare(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
/* disable crtc pair power gating before programming */
|
|
|
|
amdgpu_atombios_crtc_powergate(crtc, ATOM_DISABLE);
|
|
|
|
amdgpu_atombios_crtc_lock(crtc, ATOM_ENABLE);
|
|
|
|
dce_v10_0_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_crtc_commit(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
dce_v10_0_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
|
|
|
|
amdgpu_atombios_crtc_lock(crtc, ATOM_DISABLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_crtc_disable(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
struct amdgpu_atom_ss ss;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
dce_v10_0_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
|
|
|
if (crtc->primary->fb) {
|
|
|
|
int r;
|
|
|
|
struct amdgpu_framebuffer *amdgpu_fb;
|
2016-09-15 13:06:50 +00:00
|
|
|
struct amdgpu_bo *abo;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
amdgpu_fb = to_amdgpu_framebuffer(crtc->primary->fb);
|
2016-09-15 13:06:50 +00:00
|
|
|
abo = gem_to_amdgpu_bo(amdgpu_fb->obj);
|
2017-04-28 08:28:14 +00:00
|
|
|
r = amdgpu_bo_reserve(abo, true);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (unlikely(r))
|
2016-09-15 13:06:50 +00:00
|
|
|
DRM_ERROR("failed to reserve abo before unpin\n");
|
2015-04-20 21:31:14 +00:00
|
|
|
else {
|
2016-09-15 13:06:50 +00:00
|
|
|
amdgpu_bo_unpin(abo);
|
|
|
|
amdgpu_bo_unreserve(abo);
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/* disable the GRPH */
|
|
|
|
dce_v10_0_grph_enable(crtc, false);
|
|
|
|
|
|
|
|
amdgpu_atombios_crtc_powergate(crtc, ATOM_ENABLE);
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.num_crtc; i++) {
|
|
|
|
if (adev->mode_info.crtcs[i] &&
|
|
|
|
adev->mode_info.crtcs[i]->enabled &&
|
|
|
|
i != amdgpu_crtc->crtc_id &&
|
|
|
|
amdgpu_crtc->pll_id == adev->mode_info.crtcs[i]->pll_id) {
|
|
|
|
/* one other crtc is using this pll don't turn
|
|
|
|
* off the pll
|
|
|
|
*/
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (amdgpu_crtc->pll_id) {
|
|
|
|
case ATOM_PPLL0:
|
|
|
|
case ATOM_PPLL1:
|
|
|
|
case ATOM_PPLL2:
|
|
|
|
/* disable the ppll */
|
|
|
|
amdgpu_atombios_crtc_program_pll(crtc, amdgpu_crtc->crtc_id, amdgpu_crtc->pll_id,
|
|
|
|
0, 0, ATOM_DISABLE, 0, 0, 0, 0, 0, false, &ss);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
amdgpu_crtc->pll_id = ATOM_PPLL_INVALID;
|
|
|
|
amdgpu_crtc->adjusted_clock = 0;
|
|
|
|
amdgpu_crtc->encoder = NULL;
|
|
|
|
amdgpu_crtc->connector = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_crtc_mode_set(struct drm_crtc *crtc,
|
|
|
|
struct drm_display_mode *mode,
|
|
|
|
struct drm_display_mode *adjusted_mode,
|
|
|
|
int x, int y, struct drm_framebuffer *old_fb)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
|
|
|
|
if (!amdgpu_crtc->adjusted_clock)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
amdgpu_atombios_crtc_set_pll(crtc, adjusted_mode);
|
|
|
|
amdgpu_atombios_crtc_set_dtd_timing(crtc, adjusted_mode);
|
|
|
|
dce_v10_0_crtc_do_set_base(crtc, old_fb, x, y, 0);
|
|
|
|
amdgpu_atombios_crtc_overscan_setup(crtc, mode, adjusted_mode);
|
|
|
|
amdgpu_atombios_crtc_scaler_setup(crtc);
|
2015-09-24 21:43:09 +00:00
|
|
|
dce_v10_0_cursor_reset(crtc);
|
2015-04-20 21:31:14 +00:00
|
|
|
/* update the hw version fpr dpm */
|
|
|
|
amdgpu_crtc->hw_mode = *adjusted_mode;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool dce_v10_0_crtc_mode_fixup(struct drm_crtc *crtc,
|
|
|
|
const struct drm_display_mode *mode,
|
|
|
|
struct drm_display_mode *adjusted_mode)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct drm_encoder *encoder;
|
|
|
|
|
|
|
|
/* assign the encoder to the amdgpu crtc to avoid repeated lookups later */
|
|
|
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
|
|
|
if (encoder->crtc == crtc) {
|
|
|
|
amdgpu_crtc->encoder = encoder;
|
|
|
|
amdgpu_crtc->connector = amdgpu_get_connector_for_encoder(encoder);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((amdgpu_crtc->encoder == NULL) || (amdgpu_crtc->connector == NULL)) {
|
|
|
|
amdgpu_crtc->encoder = NULL;
|
|
|
|
amdgpu_crtc->connector = NULL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!amdgpu_crtc_scaling_mode_fixup(crtc, mode, adjusted_mode))
|
|
|
|
return false;
|
|
|
|
if (amdgpu_atombios_crtc_prepare_pll(crtc, adjusted_mode))
|
|
|
|
return false;
|
|
|
|
/* pick pll */
|
|
|
|
amdgpu_crtc->pll_id = dce_v10_0_pick_pll(crtc);
|
|
|
|
/* if we can't get a PPLL for a non-DP encoder, fail */
|
|
|
|
if ((amdgpu_crtc->pll_id == ATOM_PPLL_INVALID) &&
|
|
|
|
!ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(amdgpu_crtc->encoder)))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_crtc_set_base(struct drm_crtc *crtc, int x, int y,
|
|
|
|
struct drm_framebuffer *old_fb)
|
|
|
|
{
|
|
|
|
return dce_v10_0_crtc_do_set_base(crtc, old_fb, x, y, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_crtc_set_base_atomic(struct drm_crtc *crtc,
|
|
|
|
struct drm_framebuffer *fb,
|
|
|
|
int x, int y, enum mode_set_atomic state)
|
|
|
|
{
|
|
|
|
return dce_v10_0_crtc_do_set_base(crtc, fb, x, y, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_crtc_helper_funcs dce_v10_0_crtc_helper_funcs = {
|
|
|
|
.dpms = dce_v10_0_crtc_dpms,
|
|
|
|
.mode_fixup = dce_v10_0_crtc_mode_fixup,
|
|
|
|
.mode_set = dce_v10_0_crtc_mode_set,
|
|
|
|
.mode_set_base = dce_v10_0_crtc_set_base,
|
|
|
|
.mode_set_base_atomic = dce_v10_0_crtc_set_base_atomic,
|
|
|
|
.prepare = dce_v10_0_crtc_prepare,
|
|
|
|
.commit = dce_v10_0_crtc_commit,
|
|
|
|
.disable = dce_v10_0_crtc_disable,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int dce_v10_0_crtc_init(struct amdgpu_device *adev, int index)
|
|
|
|
{
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc;
|
|
|
|
|
|
|
|
amdgpu_crtc = kzalloc(sizeof(struct amdgpu_crtc) +
|
|
|
|
(AMDGPUFB_CONN_LIMIT * sizeof(struct drm_connector *)), GFP_KERNEL);
|
|
|
|
if (amdgpu_crtc == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
drm_crtc_init(adev->ddev, &amdgpu_crtc->base, &dce_v10_0_crtc_funcs);
|
|
|
|
|
|
|
|
drm_mode_crtc_set_gamma_size(&amdgpu_crtc->base, 256);
|
|
|
|
amdgpu_crtc->crtc_id = index;
|
|
|
|
adev->mode_info.crtcs[index] = amdgpu_crtc;
|
|
|
|
|
|
|
|
amdgpu_crtc->max_cursor_width = 128;
|
|
|
|
amdgpu_crtc->max_cursor_height = 128;
|
|
|
|
adev->ddev->mode_config.cursor_width = amdgpu_crtc->max_cursor_width;
|
|
|
|
adev->ddev->mode_config.cursor_height = amdgpu_crtc->max_cursor_height;
|
|
|
|
|
|
|
|
switch (amdgpu_crtc->crtc_id) {
|
|
|
|
case 0:
|
|
|
|
default:
|
|
|
|
amdgpu_crtc->crtc_offset = CRTC0_REGISTER_OFFSET;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
amdgpu_crtc->crtc_offset = CRTC1_REGISTER_OFFSET;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
amdgpu_crtc->crtc_offset = CRTC2_REGISTER_OFFSET;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
amdgpu_crtc->crtc_offset = CRTC3_REGISTER_OFFSET;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
amdgpu_crtc->crtc_offset = CRTC4_REGISTER_OFFSET;
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
amdgpu_crtc->crtc_offset = CRTC5_REGISTER_OFFSET;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
amdgpu_crtc->pll_id = ATOM_PPLL_INVALID;
|
|
|
|
amdgpu_crtc->adjusted_clock = 0;
|
|
|
|
amdgpu_crtc->encoder = NULL;
|
|
|
|
amdgpu_crtc->connector = NULL;
|
|
|
|
drm_crtc_helper_add(&amdgpu_crtc->base, &dce_v10_0_crtc_helper_funcs);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_early_init(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
2015-05-22 18:39:35 +00:00
|
|
|
struct amdgpu_device *adev = (struct amdgpu_device *)handle;
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
adev->audio_endpt_rreg = &dce_v10_0_audio_endpt_rreg;
|
|
|
|
adev->audio_endpt_wreg = &dce_v10_0_audio_endpt_wreg;
|
|
|
|
|
|
|
|
dce_v10_0_set_display_funcs(adev);
|
|
|
|
|
2016-08-08 03:33:11 +00:00
|
|
|
adev->mode_info.num_crtc = dce_v10_0_get_num_crtc(adev);
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
switch (adev->asic_type) {
|
2015-07-07 17:28:20 +00:00
|
|
|
case CHIP_FIJI:
|
2015-04-20 21:31:14 +00:00
|
|
|
case CHIP_TONGA:
|
|
|
|
adev->mode_info.num_hpd = 6;
|
|
|
|
adev->mode_info.num_dig = 7;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* FIXME: not supported yet */
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2017-10-24 09:03:21 +00:00
|
|
|
dce_v10_0_set_irq_funcs(adev);
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_sw_init(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
int r, i;
|
2015-05-22 18:39:35 +00:00
|
|
|
struct amdgpu_device *adev = (struct amdgpu_device *)handle;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.num_crtc; i++) {
|
2016-03-29 22:28:50 +00:00
|
|
|
r = amdgpu_irq_add_id(adev, AMDGPU_IH_CLIENTID_LEGACY, i + 1, &adev->crtc_irq);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 8; i < 20; i += 2) {
|
2016-03-29 22:28:50 +00:00
|
|
|
r = amdgpu_irq_add_id(adev, AMDGPU_IH_CLIENTID_LEGACY, i, &adev->pageflip_irq);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* HPD hotplug */
|
2016-03-29 22:28:50 +00:00
|
|
|
r = amdgpu_irq_add_id(adev, AMDGPU_IH_CLIENTID_LEGACY, 42, &adev->hpd_irq);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
adev->ddev->mode_config.funcs = &amdgpu_mode_funcs;
|
|
|
|
|
2016-05-05 20:03:57 +00:00
|
|
|
adev->ddev->mode_config.async_page_flip = true;
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
adev->ddev->mode_config.max_width = 16384;
|
|
|
|
adev->ddev->mode_config.max_height = 16384;
|
|
|
|
|
|
|
|
adev->ddev->mode_config.preferred_depth = 24;
|
|
|
|
adev->ddev->mode_config.prefer_shadow = 1;
|
|
|
|
|
2018-01-12 13:52:22 +00:00
|
|
|
adev->ddev->mode_config.fb_base = adev->gmc.aper_base;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
r = amdgpu_modeset_create_props(adev);
|
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
adev->ddev->mode_config.max_width = 16384;
|
|
|
|
adev->ddev->mode_config.max_height = 16384;
|
|
|
|
|
|
|
|
/* allocate crtcs */
|
|
|
|
for (i = 0; i < adev->mode_info.num_crtc; i++) {
|
|
|
|
r = dce_v10_0_crtc_init(adev, i);
|
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (amdgpu_atombios_get_connector_info_from_object_table(adev))
|
2018-01-19 17:02:45 +00:00
|
|
|
amdgpu_display_print_display_setup(adev->ddev);
|
2015-04-20 21:31:14 +00:00
|
|
|
else
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* setup afmt */
|
2016-03-03 14:23:48 +00:00
|
|
|
r = dce_v10_0_afmt_init(adev);
|
|
|
|
if (r)
|
|
|
|
return r;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
r = dce_v10_0_audio_init(adev);
|
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
drm_kms_helper_poll_init(adev->ddev);
|
|
|
|
|
2016-03-03 14:21:44 +00:00
|
|
|
adev->mode_info.mode_config_initialized = true;
|
|
|
|
return 0;
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_sw_fini(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
2015-05-22 18:39:35 +00:00
|
|
|
struct amdgpu_device *adev = (struct amdgpu_device *)handle;
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
kfree(adev->mode_info.bios_hardcoded_edid);
|
|
|
|
|
|
|
|
drm_kms_helper_poll_fini(adev->ddev);
|
|
|
|
|
|
|
|
dce_v10_0_audio_fini(adev);
|
|
|
|
|
|
|
|
dce_v10_0_afmt_fini(adev);
|
|
|
|
|
|
|
|
drm_mode_config_cleanup(adev->ddev);
|
|
|
|
adev->mode_info.mode_config_initialized = false;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_hw_init(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
int i;
|
2015-05-22 18:39:35 +00:00
|
|
|
struct amdgpu_device *adev = (struct amdgpu_device *)handle;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
dce_v10_0_init_golden_registers(adev);
|
|
|
|
|
2017-06-19 21:00:38 +00:00
|
|
|
/* disable vga render */
|
|
|
|
dce_v10_0_set_vga_render_state(adev, false);
|
2015-04-20 21:31:14 +00:00
|
|
|
/* init dig PHYs, disp eng pll */
|
|
|
|
amdgpu_atombios_encoder_init_dig(adev);
|
|
|
|
amdgpu_atombios_crtc_set_disp_eng_pll(adev, adev->clock.default_dispclk);
|
|
|
|
|
|
|
|
/* initialize hpd */
|
|
|
|
dce_v10_0_hpd_init(adev);
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.audio.num_pins; i++) {
|
|
|
|
dce_v10_0_audio_enable(adev, &adev->mode_info.audio.pin[i], false);
|
|
|
|
}
|
|
|
|
|
2015-10-08 08:48:04 +00:00
|
|
|
dce_v10_0_pageflip_interrupt_init(adev);
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_hw_fini(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
int i;
|
2015-05-22 18:39:35 +00:00
|
|
|
struct amdgpu_device *adev = (struct amdgpu_device *)handle;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
dce_v10_0_hpd_fini(adev);
|
|
|
|
|
|
|
|
for (i = 0; i < adev->mode_info.audio.num_pins; i++) {
|
|
|
|
dce_v10_0_audio_enable(adev, &adev->mode_info.audio.pin[i], false);
|
|
|
|
}
|
|
|
|
|
2015-10-08 08:48:04 +00:00
|
|
|
dce_v10_0_pageflip_interrupt_fini(adev);
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_suspend(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
2015-10-15 20:53:27 +00:00
|
|
|
return dce_v10_0_hw_fini(handle);
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_resume(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
2015-05-22 18:39:35 +00:00
|
|
|
struct amdgpu_device *adev = (struct amdgpu_device *)handle;
|
2015-10-15 20:53:27 +00:00
|
|
|
int ret;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
2015-10-15 20:53:27 +00:00
|
|
|
ret = dce_v10_0_hw_init(handle);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
/* turn on the BL */
|
|
|
|
if (adev->mode_info.bl_encoder) {
|
|
|
|
u8 bl_level = amdgpu_display_backlight_get_level(adev,
|
|
|
|
adev->mode_info.bl_encoder);
|
|
|
|
amdgpu_display_backlight_set_level(adev, adev->mode_info.bl_encoder,
|
|
|
|
bl_level);
|
|
|
|
}
|
|
|
|
|
2015-10-15 20:53:27 +00:00
|
|
|
return ret;
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static bool dce_v10_0_is_idle(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_wait_for_idle(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-10-13 20:07:03 +00:00
|
|
|
static bool dce_v10_0_check_soft_reset(void *handle)
|
2016-07-20 05:42:25 +00:00
|
|
|
{
|
|
|
|
struct amdgpu_device *adev = (struct amdgpu_device *)handle;
|
|
|
|
|
2016-10-13 20:07:03 +00:00
|
|
|
return dce_v10_0_is_display_hung(adev);
|
2016-07-20 05:42:25 +00:00
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_soft_reset(void *handle)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
u32 srbm_soft_reset = 0, tmp;
|
2015-05-22 18:39:35 +00:00
|
|
|
struct amdgpu_device *adev = (struct amdgpu_device *)handle;
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
if (dce_v10_0_is_display_hung(adev))
|
|
|
|
srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_DC_MASK;
|
|
|
|
|
|
|
|
if (srbm_soft_reset) {
|
|
|
|
tmp = RREG32(mmSRBM_SOFT_RESET);
|
|
|
|
tmp |= srbm_soft_reset;
|
|
|
|
dev_info(adev->dev, "SRBM_SOFT_RESET=0x%08X\n", tmp);
|
|
|
|
WREG32(mmSRBM_SOFT_RESET, tmp);
|
|
|
|
tmp = RREG32(mmSRBM_SOFT_RESET);
|
|
|
|
|
|
|
|
udelay(50);
|
|
|
|
|
|
|
|
tmp &= ~srbm_soft_reset;
|
|
|
|
WREG32(mmSRBM_SOFT_RESET, tmp);
|
|
|
|
tmp = RREG32(mmSRBM_SOFT_RESET);
|
|
|
|
|
|
|
|
/* Wait a little for things to settle down */
|
|
|
|
udelay(50);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_set_crtc_vblank_interrupt_state(struct amdgpu_device *adev,
|
|
|
|
int crtc,
|
|
|
|
enum amdgpu_interrupt_state state)
|
|
|
|
{
|
|
|
|
u32 lb_interrupt_mask;
|
|
|
|
|
|
|
|
if (crtc >= adev->mode_info.num_crtc) {
|
|
|
|
DRM_DEBUG("invalid crtc %d\n", crtc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
case AMDGPU_IRQ_STATE_DISABLE:
|
|
|
|
lb_interrupt_mask = RREG32(mmLB_INTERRUPT_MASK + crtc_offsets[crtc]);
|
|
|
|
lb_interrupt_mask = REG_SET_FIELD(lb_interrupt_mask, LB_INTERRUPT_MASK,
|
|
|
|
VBLANK_INTERRUPT_MASK, 0);
|
|
|
|
WREG32(mmLB_INTERRUPT_MASK + crtc_offsets[crtc], lb_interrupt_mask);
|
|
|
|
break;
|
|
|
|
case AMDGPU_IRQ_STATE_ENABLE:
|
|
|
|
lb_interrupt_mask = RREG32(mmLB_INTERRUPT_MASK + crtc_offsets[crtc]);
|
|
|
|
lb_interrupt_mask = REG_SET_FIELD(lb_interrupt_mask, LB_INTERRUPT_MASK,
|
|
|
|
VBLANK_INTERRUPT_MASK, 1);
|
|
|
|
WREG32(mmLB_INTERRUPT_MASK + crtc_offsets[crtc], lb_interrupt_mask);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_set_crtc_vline_interrupt_state(struct amdgpu_device *adev,
|
|
|
|
int crtc,
|
|
|
|
enum amdgpu_interrupt_state state)
|
|
|
|
{
|
|
|
|
u32 lb_interrupt_mask;
|
|
|
|
|
|
|
|
if (crtc >= adev->mode_info.num_crtc) {
|
|
|
|
DRM_DEBUG("invalid crtc %d\n", crtc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
case AMDGPU_IRQ_STATE_DISABLE:
|
|
|
|
lb_interrupt_mask = RREG32(mmLB_INTERRUPT_MASK + crtc_offsets[crtc]);
|
|
|
|
lb_interrupt_mask = REG_SET_FIELD(lb_interrupt_mask, LB_INTERRUPT_MASK,
|
|
|
|
VLINE_INTERRUPT_MASK, 0);
|
|
|
|
WREG32(mmLB_INTERRUPT_MASK + crtc_offsets[crtc], lb_interrupt_mask);
|
|
|
|
break;
|
|
|
|
case AMDGPU_IRQ_STATE_ENABLE:
|
|
|
|
lb_interrupt_mask = RREG32(mmLB_INTERRUPT_MASK + crtc_offsets[crtc]);
|
|
|
|
lb_interrupt_mask = REG_SET_FIELD(lb_interrupt_mask, LB_INTERRUPT_MASK,
|
|
|
|
VLINE_INTERRUPT_MASK, 1);
|
|
|
|
WREG32(mmLB_INTERRUPT_MASK + crtc_offsets[crtc], lb_interrupt_mask);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_set_hpd_irq_state(struct amdgpu_device *adev,
|
|
|
|
struct amdgpu_irq_src *source,
|
|
|
|
unsigned hpd,
|
|
|
|
enum amdgpu_interrupt_state state)
|
|
|
|
{
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
if (hpd >= adev->mode_info.num_hpd) {
|
|
|
|
DRM_DEBUG("invalid hdp %d\n", hpd);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
case AMDGPU_IRQ_STATE_DISABLE:
|
|
|
|
tmp = RREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[hpd]);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_INT_CONTROL, DC_HPD_INT_EN, 0);
|
|
|
|
WREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[hpd], tmp);
|
|
|
|
break;
|
|
|
|
case AMDGPU_IRQ_STATE_ENABLE:
|
|
|
|
tmp = RREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[hpd]);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_INT_CONTROL, DC_HPD_INT_EN, 1);
|
|
|
|
WREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[hpd], tmp);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_set_crtc_irq_state(struct amdgpu_device *adev,
|
|
|
|
struct amdgpu_irq_src *source,
|
|
|
|
unsigned type,
|
|
|
|
enum amdgpu_interrupt_state state)
|
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case AMDGPU_CRTC_IRQ_VBLANK1:
|
|
|
|
dce_v10_0_set_crtc_vblank_interrupt_state(adev, 0, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VBLANK2:
|
|
|
|
dce_v10_0_set_crtc_vblank_interrupt_state(adev, 1, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VBLANK3:
|
|
|
|
dce_v10_0_set_crtc_vblank_interrupt_state(adev, 2, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VBLANK4:
|
|
|
|
dce_v10_0_set_crtc_vblank_interrupt_state(adev, 3, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VBLANK5:
|
|
|
|
dce_v10_0_set_crtc_vblank_interrupt_state(adev, 4, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VBLANK6:
|
|
|
|
dce_v10_0_set_crtc_vblank_interrupt_state(adev, 5, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VLINE1:
|
|
|
|
dce_v10_0_set_crtc_vline_interrupt_state(adev, 0, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VLINE2:
|
|
|
|
dce_v10_0_set_crtc_vline_interrupt_state(adev, 1, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VLINE3:
|
|
|
|
dce_v10_0_set_crtc_vline_interrupt_state(adev, 2, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VLINE4:
|
|
|
|
dce_v10_0_set_crtc_vline_interrupt_state(adev, 3, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VLINE5:
|
|
|
|
dce_v10_0_set_crtc_vline_interrupt_state(adev, 4, state);
|
|
|
|
break;
|
|
|
|
case AMDGPU_CRTC_IRQ_VLINE6:
|
|
|
|
dce_v10_0_set_crtc_vline_interrupt_state(adev, 5, state);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_set_pageflip_irq_state(struct amdgpu_device *adev,
|
|
|
|
struct amdgpu_irq_src *src,
|
|
|
|
unsigned type,
|
|
|
|
enum amdgpu_interrupt_state state)
|
|
|
|
{
|
2015-10-09 15:38:49 +00:00
|
|
|
u32 reg;
|
|
|
|
|
|
|
|
if (type >= adev->mode_info.num_crtc) {
|
|
|
|
DRM_ERROR("invalid pageflip crtc %d\n", type);
|
|
|
|
return -EINVAL;
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
2015-10-09 15:38:49 +00:00
|
|
|
reg = RREG32(mmGRPH_INTERRUPT_CONTROL + crtc_offsets[type]);
|
2015-04-20 21:31:14 +00:00
|
|
|
if (state == AMDGPU_IRQ_STATE_DISABLE)
|
2015-10-09 15:38:49 +00:00
|
|
|
WREG32(mmGRPH_INTERRUPT_CONTROL + crtc_offsets[type],
|
|
|
|
reg & ~GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK);
|
2015-04-20 21:31:14 +00:00
|
|
|
else
|
2015-10-09 15:38:49 +00:00
|
|
|
WREG32(mmGRPH_INTERRUPT_CONTROL + crtc_offsets[type],
|
|
|
|
reg | GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_pageflip_irq(struct amdgpu_device *adev,
|
|
|
|
struct amdgpu_irq_src *source,
|
|
|
|
struct amdgpu_iv_entry *entry)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
unsigned crtc_id;
|
|
|
|
struct amdgpu_crtc *amdgpu_crtc;
|
|
|
|
struct amdgpu_flip_work *works;
|
|
|
|
|
|
|
|
crtc_id = (entry->src_id - 8) >> 1;
|
|
|
|
amdgpu_crtc = adev->mode_info.crtcs[crtc_id];
|
|
|
|
|
2015-10-09 15:38:49 +00:00
|
|
|
if (crtc_id >= adev->mode_info.num_crtc) {
|
|
|
|
DRM_ERROR("invalid pageflip crtc %d\n", crtc_id);
|
|
|
|
return -EINVAL;
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
|
|
|
|
2015-10-09 15:38:49 +00:00
|
|
|
if (RREG32(mmGRPH_INTERRUPT_STATUS + crtc_offsets[crtc_id]) &
|
|
|
|
GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_OCCURRED_MASK)
|
|
|
|
WREG32(mmGRPH_INTERRUPT_STATUS + crtc_offsets[crtc_id],
|
|
|
|
GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
/* IRQ could occur when in initial stage */
|
|
|
|
if (amdgpu_crtc == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&adev->ddev->event_lock, flags);
|
|
|
|
works = amdgpu_crtc->pflip_works;
|
|
|
|
if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED) {
|
|
|
|
DRM_DEBUG_DRIVER("amdgpu_crtc->pflip_status = %d != "
|
|
|
|
"AMDGPU_FLIP_SUBMITTED(%d)\n",
|
|
|
|
amdgpu_crtc->pflip_status,
|
|
|
|
AMDGPU_FLIP_SUBMITTED);
|
|
|
|
spin_unlock_irqrestore(&adev->ddev->event_lock, flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* page flip completed. clean up */
|
|
|
|
amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE;
|
|
|
|
amdgpu_crtc->pflip_works = NULL;
|
|
|
|
|
|
|
|
/* wakeup usersapce */
|
|
|
|
if (works->event)
|
2016-04-14 17:48:12 +00:00
|
|
|
drm_crtc_send_vblank_event(&amdgpu_crtc->base, works->event);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
spin_unlock_irqrestore(&adev->ddev->event_lock, flags);
|
|
|
|
|
2016-06-06 14:41:39 +00:00
|
|
|
drm_crtc_vblank_put(&amdgpu_crtc->base);
|
2016-02-11 16:31:37 +00:00
|
|
|
schedule_work(&works->unpin_work);
|
2015-04-20 21:31:14 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_hpd_int_ack(struct amdgpu_device *adev,
|
|
|
|
int hpd)
|
|
|
|
{
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
if (hpd >= adev->mode_info.num_hpd) {
|
|
|
|
DRM_DEBUG("invalid hdp %d\n", hpd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp = RREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[hpd]);
|
|
|
|
tmp = REG_SET_FIELD(tmp, DC_HPD_INT_CONTROL, DC_HPD_INT_ACK, 1);
|
|
|
|
WREG32(mmDC_HPD_INT_CONTROL + hpd_offsets[hpd], tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_crtc_vblank_int_ack(struct amdgpu_device *adev,
|
|
|
|
int crtc)
|
|
|
|
{
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
if (crtc >= adev->mode_info.num_crtc) {
|
|
|
|
DRM_DEBUG("invalid crtc %d\n", crtc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp = RREG32(mmLB_VBLANK_STATUS + crtc_offsets[crtc]);
|
|
|
|
tmp = REG_SET_FIELD(tmp, LB_VBLANK_STATUS, VBLANK_ACK, 1);
|
|
|
|
WREG32(mmLB_VBLANK_STATUS + crtc_offsets[crtc], tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_crtc_vline_int_ack(struct amdgpu_device *adev,
|
|
|
|
int crtc)
|
|
|
|
{
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
if (crtc >= adev->mode_info.num_crtc) {
|
|
|
|
DRM_DEBUG("invalid crtc %d\n", crtc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp = RREG32(mmLB_VLINE_STATUS + crtc_offsets[crtc]);
|
|
|
|
tmp = REG_SET_FIELD(tmp, LB_VLINE_STATUS, VLINE_ACK, 1);
|
|
|
|
WREG32(mmLB_VLINE_STATUS + crtc_offsets[crtc], tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_crtc_irq(struct amdgpu_device *adev,
|
|
|
|
struct amdgpu_irq_src *source,
|
|
|
|
struct amdgpu_iv_entry *entry)
|
|
|
|
{
|
|
|
|
unsigned crtc = entry->src_id - 1;
|
|
|
|
uint32_t disp_int = RREG32(interrupt_status_offsets[crtc].reg);
|
|
|
|
unsigned irq_type = amdgpu_crtc_idx_to_irq_type(adev, crtc);
|
|
|
|
|
2016-11-29 23:02:12 +00:00
|
|
|
switch (entry->src_data[0]) {
|
2015-04-20 21:31:14 +00:00
|
|
|
case 0: /* vblank */
|
2015-07-03 04:03:07 +00:00
|
|
|
if (disp_int & interrupt_status_offsets[crtc].vblank)
|
2015-04-20 21:31:14 +00:00
|
|
|
dce_v10_0_crtc_vblank_int_ack(adev, crtc);
|
2015-07-03 04:03:07 +00:00
|
|
|
else
|
|
|
|
DRM_DEBUG("IH: IH event w/o asserted irq bit?\n");
|
|
|
|
|
|
|
|
if (amdgpu_irq_enabled(adev, source, irq_type)) {
|
|
|
|
drm_handle_vblank(adev->ddev, crtc);
|
2015-04-20 21:31:14 +00:00
|
|
|
}
|
2015-07-03 04:03:07 +00:00
|
|
|
DRM_DEBUG("IH: D%d vblank\n", crtc + 1);
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
break;
|
|
|
|
case 1: /* vline */
|
2015-07-03 04:03:07 +00:00
|
|
|
if (disp_int & interrupt_status_offsets[crtc].vline)
|
2015-04-20 21:31:14 +00:00
|
|
|
dce_v10_0_crtc_vline_int_ack(adev, crtc);
|
2015-07-03 04:03:07 +00:00
|
|
|
else
|
|
|
|
DRM_DEBUG("IH: IH event w/o asserted irq bit?\n");
|
|
|
|
|
|
|
|
DRM_DEBUG("IH: D%d vline\n", crtc + 1);
|
|
|
|
|
2015-04-20 21:31:14 +00:00
|
|
|
break;
|
|
|
|
default:
|
2016-11-29 23:02:12 +00:00
|
|
|
DRM_DEBUG("Unhandled interrupt: %d %d\n", entry->src_id, entry->src_data[0]);
|
2015-04-20 21:31:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dce_v10_0_hpd_irq(struct amdgpu_device *adev,
|
|
|
|
struct amdgpu_irq_src *source,
|
|
|
|
struct amdgpu_iv_entry *entry)
|
|
|
|
{
|
|
|
|
uint32_t disp_int, mask;
|
|
|
|
unsigned hpd;
|
|
|
|
|
2016-11-29 23:02:12 +00:00
|
|
|
if (entry->src_data[0] >= adev->mode_info.num_hpd) {
|
|
|
|
DRM_DEBUG("Unhandled interrupt: %d %d\n", entry->src_id, entry->src_data[0]);
|
2015-04-20 21:31:14 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-29 23:02:12 +00:00
|
|
|
hpd = entry->src_data[0];
|
2015-04-20 21:31:14 +00:00
|
|
|
disp_int = RREG32(interrupt_status_offsets[hpd].reg);
|
|
|
|
mask = interrupt_status_offsets[hpd].hpd;
|
|
|
|
|
|
|
|
if (disp_int & mask) {
|
|
|
|
dce_v10_0_hpd_int_ack(adev, hpd);
|
|
|
|
schedule_work(&adev->hotplug_work);
|
|
|
|
DRM_DEBUG("IH: HPD%d\n", hpd + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_set_clockgating_state(void *handle,
|
|
|
|
enum amd_clockgating_state state)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-22 18:39:35 +00:00
|
|
|
static int dce_v10_0_set_powergating_state(void *handle,
|
|
|
|
enum amd_powergating_state state)
|
2015-04-20 21:31:14 +00:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-10-13 21:41:13 +00:00
|
|
|
static const struct amd_ip_funcs dce_v10_0_ip_funcs = {
|
2016-05-04 18:28:35 +00:00
|
|
|
.name = "dce_v10_0",
|
2015-04-20 21:31:14 +00:00
|
|
|
.early_init = dce_v10_0_early_init,
|
|
|
|
.late_init = NULL,
|
|
|
|
.sw_init = dce_v10_0_sw_init,
|
|
|
|
.sw_fini = dce_v10_0_sw_fini,
|
|
|
|
.hw_init = dce_v10_0_hw_init,
|
|
|
|
.hw_fini = dce_v10_0_hw_fini,
|
|
|
|
.suspend = dce_v10_0_suspend,
|
|
|
|
.resume = dce_v10_0_resume,
|
|
|
|
.is_idle = dce_v10_0_is_idle,
|
|
|
|
.wait_for_idle = dce_v10_0_wait_for_idle,
|
2016-07-20 05:42:25 +00:00
|
|
|
.check_soft_reset = dce_v10_0_check_soft_reset,
|
2015-04-20 21:31:14 +00:00
|
|
|
.soft_reset = dce_v10_0_soft_reset,
|
|
|
|
.set_clockgating_state = dce_v10_0_set_clockgating_state,
|
|
|
|
.set_powergating_state = dce_v10_0_set_powergating_state,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
dce_v10_0_encoder_mode_set(struct drm_encoder *encoder,
|
|
|
|
struct drm_display_mode *mode,
|
|
|
|
struct drm_display_mode *adjusted_mode)
|
|
|
|
{
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
|
|
|
|
amdgpu_encoder->pixel_clock = adjusted_mode->clock;
|
|
|
|
|
|
|
|
/* need to call this here rather than in prepare() since we need some crtc info */
|
|
|
|
amdgpu_atombios_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
|
|
|
|
|
|
|
/* set scaler clears this on some chips */
|
|
|
|
dce_v10_0_set_interleave(encoder->crtc, mode);
|
|
|
|
|
|
|
|
if (amdgpu_atombios_encoder_get_encoder_mode(encoder) == ATOM_ENCODER_MODE_HDMI) {
|
|
|
|
dce_v10_0_afmt_enable(encoder, true);
|
|
|
|
dce_v10_0_afmt_setmode(encoder, adjusted_mode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_encoder_prepare(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct amdgpu_device *adev = encoder->dev->dev_private;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct drm_connector *connector = amdgpu_get_connector_for_encoder(encoder);
|
|
|
|
|
|
|
|
if ((amdgpu_encoder->active_device &
|
|
|
|
(ATOM_DEVICE_DFP_SUPPORT | ATOM_DEVICE_LCD_SUPPORT)) ||
|
|
|
|
(amdgpu_encoder_get_dp_bridge_encoder_id(encoder) !=
|
|
|
|
ENCODER_OBJECT_ID_NONE)) {
|
|
|
|
struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv;
|
|
|
|
if (dig) {
|
|
|
|
dig->dig_encoder = dce_v10_0_pick_dig_encoder(encoder);
|
|
|
|
if (amdgpu_encoder->active_device & ATOM_DEVICE_DFP_SUPPORT)
|
|
|
|
dig->afmt = adev->mode_info.afmt[dig->dig_encoder];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
amdgpu_atombios_scratch_regs_lock(adev, true);
|
|
|
|
|
|
|
|
if (connector) {
|
|
|
|
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
|
|
|
|
|
|
|
|
/* select the clock/data port if it uses a router */
|
|
|
|
if (amdgpu_connector->router.cd_valid)
|
|
|
|
amdgpu_i2c_router_select_cd_port(amdgpu_connector);
|
|
|
|
|
|
|
|
/* turn eDP panel on for mode set */
|
|
|
|
if (connector->connector_type == DRM_MODE_CONNECTOR_eDP)
|
|
|
|
amdgpu_atombios_encoder_set_edp_panel_power(connector,
|
|
|
|
ATOM_TRANSMITTER_ACTION_POWER_ON);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* this is needed for the pll/ss setup to work correctly in some cases */
|
|
|
|
amdgpu_atombios_encoder_set_crtc_source(encoder);
|
|
|
|
/* set up the FMT blocks */
|
|
|
|
dce_v10_0_program_fmt(encoder);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_encoder_commit(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = encoder->dev;
|
|
|
|
struct amdgpu_device *adev = dev->dev_private;
|
|
|
|
|
|
|
|
/* need to call this here as we need the crtc set up */
|
|
|
|
amdgpu_atombios_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
|
|
|
|
amdgpu_atombios_scratch_regs_lock(adev, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_encoder_disable(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
struct amdgpu_encoder_atom_dig *dig;
|
|
|
|
|
|
|
|
amdgpu_atombios_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
|
|
|
|
|
|
|
if (amdgpu_atombios_encoder_is_digital(encoder)) {
|
|
|
|
if (amdgpu_atombios_encoder_get_encoder_mode(encoder) == ATOM_ENCODER_MODE_HDMI)
|
|
|
|
dce_v10_0_afmt_enable(encoder, false);
|
|
|
|
dig = amdgpu_encoder->enc_priv;
|
|
|
|
dig->dig_encoder = -1;
|
|
|
|
}
|
|
|
|
amdgpu_encoder->active_device = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* these are handled by the primary encoders */
|
|
|
|
static void dce_v10_0_ext_prepare(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_ext_commit(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dce_v10_0_ext_mode_set(struct drm_encoder *encoder,
|
|
|
|
struct drm_display_mode *mode,
|
|
|
|
struct drm_display_mode *adjusted_mode)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dce_v10_0_ext_disable(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dce_v10_0_ext_dpms(struct drm_encoder *encoder, int mode)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_encoder_helper_funcs dce_v10_0_ext_helper_funcs = {
|
|
|
|
.dpms = dce_v10_0_ext_dpms,
|
|
|
|
.prepare = dce_v10_0_ext_prepare,
|
|
|
|
.mode_set = dce_v10_0_ext_mode_set,
|
|
|
|
.commit = dce_v10_0_ext_commit,
|
|
|
|
.disable = dce_v10_0_ext_disable,
|
|
|
|
/* no detect for TMDS/LVDS yet */
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct drm_encoder_helper_funcs dce_v10_0_dig_helper_funcs = {
|
|
|
|
.dpms = amdgpu_atombios_encoder_dpms,
|
|
|
|
.mode_fixup = amdgpu_atombios_encoder_mode_fixup,
|
|
|
|
.prepare = dce_v10_0_encoder_prepare,
|
|
|
|
.mode_set = dce_v10_0_encoder_mode_set,
|
|
|
|
.commit = dce_v10_0_encoder_commit,
|
|
|
|
.disable = dce_v10_0_encoder_disable,
|
|
|
|
.detect = amdgpu_atombios_encoder_dig_detect,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct drm_encoder_helper_funcs dce_v10_0_dac_helper_funcs = {
|
|
|
|
.dpms = amdgpu_atombios_encoder_dpms,
|
|
|
|
.mode_fixup = amdgpu_atombios_encoder_mode_fixup,
|
|
|
|
.prepare = dce_v10_0_encoder_prepare,
|
|
|
|
.mode_set = dce_v10_0_encoder_mode_set,
|
|
|
|
.commit = dce_v10_0_encoder_commit,
|
|
|
|
.detect = amdgpu_atombios_encoder_dac_detect,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void dce_v10_0_encoder_destroy(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT))
|
|
|
|
amdgpu_atombios_encoder_fini_backlight(amdgpu_encoder);
|
|
|
|
kfree(amdgpu_encoder->enc_priv);
|
|
|
|
drm_encoder_cleanup(encoder);
|
|
|
|
kfree(amdgpu_encoder);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_encoder_funcs dce_v10_0_encoder_funcs = {
|
|
|
|
.destroy = dce_v10_0_encoder_destroy,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void dce_v10_0_encoder_add(struct amdgpu_device *adev,
|
|
|
|
uint32_t encoder_enum,
|
|
|
|
uint32_t supported_device,
|
|
|
|
u16 caps)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = adev->ddev;
|
|
|
|
struct drm_encoder *encoder;
|
|
|
|
struct amdgpu_encoder *amdgpu_encoder;
|
|
|
|
|
|
|
|
/* see if we already added it */
|
|
|
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
|
|
|
amdgpu_encoder = to_amdgpu_encoder(encoder);
|
|
|
|
if (amdgpu_encoder->encoder_enum == encoder_enum) {
|
|
|
|
amdgpu_encoder->devices |= supported_device;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* add a new one */
|
|
|
|
amdgpu_encoder = kzalloc(sizeof(struct amdgpu_encoder), GFP_KERNEL);
|
|
|
|
if (!amdgpu_encoder)
|
|
|
|
return;
|
|
|
|
|
|
|
|
encoder = &amdgpu_encoder->base;
|
|
|
|
switch (adev->mode_info.num_crtc) {
|
|
|
|
case 1:
|
|
|
|
encoder->possible_crtcs = 0x1;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
default:
|
|
|
|
encoder->possible_crtcs = 0x3;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
encoder->possible_crtcs = 0xf;
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
encoder->possible_crtcs = 0x3f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
amdgpu_encoder->enc_priv = NULL;
|
|
|
|
|
|
|
|
amdgpu_encoder->encoder_enum = encoder_enum;
|
|
|
|
amdgpu_encoder->encoder_id = (encoder_enum & OBJECT_ID_MASK) >> OBJECT_ID_SHIFT;
|
|
|
|
amdgpu_encoder->devices = supported_device;
|
|
|
|
amdgpu_encoder->rmx_type = RMX_OFF;
|
|
|
|
amdgpu_encoder->underscan_type = UNDERSCAN_OFF;
|
|
|
|
amdgpu_encoder->is_ext_encoder = false;
|
|
|
|
amdgpu_encoder->caps = caps;
|
|
|
|
|
|
|
|
switch (amdgpu_encoder->encoder_id) {
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1:
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2:
|
|
|
|
drm_encoder_init(dev, encoder, &dce_v10_0_encoder_funcs,
|
drm: Pass 'name' to drm_encoder_init()
Done with coccinelle for the most part. However, it thinks '...' is
part of the semantic patch, so I put an 'int DOTDOTDOT' placeholder
in its place and got rid of it with sed afterwards.
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
)
{ ... }
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
);
@@
expression E1, E2, E3, E4;
@@
drm_encoder_init(E1, E2, E3, E4
+ ,NULL
)
v2: Add ', or NULL...' to @name kernel doc (Jani)
Annotate the function with __printf() attribute (Jani)
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/1449670818-2966-1-git-send-email-ville.syrjala@linux.intel.com
2015-12-09 14:20:18 +00:00
|
|
|
DRM_MODE_ENCODER_DAC, NULL);
|
2015-04-20 21:31:14 +00:00
|
|
|
drm_encoder_helper_add(encoder, &dce_v10_0_dac_helper_funcs);
|
|
|
|
break;
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1:
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
|
|
|
|
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY3:
|
|
|
|
if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) {
|
|
|
|
amdgpu_encoder->rmx_type = RMX_FULL;
|
|
|
|
drm_encoder_init(dev, encoder, &dce_v10_0_encoder_funcs,
|
drm: Pass 'name' to drm_encoder_init()
Done with coccinelle for the most part. However, it thinks '...' is
part of the semantic patch, so I put an 'int DOTDOTDOT' placeholder
in its place and got rid of it with sed afterwards.
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
)
{ ... }
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
);
@@
expression E1, E2, E3, E4;
@@
drm_encoder_init(E1, E2, E3, E4
+ ,NULL
)
v2: Add ', or NULL...' to @name kernel doc (Jani)
Annotate the function with __printf() attribute (Jani)
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/1449670818-2966-1-git-send-email-ville.syrjala@linux.intel.com
2015-12-09 14:20:18 +00:00
|
|
|
DRM_MODE_ENCODER_LVDS, NULL);
|
2015-04-20 21:31:14 +00:00
|
|
|
amdgpu_encoder->enc_priv = amdgpu_atombios_encoder_get_lcd_info(amdgpu_encoder);
|
|
|
|
} else if (amdgpu_encoder->devices & (ATOM_DEVICE_CRT_SUPPORT)) {
|
|
|
|
drm_encoder_init(dev, encoder, &dce_v10_0_encoder_funcs,
|
drm: Pass 'name' to drm_encoder_init()
Done with coccinelle for the most part. However, it thinks '...' is
part of the semantic patch, so I put an 'int DOTDOTDOT' placeholder
in its place and got rid of it with sed afterwards.
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
)
{ ... }
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
);
@@
expression E1, E2, E3, E4;
@@
drm_encoder_init(E1, E2, E3, E4
+ ,NULL
)
v2: Add ', or NULL...' to @name kernel doc (Jani)
Annotate the function with __printf() attribute (Jani)
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/1449670818-2966-1-git-send-email-ville.syrjala@linux.intel.com
2015-12-09 14:20:18 +00:00
|
|
|
DRM_MODE_ENCODER_DAC, NULL);
|
2015-04-20 21:31:14 +00:00
|
|
|
amdgpu_encoder->enc_priv = amdgpu_atombios_encoder_get_dig_info(amdgpu_encoder);
|
|
|
|
} else {
|
|
|
|
drm_encoder_init(dev, encoder, &dce_v10_0_encoder_funcs,
|
drm: Pass 'name' to drm_encoder_init()
Done with coccinelle for the most part. However, it thinks '...' is
part of the semantic patch, so I put an 'int DOTDOTDOT' placeholder
in its place and got rid of it with sed afterwards.
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
)
{ ... }
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
);
@@
expression E1, E2, E3, E4;
@@
drm_encoder_init(E1, E2, E3, E4
+ ,NULL
)
v2: Add ', or NULL...' to @name kernel doc (Jani)
Annotate the function with __printf() attribute (Jani)
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/1449670818-2966-1-git-send-email-ville.syrjala@linux.intel.com
2015-12-09 14:20:18 +00:00
|
|
|
DRM_MODE_ENCODER_TMDS, NULL);
|
2015-04-20 21:31:14 +00:00
|
|
|
amdgpu_encoder->enc_priv = amdgpu_atombios_encoder_get_dig_info(amdgpu_encoder);
|
|
|
|
}
|
|
|
|
drm_encoder_helper_add(encoder, &dce_v10_0_dig_helper_funcs);
|
|
|
|
break;
|
|
|
|
case ENCODER_OBJECT_ID_SI170B:
|
|
|
|
case ENCODER_OBJECT_ID_CH7303:
|
|
|
|
case ENCODER_OBJECT_ID_EXTERNAL_SDVOA:
|
|
|
|
case ENCODER_OBJECT_ID_EXTERNAL_SDVOB:
|
|
|
|
case ENCODER_OBJECT_ID_TITFP513:
|
|
|
|
case ENCODER_OBJECT_ID_VT1623:
|
|
|
|
case ENCODER_OBJECT_ID_HDMI_SI1930:
|
|
|
|
case ENCODER_OBJECT_ID_TRAVIS:
|
|
|
|
case ENCODER_OBJECT_ID_NUTMEG:
|
|
|
|
/* these are handled by the primary encoders */
|
|
|
|
amdgpu_encoder->is_ext_encoder = true;
|
|
|
|
if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT))
|
|
|
|
drm_encoder_init(dev, encoder, &dce_v10_0_encoder_funcs,
|
drm: Pass 'name' to drm_encoder_init()
Done with coccinelle for the most part. However, it thinks '...' is
part of the semantic patch, so I put an 'int DOTDOTDOT' placeholder
in its place and got rid of it with sed afterwards.
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
)
{ ... }
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
);
@@
expression E1, E2, E3, E4;
@@
drm_encoder_init(E1, E2, E3, E4
+ ,NULL
)
v2: Add ', or NULL...' to @name kernel doc (Jani)
Annotate the function with __printf() attribute (Jani)
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/1449670818-2966-1-git-send-email-ville.syrjala@linux.intel.com
2015-12-09 14:20:18 +00:00
|
|
|
DRM_MODE_ENCODER_LVDS, NULL);
|
2015-04-20 21:31:14 +00:00
|
|
|
else if (amdgpu_encoder->devices & (ATOM_DEVICE_CRT_SUPPORT))
|
|
|
|
drm_encoder_init(dev, encoder, &dce_v10_0_encoder_funcs,
|
drm: Pass 'name' to drm_encoder_init()
Done with coccinelle for the most part. However, it thinks '...' is
part of the semantic patch, so I put an 'int DOTDOTDOT' placeholder
in its place and got rid of it with sed afterwards.
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
)
{ ... }
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
);
@@
expression E1, E2, E3, E4;
@@
drm_encoder_init(E1, E2, E3, E4
+ ,NULL
)
v2: Add ', or NULL...' to @name kernel doc (Jani)
Annotate the function with __printf() attribute (Jani)
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/1449670818-2966-1-git-send-email-ville.syrjala@linux.intel.com
2015-12-09 14:20:18 +00:00
|
|
|
DRM_MODE_ENCODER_DAC, NULL);
|
2015-04-20 21:31:14 +00:00
|
|
|
else
|
|
|
|
drm_encoder_init(dev, encoder, &dce_v10_0_encoder_funcs,
|
drm: Pass 'name' to drm_encoder_init()
Done with coccinelle for the most part. However, it thinks '...' is
part of the semantic patch, so I put an 'int DOTDOTDOT' placeholder
in its place and got rid of it with sed afterwards.
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
)
{ ... }
@@
identifier dev, encoder, funcs;
@@
int drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type
+ ,const char *name, int DOTDOTDOT
);
@@
expression E1, E2, E3, E4;
@@
drm_encoder_init(E1, E2, E3, E4
+ ,NULL
)
v2: Add ', or NULL...' to @name kernel doc (Jani)
Annotate the function with __printf() attribute (Jani)
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/1449670818-2966-1-git-send-email-ville.syrjala@linux.intel.com
2015-12-09 14:20:18 +00:00
|
|
|
DRM_MODE_ENCODER_TMDS, NULL);
|
2015-04-20 21:31:14 +00:00
|
|
|
drm_encoder_helper_add(encoder, &dce_v10_0_ext_helper_funcs);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct amdgpu_display_funcs dce_v10_0_display_funcs = {
|
|
|
|
.bandwidth_update = &dce_v10_0_bandwidth_update,
|
|
|
|
.vblank_get_counter = &dce_v10_0_vblank_get_counter,
|
|
|
|
.vblank_wait = &dce_v10_0_vblank_wait,
|
|
|
|
.backlight_set_level = &amdgpu_atombios_encoder_set_backlight_level,
|
|
|
|
.backlight_get_level = &amdgpu_atombios_encoder_get_backlight_level,
|
|
|
|
.hpd_sense = &dce_v10_0_hpd_sense,
|
|
|
|
.hpd_set_polarity = &dce_v10_0_hpd_set_polarity,
|
|
|
|
.hpd_get_gpio_reg = &dce_v10_0_hpd_get_gpio_reg,
|
|
|
|
.page_flip = &dce_v10_0_page_flip,
|
|
|
|
.page_flip_get_scanoutpos = &dce_v10_0_crtc_get_scanoutpos,
|
|
|
|
.add_encoder = &dce_v10_0_encoder_add,
|
|
|
|
.add_connector = &amdgpu_connector_add,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void dce_v10_0_set_display_funcs(struct amdgpu_device *adev)
|
|
|
|
{
|
|
|
|
if (adev->mode_info.funcs == NULL)
|
|
|
|
adev->mode_info.funcs = &dce_v10_0_display_funcs;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct amdgpu_irq_src_funcs dce_v10_0_crtc_irq_funcs = {
|
|
|
|
.set = dce_v10_0_set_crtc_irq_state,
|
|
|
|
.process = dce_v10_0_crtc_irq,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct amdgpu_irq_src_funcs dce_v10_0_pageflip_irq_funcs = {
|
|
|
|
.set = dce_v10_0_set_pageflip_irq_state,
|
|
|
|
.process = dce_v10_0_pageflip_irq,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct amdgpu_irq_src_funcs dce_v10_0_hpd_irq_funcs = {
|
|
|
|
.set = dce_v10_0_set_hpd_irq_state,
|
|
|
|
.process = dce_v10_0_hpd_irq,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void dce_v10_0_set_irq_funcs(struct amdgpu_device *adev)
|
|
|
|
{
|
2017-10-24 09:03:21 +00:00
|
|
|
if (adev->mode_info.num_crtc > 0)
|
|
|
|
adev->crtc_irq.num_types = AMDGPU_CRTC_IRQ_VLINE1 + adev->mode_info.num_crtc;
|
|
|
|
else
|
|
|
|
adev->crtc_irq.num_types = 0;
|
2015-04-20 21:31:14 +00:00
|
|
|
adev->crtc_irq.funcs = &dce_v10_0_crtc_irq_funcs;
|
|
|
|
|
2017-10-24 09:03:21 +00:00
|
|
|
adev->pageflip_irq.num_types = adev->mode_info.num_crtc;
|
2015-04-20 21:31:14 +00:00
|
|
|
adev->pageflip_irq.funcs = &dce_v10_0_pageflip_irq_funcs;
|
|
|
|
|
2017-10-24 09:03:21 +00:00
|
|
|
adev->hpd_irq.num_types = adev->mode_info.num_hpd;
|
2015-04-20 21:31:14 +00:00
|
|
|
adev->hpd_irq.funcs = &dce_v10_0_hpd_irq_funcs;
|
|
|
|
}
|
2016-10-13 21:41:13 +00:00
|
|
|
|
|
|
|
const struct amdgpu_ip_block_version dce_v10_0_ip_block =
|
|
|
|
{
|
|
|
|
.type = AMD_IP_BLOCK_TYPE_DCE,
|
|
|
|
.major = 10,
|
|
|
|
.minor = 0,
|
|
|
|
.rev = 0,
|
|
|
|
.funcs = &dce_v10_0_ip_funcs,
|
|
|
|
};
|
|
|
|
|
|
|
|
const struct amdgpu_ip_block_version dce_v10_1_ip_block =
|
|
|
|
{
|
|
|
|
.type = AMD_IP_BLOCK_TYPE_DCE,
|
|
|
|
.major = 10,
|
|
|
|
.minor = 1,
|
|
|
|
.rev = 0,
|
|
|
|
.funcs = &dce_v10_0_ip_funcs,
|
|
|
|
};
|