mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-25 23:35:26 +07:00
8072bfa604
i915 doesn't need this kludge for most platforms. Although we do appear to need something similar on certain platforms, but we can be more accurate when we apply the adjustment since we know exactly why the scanline counter doesn't always quite match the vblank status. Also the current code doesn't handle interlaced modes correctly, and we already deal with interlaced modes in i915 code. So let's just move the current code to radeon_get_crtc_scanoutpos() since that's why it was added. For i915 we'll add a more finely targeted variant. v2: Fix vpos vs. *vpos bug (Mario) Reviewed-by: mario.kleiner.de@gmail.com Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
1802 lines
54 KiB
C
1802 lines
54 KiB
C
/*
|
|
* Copyright 2007-8 Advanced Micro Devices, Inc.
|
|
* Copyright 2008 Red Hat Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* Authors: Dave Airlie
|
|
* Alex Deucher
|
|
*/
|
|
#include <drm/drmP.h>
|
|
#include <drm/radeon_drm.h>
|
|
#include "radeon.h"
|
|
|
|
#include "atom.h"
|
|
#include <asm/div64.h>
|
|
|
|
#include <linux/pm_runtime.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_edid.h>
|
|
|
|
static void avivo_crtc_load_lut(struct drm_crtc *crtc)
|
|
{
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
struct drm_device *dev = crtc->dev;
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
int i;
|
|
|
|
DRM_DEBUG_KMS("%d\n", radeon_crtc->crtc_id);
|
|
WREG32(AVIVO_DC_LUTA_CONTROL + radeon_crtc->crtc_offset, 0);
|
|
|
|
WREG32(AVIVO_DC_LUTA_BLACK_OFFSET_BLUE + radeon_crtc->crtc_offset, 0);
|
|
WREG32(AVIVO_DC_LUTA_BLACK_OFFSET_GREEN + radeon_crtc->crtc_offset, 0);
|
|
WREG32(AVIVO_DC_LUTA_BLACK_OFFSET_RED + radeon_crtc->crtc_offset, 0);
|
|
|
|
WREG32(AVIVO_DC_LUTA_WHITE_OFFSET_BLUE + radeon_crtc->crtc_offset, 0xffff);
|
|
WREG32(AVIVO_DC_LUTA_WHITE_OFFSET_GREEN + radeon_crtc->crtc_offset, 0xffff);
|
|
WREG32(AVIVO_DC_LUTA_WHITE_OFFSET_RED + radeon_crtc->crtc_offset, 0xffff);
|
|
|
|
WREG32(AVIVO_DC_LUT_RW_SELECT, radeon_crtc->crtc_id);
|
|
WREG32(AVIVO_DC_LUT_RW_MODE, 0);
|
|
WREG32(AVIVO_DC_LUT_WRITE_EN_MASK, 0x0000003f);
|
|
|
|
WREG8(AVIVO_DC_LUT_RW_INDEX, 0);
|
|
for (i = 0; i < 256; i++) {
|
|
WREG32(AVIVO_DC_LUT_30_COLOR,
|
|
(radeon_crtc->lut_r[i] << 20) |
|
|
(radeon_crtc->lut_g[i] << 10) |
|
|
(radeon_crtc->lut_b[i] << 0));
|
|
}
|
|
|
|
WREG32(AVIVO_D1GRPH_LUT_SEL + radeon_crtc->crtc_offset, radeon_crtc->crtc_id);
|
|
}
|
|
|
|
static void dce4_crtc_load_lut(struct drm_crtc *crtc)
|
|
{
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
struct drm_device *dev = crtc->dev;
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
int i;
|
|
|
|
DRM_DEBUG_KMS("%d\n", radeon_crtc->crtc_id);
|
|
WREG32(EVERGREEN_DC_LUT_CONTROL + radeon_crtc->crtc_offset, 0);
|
|
|
|
WREG32(EVERGREEN_DC_LUT_BLACK_OFFSET_BLUE + radeon_crtc->crtc_offset, 0);
|
|
WREG32(EVERGREEN_DC_LUT_BLACK_OFFSET_GREEN + radeon_crtc->crtc_offset, 0);
|
|
WREG32(EVERGREEN_DC_LUT_BLACK_OFFSET_RED + radeon_crtc->crtc_offset, 0);
|
|
|
|
WREG32(EVERGREEN_DC_LUT_WHITE_OFFSET_BLUE + radeon_crtc->crtc_offset, 0xffff);
|
|
WREG32(EVERGREEN_DC_LUT_WHITE_OFFSET_GREEN + radeon_crtc->crtc_offset, 0xffff);
|
|
WREG32(EVERGREEN_DC_LUT_WHITE_OFFSET_RED + radeon_crtc->crtc_offset, 0xffff);
|
|
|
|
WREG32(EVERGREEN_DC_LUT_RW_MODE + radeon_crtc->crtc_offset, 0);
|
|
WREG32(EVERGREEN_DC_LUT_WRITE_EN_MASK + radeon_crtc->crtc_offset, 0x00000007);
|
|
|
|
WREG32(EVERGREEN_DC_LUT_RW_INDEX + radeon_crtc->crtc_offset, 0);
|
|
for (i = 0; i < 256; i++) {
|
|
WREG32(EVERGREEN_DC_LUT_30_COLOR + radeon_crtc->crtc_offset,
|
|
(radeon_crtc->lut_r[i] << 20) |
|
|
(radeon_crtc->lut_g[i] << 10) |
|
|
(radeon_crtc->lut_b[i] << 0));
|
|
}
|
|
}
|
|
|
|
static void dce5_crtc_load_lut(struct drm_crtc *crtc)
|
|
{
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
struct drm_device *dev = crtc->dev;
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
int i;
|
|
|
|
DRM_DEBUG_KMS("%d\n", radeon_crtc->crtc_id);
|
|
|
|
WREG32(NI_INPUT_CSC_CONTROL + radeon_crtc->crtc_offset,
|
|
(NI_INPUT_CSC_GRPH_MODE(NI_INPUT_CSC_BYPASS) |
|
|
NI_INPUT_CSC_OVL_MODE(NI_INPUT_CSC_BYPASS)));
|
|
WREG32(NI_PRESCALE_GRPH_CONTROL + radeon_crtc->crtc_offset,
|
|
NI_GRPH_PRESCALE_BYPASS);
|
|
WREG32(NI_PRESCALE_OVL_CONTROL + radeon_crtc->crtc_offset,
|
|
NI_OVL_PRESCALE_BYPASS);
|
|
WREG32(NI_INPUT_GAMMA_CONTROL + radeon_crtc->crtc_offset,
|
|
(NI_GRPH_INPUT_GAMMA_MODE(NI_INPUT_GAMMA_USE_LUT) |
|
|
NI_OVL_INPUT_GAMMA_MODE(NI_INPUT_GAMMA_USE_LUT)));
|
|
|
|
WREG32(EVERGREEN_DC_LUT_CONTROL + radeon_crtc->crtc_offset, 0);
|
|
|
|
WREG32(EVERGREEN_DC_LUT_BLACK_OFFSET_BLUE + radeon_crtc->crtc_offset, 0);
|
|
WREG32(EVERGREEN_DC_LUT_BLACK_OFFSET_GREEN + radeon_crtc->crtc_offset, 0);
|
|
WREG32(EVERGREEN_DC_LUT_BLACK_OFFSET_RED + radeon_crtc->crtc_offset, 0);
|
|
|
|
WREG32(EVERGREEN_DC_LUT_WHITE_OFFSET_BLUE + radeon_crtc->crtc_offset, 0xffff);
|
|
WREG32(EVERGREEN_DC_LUT_WHITE_OFFSET_GREEN + radeon_crtc->crtc_offset, 0xffff);
|
|
WREG32(EVERGREEN_DC_LUT_WHITE_OFFSET_RED + radeon_crtc->crtc_offset, 0xffff);
|
|
|
|
WREG32(EVERGREEN_DC_LUT_RW_MODE + radeon_crtc->crtc_offset, 0);
|
|
WREG32(EVERGREEN_DC_LUT_WRITE_EN_MASK + radeon_crtc->crtc_offset, 0x00000007);
|
|
|
|
WREG32(EVERGREEN_DC_LUT_RW_INDEX + radeon_crtc->crtc_offset, 0);
|
|
for (i = 0; i < 256; i++) {
|
|
WREG32(EVERGREEN_DC_LUT_30_COLOR + radeon_crtc->crtc_offset,
|
|
(radeon_crtc->lut_r[i] << 20) |
|
|
(radeon_crtc->lut_g[i] << 10) |
|
|
(radeon_crtc->lut_b[i] << 0));
|
|
}
|
|
|
|
WREG32(NI_DEGAMMA_CONTROL + radeon_crtc->crtc_offset,
|
|
(NI_GRPH_DEGAMMA_MODE(NI_DEGAMMA_BYPASS) |
|
|
NI_OVL_DEGAMMA_MODE(NI_DEGAMMA_BYPASS) |
|
|
NI_ICON_DEGAMMA_MODE(NI_DEGAMMA_BYPASS) |
|
|
NI_CURSOR_DEGAMMA_MODE(NI_DEGAMMA_BYPASS)));
|
|
WREG32(NI_GAMUT_REMAP_CONTROL + radeon_crtc->crtc_offset,
|
|
(NI_GRPH_GAMUT_REMAP_MODE(NI_GAMUT_REMAP_BYPASS) |
|
|
NI_OVL_GAMUT_REMAP_MODE(NI_GAMUT_REMAP_BYPASS)));
|
|
WREG32(NI_REGAMMA_CONTROL + radeon_crtc->crtc_offset,
|
|
(NI_GRPH_REGAMMA_MODE(NI_REGAMMA_BYPASS) |
|
|
NI_OVL_REGAMMA_MODE(NI_REGAMMA_BYPASS)));
|
|
WREG32(NI_OUTPUT_CSC_CONTROL + radeon_crtc->crtc_offset,
|
|
(NI_OUTPUT_CSC_GRPH_MODE(NI_OUTPUT_CSC_BYPASS) |
|
|
NI_OUTPUT_CSC_OVL_MODE(NI_OUTPUT_CSC_BYPASS)));
|
|
/* XXX match this to the depth of the crtc fmt block, move to modeset? */
|
|
WREG32(0x6940 + radeon_crtc->crtc_offset, 0);
|
|
if (ASIC_IS_DCE8(rdev)) {
|
|
/* XXX this only needs to be programmed once per crtc at startup,
|
|
* not sure where the best place for it is
|
|
*/
|
|
WREG32(CIK_ALPHA_CONTROL + radeon_crtc->crtc_offset,
|
|
CIK_CURSOR_ALPHA_BLND_ENA);
|
|
}
|
|
}
|
|
|
|
static void legacy_crtc_load_lut(struct drm_crtc *crtc)
|
|
{
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
struct drm_device *dev = crtc->dev;
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
int i;
|
|
uint32_t dac2_cntl;
|
|
|
|
dac2_cntl = RREG32(RADEON_DAC_CNTL2);
|
|
if (radeon_crtc->crtc_id == 0)
|
|
dac2_cntl &= (uint32_t)~RADEON_DAC2_PALETTE_ACC_CTL;
|
|
else
|
|
dac2_cntl |= RADEON_DAC2_PALETTE_ACC_CTL;
|
|
WREG32(RADEON_DAC_CNTL2, dac2_cntl);
|
|
|
|
WREG8(RADEON_PALETTE_INDEX, 0);
|
|
for (i = 0; i < 256; i++) {
|
|
WREG32(RADEON_PALETTE_30_DATA,
|
|
(radeon_crtc->lut_r[i] << 20) |
|
|
(radeon_crtc->lut_g[i] << 10) |
|
|
(radeon_crtc->lut_b[i] << 0));
|
|
}
|
|
}
|
|
|
|
void radeon_crtc_load_lut(struct drm_crtc *crtc)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
|
|
if (!crtc->enabled)
|
|
return;
|
|
|
|
if (ASIC_IS_DCE5(rdev))
|
|
dce5_crtc_load_lut(crtc);
|
|
else if (ASIC_IS_DCE4(rdev))
|
|
dce4_crtc_load_lut(crtc);
|
|
else if (ASIC_IS_AVIVO(rdev))
|
|
avivo_crtc_load_lut(crtc);
|
|
else
|
|
legacy_crtc_load_lut(crtc);
|
|
}
|
|
|
|
/** Sets the color ramps on behalf of fbcon */
|
|
void radeon_crtc_fb_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
|
|
u16 blue, int regno)
|
|
{
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
|
|
radeon_crtc->lut_r[regno] = red >> 6;
|
|
radeon_crtc->lut_g[regno] = green >> 6;
|
|
radeon_crtc->lut_b[regno] = blue >> 6;
|
|
}
|
|
|
|
/** Gets the color ramps on behalf of fbcon */
|
|
void radeon_crtc_fb_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
|
|
u16 *blue, int regno)
|
|
{
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
|
|
*red = radeon_crtc->lut_r[regno] << 6;
|
|
*green = radeon_crtc->lut_g[regno] << 6;
|
|
*blue = radeon_crtc->lut_b[regno] << 6;
|
|
}
|
|
|
|
static void radeon_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green,
|
|
u16 *blue, uint32_t start, uint32_t size)
|
|
{
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
int end = (start + size > 256) ? 256 : start + size, i;
|
|
|
|
/* userspace palettes are always correct as is */
|
|
for (i = start; i < end; i++) {
|
|
radeon_crtc->lut_r[i] = red[i] >> 6;
|
|
radeon_crtc->lut_g[i] = green[i] >> 6;
|
|
radeon_crtc->lut_b[i] = blue[i] >> 6;
|
|
}
|
|
radeon_crtc_load_lut(crtc);
|
|
}
|
|
|
|
static void radeon_crtc_destroy(struct drm_crtc *crtc)
|
|
{
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
|
|
drm_crtc_cleanup(crtc);
|
|
kfree(radeon_crtc);
|
|
}
|
|
|
|
/*
|
|
* Handle unpin events outside the interrupt handler proper.
|
|
*/
|
|
static void radeon_unpin_work_func(struct work_struct *__work)
|
|
{
|
|
struct radeon_unpin_work *work =
|
|
container_of(__work, struct radeon_unpin_work, work);
|
|
int r;
|
|
|
|
/* unpin of the old buffer */
|
|
r = radeon_bo_reserve(work->old_rbo, false);
|
|
if (likely(r == 0)) {
|
|
r = radeon_bo_unpin(work->old_rbo);
|
|
if (unlikely(r != 0)) {
|
|
DRM_ERROR("failed to unpin buffer after flip\n");
|
|
}
|
|
radeon_bo_unreserve(work->old_rbo);
|
|
} else
|
|
DRM_ERROR("failed to reserve buffer after flip\n");
|
|
|
|
drm_gem_object_unreference_unlocked(&work->old_rbo->gem_base);
|
|
kfree(work);
|
|
}
|
|
|
|
void radeon_crtc_handle_flip(struct radeon_device *rdev, int crtc_id)
|
|
{
|
|
struct radeon_crtc *radeon_crtc = rdev->mode_info.crtcs[crtc_id];
|
|
struct radeon_unpin_work *work;
|
|
unsigned long flags;
|
|
u32 update_pending;
|
|
int vpos, hpos;
|
|
|
|
spin_lock_irqsave(&rdev->ddev->event_lock, flags);
|
|
work = radeon_crtc->unpin_work;
|
|
if (work == NULL ||
|
|
(work->fence && !radeon_fence_signaled(work->fence))) {
|
|
spin_unlock_irqrestore(&rdev->ddev->event_lock, flags);
|
|
return;
|
|
}
|
|
/* New pageflip, or just completion of a previous one? */
|
|
if (!radeon_crtc->deferred_flip_completion) {
|
|
/* do the flip (mmio) */
|
|
update_pending = radeon_page_flip(rdev, crtc_id, work->new_crtc_base);
|
|
} else {
|
|
/* This is just a completion of a flip queued in crtc
|
|
* at last invocation. Make sure we go directly to
|
|
* completion routine.
|
|
*/
|
|
update_pending = 0;
|
|
radeon_crtc->deferred_flip_completion = 0;
|
|
}
|
|
|
|
/* Has the pageflip already completed in crtc, or is it certain
|
|
* to complete in this vblank?
|
|
*/
|
|
if (update_pending &&
|
|
(DRM_SCANOUTPOS_VALID & radeon_get_crtc_scanoutpos(rdev->ddev, crtc_id, 0,
|
|
&vpos, &hpos, NULL, NULL)) &&
|
|
((vpos >= (99 * rdev->mode_info.crtcs[crtc_id]->base.hwmode.crtc_vdisplay)/100) ||
|
|
(vpos < 0 && !ASIC_IS_AVIVO(rdev)))) {
|
|
/* crtc didn't flip in this target vblank interval,
|
|
* but flip is pending in crtc. Based on the current
|
|
* scanout position we know that the current frame is
|
|
* (nearly) complete and the flip will (likely)
|
|
* complete before the start of the next frame.
|
|
*/
|
|
update_pending = 0;
|
|
}
|
|
if (update_pending) {
|
|
/* crtc didn't flip in this target vblank interval,
|
|
* but flip is pending in crtc. It will complete it
|
|
* in next vblank interval, so complete the flip at
|
|
* next vblank irq.
|
|
*/
|
|
radeon_crtc->deferred_flip_completion = 1;
|
|
spin_unlock_irqrestore(&rdev->ddev->event_lock, flags);
|
|
return;
|
|
}
|
|
|
|
/* Pageflip (will be) certainly completed in this vblank. Clean up. */
|
|
radeon_crtc->unpin_work = NULL;
|
|
|
|
/* wakeup userspace */
|
|
if (work->event)
|
|
drm_send_vblank_event(rdev->ddev, crtc_id, work->event);
|
|
|
|
spin_unlock_irqrestore(&rdev->ddev->event_lock, flags);
|
|
|
|
drm_vblank_put(rdev->ddev, radeon_crtc->crtc_id);
|
|
radeon_fence_unref(&work->fence);
|
|
radeon_post_page_flip(work->rdev, work->crtc_id);
|
|
schedule_work(&work->work);
|
|
}
|
|
|
|
static int radeon_crtc_page_flip(struct drm_crtc *crtc,
|
|
struct drm_framebuffer *fb,
|
|
struct drm_pending_vblank_event *event,
|
|
uint32_t page_flip_flags)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
struct radeon_framebuffer *old_radeon_fb;
|
|
struct radeon_framebuffer *new_radeon_fb;
|
|
struct drm_gem_object *obj;
|
|
struct radeon_bo *rbo;
|
|
struct radeon_unpin_work *work;
|
|
unsigned long flags;
|
|
u32 tiling_flags, pitch_pixels;
|
|
u64 base;
|
|
int r;
|
|
|
|
work = kzalloc(sizeof *work, GFP_KERNEL);
|
|
if (work == NULL)
|
|
return -ENOMEM;
|
|
|
|
work->event = event;
|
|
work->rdev = rdev;
|
|
work->crtc_id = radeon_crtc->crtc_id;
|
|
old_radeon_fb = to_radeon_framebuffer(crtc->fb);
|
|
new_radeon_fb = to_radeon_framebuffer(fb);
|
|
/* schedule unpin of the old buffer */
|
|
obj = old_radeon_fb->obj;
|
|
/* take a reference to the old object */
|
|
drm_gem_object_reference(obj);
|
|
rbo = gem_to_radeon_bo(obj);
|
|
work->old_rbo = rbo;
|
|
obj = new_radeon_fb->obj;
|
|
rbo = gem_to_radeon_bo(obj);
|
|
|
|
spin_lock(&rbo->tbo.bdev->fence_lock);
|
|
if (rbo->tbo.sync_obj)
|
|
work->fence = radeon_fence_ref(rbo->tbo.sync_obj);
|
|
spin_unlock(&rbo->tbo.bdev->fence_lock);
|
|
|
|
INIT_WORK(&work->work, radeon_unpin_work_func);
|
|
|
|
/* We borrow the event spin lock for protecting unpin_work */
|
|
spin_lock_irqsave(&dev->event_lock, flags);
|
|
if (radeon_crtc->unpin_work) {
|
|
DRM_DEBUG_DRIVER("flip queue: crtc already busy\n");
|
|
r = -EBUSY;
|
|
goto unlock_free;
|
|
}
|
|
radeon_crtc->unpin_work = work;
|
|
radeon_crtc->deferred_flip_completion = 0;
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
|
/* pin the new buffer */
|
|
DRM_DEBUG_DRIVER("flip-ioctl() cur_fbo = %p, cur_bbo = %p\n",
|
|
work->old_rbo, rbo);
|
|
|
|
r = radeon_bo_reserve(rbo, false);
|
|
if (unlikely(r != 0)) {
|
|
DRM_ERROR("failed to reserve new rbo buffer before flip\n");
|
|
goto pflip_cleanup;
|
|
}
|
|
/* Only 27 bit offset for legacy CRTC */
|
|
r = radeon_bo_pin_restricted(rbo, RADEON_GEM_DOMAIN_VRAM,
|
|
ASIC_IS_AVIVO(rdev) ? 0 : 1 << 27, &base);
|
|
if (unlikely(r != 0)) {
|
|
radeon_bo_unreserve(rbo);
|
|
r = -EINVAL;
|
|
DRM_ERROR("failed to pin new rbo buffer before flip\n");
|
|
goto pflip_cleanup;
|
|
}
|
|
radeon_bo_get_tiling_flags(rbo, &tiling_flags, NULL);
|
|
radeon_bo_unreserve(rbo);
|
|
|
|
if (!ASIC_IS_AVIVO(rdev)) {
|
|
/* crtc offset is from display base addr not FB location */
|
|
base -= radeon_crtc->legacy_display_base_addr;
|
|
pitch_pixels = fb->pitches[0] / (fb->bits_per_pixel / 8);
|
|
|
|
if (tiling_flags & RADEON_TILING_MACRO) {
|
|
if (ASIC_IS_R300(rdev)) {
|
|
base &= ~0x7ff;
|
|
} else {
|
|
int byteshift = fb->bits_per_pixel >> 4;
|
|
int tile_addr = (((crtc->y >> 3) * pitch_pixels + crtc->x) >> (8 - byteshift)) << 11;
|
|
base += tile_addr + ((crtc->x << byteshift) % 256) + ((crtc->y % 8) << 8);
|
|
}
|
|
} else {
|
|
int offset = crtc->y * pitch_pixels + crtc->x;
|
|
switch (fb->bits_per_pixel) {
|
|
case 8:
|
|
default:
|
|
offset *= 1;
|
|
break;
|
|
case 15:
|
|
case 16:
|
|
offset *= 2;
|
|
break;
|
|
case 24:
|
|
offset *= 3;
|
|
break;
|
|
case 32:
|
|
offset *= 4;
|
|
break;
|
|
}
|
|
base += offset;
|
|
}
|
|
base &= ~7;
|
|
}
|
|
|
|
spin_lock_irqsave(&dev->event_lock, flags);
|
|
work->new_crtc_base = base;
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
|
/* update crtc fb */
|
|
crtc->fb = fb;
|
|
|
|
r = drm_vblank_get(dev, radeon_crtc->crtc_id);
|
|
if (r) {
|
|
DRM_ERROR("failed to get vblank before flip\n");
|
|
goto pflip_cleanup1;
|
|
}
|
|
|
|
/* set the proper interrupt */
|
|
radeon_pre_page_flip(rdev, radeon_crtc->crtc_id);
|
|
|
|
return 0;
|
|
|
|
pflip_cleanup1:
|
|
if (unlikely(radeon_bo_reserve(rbo, false) != 0)) {
|
|
DRM_ERROR("failed to reserve new rbo in error path\n");
|
|
goto pflip_cleanup;
|
|
}
|
|
if (unlikely(radeon_bo_unpin(rbo) != 0)) {
|
|
DRM_ERROR("failed to unpin new rbo in error path\n");
|
|
}
|
|
radeon_bo_unreserve(rbo);
|
|
|
|
pflip_cleanup:
|
|
spin_lock_irqsave(&dev->event_lock, flags);
|
|
radeon_crtc->unpin_work = NULL;
|
|
unlock_free:
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
drm_gem_object_unreference_unlocked(old_radeon_fb->obj);
|
|
radeon_fence_unref(&work->fence);
|
|
kfree(work);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
radeon_crtc_set_config(struct drm_mode_set *set)
|
|
{
|
|
struct drm_device *dev;
|
|
struct radeon_device *rdev;
|
|
struct drm_crtc *crtc;
|
|
bool active = false;
|
|
int ret;
|
|
|
|
if (!set || !set->crtc)
|
|
return -EINVAL;
|
|
|
|
dev = set->crtc->dev;
|
|
|
|
ret = pm_runtime_get_sync(dev->dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = drm_crtc_helper_set_config(set);
|
|
|
|
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
|
|
if (crtc->enabled)
|
|
active = true;
|
|
|
|
pm_runtime_mark_last_busy(dev->dev);
|
|
|
|
rdev = dev->dev_private;
|
|
/* if we have active crtcs and we don't have a power ref,
|
|
take the current one */
|
|
if (active && !rdev->have_disp_power_ref) {
|
|
rdev->have_disp_power_ref = true;
|
|
return ret;
|
|
}
|
|
/* if we have no active crtcs, then drop the power ref
|
|
we got before */
|
|
if (!active && rdev->have_disp_power_ref) {
|
|
pm_runtime_put_autosuspend(dev->dev);
|
|
rdev->have_disp_power_ref = false;
|
|
}
|
|
|
|
/* drop the power reference we got coming in here */
|
|
pm_runtime_put_autosuspend(dev->dev);
|
|
return ret;
|
|
}
|
|
static const struct drm_crtc_funcs radeon_crtc_funcs = {
|
|
.cursor_set = radeon_crtc_cursor_set,
|
|
.cursor_move = radeon_crtc_cursor_move,
|
|
.gamma_set = radeon_crtc_gamma_set,
|
|
.set_config = radeon_crtc_set_config,
|
|
.destroy = radeon_crtc_destroy,
|
|
.page_flip = radeon_crtc_page_flip,
|
|
};
|
|
|
|
static void radeon_crtc_init(struct drm_device *dev, int index)
|
|
{
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
struct radeon_crtc *radeon_crtc;
|
|
int i;
|
|
|
|
radeon_crtc = kzalloc(sizeof(struct radeon_crtc) + (RADEONFB_CONN_LIMIT * sizeof(struct drm_connector *)), GFP_KERNEL);
|
|
if (radeon_crtc == NULL)
|
|
return;
|
|
|
|
drm_crtc_init(dev, &radeon_crtc->base, &radeon_crtc_funcs);
|
|
|
|
drm_mode_crtc_set_gamma_size(&radeon_crtc->base, 256);
|
|
radeon_crtc->crtc_id = index;
|
|
rdev->mode_info.crtcs[index] = radeon_crtc;
|
|
|
|
if (rdev->family >= CHIP_BONAIRE) {
|
|
radeon_crtc->max_cursor_width = CIK_CURSOR_WIDTH;
|
|
radeon_crtc->max_cursor_height = CIK_CURSOR_HEIGHT;
|
|
} else {
|
|
radeon_crtc->max_cursor_width = CURSOR_WIDTH;
|
|
radeon_crtc->max_cursor_height = CURSOR_HEIGHT;
|
|
}
|
|
|
|
#if 0
|
|
radeon_crtc->mode_set.crtc = &radeon_crtc->base;
|
|
radeon_crtc->mode_set.connectors = (struct drm_connector **)(radeon_crtc + 1);
|
|
radeon_crtc->mode_set.num_connectors = 0;
|
|
#endif
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
radeon_crtc->lut_r[i] = i << 2;
|
|
radeon_crtc->lut_g[i] = i << 2;
|
|
radeon_crtc->lut_b[i] = i << 2;
|
|
}
|
|
|
|
if (rdev->is_atom_bios && (ASIC_IS_AVIVO(rdev) || radeon_r4xx_atom))
|
|
radeon_atombios_init_crtc(dev, radeon_crtc);
|
|
else
|
|
radeon_legacy_init_crtc(dev, radeon_crtc);
|
|
}
|
|
|
|
static const char *encoder_names[38] = {
|
|
"NONE",
|
|
"INTERNAL_LVDS",
|
|
"INTERNAL_TMDS1",
|
|
"INTERNAL_TMDS2",
|
|
"INTERNAL_DAC1",
|
|
"INTERNAL_DAC2",
|
|
"INTERNAL_SDVOA",
|
|
"INTERNAL_SDVOB",
|
|
"SI170B",
|
|
"CH7303",
|
|
"CH7301",
|
|
"INTERNAL_DVO1",
|
|
"EXTERNAL_SDVOA",
|
|
"EXTERNAL_SDVOB",
|
|
"TITFP513",
|
|
"INTERNAL_LVTM1",
|
|
"VT1623",
|
|
"HDMI_SI1930",
|
|
"HDMI_INTERNAL",
|
|
"INTERNAL_KLDSCP_TMDS1",
|
|
"INTERNAL_KLDSCP_DVO1",
|
|
"INTERNAL_KLDSCP_DAC1",
|
|
"INTERNAL_KLDSCP_DAC2",
|
|
"SI178",
|
|
"MVPU_FPGA",
|
|
"INTERNAL_DDI",
|
|
"VT1625",
|
|
"HDMI_SI1932",
|
|
"DP_AN9801",
|
|
"DP_DP501",
|
|
"INTERNAL_UNIPHY",
|
|
"INTERNAL_KLDSCP_LVTMA",
|
|
"INTERNAL_UNIPHY1",
|
|
"INTERNAL_UNIPHY2",
|
|
"NUTMEG",
|
|
"TRAVIS",
|
|
"INTERNAL_VCE",
|
|
"INTERNAL_UNIPHY3",
|
|
};
|
|
|
|
static const char *hpd_names[6] = {
|
|
"HPD1",
|
|
"HPD2",
|
|
"HPD3",
|
|
"HPD4",
|
|
"HPD5",
|
|
"HPD6",
|
|
};
|
|
|
|
static void radeon_print_display_setup(struct drm_device *dev)
|
|
{
|
|
struct drm_connector *connector;
|
|
struct radeon_connector *radeon_connector;
|
|
struct drm_encoder *encoder;
|
|
struct radeon_encoder *radeon_encoder;
|
|
uint32_t devices;
|
|
int i = 0;
|
|
|
|
DRM_INFO("Radeon Display Connectors\n");
|
|
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
|
radeon_connector = to_radeon_connector(connector);
|
|
DRM_INFO("Connector %d:\n", i);
|
|
DRM_INFO(" %s\n", drm_get_connector_name(connector));
|
|
if (radeon_connector->hpd.hpd != RADEON_HPD_NONE)
|
|
DRM_INFO(" %s\n", hpd_names[radeon_connector->hpd.hpd]);
|
|
if (radeon_connector->ddc_bus) {
|
|
DRM_INFO(" DDC: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
|
|
radeon_connector->ddc_bus->rec.mask_clk_reg,
|
|
radeon_connector->ddc_bus->rec.mask_data_reg,
|
|
radeon_connector->ddc_bus->rec.a_clk_reg,
|
|
radeon_connector->ddc_bus->rec.a_data_reg,
|
|
radeon_connector->ddc_bus->rec.en_clk_reg,
|
|
radeon_connector->ddc_bus->rec.en_data_reg,
|
|
radeon_connector->ddc_bus->rec.y_clk_reg,
|
|
radeon_connector->ddc_bus->rec.y_data_reg);
|
|
if (radeon_connector->router.ddc_valid)
|
|
DRM_INFO(" DDC Router 0x%x/0x%x\n",
|
|
radeon_connector->router.ddc_mux_control_pin,
|
|
radeon_connector->router.ddc_mux_state);
|
|
if (radeon_connector->router.cd_valid)
|
|
DRM_INFO(" Clock/Data Router 0x%x/0x%x\n",
|
|
radeon_connector->router.cd_mux_control_pin,
|
|
radeon_connector->router.cd_mux_state);
|
|
} else {
|
|
if (connector->connector_type == DRM_MODE_CONNECTOR_VGA ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_DVII ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_DVID ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_DVIA ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_HDMIA ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_HDMIB)
|
|
DRM_INFO(" DDC: no ddc bus - possible BIOS bug - please report to xorg-driver-ati@lists.x.org\n");
|
|
}
|
|
DRM_INFO(" Encoders:\n");
|
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
|
radeon_encoder = to_radeon_encoder(encoder);
|
|
devices = radeon_encoder->devices & radeon_connector->devices;
|
|
if (devices) {
|
|
if (devices & ATOM_DEVICE_CRT1_SUPPORT)
|
|
DRM_INFO(" CRT1: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_CRT2_SUPPORT)
|
|
DRM_INFO(" CRT2: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_LCD1_SUPPORT)
|
|
DRM_INFO(" LCD1: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_DFP1_SUPPORT)
|
|
DRM_INFO(" DFP1: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_DFP2_SUPPORT)
|
|
DRM_INFO(" DFP2: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_DFP3_SUPPORT)
|
|
DRM_INFO(" DFP3: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_DFP4_SUPPORT)
|
|
DRM_INFO(" DFP4: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_DFP5_SUPPORT)
|
|
DRM_INFO(" DFP5: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_DFP6_SUPPORT)
|
|
DRM_INFO(" DFP6: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_TV1_SUPPORT)
|
|
DRM_INFO(" TV1: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
if (devices & ATOM_DEVICE_CV_SUPPORT)
|
|
DRM_INFO(" CV: %s\n", encoder_names[radeon_encoder->encoder_id]);
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static bool radeon_setup_enc_conn(struct drm_device *dev)
|
|
{
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
bool ret = false;
|
|
|
|
if (rdev->bios) {
|
|
if (rdev->is_atom_bios) {
|
|
ret = radeon_get_atom_connector_info_from_supported_devices_table(dev);
|
|
if (ret == false)
|
|
ret = radeon_get_atom_connector_info_from_object_table(dev);
|
|
} else {
|
|
ret = radeon_get_legacy_connector_info_from_bios(dev);
|
|
if (ret == false)
|
|
ret = radeon_get_legacy_connector_info_from_table(dev);
|
|
}
|
|
} else {
|
|
if (!ASIC_IS_AVIVO(rdev))
|
|
ret = radeon_get_legacy_connector_info_from_table(dev);
|
|
}
|
|
if (ret) {
|
|
radeon_setup_encoder_clones(dev);
|
|
radeon_print_display_setup(dev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int radeon_ddc_get_modes(struct radeon_connector *radeon_connector)
|
|
{
|
|
struct drm_device *dev = radeon_connector->base.dev;
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
int ret = 0;
|
|
|
|
/* on hw with routers, select right port */
|
|
if (radeon_connector->router.ddc_valid)
|
|
radeon_router_select_ddc_port(radeon_connector);
|
|
|
|
if (radeon_connector_encoder_get_dp_bridge_encoder_id(&radeon_connector->base) !=
|
|
ENCODER_OBJECT_ID_NONE) {
|
|
struct radeon_connector_atom_dig *dig = radeon_connector->con_priv;
|
|
|
|
if (dig->dp_i2c_bus)
|
|
radeon_connector->edid = drm_get_edid(&radeon_connector->base,
|
|
&dig->dp_i2c_bus->adapter);
|
|
} else if ((radeon_connector->base.connector_type == DRM_MODE_CONNECTOR_DisplayPort) ||
|
|
(radeon_connector->base.connector_type == DRM_MODE_CONNECTOR_eDP)) {
|
|
struct radeon_connector_atom_dig *dig = radeon_connector->con_priv;
|
|
|
|
if ((dig->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT ||
|
|
dig->dp_sink_type == CONNECTOR_OBJECT_ID_eDP) && dig->dp_i2c_bus)
|
|
radeon_connector->edid = drm_get_edid(&radeon_connector->base,
|
|
&dig->dp_i2c_bus->adapter);
|
|
else if (radeon_connector->ddc_bus && !radeon_connector->edid)
|
|
radeon_connector->edid = drm_get_edid(&radeon_connector->base,
|
|
&radeon_connector->ddc_bus->adapter);
|
|
} else {
|
|
if (radeon_connector->ddc_bus && !radeon_connector->edid)
|
|
radeon_connector->edid = drm_get_edid(&radeon_connector->base,
|
|
&radeon_connector->ddc_bus->adapter);
|
|
}
|
|
|
|
if (!radeon_connector->edid) {
|
|
if (rdev->is_atom_bios) {
|
|
/* some laptops provide a hardcoded edid in rom for LCDs */
|
|
if (((radeon_connector->base.connector_type == DRM_MODE_CONNECTOR_LVDS) ||
|
|
(radeon_connector->base.connector_type == DRM_MODE_CONNECTOR_eDP)))
|
|
radeon_connector->edid = radeon_bios_get_hardcoded_edid(rdev);
|
|
} else
|
|
/* some servers provide a hardcoded edid in rom for KVMs */
|
|
radeon_connector->edid = radeon_bios_get_hardcoded_edid(rdev);
|
|
}
|
|
if (radeon_connector->edid) {
|
|
drm_mode_connector_update_edid_property(&radeon_connector->base, radeon_connector->edid);
|
|
ret = drm_add_edid_modes(&radeon_connector->base, radeon_connector->edid);
|
|
return ret;
|
|
}
|
|
drm_mode_connector_update_edid_property(&radeon_connector->base, NULL);
|
|
return 0;
|
|
}
|
|
|
|
/* avivo */
|
|
static void avivo_get_fb_div(struct radeon_pll *pll,
|
|
u32 target_clock,
|
|
u32 post_div,
|
|
u32 ref_div,
|
|
u32 *fb_div,
|
|
u32 *frac_fb_div)
|
|
{
|
|
u32 tmp = post_div * ref_div;
|
|
|
|
tmp *= target_clock;
|
|
*fb_div = tmp / pll->reference_freq;
|
|
*frac_fb_div = tmp % pll->reference_freq;
|
|
|
|
if (*fb_div > pll->max_feedback_div)
|
|
*fb_div = pll->max_feedback_div;
|
|
else if (*fb_div < pll->min_feedback_div)
|
|
*fb_div = pll->min_feedback_div;
|
|
}
|
|
|
|
static u32 avivo_get_post_div(struct radeon_pll *pll,
|
|
u32 target_clock)
|
|
{
|
|
u32 vco, post_div, tmp;
|
|
|
|
if (pll->flags & RADEON_PLL_USE_POST_DIV)
|
|
return pll->post_div;
|
|
|
|
if (pll->flags & RADEON_PLL_PREFER_MINM_OVER_MAXP) {
|
|
if (pll->flags & RADEON_PLL_IS_LCD)
|
|
vco = pll->lcd_pll_out_min;
|
|
else
|
|
vco = pll->pll_out_min;
|
|
} else {
|
|
if (pll->flags & RADEON_PLL_IS_LCD)
|
|
vco = pll->lcd_pll_out_max;
|
|
else
|
|
vco = pll->pll_out_max;
|
|
}
|
|
|
|
post_div = vco / target_clock;
|
|
tmp = vco % target_clock;
|
|
|
|
if (pll->flags & RADEON_PLL_PREFER_MINM_OVER_MAXP) {
|
|
if (tmp)
|
|
post_div++;
|
|
} else {
|
|
if (!tmp)
|
|
post_div--;
|
|
}
|
|
|
|
if (post_div > pll->max_post_div)
|
|
post_div = pll->max_post_div;
|
|
else if (post_div < pll->min_post_div)
|
|
post_div = pll->min_post_div;
|
|
|
|
return post_div;
|
|
}
|
|
|
|
#define MAX_TOLERANCE 10
|
|
|
|
void radeon_compute_pll_avivo(struct radeon_pll *pll,
|
|
u32 freq,
|
|
u32 *dot_clock_p,
|
|
u32 *fb_div_p,
|
|
u32 *frac_fb_div_p,
|
|
u32 *ref_div_p,
|
|
u32 *post_div_p)
|
|
{
|
|
u32 target_clock = freq / 10;
|
|
u32 post_div = avivo_get_post_div(pll, target_clock);
|
|
u32 ref_div = pll->min_ref_div;
|
|
u32 fb_div = 0, frac_fb_div = 0, tmp;
|
|
|
|
if (pll->flags & RADEON_PLL_USE_REF_DIV)
|
|
ref_div = pll->reference_div;
|
|
|
|
if (pll->flags & RADEON_PLL_USE_FRAC_FB_DIV) {
|
|
avivo_get_fb_div(pll, target_clock, post_div, ref_div, &fb_div, &frac_fb_div);
|
|
frac_fb_div = (100 * frac_fb_div) / pll->reference_freq;
|
|
if (frac_fb_div >= 5) {
|
|
frac_fb_div -= 5;
|
|
frac_fb_div = frac_fb_div / 10;
|
|
frac_fb_div++;
|
|
}
|
|
if (frac_fb_div >= 10) {
|
|
fb_div++;
|
|
frac_fb_div = 0;
|
|
}
|
|
} else {
|
|
while (ref_div <= pll->max_ref_div) {
|
|
avivo_get_fb_div(pll, target_clock, post_div, ref_div,
|
|
&fb_div, &frac_fb_div);
|
|
if (frac_fb_div >= (pll->reference_freq / 2))
|
|
fb_div++;
|
|
frac_fb_div = 0;
|
|
tmp = (pll->reference_freq * fb_div) / (post_div * ref_div);
|
|
tmp = (tmp * 10000) / target_clock;
|
|
|
|
if (tmp > (10000 + MAX_TOLERANCE))
|
|
ref_div++;
|
|
else if (tmp >= (10000 - MAX_TOLERANCE))
|
|
break;
|
|
else
|
|
ref_div++;
|
|
}
|
|
}
|
|
|
|
*dot_clock_p = ((pll->reference_freq * fb_div * 10) + (pll->reference_freq * frac_fb_div)) /
|
|
(ref_div * post_div * 10);
|
|
*fb_div_p = fb_div;
|
|
*frac_fb_div_p = frac_fb_div;
|
|
*ref_div_p = ref_div;
|
|
*post_div_p = post_div;
|
|
DRM_DEBUG_KMS("%d, pll dividers - fb: %d.%d ref: %d, post %d\n",
|
|
*dot_clock_p, fb_div, frac_fb_div, ref_div, post_div);
|
|
}
|
|
|
|
/* pre-avivo */
|
|
static inline uint32_t radeon_div(uint64_t n, uint32_t d)
|
|
{
|
|
uint64_t mod;
|
|
|
|
n += d / 2;
|
|
|
|
mod = do_div(n, d);
|
|
return n;
|
|
}
|
|
|
|
void radeon_compute_pll_legacy(struct radeon_pll *pll,
|
|
uint64_t freq,
|
|
uint32_t *dot_clock_p,
|
|
uint32_t *fb_div_p,
|
|
uint32_t *frac_fb_div_p,
|
|
uint32_t *ref_div_p,
|
|
uint32_t *post_div_p)
|
|
{
|
|
uint32_t min_ref_div = pll->min_ref_div;
|
|
uint32_t max_ref_div = pll->max_ref_div;
|
|
uint32_t min_post_div = pll->min_post_div;
|
|
uint32_t max_post_div = pll->max_post_div;
|
|
uint32_t min_fractional_feed_div = 0;
|
|
uint32_t max_fractional_feed_div = 0;
|
|
uint32_t best_vco = pll->best_vco;
|
|
uint32_t best_post_div = 1;
|
|
uint32_t best_ref_div = 1;
|
|
uint32_t best_feedback_div = 1;
|
|
uint32_t best_frac_feedback_div = 0;
|
|
uint32_t best_freq = -1;
|
|
uint32_t best_error = 0xffffffff;
|
|
uint32_t best_vco_diff = 1;
|
|
uint32_t post_div;
|
|
u32 pll_out_min, pll_out_max;
|
|
|
|
DRM_DEBUG_KMS("PLL freq %llu %u %u\n", freq, pll->min_ref_div, pll->max_ref_div);
|
|
freq = freq * 1000;
|
|
|
|
if (pll->flags & RADEON_PLL_IS_LCD) {
|
|
pll_out_min = pll->lcd_pll_out_min;
|
|
pll_out_max = pll->lcd_pll_out_max;
|
|
} else {
|
|
pll_out_min = pll->pll_out_min;
|
|
pll_out_max = pll->pll_out_max;
|
|
}
|
|
|
|
if (pll_out_min > 64800)
|
|
pll_out_min = 64800;
|
|
|
|
if (pll->flags & RADEON_PLL_USE_REF_DIV)
|
|
min_ref_div = max_ref_div = pll->reference_div;
|
|
else {
|
|
while (min_ref_div < max_ref_div-1) {
|
|
uint32_t mid = (min_ref_div + max_ref_div) / 2;
|
|
uint32_t pll_in = pll->reference_freq / mid;
|
|
if (pll_in < pll->pll_in_min)
|
|
max_ref_div = mid;
|
|
else if (pll_in > pll->pll_in_max)
|
|
min_ref_div = mid;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pll->flags & RADEON_PLL_USE_POST_DIV)
|
|
min_post_div = max_post_div = pll->post_div;
|
|
|
|
if (pll->flags & RADEON_PLL_USE_FRAC_FB_DIV) {
|
|
min_fractional_feed_div = pll->min_frac_feedback_div;
|
|
max_fractional_feed_div = pll->max_frac_feedback_div;
|
|
}
|
|
|
|
for (post_div = max_post_div; post_div >= min_post_div; --post_div) {
|
|
uint32_t ref_div;
|
|
|
|
if ((pll->flags & RADEON_PLL_NO_ODD_POST_DIV) && (post_div & 1))
|
|
continue;
|
|
|
|
/* legacy radeons only have a few post_divs */
|
|
if (pll->flags & RADEON_PLL_LEGACY) {
|
|
if ((post_div == 5) ||
|
|
(post_div == 7) ||
|
|
(post_div == 9) ||
|
|
(post_div == 10) ||
|
|
(post_div == 11) ||
|
|
(post_div == 13) ||
|
|
(post_div == 14) ||
|
|
(post_div == 15))
|
|
continue;
|
|
}
|
|
|
|
for (ref_div = min_ref_div; ref_div <= max_ref_div; ++ref_div) {
|
|
uint32_t feedback_div, current_freq = 0, error, vco_diff;
|
|
uint32_t pll_in = pll->reference_freq / ref_div;
|
|
uint32_t min_feed_div = pll->min_feedback_div;
|
|
uint32_t max_feed_div = pll->max_feedback_div + 1;
|
|
|
|
if (pll_in < pll->pll_in_min || pll_in > pll->pll_in_max)
|
|
continue;
|
|
|
|
while (min_feed_div < max_feed_div) {
|
|
uint32_t vco;
|
|
uint32_t min_frac_feed_div = min_fractional_feed_div;
|
|
uint32_t max_frac_feed_div = max_fractional_feed_div + 1;
|
|
uint32_t frac_feedback_div;
|
|
uint64_t tmp;
|
|
|
|
feedback_div = (min_feed_div + max_feed_div) / 2;
|
|
|
|
tmp = (uint64_t)pll->reference_freq * feedback_div;
|
|
vco = radeon_div(tmp, ref_div);
|
|
|
|
if (vco < pll_out_min) {
|
|
min_feed_div = feedback_div + 1;
|
|
continue;
|
|
} else if (vco > pll_out_max) {
|
|
max_feed_div = feedback_div;
|
|
continue;
|
|
}
|
|
|
|
while (min_frac_feed_div < max_frac_feed_div) {
|
|
frac_feedback_div = (min_frac_feed_div + max_frac_feed_div) / 2;
|
|
tmp = (uint64_t)pll->reference_freq * 10000 * feedback_div;
|
|
tmp += (uint64_t)pll->reference_freq * 1000 * frac_feedback_div;
|
|
current_freq = radeon_div(tmp, ref_div * post_div);
|
|
|
|
if (pll->flags & RADEON_PLL_PREFER_CLOSEST_LOWER) {
|
|
if (freq < current_freq)
|
|
error = 0xffffffff;
|
|
else
|
|
error = freq - current_freq;
|
|
} else
|
|
error = abs(current_freq - freq);
|
|
vco_diff = abs(vco - best_vco);
|
|
|
|
if ((best_vco == 0 && error < best_error) ||
|
|
(best_vco != 0 &&
|
|
((best_error > 100 && error < best_error - 100) ||
|
|
(abs(error - best_error) < 100 && vco_diff < best_vco_diff)))) {
|
|
best_post_div = post_div;
|
|
best_ref_div = ref_div;
|
|
best_feedback_div = feedback_div;
|
|
best_frac_feedback_div = frac_feedback_div;
|
|
best_freq = current_freq;
|
|
best_error = error;
|
|
best_vco_diff = vco_diff;
|
|
} else if (current_freq == freq) {
|
|
if (best_freq == -1) {
|
|
best_post_div = post_div;
|
|
best_ref_div = ref_div;
|
|
best_feedback_div = feedback_div;
|
|
best_frac_feedback_div = frac_feedback_div;
|
|
best_freq = current_freq;
|
|
best_error = error;
|
|
best_vco_diff = vco_diff;
|
|
} else if (((pll->flags & RADEON_PLL_PREFER_LOW_REF_DIV) && (ref_div < best_ref_div)) ||
|
|
((pll->flags & RADEON_PLL_PREFER_HIGH_REF_DIV) && (ref_div > best_ref_div)) ||
|
|
((pll->flags & RADEON_PLL_PREFER_LOW_FB_DIV) && (feedback_div < best_feedback_div)) ||
|
|
((pll->flags & RADEON_PLL_PREFER_HIGH_FB_DIV) && (feedback_div > best_feedback_div)) ||
|
|
((pll->flags & RADEON_PLL_PREFER_LOW_POST_DIV) && (post_div < best_post_div)) ||
|
|
((pll->flags & RADEON_PLL_PREFER_HIGH_POST_DIV) && (post_div > best_post_div))) {
|
|
best_post_div = post_div;
|
|
best_ref_div = ref_div;
|
|
best_feedback_div = feedback_div;
|
|
best_frac_feedback_div = frac_feedback_div;
|
|
best_freq = current_freq;
|
|
best_error = error;
|
|
best_vco_diff = vco_diff;
|
|
}
|
|
}
|
|
if (current_freq < freq)
|
|
min_frac_feed_div = frac_feedback_div + 1;
|
|
else
|
|
max_frac_feed_div = frac_feedback_div;
|
|
}
|
|
if (current_freq < freq)
|
|
min_feed_div = feedback_div + 1;
|
|
else
|
|
max_feed_div = feedback_div;
|
|
}
|
|
}
|
|
}
|
|
|
|
*dot_clock_p = best_freq / 10000;
|
|
*fb_div_p = best_feedback_div;
|
|
*frac_fb_div_p = best_frac_feedback_div;
|
|
*ref_div_p = best_ref_div;
|
|
*post_div_p = best_post_div;
|
|
DRM_DEBUG_KMS("%lld %d, pll dividers - fb: %d.%d ref: %d, post %d\n",
|
|
(long long)freq,
|
|
best_freq / 1000, best_feedback_div, best_frac_feedback_div,
|
|
best_ref_div, best_post_div);
|
|
|
|
}
|
|
|
|
static void radeon_user_framebuffer_destroy(struct drm_framebuffer *fb)
|
|
{
|
|
struct radeon_framebuffer *radeon_fb = to_radeon_framebuffer(fb);
|
|
|
|
if (radeon_fb->obj) {
|
|
drm_gem_object_unreference_unlocked(radeon_fb->obj);
|
|
}
|
|
drm_framebuffer_cleanup(fb);
|
|
kfree(radeon_fb);
|
|
}
|
|
|
|
static int radeon_user_framebuffer_create_handle(struct drm_framebuffer *fb,
|
|
struct drm_file *file_priv,
|
|
unsigned int *handle)
|
|
{
|
|
struct radeon_framebuffer *radeon_fb = to_radeon_framebuffer(fb);
|
|
|
|
return drm_gem_handle_create(file_priv, radeon_fb->obj, handle);
|
|
}
|
|
|
|
static const struct drm_framebuffer_funcs radeon_fb_funcs = {
|
|
.destroy = radeon_user_framebuffer_destroy,
|
|
.create_handle = radeon_user_framebuffer_create_handle,
|
|
};
|
|
|
|
int
|
|
radeon_framebuffer_init(struct drm_device *dev,
|
|
struct radeon_framebuffer *rfb,
|
|
struct drm_mode_fb_cmd2 *mode_cmd,
|
|
struct drm_gem_object *obj)
|
|
{
|
|
int ret;
|
|
rfb->obj = obj;
|
|
drm_helper_mode_fill_fb_struct(&rfb->base, mode_cmd);
|
|
ret = drm_framebuffer_init(dev, &rfb->base, &radeon_fb_funcs);
|
|
if (ret) {
|
|
rfb->obj = NULL;
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct drm_framebuffer *
|
|
radeon_user_framebuffer_create(struct drm_device *dev,
|
|
struct drm_file *file_priv,
|
|
struct drm_mode_fb_cmd2 *mode_cmd)
|
|
{
|
|
struct drm_gem_object *obj;
|
|
struct radeon_framebuffer *radeon_fb;
|
|
int ret;
|
|
|
|
obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]);
|
|
if (obj == NULL) {
|
|
dev_err(&dev->pdev->dev, "No GEM object associated to handle 0x%08X, "
|
|
"can't create framebuffer\n", mode_cmd->handles[0]);
|
|
return ERR_PTR(-ENOENT);
|
|
}
|
|
|
|
radeon_fb = kzalloc(sizeof(*radeon_fb), GFP_KERNEL);
|
|
if (radeon_fb == NULL) {
|
|
drm_gem_object_unreference_unlocked(obj);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
ret = radeon_framebuffer_init(dev, radeon_fb, mode_cmd, obj);
|
|
if (ret) {
|
|
kfree(radeon_fb);
|
|
drm_gem_object_unreference_unlocked(obj);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
return &radeon_fb->base;
|
|
}
|
|
|
|
static void radeon_output_poll_changed(struct drm_device *dev)
|
|
{
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
radeon_fb_output_poll_changed(rdev);
|
|
}
|
|
|
|
static const struct drm_mode_config_funcs radeon_mode_funcs = {
|
|
.fb_create = radeon_user_framebuffer_create,
|
|
.output_poll_changed = radeon_output_poll_changed
|
|
};
|
|
|
|
static struct drm_prop_enum_list radeon_tmds_pll_enum_list[] =
|
|
{ { 0, "driver" },
|
|
{ 1, "bios" },
|
|
};
|
|
|
|
static struct drm_prop_enum_list radeon_tv_std_enum_list[] =
|
|
{ { TV_STD_NTSC, "ntsc" },
|
|
{ TV_STD_PAL, "pal" },
|
|
{ TV_STD_PAL_M, "pal-m" },
|
|
{ TV_STD_PAL_60, "pal-60" },
|
|
{ TV_STD_NTSC_J, "ntsc-j" },
|
|
{ TV_STD_SCART_PAL, "scart-pal" },
|
|
{ TV_STD_PAL_CN, "pal-cn" },
|
|
{ TV_STD_SECAM, "secam" },
|
|
};
|
|
|
|
static struct drm_prop_enum_list radeon_underscan_enum_list[] =
|
|
{ { UNDERSCAN_OFF, "off" },
|
|
{ UNDERSCAN_ON, "on" },
|
|
{ UNDERSCAN_AUTO, "auto" },
|
|
};
|
|
|
|
static struct drm_prop_enum_list radeon_audio_enum_list[] =
|
|
{ { RADEON_AUDIO_DISABLE, "off" },
|
|
{ RADEON_AUDIO_ENABLE, "on" },
|
|
{ RADEON_AUDIO_AUTO, "auto" },
|
|
};
|
|
|
|
/* XXX support different dither options? spatial, temporal, both, etc. */
|
|
static struct drm_prop_enum_list radeon_dither_enum_list[] =
|
|
{ { RADEON_FMT_DITHER_DISABLE, "off" },
|
|
{ RADEON_FMT_DITHER_ENABLE, "on" },
|
|
};
|
|
|
|
static int radeon_modeset_create_props(struct radeon_device *rdev)
|
|
{
|
|
int sz;
|
|
|
|
if (rdev->is_atom_bios) {
|
|
rdev->mode_info.coherent_mode_property =
|
|
drm_property_create_range(rdev->ddev, 0 , "coherent", 0, 1);
|
|
if (!rdev->mode_info.coherent_mode_property)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!ASIC_IS_AVIVO(rdev)) {
|
|
sz = ARRAY_SIZE(radeon_tmds_pll_enum_list);
|
|
rdev->mode_info.tmds_pll_property =
|
|
drm_property_create_enum(rdev->ddev, 0,
|
|
"tmds_pll",
|
|
radeon_tmds_pll_enum_list, sz);
|
|
}
|
|
|
|
rdev->mode_info.load_detect_property =
|
|
drm_property_create_range(rdev->ddev, 0, "load detection", 0, 1);
|
|
if (!rdev->mode_info.load_detect_property)
|
|
return -ENOMEM;
|
|
|
|
drm_mode_create_scaling_mode_property(rdev->ddev);
|
|
|
|
sz = ARRAY_SIZE(radeon_tv_std_enum_list);
|
|
rdev->mode_info.tv_std_property =
|
|
drm_property_create_enum(rdev->ddev, 0,
|
|
"tv standard",
|
|
radeon_tv_std_enum_list, sz);
|
|
|
|
sz = ARRAY_SIZE(radeon_underscan_enum_list);
|
|
rdev->mode_info.underscan_property =
|
|
drm_property_create_enum(rdev->ddev, 0,
|
|
"underscan",
|
|
radeon_underscan_enum_list, sz);
|
|
|
|
rdev->mode_info.underscan_hborder_property =
|
|
drm_property_create_range(rdev->ddev, 0,
|
|
"underscan hborder", 0, 128);
|
|
if (!rdev->mode_info.underscan_hborder_property)
|
|
return -ENOMEM;
|
|
|
|
rdev->mode_info.underscan_vborder_property =
|
|
drm_property_create_range(rdev->ddev, 0,
|
|
"underscan vborder", 0, 128);
|
|
if (!rdev->mode_info.underscan_vborder_property)
|
|
return -ENOMEM;
|
|
|
|
sz = ARRAY_SIZE(radeon_audio_enum_list);
|
|
rdev->mode_info.audio_property =
|
|
drm_property_create_enum(rdev->ddev, 0,
|
|
"audio",
|
|
radeon_audio_enum_list, sz);
|
|
|
|
sz = ARRAY_SIZE(radeon_dither_enum_list);
|
|
rdev->mode_info.dither_property =
|
|
drm_property_create_enum(rdev->ddev, 0,
|
|
"dither",
|
|
radeon_dither_enum_list, sz);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void radeon_update_display_priority(struct radeon_device *rdev)
|
|
{
|
|
/* adjustment options for the display watermarks */
|
|
if ((radeon_disp_priority == 0) || (radeon_disp_priority > 2)) {
|
|
/* set display priority to high for r3xx, rv515 chips
|
|
* this avoids flickering due to underflow to the
|
|
* display controllers during heavy acceleration.
|
|
* Don't force high on rs4xx igp chips as it seems to
|
|
* affect the sound card. See kernel bug 15982.
|
|
*/
|
|
if ((ASIC_IS_R300(rdev) || (rdev->family == CHIP_RV515)) &&
|
|
!(rdev->flags & RADEON_IS_IGP))
|
|
rdev->disp_priority = 2;
|
|
else
|
|
rdev->disp_priority = 0;
|
|
} else
|
|
rdev->disp_priority = radeon_disp_priority;
|
|
|
|
}
|
|
|
|
/*
|
|
* Allocate hdmi structs and determine register offsets
|
|
*/
|
|
static void radeon_afmt_init(struct radeon_device *rdev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < RADEON_MAX_AFMT_BLOCKS; i++)
|
|
rdev->mode_info.afmt[i] = NULL;
|
|
|
|
if (ASIC_IS_NODCE(rdev)) {
|
|
/* nothing to do */
|
|
} else if (ASIC_IS_DCE4(rdev)) {
|
|
static uint32_t eg_offsets[] = {
|
|
EVERGREEN_CRTC0_REGISTER_OFFSET,
|
|
EVERGREEN_CRTC1_REGISTER_OFFSET,
|
|
EVERGREEN_CRTC2_REGISTER_OFFSET,
|
|
EVERGREEN_CRTC3_REGISTER_OFFSET,
|
|
EVERGREEN_CRTC4_REGISTER_OFFSET,
|
|
EVERGREEN_CRTC5_REGISTER_OFFSET,
|
|
0x13830 - 0x7030,
|
|
};
|
|
int num_afmt;
|
|
|
|
/* DCE8 has 7 audio blocks tied to DIG encoders */
|
|
/* DCE6 has 6 audio blocks tied to DIG encoders */
|
|
/* DCE4/5 has 6 audio blocks tied to DIG encoders */
|
|
/* DCE4.1 has 2 audio blocks tied to DIG encoders */
|
|
if (ASIC_IS_DCE8(rdev))
|
|
num_afmt = 7;
|
|
else if (ASIC_IS_DCE6(rdev))
|
|
num_afmt = 6;
|
|
else if (ASIC_IS_DCE5(rdev))
|
|
num_afmt = 6;
|
|
else if (ASIC_IS_DCE41(rdev))
|
|
num_afmt = 2;
|
|
else /* DCE4 */
|
|
num_afmt = 6;
|
|
|
|
BUG_ON(num_afmt > ARRAY_SIZE(eg_offsets));
|
|
for (i = 0; i < num_afmt; i++) {
|
|
rdev->mode_info.afmt[i] = kzalloc(sizeof(struct radeon_afmt), GFP_KERNEL);
|
|
if (rdev->mode_info.afmt[i]) {
|
|
rdev->mode_info.afmt[i]->offset = eg_offsets[i];
|
|
rdev->mode_info.afmt[i]->id = i;
|
|
}
|
|
}
|
|
} else if (ASIC_IS_DCE3(rdev)) {
|
|
/* DCE3.x has 2 audio blocks tied to DIG encoders */
|
|
rdev->mode_info.afmt[0] = kzalloc(sizeof(struct radeon_afmt), GFP_KERNEL);
|
|
if (rdev->mode_info.afmt[0]) {
|
|
rdev->mode_info.afmt[0]->offset = DCE3_HDMI_OFFSET0;
|
|
rdev->mode_info.afmt[0]->id = 0;
|
|
}
|
|
rdev->mode_info.afmt[1] = kzalloc(sizeof(struct radeon_afmt), GFP_KERNEL);
|
|
if (rdev->mode_info.afmt[1]) {
|
|
rdev->mode_info.afmt[1]->offset = DCE3_HDMI_OFFSET1;
|
|
rdev->mode_info.afmt[1]->id = 1;
|
|
}
|
|
} else if (ASIC_IS_DCE2(rdev)) {
|
|
/* DCE2 has at least 1 routable audio block */
|
|
rdev->mode_info.afmt[0] = kzalloc(sizeof(struct radeon_afmt), GFP_KERNEL);
|
|
if (rdev->mode_info.afmt[0]) {
|
|
rdev->mode_info.afmt[0]->offset = DCE2_HDMI_OFFSET0;
|
|
rdev->mode_info.afmt[0]->id = 0;
|
|
}
|
|
/* r6xx has 2 routable audio blocks */
|
|
if (rdev->family >= CHIP_R600) {
|
|
rdev->mode_info.afmt[1] = kzalloc(sizeof(struct radeon_afmt), GFP_KERNEL);
|
|
if (rdev->mode_info.afmt[1]) {
|
|
rdev->mode_info.afmt[1]->offset = DCE2_HDMI_OFFSET1;
|
|
rdev->mode_info.afmt[1]->id = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void radeon_afmt_fini(struct radeon_device *rdev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < RADEON_MAX_AFMT_BLOCKS; i++) {
|
|
kfree(rdev->mode_info.afmt[i]);
|
|
rdev->mode_info.afmt[i] = NULL;
|
|
}
|
|
}
|
|
|
|
int radeon_modeset_init(struct radeon_device *rdev)
|
|
{
|
|
int i;
|
|
int ret;
|
|
|
|
drm_mode_config_init(rdev->ddev);
|
|
rdev->mode_info.mode_config_initialized = true;
|
|
|
|
rdev->ddev->mode_config.funcs = &radeon_mode_funcs;
|
|
|
|
if (ASIC_IS_DCE5(rdev)) {
|
|
rdev->ddev->mode_config.max_width = 16384;
|
|
rdev->ddev->mode_config.max_height = 16384;
|
|
} else if (ASIC_IS_AVIVO(rdev)) {
|
|
rdev->ddev->mode_config.max_width = 8192;
|
|
rdev->ddev->mode_config.max_height = 8192;
|
|
} else {
|
|
rdev->ddev->mode_config.max_width = 4096;
|
|
rdev->ddev->mode_config.max_height = 4096;
|
|
}
|
|
|
|
rdev->ddev->mode_config.preferred_depth = 24;
|
|
rdev->ddev->mode_config.prefer_shadow = 1;
|
|
|
|
rdev->ddev->mode_config.fb_base = rdev->mc.aper_base;
|
|
|
|
ret = radeon_modeset_create_props(rdev);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* init i2c buses */
|
|
radeon_i2c_init(rdev);
|
|
|
|
/* check combios for a valid hardcoded EDID - Sun servers */
|
|
if (!rdev->is_atom_bios) {
|
|
/* check for hardcoded EDID in BIOS */
|
|
radeon_combios_check_hardcoded_edid(rdev);
|
|
}
|
|
|
|
/* allocate crtcs */
|
|
for (i = 0; i < rdev->num_crtc; i++) {
|
|
radeon_crtc_init(rdev->ddev, i);
|
|
}
|
|
|
|
/* okay we should have all the bios connectors */
|
|
ret = radeon_setup_enc_conn(rdev->ddev);
|
|
if (!ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* init dig PHYs, disp eng pll */
|
|
if (rdev->is_atom_bios) {
|
|
radeon_atom_encoder_init(rdev);
|
|
radeon_atom_disp_eng_pll_init(rdev);
|
|
}
|
|
|
|
/* initialize hpd */
|
|
radeon_hpd_init(rdev);
|
|
|
|
/* setup afmt */
|
|
radeon_afmt_init(rdev);
|
|
|
|
radeon_fbdev_init(rdev);
|
|
drm_kms_helper_poll_init(rdev->ddev);
|
|
|
|
if (rdev->pm.dpm_enabled) {
|
|
/* do dpm late init */
|
|
ret = radeon_pm_late_init(rdev);
|
|
if (ret) {
|
|
rdev->pm.dpm_enabled = false;
|
|
DRM_ERROR("radeon_pm_late_init failed, disabling dpm\n");
|
|
}
|
|
/* set the dpm state for PX since there won't be
|
|
* a modeset to call this.
|
|
*/
|
|
radeon_pm_compute_clocks(rdev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void radeon_modeset_fini(struct radeon_device *rdev)
|
|
{
|
|
radeon_fbdev_fini(rdev);
|
|
kfree(rdev->mode_info.bios_hardcoded_edid);
|
|
|
|
if (rdev->mode_info.mode_config_initialized) {
|
|
radeon_afmt_fini(rdev);
|
|
drm_kms_helper_poll_fini(rdev->ddev);
|
|
radeon_hpd_fini(rdev);
|
|
drm_mode_config_cleanup(rdev->ddev);
|
|
rdev->mode_info.mode_config_initialized = false;
|
|
}
|
|
/* free i2c buses */
|
|
radeon_i2c_fini(rdev);
|
|
}
|
|
|
|
static bool is_hdtv_mode(const struct drm_display_mode *mode)
|
|
{
|
|
/* try and guess if this is a tv or a monitor */
|
|
if ((mode->vdisplay == 480 && mode->hdisplay == 720) || /* 480p */
|
|
(mode->vdisplay == 576) || /* 576p */
|
|
(mode->vdisplay == 720) || /* 720p */
|
|
(mode->vdisplay == 1080)) /* 1080p */
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool radeon_crtc_scaling_mode_fixup(struct drm_crtc *crtc,
|
|
const struct drm_display_mode *mode,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
struct drm_encoder *encoder;
|
|
struct radeon_crtc *radeon_crtc = to_radeon_crtc(crtc);
|
|
struct radeon_encoder *radeon_encoder;
|
|
struct drm_connector *connector;
|
|
struct radeon_connector *radeon_connector;
|
|
bool first = true;
|
|
u32 src_v = 1, dst_v = 1;
|
|
u32 src_h = 1, dst_h = 1;
|
|
|
|
radeon_crtc->h_border = 0;
|
|
radeon_crtc->v_border = 0;
|
|
|
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
|
if (encoder->crtc != crtc)
|
|
continue;
|
|
radeon_encoder = to_radeon_encoder(encoder);
|
|
connector = radeon_get_connector_for_encoder(encoder);
|
|
radeon_connector = to_radeon_connector(connector);
|
|
|
|
if (first) {
|
|
/* set scaling */
|
|
if (radeon_encoder->rmx_type == RMX_OFF)
|
|
radeon_crtc->rmx_type = RMX_OFF;
|
|
else if (mode->hdisplay < radeon_encoder->native_mode.hdisplay ||
|
|
mode->vdisplay < radeon_encoder->native_mode.vdisplay)
|
|
radeon_crtc->rmx_type = radeon_encoder->rmx_type;
|
|
else
|
|
radeon_crtc->rmx_type = RMX_OFF;
|
|
/* copy native mode */
|
|
memcpy(&radeon_crtc->native_mode,
|
|
&radeon_encoder->native_mode,
|
|
sizeof(struct drm_display_mode));
|
|
src_v = crtc->mode.vdisplay;
|
|
dst_v = radeon_crtc->native_mode.vdisplay;
|
|
src_h = crtc->mode.hdisplay;
|
|
dst_h = radeon_crtc->native_mode.hdisplay;
|
|
|
|
/* fix up for overscan on hdmi */
|
|
if (ASIC_IS_AVIVO(rdev) &&
|
|
(!(mode->flags & DRM_MODE_FLAG_INTERLACE)) &&
|
|
((radeon_encoder->underscan_type == UNDERSCAN_ON) ||
|
|
((radeon_encoder->underscan_type == UNDERSCAN_AUTO) &&
|
|
drm_detect_hdmi_monitor(radeon_connector->edid) &&
|
|
is_hdtv_mode(mode)))) {
|
|
if (radeon_encoder->underscan_hborder != 0)
|
|
radeon_crtc->h_border = radeon_encoder->underscan_hborder;
|
|
else
|
|
radeon_crtc->h_border = (mode->hdisplay >> 5) + 16;
|
|
if (radeon_encoder->underscan_vborder != 0)
|
|
radeon_crtc->v_border = radeon_encoder->underscan_vborder;
|
|
else
|
|
radeon_crtc->v_border = (mode->vdisplay >> 5) + 16;
|
|
radeon_crtc->rmx_type = RMX_FULL;
|
|
src_v = crtc->mode.vdisplay;
|
|
dst_v = crtc->mode.vdisplay - (radeon_crtc->v_border * 2);
|
|
src_h = crtc->mode.hdisplay;
|
|
dst_h = crtc->mode.hdisplay - (radeon_crtc->h_border * 2);
|
|
}
|
|
first = false;
|
|
} else {
|
|
if (radeon_crtc->rmx_type != radeon_encoder->rmx_type) {
|
|
/* WARNING: Right now this can't happen but
|
|
* in the future we need to check that scaling
|
|
* are consistent across different encoder
|
|
* (ie all encoder can work with the same
|
|
* scaling).
|
|
*/
|
|
DRM_ERROR("Scaling not consistent across encoder.\n");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (radeon_crtc->rmx_type != RMX_OFF) {
|
|
fixed20_12 a, b;
|
|
a.full = dfixed_const(src_v);
|
|
b.full = dfixed_const(dst_v);
|
|
radeon_crtc->vsc.full = dfixed_div(a, b);
|
|
a.full = dfixed_const(src_h);
|
|
b.full = dfixed_const(dst_h);
|
|
radeon_crtc->hsc.full = dfixed_div(a, b);
|
|
} else {
|
|
radeon_crtc->vsc.full = dfixed_const(1);
|
|
radeon_crtc->hsc.full = dfixed_const(1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Retrieve current video scanout position of crtc on a given gpu, and
|
|
* an optional accurate timestamp of when query happened.
|
|
*
|
|
* \param dev Device to query.
|
|
* \param crtc Crtc to query.
|
|
* \param flags Flags from caller (DRM_CALLED_FROM_VBLIRQ or 0).
|
|
* \param *vpos Location where vertical scanout position should be stored.
|
|
* \param *hpos Location where horizontal scanout position should go.
|
|
* \param *stime Target location for timestamp taken immediately before
|
|
* scanout position query. Can be NULL to skip timestamp.
|
|
* \param *etime Target location for timestamp taken immediately after
|
|
* scanout position query. Can be NULL to skip timestamp.
|
|
*
|
|
* Returns vpos as a positive number while in active scanout area.
|
|
* Returns vpos as a negative number inside vblank, counting the number
|
|
* of scanlines to go until end of vblank, e.g., -1 means "one scanline
|
|
* until start of active scanout / end of vblank."
|
|
*
|
|
* \return Flags, or'ed together as follows:
|
|
*
|
|
* DRM_SCANOUTPOS_VALID = Query successful.
|
|
* DRM_SCANOUTPOS_INVBL = Inside vblank.
|
|
* DRM_SCANOUTPOS_ACCURATE = Returned position is accurate. A lack of
|
|
* this flag means that returned position may be offset by a constant but
|
|
* unknown small number of scanlines wrt. real scanout position.
|
|
*
|
|
*/
|
|
int radeon_get_crtc_scanoutpos(struct drm_device *dev, int crtc, unsigned int flags,
|
|
int *vpos, int *hpos, ktime_t *stime, ktime_t *etime)
|
|
{
|
|
u32 stat_crtc = 0, vbl = 0, position = 0;
|
|
int vbl_start, vbl_end, vtotal, ret = 0;
|
|
bool in_vbl = true;
|
|
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
|
|
/* preempt_disable_rt() should go right here in PREEMPT_RT patchset. */
|
|
|
|
/* Get optional system timestamp before query. */
|
|
if (stime)
|
|
*stime = ktime_get();
|
|
|
|
if (ASIC_IS_DCE4(rdev)) {
|
|
if (crtc == 0) {
|
|
vbl = RREG32(EVERGREEN_CRTC_V_BLANK_START_END +
|
|
EVERGREEN_CRTC0_REGISTER_OFFSET);
|
|
position = RREG32(EVERGREEN_CRTC_STATUS_POSITION +
|
|
EVERGREEN_CRTC0_REGISTER_OFFSET);
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
if (crtc == 1) {
|
|
vbl = RREG32(EVERGREEN_CRTC_V_BLANK_START_END +
|
|
EVERGREEN_CRTC1_REGISTER_OFFSET);
|
|
position = RREG32(EVERGREEN_CRTC_STATUS_POSITION +
|
|
EVERGREEN_CRTC1_REGISTER_OFFSET);
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
if (crtc == 2) {
|
|
vbl = RREG32(EVERGREEN_CRTC_V_BLANK_START_END +
|
|
EVERGREEN_CRTC2_REGISTER_OFFSET);
|
|
position = RREG32(EVERGREEN_CRTC_STATUS_POSITION +
|
|
EVERGREEN_CRTC2_REGISTER_OFFSET);
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
if (crtc == 3) {
|
|
vbl = RREG32(EVERGREEN_CRTC_V_BLANK_START_END +
|
|
EVERGREEN_CRTC3_REGISTER_OFFSET);
|
|
position = RREG32(EVERGREEN_CRTC_STATUS_POSITION +
|
|
EVERGREEN_CRTC3_REGISTER_OFFSET);
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
if (crtc == 4) {
|
|
vbl = RREG32(EVERGREEN_CRTC_V_BLANK_START_END +
|
|
EVERGREEN_CRTC4_REGISTER_OFFSET);
|
|
position = RREG32(EVERGREEN_CRTC_STATUS_POSITION +
|
|
EVERGREEN_CRTC4_REGISTER_OFFSET);
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
if (crtc == 5) {
|
|
vbl = RREG32(EVERGREEN_CRTC_V_BLANK_START_END +
|
|
EVERGREEN_CRTC5_REGISTER_OFFSET);
|
|
position = RREG32(EVERGREEN_CRTC_STATUS_POSITION +
|
|
EVERGREEN_CRTC5_REGISTER_OFFSET);
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
} else if (ASIC_IS_AVIVO(rdev)) {
|
|
if (crtc == 0) {
|
|
vbl = RREG32(AVIVO_D1CRTC_V_BLANK_START_END);
|
|
position = RREG32(AVIVO_D1CRTC_STATUS_POSITION);
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
if (crtc == 1) {
|
|
vbl = RREG32(AVIVO_D2CRTC_V_BLANK_START_END);
|
|
position = RREG32(AVIVO_D2CRTC_STATUS_POSITION);
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
} else {
|
|
/* Pre-AVIVO: Different encoding of scanout pos and vblank interval. */
|
|
if (crtc == 0) {
|
|
/* Assume vbl_end == 0, get vbl_start from
|
|
* upper 16 bits.
|
|
*/
|
|
vbl = (RREG32(RADEON_CRTC_V_TOTAL_DISP) &
|
|
RADEON_CRTC_V_DISP) >> RADEON_CRTC_V_DISP_SHIFT;
|
|
/* Only retrieve vpos from upper 16 bits, set hpos == 0. */
|
|
position = (RREG32(RADEON_CRTC_VLINE_CRNT_VLINE) >> 16) & RADEON_CRTC_V_TOTAL;
|
|
stat_crtc = RREG32(RADEON_CRTC_STATUS);
|
|
if (!(stat_crtc & 1))
|
|
in_vbl = false;
|
|
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
if (crtc == 1) {
|
|
vbl = (RREG32(RADEON_CRTC2_V_TOTAL_DISP) &
|
|
RADEON_CRTC_V_DISP) >> RADEON_CRTC_V_DISP_SHIFT;
|
|
position = (RREG32(RADEON_CRTC2_VLINE_CRNT_VLINE) >> 16) & RADEON_CRTC_V_TOTAL;
|
|
stat_crtc = RREG32(RADEON_CRTC2_STATUS);
|
|
if (!(stat_crtc & 1))
|
|
in_vbl = false;
|
|
|
|
ret |= DRM_SCANOUTPOS_VALID;
|
|
}
|
|
}
|
|
|
|
/* Get optional system timestamp after query. */
|
|
if (etime)
|
|
*etime = ktime_get();
|
|
|
|
/* preempt_enable_rt() should go right here in PREEMPT_RT patchset. */
|
|
|
|
/* Decode into vertical and horizontal scanout position. */
|
|
*vpos = position & 0x1fff;
|
|
*hpos = (position >> 16) & 0x1fff;
|
|
|
|
/* Valid vblank area boundaries from gpu retrieved? */
|
|
if (vbl > 0) {
|
|
/* Yes: Decode. */
|
|
ret |= DRM_SCANOUTPOS_ACCURATE;
|
|
vbl_start = vbl & 0x1fff;
|
|
vbl_end = (vbl >> 16) & 0x1fff;
|
|
}
|
|
else {
|
|
/* No: Fake something reasonable which gives at least ok results. */
|
|
vbl_start = rdev->mode_info.crtcs[crtc]->base.hwmode.crtc_vdisplay;
|
|
vbl_end = 0;
|
|
}
|
|
|
|
/* Test scanout position against vblank region. */
|
|
if ((*vpos < vbl_start) && (*vpos >= vbl_end))
|
|
in_vbl = false;
|
|
|
|
/* Check if inside vblank area and apply corrective offsets:
|
|
* vpos will then be >=0 in video scanout area, but negative
|
|
* within vblank area, counting down the number of lines until
|
|
* start of scanout.
|
|
*/
|
|
|
|
/* Inside "upper part" of vblank area? Apply corrective offset if so: */
|
|
if (in_vbl && (*vpos >= vbl_start)) {
|
|
vtotal = rdev->mode_info.crtcs[crtc]->base.hwmode.crtc_vtotal;
|
|
*vpos = *vpos - vtotal;
|
|
}
|
|
|
|
/* Correct for shifted end of vbl at vbl_end. */
|
|
*vpos = *vpos - vbl_end;
|
|
|
|
/* In vblank? */
|
|
if (in_vbl)
|
|
ret |= DRM_SCANOUTPOS_INVBL;
|
|
|
|
/* Is vpos outside nominal vblank area, but less than
|
|
* 1/100 of a frame height away from start of vblank?
|
|
* If so, assume this isn't a massively delayed vblank
|
|
* interrupt, but a vblank interrupt that fired a few
|
|
* microseconds before true start of vblank. Compensate
|
|
* by adding a full frame duration to the final timestamp.
|
|
* Happens, e.g., on ATI R500, R600.
|
|
*
|
|
* We only do this if DRM_CALLED_FROM_VBLIRQ.
|
|
*/
|
|
if ((flags & DRM_CALLED_FROM_VBLIRQ) && !in_vbl) {
|
|
vbl_start = rdev->mode_info.crtcs[crtc]->base.hwmode.crtc_vdisplay;
|
|
vtotal = rdev->mode_info.crtcs[crtc]->base.hwmode.crtc_vtotal;
|
|
|
|
if (vbl_start - *vpos < vtotal / 100) {
|
|
*vpos -= vtotal;
|
|
|
|
/* Signal this correction as "applied". */
|
|
ret |= 0x8;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|