root/sys/dev/drm/amd/amdgpu/amdgpu_display.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/amdgpu_drm.h>
#include "amdgpu.h"
#include "amdgpu_i2c.h"
#include "atom.h"
#include "amdgpu_connectors.h"
#include "amdgpu_display.h"
#include <asm/div64.h>

#include <linux/pm_runtime.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_fb_helper.h>

static void amdgpu_display_flip_callback(struct dma_fence *f,
                                         struct dma_fence_cb *cb)
{
        struct amdgpu_flip_work *work =
                container_of(cb, struct amdgpu_flip_work, cb);

        dma_fence_put(f);
        schedule_work(&work->flip_work.work);
}

static bool amdgpu_display_flip_handle_fence(struct amdgpu_flip_work *work,
                                             struct dma_fence **f)
{
        struct dma_fence *fence= *f;

        if (fence == NULL)
                return false;

        *f = NULL;

        if (!dma_fence_add_callback(fence, &work->cb,
                                    amdgpu_display_flip_callback))
                return true;

        dma_fence_put(fence);
        return false;
}

static void amdgpu_display_flip_work_func(struct work_struct *__work)
{
        struct delayed_work *delayed_work =
                container_of(__work, struct delayed_work, work);
        struct amdgpu_flip_work *work =
                container_of(delayed_work, struct amdgpu_flip_work, flip_work);
        struct amdgpu_device *adev = work->adev;
        struct amdgpu_crtc *amdgpu_crtc = adev->mode_info.crtcs[work->crtc_id];

        struct drm_crtc *crtc = &amdgpu_crtc->base;
        unsigned long flags;
        unsigned i;
        int vpos, hpos;

        if (amdgpu_display_flip_handle_fence(work, &work->excl))
                return;

        for (i = 0; i < work->shared_count; ++i)
                if (amdgpu_display_flip_handle_fence(work, &work->shared[i]))
                        return;

        /* Wait until we're out of the vertical blank period before the one
         * targeted by the flip
         */
        if (amdgpu_crtc->enabled &&
            (amdgpu_display_get_crtc_scanoutpos(adev->ddev, work->crtc_id, 0,
                                                &vpos, &hpos, NULL, NULL,
                                                &crtc->hwmode)
             & (DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_IN_VBLANK)) ==
            (DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_IN_VBLANK) &&
            (int)(work->target_vblank -
                  amdgpu_get_vblank_counter_kms(adev->ddev, amdgpu_crtc->crtc_id)) > 0) {
                schedule_delayed_work(&work->flip_work, usecs_to_jiffies(1000));
                return;
        }

        /* We borrow the event spin lock for protecting flip_status */
        spin_lock_irqsave(&crtc->dev->event_lock, flags);

        /* Do the flip (mmio) */
        adev->mode_info.funcs->page_flip(adev, work->crtc_id, work->base, work->async);

        /* Set the flip status */
        amdgpu_crtc->pflip_status = AMDGPU_FLIP_SUBMITTED;
        spin_unlock_irqrestore(&crtc->dev->event_lock, flags);


        DRM_DEBUG_DRIVER("crtc:%d[%p], pflip_stat:AMDGPU_FLIP_SUBMITTED, work: %p,\n",
                                         amdgpu_crtc->crtc_id, amdgpu_crtc, work);

}

/*
 * Handle unpin events outside the interrupt handler proper.
 */
static void amdgpu_display_unpin_work_func(struct work_struct *__work)
{
        struct amdgpu_flip_work *work =
                container_of(__work, struct amdgpu_flip_work, unpin_work);
        int r;

        /* unpin of the old buffer */
        r = amdgpu_bo_reserve(work->old_abo, true);
        if (likely(r == 0)) {
                r = amdgpu_bo_unpin(work->old_abo);
                if (unlikely(r != 0)) {
                        DRM_ERROR("failed to unpin buffer after flip\n");
                }
                amdgpu_bo_unreserve(work->old_abo);
        } else
                DRM_ERROR("failed to reserve buffer after flip\n");

        amdgpu_bo_unref(&work->old_abo);
        kfree(work->shared);
        kfree(work);
}

