diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/gpu/drm/amd/amdgpu/amdgpu_display.c | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/amd/amdgpu/amdgpu_display.c')
-rw-r--r-- | drivers/gpu/drm/amd/amdgpu/amdgpu_display.c | 891 |
1 files changed, 891 insertions, 0 deletions
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c new file mode 100644 index 000000000..5f85c9586 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c @@ -0,0 +1,891 @@ +/* + * 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) + goto out; + + ret = drm_crtc_helper_set_config(set, ctx); + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + if (crtc->enabled) + active = true; + + pm_runtime_mark_last_busy(dev->dev); + + adev = 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; + } + +out: + /* drop the power reference we got coming in here */ + pm_runtime_put_autosuspend(dev->dev); + return ret; +} + +static const char *encoder_names[41] = { + "NONE", + "INTERNAL_LVDS", + "INTERNAL_TMDS1", + "INTERNAL_TMDS2", + "INTERNAL_DAC1", + "INTERNAL_DAC2", + "INTERNAL_SDVOA", + "INTERNAL_SDVOB", + "SI170B", + "CH7303", + "CH7301", + "INTERNAL_DVO1", + "EXTERNAL_SDVOA", + "EXTERNAL_SDVOB", + "TITFP513", + "INTERNAL_LVTM1", + "VT1623", + "HDMI_SI1930", + "HDMI_INTERNAL", + "INTERNAL_KLDSCP_TMDS1", + "INTERNAL_KLDSCP_DVO1", + "INTERNAL_KLDSCP_DAC1", + "INTERNAL_KLDSCP_DAC2", + "SI178", + "MVPU_FPGA", + "INTERNAL_DDI", + "VT1625", + "HDMI_SI1932", + "DP_AN9801", + "DP_DP501", + "INTERNAL_UNIPHY", + "INTERNAL_KLDSCP_LVTMA", + "INTERNAL_UNIPHY1", + "INTERNAL_UNIPHY2", + "NUTMEG", + "TRAVIS", + "INTERNAL_VCE", + "INTERNAL_UNIPHY3", + "HDMI_ANX9805", + "INTERNAL_AMCLK", + "VIRTUAL", +}; + +static const char *hpd_names[6] = { + "HPD1", + "HPD2", + "HPD3", + "HPD4", + "HPD5", + "HPD6", +}; + +void amdgpu_display_print_display_setup(struct drm_device *dev) +{ + struct drm_connector *connector; + struct amdgpu_connector *amdgpu_connector; + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + 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; + } +} |