int amdgpu_display_crtc_page_flip_target(struct drm_crtc *crtc,
                                struct drm_framebuffer *fb,
                                struct drm_pending_vblank_event *event,
                                uint32_t page_flip_flags, uint32_t target,
                                struct drm_modeset_acquire_ctx *ctx)
{
        struct drm_device *dev = crtc->dev;
        struct amdgpu_device *adev = dev->dev_private;
        struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
        struct drm_gem_object *obj;
        struct amdgpu_flip_work *work;
        struct amdgpu_bo *new_abo;
        unsigned long flags;
        u64 tiling_flags;
        int i, r;

        work = kzalloc(sizeof *work, GFP_KERNEL);
        if (work == NULL)
                return -ENOMEM;

        INIT_DELAYED_WORK(&work->flip_work, amdgpu_display_flip_work_func);
        INIT_WORK(&work->unpin_work, amdgpu_display_unpin_work_func);

        work->event = event;
        work->adev = adev;
        work->crtc_id = amdgpu_crtc->crtc_id;
        work->async = (page_flip_flags & DRM_MODE_PAGE_FLIP_ASYNC) != 0;

        /* schedule unpin of the old buffer */
        obj = crtc->primary->fb->obj[0];

        /* take a reference to the old object */
        work->old_abo = gem_to_amdgpu_bo(obj);
        amdgpu_bo_ref(work->old_abo);

        obj = fb->obj[0];
        new_abo = gem_to_amdgpu_bo(obj);

        /* pin the new buffer */
        r = amdgpu_bo_reserve(new_abo, false);
        if (unlikely(r != 0)) {
                DRM_ERROR("failed to reserve new abo buffer before flip\n");
                goto cleanup;
        }

        r = amdgpu_bo_pin(new_abo, amdgpu_display_supported_domains(adev));
        if (unlikely(r != 0)) {
                DRM_ERROR("failed to pin new abo buffer before flip\n");
                goto unreserve;
        }

        r = amdgpu_ttm_alloc_gart(&new_abo->tbo);
        if (unlikely(r != 0)) {
                DRM_ERROR("%p bind failed\n", new_abo);
                goto unpin;
        }

        r = reservation_object_get_fences_rcu(new_abo->tbo.resv, &work->excl,
                                              &work->shared_count,
                                              &work->shared);
        if (unlikely(r != 0)) {
                DRM_ERROR("failed to get fences for buffer\n");
                goto unpin;
        }

        amdgpu_bo_get_tiling_flags(new_abo, &tiling_flags);
        amdgpu_bo_unreserve(new_abo);

        work->base = amdgpu_bo_gpu_offset(new_abo);
        work->target_vblank = target - (uint32_t)drm_crtc_vblank_count(crtc) +
                amdgpu_get_vblank_counter_kms(dev, work->crtc_id);

        /* we borrow the event spin lock for protecting flip_wrok */
        spin_lock_irqsave(&crtc->dev->event_lock, flags);
        if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_NONE) {
                DRM_DEBUG_DRIVER("flip queue: crtc already busy\n");
                spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
                r = -EBUSY;
                goto pflip_cleanup;
        }

        amdgpu_crtc->pflip_status = AMDGPU_FLIP_PENDING;
        amdgpu_crtc->pflip_works = work;


        DRM_DEBUG_DRIVER("crtc:%d[%p], pflip_stat:AMDGPU_FLIP_PENDING, work: %p,\n",
                                         amdgpu_crtc->crtc_id, amdgpu_crtc, work);
        /* update crtc fb */
        crtc->primary->fb = fb;
        spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
        amdgpu_display_flip_work_func(&work->flip_work.work);
        return 0;

pflip_cleanup:
        if (unlikely(amdgpu_bo_reserve(new_abo, false) != 0)) {
                DRM_ERROR("failed to reserve new abo in error path\n");
                goto cleanup;
        }
unpin:
        if (unlikely(amdgpu_bo_unpin(new_abo) != 0)) {
                DRM_ERROR("failed to unpin new abo in error path\n");
        }
unreserve:
        amdgpu_bo_unreserve(new_abo);

cleanup:
        amdgpu_bo_unref(&work->old_abo);
        dma_fence_put(work->excl);
        for (i = 0; i < work->shared_count; ++i)
                dma_fence_put(work->shared[i]);
        kfree(work->shared);
        kfree(work);

        return r;
}

int amdgpu_display_crtc_set_config(struct drm_mode_set *set,
                                   struct drm_modeset_acquire_ctx *ctx)
{
        struct drm_device *dev;
        struct amdgpu_device *adev;
        struct drm_crtc *crtc;
        bool active = false;
        int ret;

        if (!set || !set->crtc)
                return -EINVAL;

        dev = set->crtc->dev;

        ret = pm_runtime_get_sync(dev->dev);
        if (ret < 0)
                return ret;

        ret = drm_crtc_helper_set_config(set, ctx);

        list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
                if (crtc->enabled)
                        active = true;

        pm_runtime_mark_last_busy(dev->dev);

        adev = dev->dev_private;
        /* if we have active crtcs and we don't have a power ref,
           take the current one */
        if (active && !adev->have_disp_power_ref) {
                adev->have_disp_power_ref = true;
                return ret;
        }
        /* if we have no active crtcs, then drop the power ref
           we got before */
        if (!active && adev->have_disp_power_ref) {
                pm_runtime_put_autosuspend(dev->dev);
                adev->have_disp_power_ref = false;
        }

        /* drop the power reference we got coming in here */
        pm_runtime_put_autosuspend(dev->dev);
        return ret;
}

static const char *encoder_names[41] = {
        "NONE",
        "INTERNAL_LVDS",
        "INTERNAL_TMDS1",
        "INTERNAL_TMDS2",
        "INTERNAL_DAC1",
        "INTERNAL_DAC2",
        "INTERNAL_SDVOA",
        "INTERNAL_SDVOB",
        "SI170B",
        "CH7303",
        "CH7301",
        "INTERNAL_DVO1",
        "EXTERNAL_SDVOA",
        "EXTERNAL_SDVOB",
        "TITFP513",
        "INTERNAL_LVTM1",
        "VT1623",
        "HDMI_SI1930",
        "HDMI_INTERNAL",
        "INTERNAL_KLDSCP_TMDS1",
        "INTERNAL_KLDSCP_DVO1",
        "INTERNAL_KLDSCP_DAC1",
        "INTERNAL_KLDSCP_DAC2",
        "SI178",
        "MVPU_FPGA",
        "INTERNAL_DDI",
        "VT1625",
        "HDMI_SI1932",
        "DP_AN9801",
        "DP_DP501",
        "INTERNAL_UNIPHY",
        "INTERNAL_KLDSCP_LVTMA",
        "INTERNAL_UNIPHY1",
        "INTERNAL_UNIPHY2",
        "NUTMEG",
        "TRAVIS",
        "INTERNAL_VCE",
        "INTERNAL_UNIPHY3",
        "HDMI_ANX9805",
        "INTERNAL_AMCLK",
        "VIRTUAL",
};

static const char *hpd_names[6] = {
        "HPD1",
        "HPD2",
        "HPD3",
        "HPD4",
        "HPD5",
        "HPD6",
};

void amdgpu_display_print_display_setup(struct drm_device *dev)
{
        struct drm_connector *connector;
        struct amdgpu_connector *amdgpu_connector;
        struct drm_encoder *encoder;
        struct amdgpu_encoder *amdgpu_encoder;
        uint32_t devices;
        int i = 0;

        DRM_INFO("AMDGPU Display Connectors\n");
        list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
                amdgpu_connector = to_amdgpu_connector(connector);
                DRM_INFO("Connector %d:\n", i);
                DRM_INFO("  %s\n", connector->name);
                if (amdgpu_connector->hpd.hpd != AMDGPU_HPD_NONE)
                        DRM_INFO("  %s\n", hpd_names[amdgpu_connector->hpd.hpd]);
                if (amdgpu_connector->ddc_bus) {
                        DRM_INFO("  DDC: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
                                 amdgpu_connector->ddc_bus->rec.mask_clk_reg,
                                 amdgpu_connector->ddc_bus->rec.mask_data_reg,
                                 amdgpu_connector->ddc_bus->rec.a_clk_reg,
                                 amdgpu_connector->ddc_bus->rec.a_data_reg,
                                 amdgpu_connector->ddc_bus->rec.en_clk_reg,
                                 amdgpu_connector->ddc_bus->rec.en_data_reg,
                                 amdgpu_connector->ddc_bus->rec.y_clk_reg,
                                 amdgpu_connector->ddc_bus->rec.y_data_reg);
                        if (amdgpu_connector->router.ddc_valid)
                                DRM_INFO("  DDC Router 0x%x/0x%x\n",
                                         amdgpu_connector->router.ddc_mux_control_pin,
                                         amdgpu_connector->router.ddc_mux_state);
                        if (amdgpu_connector->router.cd_valid)
                                DRM_INFO("  Clock/Data Router 0x%x/0x%x\n",
                                         amdgpu_connector->router.cd_mux_control_pin,
                                         amdgpu_connector->router.cd_mux_state);
                } else {
                        if (connector->connector_type == DRM_MODE_CONNECTOR_VGA ||
                            connector->connector_type == DRM_MODE_CONNECTOR_DVII ||
                            connector->connector_type == DRM_MODE_CONNECTOR_DVID ||
                            connector->connector_type == DRM_MODE_CONNECTOR_DVIA ||
                            connector->connector_type == DRM_MODE_CONNECTOR_HDMIA ||
                            connector->connector_type == DRM_MODE_CONNECTOR_HDMIB)
                                DRM_INFO("  DDC: no ddc bus - possible BIOS bug - please report to xorg-driver-ati@lists.x.org\n");
                }
                DRM_INFO("  Encoders:\n");
                list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
                        amdgpu_encoder = to_amdgpu_encoder(encoder);
                        devices = amdgpu_encoder->devices & amdgpu_connector->devices;
                        if (devices) {
                                if (devices & ATOM_DEVICE_CRT1_SUPPORT)
                                        DRM_INFO("    CRT1: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_CRT2_SUPPORT)
                                        DRM_INFO("    CRT2: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_LCD1_SUPPORT)
                                        DRM_INFO("    LCD1: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_DFP1_SUPPORT)
                                        DRM_INFO("    DFP1: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_DFP2_SUPPORT)
                                        DRM_INFO("    DFP2: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_DFP3_SUPPORT)
                                        DRM_INFO("    DFP3: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_DFP4_SUPPORT)
                                        DRM_INFO("    DFP4: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_DFP5_SUPPORT)
                                        DRM_INFO("    DFP5: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_DFP6_SUPPORT)
                                        DRM_INFO("    DFP6: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_TV1_SUPPORT)
                                        DRM_INFO("    TV1: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                                if (devices & ATOM_DEVICE_CV_SUPPORT)
                                        DRM_INFO("    CV: %s\n", encoder_names[amdgpu_encoder->encoder_id]);
                        }
                }
                i++;
        }
}

/**
 * amdgpu_display_ddc_probe
 *
 */
bool amdgpu_display_ddc_probe(struct amdgpu_connector *amdgpu_connector,
                              bool use_aux)
{
        u8 out = 0x0;
        u8 buf[8];
        int ret;
        struct i2c_msg msgs[] = {
                {
                        .addr = DDC_ADDR,
                        .flags = 0,
                        .len = 1,
                        .buf = &out,
                },
                {
                        .addr = DDC_ADDR,
                        .flags = I2C_M_RD,
                        .len = 8,
                        .buf = buf,
                }
        };

        /* on hw with routers, select right port */
        if (amdgpu_connector->router.ddc_valid)
                amdgpu_i2c_router_select_ddc_port(amdgpu_connector);

        if (use_aux) {
                ret = i2c_transfer(&amdgpu_connector->ddc_bus->aux.ddc, msgs, 2);
        } else {
                ret = i2c_transfer(&amdgpu_connector->ddc_bus->adapter, msgs, 2);
        }

        if (ret != 2)
                /* Couldn't find an accessible DDC on this connector */
                return false;
        /* Probe also for valid EDID header
         * EDID header starts with:
         * 0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00.
         * Only the first 6 bytes must be valid as
         * drm_edid_block_valid() can fix the last 2 bytes */
        if (drm_edid_header_is_valid(buf) < 6) {
                /* Couldn't find an accessible EDID on this
                 * connector */
                return false;
        }
        return true;
}

static const struct drm_framebuffer_funcs amdgpu_fb_funcs = {
        .destroy = drm_gem_fb_destroy,
        .create_handle = drm_gem_fb_create_handle,
};

uint32_t amdgpu_display_supported_domains(struct amdgpu_device *adev)
{
        uint32_t domain = AMDGPU_GEM_DOMAIN_VRAM;

#if defined(CONFIG_DRM_AMD_DC)
        if (adev->asic_type >= CHIP_CARRIZO && adev->asic_type < CHIP_RAVEN &&
            adev->flags & AMD_IS_APU &&
            amdgpu_device_asic_has_dc_support(adev->asic_type))
                domain |= AMDGPU_GEM_DOMAIN_GTT;
#endif

        return domain;
}

int amdgpu_display_framebuffer_init(struct drm_device *dev,
                                    struct amdgpu_framebuffer *rfb,
                                    const struct drm_mode_fb_cmd2 *mode_cmd,
                                    struct drm_gem_object *obj)
{
        int ret;
        rfb->base.obj[0] = obj;
        drm_helper_mode_fill_fb_struct(dev, &rfb->base, mode_cmd);
        ret = drm_framebuffer_init(dev, &rfb->base, &amdgpu_fb_funcs);
        if (ret) {
                rfb->base.obj[0] = NULL;
                return ret;
        }
        return 0;
}

struct drm_framebuffer *
amdgpu_display_user_framebuffer_create(struct drm_device *dev,
                                       struct drm_file *file_priv,
                                       const struct drm_mode_fb_cmd2 *mode_cmd)
{
        struct drm_gem_object *obj;
        struct amdgpu_framebuffer *amdgpu_fb;
        int ret;

        obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[0]);
        if (obj ==  NULL) {
                dev_err(&dev->pdev->dev, "No GEM object associated to handle 0x%08X, "
                        "can't create framebuffer\n", mode_cmd->handles[0]);
                return ERR_PTR(-ENOENT);
        }

        /* Handle is imported dma-buf, so cannot be migrated to VRAM for scanout */
        if (obj->import_attach) {
                DRM_DEBUG_KMS("Cannot create framebuffer from imported dma_buf\n");
                return ERR_PTR(-EINVAL);
        }

        amdgpu_fb = kzalloc(sizeof(*amdgpu_fb), GFP_KERNEL);
        if (amdgpu_fb == NULL) {
                drm_gem_object_put_unlocked(obj);
                return ERR_PTR(-ENOMEM);
        }

        ret = amdgpu_display_framebuffer_init(dev, amdgpu_fb, mode_cmd, obj);
        if (ret) {
                kfree(amdgpu_fb);
                drm_gem_object_put_unlocked(obj);
                return ERR_PTR(ret);
        }

        return &amdgpu_fb->base;
}

const struct drm_mode_config_funcs amdgpu_mode_funcs = {
        .fb_create = amdgpu_display_user_framebuffer_create,
        .output_poll_changed = drm_fb_helper_output_poll_changed,
};

static const struct drm_prop_enum_list amdgpu_underscan_enum_list[] =
{       { UNDERSCAN_OFF, "off" },
        { UNDERSCAN_ON, "on" },
        { UNDERSCAN_AUTO, "auto" },
};

static const struct drm_prop_enum_list amdgpu_audio_enum_list[] =
{       { AMDGPU_AUDIO_DISABLE, "off" },
        { AMDGPU_AUDIO_ENABLE, "on" },
        { AMDGPU_AUDIO_AUTO, "auto" },
};

/* XXX support different dither options? spatial, temporal, both, etc. */
static const struct drm_prop_enum_list amdgpu_dither_enum_list[] =
{       { AMDGPU_FMT_DITHER_DISABLE, "off" },
        { AMDGPU_FMT_DITHER_ENABLE, "on" },
};

int amdgpu_display_modeset_create_props(struct amdgpu_device *adev)
{
        int sz;

        adev->mode_info.coherent_mode_property =
                drm_property_create_range(adev->ddev, 0 , "coherent", 0, 1);
        if (!adev->mode_info.coherent_mode_property)
                return -ENOMEM;

        adev->mode_info.load_detect_property =
                drm_property_create_range(adev->ddev, 0, "load detection", 0, 1);
        if (!adev->mode_info.load_detect_property)
                return -ENOMEM;

        drm_mode_create_scaling_mode_property(adev->ddev);

        sz = ARRAY_SIZE(amdgpu_underscan_enum_list);
        adev->mode_info.underscan_property =
                drm_property_create_enum(adev->ddev, 0,
                                    "underscan",
                                    amdgpu_underscan_enum_list, sz);

        adev->mode_info.underscan_hborder_property =
                drm_property_create_range(adev->ddev, 0,
                                        "underscan hborder", 0, 128);
        if (!adev->mode_info.underscan_hborder_property)
                return -ENOMEM;

        adev->mode_info.underscan_vborder_property =
                drm_property_create_range(adev->ddev, 0,
                                        "underscan vborder", 0, 128);
        if (!adev->mode_info.underscan_vborder_property)
                return -ENOMEM;

        sz = ARRAY_SIZE(amdgpu_audio_enum_list);
        adev->mode_info.audio_property =
                drm_property_create_enum(adev->ddev, 0,
                                         "audio",
                                         amdgpu_audio_enum_list, sz);

        sz = ARRAY_SIZE(amdgpu_dither_enum_list);
        adev->mode_info.dither_property =
                drm_property_create_enum(adev->ddev, 0,
                                         "dither",
                                         amdgpu_dither_enum_list, sz);

        if (amdgpu_device_has_dc_support(adev)) {
                adev->mode_info.max_bpc_property =
                        drm_property_create_range(adev->ddev, 0, "max bpc", 8, 16);
                if (!adev->mode_info.max_bpc_property)
                        return -ENOMEM;
        }

        return 0;
}

void amdgpu_display_update_priority(struct amdgpu_device *adev)
{
        /* adjustment options for the display watermarks */
        if ((amdgpu_disp_priority == 0) || (amdgpu_disp_priority > 2))
                adev->mode_info.disp_priority = 0;
        else
                adev->mode_info.disp_priority = amdgpu_disp_priority;

}

static bool amdgpu_display_is_hdtv_mode(const struct drm_display_mode *mode)
{
        /* try and guess if this is a tv or a monitor */
        if ((mode->vdisplay == 480 && mode->hdisplay == 720) || /* 480p */
            (mode->vdisplay == 576) || /* 576p */
            (mode->vdisplay == 720) || /* 720p */
            (mode->vdisplay == 1080)) /* 1080p */
                return true;
        else
                return false;
}

bool amdgpu_display_crtc_scaling_mode_fixup(struct drm_crtc *crtc,
                                        const struct drm_display_mode *mode,
                                        struct drm_display_mode *adjusted_mode)
{
        struct drm_device *dev = crtc->dev;
        struct drm_encoder *encoder;
        struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
        struct amdgpu_encoder *amdgpu_encoder;
        struct drm_connector *connector;
        struct amdgpu_connector *amdgpu_connector;
        u32 src_v = 1, dst_v = 1;
        u32 src_h = 1, dst_h = 1;

        amdgpu_crtc->h_border = 0;
        amdgpu_crtc->v_border = 0;

        list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
                if (encoder->crtc != crtc)
                        continue;
                amdgpu_encoder = to_amdgpu_encoder(encoder);
                connector = amdgpu_get_connector_for_encoder(encoder);
                amdgpu_connector = to_amdgpu_connector(connector);

                /* set scaling */
                if (amdgpu_encoder->rmx_type == RMX_OFF)
                        amdgpu_crtc->rmx_type = RMX_OFF;
                else if (mode->hdisplay < amdgpu_encoder->native_mode.hdisplay ||
                         mode->vdisplay < amdgpu_encoder->native_mode.vdisplay)
                        amdgpu_crtc->rmx_type = amdgpu_encoder->rmx_type;
                else
                        amdgpu_crtc->rmx_type = RMX_OFF;
                /* copy native mode */
                memcpy(&amdgpu_crtc->native_mode,
                       &amdgpu_encoder->native_mode,
                       sizeof(struct drm_display_mode));
                src_v = crtc->mode.vdisplay;
                dst_v = amdgpu_crtc->native_mode.vdisplay;
                src_h = crtc->mode.hdisplay;
                dst_h = amdgpu_crtc->native_mode.hdisplay;

                /* fix up for overscan on hdmi */
                if ((!(mode->flags & DRM_MODE_FLAG_INTERLACE)) &&
                    ((amdgpu_encoder->underscan_type == UNDERSCAN_ON) ||
                     ((amdgpu_encoder->underscan_type == UNDERSCAN_AUTO) &&
                      drm_detect_hdmi_monitor(amdgpu_connector_edid(connector)) &&
                      amdgpu_display_is_hdtv_mode(mode)))) {
                        if (amdgpu_encoder->underscan_hborder != 0)
                                amdgpu_crtc->h_border = amdgpu_encoder->underscan_hborder;
                        else
                                amdgpu_crtc->h_border = (mode->hdisplay >> 5) + 16;
                        if (amdgpu_encoder->underscan_vborder != 0)
                                amdgpu_crtc->v_border = amdgpu_encoder->underscan_vborder;
                        else
                                amdgpu_crtc->v_border = (mode->vdisplay >> 5) + 16;
                        amdgpu_crtc->rmx_type = RMX_FULL;
                        src_v = crtc->mode.vdisplay;
                        dst_v = crtc->mode.vdisplay - (amdgpu_crtc->v_border * 2);
                        src_h = crtc->mode.hdisplay;
                        dst_h = crtc->mode.hdisplay - (amdgpu_crtc->h_border * 2);
                }
        }
        if (amdgpu_crtc->rmx_type != RMX_OFF) {
                fixed20_12 a, b;
                a.full = dfixed_const(src_v);
                b.full = dfixed_const(dst_v);
                amdgpu_crtc->vsc.full = dfixed_div(a, b);
                a.full = dfixed_const(src_h);
                b.full = dfixed_const(dst_h);
                amdgpu_crtc->hsc.full = dfixed_div(a, b);
        } else {
                amdgpu_crtc->vsc.full = dfixed_const(1);
                amdgpu_crtc->hsc.full = dfixed_const(1);
        }
        return true;
}

/*
 * Retrieve current video scanout position of crtc on a given gpu, and
 * an optional accurate timestamp of when query happened.
 *
 * \param dev Device to query.
 * \param pipe Crtc to query.
 * \param flags Flags from caller (DRM_CALLED_FROM_VBLIRQ or 0).
 *              For driver internal use only also supports these flags:
 *
 *              USE_REAL_VBLANKSTART to use the real start of vblank instead
 *              of a fudged earlier start of vblank.
 *
 *              GET_DISTANCE_TO_VBLANKSTART to return distance to the
 *              fudged earlier start of vblank in *vpos and the distance
 *              to true start of vblank in *hpos.
 *
 * \param *vpos Location where vertical scanout position should be stored.
 * \param *hpos Location where horizontal scanout position should go.
 * \param *stime Target location for timestamp taken immediately before
 *               scanout position query. Can be NULL to skip timestamp.
 * \param *etime Target location for timestamp taken immediately after
 *               scanout position query. Can be NULL to skip timestamp.
 *
 * Returns vpos as a positive number while in active scanout area.
 * Returns vpos as a negative number inside vblank, counting the number
 * of scanlines to go until end of vblank, e.g., -1 means "one scanline
 * until start of active scanout / end of vblank."
 *
 * \return Flags, or'ed together as follows:
 *
 * DRM_SCANOUTPOS_VALID = Query successful.
 * DRM_SCANOUTPOS_INVBL = Inside vblank.
 * DRM_SCANOUTPOS_ACCURATE = Returned position is accurate. A lack of
 * this flag means that returned position may be offset by a constant but
 * unknown small number of scanlines wrt. real scanout position.
 *
 */
int amdgpu_display_get_crtc_scanoutpos(struct drm_device *dev,
                        unsigned int pipe, unsigned int flags, int *vpos,
                        int *hpos, ktime_t *stime, ktime_t *etime,
                        const struct drm_display_mode *mode)
{
        u32 vbl = 0, position = 0;
        int vbl_start, vbl_end, vtotal, ret = 0;
        bool in_vbl = true;

        struct amdgpu_device *adev = 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 (amdgpu_display_page_flip_get_scanoutpos(adev, pipe, &vbl, &position) == 0)
                ret |= DRM_SCANOUTPOS_VALID;

        /* Get optional system timestamp after query. */
        if (etime)
                *etime = ktime_get();

        /* preempt_enable_rt() should go right here in PREEMPT_RT patchset. */

        /* Decode into vertical and horizontal scanout position. */
        *vpos = position & 0x1fff;
        *hpos = (position >> 16) & 0x1fff;

        /* Valid vblank area boundaries from gpu retrieved? */
        if (vbl > 0) {
                /* Yes: Decode. */
                ret |= DRM_SCANOUTPOS_ACCURATE;
                vbl_start = vbl & 0x1fff;
                vbl_end = (vbl >> 16) & 0x1fff;
        }
        else {
                /* No: Fake something reasonable which gives at least ok results. */
                vbl_start = mode->crtc_vdisplay;
                vbl_end = 0;
        }

        /* Called from driver internal vblank counter query code? */
        if (flags & GET_DISTANCE_TO_VBLANKSTART) {
            /* Caller wants distance from real vbl_start in *hpos */
            *hpos = *vpos - vbl_start;
        }

        /* Fudge vblank to start a few scanlines earlier to handle the
         * problem that vblank irqs fire a few scanlines before start
         * of vblank. Some driver internal callers need the true vblank
         * start to be used and signal this via the USE_REAL_VBLANKSTART flag.
         *
         * The cause of the "early" vblank irq is that the irq is triggered
         * by the line buffer logic when the line buffer read position enters
         * the vblank, whereas our crtc scanout position naturally lags the
         * line buffer read position.
         */
        if (!(flags & USE_REAL_VBLANKSTART))
                vbl_start -= adev->mode_info.crtcs[pipe]->lb_vblank_lead_lines;

        /* Test scanout position against vblank region. */
        if ((*vpos < vbl_start) && (*vpos >= vbl_end))
                in_vbl = false;

        /* In vblank? */
        if (in_vbl)
            ret |= DRM_SCANOUTPOS_IN_VBLANK;

        /* Called from driver internal vblank counter query code? */
        if (flags & GET_DISTANCE_TO_VBLANKSTART) {
                /* Caller wants distance from fudged earlier vbl_start */
                *vpos -= vbl_start;
                return ret;
        }

        /* Check if inside vblank area and apply corrective offsets:
         * vpos will then be >=0 in video scanout area, but negative
         * within vblank area, counting down the number of lines until
         * start of scanout.
         */

        /* Inside "upper part" of vblank area? Apply corrective offset if so: */
        if (in_vbl && (*vpos >= vbl_start)) {
                vtotal = mode->crtc_vtotal;
                *vpos = *vpos - vtotal;
        }

        /* Correct for shifted end of vbl at vbl_end. */
        *vpos = *vpos - vbl_end;

        return ret;
}

int amdgpu_display_crtc_idx_to_irq_type(struct amdgpu_device *adev, int crtc)
{
        if (crtc < 0 || crtc >= adev->mode_info.num_crtc)
                return AMDGPU_CRTC_IRQ_NONE;

        switch (crtc) {
        case 0:
                return AMDGPU_CRTC_IRQ_VBLANK1;
        case 1:
                return AMDGPU_CRTC_IRQ_VBLANK2;
        case 2:
                return AMDGPU_CRTC_IRQ_VBLANK3;
        case 3:
                return AMDGPU_CRTC_IRQ_VBLANK4;
        case 4:
                return AMDGPU_CRTC_IRQ_VBLANK5;
        case 5:
                return AMDGPU_CRTC_IRQ_VBLANK6;
        default:
                return AMDGPU_CRTC_IRQ_NONE;
        }
}