diff --git a/.clang-format b/.clang-format index 6a3de86ab27a..5ef5743b77c9 100644 --- a/.clang-format +++ b/.clang-format @@ -167,7 +167,7 @@ ForEachMacros: - 'drm_connector_for_each_possible_encoder' - 'drm_exec_for_each_locked_object' - 'drm_exec_for_each_locked_object_reverse' - - 'drm_for_each_bridge_in_chain_scoped' + - 'drm_for_each_bridge_in_chain' - 'drm_for_each_connector_iter' - 'drm_for_each_crtc' - 'drm_for_each_crtc_reverse' diff --git a/.mailmap b/.mailmap index f4f18b06f2f1..d8a1f19c381b 100644 --- a/.mailmap +++ b/.mailmap @@ -628,7 +628,8 @@ Nadav Amit Nadia Yvette Chambers William Lee Irwin III Naoya Horiguchi Naoya Horiguchi -Natalie Vock +Natalie Vock +Natalie Vock Nathan Chancellor Naveen N Rao Naveen N Rao diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,cec.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,cec.yaml index 080cf321209e..bc288b1c6f07 100644 --- a/Documentation/devicetree/bindings/display/mediatek/mediatek,cec.yaml +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,cec.yaml @@ -15,10 +15,13 @@ description: | properties: compatible: - enum: - - mediatek,mt7623-cec - - mediatek,mt8167-cec - - mediatek,mt8173-cec + oneOf: + - const: mediatek,mt8173-cec + - items: + - enum: + - mediatek,mt7623-cec + - mediatek,mt8167-cec + - const: mediatek,mt8173-cec reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml b/Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml new file mode 100644 index 000000000000..1e86f5329b22 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/novatek,nt36536.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Novatek NT36536 based DSI display Panels + +maintainers: + - Pengyu Luo + +description: + The Novatek NT36536 is a generic DSI Panel IC used to drive dsi + panels. Support video mode panels from China Star Optoelectronics + Technology (CSOT), such as PP8807HB1-1 which is a dual-link 10-bit + panel. + +allOf: + - $ref: panel-common-dual.yaml# + +properties: + compatible: + items: + - enum: + - csot,pp8807hb1-1 + - const: novatek,nt36536 + + reg: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + vddio-supply: + description: I/O source voltage rail + + vsp-supply: + description: Positive source voltage rail + + vsn-supply: + description: Negative source voltage rail + + backlight: true + ports: true + +required: + - compatible + - reg + - vddio-supply + - reset-gpios + - ports + +additionalProperties: false + +examples: + - | + #include + + dsi { + #address-cells = <1>; + #size-cells = <0>; + + panel@0 { + compatible = "csot,pp8807hb1-1", "novatek,nt36536"; + reg = <0>; + + vddio-supply = <&vreg_iovdd_1p8>; + reset-gpios = <&tlmm 98 GPIO_ACTIVE_LOW>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + panel_in_0: endpoint { + remote-endpoint = <&dsi0_out>; + }; + }; + + port@1{ + reg = <1>; + panel_in_1: endpoint { + remote-endpoint = <&dsi1_out>; + }; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml b/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml index b89f86bc0683..8382d4920bfd 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml @@ -58,6 +58,8 @@ properties: - hydis,hv070wx2-1e0 # Jenson Display BL-JT60050-01A 7" WSVGA (1024x600) color TFT LCD LVDS panel - jenson,bl-jt60050-01a + # Opto Logic SCX1001511GGC49 10.1" WXGA (1280x800) TFT LCD LVDS panel + - optologic,scx1001511ggc49 # Riverdi RVT101HVLNWC00 10.1" WXGA (1280x800) TFT LCD LVDS panel - riverdi,rvt101hvlnwc00 # Riverdi RVT70HSLNWCA0 7.0" WSVGA (1024x600) TFT LCD LVDS panel diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml index 7200095ef19e..bdf981781bd5 100644 --- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml +++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml @@ -22,6 +22,7 @@ properties: - nvidia,tegra186-vic - nvidia,tegra194-vic - nvidia,tegra234-vic + - nvidia,tegra264-vic - items: - const: nvidia,tegra132-vic diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml index 3563378a01af..8312b7699cbe 100644 --- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml +++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml @@ -25,6 +25,7 @@ properties: - nvidia,tegra186-host1x - nvidia,tegra194-host1x - nvidia,tegra234-host1x + - nvidia,tegra264-host1x - items: - const: nvidia,tegra132-host1x @@ -57,7 +58,8 @@ properties: enum: [1, 2] ranges: - maxItems: 1 + minItems: 1 + maxItems: 2 clocks: description: Must contain one entry, for the module clock. See @@ -192,6 +194,7 @@ allOf: contains: enum: - nvidia,tegra234-host1x + - nvidia,tegra264-host1x then: properties: reg-names: @@ -239,6 +242,21 @@ allOf: required: - reg-names + - if: + properties: + compatible: + contains: + enum: + - nvidia,tegra264-host1x + then: + properties: + ranges: + minItems: 2 + maxItems: 2 + else: + properties: + ranges: + maxItems: 1 examples: - | diff --git a/Documentation/gpu/amdgpu/display/display-manager.rst b/Documentation/gpu/amdgpu/display/display-manager.rst index b269ff3f7a54..f6de9e7779e2 100644 --- a/Documentation/gpu/amdgpu/display/display-manager.rst +++ b/Documentation/gpu/amdgpu/display/display-manager.rst @@ -32,6 +32,9 @@ Interrupts .. kernel-doc:: drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c :functions: register_hpd_handlers dm_crtc_high_irq dm_pflip_high_irq +.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_display.c + :functions: amdgpu_display_hotplug_work_func + Atomic Implementation ===================== @@ -178,3 +181,9 @@ following path: 2. On DC interface, :c:type:`struct mpcc_blnd_cfg ` programs the MPCC blend configuration considering the :c:type:`dc_plane_info ` input from DPP. + +Display Properties +================== + +.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_display.c + :doc: property for adaptive backlight modulation diff --git a/Documentation/gpu/intel-display/dp-link-capabilities.rst b/Documentation/gpu/intel-display/dp-link-capabilities.rst new file mode 100644 index 000000000000..331cc69d13a0 --- /dev/null +++ b/Documentation/gpu/intel-display/dp-link-capabilities.rst @@ -0,0 +1,11 @@ +.. SPDX-License-Identifier: MIT +.. Copyright © 2026 Intel Corporation + +DisplayPort Link Capabilities +============================= + +.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dp_link_caps.c + :doc: DisplayPort link capabilities + +.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dp_link_caps.h + :internal: diff --git a/Documentation/gpu/intel-display/index.rst b/Documentation/gpu/intel-display/index.rst index 6fa929d82c38..e81f49bf20df 100644 --- a/Documentation/gpu/intel-display/index.rst +++ b/Documentation/gpu/intel-display/index.rst @@ -39,6 +39,7 @@ driver. The display driver isn't an independent driver in that sense. frontbuffer hotplug dp-link-training + dp-link-capabilities plane psr snps-phy diff --git a/MAINTAINERS b/MAINTAINERS index 6dea93a41962..721ecafe4b2a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6636,7 +6636,7 @@ F: tools/testing/selftests/cgroup/test_cpuset_v1_base.sh CONTROL GROUP - DEVICE MEMORY CONTROLLER (DMEM) M: Maarten Lankhorst M: Maxime Ripard -M: Natalie Vock +M: Natalie Vock L: cgroups@vger.kernel.org L: dri-devel@lists.freedesktop.org S: Maintained diff --git a/drivers/accel/ivpu/ivpu_fw.c b/drivers/accel/ivpu/ivpu_fw.c index 5a8f7dfb54e6..b78b7c35cd9b 100644 --- a/drivers/accel/ivpu/ivpu_fw.c +++ b/drivers/accel/ivpu/ivpu_fw.c @@ -318,7 +318,7 @@ static int ivpu_fw_parse(struct ivpu_device *vdev) fw->shave_nn_size = PAGE_ALIGN(fw_hdr->shave_nn_fw_size); fw->cold_boot_entry_point = fw_hdr->entry_point; - fw->trace_level = min_t(u32, ivpu_fw_log_level, IVPU_FW_LOG_FATAL); + fw->trace_level = min(ivpu_fw_log_level, IVPU_FW_LOG_FATAL); fw->trace_destination_mask = VPU_TRACE_DESTINATION_VERBOSE_TRACING; fw->trace_hw_component_mask = -1; diff --git a/drivers/accel/ivpu/ivpu_mmu_context.c b/drivers/accel/ivpu/ivpu_mmu_context.c index c4014c83e727..bb3ca81838e3 100644 --- a/drivers/accel/ivpu/ivpu_mmu_context.c +++ b/drivers/accel/ivpu/ivpu_mmu_context.c @@ -588,8 +588,8 @@ void ivpu_mmu_context_init(struct ivpu_device *vdev, struct ivpu_mmu_context *ct start = vdev->hw->ranges.runtime.start; end = vdev->hw->ranges.shave.end; } else { - start = min_t(u64, vdev->hw->ranges.user.start, vdev->hw->ranges.shave.start); - end = max_t(u64, vdev->hw->ranges.user.end, vdev->hw->ranges.dma.end); + start = min(vdev->hw->ranges.user.start, vdev->hw->ranges.shave.start); + end = max(vdev->hw->ranges.user.end, vdev->hw->ranges.dma.end); } drm_mm_init(&ctx->mm, start, end - start); diff --git a/drivers/gpu/drm/adp/adp-mipi.c b/drivers/gpu/drm/adp/adp-mipi.c index cba7d32150a9..232bb9539423 100644 --- a/drivers/gpu/drm/adp/adp-mipi.c +++ b/drivers/gpu/drm/adp/adp-mipi.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -222,6 +223,9 @@ static int adp_dsi_bridge_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs adp_dsi_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = adp_dsi_bridge_attach, }; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 8339ab6f5d32..7974f9b7944f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -223,6 +223,7 @@ extern int amdgpu_smartshift_bias; extern int amdgpu_use_xgmi_p2p; extern int amdgpu_mtype_local; extern int amdgpu_enforce_isolation; +extern uint amdgpu_debug_mask; #ifdef CONFIG_HSA_AMD extern int sched_policy; extern bool debug_evictions; @@ -926,7 +927,6 @@ struct amdgpu_device { bool debug_largebar; bool debug_disable_soft_recovery; bool debug_use_vram_fw_buf; - bool debug_enable_ras_aca; bool debug_exp_resets; bool debug_disable_gpu_ring_reset; bool debug_vm_userptr; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index c693c508df1a..816d8817f0b2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -39,6 +39,7 @@ #if IS_ENABLED(CONFIG_HSA_AMD) #include "kfd_priv.h" #endif +#include "kfd_svm.h" /* Total memory size in system memory and all GPU VRAM. Used to * estimate worst case amount of memory to reserve for page tables @@ -711,7 +712,8 @@ int amdgpu_amdkfd_submit_ib(struct amdgpu_device *adev, goto err; } - ret = amdgpu_job_alloc(adev, NULL, NULL, NULL, 1, &job, 0); + ret = amdgpu_job_alloc(adev, NULL, NULL, NULL, 1, 0, GFP_KERNEL, + &job); if (ret) goto err; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h index 338412a750ed..1b7dc0d3963b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h @@ -99,7 +99,6 @@ struct amdgpu_amdkfd_fence { struct mm_struct *mm; spinlock_t lock; char timeline_name[TASK_COMM_LEN]; - struct svm_range_bo *svm_bo; uint16_t context_id; }; @@ -194,7 +193,6 @@ int amdgpu_queue_mask_bit_to_set_resource_bit(struct amdgpu_device *adev, struct amdgpu_amdkfd_fence *amdgpu_amdkfd_fence_create(u64 context, struct mm_struct *mm, - struct svm_range_bo *svm_bo, u16 context_id); int amdgpu_amdkfd_drm_client_create(struct amdgpu_device *adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c index 6a364357522b..9b10d015671c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c @@ -62,7 +62,6 @@ static atomic_t fence_seq = ATOMIC_INIT(0); struct amdgpu_amdkfd_fence *amdgpu_amdkfd_fence_create(u64 context, struct mm_struct *mm, - struct svm_range_bo *svm_bo, u16 context_id) { struct amdgpu_amdkfd_fence *fence; @@ -76,7 +75,6 @@ struct amdgpu_amdkfd_fence *amdgpu_amdkfd_fence_create(u64 context, fence->mm = mm; get_task_comm(fence->timeline_name, current); spin_lock_init(&fence->lock); - fence->svm_bo = svm_bo; fence->context_id = context_id; dma_fence_init(&fence->base, &amdkfd_fence_ops, &fence->lock, context, atomic_inc_return(&fence_seq)); @@ -92,7 +90,7 @@ struct amdgpu_amdkfd_fence *to_amdgpu_amdkfd_fence(struct dma_fence *f) return NULL; fence = container_of(f, struct amdgpu_amdkfd_fence, base); - if (f->ops == &amdkfd_fence_ops) + if (rcu_access_pointer(f->ops) == &amdkfd_fence_ops) return fence; return NULL; @@ -128,17 +126,8 @@ static bool amdkfd_fence_enable_signaling(struct dma_fence *f) if (dma_fence_is_signaled(f)) return true; - /* if fence->svm_bo is NULL, means this fence is created through - * init_kfd_vm() or amdgpu_amdkfd_gpuvm_restore_process_bos(). - * Therefore, this fence is amdgpu_amdkfd_fence->eviction_fence. - */ - if (!fence->svm_bo) { - if (!kgd2kfd_schedule_evict_and_restore_process(fence->mm, fence->context_id, f)) - return true; - } else { - if (!svm_range_schedule_evict_svm_bo(fence)) - return true; - } + if (!kgd2kfd_schedule_evict_and_restore_process(fence->mm, fence->context_id, f)) + return true; return false; } @@ -172,7 +161,6 @@ static void amdkfd_fence_release(struct dma_fence *f) * * Check if @mm is same as that of the fence @f, if same return TRUE else * return FALSE. - * For svm bo, which support vram overcommitment, always return FALSE. */ bool amdkfd_fence_check_mm(struct dma_fence *f, struct mm_struct *mm) { @@ -180,7 +168,7 @@ bool amdkfd_fence_check_mm(struct dma_fence *f, struct mm_struct *mm) if (!fence) return false; - else if (fence->mm == mm && !fence->svm_bo) + else if (fence->mm == mm) return true; return false; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c index 20831dbebc31..34481ee7065a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c @@ -1423,7 +1423,7 @@ static int init_kfd_vm(struct amdgpu_vm *vm, void **process_info, info->eviction_fence = amdgpu_amdkfd_fence_create(dma_fence_context_alloc(1), current->mm, - NULL, process->context_id); + process->context_id); if (!info->eviction_fence) { pr_err("Failed to create eviction fence\n"); ret = -ENOMEM; @@ -3093,7 +3093,7 @@ int amdgpu_amdkfd_gpuvm_restore_process_bos(void *info, struct dma_fence __rcu * amdgpu_amdkfd_fence_create( process_info->eviction_fence->base.context, process_info->eviction_fence->mm, - NULL, process_info->context_id); + process_info->context_id); if (!new_fence) { pr_err("Failed to create eviction fence\n"); @@ -3104,7 +3104,7 @@ int amdgpu_amdkfd_gpuvm_restore_process_bos(void *info, struct dma_fence __rcu * process_info->eviction_fence = new_fence; replace_eviction_fence(ef, dma_fence_get(&new_fence->base)); } else { - WARN_ONCE(*ef != &process_info->eviction_fence->base, + WARN_ONCE(rcu_access_pointer(*ef) != &process_info->eviction_fence->base, "KFD eviction fence doesn't match KGD process_info"); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c index 3ebdd792feec..08ed1eb69558 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c @@ -378,19 +378,59 @@ static bool amdgpu_read_disabled_bios(struct amdgpu_device *adev) } #ifdef CONFIG_ACPI +/** + * amdgpu_acpi_vfct_match() - Check if a VFCT entry matches the device + * @adev: AMDGPU device + * @vhdr: VFCT image header to check + * + * VFCT entries contain the PCI bus number as recorded during BIOS POST. + * On systems where the kernel renumbers PCI buses (e.g. pci=realloc or + * resource conflicts), the runtime bus number may differ from the POST + * value. Match by device identity (vendor + device + function) and use + * the bus number as a preference: exact bus match is preferred, but when + * the bus numbers disagree we accept the entry if the device identity + * matches. + * + * Returns: 0 on match, -ENODEV on no match + */ +static int amdgpu_acpi_vfct_match(struct amdgpu_device *adev, + VFCT_IMAGE_HEADER *vhdr) +{ + /* Vendor and device IDs must always match */ + if (vhdr->VendorID != adev->pdev->vendor || + vhdr->DeviceID != adev->pdev->device) + return -ENODEV; + + if (vhdr->PCIDevice != PCI_SLOT(adev->pdev->devfn) || + vhdr->PCIFunction != PCI_FUNC(adev->pdev->devfn)) + return -ENODEV; + + /* Exact bus number match - preferred */ + if (vhdr->PCIBus == adev->pdev->bus->number) + return 0; + + /* Bus mismatch but device identity matches (PCI renumbering case) */ + dev_notice(adev->dev, + "VFCT bus number mismatch: table %u != runtime %u, matching by device identity (vendor 0x%04x device 0x%04x)\n", + vhdr->PCIBus, adev->pdev->bus->number, + adev->pdev->vendor, adev->pdev->device); + return 0; +} + static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) { struct acpi_table_header *hdr; acpi_size tbl_size; UEFI_ACPI_VFCT *vfct; unsigned int offset; + bool r = false; if (!ACPI_SUCCESS(acpi_get_table("VFCT", 1, &hdr))) return false; tbl_size = hdr->length; if (tbl_size < sizeof(UEFI_ACPI_VFCT)) { dev_info(adev->dev, "ACPI VFCT table present but broken (too short #1),skipping\n"); - return false; + goto out; } vfct = (UEFI_ACPI_VFCT *)hdr; @@ -403,36 +443,36 @@ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) offset += sizeof(VFCT_IMAGE_HEADER); if (offset > tbl_size) { dev_info(adev->dev, "ACPI VFCT image header truncated,skipping\n"); - return false; + goto out; } offset += vhdr->ImageLength; if (offset > tbl_size) { dev_info(adev->dev, "ACPI VFCT image truncated,skipping\n"); - return false; + goto out; } if (vhdr->ImageLength && - vhdr->PCIBus == adev->pdev->bus->number && - vhdr->PCIDevice == PCI_SLOT(adev->pdev->devfn) && - vhdr->PCIFunction == PCI_FUNC(adev->pdev->devfn) && - vhdr->VendorID == adev->pdev->vendor && - vhdr->DeviceID == adev->pdev->device) { + !amdgpu_acpi_vfct_match(adev, vhdr)) { adev->bios = kmemdup(&vbios->VbiosContent, vhdr->ImageLength, GFP_KERNEL); if (!check_atom_bios(adev, vhdr->ImageLength)) { amdgpu_bios_release(adev); - return false; + goto out; } adev->bios_size = vhdr->ImageLength; - return true; + r = true; + goto out; } } dev_info(adev->dev, "ACPI VFCT table present but broken (too short #2),skipping\n"); - return false; + +out: + acpi_put_table(hdr); + return r; } #else static inline bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cgs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cgs.c index 9bbe60968352..d3531f0c90b4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cgs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cgs.c @@ -239,7 +239,7 @@ static int amdgpu_cgs_get_firmware_info(struct cgs_device *cgs_device, info->fw_version = amdgpu_get_firmware_version(cgs_device, type); info->feature_version = (uint16_t)le32_to_cpu(header->ucode_feature_version); } else { - char fw_name[30] = {0}; + const char *fw_name = NULL; int err = 0; uint32_t ucode_size; uint32_t ucode_start_address; @@ -255,17 +255,17 @@ static int amdgpu_cgs_get_firmware_info(struct cgs_device *cgs_device, (adev->pdev->revision == 0x81) || (adev->pdev->device == 0x665f)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/bonaire_k_smc.bin"); + fw_name = "bonaire_k_smc.bin"; } else { - strscpy(fw_name, "amdgpu/bonaire_smc.bin"); + fw_name = "bonaire_smc.bin"; } break; case CHIP_HAWAII: if (adev->pdev->revision == 0x80) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/hawaii_k_smc.bin"); + fw_name = "hawaii_k_smc.bin"; } else { - strscpy(fw_name, "amdgpu/hawaii_smc.bin"); + fw_name = "hawaii_smc.bin"; } break; case CHIP_TOPAZ: @@ -275,76 +275,76 @@ static int amdgpu_cgs_get_firmware_info(struct cgs_device *cgs_device, ((adev->pdev->device == 0x6900) && (adev->pdev->revision == 0xD1)) || ((adev->pdev->device == 0x6900) && (adev->pdev->revision == 0xD3))) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/topaz_k_smc.bin"); + fw_name = "topaz_k_smc.bin"; } else - strscpy(fw_name, "amdgpu/topaz_smc.bin"); + fw_name = "topaz_smc.bin"; break; case CHIP_TONGA: if (((adev->pdev->device == 0x6939) && (adev->pdev->revision == 0xf1)) || ((adev->pdev->device == 0x6938) && (adev->pdev->revision == 0xf1))) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/tonga_k_smc.bin"); + fw_name = "tonga_k_smc.bin"; } else - strscpy(fw_name, "amdgpu/tonga_smc.bin"); + fw_name = "tonga_smc.bin"; break; case CHIP_FIJI: - strscpy(fw_name, "amdgpu/fiji_smc.bin"); + fw_name = "fiji_smc.bin"; break; case CHIP_POLARIS11: if (type == CGS_UCODE_ID_SMU) { if (ASICID_IS_P21(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris11_k_smc.bin"); + fw_name = "polaris11_k_smc.bin"; } else if (ASICID_IS_P31(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris11_k2_smc.bin"); + fw_name = "polaris11_k2_smc.bin"; } else { - strscpy(fw_name, "amdgpu/polaris11_smc.bin"); + fw_name = "polaris11_smc.bin"; } } else if (type == CGS_UCODE_ID_SMU_SK) { - strscpy(fw_name, "amdgpu/polaris11_smc_sk.bin"); + fw_name = "polaris11_smc_sk.bin"; } break; case CHIP_POLARIS10: if (type == CGS_UCODE_ID_SMU) { if (ASICID_IS_P20(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris10_k_smc.bin"); + fw_name = "polaris10_k_smc.bin"; } else if (ASICID_IS_P30(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris10_k2_smc.bin"); + fw_name = "polaris10_k2_smc.bin"; } else { - strscpy(fw_name, "amdgpu/polaris10_smc.bin"); + fw_name = "polaris10_smc.bin"; } } else if (type == CGS_UCODE_ID_SMU_SK) { - strscpy(fw_name, "amdgpu/polaris10_smc_sk.bin"); + fw_name = "polaris10_smc_sk.bin"; } break; case CHIP_POLARIS12: if (ASICID_IS_P23(adev->pdev->device, adev->pdev->revision)) { info->is_kicker = true; - strscpy(fw_name, "amdgpu/polaris12_k_smc.bin"); + fw_name = "polaris12_k_smc.bin"; } else { - strscpy(fw_name, "amdgpu/polaris12_smc.bin"); + fw_name = "polaris12_smc.bin"; } break; case CHIP_VEGAM: - strscpy(fw_name, "amdgpu/vegam_smc.bin"); + fw_name = "vegam_smc.bin"; break; case CHIP_VEGA10: if ((adev->pdev->device == 0x687f) && ((adev->pdev->revision == 0xc0) || (adev->pdev->revision == 0xc1) || (adev->pdev->revision == 0xc3))) - strscpy(fw_name, "amdgpu/vega10_acg_smc.bin"); + fw_name = "vega10_acg_smc.bin"; else - strscpy(fw_name, "amdgpu/vega10_smc.bin"); + fw_name = "vega10_smc.bin"; break; case CHIP_VEGA12: - strscpy(fw_name, "amdgpu/vega12_smc.bin"); + fw_name = "vega12_smc.bin"; break; case CHIP_VEGA20: - strscpy(fw_name, "amdgpu/vega20_smc.bin"); + fw_name = "vega20_smc.bin"; break; default: drm_err(adev_to_drm(adev), "SMC firmware not supported\n"); @@ -353,10 +353,8 @@ static int amdgpu_cgs_get_firmware_info(struct cgs_device *cgs_device, err = amdgpu_ucode_request(adev, &adev->pm.fw, AMDGPU_UCODE_REQUIRED, - "%s", fw_name); + "amdgpu/%s", fw_name); if (err) { - drm_err(adev_to_drm(adev), - "Failed to load firmware \"%s\"\n", fw_name); amdgpu_ucode_release(&adev->pm.fw); return err; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c index 6fb129025761..373e47050630 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c @@ -498,9 +498,28 @@ int amdgpu_cper_init(struct amdgpu_device *adev) return 0; } +int amdgpu_cper_deferred_init(struct amdgpu_device *adev) +{ + int r; + + if (adev->cper.enabled) + return 0; + + r = amdgpu_cper_init(adev); + if (r || !adev->cper.enabled) + return r; + +#if defined(CONFIG_DEBUG_FS) + if (adev_to_drm(adev)->primary->debugfs_root) + amdgpu_debugfs_ring_init(adev, &adev->cper.ring_buf); +#endif + + return 0; +} + int amdgpu_cper_fini(struct amdgpu_device *adev) { - if (amdgpu_sriov_vf(adev)) + if (amdgpu_sriov_vf(adev) && !amdgpu_sriov_ras_cper_en(adev)) return 0; adev->cper.enabled = false; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h index d12c98077d9d..eea30be91b47 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h @@ -92,6 +92,7 @@ int amdgpu_cper_generate_bp_threshold_record(struct amdgpu_device *adev); void amdgpu_cper_ring_write(struct amdgpu_ring *ring, void *src, int count); int amdgpu_cper_init(struct amdgpu_device *adev); +int amdgpu_cper_deferred_init(struct amdgpu_device *adev); int amdgpu_cper_fini(struct amdgpu_device *adev); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index d58701908158..6b39c7f0e894 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -267,8 +267,8 @@ static int amdgpu_cs_pass1(struct amdgpu_cs_parser *p, for (i = 0; i < p->gang_size; ++i) { ret = amdgpu_job_alloc(p->adev, vm, p->entities[i], vm, - num_ibs[i], &p->jobs[i], - p->filp->client_id); + num_ibs[i], p->filp->client_id, + GFP_KERNEL, &p->jobs[i]); if (ret) goto free_all_kdata; switch (p->adev->enforce_isolation[fpriv->xcp_id]) { @@ -1171,19 +1171,6 @@ static int amdgpu_cs_vm_handling(struct amdgpu_cs_parser *p) job->vm_pd_addr = amdgpu_gmc_pd_addr(vm->root.bo); } - if (adev->debug_vm) { - /* Invalidate all BOs to test for userspace bugs */ - amdgpu_bo_list_for_each_entry(e, p->bo_list) { - struct amdgpu_bo *bo = e->bo; - - /* ignore duplicates */ - if (!bo) - continue; - - amdgpu_vm_bo_invalidate(bo, false); - } - } - return 0; } @@ -1371,6 +1358,8 @@ static int amdgpu_cs_submit(struct amdgpu_cs_parser *p, /* Cleanup the parser structure */ static void amdgpu_cs_parser_fini(struct amdgpu_cs_parser *parser) { + struct amdgpu_device *adev = parser->adev; + struct amdgpu_bo_list_entry *e; unsigned int i; amdgpu_sync_free(&parser->sync); @@ -1386,8 +1375,21 @@ static void amdgpu_cs_parser_fini(struct amdgpu_cs_parser *parser) if (parser->ctx) amdgpu_ctx_put(parser->ctx); - if (parser->bo_list) + if (parser->bo_list) { + if (adev->debug_vm) { + /* Invalidate all BOs to test for userspace bugs */ + amdgpu_bo_list_for_each_entry(e, parser->bo_list) { + struct amdgpu_bo *bo = e->bo; + + /* ignore duplicates */ + if (!bo) + continue; + + amdgpu_vm_bo_invalidate(bo, false); + } + } amdgpu_bo_list_put(parser->bo_list); + } for (i = 0; i < parser->nchunks; i++) kvfree(parser->chunks[i].kdata); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index 0455c2cd043f..132d054900b5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -44,6 +44,11 @@ #if defined(CONFIG_DEBUG_FS) +/* Encode milliwatts in the raw Q24.8 sensor report format used by UMR. */ +#define AMDGPU_DEBUGFS_PWR_MW_TO_Q24_8(power_mw) \ + DIV_ROUND_CLOSEST_ULL((u64)(power_mw) * BIT(8), \ + MILLIWATT_PER_WATT) + /** * amdgpu_debugfs_process_reg_op - Handle MMIO register reads/writes * @@ -1105,6 +1110,10 @@ static ssize_t amdgpu_debugfs_sensor_read(struct file *f, char __user *buf, return r; } + if (idx == AMDGPU_PP_SENSOR_GPU_AVG_POWER || + idx == AMDGPU_PP_SENSOR_GPU_INPUT_POWER) + values[0] = AMDGPU_DEBUGFS_PWR_MW_TO_Q24_8(values[0]); + if (size > valuesize) { amdgpu_virt_disable_access_debugfs(adev); return -EINVAL; @@ -1320,8 +1329,8 @@ err: * @size: Number of bytes to read * @pos: Offset to seek to * - * Read the last residency value logged. It doesn't auto update, one needs to - * stop logging before getting the current value. + * Read a live GFXOFF residency sample from firmware. One needs to start logging + * before getting the current value. */ static ssize_t amdgpu_debugfs_gfxoff_residency_read(struct file *f, char __user *buf, size_t size, loff_t *pos) @@ -2178,6 +2187,8 @@ int amdgpu_debugfs_init(struct amdgpu_device *adev) if (!ring) continue; + if (ring == &adev->cper.ring_buf && !adev->cper.enabled) + continue; amdgpu_debugfs_ring_init(adev, ring); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index 4fd0df3aa70d..39b2a4c0e011 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -235,6 +235,9 @@ amdgpu_devcoredump_print_ibs(struct drm_printer *p, drm_printf(p, "\nIB #%d 0x%llx %d dw\n", i, coredump->ibs[i].gpu_addr, coredump->ibs[i].ib_size_dw); + + for (int j = 0; j < coredump->ibs[i].ib_size_dw; j++) + drm_printf(p, "0xffffffff\n"); } return; } @@ -356,10 +359,14 @@ amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_inf drm_printf(&p, "kernel: %s\n", init_utsname()->release); drm_printf(&p, "module: " KBUILD_MODNAME "\n"); drm_printf(&p, "time: %ptSp\n", &coredump->reset_time); + drm_printf(&p, "pasid: %u\n", coredump->pasid); + drm_printf(&p, "vmid: %u\n", coredump->vmid); if (coredump->reset_task_info.task.pid) - drm_printf(&p, "process_name: %s PID: %d\n", + drm_printf(&p, "process_name: %s TGID: %d thread: %s PID: %d\n", coredump->reset_task_info.process_name, + coredump->reset_task_info.tgid, + coredump->reset_task_info.task.comm, coredump->reset_task_info.task.pid); /* SOC Information */ @@ -563,6 +570,7 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, amdgpu_vm_put_task_info(ti); } coredump->pasid = job->pasid; + coredump->vmid = job->vmid; coredump->num_ibs = job->num_ibs; for (i = 0; i < job->num_ibs; ++i) { coredump->ibs[i].gpu_addr = job->ibs[i].gpu_addr; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h index 2371e20fc68b..63f27337c09a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h @@ -63,6 +63,7 @@ struct amdgpu_coredump_info { char *formatted; unsigned int pasid; + unsigned int vmid; int num_ibs; struct amdgpu_coredump_ib_info ibs[] __counted_by(num_ibs); }; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index b39ec5ed6242..638dfaf127fd 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -745,6 +745,64 @@ void amdgpu_device_mm_access(struct amdgpu_device *adev, loff_t pos, drm_dev_exit(idx); } +#ifdef CONFIG_64BIT +/* + * During early SR-IOV VF init, host-provided init data can live in FB before + * the normal VRAM aperture mapping is ready. Use a temporary BAR0 mapping for + * reads only, and verify it matches the VRAM aperture when aperture information + * is already available. + */ +static int amdgpu_device_read_fb_via_bar0(struct amdgpu_device *adev, + u64 offset, void *buf, size_t size) +{ + resource_size_t aper_base, aper_size, bar_start, bar_size, map_base; + void __iomem *vram; + size_t map_offset, map_size; + unsigned long flags; + u64 end; + + if (!buf || !size) + return -EINVAL; + + flags = pci_resource_flags(adev->pdev, 0); + if ((flags & IORESOURCE_UNSET) || !(flags & IORESOURCE_MEM)) + return -EINVAL; + + bar_start = pci_resource_start(adev->pdev, 0); + bar_size = pci_resource_len(adev->pdev, 0); + if (!bar_size) + return -ENODEV; + + aper_base = adev->gmc.aper_base; + aper_size = adev->gmc.visible_vram_size ? adev->gmc.visible_vram_size : + adev->gmc.aper_size; + + if (aper_base || aper_size) { + if (aper_base != bar_start || aper_size > bar_size) + return -EINVAL; + } else { + aper_base = bar_start; + aper_size = bar_size; + } + + if (check_add_overflow(offset, size, &end) || end > aper_size) + return -EINVAL; + + map_offset = offset_in_page(offset); + map_base = aper_base + (offset & PAGE_MASK); + map_size = PAGE_ALIGN(map_offset + size); + + vram = ioremap_wc(map_base, map_size); + if (!vram) + return -ENOMEM; + + memcpy_fromio(buf, (u8 __iomem *)vram + map_offset, size); + iounmap(vram); + + return 0; +} +#endif + /** * amdgpu_device_aper_access - access vram by vram aperture * @@ -764,8 +822,12 @@ size_t amdgpu_device_aper_access(struct amdgpu_device *adev, loff_t pos, size_t count = 0; uint64_t last; - if (!adev->mman.aper_base_kaddr) + if (!adev->mman.aper_base_kaddr) { + /* Writes still require the regular aperture/MM path. */ + if (!write && !amdgpu_device_read_fb_via_bar0(adev, pos, buf, size)) + return size; return 0; + } last = min(pos + size, adev->gmc.visible_vram_size); if (last > pos) { @@ -1203,28 +1265,44 @@ bool amdgpu_device_seamless_boot_supported(struct amdgpu_device *adev) return amdgpu_ip_version(adev, DCE_HWIP, 0) >= IP_VERSION(3, 0, 0); } -/* - * Intel hosts such as Rocket Lake, Alder Lake, Raptor Lake and Sapphire Rapids - * don't support dynamic speed switching. Until we have confirmation from Intel - * that a specific host supports it, it's safer that we keep it disabled for all. - * - * https://edc.intel.com/content/www/us/en/design/products/platforms/details/raptor-lake-s/13th-generation-core-processors-datasheet-volume-1-of-2/005/pci-express-support/ - * https://gitlab.freedesktop.org/drm/amd/-/issues/2663 - */ +#if IS_ENABLED(CONFIG_X86) +static const struct x86_cpu_id amdgpu_pcie_dynamic_switching_quirks[] = { + /* + * Intel hosts such as Rocket Lake, Alder Lake, Raptor Lake and Sapphire Rapids + * don't support dynamic speed switching. Until we have confirmation from Intel + * that a specific host supports it, it's safer that we keep it disabled for all. + * + * https://edc.intel.com/content/www/us/en/design/products/platforms/details/raptor-lake-s/13th-generation-core-processors-datasheet-volume-1-of-2/005/pci-express-support/ + * https://gitlab.freedesktop.org/drm/amd/-/issues/2663 + */ + X86_MATCH_VENDOR_FAM(INTEL, X86_FAMILY_ANY, NULL), + /* + * AMD Ryzen Pinnacle Ridge (Zen+, family 0x17 model 0x08) CPUs don't + * support PCIe dynamic speed switching. + * https://gitlab.freedesktop.org/drm/amd/-/work_items/5436 + */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x08, NULL), + {} +}; + static bool amdgpu_device_pcie_dynamic_switching_supported(struct amdgpu_device *adev) { -#if IS_ENABLED(CONFIG_X86) - struct cpuinfo_x86 *c = &cpu_data(0); - /* eGPU change speeds based on USB4 fabric conditions */ if (dev_is_removable(adev->dev)) return true; - if (c->x86_vendor == X86_VENDOR_INTEL) + /* Hosts have problems with dynamic speed switching */ + if (x86_match_cpu(amdgpu_pcie_dynamic_switching_quirks)) return false; -#endif + return true; } +#else +static inline bool amdgpu_device_pcie_dynamic_switching_supported(struct amdgpu_device *adev) +{ + return true; +} +#endif static bool amdgpu_device_aspm_support_quirk(struct amdgpu_device *adev) { @@ -1846,16 +1924,25 @@ static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) { struct amdgpu_ip_block *ip_block; struct pci_dev *parent; - bool total, skip_bios; + bool total, skip_bios, early_full_gpu_access = false; uint32_t bios_flags; int i, r; amdgpu_device_enable_virtual_display(adev); if (amdgpu_sriov_vf(adev)) { - r = amdgpu_virt_request_full_gpu(adev, true); - if (r) - return r; + /* + * Legacy hosts do not provide init data before early init, so + * keep the original early full GPU access request for them. Newer + * hosts publish the init data through VF FB, which lets us defer + * full GPU access until after non-GPU early init work is done. + */ + early_full_gpu_access = (adev->virt.req_init_data_ver == 0); + if (early_full_gpu_access) { + r = amdgpu_virt_request_full_gpu(adev, true); + if (r) + return r; + } r = amdgpu_virt_init_critical_region(adev); if (r) @@ -2018,6 +2105,13 @@ static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) if (!total) return -ENODEV; + /* Request full GPU access only for the remaining SR-IOV init work. */ + if (amdgpu_sriov_vf(adev) && !early_full_gpu_access) { + r = amdgpu_virt_request_full_gpu(adev, true); + if (r) + return r; + } + if (adev->gmc.xgmi.supported) amdgpu_xgmi_early_init(adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index d9edac5f321d..cf9c4b8df28b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -2529,6 +2529,7 @@ static int amdgpu_discovery_set_psp_ip_blocks(struct amdgpu_device *adev) amdgpu_device_ip_block_add(adev, &psp_v14_0_ip_block); break; case IP_VERSION(15, 0, 0): + case IP_VERSION(15, 0, 5): case IP_VERSION(15, 0, 9): amdgpu_device_ip_block_add(adev, &psp_v15_0_ip_block); break; @@ -3367,9 +3368,11 @@ int amdgpu_discovery_set_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + adev->family = AMDGPU_FAMILY_GC_11_5_0; + break; case IP_VERSION(11, 7, 0): case IP_VERSION(11, 7, 1): - adev->family = AMDGPU_FAMILY_GC_11_5_0; + adev->family = AMDGPU_FAMILY_GC_11_5_4; break; case IP_VERSION(12, 0, 0): case IP_VERSION(12, 0, 1): diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c index f5cd68542442..59ea338b554c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c @@ -746,7 +746,7 @@ static int convert_tiling_flags_to_modifier_gfx12(struct amdgpu_framebuffer *afb return 0; } -static int convert_tiling_flags_to_modifier(struct amdgpu_framebuffer *afb) +static int convert_tiling_flags_to_modifier_gfx9(struct amdgpu_framebuffer *afb) { struct amdgpu_device *adev = drm_to_adev(afb->base.dev); uint64_t modifier = 0; @@ -940,25 +940,85 @@ static int convert_tiling_flags_to_modifier(struct amdgpu_framebuffer *afb) return 0; } -/* Mirrors the is_displayable check in radeonsi's gfx6_compute_surface */ +static int convert_tiling_flags_to_modifier_gfx6(struct amdgpu_framebuffer *afb) +{ + const uint32_t array_mode = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE); + const uint32_t pipe_config = AMDGPU_TILING_GET(afb->tiling_flags, PIPE_CONFIG); + const uint32_t tile_split = AMDGPU_TILING_GET(afb->tiling_flags, TILE_SPLIT); + const uint32_t micro_tile_mode = AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE); + const uint32_t bank_width = AMDGPU_TILING_GET(afb->tiling_flags, BANK_WIDTH); + const uint32_t bank_height = AMDGPU_TILING_GET(afb->tiling_flags, BANK_HEIGHT); + const uint32_t macro_tile_aspect = AMDGPU_TILING_GET(afb->tiling_flags, MACRO_TILE_ASPECT); + const uint32_t num_banks = AMDGPU_TILING_GET(afb->tiling_flags, NUM_BANKS); + struct amdgpu_device *adev = drm_to_adev(afb->base.dev); + uint64_t modifier = 0; + + switch (array_mode) { + case DC_ARRAY_LINEAR_GENERAL: + case DC_ARRAY_LINEAR_ALLIGNED: + modifier = DRM_FORMAT_MOD_LINEAR; + break; + + case DC_ARRAY_2D_TILED_THIN1: + /* Macro tiled modes only */ + modifier |= + AMD_FMT_MOD_SET(PIPE_CONFIG, pipe_config) | + AMD_FMT_MOD_SET(TILE_SPLIT, tile_split) | + AMD_FMT_MOD_SET(BANK_WIDTH, bank_width) | + AMD_FMT_MOD_SET(BANK_HEIGHT, bank_height) | + AMD_FMT_MOD_SET(MACRO_TILE_ASPECT, macro_tile_aspect) | + AMD_FMT_MOD_SET(NUM_BANKS, num_banks); + fallthrough; + + case DC_ARRAY_1D_TILED_THIN1: + /* Micro and macro tiled modes */ + modifier |= + AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX6) | + AMD_FMT_MOD_SET(TILE, array_mode) | + AMD_FMT_MOD_SET(MICROTILE, micro_tile_mode); + break; + + default: + drm_dbg_kms(&adev->ddev, "array mode 0x%x not supported\n", array_mode); + return -EINVAL; + } + + afb->base.modifier = modifier; + afb->base.flags |= DRM_MODE_FB_MODIFIERS; + return 0; +} + static int check_tiling_flags_gfx6(struct amdgpu_framebuffer *afb) { - u64 micro_tile_mode; + const u64 array_mode = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE); + const u64 micro_tile_mode = AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE); - if (AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) == 1) /* LINEAR_ALIGNED */ + /* LINEAR_GENERAL, LINEAR_ALIGNED */ + if (array_mode == 0 || array_mode == 1) return 0; - micro_tile_mode = AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE); - switch (micro_tile_mode) { - case 0: /* DISPLAY */ - case 3: /* RENDER */ - return 0; - default: + /* 1D_TILED_THIN1, 2D_TILED_THIN1 */ + if (array_mode != 2 && array_mode != 4) { + drm_dbg_kms(afb->base.dev, + "Array mode %llu not supported for scanout\n", + array_mode); + return -EINVAL; + } + + /* + * For now, only allow DISPLAY micro tile mode. + * THIN, DEPTH and THICK modes are not displayable. + * ROTATED has never been supported by Linux or Mesa. + */ + if (micro_tile_mode != 0) { drm_dbg_kms(afb->base.dev, "Micro tile mode %llu not supported for scanout\n", micro_tile_mode); return -EINVAL; } + + return 0; } static void get_block_dimensions(unsigned int block_log2, unsigned int cpp, @@ -1053,6 +1113,85 @@ static int amdgpu_display_verify_plane(struct amdgpu_framebuffer *rfb, int plane return 0; } +static int amdgpu_display_verify_sizes_gfx6(struct drm_device *dev, + const u64 modifier, + unsigned int *out_block_width, + unsigned int *out_block_height) +{ + const u32 display_micro_tile_pitch = 32; /* required by DCE */ + const u32 micro_tile_width = 8; + const u32 micro_tile_height = 8; + const u32 micro_tile_mode = AMD_FMT_MOD_GET(MICROTILE, modifier); + const u32 array_mode = AMD_FMT_MOD_GET(TILE, modifier); + u32 num_banks, bank_width, bank_height, pipe_config, macro_tile_aspect; + u32 num_pipes; + + if (AMD_FMT_MOD_GET(DCC, modifier)) { + drm_dbg_kms(dev, "DCC is not displayable on GFX6-8\n"); + return -EINVAL; + } + if (array_mode != AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1 && + array_mode != AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) { + drm_dbg_kms(dev, "Array mode %u not supported for scanout\n", array_mode); + return -EINVAL; + } + if (micro_tile_mode != AMD_FMT_MOD_MICROTILE_DISPLAY) { + drm_dbg_kms(dev, "Microtile mode %u not supported for scanout\n", micro_tile_mode); + return -EINVAL; + } + + num_banks = 2 << AMD_FMT_MOD_GET(NUM_BANKS, modifier); + bank_width = 1 << AMD_FMT_MOD_GET(BANK_WIDTH, modifier); + bank_height = 1 << AMD_FMT_MOD_GET(BANK_HEIGHT, modifier); + pipe_config = AMD_FMT_MOD_GET(PIPE_CONFIG, modifier); + macro_tile_aspect = 1 << AMD_FMT_MOD_GET(MACRO_TILE_ASPECT, modifier); + + switch (pipe_config) { + case AMD_FMT_MOD_PIPE_CONFIG_P16_32x32_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P16_32x32_16x16: + num_pipes = 16; + break; + + case AMD_FMT_MOD_PIPE_CONFIG_P8_16x16_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_16x32_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_16x32_16x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_16x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_16x32: + case AMD_FMT_MOD_PIPE_CONFIG_P8_32x64_32x32: + num_pipes = 8; + break; + + case AMD_FMT_MOD_PIPE_CONFIG_P4_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P4_16x16: + case AMD_FMT_MOD_PIPE_CONFIG_P4_16x32: + case AMD_FMT_MOD_PIPE_CONFIG_P4_32x32: + num_pipes = 4; + break; + + case AMD_FMT_MOD_PIPE_CONFIG_P2: + num_pipes = 2; + break; + + default: + drm_dbg_kms(dev, "Pipe config %u invalid\n", pipe_config); + return -EINVAL; + } + + /* For reference, see SiLib::InitEquationTable() in addrlib */ + if (array_mode < AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) { + *out_block_width = display_micro_tile_pitch; + *out_block_height = micro_tile_height; + } else { + /* Assume non-PRT macro tiling modes */ + *out_block_width = num_pipes * micro_tile_width * + bank_width * macro_tile_aspect; + *out_block_height = micro_tile_height * bank_height * + num_banks / macro_tile_aspect; + } + + return 0; +} static int amdgpu_display_verify_sizes(struct amdgpu_framebuffer *rfb) { @@ -1093,7 +1232,7 @@ static int amdgpu_display_verify_sizes(struct amdgpu_framebuffer *rfb) get_block_dimensions(block_size_log2, format_info->cpp[i], &block_width, &block_height); - } else { + } else if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) >= AMD_FMT_MOD_TILE_VER_GFX9) { int swizzle = AMD_FMT_MOD_GET(TILE, modifier); switch ((swizzle & ~3) + 1) { @@ -1120,6 +1259,18 @@ static int amdgpu_display_verify_sizes(struct amdgpu_framebuffer *rfb) get_block_dimensions(block_size_log2, format_info->cpp[i], &block_width, &block_height); + } else if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) == AMD_FMT_MOD_TILE_VER_GFX6) { + ret = amdgpu_display_verify_sizes_gfx6(rfb->base.dev, modifier, + &block_width, &block_height); + if (ret) + return ret; + + /* amdgpu_display_verify_plane() assumes the block size is a power of 2 */ + WARN_ON(!is_power_of_2(block_width)); + WARN_ON(!is_power_of_2(block_height)); + WARN_ON(!is_power_of_2(format_info->cpp[i])); + + block_size_log2 = ilog2(block_width * block_height * format_info->cpp[i]); } ret = amdgpu_display_verify_plane(rfb, i, format_info, @@ -1271,8 +1422,10 @@ static int amdgpu_display_framebuffer_init(struct drm_device *dev, !(rfb->base.flags & DRM_MODE_FB_MODIFIERS)) { if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(12, 0, 0)) ret = convert_tiling_flags_to_modifier_gfx12(rfb); + else if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(9, 0, 0)) + ret = convert_tiling_flags_to_modifier_gfx9(rfb); else - ret = convert_tiling_flags_to_modifier(rfb); + ret = convert_tiling_flags_to_modifier_gfx6(rfb); if (ret) { drm_dbg_kms(dev, "Failed to convert tiling flags 0x%llX to a modifier", diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index ad631ad31899..0ab380ca7e64 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -139,7 +139,6 @@ enum AMDGPU_DEBUG_MASK { AMDGPU_DEBUG_LARGEBAR = BIT(1), AMDGPU_DEBUG_DISABLE_GPU_SOFT_RECOVERY = BIT(2), AMDGPU_DEBUG_USE_VRAM_FW_BUF = BIT(3), - AMDGPU_DEBUG_ENABLE_RAS_ACA = BIT(4), AMDGPU_DEBUG_ENABLE_EXP_RESETS = BIT(5), AMDGPU_DEBUG_DISABLE_GPU_RING_RESET = BIT(6), AMDGPU_DEBUG_SMU_POOL = BIT(7), @@ -1839,357 +1838,357 @@ static const u16 amdgpu_unsupported_pciidlist[] = { }; static const struct pci_device_id pciidlist[] = { - {0x1002, 0x6780, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6784, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6788, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x678A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6790, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6791, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6792, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6798, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6799, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x679A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x679B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x679E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x679F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6800, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|AMD_IS_MOBILITY}, - {0x1002, 0x6801, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|AMD_IS_MOBILITY}, - {0x1002, 0x6802, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|AMD_IS_MOBILITY}, - {0x1002, 0x6806, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6808, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6809, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6810, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6811, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6816, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6817, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6818, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6819, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6600, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6601, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6602, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6603, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6604, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6605, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6606, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6607, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6608, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6610, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6611, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6613, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6617, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6620, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6621, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6623, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6631, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6820, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6821, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6822, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6823, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6824, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6825, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6826, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6827, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6828, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6829, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x682A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x682B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x682C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x682D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x682F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6830, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6831, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6837, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6838, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6839, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x683B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x683D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x683F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6660, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x6663, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x6664, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x6665, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x6667, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x666F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, + { PCI_DEVICE(0x1002, 0x6780), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6784), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6788), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x678A), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6790), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6791), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6792), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6798), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6799), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x679A), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x679B), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x679E), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x679F), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6800), .driver_data = CHIP_PITCAIRN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6801), .driver_data = CHIP_PITCAIRN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6802), .driver_data = CHIP_PITCAIRN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6806), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6808), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6809), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6810), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6811), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6816), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6817), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6818), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6819), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6600), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6601), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6602), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6603), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6604), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6605), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6606), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6607), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6608), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6610), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6611), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6613), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6617), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6620), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6621), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6623), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6631), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6820), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6821), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6822), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6823), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6824), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6825), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6826), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6827), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6828), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6829), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x682A), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x682B), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x682C), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x682D), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x682F), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6830), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6831), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6835), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6837), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6838), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6839), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x683B), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x683D), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x683F), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6660), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6663), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6664), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6665), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6667), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x666F), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, /* Kaveri */ - {0x1002, 0x1304, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x1305, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1306, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x1307, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1309, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1310, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1311, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1312, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1313, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1315, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1316, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1317, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x1318, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x131B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x131C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x131D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x1304), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1305), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1306), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1307), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1309), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130A), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130B), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130C), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130D), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130E), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130F), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1310), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1311), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1312), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1313), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1315), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1316), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1317), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1318), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x131B), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x131C), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x131D), .driver_data = CHIP_KAVERI|AMD_IS_APU }, /* Bonaire */ - {0x1002, 0x6640, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|AMD_IS_MOBILITY}, - {0x1002, 0x6641, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|AMD_IS_MOBILITY}, - {0x1002, 0x6646, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|AMD_IS_MOBILITY}, - {0x1002, 0x6647, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|AMD_IS_MOBILITY}, - {0x1002, 0x6649, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x664D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x6650, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x6651, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x6658, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x665c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x665d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x665f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, + { PCI_DEVICE(0x1002, 0x6640), .driver_data = CHIP_BONAIRE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6641), .driver_data = CHIP_BONAIRE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6646), .driver_data = CHIP_BONAIRE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6647), .driver_data = CHIP_BONAIRE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6649), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x664D), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x6650), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x6651), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x6658), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x665c), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x665d), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x665f), .driver_data = CHIP_BONAIRE }, /* Hawaii */ - {0x1002, 0x67A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67A8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67A9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67AA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67B0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67B1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67B8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67B9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67BA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67BE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, + { PCI_DEVICE(0x1002, 0x67A0), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67A1), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67A2), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67A8), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67A9), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67AA), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67B0), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67B1), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67B8), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67B9), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67BA), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67BE), .driver_data = CHIP_HAWAII }, /* Kabini */ - {0x1002, 0x9830, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9831, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x9832, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9833, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x9834, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x9836, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9837, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x9838, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9839, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x983a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x983b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x983c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x983d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x983e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x983f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x9830), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9831), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9832), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9833), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9834), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9835), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9836), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9837), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9838), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9839), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983a), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983b), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983c), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983d), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983e), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983f), .driver_data = CHIP_KABINI|AMD_IS_APU }, /* mullins */ - {0x1002, 0x9850, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9851, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9852, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9853, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9854, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9855, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9856, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9857, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9858, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9859, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x9850), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9851), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9852), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9853), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9854), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9855), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9856), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9857), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9858), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9859), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985A), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985B), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985C), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985D), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985E), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985F), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, /* topaz */ - {0x1002, 0x6900, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, - {0x1002, 0x6901, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, - {0x1002, 0x6902, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, - {0x1002, 0x6903, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, - {0x1002, 0x6907, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, + { PCI_DEVICE(0x1002, 0x6900), .driver_data = CHIP_TOPAZ }, + { PCI_DEVICE(0x1002, 0x6901), .driver_data = CHIP_TOPAZ }, + { PCI_DEVICE(0x1002, 0x6902), .driver_data = CHIP_TOPAZ }, + { PCI_DEVICE(0x1002, 0x6903), .driver_data = CHIP_TOPAZ }, + { PCI_DEVICE(0x1002, 0x6907), .driver_data = CHIP_TOPAZ }, /* tonga */ - {0x1002, 0x6920, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6921, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6928, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6929, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x692B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x692F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6930, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6938, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6939, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x693B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, + { PCI_DEVICE(0x1002, 0x6920), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6921), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6928), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6929), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x692B), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x692F), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6930), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6938), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6939), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x693B), .driver_data = CHIP_TONGA }, /* fiji */ - {0x1002, 0x7300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_FIJI}, - {0x1002, 0x730F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_FIJI}, + { PCI_DEVICE(0x1002, 0x7300), .driver_data = CHIP_FIJI }, + { PCI_DEVICE(0x1002, 0x730F), .driver_data = CHIP_FIJI }, /* carrizo */ - {0x1002, 0x9870, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, - {0x1002, 0x9874, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, - {0x1002, 0x9875, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, - {0x1002, 0x9876, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, - {0x1002, 0x9877, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x9870), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9874), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9875), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9876), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9877), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, /* stoney */ - {0x1002, 0x98E4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_STONEY|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x98E4), .driver_data = CHIP_STONEY|AMD_IS_APU }, /* Polaris11 */ - {0x1002, 0x67E0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67EB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67EF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67FF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, + { PCI_DEVICE(0x1002, 0x67E0), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E3), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E8), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67EB), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67EF), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67FF), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E1), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E7), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E9), .driver_data = CHIP_POLARIS11 }, /* Polaris10 */ - {0x1002, 0x67C0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67D0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67D4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67DF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67CA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67CC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67CF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x6FDF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + { PCI_DEVICE(0x1002, 0x67C0), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C1), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C2), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C4), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C7), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67D0), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67D4), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67DF), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C8), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C9), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67CA), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67CC), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67CF), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x6FDF), .driver_data = CHIP_POLARIS10 }, /* Polaris12 */ - {0x1002, 0x6980, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6981, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6985, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6986, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6987, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x698F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6995, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6997, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x699F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, + { PCI_DEVICE(0x1002, 0x6980), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6981), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6985), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6986), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6987), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x698F), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6995), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6997), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x699F), .driver_data = CHIP_POLARIS12 }, /* VEGAM */ - {0x1002, 0x694C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGAM}, - {0x1002, 0x694E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGAM}, - {0x1002, 0x694F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGAM}, + { PCI_DEVICE(0x1002, 0x694C), .driver_data = CHIP_VEGAM }, + { PCI_DEVICE(0x1002, 0x694E), .driver_data = CHIP_VEGAM }, + { PCI_DEVICE(0x1002, 0x694F), .driver_data = CHIP_VEGAM }, /* Vega 10 */ - {0x1002, 0x6860, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6861, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6862, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6863, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6864, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6867, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6868, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6869, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x687f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, + { PCI_DEVICE(0x1002, 0x6860), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6861), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6862), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6863), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6864), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6867), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6868), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6869), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686a), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686b), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686c), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686d), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686e), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686f), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x687f), .driver_data = CHIP_VEGA10 }, /* Vega 12 */ - {0x1002, 0x69A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, - {0x1002, 0x69A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, - {0x1002, 0x69A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, - {0x1002, 0x69A3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, - {0x1002, 0x69AF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, + { PCI_DEVICE(0x1002, 0x69A0), .driver_data = CHIP_VEGA12 }, + { PCI_DEVICE(0x1002, 0x69A1), .driver_data = CHIP_VEGA12 }, + { PCI_DEVICE(0x1002, 0x69A2), .driver_data = CHIP_VEGA12 }, + { PCI_DEVICE(0x1002, 0x69A3), .driver_data = CHIP_VEGA12 }, + { PCI_DEVICE(0x1002, 0x69AF), .driver_data = CHIP_VEGA12 }, /* Vega 20 */ - {0x1002, 0x66A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66AF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, + { PCI_DEVICE(0x1002, 0x66A0), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A1), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A2), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A3), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A4), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A7), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66AF), .driver_data = CHIP_VEGA20 }, /* Raven */ - {0x1002, 0x15dd, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RAVEN|AMD_IS_APU}, - {0x1002, 0x15d8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RAVEN|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x15dd), .driver_data = CHIP_RAVEN|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x15d8), .driver_data = CHIP_RAVEN|AMD_IS_APU }, /* Arcturus */ - {0x1002, 0x738C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARCTURUS}, - {0x1002, 0x7388, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARCTURUS}, - {0x1002, 0x738E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARCTURUS}, - {0x1002, 0x7390, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARCTURUS}, + { PCI_DEVICE(0x1002, 0x738C), .driver_data = CHIP_ARCTURUS }, + { PCI_DEVICE(0x1002, 0x7388), .driver_data = CHIP_ARCTURUS }, + { PCI_DEVICE(0x1002, 0x738E), .driver_data = CHIP_ARCTURUS }, + { PCI_DEVICE(0x1002, 0x7390), .driver_data = CHIP_ARCTURUS }, /* Navi10 */ - {0x1002, 0x7310, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x7312, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x7318, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x7319, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x731A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x731B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x731E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x731F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, + { PCI_DEVICE(0x1002, 0x7310), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x7312), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x7318), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x7319), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x731A), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x731B), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x731E), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x731F), .driver_data = CHIP_NAVI10 }, /* Navi14 */ - {0x1002, 0x7340, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI14}, - {0x1002, 0x7341, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI14}, - {0x1002, 0x7347, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI14}, - {0x1002, 0x734F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI14}, + { PCI_DEVICE(0x1002, 0x7340), .driver_data = CHIP_NAVI14 }, + { PCI_DEVICE(0x1002, 0x7341), .driver_data = CHIP_NAVI14 }, + { PCI_DEVICE(0x1002, 0x7347), .driver_data = CHIP_NAVI14 }, + { PCI_DEVICE(0x1002, 0x734F), .driver_data = CHIP_NAVI14 }, /* Renoir */ - {0x1002, 0x15E7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RENOIR|AMD_IS_APU}, - {0x1002, 0x1636, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RENOIR|AMD_IS_APU}, - {0x1002, 0x1638, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RENOIR|AMD_IS_APU}, - {0x1002, 0x164C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RENOIR|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x15E7), .driver_data = CHIP_RENOIR|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1636), .driver_data = CHIP_RENOIR|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1638), .driver_data = CHIP_RENOIR|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x164C), .driver_data = CHIP_RENOIR|AMD_IS_APU }, /* Navi12 */ - {0x1002, 0x7360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI12}, - {0x1002, 0x7362, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI12}, + { PCI_DEVICE(0x1002, 0x7360), .driver_data = CHIP_NAVI12 }, + { PCI_DEVICE(0x1002, 0x7362), .driver_data = CHIP_NAVI12 }, /* Sienna_Cichlid */ - {0x1002, 0x73A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AD, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73BF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, + { PCI_DEVICE(0x1002, 0x73A0), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A1), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A2), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A3), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A5), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A8), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A9), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AB), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AC), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AD), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AE), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AF), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73BF), .driver_data = CHIP_SIENNA_CICHLID }, /* Yellow Carp */ - {0x1002, 0x164D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_YELLOW_CARP|AMD_IS_APU}, - {0x1002, 0x1681, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_YELLOW_CARP|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x164D), .driver_data = CHIP_YELLOW_CARP|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1681), .driver_data = CHIP_YELLOW_CARP|AMD_IS_APU }, /* Navy_Flounder */ - {0x1002, 0x73C0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73C1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73C3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DD, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, + { PCI_DEVICE(0x1002, 0x73C0), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73C1), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73C3), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DA), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DB), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DC), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DD), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DE), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DF), .driver_data = CHIP_NAVY_FLOUNDER }, /* DIMGREY_CAVEFISH */ - {0x1002, 0x73E0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73EA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73EB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73EC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73ED, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73EF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73FF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, + { PCI_DEVICE(0x1002, 0x73E0), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E1), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E2), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E3), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E8), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E9), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73EA), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73EB), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73EC), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73ED), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73EF), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73FF), .driver_data = CHIP_DIMGREY_CAVEFISH }, /* Aldebaran */ - {0x1002, 0x7408, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ALDEBARAN}, - {0x1002, 0x740C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ALDEBARAN}, - {0x1002, 0x740F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ALDEBARAN}, - {0x1002, 0x7410, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ALDEBARAN}, + { PCI_DEVICE(0x1002, 0x7408), .driver_data = CHIP_ALDEBARAN }, + { PCI_DEVICE(0x1002, 0x740C), .driver_data = CHIP_ALDEBARAN }, + { PCI_DEVICE(0x1002, 0x740F), .driver_data = CHIP_ALDEBARAN }, + { PCI_DEVICE(0x1002, 0x7410), .driver_data = CHIP_ALDEBARAN }, /* CYAN_SKILLFISH */ - {0x1002, 0x13DB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13F9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13FA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13FB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13FC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13FE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x143F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x13DB), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13F9), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13FA), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13FB), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13FC), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13FE), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x143F), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, /* BEIGE_GOBY */ - {0x1002, 0x7420, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x7421, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x7422, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x7423, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x7424, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x743F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, + { PCI_DEVICE(0x1002, 0x7420), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x7421), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x7422), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x7423), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x7424), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x743F), .driver_data = CHIP_BEIGE_GOBY }, { PCI_DEVICE(0x1002, PCI_ANY_ID), .class = PCI_CLASS_DISPLAY_VGA << 8, @@ -2206,7 +2205,7 @@ static const struct pci_device_id pciidlist[] = { .class_mask = 0xffffff, .driver_data = CHIP_IP_DISCOVERY }, - {0, 0, 0} + { } }; MODULE_DEVICE_TABLE(pci, pciidlist); @@ -2264,11 +2263,6 @@ static void amdgpu_init_debug_options(struct amdgpu_device *adev) adev->debug_use_vram_fw_buf = true; } - if (amdgpu_debug_mask & AMDGPU_DEBUG_ENABLE_RAS_ACA) { - pr_info("debug: enable RAS ACA\n"); - adev->debug_enable_ras_aca = true; - } - if (amdgpu_debug_mask & AMDGPU_DEBUG_ENABLE_EXP_RESETS) { pr_info("debug: enable experimental reset features\n"); adev->debug_exp_resets = true; @@ -2926,6 +2920,19 @@ static int amdgpu_pmops_runtime_suspend(struct device *dev) return 0; } +static void amdgpu_restore_umd_profile_pstate_after_runpm(struct amdgpu_device *adev) +{ + enum amd_dpm_forced_level level; + uint32_t profile_mode_mask = AMD_DPM_FORCED_LEVEL_PROFILE_STANDARD | + AMD_DPM_FORCED_LEVEL_PROFILE_MIN_SCLK | + AMD_DPM_FORCED_LEVEL_PROFILE_MIN_MCLK | + AMD_DPM_FORCED_LEVEL_PROFILE_PEAK; + + level = amdgpu_dpm_get_performance_level(adev); + if (level & profile_mode_mask) + amdgpu_asic_update_umd_stable_pstate(adev, true); +} + static int amdgpu_pmops_runtime_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); @@ -2970,6 +2977,8 @@ static int amdgpu_pmops_runtime_resume(struct device *dev) if (adev->pm.rpm_mode == AMDGPU_RUNPM_PX) drm_dev->switch_power_state = DRM_SWITCH_POWER_ON; + + amdgpu_restore_umd_profile_pstate_after_runpm(adev); adev->in_runpm = false; return 0; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index 1e275c2e7dd3..4d21d83451a4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -34,6 +34,7 @@ #include "amdgpu_xcp.h" #include "amdgpu_xgmi.h" #include "amdgpu_mes.h" +#include "amdgpu_userq.h" #include "mes_userqueue.h" #include "nvd.h" @@ -419,8 +420,8 @@ int amdgpu_gfx_mqd_sw_init(struct amdgpu_device *adev, domain |= AMDGPU_GEM_DOMAIN_VRAM; #endif - /* create MQD for KIQ */ - if (!adev->enable_mes_kiq && !ring->mqd_obj) { + /* create MQD for KIQ (on GFX8+ where we use KIQ) */ + if (adev->asic_type >= CHIP_TOPAZ && !adev->enable_mes_kiq && !ring->mqd_obj) { /* originaly the KIQ MQD is put in GTT domain, but for SRIOV VRAM domain is a must * otherwise hypervisor trigger SAVE_VF fail after driver unloaded which mean MQD * deallocated and gart_unbind, to strict diverage we decide to use VRAM domain for @@ -855,6 +856,59 @@ int amdgpu_gfx_enable_kgq(struct amdgpu_device *adev, int xcc_id) return r; } +/** + * amdgpu_gfx_handle_priv_fault - Handle privileged instruction fault + * + * @adev: amdgpu_device pointer + * @entry: interrupt vector entry containing fault information + * @me_id: micro-engine ID of the faulty ring + * @pipe_id: pipe ID of the faulty ring + * @queue_id: queue ID of the faulty ring + * + * This function handles privileged instruction faults by identifying + * the faulty ring (gfx or compute) and triggering a scheduler fault + */ +void amdgpu_gfx_handle_priv_fault(struct amdgpu_device *adev, + struct amdgpu_iv_entry *entry, + u8 me_id, u8 pipe_id, u8 queue_id) +{ + struct amdgpu_ring *ring; + u32 doorbell_offset; + int i; + + /* + * Try KQ first by ring_id (HW slot is authoritative). The + * KMD compute_hqd_mask contract guarantees KCQ and user queues + * never share a HW slot. + */ + if (!adev->gfx.disable_kq) { + for (i = 0; i < adev->gfx.num_gfx_rings; i++) { + ring = &adev->gfx.gfx_ring[i]; + if (ring->me == me_id && ring->pipe == pipe_id && + ring->queue == queue_id) { + drm_sched_fault(&ring->sched); + return; + } + } + + for (i = 0; i < adev->gfx.num_compute_rings; i++) { + ring = &adev->gfx.compute_ring[i]; + if (ring->me == me_id && ring->pipe == pipe_id && + ring->queue == queue_id) { + drm_sched_fault(&ring->sched); + return; + } + } + } + + doorbell_offset = entry->src_data[0] & AMDGPU_CTXID0_DOORBELL_ID_MASK; + + /* No KQ matched: HW slot is a MES-scheduled user queue. */ + if (adev->enable_mes && doorbell_offset) + amdgpu_userq_process_reset_irq(adev, entry->pasid, + doorbell_offset); +} + static void amdgpu_gfx_do_off_ctrl(struct amdgpu_device *adev, bool enable, bool no_delay) { @@ -1713,8 +1767,9 @@ static int amdgpu_gfx_run_cleaner_shader_job(struct amdgpu_ring *ring) owner = (void *)(unsigned long)atomic_inc_return(&counter); r = amdgpu_job_alloc_with_ib(ring->adev, &entity, owner, - ib_size_dw * sizeof(uint32_t), 0, &job, - AMDGPU_KERNEL_JOB_ID_CLEANER_SHADER); + ib_size_dw * sizeof(uint32_t), 0, + AMDGPU_KERNEL_JOB_ID_CLEANER_SHADER, + &job); if (r) goto err; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h index aefd4f03b443..c15f45a7347c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -620,6 +620,9 @@ bool amdgpu_gfx_is_high_priority_graphics_queue(struct amdgpu_device *adev, struct amdgpu_ring *ring); bool amdgpu_gfx_is_me_queue_enabled(struct amdgpu_device *adev, int me, int pipe, int queue); +void amdgpu_gfx_handle_priv_fault(struct amdgpu_device *adev, + struct amdgpu_iv_entry *entry, + u8 me_id, u8 pipe_id, u8 queue_id); void amdgpu_gfx_off_ctrl(struct amdgpu_device *adev, bool enable); void amdgpu_gfx_off_ctrl_immediate(struct amdgpu_device *adev, bool enable); int amdgpu_get_gfx_off_status(struct amdgpu_device *adev, uint32_t *value); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index 4000b2c6fc98..3da50132c5d0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -761,7 +761,8 @@ void amdgpu_gmc_flush_gpu_tlb(struct amdgpu_device *adev, uint32_t vmid, r = amdgpu_job_alloc_with_ib(ring->adev, &adev->mman.default_entity.base, AMDGPU_FENCE_OWNER_UNDEFINED, 16 * 4, AMDGPU_IB_POOL_IMMEDIATE, - &job, AMDGPU_KERNEL_JOB_ID_FLUSH_GPU_TLB); + AMDGPU_KERNEL_JOB_ID_FLUSH_GPU_TLB, + &job); if (r) goto error_alloc; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c index 63f62c670df5..634b3f7a5fff 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c @@ -351,6 +351,30 @@ free_fence: */ int amdgpu_ib_pool_init(struct amdgpu_device *adev) { + const int sizes[AMDGPU_IB_POOL_MAX] = { + [AMDGPU_IB_POOL_DELAYED] = SZ_1M, + [AMDGPU_IB_POOL_IMMEDIATE] = SZ_128K, + [AMDGPU_IB_POOL_DIRECT] = SZ_512K + }; + const gfp_t gfp_flags[AMDGPU_IB_POOL_MAX] = { + /* + * For normal page table updates and recoverable retry faults + * (for SVM), further restricted by the VM eviction lock to not + * wait for memory reclaim. + */ + [AMDGPU_IB_POOL_DELAYED] = GFP_KERNEL, + /* + * For redirecting unrecoverable retry faults to the dummy page + * or set the PRT bits. dma_fence submissions might depend on + * that so we need the emmergency reserves. + */ + [AMDGPU_IB_POOL_IMMEDIATE] = GFP_ATOMIC, + /* + * For IB tests during GPU resets. Only very small and temporary + * allocation to allow dma_fences to signal. + */ + [AMDGPU_IB_POOL_DIRECT] = GFP_ATOMIC + }; int r, i; if (adev->ib_pool_ready) @@ -358,8 +382,7 @@ int amdgpu_ib_pool_init(struct amdgpu_device *adev) for (i = 0; i < AMDGPU_IB_POOL_MAX; i++) { r = amdgpu_sa_bo_manager_init(adev, &adev->ib_pools[i], - AMDGPU_IB_POOL_SIZE, 256, - AMDGPU_GEM_DOMAIN_GTT); + sizes[i], gfp_flags[i]); if (r) goto error; } @@ -393,6 +416,17 @@ void amdgpu_ib_pool_fini(struct amdgpu_device *adev) adev->ib_pool_ready = false; } +/** + * amdgpu_ib_pool_gfp_flags - Returns the gfp flags to use for each pool + * @adev: amdgpu device pointer + * @type: the IB pool type + */ +gfp_t amdgpu_ib_pool_gfp_flags(struct amdgpu_device *adev, + enum amdgpu_ib_pool_type type) +{ + return adev->ib_pools[type].gfp_flags; +} + /** * amdgpu_ib_ring_tests - test IBs on the rings * diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c index 124fb38eb465..8a2d64f0ebc3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c @@ -51,16 +51,16 @@ struct amdgpu_pasid_cb { /** * amdgpu_pasid_alloc - Allocate a PASID - * @bits: Maximum width of the PASID in bits, must be at least 1 + * @bits: Number of PASID bits supported by the hardware + * @fpriv: DRM file private to associate with the PASID * - * Uses kernel's IDR cyclic allocator (same as PID allocation). + * Uses the kernel's XArray cyclic allocator. * Allocates sequentially with automatic wrap-around. * - * Returns a positive integer on success. Returns %-EINVAL if bits==0. - * Returns %-ENOSPC if no PASID was available. Returns %-ENOMEM on - * memory allocation failure. + * Returns: + * Allocated PASID on success or a negative error code on failure. */ -int amdgpu_pasid_alloc(unsigned int bits) +int amdgpu_pasid_alloc(unsigned int bits, struct amdgpu_fpriv *fpriv) { u32 pasid; int r; @@ -68,9 +68,9 @@ int amdgpu_pasid_alloc(unsigned int bits) if (bits == 0) return -EINVAL; - r = xa_alloc_cyclic_irq(&amdgpu_pasid_xa, &pasid, xa_mk_value(0), - XA_LIMIT(1, (1U << bits) - 1), - &amdgpu_pasid_xa_next, GFP_KERNEL); + r = xa_alloc_cyclic_irq(&amdgpu_pasid_xa, &pasid, fpriv, + XA_LIMIT(1, (1U << bits) - 1), + &amdgpu_pasid_xa_next, GFP_KERNEL); if (r < 0) return r; @@ -106,6 +106,71 @@ static void amdgpu_pasid_free_cb(struct dma_fence *fence, kfree(cb); } +/** + * amdgpu_pasid_clear_owner - Clear the owner associated with a PASID + * @pasid: PASID whose owner should be cleared + * + * Replace the stored owner with NULL while keeping the PASID allocated. + * + * This is used by the delayed PASID free path so that future PASID + * lookups cannot resolve a stale DRM file-private object while the PASID + * is still waiting for outstanding fences before being released. + */ +static void amdgpu_pasid_clear_owner(u32 pasid) +{ + unsigned long flags; + + if (!pasid) + return; + + xa_lock_irqsave(&amdgpu_pasid_xa, flags); + __xa_store(&amdgpu_pasid_xa, pasid, NULL, GFP_ATOMIC); + xa_unlock_irqrestore(&amdgpu_pasid_xa, flags); +} + +/** + * amdgpu_pasid_lock - acquire the global PASID xarray lock + * @flags: storage for interrupt state + * + * Acquire the global PASID xarray lock with interrupts disabled. + * The saved interrupt state must be passed to + * amdgpu_pasid_unlock(). + */ +void amdgpu_pasid_lock(unsigned long *flags) +{ + xa_lock_irqsave(&amdgpu_pasid_xa, *flags); +} + +/** + * amdgpu_pasid_unlock - release the global PASID xarray lock + * @flags: interrupt state returned by amdgpu_pasid_lock() + * + * Release the global PASID xarray lock and restore the previous + * interrupt state. + */ +void amdgpu_pasid_unlock(unsigned long flags) +{ + xa_unlock_irqrestore(&amdgpu_pasid_xa, flags); +} + +/** + * amdgpu_pasid_get_fpriv_locked - get the DRM owner of a PASID + * @pasid: PASID to resolve + * + * The caller must hold the PASID XArray lock. + * + * Returns: + * Pointer to the owning DRM file private, or %NULL if the PASID has no + * current owner. + * + * The returned pointer is only valid while the PASID lock remains held + * and must not be retained after calling amdgpu_pasid_unlock(). + */ +struct amdgpu_fpriv *amdgpu_pasid_get_fpriv_locked(u32 pasid) +{ + return xa_load(&amdgpu_pasid_xa, pasid); +} + /** * amdgpu_pasid_free_delayed - free pasid when fences signal * @@ -121,6 +186,8 @@ void amdgpu_pasid_free_delayed(struct dma_resv *resv, struct dma_fence *fence; int r; + amdgpu_pasid_clear_owner(pasid); + r = dma_resv_get_singleton(resv, DMA_RESV_USAGE_BOOKKEEP, &fence); if (r) goto fallback; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h index a57919478d3b..4b55d0d9703a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h @@ -34,6 +34,7 @@ #define AMDGPU_NUM_VMID 16 struct amdgpu_device; +struct amdgpu_fpriv; struct amdgpu_vm; struct amdgpu_ring; struct amdgpu_sync; @@ -70,7 +71,10 @@ struct amdgpu_vmid_mgr { bool reserved_vmid; }; -int amdgpu_pasid_alloc(unsigned int bits); +int amdgpu_pasid_alloc(unsigned int bits, struct amdgpu_fpriv *fpriv); +void amdgpu_pasid_lock(unsigned long *flags); +void amdgpu_pasid_unlock(unsigned long flags); +struct amdgpu_fpriv *amdgpu_pasid_get_fpriv_locked(u32 pasid); void amdgpu_pasid_free(u32 pasid); void amdgpu_pasid_free_delayed(struct dma_resv *resv, u32 pasid); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c index 33a04113ed74..922f4b15619d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c @@ -489,7 +489,13 @@ int amdgpu_device_ip_soft_reset(struct amdgpu_ring *guilty_ring, ip_type = amdgpu_ip_from_ring(guilty_ring->funcs->type); ip_block = amdgpu_device_ip_get_ip_block(adev, ip_type); - if (!ip_block || !ip_block->version->funcs->soft_reset) { + if (unlikely(!ip_block)) { + dev_warn(adev->dev, "IP block not found for ring %s\n", + guilty_ring->name); + return -EOPNOTSUPP; + } + + if (!ip_block->version->funcs->soft_reset) { dev_warn(adev->dev, "IP block soft reset not supported on %s\n", ip_block->version->funcs->name); return -EOPNOTSUPP; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c index 53be764968e4..95cceed4e971 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c @@ -545,7 +545,7 @@ void amdgpu_irq_delegate(struct amdgpu_device *adev, unsigned int num_dw) { amdgpu_ih_ring_write(adev, &adev->irq.ih_soft, entry->iv_entry, num_dw); - queue_work(system_unbound_wq, &adev->irq.ih_soft_work); + queue_work(system_dfl_wq, &adev->irq.ih_soft_work); } /** diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c index cff73f1b5a72..d97cf1212e0f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c @@ -198,8 +198,8 @@ exit: int amdgpu_job_alloc(struct amdgpu_device *adev, struct amdgpu_vm *vm, struct drm_sched_entity *entity, void *owner, - unsigned int num_ibs, struct amdgpu_job **job, - u64 drm_client_id) + unsigned int num_ibs, u64 drm_client_id, + gfp_t gfp_flags, struct amdgpu_job **job) { struct amdgpu_fence *af; int r; @@ -207,18 +207,18 @@ int amdgpu_job_alloc(struct amdgpu_device *adev, struct amdgpu_vm *vm, if (num_ibs == 0) return -EINVAL; - *job = kzalloc_flex(**job, ibs, num_ibs); + *job = kzalloc_flex(**job, ibs, num_ibs, gfp_flags); if (!*job) return -ENOMEM; - af = kzalloc_obj(struct amdgpu_fence); + af = kzalloc_obj(struct amdgpu_fence, gfp_flags); if (!af) { r = -ENOMEM; goto err_job; } (*job)->hw_fence = af; - af = kzalloc_obj(struct amdgpu_fence); + af = kzalloc_obj(struct amdgpu_fence, gfp_flags); if (!af) { r = -ENOMEM; goto err_fence; @@ -252,12 +252,13 @@ err_job: int amdgpu_job_alloc_with_ib(struct amdgpu_device *adev, struct drm_sched_entity *entity, void *owner, size_t size, enum amdgpu_ib_pool_type pool_type, - struct amdgpu_job **job, u64 k_job_id) + u64 k_job_id, struct amdgpu_job **job) { int r; - r = amdgpu_job_alloc(adev, NULL, entity, owner, 1, job, - k_job_id); + r = amdgpu_job_alloc(adev, NULL, entity, owner, 1, k_job_id, + amdgpu_ib_pool_gfp_flags(adev, pool_type), + job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h index e70a1117b812..7e03bd2ff639 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h @@ -115,13 +115,13 @@ static inline struct amdgpu_ring *amdgpu_job_ring(struct amdgpu_job *job) int amdgpu_job_alloc(struct amdgpu_device *adev, struct amdgpu_vm *vm, struct drm_sched_entity *entity, void *owner, - unsigned int num_ibs, struct amdgpu_job **job, - u64 drm_client_id); + unsigned int num_ibs, u64 drm_client_id, + gfp_t gfp_flags, struct amdgpu_job **job); int amdgpu_job_alloc_with_ib(struct amdgpu_device *adev, struct drm_sched_entity *entity, void *owner, size_t size, enum amdgpu_ib_pool_type pool_type, - struct amdgpu_job **job, - u64 k_job_id); + u64 k_job_id, + struct amdgpu_job **job); void amdgpu_job_set_resources(struct amdgpu_job *job, struct amdgpu_bo *gds, struct amdgpu_bo *gws, struct amdgpu_bo *oa); void amdgpu_job_free_resources(struct amdgpu_job *job); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c index 57935c321515..363b99168478 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c @@ -197,8 +197,9 @@ static int amdgpu_jpeg_dec_set_reg(struct amdgpu_ring *ring, uint32_t handle, int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 287ede40cd91..242c48e85912 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -1211,7 +1211,7 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) return -EINVAL; } } - ui32 >>= 8; + ui32 /= MILLIWATT_PER_WATT; break; case AMDGPU_INFO_SENSOR_GPU_INPUT_POWER: /* get input GPU power */ @@ -1220,7 +1220,7 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) (void *)&ui32, &ui32_size)) { return -EINVAL; } - ui32 >>= 8; + ui32 /= MILLIWATT_PER_WATT; break; case AMDGPU_INFO_SENSOR_VDDNB: /* get VDDNB in millivolts */ @@ -1376,11 +1376,11 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) memset(&gpuvm_fault, 0, sizeof(gpuvm_fault)); - xa_lock_irqsave(&adev->vm_manager.pasids, flags); + amdgpu_pasid_lock(&flags); gpuvm_fault.addr = vm->fault_info.addr; gpuvm_fault.status = vm->fault_info.status; gpuvm_fault.vmhub = vm->fault_info.vmhub; - xa_unlock_irqrestore(&adev->vm_manager.pasids, flags); + amdgpu_pasid_unlock(flags); return copy_to_user(out, &gpuvm_fault, min((size_t)size, sizeof(gpuvm_fault))) ? -EFAULT : 0; @@ -1465,7 +1465,7 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) struct amdgpu_device *adev = drm_to_adev(dev); struct amdgpu_fpriv *fpriv; struct drm_exec exec; - int r, pasid; + int r, pasid = 0; /* Ensure IB tests are run on ring */ flush_delayed_work(&adev->delayed_init_work); @@ -1488,22 +1488,23 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) goto out_suspend; } - pasid = amdgpu_pasid_alloc(16); - if (pasid < 0) { - dev_warn(adev->dev, "No more PASIDs available!"); - pasid = 0; - } - r = amdgpu_xcp_open_device(adev, fpriv, file_priv); if (r) goto error_pasid; amdgpu_debugfs_vm_init(file_priv); - r = amdgpu_vm_init(adev, &fpriv->vm, fpriv->xcp_id, pasid); + r = amdgpu_vm_init(adev, &fpriv->vm, fpriv->xcp_id); if (r) goto error_pasid; + pasid = amdgpu_pasid_alloc(16, fpriv); + if (pasid < 0) { + dev_warn(adev->dev, "No more PASIDs available!"); + pasid = 0; + } + fpriv->vm.pasid = pasid; + drm_exec_init(&exec, DRM_EXEC_IGNORE_DUPLICATES, 0); drm_exec_until_all_locked(&exec) { r = amdgpu_vm_lock_pd(&fpriv->vm, &exec, 0); @@ -1547,12 +1548,14 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) goto out_suspend; error_vm: + if (pasid) { + amdgpu_pasid_free(pasid); + pasid = 0; + } + amdgpu_vm_fini(adev, &fpriv->vm); error_pasid: - if (pasid) - amdgpu_pasid_free(pasid); - kfree(fpriv); out_suspend: @@ -1608,10 +1611,11 @@ void amdgpu_driver_postclose_kms(struct drm_device *dev, } amdgpu_ctx_mgr_fini(&fpriv->ctx_mgr); - amdgpu_vm_fini(adev, &fpriv->vm); if (pasid) amdgpu_pasid_free_delayed(pd->tbo.base.resv, pasid); + + amdgpu_vm_fini(adev, &fpriv->vm); amdgpu_bo_unref(&pd); xa_for_each(&fpriv->bo_list_handles, handle, list) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index c66e69b62361..9af7c97af5be 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -921,6 +921,221 @@ int amdgpu_mes_update_enforce_isolation(struct amdgpu_device *adev) return r; } +/** + * amdgpu_mes_rs64mem_init - initialize RS64 local memory context arrays + * + * @mes: MES instance + * + * Returns 0 on success, negative errno on failure. + */ +int amdgpu_mes_rs64mem_init(struct amdgpu_mes *mes) +{ + struct amdgpu_device *adev = container_of(mes, struct amdgpu_device, mes); + int r; + + if (!mes->use_rs64mem) + return 0; + + r = amdgpu_bo_create_kernel(adev, PAGE_SIZE, PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT, + &mes->ctx_array_size_bo, + &mes->ctx_array_size_gpu_addr, + (void **)&mes->ctx_array_size_cpu_ptr); + if (r) { + dev_err(adev->dev, + "Failed to allocate ctx array size BO, r=%d\n", r); + return r; + } + + memset(mes->ctx_array_size_cpu_ptr, 0, PAGE_SIZE); + + return 0; +} + + /** + * amdgpu_mes_rs64mem_fini - tear down RS64 local memory management + * + * @mes: MES instance + */ +void amdgpu_mes_rs64mem_fini(struct amdgpu_mes *mes) +{ + if (mes->ctx_array_size_bo) { + amdgpu_bo_free_kernel(&mes->ctx_array_size_bo, + &mes->ctx_array_size_gpu_addr, + (void **)&mes->ctx_array_size_cpu_ptr); + } + bitmap_free(mes->proc_ctx_bitmap); + bitmap_free(mes->gang_ctx_bitmap); + mes->use_rs64mem = false; +} + +/** + * amdgpu_mes_rs64mem_setup_bitmaps - allocate bitmaps after querying MES + * + * Called after QUERY_SCHEDULER_STATUS returns and MES has written + * the array sizes to the GPU buffer. Reads the sizes and allocates + * the tracking bitmaps. + * + * @mes: MES instance + * + * Returns 0 on success, negative errno on failure. + */ +int amdgpu_mes_rs64mem_setup_bitmaps(struct amdgpu_mes *mes) +{ + struct amdgpu_device *adev = container_of(mes, struct amdgpu_device, mes); + + if (!mes->use_rs64mem || !mes->ctx_array_size_cpu_ptr) + return 0; + + /* + * MES FW wrote the sizes to the GPU buffer: + * ctx_array_size_cpu_ptr[0] = proc_ctx_array_size (N) + * ctx_array_size_cpu_ptr[1] = gang_ctx_array_size (M) + */ + mes->proc_ctx_array_size = mes->ctx_array_size_cpu_ptr[0]; + mes->gang_ctx_array_size = mes->ctx_array_size_cpu_ptr[1]; + + /* Sanity check - MES FW typically returns N=50, M=300 */ + if (mes->proc_ctx_array_size == 0 || mes->gang_ctx_array_size == 0) { + dev_warn(adev->dev, + "MES returned zero ctx array sizes (proc=%u, gang=%u), " + "disabling RS64 local memory optimization\n", + mes->proc_ctx_array_size, mes->gang_ctx_array_size); + mes->use_rs64mem = false; + return 0; + } + + /* Cap to safety limits */ + if (mes->proc_ctx_array_size > AMDGPU_MES_PROC_CTX_ARRAY_MAX) + mes->proc_ctx_array_size = AMDGPU_MES_PROC_CTX_ARRAY_MAX; + if (mes->gang_ctx_array_size > AMDGPU_MES_GANG_CTX_ARRAY_MAX) + mes->gang_ctx_array_size = AMDGPU_MES_GANG_CTX_ARRAY_MAX; + + dev_info(adev->dev, + "MES RS64 local memory: proc_ctx_array_size:%u, " + "gang_ctx_array_size:%u\n", + mes->proc_ctx_array_size, mes->gang_ctx_array_size); + + /* Allocate bitmaps */ + mes->proc_ctx_bitmap = bitmap_zalloc(mes->proc_ctx_array_size, + GFP_KERNEL); + if (!mes->proc_ctx_bitmap) { + mes->use_rs64mem = false; + return -ENOMEM; + } + + mes->gang_ctx_bitmap = bitmap_zalloc(mes->gang_ctx_array_size, + GFP_KERNEL); + if (!mes->gang_ctx_bitmap) { + bitmap_free(mes->proc_ctx_bitmap); + mes->proc_ctx_bitmap = NULL; + mes->use_rs64mem = false; + return -ENOMEM; + } + + return 0; +} + +/** + * amdgpu_mes_alloc_proc_ctx_index - allocate a process context slot + * + * @mes: MES instance + * @queue: Usermode queue receiving the allocated process context index + * + * Returns 0 on success, -ENOSPC if all slots are used, or + * -EOPNOTSUPP if RS64 local memory is unavailable. + */ +int amdgpu_mes_alloc_proc_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue) +{ + unsigned long bit; + + if (!mes->use_rs64mem || !mes->proc_ctx_bitmap) + return -EOPNOTSUPP; + + amdgpu_mes_lock(mes); + bit = find_first_zero_bit(mes->proc_ctx_bitmap, + mes->proc_ctx_array_size); + if (bit >= mes->proc_ctx_array_size) { + amdgpu_mes_unlock(mes); + return -ENOSPC; + } + set_bit(bit, mes->proc_ctx_bitmap); + queue->proc_ctx_array_index = (uint32_t)bit; + amdgpu_mes_unlock(mes); + + return 0; +} + + /** + * amdgpu_mes_free_proc_ctx_index - free a process context slot + * + * @mes: MES instance + * @queue: Usermode queue whose process context index is released + */ +void amdgpu_mes_free_proc_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue) +{ + if (!mes->use_rs64mem || !mes->proc_ctx_bitmap) + return; + if (queue->proc_ctx_array_index >= mes->proc_ctx_array_size) + return; + + amdgpu_mes_lock(mes); + clear_bit(queue->proc_ctx_array_index, mes->proc_ctx_bitmap); + amdgpu_mes_unlock(mes); +} + + /** + * amdgpu_mes_alloc_gang_ctx_index - allocate a gang context slot + * + * @mes: MES instance + * @queue: Usermode queue receiving the allocated gang context index + * + * Returns 0 on success, -ENOSPC if all slots are used, or + * -EOPNOTSUPP if RS64 local memory is unavailable. + */ +int amdgpu_mes_alloc_gang_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue) +{ + unsigned long bit; + + if (!mes->use_rs64mem || !mes->gang_ctx_bitmap) + return -EOPNOTSUPP; + + amdgpu_mes_lock(mes); + bit = find_first_zero_bit(mes->gang_ctx_bitmap, + mes->gang_ctx_array_size); + if (bit >= mes->gang_ctx_array_size) { + amdgpu_mes_unlock(mes); + return -ENOSPC; + } + set_bit(bit, mes->gang_ctx_bitmap); + queue->gang_ctx_array_index = bit; + amdgpu_mes_unlock(mes); + + return 0; +} + + /** + * amdgpu_mes_free_gang_ctx_index - free a gang context slot + * + * @mes: MES instance + * @queue: Usermode queue whose gang context index is released + */ +void amdgpu_mes_free_gang_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue) +{ + if (!mes->use_rs64mem || !mes->gang_ctx_bitmap) + return; + if (queue->gang_ctx_array_index >= mes->gang_ctx_array_size) + return; + + amdgpu_mes_lock(mes); + clear_bit(queue->gang_ctx_array_index, mes->gang_ctx_bitmap); + amdgpu_mes_unlock(mes); +} + #if defined(CONFIG_DEBUG_FS) static int amdgpu_debugfs_mes_event_log_show(struct seq_file *m, void *unused) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 389e3324caea..c67db2d6e122 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -54,7 +54,8 @@ enum amdgpu_mes_priority_level { #define AMDGPU_MES_PROC_CTX_SIZE 0x1000 /* one page area */ #define AMDGPU_MES_GANG_CTX_SIZE 0x1000 /* one page area */ - +#define AMDGPU_MES_PROC_CTX_ARRAY_MAX 128 +#define AMDGPU_MES_GANG_CTX_ARRAY_MAX 512 struct amdgpu_mes_funcs; enum amdgpu_mes_pipe { @@ -171,6 +172,17 @@ struct amdgpu_mes { bool compute_pipe_reset_enabled; bool gfx_pipe_reset_enabled; + + bool use_rs64mem; + struct amdgpu_bo *ctx_array_size_bo; + uint64_t ctx_array_size_gpu_addr; + uint32_t *ctx_array_size_cpu_ptr; + + uint32_t proc_ctx_array_size; + unsigned long *proc_ctx_bitmap; + uint32_t gang_ctx_array_size; + uint32_t gang_ctx_array_index; + unsigned long *gang_ctx_bitmap; }; struct amdgpu_mes_hung_queue_hqd_info { @@ -268,6 +280,8 @@ struct mes_add_queue_input { uint32_t exclusively_scheduled; uint32_t sh_mem_config_data; uint32_t vm_cntx_cntl; + uint32_t process_context_array_index; + uint32_t gang_context_array_index; }; struct mes_remove_queue_input { @@ -276,6 +290,7 @@ struct mes_remove_queue_input { uint64_t gang_context_addr; uint32_t queue_type; bool remove_queue_after_reset; + uint32_t gang_context_array_index; }; struct mes_map_legacy_queue_input { @@ -617,4 +632,15 @@ bool amdgpu_mes_queue_reset_by_mes_supported(struct amdgpu_device *adev); int amdgpu_mes_update_enforce_isolation(struct amdgpu_device *adev); +int amdgpu_mes_rs64mem_init(struct amdgpu_mes *mes); +void amdgpu_mes_rs64mem_fini(struct amdgpu_mes *mes); +int amdgpu_mes_rs64mem_setup_bitmaps(struct amdgpu_mes *mes); +int amdgpu_mes_alloc_proc_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue); +void amdgpu_mes_free_proc_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue); +int amdgpu_mes_alloc_gang_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue); +void amdgpu_mes_free_gang_ctx_index(struct amdgpu_mes *mes, + struct amdgpu_usermode_queue *queue); #endif /* __AMDGPU_MES_H__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h index 8069fc41cc7f..7c784277396a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h @@ -509,6 +509,7 @@ struct amdgpu_crtc { struct drm_pending_vblank_event *event; bool wb_pending; + bool wb_frame_done; bool wb_enabled; struct drm_writeback_connector *wb_conn; }; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c index f98bfba59a2c..5d9d137209b6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c @@ -43,6 +43,7 @@ #include "amdgpu_vram_mgr.h" #include "amdgpu_vm.h" #include "amdgpu_dma_buf.h" +#include "kfd_svm.h" /** * DOC: amdgpu_object @@ -93,7 +94,8 @@ static void amdgpu_bo_user_destroy(struct ttm_buffer_object *tbo) bool amdgpu_bo_is_amdgpu_bo(struct ttm_buffer_object *bo) { if (bo->destroy == &amdgpu_bo_destroy || - bo->destroy == &amdgpu_bo_user_destroy) + bo->destroy == &amdgpu_bo_user_destroy || + bo->destroy == &svm_range_bo_destroy) return true; return false; @@ -276,10 +278,12 @@ int amdgpu_bo_create_reserved(struct amdgpu_device *adev, goto error_free; } - r = amdgpu_bo_pin(*bo_ptr, domain); - if (r) { - dev_err(adev->dev, "(%d) kernel bo pin failed\n", r); - goto error_unreserve; + if (free) { + r = amdgpu_bo_pin(*bo_ptr, domain); + if (r) { + dev_err(adev->dev, "(%d) kernel bo pin failed\n", r); + goto error_unreserve; + } } r = amdgpu_ttm_alloc_gart(&(*bo_ptr)->tbo); @@ -302,7 +306,8 @@ int amdgpu_bo_create_reserved(struct amdgpu_device *adev, return 0; error_unpin: - amdgpu_bo_unpin(*bo_ptr); + if (free) + amdgpu_bo_unpin(*bo_ptr); error_unreserve: amdgpu_bo_unreserve(*bo_ptr); @@ -1696,8 +1701,9 @@ u64 amdgpu_bo_print_info(int id, struct amdgpu_bo *bo, struct seq_file *m) if (dma_resv_trylock(bo->tbo.base.resv)) { dma_resv_describe(bo->tbo.base.resv, m); dma_resv_unlock(bo->tbo.base.resv); + } else { + seq_puts(m, "\n"); } - seq_puts(m, "\n"); return size; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c index e0c0d7872e45..04f6ebf31cca 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c @@ -275,6 +275,7 @@ static int psp_early_init(struct amdgpu_ip_block *ip_block) psp->boot_time_tmr = false; break; case IP_VERSION(15, 0, 0): + case IP_VERSION(15, 0, 5): case IP_VERSION(15, 0, 9): psp_v15_0_0_set_psp_funcs(psp); psp->boot_time_tmr = false; @@ -1222,6 +1223,33 @@ int psp_memory_partition(struct psp_context *psp, int mode) return ret; } +int psp_set_mmhub_eco_sec_level(struct amdgpu_device *adev) +{ + int ret; + struct psp_context *psp = &adev->psp; + struct psp_gfx_cmd_resp *cmd = acquire_psp_cmd_buf(psp); + + cmd->cmd_id = GFX_CMD_ID_SET_MMHUB_ECO_SEC_LEVEL; + + ret = psp_cmd_submit_buf(psp, NULL, cmd, psp->fence_buf_mc_addr); + if (ret) { + dev_err(psp->adev->dev, + "PSP request failed to set mmuhub eco sec level with ret=%d\n", ret); + release_psp_cmd_buf(psp); + return ret; + } + + if (cmd->resp.status) { + dev_err(psp->adev->dev, + "MMHUB ECO SEC LEVEL command 0x%x failed, PSP response status: 0x%X\n", + cmd->cmd_id, cmd->resp.status); + ret = -EIO; + } + release_psp_cmd_buf(psp); + + return ret; +} + static int psp_ptl_fmt_verify(struct psp_context *psp, enum amdgpu_ptl_fmt fmt, uint32_t *ptl_fmt) { @@ -1264,8 +1292,19 @@ static int psp_ptl_invoke(struct psp_context *psp, u32 req_code, { struct psp_gfx_cmd_resp *cmd; struct amdgpu_ptl *ptl = &psp->ptl; + struct amdgpu_device *adev = psp->adev; int ret; + if (amdgpu_sriov_vf(adev)) { + ret = amdgpu_virt_ptl_request(adev, req_code, ptl_state, fmt1, fmt2); + if (!ret) { + ptl->enabled = *ptl_state; + ptl->fmt1 = *fmt1; + ptl->fmt2 = *fmt2; + } + return ret; + } + cmd = acquire_psp_cmd_buf(psp); cmd->cmd_id = GFX_CMD_ID_PERF_HW; @@ -1328,15 +1367,18 @@ int amdgpu_ptl_perf_monitor_ctrl(struct amdgpu_device *adev, u32 req_code, if (!adev || !ptl_state || !fmt1 || !fmt2) return -EINVAL; - if (amdgpu_sriov_vf(adev)) - return 0; - psp = &adev->psp; ptl = &psp->ptl; if (ptl->permanently_disabled && *ptl_state == 1) return 0; + if (amdgpu_sriov_vf(adev)) { + ptl_fmt1 = *fmt1; + ptl_fmt2 = *fmt2; + return psp_ptl_invoke(psp, req_code, ptl_state, &ptl_fmt1, &ptl_fmt2); + } + if (amdgpu_ip_version(adev, GC_HWIP, 0) != IP_VERSION(9, 4, 4) || psp->sos.fw_version < 0x0036081a) return -EOPNOTSUPP; @@ -1475,6 +1517,18 @@ static ssize_t ptl_enable_show(struct device *dev, struct device_attribute *attr struct drm_device *ddev = dev_get_drvdata(dev); struct amdgpu_device *adev = drm_to_adev(ddev); struct amdgpu_ptl *ptl = &adev->psp.ptl; + uint32_t ptl_state, fmt1, fmt2; + int ret; + + if (amdgpu_sriov_vf(adev)) { + ptl_state = ptl->enabled; + fmt1 = ptl->fmt1; + fmt2 = ptl->fmt2; + ret = amdgpu_ptl_perf_monitor_ctrl(adev, PSP_PTL_PERF_MON_QUERY, + &ptl_state, &fmt1, &fmt2); + if (ret) + return ret; + } if (ptl->permanently_disabled) return sysfs_emit(buf, "permanently disabled\n"); @@ -1533,11 +1587,23 @@ static ssize_t ptl_format_show(struct device *dev, struct device_attribute *attr { struct drm_device *ddev = dev_get_drvdata(dev); struct amdgpu_device *adev = drm_to_adev(ddev); - struct psp_context *psp = &adev->psp; + struct amdgpu_ptl *ptl = &adev->psp.ptl; + uint32_t ptl_state, fmt1, fmt2; + int ret; + + if (amdgpu_sriov_vf(adev)) { + ptl_state = ptl->enabled; + fmt1 = ptl->fmt1; + fmt2 = ptl->fmt2; + ret = amdgpu_ptl_perf_monitor_ctrl(adev, PSP_PTL_PERF_MON_QUERY, + &ptl_state, &fmt1, &fmt2); + if (ret) + return ret; + } return sysfs_emit(buf, "%s,%s\n", - amdgpu_ptl_fmt_str[psp->ptl.fmt1], - amdgpu_ptl_fmt_str[psp->ptl.fmt2]); + amdgpu_ptl_fmt_str[ptl->fmt1], + amdgpu_ptl_fmt_str[ptl->fmt2]); } static umode_t amdgpu_ptl_is_visible(struct kobject *kobj, struct attribute *attr, int idx) @@ -1547,7 +1613,7 @@ static umode_t amdgpu_ptl_is_visible(struct kobject *kobj, struct attribute *att struct amdgpu_device *adev = drm_to_adev(ddev); /* Only show PTL sysfs files if PTL hardware is supported */ - if (!adev->psp.ptl.hw_supported) + if (adev->psp.ptl.hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return 0; return attr->mode; @@ -1558,7 +1624,7 @@ int amdgpu_ptl_sysfs_init(struct amdgpu_device *adev) struct amdgpu_ptl *ptl = &adev->psp.ptl; int ret; - if (!ptl->hw_supported) + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return 0; if (ptl->ptl_sysfs_created) @@ -1575,7 +1641,7 @@ void amdgpu_ptl_sysfs_fini(struct amdgpu_device *adev) { struct amdgpu_ptl *ptl = &adev->psp.ptl; - if (!ptl->hw_supported) + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return; if (!ptl->ptl_sysfs_created) @@ -1861,6 +1927,12 @@ invoke: /* note down the capbility flag for XGMI TA */ psp->xgmi_context.xgmi_ta_caps = xgmi_cmd->caps_flag; + if (!amdgpu_sriov_vf(psp->adev)) + psp->xgmi_context.supports_ext_link_info = psp->xgmi_context.xgmi_ta_caps & + EXTEND_PEER_LINK_INFO_CMD_FLAG; + else + psp->xgmi_context.supports_ext_link_info = amdgpu_sriov_xgmi_ta_ext_peer_link_en(psp->adev); + return ret; } @@ -2038,15 +2110,13 @@ int psp_xgmi_get_topology_info(struct psp_context *psp, amdgpu_ip_version(psp->adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || amdgpu_sriov_vf(psp->adev); - bool ta_port_num_support = psp->xgmi_context.xgmi_ta_caps & EXTEND_PEER_LINK_INFO_CMD_FLAG || - amdgpu_sriov_xgmi_ta_ext_peer_link_en(psp->adev); /* popluate the shared output buffer rather than the cmd input buffer * with node_ids as the input for GET_PEER_LINKS command execution. * This is required for GET_PEER_LINKS per xgmi ta implementation. * The same requirement for GET_EXTEND_PEER_LINKS command. */ - if (ta_port_num_support) { + if (psp->xgmi_context.supports_ext_link_info) { link_extend_info_output = &xgmi_cmd->xgmi_out_message.get_extend_link_info; for (i = 0; i < topology->num_nodes; i++) @@ -2069,7 +2139,7 @@ int psp_xgmi_get_topology_info(struct psp_context *psp, return ret; for (i = 0; i < topology->num_nodes; i++) { - uint8_t node_num_links = ta_port_num_support ? + uint8_t node_num_links = psp->xgmi_context.supports_ext_link_info ? link_extend_info_output->nodes[i].num_links : link_info_output->nodes[i].num_links; /* accumulate num_links on extended data */ if (get_extended_data) { @@ -2079,7 +2149,7 @@ int psp_xgmi_get_topology_info(struct psp_context *psp, topology->nodes[i].num_links : node_num_links; } /* popluate the connected port num info if supported and available */ - if (ta_port_num_support && topology->nodes[i].num_links) { + if (psp->xgmi_context.supports_ext_link_info && topology->nodes[i].num_links) { memcpy(topology->nodes[i].port_num, link_extend_info_output->nodes[i].port_num, sizeof(struct xgmi_connected_port_num) * TA_XGMI__MAX_PORT_NUM); } @@ -3476,8 +3546,10 @@ static int psp_load_non_psp_fw(struct psp_context *psp) amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 0) || amdgpu_ip_version(adev, MP0_HWIP, 0) == - IP_VERSION(15, 0, 8) || + IP_VERSION(15, 0, 5) || amdgpu_ip_version(adev, MP0_HWIP, 0) == + IP_VERSION(15, 0, 8) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 9)) && (ucode->ucode_id == AMDGPU_UCODE_ID_SDMA1 || ucode->ucode_id == AMDGPU_UCODE_ID_SDMA2 || diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h index 2d838b1b2b11..d80c85793e3b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h @@ -232,6 +232,7 @@ struct psp_xgmi_context { struct ta_context context; struct psp_xgmi_topology_info top_info; bool supports_extended_data; + bool supports_ext_link_info; uint8_t xgmi_ta_caps; }; @@ -656,5 +657,6 @@ int amdgpu_psp_reg_program_no_ring(struct psp_context *psp, uint32_t val, void amdgpu_psp_debugfs_init(struct amdgpu_device *adev); int amdgpu_psp_get_fw_type(struct amdgpu_firmware_info *ucode, enum psp_gfx_fw_type *type); +int psp_set_mmhub_eco_sec_level(struct amdgpu_device *adev); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index fd2c0cc1b958..297f8b04c4eb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -596,8 +596,14 @@ static ssize_t amdgpu_ras_debugfs_ctrl_write(struct file *f, ret = amdgpu_ras_feature_enable(adev, &data.head, 1); break; case 2: - /* umc ce/ue error injection for a bad page is not allowed */ - if (data.head.block == AMDGPU_RAS_BLOCK__UMC) + /* + * UMC ce/ue error injection for a bad page is not allowed. For + * uniras (SMU v13+) devices the injection address is validated by + * the ras_mgr inject handler, so only run the legacy bad page + * check for the legacy RAS path. + */ + if (data.head.block == AMDGPU_RAS_BLOCK__UMC && + !amdgpu_uniras_enabled(adev)) ret = amdgpu_ras_check_bad_page(adev, data.inject.address); if (ret == -EINVAL) { dev_warn(adev->dev, "RAS WARN: input address 0x%llx is invalid.", @@ -1430,7 +1436,7 @@ static int amdgpu_uniras_clear_badpages_info(struct amdgpu_device *adev) &req, sizeof(req), NULL, 0); if (ret) { dev_err(adev->dev, "Failed to clear bad pages info, ret: %d\n", ret); - return ret; + return -EINVAL; } return 0; @@ -1530,7 +1536,7 @@ static int amdgpu_uniras_error_inject(struct amdgpu_device *adev, inject_req.method = info->value; return amdgpu_ras_mgr_handle_ras_cmd(adev, RAS_CMD__INJECT_ERROR, - &inject_req, sizeof(inject_req), &rsp, sizeof(rsp)); + &inject_req, sizeof(inject_req), &rsp, sizeof(rsp)) ? -EINVAL : 0; } /* wrapper of psp_ras_trigger_error */ @@ -3777,8 +3783,11 @@ int amdgpu_ras_block_late_init(struct amdgpu_device *adev, } if (amdgpu_uniras_enabled(adev) || (ras_obj->hw_ops && - (ras_obj->hw_ops->query_ras_error_count || - ras_obj->hw_ops->query_ras_error_status))) { + (ras_obj->hw_ops->query_ras_error_count || + ras_obj->hw_ops->query_ras_error_status)) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 12) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6)) { r = amdgpu_ras_sysfs_create(adev, ras_block); if (r) goto interrupt; @@ -4981,7 +4990,13 @@ void amdgpu_ras_post_reset(struct amdgpu_device *adev, } } -void amdgpu_ras_resume_after_reset(struct amdgpu_device *adev) +int amdgpu_ras_resume_after_reset(struct amdgpu_device *adev) { - amdgpu_ras_mgr_resume_after_reset(adev); + int r; + + r = amdgpu_ras_mgr_resume_after_reset(adev); + if (r) + return r; + + return amdgpu_cper_deferred_init(adev); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h index 23bff7a0f35b..c53f911d5729 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h @@ -980,5 +980,5 @@ void amdgpu_ras_pre_reset(struct amdgpu_device *adev, struct list_head *device_list); void amdgpu_ras_post_reset(struct amdgpu_device *adev, struct list_head *device_list); -void amdgpu_ras_resume_after_reset(struct amdgpu_device *adev); +int amdgpu_ras_resume_after_reset(struct amdgpu_device *adev); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index 0d34f0eca991..686c92e96025 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -526,7 +526,7 @@ static ssize_t amdgpu_ras_cper_debugfs_read(struct file *f, char __user *buf, snapshot_req, sizeof(struct ras_cmd_cper_snapshot_req), snapshot_rsp, sizeof(struct ras_cmd_cper_snapshot_rsp)); if (r) - return r; + return -EINVAL; if (!snapshot_rsp->total_cper_num) { if (!read_header) @@ -568,7 +568,7 @@ static ssize_t amdgpu_ras_cper_debugfs_read(struct file *f, char __user *buf, record_rsp, sizeof(struct ras_cmd_cper_record_rsp)); if (r) - return r; + return -EINVAL; if (!record_rsp->real_data_size || !record_rsp->real_cper_num) break; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h index 9d3934b4f106..015623b8fd05 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h @@ -68,8 +68,6 @@ enum amdgpu_ring_priority_level { #define to_amdgpu_ring(s) container_of((s), struct amdgpu_ring, sched) -#define AMDGPU_IB_POOL_SIZE (1024 * 1024) - enum amdgpu_ring_type { AMDGPU_RING_TYPE_GFX = AMDGPU_HW_IP_GFX, AMDGPU_RING_TYPE_COMPUTE = AMDGPU_HW_IP_COMPUTE, @@ -585,6 +583,8 @@ int amdgpu_ib_schedule(struct amdgpu_ring *ring, unsigned num_ibs, struct dma_fence **f); int amdgpu_ib_pool_init(struct amdgpu_device *adev); void amdgpu_ib_pool_fini(struct amdgpu_device *adev); +gfp_t amdgpu_ib_pool_gfp_flags(struct amdgpu_device *adev, + enum amdgpu_ib_pool_type type); int amdgpu_ib_ring_tests(struct amdgpu_device *adev); bool amdgpu_ring_sched_ready(struct amdgpu_ring *ring); void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c index 39070b2a4c04..74124f80601e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c @@ -46,11 +46,13 @@ int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, struct amdgpu_sa_manager *sa_manager, - unsigned int size, u32 suballoc_align, u32 domain) + unsigned int size, gfp_t gfp_flags) { int r; - r = amdgpu_bo_create_kernel(adev, size, AMDGPU_GPU_PAGE_SIZE, domain, + sa_manager->gfp_flags = gfp_flags; + r = amdgpu_bo_create_kernel(adev, size, AMDGPU_GPU_PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT, &sa_manager->bo, &sa_manager->gpu_addr, &sa_manager->cpu_ptr); if (r) { @@ -59,7 +61,8 @@ int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, } memset(sa_manager->cpu_ptr, 0, size); - drm_suballoc_manager_init(&sa_manager->base, size, suballoc_align); + drm_suballoc_manager_init(&sa_manager->base, size, 256); + return r; } @@ -73,7 +76,8 @@ void amdgpu_sa_bo_manager_fini(struct amdgpu_device *adev, drm_suballoc_manager_fini(&sa_manager->base); - amdgpu_bo_free_kernel(&sa_manager->bo, &sa_manager->gpu_addr, &sa_manager->cpu_ptr); + amdgpu_bo_free_kernel(&sa_manager->bo, &sa_manager->gpu_addr, + &sa_manager->cpu_ptr); } int amdgpu_sa_bo_new(struct amdgpu_sa_manager *sa_manager, @@ -81,7 +85,8 @@ int amdgpu_sa_bo_new(struct amdgpu_sa_manager *sa_manager, unsigned int size) { struct drm_suballoc *sa = drm_suballoc_new(&sa_manager->base, size, - GFP_KERNEL, false, 0); + sa_manager->gfp_flags, + false, 0); if (IS_ERR(sa)) { *sa_bo = NULL; @@ -110,6 +115,7 @@ void amdgpu_sa_bo_dump_debug_info(struct amdgpu_sa_manager *sa_manager, { struct drm_printer p = drm_seq_file_printer(m); - drm_suballoc_dump_debug_info(&sa_manager->base, &p, sa_manager->gpu_addr); + drm_suballoc_dump_debug_info(&sa_manager->base, &p, + sa_manager->gpu_addr); } #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h index 8c85c80fc119..1d1c89348709 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h @@ -35,6 +35,7 @@ struct amdgpu_sa_manager { struct amdgpu_bo *bo; uint64_t gpu_addr; void *cpu_ptr; + gfp_t gfp_flags; }; static inline struct amdgpu_sa_manager * @@ -57,7 +58,7 @@ static inline void *amdgpu_sa_bo_cpu_addr(struct drm_suballoc *sa_bo) int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, struct amdgpu_sa_manager *sa_manager, - unsigned size, u32 align, u32 domain); + unsigned size, gfp_t gfp_flags); void amdgpu_sa_bo_manager_fini(struct amdgpu_device *adev, struct amdgpu_sa_manager *sa_manager); int amdgpu_sa_bo_manager_start(struct amdgpu_device *adev, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index b10b0878df37..c4094edbdff1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -60,6 +60,7 @@ #include "amdgpu_atomfirmware.h" #include "amdgpu_res_cursor.h" #include "bif/bif_4_1_d.h" +#include "kfd_svm.h" MODULE_IMPORT_NS("DMA_BUF"); @@ -244,8 +245,9 @@ static int amdgpu_ttm_map_buffer(struct amdgpu_ttm_buffer_entity *entity, r = amdgpu_job_alloc_with_ib(adev, &entity->base, AMDGPU_FENCE_OWNER_UNDEFINED, num_dw * 4 + num_bytes, - AMDGPU_IB_POOL_DELAYED, &job, - AMDGPU_KERNEL_JOB_ID_TTM_MAP_BUFFER); + AMDGPU_IB_POOL_DELAYED, + AMDGPU_KERNEL_JOB_ID_TTM_MAP_BUFFER, + &job); if (r) return r; @@ -1487,6 +1489,7 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo, const struct ttm_place *place) { struct dma_resv_iter resv_cursor; + struct amdgpu_bo *abo; struct dma_fence *f; if (!amdgpu_bo_is_amdgpu_bo(bo)) @@ -1496,6 +1499,22 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo, if (bo->resource->mem_type == TTM_PL_SYSTEM) return true; + abo = ttm_to_amdgpu_bo(bo); + if ((abo->flags & AMDGPU_GEM_CREATE_DISCARDABLE) && + bo->destroy == &svm_range_bo_destroy) { + /* + * SVM BOs are migrated to system memory synchronously in this + * TTM eviction context. The migration needs the owning + * process's mmap lock, but the normal lock order is + * mmap_lock -> BO reservation and the BO is already reserved + * here. svm_range_evict_svm_bo() only trylocks the mmap lock; + * if the eviction fails for any reason, we return false so TTM + * skips this BO instead of risking a deadlock. + */ + if (svm_range_evict_svm_bo(abo) < 0) + return false; + } + if (bo->type == ttm_bo_type_kernel && !amdgpu_vm_evictable(ttm_to_amdgpu_bo(bo))) return false; @@ -1592,8 +1611,8 @@ static int amdgpu_ttm_access_memory_sdma(struct ttm_buffer_object *bo, r = amdgpu_job_alloc_with_ib(adev, &adev->mman.default_entity.base, AMDGPU_FENCE_OWNER_UNDEFINED, num_dw * 4, AMDGPU_IB_POOL_DELAYED, - &job, - AMDGPU_KERNEL_JOB_ID_TTM_ACCESS_MEMORY_SDMA); + AMDGPU_KERNEL_JOB_ID_TTM_ACCESS_MEMORY_SDMA, + &job); if (r) goto out; @@ -2173,6 +2192,18 @@ int amdgpu_ttm_init(struct amdgpu_device *adev) gtt_size = configured_size; } + /* Cap GTT so that it does not exceed total physical RAM. */ + if (adev->flags & AMD_IS_APU) { + u64 phys_ram = (u64)totalram_pages() << PAGE_SHIFT; + + if (gtt_size > phys_ram) { + gtt_size = phys_ram; + dev_info(adev->dev, + "Capping GTT to %uM to not exceed available system memory\n", + (unsigned int)(gtt_size / (1024 * 1024))); + } + } + /* Initialize GTT memory pool */ r = amdgpu_gtt_mgr_init(adev, gtt_size); if (r) { @@ -2443,7 +2474,7 @@ static int amdgpu_ttm_prepare_job(struct amdgpu_device *adev, int r; r = amdgpu_job_alloc_with_ib(adev, &entity->base, AMDGPU_FENCE_OWNER_UNDEFINED, - num_dw * 4, pool, job, k_job_id); + num_dw * 4, pool, k_job_id, job); if (r) return r; @@ -2460,6 +2491,27 @@ static int amdgpu_ttm_prepare_job(struct amdgpu_device *adev, DMA_RESV_USAGE_BOOKKEEP); } +static int amdgpu_calc_bytes_per_packet(u32 max_bytes_per_packet, + u32 byte_count) +{ + /* Byte count is dword-aligned and fits a single packet */ + if (!(byte_count & 0x3) && byte_count <= max_bytes_per_packet) + return max_bytes_per_packet; + + /* + * Align down maximum byte count to 256 bytes so that + * the copy optimally uses all memory channels and + * also to ensure that SDMA can use its dword mode, which + * is faster. + * + * This assumes that the starting addresses of BOs are always + * dword aligned, which should be the case for every copy + * operation in the kernel, because the kernel always copies + * pages. + */ + return ALIGN_DOWN(max_bytes_per_packet, SZ_256); +} + int amdgpu_copy_buffer(struct amdgpu_device *adev, struct amdgpu_ttm_buffer_entity *entity, uint64_t src_offset, @@ -2483,7 +2535,8 @@ int amdgpu_copy_buffer(struct amdgpu_device *adev, return -EINVAL; } - max_bytes = adev->mman.buffer_funcs->copy_max_bytes; + max_bytes = amdgpu_calc_bytes_per_packet(adev->mman.buffer_funcs->copy_max_bytes, + byte_count); num_loops = DIV_ROUND_UP(byte_count, max_bytes); num_dw = ALIGN(num_loops * adev->mman.buffer_funcs->copy_num_dw, 8); r = amdgpu_ttm_prepare_job(adev, entity, num_dw, @@ -2527,7 +2580,8 @@ static int amdgpu_ttm_fill_mem(struct amdgpu_device *adev, unsigned int i; int r; - max_bytes = adev->mman.buffer_funcs->fill_max_bytes; + max_bytes = amdgpu_calc_bytes_per_packet(adev->mman.buffer_funcs->fill_max_bytes, + byte_count); num_loops = DIV_ROUND_UP_ULL(byte_count, max_bytes); num_dw = ALIGN(num_loops * adev->mman.buffer_funcs->fill_num_dw, 8); r = amdgpu_ttm_prepare_job(adev, entity, num_dw, resv, @@ -2684,12 +2738,22 @@ void amdgpu_sdma_set_buffer_funcs_scheds(struct amdgpu_device *adev, return; } - /* Navi1x's workaround requires us to limit to a single SDMA sched - * for ttm. - */ hub = &adev->vmhub[AMDGPU_GFXHUB(0)]; - adev->mman.num_buffer_funcs_scheds = hub->sdma_invalidation_workaround ? - 1 : n; + + /* + * Allow using multiple SDMA schedulers only on GPUs where + * we are allowed to do concurrent VM flushes. + * This consideration is necessary because all GART windows + * are mapped in VMID 0 (the kernel VMID) so each buffer + * entity would flush VMID 0 concurrently. + * + * Also consider the SDMA invalidation workaround on + * Navi 1x GPUs, which also prevents us from using + * multiple SDMA engines on VMID 0 at the same time. + */ + adev->mman.num_buffer_funcs_scheds = + (adev->vm_manager.concurrent_flush && + !hub->sdma_invalidation_workaround) ? n : 1; } #if defined(CONFIG_DEBUG_FS) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 9cc71a3a5362..3fe10d6af757 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -123,6 +123,8 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) struct amdgpu_device *adev = uq_mgr->adev; const struct amdgpu_userq_funcs *userq_funcs = adev->userq_funcs[queue->queue_type]; + struct drm_wedge_task_info *info = NULL; + struct amdgpu_task_info *ti = NULL; bool gpu_reset = false; if (unlikely(adev->debug_disable_gpu_ring_reset)) { @@ -137,6 +139,14 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) if (!amdgpu_gpu_recovery) return; + if (queue->vm && queue->vm->pasid) { + ti = amdgpu_vm_get_task_info_pasid(adev, queue->vm->pasid); + if (ti) { + amdgpu_vm_print_task_info(adev, ti); + info = &ti->task; + } + } + if (amdgpu_userq_is_reset_type_supported(adev, queue->queue_type, AMDGPU_RESET_TYPE_PER_QUEUE)) { int r; @@ -146,11 +156,17 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) queue, NULL, NULL); else r = userq_funcs->reset(queue); - if (r) + if (r) { gpu_reset = true; + } else { + atomic_inc(&adev->gpu_reset_counter); + amdgpu_userq_fence_driver_force_completion(queue); + drm_dev_wedged_event(adev_to_drm(adev), DRM_WEDGE_RECOVERY_NONE, info); + } } else { gpu_reset = true; } + amdgpu_vm_put_task_info(ti); /* * Don't schedule the work here! Scheduling or queue work from one reset @@ -985,6 +1001,7 @@ amdgpu_userq_vm_validate_and_restore_queue(struct amdgpu_userq_mgr *uq_mgr) struct amdgpu_vm *vm = &fpriv->vm; unsigned long key, tmp_key; struct amdgpu_bo_va *bo_va; + struct amdgpu_usermode_queue *queue; struct amdgpu_bo *bo; struct drm_exec exec; struct xarray xa; @@ -1100,6 +1117,24 @@ retry_lock: dma_fence_wait(bo_va->last_pt_update, false); dma_fence_wait(vm->last_update, false); + xa_for_each(&uq_mgr->userq_xa, tmp_key, queue) { + bo = queue->wptr_obj.obj; + if (!bo) { + ret = -EINVAL; + goto unlock_all; + } + + ret = amdgpu_ttm_alloc_gart(&bo->tbo); + if (unlikely(ret)) { + drm_file_err(uq_mgr->file, + "failed to bind wptr bo to gart on resume, qid=%lu ret=%d\n", + tmp_key, ret); + goto unlock_all; + } + + queue->wptr_obj.gpu_addr = amdgpu_bo_gpu_offset(bo); + } + ret = amdgpu_evf_mgr_rearm(&fpriv->evf_mgr, &exec); if (ret) { drm_file_err(uq_mgr->file, "Failed to replace eviction fence\n"); @@ -1422,18 +1457,21 @@ void amdgpu_userq_pre_reset(struct amdgpu_device *adev) /* TODO: We probably need a new lock for the queue state */ xa_for_each(&adev->userq_doorbell_xa, queue_id, queue) { - if (queue->state != AMDGPU_USERQ_STATE_MAPPED) - continue; - - trace_amdgpu_userq_state_start(queue); - userq_funcs = adev->userq_funcs[queue->queue_type]; - userq_funcs->unmap(queue); - /* just mark all queues as hung at this point. - * if unmap succeeds, we could map again - * in amdgpu_userq_post_reset() if vram is not lost + if (queue->state == AMDGPU_USERQ_STATE_MAPPED) { + trace_amdgpu_userq_state_start(queue); + userq_funcs = adev->userq_funcs[queue->queue_type]; + userq_funcs->unmap(queue); + /* just mark all queues as hung at this point. + * if unmap succeeds, we could map again + * in amdgpu_userq_post_reset() if vram is not lost + */ + trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_HUNG); + queue->state = AMDGPU_USERQ_STATE_HUNG; + } + /* Force-complete any pending fence regardless of queue state so + * that eviction/suspend and queue teardown waiters don't block + * forever on a fence that will never signal after the reset. */ - trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_HUNG); - queue->state = AMDGPU_USERQ_STATE_HUNG; amdgpu_userq_fence_driver_force_completion(queue); } } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 61e5f8a06eb2..8333bab537d4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -100,6 +100,9 @@ struct amdgpu_usermode_queue { } va; u64 va_array[6]; } userq_vas; + + uint32_t proc_ctx_array_index; + uint32_t gang_ctx_array_index; }; struct amdgpu_userq_funcs { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c index 23383ac5323f..e8b0c62f72be 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c @@ -1165,8 +1165,9 @@ static int amdgpu_uvd_send_msg(struct amdgpu_ring *ring, struct amdgpu_bo *bo, r = amdgpu_job_alloc_with_ib(ring->adev, &adev->uvd.entity, AMDGPU_FENCE_OWNER_UNDEFINED, 64, direct ? AMDGPU_IB_POOL_DIRECT : - AMDGPU_IB_POOL_DELAYED, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DELAYED, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c index eef3c9853a5c..1f7a98cdce5a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c @@ -473,7 +473,8 @@ static int amdgpu_vce_get_create_msg(struct amdgpu_ring *ring, uint32_t handle, r = amdgpu_job_alloc_with_ib(ring->adev, &ring->adev->vce.entity, AMDGPU_FENCE_OWNER_UNDEFINED, ib_size_dw * 4, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; @@ -564,8 +565,9 @@ static int amdgpu_vce_get_destroy_msg(struct amdgpu_ring *ring, uint32_t handle, AMDGPU_FENCE_OWNER_UNDEFINED, ib_size_dw * 4, direct ? AMDGPU_IB_POOL_DIRECT : - AMDGPU_IB_POOL_DELAYED, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DELAYED, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c index fe504f1a3fc8..17db7264269e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c @@ -629,7 +629,8 @@ static int amdgpu_vcn_dec_send_msg(struct amdgpu_ring *ring, r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, 64, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) goto err; @@ -809,7 +810,8 @@ static int amdgpu_vcn_dec_sw_send_msg(struct amdgpu_ring *ring, r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) goto err; @@ -939,7 +941,8 @@ static int amdgpu_vcn_enc_get_create_msg(struct amdgpu_ring *ring, uint32_t hand r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; @@ -1006,7 +1009,8 @@ static int amdgpu_vcn_enc_get_destroy_msg(struct amdgpu_ring *ring, uint32_t han r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c index 8f4b86063507..b43fc643668d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c @@ -2090,3 +2090,39 @@ int amdgpu_virt_send_remote_ras_cmd(struct amdgpu_device *adev, return ret; } + +int amdgpu_virt_ptl_request(struct amdgpu_device *adev, u32 req_code, + uint32_t *ptl_state, uint32_t *fmt1, uint32_t *fmt2) +{ + int ret; + + if (!ptl_state || !fmt1 || !fmt2) + return -EINVAL; + + if (!amdgpu_sriov_ptl_support(adev) || + !adev->virt.ops || !adev->virt.ops->req_ptl_update) + return -EOPNOTSUPP; + + if (req_code == PSP_PTL_PERF_MON_SET && *ptl_state) { + if (*fmt1 == *fmt2) { + dev_warn(adev->dev, + "PTL formats must be different (fmt1=%u, fmt2=%u)\n", + *fmt1, *fmt2); + return -EINVAL; + } + } + + ret = adev->virt.ops->req_ptl_update(adev, req_code, *ptl_state, *fmt1, *fmt2); + if (ret) { + dev_warn(adev->dev, "VF PTL update request failed: %d\n", ret); + return ret; + } + + if (req_code == PSP_PTL_PERF_MON_QUERY) { + *ptl_state = adev->virt.ptl_state; + *fmt1 = adev->virt.ptl_pref_format1; + *fmt2 = adev->virt.ptl_pref_format2; + } + + return 0; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.h index d8500c3e48a1..bcf7156a4a9e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.h @@ -116,6 +116,8 @@ struct amdgpu_virt_ops { int (*req_ras_chk_criti)(struct amdgpu_device *adev, u64 addr); int (*req_remote_ras_cmd)(struct amdgpu_device *adev, u32 param1, u32 param2, u32 param3); + int (*req_ptl_update)(struct amdgpu_device *adev, + u32 req_code, u32 ptl_state, u32 fmt1, u32 fmt2); }; /* @@ -341,6 +343,11 @@ struct amdgpu_virt { /* Spinlock to protect access to the RLCG register interface */ spinlock_t rlcg_reg_lock; + /* PTL (Performance Throttle Limiter) response from host */ + uint32_t ptl_state; + uint32_t ptl_pref_format1; + uint32_t ptl_pref_format2; + struct mutex access_req_mutex; union amd_sriov_ras_caps ras_en_caps; @@ -447,6 +454,8 @@ static inline bool is_virtual_machine(void) ((adev)->virt.gim_feature & AMDGIM_FEATURE_MES_INFO_ENABLE) #define amdgpu_sriov_is_unitid_support(adev) \ ((adev)->virt.gim_feature & AMDGIM_FEATURE_UNITID_SUPPORT) +#define amdgpu_sriov_ptl_support(adev) \ + ((adev)->virt.gim_feature & AMDGIM_FEATURE_PTL_SUPPORT) #define amdgpu_virt_xgmi_migrate_enabled(adev) \ ((adev)->virt.is_xgmi_node_migrate_enabled && (adev)->gmc.xgmi.node_segment_size != 0) @@ -457,6 +466,8 @@ int amdgpu_virt_request_full_gpu(struct amdgpu_device *adev, bool init); int amdgpu_virt_release_full_gpu(struct amdgpu_device *adev, bool init); int amdgpu_virt_reset_gpu(struct amdgpu_device *adev); void amdgpu_virt_request_init_data(struct amdgpu_device *adev); +int amdgpu_virt_ptl_request(struct amdgpu_device *adev, u32 req_code, + uint32_t *ptl_state, uint32_t *fmt1, uint32_t *fmt2); void amdgpu_virt_ready_to_reset(struct amdgpu_device *adev); int amdgpu_virt_wait_reset(struct amdgpu_device *adev); int amdgpu_virt_alloc_mm_table(struct amdgpu_device *adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c index 170adaf7e76a..c835504fdf2b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -316,6 +316,10 @@ static struct drm_plane *amdgpu_vkms_plane_init(struct drm_device *dev, return plane; } +static const struct drm_encoder_funcs drm_encoder_funcs_cleanup = { + .destroy = drm_encoder_cleanup, +}; + static int amdgpu_vkms_output_init(struct drm_device *dev, struct amdgpu_vkms_output *output, int index) { @@ -342,7 +346,9 @@ static int amdgpu_vkms_output_init(struct drm_device *dev, struct drm_connector_helper_add(connector, &amdgpu_vkms_conn_helper_funcs); - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_VIRTUAL); + ret = drm_encoder_init(dev, encoder, + &drm_encoder_funcs_cleanup, + DRM_MODE_ENCODER_VIRTUAL, NULL); if (ret) { DRM_ERROR("Failed to init encoder\n"); goto err_encoder; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index f1dcf4f5bb78..4c90e88e2e30 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -855,12 +855,10 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, job->oa_size); } - if (vm_flush_needed || pasid_mapping_needed || cleaner_shader_needed) { - amdgpu_fence_emit(ring, job->hw_vm_fence, 0); - fence = &job->hw_vm_fence->base; - /* get a ref for the job */ - dma_fence_get(fence); - } + amdgpu_fence_emit(ring, job->hw_vm_fence, 0); + fence = &job->hw_vm_fence->base; + /* get a ref for the job */ + dma_fence_get(fence); if (vm_flush_needed) { mutex_lock(&id_mgr->lock); @@ -894,7 +892,12 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, amdgpu_ring_patch_cond_exec(ring, patch); - /* the double SWITCH_BUFFER here *cannot* be skipped by COND_EXEC */ + /* + * Sync CE with ME to prevent CE from fetching the next CE IB + * before the context switch is done. This is emitted before + * the first IB of a job submission after a context switch. + * The double SWITCH_BUFFER here *cannot* be skipped by COND_EXEC. + */ if (ring->funcs->emit_switch_buffer) { amdgpu_ring_emit_switch_buffer(ring); amdgpu_ring_emit_switch_buffer(ring); @@ -1555,6 +1558,8 @@ int amdgpu_vm_clear_freed(struct amdgpu_device *adev, struct amdgpu_sync sync; int r; + if (list_empty(&vm->freed)) + return 0; /* * Implicitly sync to command submissions in the same VM before @@ -2506,14 +2511,16 @@ amdgpu_vm_get_task_info_vm(struct amdgpu_vm *vm) struct amdgpu_task_info * amdgpu_vm_get_task_info_pasid(struct amdgpu_device *adev, u32 pasid) { + struct amdgpu_fpriv *fpriv; struct amdgpu_task_info *ti; struct amdgpu_vm *vm; unsigned long flags; - xa_lock_irqsave(&adev->vm_manager.pasids, flags); - vm = xa_load(&adev->vm_manager.pasids, pasid); + amdgpu_pasid_lock(&flags); + fpriv = amdgpu_pasid_get_fpriv_locked(pasid); + vm = fpriv ? &fpriv->vm : NULL; ti = amdgpu_vm_get_task_info_vm(vm); - xa_unlock_irqrestore(&adev->vm_manager.pasids, flags); + amdgpu_pasid_unlock(flags); return ti; } @@ -2554,7 +2561,6 @@ void amdgpu_vm_set_task_info(struct amdgpu_vm *vm) * @adev: amdgpu_device pointer * @vm: requested vm * @xcp_id: GPU partition selection id - * @pasid: the pasid the VM is using on this GPU * * Init @vm fields. * @@ -2562,7 +2568,7 @@ void amdgpu_vm_set_task_info(struct amdgpu_vm *vm) * 0 for success, error for failure. */ int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, - int32_t xcp_id, uint32_t pasid) + int32_t xcp_id) { struct amdgpu_bo *root_bo; struct amdgpu_bo_vm *root; @@ -2637,26 +2643,12 @@ int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, if (r) dev_dbg(adev->dev, "Failed to create task info for VM\n"); - /* Store new PASID in XArray (if non-zero) */ - if (pasid != 0) { - r = xa_err(xa_store_irq(&adev->vm_manager.pasids, pasid, vm, GFP_KERNEL)); - if (r < 0) - goto error_free_root; - - vm->pasid = pasid; - } - amdgpu_bo_unreserve(vm->root.bo); amdgpu_bo_unref(&root_bo); return 0; error_free_root: - /* If PASID was partially set, erase it from XArray before failing */ - if (vm->pasid != 0) { - xa_erase_irq(&adev->vm_manager.pasids, vm->pasid); - vm->pasid = 0; - } amdgpu_vm_pt_free_root(adev, vm); amdgpu_bo_unreserve(vm->root.bo); amdgpu_bo_unref(&root_bo); @@ -2763,11 +2755,6 @@ void amdgpu_vm_fini(struct amdgpu_device *adev, struct amdgpu_vm *vm) root = amdgpu_bo_ref(vm->root.bo); amdgpu_bo_reserve(root, true); - /* Remove PASID mapping before destroying VM */ - if (vm->pasid != 0) { - xa_erase_irq(&adev->vm_manager.pasids, vm->pasid); - vm->pasid = 0; - } dma_fence_wait(vm->last_unlocked, false); dma_fence_put(vm->last_unlocked); dma_fence_wait(vm->last_tlb_flush, false); @@ -2863,8 +2850,6 @@ void amdgpu_vm_manager_init(struct amdgpu_device *adev) #else adev->vm_manager.vm_update_mode = 0; #endif - - xa_init_flags(&adev->vm_manager.pasids, XA_FLAGS_LOCK_IRQ); } /** @@ -2876,9 +2861,6 @@ void amdgpu_vm_manager_init(struct amdgpu_device *adev) */ void amdgpu_vm_manager_fini(struct amdgpu_device *adev) { - WARN_ON(!xa_empty(&adev->vm_manager.pasids)); - xa_destroy(&adev->vm_manager.pasids); - amdgpu_vmid_mgr_fini(adev); amdgpu_pasid_mgr_cleanup(); } @@ -2935,14 +2917,16 @@ struct amdgpu_vm *amdgpu_vm_lock_by_pasid(struct amdgpu_device *adev, u32 pasid, struct drm_exec *exec) { unsigned long irqflags; + struct amdgpu_fpriv *fpriv; struct amdgpu_bo *root; struct amdgpu_vm *vm; int r; - xa_lock_irqsave(&adev->vm_manager.pasids, irqflags); - vm = xa_load(&adev->vm_manager.pasids, pasid); - root = vm ? amdgpu_bo_ref(vm->root.bo) : NULL; - xa_unlock_irqrestore(&adev->vm_manager.pasids, irqflags); + amdgpu_pasid_lock(&irqflags); + fpriv = amdgpu_pasid_get_fpriv_locked(pasid); + vm = fpriv ? &fpriv->vm : NULL; + root = vm && vm->root.bo ? amdgpu_bo_ref(vm->root.bo) : NULL; + amdgpu_pasid_unlock(irqflags); if (!root) return NULL; @@ -2954,11 +2938,17 @@ struct amdgpu_vm *amdgpu_vm_lock_by_pasid(struct amdgpu_device *adev, } /* Double check that the VM still exists */ - xa_lock_irqsave(&adev->vm_manager.pasids, irqflags); - vm = xa_load(&adev->vm_manager.pasids, pasid); - if (vm && vm->root.bo != root) + amdgpu_pasid_lock(&irqflags); + fpriv = amdgpu_pasid_get_fpriv_locked(pasid); + if (!fpriv) { vm = NULL; - xa_unlock_irqrestore(&adev->vm_manager.pasids, irqflags); + } else { + vm = &fpriv->vm; + if (vm->root.bo != root) + vm = NULL; + } + amdgpu_pasid_unlock(irqflags); + if (!vm) { drm_exec_unlock_obj(exec, &root->tbo.base); amdgpu_bo_unref(&root); @@ -3155,12 +3145,14 @@ void amdgpu_vm_update_fault_cache(struct amdgpu_device *adev, uint32_t status, unsigned int vmhub) { + struct amdgpu_fpriv *fpriv; struct amdgpu_vm *vm; unsigned long flags; - xa_lock_irqsave(&adev->vm_manager.pasids, flags); + amdgpu_pasid_lock(&flags); - vm = xa_load(&adev->vm_manager.pasids, pasid); + fpriv = amdgpu_pasid_get_fpriv_locked(pasid); + vm = fpriv ? &fpriv->vm : NULL; /* Don't update the fault cache if status is 0. In the multiple * fault case, subsequent faults will return a 0 status which is * useless for userspace and replaces the useful fault status, so @@ -3193,7 +3185,7 @@ void amdgpu_vm_update_fault_cache(struct amdgpu_device *adev, WARN_ONCE(1, "Invalid vmhub %u\n", vmhub); } } - xa_unlock_irqrestore(&adev->vm_manager.pasids, flags); + amdgpu_pasid_unlock(flags); } void amdgpu_vm_print_task_info(struct amdgpu_device *adev, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h index 5822836fa4a3..2f8234560764 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h @@ -481,10 +481,6 @@ struct amdgpu_vm_manager { */ int vm_update_mode; - /* PASID to VM mapping, will be used in interrupt context to - * look up VM of a page fault - */ - struct xarray pasids; /* Global registration of recent page fault information */ struct amdgpu_vm_fault_info fault_info; }; @@ -502,7 +498,7 @@ void amdgpu_vm_manager_init(struct amdgpu_device *adev); void amdgpu_vm_manager_fini(struct amdgpu_device *adev); long amdgpu_vm_wait_idle(struct amdgpu_vm *vm, long timeout); -int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, int32_t xcp_id, uint32_t pasid); +int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, int32_t xcp_id); int amdgpu_vm_make_compute(struct amdgpu_device *adev, struct amdgpu_vm *vm); void amdgpu_vm_fini(struct amdgpu_device *adev, struct amdgpu_vm *vm); int amdgpu_vm_lock_pd(struct amdgpu_vm *vm, struct drm_exec *exec, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c index fd09a2b5a147..430274237689 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c @@ -56,7 +56,7 @@ static int amdgpu_vm_sdma_alloc_job(struct amdgpu_vm_update_params *p, ndw = min(ndw, AMDGPU_VM_SDMA_MAX_NUM_DW); r = amdgpu_job_alloc_with_ib(p->adev, entity, AMDGPU_FENCE_OWNER_VM, - ndw * 4, pool, &p->job, k_job_id); + ndw * 4, pool, k_job_id, &p->job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index d2c5bb50d94a..2725230aa5e3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -559,7 +559,7 @@ static int amdgpu_xgmi_sysfs_add_dev_info(struct amdgpu_device *adev, pr_err("failed to create xgmi_num_links\n"); /* Create xgmi port num file if supported */ - if (adev->psp.xgmi_context.xgmi_ta_caps & EXTEND_PEER_LINK_INFO_CMD_FLAG) { + if (adev->psp.xgmi_context.supports_ext_link_info) { ret = device_create_file(adev->dev, &dev_attr_xgmi_port_num); if (ret) dev_err(adev->dev, "failed to create xgmi_port_num\n"); @@ -595,7 +595,7 @@ remove_file: device_remove_file(adev->dev, &dev_attr_xgmi_error); device_remove_file(adev->dev, &dev_attr_xgmi_num_hops); device_remove_file(adev->dev, &dev_attr_xgmi_num_links); - if (adev->psp.xgmi_context.xgmi_ta_caps & EXTEND_PEER_LINK_INFO_CMD_FLAG) + if (adev->psp.xgmi_context.supports_ext_link_info) device_remove_file(adev->dev, &dev_attr_xgmi_port_num); success: @@ -613,7 +613,7 @@ static void amdgpu_xgmi_sysfs_rem_dev_info(struct amdgpu_device *adev, device_remove_file(adev->dev, &dev_attr_xgmi_error); device_remove_file(adev->dev, &dev_attr_xgmi_num_hops); device_remove_file(adev->dev, &dev_attr_xgmi_num_links); - if (adev->psp.xgmi_context.xgmi_ta_caps & EXTEND_PEER_LINK_INFO_CMD_FLAG) + if (adev->psp.xgmi_context.supports_ext_link_info) device_remove_file(adev->dev, &dev_attr_xgmi_port_num); if (hive->kobj.parent != (&adev->dev->kobj)) @@ -1393,7 +1393,10 @@ static void amdgpu_xgmi_reset_on_init_work(struct work_struct *work) * no-op for any other reset path where RAS is already * initialized, and for non-uniras devices. */ - amdgpu_ras_resume_after_reset(tmp_adev); + r = amdgpu_ras_resume_after_reset(tmp_adev); + if (r) + dev_err(tmp_adev->dev, + "failed to resume RAS after XGMI reset-on-init\n"); } } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h b/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h index d80f01c0e754..33421ccdff04 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h @@ -379,7 +379,7 @@ struct amd_sriov_msg_vf2pf_info { struct { uint8_t id; uint32_t version; - } ucode_info[AMD_SRIOV_MSG_RESERVE_UCODE]; + } __packed ucode_info[AMD_SRIOV_MSG_RESERVE_UCODE]; uint64_t dummy_page_addr; /* FB allocated for guest MES to record UQ info */ uint64_t mes_info_addr; @@ -404,6 +404,9 @@ enum amd_sriov_mailbox_request_message { MB_REQ_RAS_ERROR_COUNT = 203, MB_REQ_RAS_CPER_DUMP = 204, MB_REQ_RAS_BAD_PAGES = 205, + MB_REQ_RAS_CHK_CRITI = 206, + MB_REQ_RAS_REMOTE_CMD = 207, + MB_REQ_MSG_PTL_UPDATE = 208, }; /* mailbox message send from host to guest */ @@ -424,9 +427,33 @@ enum amd_sriov_mailbox_response_message { MB_RES_MSG_RAS_BAD_PAGES_READY = 15, MB_RES_MSG_RAS_BAD_PAGES_NOTIFICATION = 16, MB_RES_MSG_UNRECOV_ERR_NOTIFICATION = 17, + MB_RES_RAS_CHK_CRITI_READY = 18, + MB_RES_RAS_REMOTE_CMD_READY = 19, + MB_RES_MSG_PTL_UPDATE_READY = 20, MB_RES_MSG_TEXT_MESSAGE = 255 }; +/* + * Generic response status codes for mailbox data fields. + * Used in msg_data[1..N] to indicate operation result. + */ +enum amd_sriov_response_status { + AMD_SRIOV_RESP_SUCCESS = 0, + AMD_SRIOV_RESP_FAIL = 1, + AMD_SRIOV_RESP_UNSUPPORTED = 2, +}; + +/* + * PTL mailbox data format: + * Request: msg_data[1]=req_code, msg_data[2]=ptl_state, msg_data[3]=(fmt1<<16)|fmt2 + * Response: msg_data[1]=(status<<16)|ptl_state, msg_data[2]=(fmt1<<16)|fmt2 + */ +#define AMD_SRIOV_PTL_PACK_FORMATS(fmt1, fmt2) ((((fmt1) & 0xFFFF) << 16) | ((fmt2) & 0xFFFF)) +#define AMD_SRIOV_PTL_UNPACK_STATUS(dw) (((dw) >> 16) & 0xFFFF) +#define AMD_SRIOV_PTL_UNPACK_STATE(dw) ((dw) & 0xFFFF) +#define AMD_SRIOV_PTL_UNPACK_FMT1(dw) (((dw) >> 16) & 0xFFFF) +#define AMD_SRIOV_PTL_UNPACK_FMT2(dw) ((dw) & 0xFFFF) + enum amd_sriov_ras_telemetry_gpu_block { RAS_TELEMETRY_GPU_BLOCK_UMC = 0, RAS_TELEMETRY_GPU_BLOCK_SDMA = 1, diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index 55342962422b..00798d80479f 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -4580,7 +4580,7 @@ static const struct amdgpu_gfx_funcs gfx_v10_0_gfx_funcs = { static void gfx_v10_0_gpu_early_init(struct amdgpu_device *adev) { - u32 gb_addr_config; + u32 gb_addr_config = 0; switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { case IP_VERSION(10, 1, 10): @@ -4620,8 +4620,9 @@ static void gfx_v10_0_gpu_early_init(struct amdgpu_device *adev) gb_addr_config = CYAN_SKILLFISH_GB_ADDR_CONFIG_GOLDEN; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return; } adev->gfx.config.gb_addr_config = gb_addr_config; @@ -4988,12 +4989,12 @@ static int gfx_v10_0_sw_init(struct amdgpu_ip_block *ip_block) gfx_v10_0_gpu_early_init(adev); - gfx_v10_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v10_0_alloc_ip_dump(adev); + return 0; } @@ -8602,13 +8603,16 @@ static u64 gfx_v10_0_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v10_0_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -8621,7 +8625,7 @@ static void gfx_v10_0_ring_set_wptr_compute(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx10 now */ + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -9379,7 +9383,7 @@ static void gfx_v10_0_handle_priv_fault(struct amdgpu_device *adev, } break; default: - BUG(); + break; } } @@ -9449,7 +9453,7 @@ static int gfx_v10_0_kiq_set_interrupt_state(struct amdgpu_device *adev, } break; default: - BUG(); /* kiq only support GENERIC2_INT now */ + /* kiq only support GENERIC2_INT now */ break; } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 2ef6bc818aba..9faf31421a15 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -1145,8 +1145,9 @@ static int gfx_v11_0_gpu_early_init(struct amdgpu_device *adev) adev->gfx.config.sc_earlyz_tile_fifo_size = 0x300; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return -EINVAL; } return 0; @@ -1920,12 +1921,12 @@ static int gfx_v11_0_sw_init(struct amdgpu_ip_block *ip_block) return -EINVAL; } - gfx_v11_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v11_0_alloc_ip_dump(adev); + adev->gfx.me.use_mmio_for_reset = false; adev->gfx.mec.use_mmio_for_reset = true; @@ -5954,13 +5955,16 @@ static u64 gfx_v11_0_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v11_0_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -5974,7 +5978,7 @@ static void gfx_v11_0_ring_set_wptr_compute(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx11 now */ + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -6710,52 +6714,13 @@ static int gfx_v11_0_set_priv_inst_fault_state(struct amdgpu_device *adev, static void gfx_v11_0_handle_priv_fault(struct amdgpu_device *adev, struct amdgpu_iv_entry *entry) { - u32 doorbell_offset = entry->src_data[0] & AMDGPU_CTXID0_DOORBELL_ID_MASK; + u8 me_id, pipe_id, queue_id; - /* - * Try KQ first by ring_id (HW slot is authoritative). The - * KMD compute_hqd_mask contract guarantees KCQ and user queues - * never share a HW slot. - */ - if (!adev->gfx.disable_kq) { - u8 me_id = (entry->ring_id & 0x0c) >> 2; - u8 pipe_id = (entry->ring_id & 0x03) >> 0; - u8 queue_id = (entry->ring_id & 0x70) >> 4; - struct amdgpu_ring *ring; - int i; + me_id = (entry->ring_id & 0x0c) >> 2; + pipe_id = (entry->ring_id & 0x03) >> 0; + queue_id = (entry->ring_id & 0x70) >> 4; - switch (me_id) { - case 0: - for (i = 0; i < adev->gfx.num_gfx_rings; i++) { - ring = &adev->gfx.gfx_ring[i]; - if (ring->me == me_id && ring->pipe == pipe_id && - ring->queue == queue_id) { - drm_sched_fault(&ring->sched); - return; - } - } - break; - case 1: - case 2: - for (i = 0; i < adev->gfx.num_compute_rings; i++) { - ring = &adev->gfx.compute_ring[i]; - if (ring->me == me_id && ring->pipe == pipe_id && - ring->queue == queue_id) { - drm_sched_fault(&ring->sched); - return; - } - } - break; - default: - BUG(); - break; - } - } - - /* No KQ matched: HW slot is a MES-scheduled user queue. */ - if (adev->enable_mes && doorbell_offset) - amdgpu_userq_process_reset_irq(adev, entry->pasid, - doorbell_offset); + amdgpu_gfx_handle_priv_fault(adev, entry, me_id, pipe_id, queue_id); } static int gfx_v11_0_priv_reg_irq(struct amdgpu_device *adev, @@ -6832,7 +6797,7 @@ static int gfx_v11_0_kiq_set_interrupt_state(struct amdgpu_device *adev, } break; default: - BUG(); /* kiq only support GENERIC2_INT now */ + /* kiq only support GENERIC2_INT now */ break; } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 6fedb6df8db9..45a47078f876 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -964,8 +964,9 @@ static int gfx_v12_0_gpu_early_init(struct amdgpu_device *adev) adev->gfx.config.sc_earlyz_tile_fifo_size = 0x4C0; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return -EINVAL; } return 0; @@ -1597,12 +1598,12 @@ static int gfx_v12_0_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; - gfx_v12_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v12_0_alloc_ip_dump(adev); + adev->gfx.me.use_mmio_for_reset = false; adev->gfx.mec.use_mmio_for_reset = true; @@ -4464,13 +4465,16 @@ static u64 gfx_v12_0_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v12_0_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -4484,7 +4488,7 @@ static void gfx_v12_0_ring_set_wptr_compute(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx12 now */ + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -5041,53 +5045,13 @@ static int gfx_v12_0_set_priv_inst_fault_state(struct amdgpu_device *adev, static void gfx_v12_0_handle_priv_fault(struct amdgpu_device *adev, struct amdgpu_iv_entry *entry) { - u32 doorbell_offset = entry->src_data[0] & AMDGPU_CTXID0_DOORBELL_ID_MASK; + u8 me_id, pipe_id, queue_id; - /* - * Try KQ first by ring_id; UQ as fallback. KCQ and UQ never share - * a HW slot (compute_hqd_mask contract). - */ - if (!adev->gfx.disable_kq) { - u8 me_id, pipe_id, queue_id; - struct amdgpu_ring *ring; - int i; + me_id = (entry->ring_id & 0x0c) >> 2; + pipe_id = (entry->ring_id & 0x03) >> 0; + queue_id = (entry->ring_id & 0x70) >> 4; - me_id = (entry->ring_id & 0x0c) >> 2; - pipe_id = (entry->ring_id & 0x03) >> 0; - queue_id = (entry->ring_id & 0x70) >> 4; - - switch (me_id) { - case 0: - for (i = 0; i < adev->gfx.num_gfx_rings; i++) { - ring = &adev->gfx.gfx_ring[i]; - if (ring->me == me_id && ring->pipe == pipe_id && - ring->queue == queue_id) { - drm_sched_fault(&ring->sched); - return; - } - } - break; - case 1: - case 2: - for (i = 0; i < adev->gfx.num_compute_rings; i++) { - ring = &adev->gfx.compute_ring[i]; - if (ring->me == me_id && ring->pipe == pipe_id && - ring->queue == queue_id) { - drm_sched_fault(&ring->sched); - return; - } - } - break; - default: - BUG(); - break; - } - } - - /* No KQ matched: HW slot is a MES-scheduled user queue. */ - if (adev->enable_mes && doorbell_offset) - amdgpu_userq_process_reset_irq(adev, entry->pasid, - doorbell_offset); + amdgpu_gfx_handle_priv_fault(adev, entry, me_id, pipe_id, queue_id); } static int gfx_v12_0_priv_reg_irq(struct amdgpu_device *adev, diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c index b19a4956c222..e49d8a79045c 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c @@ -69,6 +69,127 @@ MODULE_FIRMWARE("amdgpu/gc_12_1_0_rlc_1.bin"); (SH_MEM_ALIGNMENT_MODE_UNALIGNED_GFX12_1_0 << SH_MEM_CONFIG__ALIGNMENT_MODE__SHIFT) | \ (3 << SH_MEM_CONFIG__INITIAL_INST_PREFETCH__SHIFT)) + +static const struct amdgpu_hwip_reg_entry gc_reg_list_12_1[] = { + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS2), + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS3), + SOC15_REG_ENTRY_STR(GC, 0, regCP_STALLED_STAT1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_STALLED_STAT2), + SOC15_REG_ENTRY_STR(GC, 0, regCP_STALLED_STAT3), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_STALLED_STAT1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_STALLED_STAT1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_BUSY_STAT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_BUSY_STAT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_BUSY_STAT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_BUSY_STAT2), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_BUSY_STAT2), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_ERROR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_HPD_STATUS0), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB_BASE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB_WPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB0_BASE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB0_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB0_WPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_CMD_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_CMD_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_BASE_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_BASE_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_BASE_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_BASE_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCPF_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCPC_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCPG_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regIA_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regIA_UTCL1_STATUS_2), + SOC15_REG_ENTRY_STR(GC, 0, regPA_CL_CNTL_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regRMI_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regSQC_CACHES), + SOC15_REG_ENTRY_STR(GC, 0, regSQG_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regWD_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regGCVM_L2_PROTECTION_FAULT_CNTL2), + SOC15_REG_ENTRY_STR(GC, 0, regGCVM_L2_PROTECTION_FAULT_STATUS_LO32), + SOC15_REG_ENTRY_STR(GC, 0, regGCVM_L2_PROTECTION_FAULT_STATUS_HI32), + SOC15_REG_ENTRY_STR(GC, 0, regCP_DEBUG), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_CNTL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_CNTL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_INSTR_PNTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_ME_INSTR_PNTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_PFP_INSTR_PNTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_RS64_INSTR_PNTR0), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_RS64_INSTR_PNTR1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_RS64_INSTR_PNTR), + /* cp header registers */ + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + /* SE status registers */ + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS_SE0), + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS_SE1), +}; + +static const struct amdgpu_hwip_reg_entry gc_cp_reg_list_12_1[] = { + /* compute registers */ + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_VMID), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PERSISTENT_STATE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PIPE_PRIORITY), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_QUEUE_PRIORITY), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_QUANTUM), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_BASE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_BASE_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_POLL_ADDR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_POLL_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_DOORBELL_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_BASE_ADDR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_BASE_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_DEQUEUE_REQUEST), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_BASE_ADDR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_BASE_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_WPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_EVENTS), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_BASE_ADDR_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_BASE_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CNTL_STACK_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CNTL_STACK_SIZE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_WG_STATE_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_SIZE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_GDS_RESOURCE_STATE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_ERROR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_WPTR_MEM), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_SUSPEND_CNTL_STACK_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_SUSPEND_CNTL_STACK_DW_CNT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_SUSPEND_WG_STATE_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_DEQUEUE_STATUS), + /* cp header registers */ + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), +}; + static void gfx_v12_1_xcc_disable_gpa_mode(struct amdgpu_device *adev, int xcc_id); static void gfx_v12_1_set_ring_funcs(struct amdgpu_device *adev); static void gfx_v12_1_set_irq_funcs(struct amdgpu_device *adev); @@ -770,8 +891,9 @@ static int gfx_v12_1_gpu_early_init(struct amdgpu_device *adev) adev->gfx.config.sc_earlyz_tile_fifo_size = 0x4C0; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return -EINVAL; } return 0; @@ -1149,6 +1271,155 @@ static int gfx_v12_1_rlc_backdoor_autoload_enable(struct amdgpu_device *adev) return 0; } +static void gfx_v12_1_alloc_ip_dump(struct amdgpu_device *adev) +{ + uint32_t reg_count = ARRAY_SIZE(gc_reg_list_12_1); + uint32_t *ptr, inst, num_xcc; + + num_xcc = NUM_XCC(adev->gfx.xcc_mask); + + ptr = kcalloc(reg_count * num_xcc, sizeof(uint32_t), GFP_KERNEL); + if (!ptr) { + DRM_ERROR("Failed to allocate memory for GFX IP Dump\n"); + adev->gfx.ip_dump_core = NULL; + } else { + adev->gfx.ip_dump_core = ptr; + } + + /* Allocate memory for compute queue registers for all the instances */ + reg_count = ARRAY_SIZE(gc_cp_reg_list_12_1); + inst = adev->gfx.mec.num_mec * adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + + ptr = kcalloc(reg_count * inst * num_xcc, sizeof(uint32_t), GFP_KERNEL); + if (!ptr) { + DRM_ERROR("Failed to allocate memory for Compute Queues IP Dump\n"); + adev->gfx.ip_dump_compute_queues = NULL; + } else { + adev->gfx.ip_dump_compute_queues = ptr; + } +} + +static void gfx_v12_1_ip_print(struct amdgpu_ip_block *ip_block, + struct drm_printer *p) +{ + struct amdgpu_device *adev = ip_block->adev; + uint32_t i, j, k; + uint32_t xcc_id, xcc_offset, inst_offset; + uint32_t num_xcc, reg, num_inst; + uint32_t reg_count = ARRAY_SIZE(gc_reg_list_12_1); + + if (!adev->gfx.ip_dump_core) + return; + + num_xcc = NUM_XCC(adev->gfx.xcc_mask); + drm_printf(p, "Number of Instances:%d\n", num_xcc); + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count; + drm_printf(p, "\nInstance id:%d\n", xcc_id); + for (i = 0; i < reg_count; i++) + drm_printf(p, "%-50s \t 0x%08x\n", + gc_reg_list_12_1[i].reg_name, + adev->gfx.ip_dump_core[xcc_offset + i]); + } + + /* print compute queue registers for all instances */ + if (!adev->gfx.ip_dump_compute_queues) + return; + + reg_count = ARRAY_SIZE(gc_cp_reg_list_12_1); + drm_printf(p, "\nnum_xcc: %d num_mec: %d num_pipe: %d num_queue: %d\n", + num_xcc, + adev->gfx.mec.num_mec, + adev->gfx.mec.num_pipe_per_mec, + adev->gfx.mec.num_queue_per_pipe); + + num_inst = adev->gfx.mec.num_mec * adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count * num_inst; + inst_offset = 0; + for (i = 0; i < adev->gfx.mec.num_mec; i++) { + for (j = 0; j < adev->gfx.mec.num_pipe_per_mec; j++) { + for (k = 0; k < adev->gfx.mec.num_queue_per_pipe; k++) { + drm_printf(p, + "\nxcc:%d mec:%d, pipe:%d, queue:%d\n", + xcc_id, i, j, k); + for (reg = 0; reg < reg_count; reg++) { + drm_printf(p, + "%-50s \t 0x%08x\n", + gc_cp_reg_list_12_1[reg].reg_name, + adev->gfx.ip_dump_compute_queues + [xcc_offset + inst_offset + + reg]); + } + inst_offset += reg_count; + } + } + } + } +} + +static void gfx_v12_1_ip_dump(struct amdgpu_ip_block *ip_block) +{ + struct amdgpu_device *adev = ip_block->adev; + uint32_t i, j, k; + uint32_t num_xcc, reg, num_inst; + uint32_t xcc_id, xcc_offset, inst_offset; + uint32_t reg_count = ARRAY_SIZE(gc_reg_list_12_1); + + if (!adev->gfx.ip_dump_core) + return; + + num_xcc = NUM_XCC(adev->gfx.xcc_mask); + + amdgpu_gfx_off_ctrl(adev, false); + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count; + for (i = 0; i < reg_count; i++) + adev->gfx.ip_dump_core[xcc_offset + i] = + RREG32(SOC15_REG_ENTRY_OFFSET_INST(gc_reg_list_12_1[i], + GET_INST(GC, xcc_id))); + } + amdgpu_gfx_off_ctrl(adev, true); + + /* dump compute queue registers for all instances */ + if (!adev->gfx.ip_dump_compute_queues) + return; + + num_inst = adev->gfx.mec.num_mec * adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + reg_count = ARRAY_SIZE(gc_cp_reg_list_12_1); + amdgpu_gfx_off_ctrl(adev, false); + mutex_lock(&adev->srbm_mutex); + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count * num_inst; + inst_offset = 0; + for (i = 0; i < adev->gfx.mec.num_mec; i++) { + for (j = 0; j < adev->gfx.mec.num_pipe_per_mec; j++) { + for (k = 0; k < adev->gfx.mec.num_queue_per_pipe; k++) { + /* ME0 is for GFX so start from 1 for CP */ + soc_v1_0_grbm_select(adev, 1 + i, j, k, 0, + GET_INST(GC, xcc_id)); + + for (reg = 0; reg < reg_count; reg++) { + adev->gfx.ip_dump_compute_queues + [xcc_offset + + inst_offset + reg] = + RREG32(SOC15_REG_ENTRY_OFFSET_INST( + gc_cp_reg_list_12_1[reg], + GET_INST(GC, xcc_id))); + } + inst_offset += reg_count; + } + } + } + } + soc_v1_0_grbm_select(adev, 0, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + amdgpu_gfx_off_ctrl(adev, true); +} + static int gfx_v12_1_sw_init(struct amdgpu_ip_block *ip_block) { uint16_t major_ver, minor_ver; @@ -1287,6 +1558,8 @@ static int gfx_v12_1_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; + gfx_v12_1_alloc_ip_dump(adev); + mutex_init(&adev->gfx.mec.reset_mutex); return 0; @@ -1326,6 +1599,9 @@ static int gfx_v12_1_sw_fini(struct amdgpu_ip_block *ip_block) gfx_v12_1_free_microcode(adev); amdgpu_gfx_sysfs_fini(adev); + kfree(adev->gfx.ip_dump_core); + kfree(adev->gfx.ip_dump_compute_queues); + return 0; } @@ -3398,13 +3674,16 @@ static u64 gfx_v12_1_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v12_1_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -3418,7 +3697,7 @@ static void gfx_v12_1_ring_set_wptr_compute(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx12 now */ + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -3912,6 +4191,8 @@ static const struct amd_ip_funcs gfx_v12_1_ip_funcs = { .set_clockgating_state = gfx_v12_1_set_clockgating_state, .set_powergating_state = gfx_v12_1_set_powergating_state, .get_clockgating_state = gfx_v12_1_get_clockgating_state, + .dump_ip_state = gfx_v12_1_ip_dump, + .print_ip_state = gfx_v12_1_ip_print, }; static const struct amdgpu_ring_funcs gfx_v12_1_ring_funcs_compute = { diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c index ac90d8e9d86a..cd2a14ecdba8 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c @@ -347,7 +347,8 @@ static int gfx_v6_0_init_microcode(struct amdgpu_device *adev) case CHIP_HAINAN: chip_name = "hainan"; break; - default: BUG(); + default: + return -EINVAL; } err = amdgpu_ucode_request(adev, &adev->gfx.pfp_fw, @@ -1736,8 +1737,8 @@ static void gfx_v6_0_constants_init(struct amdgpu_device *adev) gb_addr_config = HAINAN_GB_ADDR_CONFIG_GOLDEN; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported asic_type 0x%08x\n", adev->asic_type); + return; } WREG32(mmGRBM_CNTL, (0xff << GRBM_CNTL__READ_TIMEOUT__SHIFT)); @@ -2202,7 +2203,7 @@ static u64 gfx_v6_0_ring_get_wptr(struct amdgpu_ring *ring) else if (ring == &adev->gfx.compute_ring[1]) return RREG32(mmCP_RB2_WPTR); else - BUG(); + return 0; } static void gfx_v6_0_ring_set_wptr_gfx(struct amdgpu_ring *ring) @@ -2223,8 +2224,6 @@ static void gfx_v6_0_ring_set_wptr_compute(struct amdgpu_ring *ring) } else if (ring == &adev->gfx.compute_ring[1]) { WREG32(mmCP_RB2_WPTR, lower_32_bits(ring->wptr)); (void)RREG32(mmCP_RB2_WPTR); - } else { - BUG(); } } @@ -3310,7 +3309,6 @@ static void gfx_v6_0_set_compute_eop_interrupt_state(struct amdgpu_device *adev, } default: - BUG(); break; } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c index 65b8497ad5f0..5fc19fefca81 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c @@ -933,7 +933,8 @@ static int gfx_v7_0_init_microcode(struct amdgpu_device *adev) chip_name = "mullins"; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported asic_type 0x%08x\n", adev->asic_type); + return -EINVAL; } err = amdgpu_ucode_request(adev, &adev->gfx.pfp_fw, @@ -2201,12 +2202,6 @@ static void gfx_v7_0_ring_emit_ib_gfx(struct amdgpu_ring *ring, unsigned vmid = AMDGPU_JOB_GET_VMID(job); u32 header, control = 0; - /* insert SWITCH_BUFFER packet before first IB in the ring frame */ - if (flags & AMDGPU_HAVE_CTX_SWITCH) { - amdgpu_ring_write(ring, PACKET3(PACKET3_SWITCH_BUFFER, 0)); - amdgpu_ring_write(ring, 0); - } - if (ib->flags & AMDGPU_IB_FLAG_CE) header = PACKET3(PACKET3_INDIRECT_BUFFER_CONST, 2); else @@ -2258,6 +2253,12 @@ static void gfx_v7_0_ring_emit_ib_compute(struct amdgpu_ring *ring, amdgpu_ring_write(ring, control); } +static void gfx_v7_0_ring_emit_sb(struct amdgpu_ring *ring) +{ + amdgpu_ring_write(ring, PACKET3(PACKET3_SWITCH_BUFFER, 0)); + amdgpu_ring_write(ring, 0); +} + static void gfx_v7_ring_emit_cntxcntl(struct amdgpu_ring *ring, uint32_t flags) { uint32_t dw2 = 0; @@ -2546,8 +2547,12 @@ static int gfx_v7_0_cp_gfx_resume(struct amdgpu_device *adev) WREG32(mmSCRATCH_ADDR, 0); /* ring 0 - compute and gfx */ - /* Set ring buffer size */ ring = &adev->gfx.gfx_ring[0]; + *ring->wptr_cpu_addr = 0; + *ring->rptr_cpu_addr = 0; + amdgpu_ring_clear_ring(ring); + + /* Set ring buffer size */ rb_bufsz = order_base_2(ring->ring_size / 8); tmp = (order_base_2(AMDGPU_GPU_PAGE_SIZE/8) << 8) | rb_bufsz; #ifdef __BIG_ENDIAN @@ -2559,6 +2564,7 @@ static int gfx_v7_0_cp_gfx_resume(struct amdgpu_device *adev) WREG32(mmCP_RB0_CNTL, tmp | CP_RB0_CNTL__RB_RPTR_WR_ENA_MASK); ring->wptr = 0; WREG32(mmCP_RB0_WPTR, lower_32_bits(ring->wptr)); + WREG32(mmCP_RB0_RPTR, lower_32_bits(ring->wptr)); /* set the wb address whether it's enabled or not */ rptr_addr = ring->rptr_gpu_addr; @@ -2576,7 +2582,10 @@ static int gfx_v7_0_cp_gfx_resume(struct amdgpu_device *adev) WREG32(mmCP_RB0_BASE_HI, upper_32_bits(rb_addr)); /* start the ring */ - gfx_v7_0_cp_gfx_start(adev); + r = gfx_v7_0_cp_gfx_start(adev); + if (r) + return r; + r = amdgpu_ring_test_helper(ring); if (r) return r; @@ -2698,25 +2707,6 @@ static int gfx_v7_0_cp_compute_load_microcode(struct amdgpu_device *adev) return 0; } -/** - * gfx_v7_0_cp_compute_fini - stop the compute queues - * - * @adev: amdgpu_device pointer - * - * Stop the compute queues and tear down the driver queue - * info. - */ -static void gfx_v7_0_cp_compute_fini(struct amdgpu_device *adev) -{ - int i; - - for (i = 0; i < adev->gfx.num_compute_rings; i++) { - struct amdgpu_ring *ring = &adev->gfx.compute_ring[i]; - - amdgpu_bo_free_kernel(&ring->mqd_obj, NULL, NULL); - } -} - static void gfx_v7_0_mec_fini(struct amdgpu_device *adev) { amdgpu_bo_free_kernel(&adev->gfx.mec.hpd_eop_obj, NULL, NULL); @@ -2788,28 +2778,29 @@ static void gfx_v7_0_compute_pipe_init(struct amdgpu_device *adev, mutex_unlock(&adev->srbm_mutex); } -static int gfx_v7_0_mqd_deactivate(struct amdgpu_device *adev) +static int gfx_v7_0_deactivate_hqd(struct amdgpu_device *adev, u32 req) { - int i; + int i, r = 0; /* disable the queue if it's active */ - if (RREG32(mmCP_HQD_ACTIVE) & 1) { - WREG32(mmCP_HQD_DEQUEUE_REQUEST, 1); + if (RREG32(mmCP_HQD_ACTIVE) & CP_HQD_ACTIVE__ACTIVE_MASK) { + WREG32_FIELD(CP_HQD_DEQUEUE_REQUEST, DEQUEUE_REQ, req); for (i = 0; i < adev->usec_timeout; i++) { - if (!(RREG32(mmCP_HQD_ACTIVE) & 1)) + if (!(RREG32(mmCP_HQD_ACTIVE) & CP_HQD_ACTIVE__ACTIVE_MASK)) break; udelay(1); } if (i == adev->usec_timeout) - return -ETIMEDOUT; + r = -ETIMEDOUT; - WREG32(mmCP_HQD_DEQUEUE_REQUEST, 0); - WREG32(mmCP_HQD_PQ_RPTR, 0); - WREG32(mmCP_HQD_PQ_WPTR, 0); } - return 0; + WREG32(mmCP_HQD_DEQUEUE_REQUEST, 0); + WREG32(mmCP_HQD_PQ_RPTR, 0); + WREG32(mmCP_HQD_PQ_WPTR, 0); + + return r; } static void gfx_v7_0_mqd_init(struct amdgpu_device *adev, @@ -2964,31 +2955,42 @@ static int gfx_v7_0_mqd_commit(struct amdgpu_device *adev, struct cik_mqd *mqd) static int gfx_v7_0_compute_queue_init(struct amdgpu_device *adev, int ring_id) { - int r; - u64 mqd_gpu_addr; - struct cik_mqd *mqd; struct amdgpu_ring *ring = &adev->gfx.compute_ring[ring_id]; + struct cik_mqd *mqd = ring->mqd_ptr; + int mqd_idx = ring - &adev->gfx.compute_ring[0]; - r = amdgpu_bo_create_reserved(adev, sizeof(struct cik_mqd), PAGE_SIZE, - AMDGPU_GEM_DOMAIN_GTT, &ring->mqd_obj, - &mqd_gpu_addr, (void **)&mqd); - if (r) { - dev_warn(adev->dev, "(%d) create MQD bo failed\n", r); - return r; + if (!amdgpu_in_reset(adev) && !adev->in_suspend) { + memset((void *)mqd, 0, ring->mqd_size); + mutex_lock(&adev->srbm_mutex); + cik_srbm_select(adev, ring->me, ring->pipe, ring->queue, 0); + gfx_v7_0_mqd_init(adev, mqd, ring->mqd_gpu_addr, ring); + gfx_v7_0_deactivate_hqd(adev, 1); + gfx_v7_0_mqd_commit(adev, mqd); + cik_srbm_select(adev, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + + if (adev->gfx.mec.mqd_backup[mqd_idx]) + memcpy(adev->gfx.mec.mqd_backup[mqd_idx], mqd, ring->mqd_size); + } else { + /* restore MQD to a clean status */ + if (adev->gfx.mec.mqd_backup[mqd_idx]) + memcpy(mqd, adev->gfx.mec.mqd_backup[mqd_idx], ring->mqd_size); + + /* Re-commit the restored backup */ + mutex_lock(&adev->srbm_mutex); + cik_srbm_select(adev, ring->me, ring->pipe, ring->queue, 0); + gfx_v7_0_deactivate_hqd(adev, 2); + gfx_v7_0_mqd_commit(adev, mqd); + cik_srbm_select(adev, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + + /* reset ring buffer */ + ring->wptr = 0; + atomic64_set((atomic64_t *)ring->wptr_cpu_addr, 0); + atomic64_set((atomic64_t *)ring->rptr_cpu_addr, 0); + amdgpu_ring_clear_ring(ring); } - mutex_lock(&adev->srbm_mutex); - cik_srbm_select(adev, ring->me, ring->pipe, ring->queue, 0); - - gfx_v7_0_mqd_init(adev, mqd, mqd_gpu_addr, ring); - gfx_v7_0_mqd_deactivate(adev); - gfx_v7_0_mqd_commit(adev, mqd); - - cik_srbm_select(adev, 0, 0, 0, 0); - mutex_unlock(&adev->srbm_mutex); - - amdgpu_bo_kunmap(ring->mqd_obj); - amdgpu_bo_unreserve(ring->mqd_obj); return 0; } @@ -3020,20 +3022,20 @@ static int gfx_v7_0_cp_compute_resume(struct amdgpu_device *adev) /* init the queues */ for (i = 0; i < adev->gfx.num_compute_rings; i++) { r = gfx_v7_0_compute_queue_init(adev, i); - if (r) { - gfx_v7_0_cp_compute_fini(adev); + if (r) return r; - } } gfx_v7_0_cp_compute_enable(adev, true); + r = 0; + for (i = 0; i < adev->gfx.num_compute_rings; i++) { ring = &adev->gfx.compute_ring[i]; - amdgpu_ring_test_helper(ring); + r |= amdgpu_ring_test_helper(ring); } - return 0; + return r; } static void gfx_v7_0_cp_enable(struct amdgpu_device *adev, bool enable) @@ -3115,14 +3117,6 @@ static void gfx_v7_0_ring_emit_pipeline_sync(struct amdgpu_ring *ring) amdgpu_ring_write(ring, seq); amdgpu_ring_write(ring, 0xffffffff); amdgpu_ring_write(ring, 4); /* poll interval */ - - if (usepfp) { - /* sync CE with ME to prevent CE fetch CEIB before context switch done */ - amdgpu_ring_write(ring, PACKET3(PACKET3_SWITCH_BUFFER, 0)); - amdgpu_ring_write(ring, 0); - amdgpu_ring_write(ring, PACKET3(PACKET3_SWITCH_BUFFER, 0)); - amdgpu_ring_write(ring, 0); - } } /* @@ -3164,15 +3158,25 @@ static void gfx_v7_0_ring_emit_vm_flush(struct amdgpu_ring *ring, /* sync PFP to ME, otherwise we might get invalid PFP reads */ amdgpu_ring_write(ring, PACKET3(PACKET3_PFP_SYNC_ME, 0)); amdgpu_ring_write(ring, 0x0); - - /* synce CE with ME to prevent CE fetch CEIB before context switch done */ - amdgpu_ring_write(ring, PACKET3(PACKET3_SWITCH_BUFFER, 0)); - amdgpu_ring_write(ring, 0); - amdgpu_ring_write(ring, PACKET3(PACKET3_SWITCH_BUFFER, 0)); - amdgpu_ring_write(ring, 0); } } +static unsigned int gfx_v7_0_ring_emit_init_cond_exec(struct amdgpu_ring *ring, + uint64_t gpu_addr) +{ + unsigned int ret; + + /* Discard following DWs after this packet when gpu_addr==0 */ + amdgpu_ring_write(ring, PACKET3(PACKET3_COND_EXEC, 3)); + amdgpu_ring_write(ring, lower_32_bits(gpu_addr)); + amdgpu_ring_write(ring, upper_32_bits(gpu_addr)); + amdgpu_ring_write(ring, 0); + ret = ring->wptr & ring->buf_mask; + /* patch dummy value later */ + amdgpu_ring_write(ring, 0); + return ret; +} + static void gfx_v7_0_ring_emit_wreg(struct amdgpu_ring *ring, uint32_t reg, uint32_t val) { @@ -3613,21 +3617,6 @@ static void gfx_v7_0_enable_mgcg(struct amdgpu_device *adev, bool enable) } } -static void gfx_v7_0_update_cg(struct amdgpu_device *adev, - bool enable) -{ - gfx_v7_0_enable_gui_idle_interrupt(adev, false); - /* order matters! */ - if (enable) { - gfx_v7_0_enable_mgcg(adev, true); - gfx_v7_0_enable_cgcg(adev, true); - } else { - gfx_v7_0_enable_cgcg(adev, false); - gfx_v7_0_enable_mgcg(adev, false); - } - gfx_v7_0_enable_gui_idle_interrupt(adev, true); -} - static void gfx_v7_0_enable_sclk_slowdown_on_pu(struct amdgpu_device *adev, bool enable) { @@ -4430,6 +4419,11 @@ static int gfx_v7_0_sw_init(struct amdgpu_ip_block *ip_block) } } + /* create MQD for all compute queues */ + r = amdgpu_gfx_mqd_sw_init(adev, sizeof(struct cik_mqd), 0); + if (r) + return r; + adev->gfx.ce_ram_size = 0x8000; gfx_v7_0_gpu_early_init(adev); @@ -4439,6 +4433,11 @@ static int gfx_v7_0_sw_init(struct amdgpu_ip_block *ip_block) adev->gfx.compute_supported_reset = amdgpu_get_soft_full_reset_mask(&adev->gfx.compute_ring[0]); + if (!amdgpu_sriov_vf(adev) && !adev->debug_disable_ip_block_soft_reset) { + adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_IP_BLOCK_SOFT_RESET; + adev->gfx.gfx_supported_reset |= AMDGPU_RESET_TYPE_IP_BLOCK_SOFT_RESET; + } + return r; } @@ -4452,7 +4451,7 @@ static int gfx_v7_0_sw_fini(struct amdgpu_ip_block *ip_block) for (i = 0; i < adev->gfx.num_compute_rings; i++) amdgpu_ring_fini(&adev->gfx.compute_ring[i]); - gfx_v7_0_cp_compute_fini(adev); + amdgpu_gfx_mqd_sw_fini(adev, 0); amdgpu_gfx_rlc_fini(adev); gfx_v7_0_mec_fini(adev); amdgpu_bo_free_kernel(&adev->gfx.rlc.clear_state_obj, @@ -4541,80 +4540,99 @@ static int gfx_v7_0_wait_for_idle(struct amdgpu_ip_block *ip_block) static int gfx_v7_0_soft_reset(struct amdgpu_ip_block *ip_block) { + struct amdgpu_device *adev = ip_block->adev; u32 grbm_soft_reset = 0, srbm_soft_reset = 0; u32 tmp; - struct amdgpu_device *adev = ip_block->adev; + int i; + int r; - /* GRBM_STATUS */ - tmp = RREG32(mmGRBM_STATUS); - if (tmp & (GRBM_STATUS__PA_BUSY_MASK | GRBM_STATUS__SC_BUSY_MASK | - GRBM_STATUS__BCI_BUSY_MASK | GRBM_STATUS__SX_BUSY_MASK | - GRBM_STATUS__TA_BUSY_MASK | GRBM_STATUS__VGT_BUSY_MASK | - GRBM_STATUS__DB_BUSY_MASK | GRBM_STATUS__CB_BUSY_MASK | - GRBM_STATUS__GDS_BUSY_MASK | GRBM_STATUS__SPI_BUSY_MASK | - GRBM_STATUS__IA_BUSY_MASK | GRBM_STATUS__IA_BUSY_NO_DMA_MASK)) - grbm_soft_reset |= GRBM_SOFT_RESET__SOFT_RESET_CP_MASK | - GRBM_SOFT_RESET__SOFT_RESET_GFX_MASK; + grbm_soft_reset = + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_RLC, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_GFX, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_CP, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_CPF, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_CPC, 1) | + REG_SET_FIELD(0, GRBM_SOFT_RESET, SOFT_RESET_CPG, 1); - if (tmp & (GRBM_STATUS__CP_BUSY_MASK | GRBM_STATUS__CP_COHERENCY_BUSY_MASK)) { - grbm_soft_reset |= GRBM_SOFT_RESET__SOFT_RESET_CP_MASK; - srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_GRBM_MASK; - } + srbm_soft_reset = + REG_SET_FIELD(0, SRBM_SOFT_RESET, SOFT_RESET_GRBM, 1) | + REG_SET_FIELD(0, SRBM_SOFT_RESET, SOFT_RESET_SEM, 1); - /* GRBM_STATUS2 */ - tmp = RREG32(mmGRBM_STATUS2); - if (tmp & GRBM_STATUS2__RLC_BUSY_MASK) - grbm_soft_reset |= GRBM_SOFT_RESET__SOFT_RESET_RLC_MASK; + for (i = 0; i < adev->gfx.num_compute_rings; i++) { + struct amdgpu_ring *ring = &adev->gfx.compute_ring[i]; - /* SRBM_STATUS */ - tmp = RREG32(mmSRBM_STATUS); - if (tmp & SRBM_STATUS__GRBM_RQ_PENDING_MASK) - srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_GRBM_MASK; + mutex_lock(&adev->srbm_mutex); + cik_srbm_select(adev, ring->me, ring->pipe, ring->queue, 0); + gfx_v7_0_deactivate_hqd(adev, 2); + cik_srbm_select(adev, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); - if (grbm_soft_reset || srbm_soft_reset) { - /* disable CG/PG */ - gfx_v7_0_fini_pg(adev); - gfx_v7_0_update_cg(adev, false); - - /* stop the rlc */ - adev->gfx.rlc.funcs->stop(adev); - - /* Disable GFX parsing/prefetching */ - WREG32(mmCP_ME_CNTL, CP_ME_CNTL__ME_HALT_MASK | CP_ME_CNTL__PFP_HALT_MASK | CP_ME_CNTL__CE_HALT_MASK); - - /* Disable MEC parsing/prefetching */ - WREG32(mmCP_MEC_CNTL, CP_MEC_CNTL__MEC_ME1_HALT_MASK | CP_MEC_CNTL__MEC_ME2_HALT_MASK); - - if (grbm_soft_reset) { - tmp = RREG32(mmGRBM_SOFT_RESET); - tmp |= grbm_soft_reset; - dev_info(adev->dev, "GRBM_SOFT_RESET=0x%08X\n", tmp); - WREG32(mmGRBM_SOFT_RESET, tmp); - tmp = RREG32(mmGRBM_SOFT_RESET); - - udelay(50); - - tmp &= ~grbm_soft_reset; - WREG32(mmGRBM_SOFT_RESET, tmp); - tmp = RREG32(mmGRBM_SOFT_RESET); - } - - if (srbm_soft_reset) { - tmp = RREG32(mmSRBM_SOFT_RESET); - tmp |= srbm_soft_reset; - dev_info(adev->dev, "SRBM_SOFT_RESET=0x%08X\n", tmp); - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - - udelay(50); - - tmp &= ~srbm_soft_reset; - WREG32(mmSRBM_SOFT_RESET, tmp); - tmp = RREG32(mmSRBM_SOFT_RESET); - } - /* Wait a little for things to settle down */ udelay(50); } + + ip_block->version->funcs->set_clockgating_state(ip_block, AMD_CG_STATE_UNGATE); + ip_block->version->funcs->set_powergating_state(ip_block, AMD_PG_STATE_UNGATE); + ip_block->version->funcs->suspend(ip_block); + + if (grbm_soft_reset || srbm_soft_reset) { + tmp = RREG32(mmGMCON_DEBUG); + tmp = REG_SET_FIELD(tmp, GMCON_DEBUG, GFX_STALL, 1); + tmp = REG_SET_FIELD(tmp, GMCON_DEBUG, GFX_CLEAR, 1); + WREG32(mmGMCON_DEBUG, tmp); + + udelay(100); + } + + if (grbm_soft_reset) { + tmp = RREG32(mmGRBM_SOFT_RESET); + tmp |= grbm_soft_reset; + dev_info(adev->dev, "GRBM_SOFT_RESET=0x%08X\n", tmp); + WREG32(mmGRBM_SOFT_RESET, tmp); + tmp = RREG32(mmGRBM_SOFT_RESET); + + udelay(100); + + tmp &= ~grbm_soft_reset; + WREG32(mmGRBM_SOFT_RESET, tmp); + tmp = RREG32(mmGRBM_SOFT_RESET); + + udelay(100); + } + + if (srbm_soft_reset) { + tmp = RREG32(mmSRBM_SOFT_RESET); + tmp |= srbm_soft_reset; + dev_info(adev->dev, "SRBM_SOFT_RESET=0x%08X\n", tmp); + WREG32(mmSRBM_SOFT_RESET, tmp); + tmp = RREG32(mmSRBM_SOFT_RESET); + + udelay(100); + + tmp &= ~srbm_soft_reset; + WREG32(mmSRBM_SOFT_RESET, tmp); + tmp = RREG32(mmSRBM_SOFT_RESET); + + udelay(100); + } + + if (grbm_soft_reset || srbm_soft_reset) { + tmp = RREG32(mmGMCON_DEBUG); + tmp = REG_SET_FIELD(tmp, GMCON_DEBUG, GFX_STALL, 0); + tmp = REG_SET_FIELD(tmp, GMCON_DEBUG, GFX_CLEAR, 0); + WREG32(mmGMCON_DEBUG, tmp); + } + + /* Wait a little for things to settle down */ + udelay(100); + + r = ip_block->version->funcs->resume(ip_block); + r |= ip_block->version->funcs->late_init(ip_block); + if (r) + return r; + + ip_block->version->funcs->set_clockgating_state(ip_block, AMD_CG_STATE_GATE); + ip_block->version->funcs->set_powergating_state(ip_block, AMD_PG_STATE_GATE); + return 0; } @@ -4949,12 +4967,15 @@ static const struct amdgpu_ring_funcs gfx_v7_0_ring_funcs_gfx = { .get_wptr = gfx_v7_0_ring_get_wptr_gfx, .set_wptr = gfx_v7_0_ring_set_wptr_gfx, .emit_frame_size = + 5 + /* gfx_v7_0_ring_emit_init_cond_exec (from amdgpu_ib_schedule) */ + 5 + /* gfx_v7_0_ring_emit_init_cond_exec (from amdgpu_vm_flush) */ 20 + /* gfx_v7_0_ring_emit_gds_switch */ 7 + /* gfx_v7_0_ring_emit_hdp_flush */ 5 + /* hdp invalidate */ 12 + 12 + 12 + /* gfx_v7_0_ring_emit_fence_gfx x3 for user fence, vm fence */ - 7 + 4 + /* gfx_v7_0_ring_emit_pipeline_sync */ - CIK_FLUSH_GPU_TLB_NUM_WREG * 5 + 7 + 6 + /* gfx_v7_0_ring_emit_vm_flush */ + 7 + /* gfx_v7_0_ring_emit_pipeline_sync */ + CIK_FLUSH_GPU_TLB_NUM_WREG * 5 + 7 + 2 + /* gfx_v7_0_ring_emit_vm_flush */ + 3 * 2 + /* gfx_v7_0_ring_emit_sb x3 (from amdgpu_vm_flush, amdgpu_ib_schedule) */ 3 + 4 + /* gfx_v7_ring_emit_cntxcntl including vgt flush*/ 5, /* SURFACE_SYNC */ .emit_ib_size = 4, /* gfx_v7_0_ring_emit_ib_gfx */ @@ -4968,7 +4989,9 @@ static const struct amdgpu_ring_funcs gfx_v7_0_ring_funcs_gfx = { .test_ib = gfx_v7_0_ring_test_ib, .insert_nop = amdgpu_ring_insert_nop, .pad_ib = amdgpu_ring_generic_pad_ib, + .emit_switch_buffer = gfx_v7_0_ring_emit_sb, .emit_cntxcntl = gfx_v7_ring_emit_cntxcntl, + .init_cond_exec = gfx_v7_0_ring_emit_init_cond_exec, .emit_wreg = gfx_v7_0_ring_emit_wreg, .soft_recovery = gfx_v7_0_ring_soft_recovery, .emit_mem_sync = gfx_v7_0_emit_mem_sync, @@ -4983,6 +5006,8 @@ static const struct amdgpu_ring_funcs gfx_v7_0_ring_funcs_compute = { .get_wptr = gfx_v7_0_ring_get_wptr_compute, .set_wptr = gfx_v7_0_ring_set_wptr_compute, .emit_frame_size = + 5 + /* gfx_v7_0_ring_emit_init_cond_exec (from amdgpu_ib_schedule) */ + 5 + /* gfx_v7_0_ring_emit_init_cond_exec (from amdgpu_vm_flush) */ 20 + /* gfx_v7_0_ring_emit_gds_switch */ 7 + /* gfx_v7_0_ring_emit_hdp_flush */ 5 + /* hdp invalidate */ @@ -5001,6 +5026,7 @@ static const struct amdgpu_ring_funcs gfx_v7_0_ring_funcs_compute = { .test_ib = gfx_v7_0_ring_test_ib, .insert_nop = amdgpu_ring_insert_nop, .pad_ib = amdgpu_ring_generic_pad_ib, + .init_cond_exec = gfx_v7_0_ring_emit_init_cond_exec, .emit_wreg = gfx_v7_0_ring_emit_wreg, .soft_recovery = gfx_v7_0_ring_soft_recovery, .emit_mem_sync = gfx_v7_0_emit_mem_sync_compute, diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 9e0840df8849..6cf427995078 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -977,7 +977,8 @@ static int gfx_v8_0_init_microcode(struct amdgpu_device *adev) chip_name = "vegam"; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported asic_type 0x%08x\n", adev->asic_type); + return -EINVAL; } if (adev->asic_type >= CHIP_POLARIS10 && adev->asic_type <= CHIP_POLARIS12) { diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index 556e5cb3f4cd..c3322058720f 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c @@ -2107,8 +2107,9 @@ static int gfx_v9_0_gpu_early_init(struct amdgpu_device *adev) return err; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return -EINVAL; } adev->gfx.config.gb_addr_config = gb_addr_config; @@ -2442,12 +2443,12 @@ static int gfx_v9_0_sw_init(struct amdgpu_ip_block *ip_block) return -EINVAL; } - gfx_v9_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v9_0_alloc_ip_dump(adev); + return 0; } @@ -5681,13 +5682,16 @@ static u64 gfx_v9_0_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v9_0_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -5699,8 +5703,8 @@ static void gfx_v9_0_ring_set_wptr_compute(struct amdgpu_ring *ring) if (ring->use_doorbell) { atomic64_set((atomic64_t *)ring->wptr_cpu_addr, ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); - } else{ - BUG(); /* only DOORBELL method supported on gfx9 now */ + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c index 44d38b76cb4f..522981fa182d 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -2331,7 +2331,7 @@ err_compute: } } for (xcc_id--; xcc_id >= 0; xcc_id--) { - for (m = adev->gfx.mec.num_mec - 1; m <= 0; m--) { + for (m = adev->gfx.mec.num_mec - 1; m >= 0; m--) { for (p = adev->gfx.mec.num_pipe_per_mec - 1; p >= 0; p--) { irq_type = AMDGPU_CP_IRQ_COMPUTE_MEC1_PIPE0_EOP + (m * adev->gfx.mec.num_pipe_per_mec) + p; @@ -2400,7 +2400,10 @@ static int gfx_v9_4_3_perf_monitor_ptl_init(struct amdgpu_device *adev, bool ena if (!adev->psp.funcs) return -EOPNOTSUPP; - if (!ptl->hw_supported) { + if (ptl->hw_supported_state == AMDGPU_PTL_HW_NOT_SUPPORTED) + return -EOPNOTSUPP; + + if (ptl->hw_supported_state == AMDGPU_PTL_HW_UNINIT) { fmt1 = GFX_FTYPE_VECTOR; fmt2 = GFX_FTYPE_F8; } else { @@ -2411,10 +2414,17 @@ static int gfx_v9_4_3_perf_monitor_ptl_init(struct amdgpu_device *adev, bool ena /* initialize PTL with default formats: GFX_FTYPE_VECTOR & GFX_FTYPE_F8 */ r = amdgpu_ptl_perf_monitor_ctrl(adev, PSP_PTL_PERF_MON_SET, &ptl_state, &fmt1, &fmt2); - if (r) - return r; + if (r) { + if (ptl->hw_supported_state == AMDGPU_PTL_HW_UNINIT) + ptl->hw_supported_state = AMDGPU_PTL_HW_NOT_SUPPORTED; - ptl->hw_supported = true; + if (r != -EOPNOTSUPP) + dev_err(adev->dev, "PTL initialization failed (%d)\n", r); + + return r; + } + + ptl->hw_supported_state = AMDGPU_PTL_HW_SUPPORTED; atomic_set(&ptl->disable_ref, 0); if (!enable && !amdgpu_in_reset(adev) && !adev->in_suspend) { @@ -2459,7 +2469,8 @@ static int gfx_v9_4_3_hw_fini(struct amdgpu_ip_block *ip_block) struct amdgpu_device *adev = ip_block->adev; int i, num_xcc; - if (adev->psp.ptl.hw_supported && !amdgpu_in_reset(adev)) + if (adev->psp.ptl.hw_supported_state == AMDGPU_PTL_HW_SUPPORTED && + !amdgpu_in_reset(adev)) gfx_v9_4_3_perf_monitor_ptl_init(adev, false); amdgpu_irq_put(adev, &adev->gfx.bad_op_irq, 0); @@ -3029,13 +3040,17 @@ static u64 gfx_v9_4_3_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v9_4_3_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)&ring->adev->wb.wb[ring->wptr_offs]); - else - BUG(); + } else { + dev_warn_once(adev->dev, + "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -3048,7 +3063,8 @@ static void gfx_v9_4_3_ring_set_wptr_compute(struct amdgpu_ring *ring) atomic64_set((atomic64_t *)&adev->wb.wb[ring->wptr_offs], ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx9 now */ + dev_warn_once(adev->dev, + "%s requires doorbell!\n", __func__); } } diff --git a/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c b/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c index 05b164f38c97..09dc01a425d1 100644 --- a/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c @@ -377,8 +377,9 @@ static void imu_v11_0_program_rlc_ram(struct amdgpu_device *adev) imu_v11_0_3_program_rlc_ram(adev); break; default: - BUG(); - break; + WARN(1, "Invalid GFX/IMU IP version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return; } //Indicate the contents of the RAM are valid diff --git a/drivers/gpu/drm/amd/amdgpu/imu_v12_0.c b/drivers/gpu/drm/amd/amdgpu/imu_v12_0.c index 58cd87db8061..4256225f2495 100644 --- a/drivers/gpu/drm/amd/amdgpu/imu_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/imu_v12_0.c @@ -382,8 +382,9 @@ static void imu_v12_0_program_rlc_ram(struct amdgpu_device *adev) (const u32)ARRAY_SIZE(imu_rlc_ram_golden_12_0_1)); break; default: - BUG(); - break; + WARN(1, "Invalid GFX/IMU IP version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return; } //Indicate the latest entry diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c index 8846cb3ed12b..db3fbe4eabb9 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c @@ -772,7 +772,7 @@ static int jpeg_v5_0_1_set_ras_interrupt_state(struct amdgpu_device *adev, -static int jpeg_v5_0_1_process_interrupt(struct amdgpu_device *adev, +int jpeg_v5_0_1_process_interrupt(struct amdgpu_device *adev, struct amdgpu_irq_src *source, struct amdgpu_iv_entry *entry) { diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.h b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.h index a7e58d5fb246..67346faecb47 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.h +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.h @@ -108,4 +108,12 @@ enum amdgpu_jpeg_v5_0_1_sub_block { AMDGPU_JPEG_V5_0_1_MAX_SUB_BLOCK, }; +struct amdgpu_irq_src; +struct amdgpu_iv_entry; +struct amdgpu_device; + +int jpeg_v5_0_1_process_interrupt(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + struct amdgpu_iv_entry *entry); + #endif /* __JPEG_V5_0_1_H__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c index ff02f72352a8..ea54a2b6eddb 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c @@ -34,6 +34,8 @@ #include "vcn/vcn_5_0_0_sh_mask.h" #include "ivsrcid/vcn/irqsrcs_vcn_5_0.h" +#include "jpeg_v5_0_1.h" + static void jpeg_v5_0_2_set_dec_ring_funcs(struct amdgpu_device *adev); static void jpeg_v5_0_2_set_irq_funcs(struct amdgpu_device *adev); static int jpeg_v5_0_2_set_powergating_state(struct amdgpu_ip_block *ip_block, @@ -583,65 +585,6 @@ static int jpeg_v5_0_2_set_interrupt_state(struct amdgpu_device *adev, return 0; } -static int jpeg_v5_0_2_process_interrupt(struct amdgpu_device *adev, - struct amdgpu_irq_src *source, - struct amdgpu_iv_entry *entry) -{ - u32 i, inst; - - i = node_id_to_phys_map[entry->node_id]; - DRM_DEV_DEBUG(adev->dev, "IH: JPEG TRAP\n"); - - for (inst = 0; inst < adev->jpeg.num_jpeg_inst; ++inst) - if (adev->jpeg.inst[inst].aid_id == i) - break; - - if (inst >= adev->jpeg.num_jpeg_inst) { - dev_WARN_ONCE(adev->dev, 1, - "Interrupt received for unknown JPEG instance %d", - entry->node_id); - return 0; - } - - switch (entry->src_id) { - case VCN_5_0__SRCID__JPEG_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[0]); - break; - case VCN_5_0__SRCID__JPEG1_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[1]); - break; - case VCN_5_0__SRCID__JPEG2_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[2]); - break; - case VCN_5_0__SRCID__JPEG3_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[3]); - break; - case VCN_5_0__SRCID__JPEG4_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[4]); - break; - case VCN_5_0__SRCID__JPEG5_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[5]); - break; - case VCN_5_0__SRCID__JPEG6_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[6]); - break; - case VCN_5_0__SRCID__JPEG7_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[7]); - break; - case VCN_5_0__SRCID__JPEG8_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[8]); - break; - case VCN_5_0__SRCID__JPEG9_DECODE: - amdgpu_fence_process(&adev->jpeg.inst[inst].ring_dec[9]); - break; - default: - DRM_DEV_ERROR(adev->dev, "Unhandled interrupt: %d %d\n", - entry->src_id, entry->src_data[0]); - break; - } - - return 0; -} static void jpeg_v5_0_2_core_stall_reset(struct amdgpu_ring *ring) { @@ -747,7 +690,7 @@ static void jpeg_v5_0_2_set_dec_ring_funcs(struct amdgpu_device *adev) static const struct amdgpu_irq_src_funcs jpeg_v5_0_2_irq_funcs = { .set = jpeg_v5_0_2_set_interrupt_state, - .process = jpeg_v5_0_2_process_interrupt, + .process = jpeg_v5_0_1_process_interrupt, }; static void jpeg_v5_0_2_set_irq_funcs(struct amdgpu_device *adev) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c index 66d4c487eee5..68152b2ce015 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c @@ -399,6 +399,25 @@ static void jpeg_v5_3_0_stop_dpg_mode(struct amdgpu_device *adev, int inst_idx) WREG32_SOC15(JPEG, inst_idx, regUVD_JPEG_POWER_STATUS, reg_data); } +/** + * jpeg_v5_3_0_set_mmhub_eco_sec_level - set jpeg sec lvl reg + * + * @adev: amdgpu_device pointer + * + * request psp to set secure lvl + */ +static int jpeg_v5_3_0_set_mmhub_eco_sec_level(struct amdgpu_device *adev) +{ + int r = 0; + + if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) { + /* Request to PSP to program JPEG secure lvl */ + r = psp_set_mmhub_eco_sec_level(adev); + } + + return r; +} + /** * jpeg_v5_3_0_start - start JPEG block * @@ -424,6 +443,11 @@ static int jpeg_v5_3_0_start(struct amdgpu_device *adev) if (r) return r; + /* program JPEG secure lvl register */ + r = jpeg_v5_3_0_set_mmhub_eco_sec_level(adev); + if (r) + return r; + /* JPEG disable CGC */ jpeg_v5_3_0_disable_clock_gating(adev); diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index defa050fd871..639e4a37f872 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -70,27 +70,23 @@ mes_userq_create_wptr_mapping(struct amdgpu_device *adev, ret = -EINVAL; goto fail_map; } - - /* TODO use eviction fence instead of pinning. */ - ret = amdgpu_bo_pin(wptr_obj->obj, AMDGPU_GEM_DOMAIN_GTT); + /* Keep WPTR BO under eviction-fence control instead of pinning. */ + ret = amdgpu_evf_mgr_attach_fence(&uq_mgr_to_fpriv(uq_mgr)->evf_mgr, wptr_obj->obj); if (ret) { - DRM_ERROR("Failed to pin wptr bo. ret %d\n", ret); + DRM_ERROR("Failed to attach eviction fence to wptr bo. ret %d\n", ret); goto fail_map; } ret = amdgpu_ttm_alloc_gart(&wptr_obj->obj->tbo); if (ret) { - DRM_ERROR("Failed to bind bo to GART. ret %d\n", ret); - goto fail_alloc_gart; + DRM_ERROR("Failed to bind wptr bo to GART. ret %d\n", ret); + goto fail_map; } queue->wptr_obj.gpu_addr = amdgpu_bo_gpu_offset(wptr_obj->obj); drm_exec_fini(&exec); return 0; - -fail_alloc_gart: - amdgpu_bo_unpin(wptr_obj->obj); fail_map: amdgpu_bo_unref(&wptr_obj->obj); fail_lock: @@ -121,6 +117,7 @@ static int mes_userq_map(struct amdgpu_usermode_queue *queue) struct amdgpu_userq_obj *ctx = &queue->fw_obj; struct amdgpu_mqd_prop *userq_props = queue->userq_prop; struct mes_add_queue_input queue_input; + struct amdgpu_mes *mes = &adev->mes; int r; memset(&queue_input, 0x0, sizeof(struct mes_add_queue_input)); @@ -146,7 +143,12 @@ static int mes_userq_map(struct amdgpu_usermode_queue *queue) queue_input.doorbell_offset = userq_props->doorbell_index; queue_input.page_table_base_addr = amdgpu_gmc_pd_addr(queue->vm->root.bo); queue_input.wptr_mc_addr = queue->wptr_obj.gpu_addr; - + if (mes->use_rs64mem) { + amdgpu_mes_alloc_proc_ctx_index(mes, queue); + queue_input.process_context_array_index = queue->proc_ctx_array_index; + amdgpu_mes_alloc_gang_ctx_index(mes, queue); + queue_input.gang_context_array_index = queue->gang_ctx_array_index; + } amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->add_hw_queue(&adev->mes, &queue_input); amdgpu_mes_unlock(&adev->mes); @@ -165,9 +167,11 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) struct amdgpu_device *adev = uq_mgr->adev; struct mes_remove_queue_input queue_input; struct amdgpu_userq_obj *ctx = &queue->fw_obj; + struct amdgpu_mes *mes = &adev->mes; int r; memset(&queue_input, 0x0, sizeof(struct mes_remove_queue_input)); + queue_input.gang_context_array_index = queue->gang_ctx_array_index; queue_input.doorbell_offset = queue->doorbell_index; queue_input.gang_context_addr = ctx->gpu_addr; queue_input.queue_type = queue->queue_type; @@ -175,6 +179,10 @@ static int mes_userq_unmap(struct amdgpu_usermode_queue *queue) amdgpu_mes_lock(&adev->mes); r = adev->mes.funcs->remove_hw_queue(&adev->mes, &queue_input); amdgpu_mes_unlock(&adev->mes); + if (mes->use_rs64mem) { + amdgpu_mes_free_proc_ctx_index(mes, queue); + amdgpu_mes_free_gang_ctx_index(mes, queue); + } if (r) DRM_ERROR("Failed to unmap queue in HW, err (%d)\n", r); return r; @@ -227,9 +235,7 @@ int mes_userq_reset_queue(struct amdgpu_device *adev, r = mes_userq_unmap(uq); if (r) return r; - atomic_inc(&adev->gpu_reset_counter); amdgpu_userq_fence_driver_force_completion(uq); - drm_dev_wedged_event(adev_to_drm(adev), DRM_WEDGE_RECOVERY_NONE, NULL); break; } } @@ -501,9 +507,6 @@ static void mes_userq_mqd_destroy(struct amdgpu_usermode_queue *queue) amdgpu_bo_free_kernel(&queue->mqd.obj, &queue->mqd.gpu_addr, &queue->mqd.cpu_ptr); - amdgpu_bo_reserve(queue->wptr_obj.obj, true); - amdgpu_bo_unpin(queue->wptr_obj.obj); - amdgpu_bo_unreserve(queue->wptr_obj.obj); amdgpu_bo_unref(&queue->wptr_obj.obj); } diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 72ca7302bbfb..553f9d39add8 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -86,7 +86,7 @@ static void mes_v11_0_ring_set_wptr(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -97,12 +97,15 @@ static u64 mes_v11_0_ring_get_rptr(struct amdgpu_ring *ring) static u64 mes_v11_0_ring_get_wptr(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -294,8 +297,8 @@ static int convert_to_mes_queue_type(int queue_type) else if (queue_type == AMDGPU_RING_TYPE_SDMA) return MES_QUEUE_TYPE_SDMA; else - BUG(); - return -1; + WARN(1, "Invalid queue type %d\n", queue_type); + return MES_QUEUE_TYPE_GFX; } static int convert_to_mes_priority_level(int priority_level) @@ -335,8 +338,10 @@ static int mes_v11_0_add_hw_queue(struct amdgpu_mes *mes, mes_add_queue_pkt.process_va_end = input->process_va_end; mes_add_queue_pkt.process_quantum = input->process_quantum; mes_add_queue_pkt.process_context_addr = input->process_context_addr; + mes_add_queue_pkt.process_context_array_index = input->process_context_array_index; mes_add_queue_pkt.gang_quantum = input->gang_quantum; mes_add_queue_pkt.gang_context_addr = input->gang_context_addr; + mes_add_queue_pkt.gang_context_array_index = input->gang_context_array_index; mes_add_queue_pkt.inprocess_gang_priority = convert_to_mes_priority_level(input->inprocess_gang_priority); mes_add_queue_pkt.gang_global_priority_level = @@ -387,6 +392,7 @@ static int mes_v11_0_remove_hw_queue(struct amdgpu_mes *mes, mes_remove_queue_pkt.doorbell_offset = input->doorbell_offset; mes_remove_queue_pkt.gang_context_addr = input->gang_context_addr; + mes_remove_queue_pkt.gang_context_array_index = input->gang_context_array_index; mes_remove_queue_pkt.queue_type = convert_to_mes_queue_type(input->queue_type); @@ -769,6 +775,11 @@ static int mes_v11_0_unmap_legacy_queue(struct amdgpu_mes *mes, convert_to_mes_queue_type(input->queue_type); } + if (input->action == RESET_QUEUES && + (mes->sched_version & AMDGPU_MES_VERSION_MASK) >= 0x60) + mes_remove_queue_pkt.remove_queue_after_reset = 1; + + return mes_v11_0_submit_pkt_and_poll_completion(mes, &mes_remove_queue_pkt, sizeof(mes_remove_queue_pkt), offsetof(union MESAPI__REMOVE_QUEUE, api_status)); @@ -831,6 +842,58 @@ static int mes_v11_0_query_sched_status(struct amdgpu_mes *mes) offsetof(union MESAPI__QUERY_MES_STATUS, api_status)); } +/* + * QUERY_SCHEDULER_STATUS: get proc/gang context array sizes + */ +static int mes_v11_0_query_ctx_array_sizes(struct amdgpu_mes *mes) +{ + struct amdgpu_device *adev = mes->adev; + union MESAPI__QUERY_MES_STATUS mes_query_pkt; + int r; + + if (!mes->ctx_array_size_cpu_ptr) + return -EINVAL; + + /* Clear the output buffer */ + mes->ctx_array_size_cpu_ptr[0] = 0; /* proc_ctx_array_size */ + mes->ctx_array_size_cpu_ptr[1] = 0; /* gang_ctx_array_size */ + + memset(&mes_query_pkt, 0, sizeof(mes_query_pkt)); + + mes_query_pkt.header.type = MES_API_TYPE_SCHEDULER; + mes_query_pkt.header.opcode = MES_SCH_API_QUERY_SCHEDULER_STATUS; + mes_query_pkt.header.dwsize = API_FRAME_SIZE_IN_DWORDS; + + mes_query_pkt.subopcode = MES_API_QUERY_MES__GET_CTX_ARRAY_SIZE; + + /* + * MES FW will write the array sizes to these GPU addresses: + * proc_ctx_array_size_addr -> uint32_t N (e.g., 50) + * gang_ctx_array_size_addr -> uint32_t M (e.g., 300) + */ + mes_query_pkt.ctx_array_size.proc_ctx_array_size_addr = + mes->ctx_array_size_gpu_addr; + mes_query_pkt.ctx_array_size.gang_ctx_array_size_addr = + mes->ctx_array_size_gpu_addr + sizeof(uint32_t); + + mes_query_pkt.api_status.api_completion_fence_addr = + mes->ring[0].fence_drv.gpu_addr; + mes_query_pkt.api_status.api_completion_fence_value = + ++mes->ring[0].fence_drv.sync_seq; + + r = mes_v11_0_submit_pkt_and_poll_completion(mes, + &mes_query_pkt, sizeof(mes_query_pkt), + offsetof(union MESAPI__QUERY_MES_STATUS, api_status)); + if (r) { + dev_err(adev->dev, + "MES QUERY_SCHEDULER_STATUS (GET_CTX_ARRAY_SIZE) failed, r=%d\n", r); + return r; + } + + /* MES has written the sizes - now set up bitmaps */ + return amdgpu_mes_rs64mem_setup_bitmaps(mes); +} + static int mes_v11_0_misc_op(struct amdgpu_mes *mes, struct mes_misc_op_input *input) { @@ -1539,12 +1602,14 @@ static int mes_v11_0_queue_init(struct amdgpu_device *adev, struct amdgpu_ring *ring; int r; - if (pipe == AMDGPU_MES_KIQ_PIPE) + if (pipe == AMDGPU_MES_KIQ_PIPE) { ring = &adev->gfx.kiq[0].ring; - else if (pipe == AMDGPU_MES_SCHED_PIPE) + } else if (pipe == AMDGPU_MES_SCHED_PIPE) { ring = &adev->mes.ring[0]; - else - BUG(); + } else { + WARN(1, "Invalid MES pipe %d\n", pipe); + return -EINVAL; + } if ((pipe == AMDGPU_MES_SCHED_PIPE) && (amdgpu_in_reset(adev) || adev->in_suspend)) { @@ -1627,7 +1692,7 @@ static int mes_v11_0_mqd_sw_init(struct amdgpu_device *adev, else if (pipe == AMDGPU_MES_SCHED_PIPE) ring = &adev->mes.ring[0]; else - BUG(); + return -EINVAL; if (ring->mqd_obj) return 0; @@ -1904,10 +1969,33 @@ static int mes_v11_0_hw_init(struct amdgpu_ip_block *ip_block) if (r) goto failure; + /* Allocate GPU buffer for array size query results */ + r = amdgpu_mes_rs64mem_init(&adev->mes); + if (r) + dev_warn(adev->dev, + "RS64 local memory init failed (%d)," + "falling back to system memory path\n", r); + r = mes_v11_0_set_hw_resources(&adev->mes); + if (r) goto failure; + /* + * QUERY_SCHEDULER_STATUS to get array sizes (N, M). + * MES writes the sizes to the GPU buffer, then we allocate bitmaps. + */ + if (adev->mes.use_rs64mem) { + r = mes_v11_0_query_ctx_array_sizes(&adev->mes); + if (r) { + dev_warn(adev->dev, + "Failed to query ctx array sizes (%d)," + "disabling RS64 local memory\n", r); + /* Continue without optimization - not fatal */ + } + } + + if ((adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x52) { r = mes_v11_0_set_hw_resources_1(&adev->mes); if (r) { @@ -1945,6 +2033,10 @@ failure: static int mes_v11_0_hw_fini(struct amdgpu_ip_block *ip_block) { + struct amdgpu_device *adev = ip_block->adev; + + if (adev->mes.use_rs64mem) + amdgpu_mes_rs64mem_fini(&adev->mes); return 0; } diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c index 04465804c254..3b2b955cf82e 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c @@ -59,7 +59,7 @@ static void mes_v12_0_ring_set_wptr(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -70,12 +70,15 @@ static u64 mes_v12_0_ring_get_rptr(struct amdgpu_ring *ring) static u64 mes_v12_0_ring_get_wptr(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -279,8 +282,8 @@ static int convert_to_mes_queue_type(int queue_type) else if (queue_type == AMDGPU_RING_TYPE_MES) return MES_QUEUE_TYPE_SCHQ; else - BUG(); - return -1; + WARN(1, "Invalid queue type %d\n", queue_type); + return MES_QUEUE_TYPE_GFX; } static int convert_to_mes_priority_level(int priority_level) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c index dbdb9ba02a5f..f1098118d5c0 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c @@ -61,7 +61,7 @@ static void mes_v12_1_ring_set_wptr(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -72,12 +72,16 @@ static u64 mes_v12_1_ring_get_rptr(struct amdgpu_ring *ring) static u64 mes_v12_1_ring_get_wptr(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + + } return wptr; } @@ -278,8 +282,8 @@ static int convert_to_mes_queue_type(int queue_type) else if (queue_type == AMDGPU_RING_TYPE_MES) return MES_QUEUE_TYPE_SCHQ; else - BUG(); - return -1; + WARN(1, "Invalid queue type %d\n", queue_type); + return MES_QUEUE_TYPE_GFX; } static int mes_v12_1_add_hw_queue(struct amdgpu_mes *mes, diff --git a/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c b/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c index 49b7f16a941f..5827c758b373 100644 --- a/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c @@ -36,40 +36,41 @@ static const char *mmhub_client_ids_v4_2_0[][2] = { [0][0] = "VMC", - [4][0] = "DCEDMC", - [5][0] = "DCEVGA", - [6][0] = "MP0", - [7][0] = "MP1", + [2][0] = "MPNHT", + [7][0] = "MPIFOE", [8][0] = "MPIO", - [16][0] = "HDP", - [17][0] = "LSDMA", - [18][0] = "JPEG", - [19][0] = "VCNU0", - [21][0] = "VSCH", - [22][0] = "VCNU1", - [23][0] = "VCN1", - [32+20][0] = "VCN0", - [2][1] = "DBGUNBIO", - [3][1] = "DCEDWB", - [4][1] = "DCEDMC", - [5][1] = "DCEVGA", - [6][1] = "MP0", - [7][1] = "MP1", + [11][0] = "JPEG0", + [12][0] = "VCN0", + [13][0] = "VCNU0", + [14][0] = "VSCH0", + [15][0] = "LSDMA", + [32+5][0] = "MPRAS", + [32+6][0] = "MP1", + [32+7][0] = "MP0", + [32+11][0] = "JPEG1", + [32+12][0] = "VCN1", + [32+13][0] = "VCNU1", + [32+14][0] = "VSCH1", + [2][1] = "MPNHT", + [3][1] = "DBGU0", + [7][1] = "MPIFOE", [8][1] = "MPIO", - [10][1] = "DBGU0", - [11][1] = "DBGU1", - [12][1] = "DBGU2", - [13][1] = "DBGU3", - [14][1] = "XDP", - [15][1] = "OSSSYS", - [16][1] = "HDP", - [17][1] = "LSDMA", - [18][1] = "JPEG", - [19][1] = "VCNU0", - [20][1] = "VCN0", - [21][1] = "VSCH", - [22][1] = "VCNU1", - [23][1] = "VCN1", + [10][1] = "UTCL2_NHT", + [11][1] = "JPEG0", + [12][1] = "VCN0", + [13][1] = "VCNU0", + [14][1] = "VSCH0", + [15][1] = "LSDMA", + [32+3][1] = "DBGU1", + [32+4][1] = "DBGU2", + [32+5][1] = "MPRAS", + [32+6][1] = "MP1", + [32+7][1] = "MP0", + [32+8][1] = "IH", + [32+11][1] = "JPEG1", + [32+12][1] = "VCN1", + [32+13][1] = "VCNU1", + [32+14][1] = "VSCH1", }; static int mmhub_v4_2_0_get_xgmi_info(struct amdgpu_device *adev) diff --git a/drivers/gpu/drm/amd/amdgpu/mxgpu_ai.c b/drivers/gpu/drm/amd/amdgpu/mxgpu_ai.c index 9a40107a0869..fec08f235b92 100644 --- a/drivers/gpu/drm/amd/amdgpu/mxgpu_ai.c +++ b/drivers/gpu/drm/amd/amdgpu/mxgpu_ai.c @@ -185,8 +185,11 @@ static int xgpu_ai_send_access_requests(struct amdgpu_device *adev, } else if (req == IDH_REQ_GPU_INIT_DATA){ /* Dummy REQ_GPU_INIT_DATA handling */ r = xgpu_ai_poll_msg(adev, IDH_REQ_GPU_INIT_DATA_READY); - /* version set to 0 since dummy */ - adev->virt.req_init_data_ver = 0; + /* + * AI uses the GPU_CRIT_REGION_V1 layout in practice, so fix the + * dummy version value to match the actual init-data format. + */ + adev->virt.req_init_data_ver = GPU_CRIT_REGION_V1; } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.c b/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.c index f2e456390b27..eb537b2ae685 100644 --- a/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.c +++ b/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.c @@ -214,6 +214,9 @@ send_request: case IDH_REQ_RAS_REMOTE_CMD: event = IDH_REQ_RAS_REMOTE_CMD_READY; break; + case IDH_REQ_PTL_UPDATE: + event = IDH_PTL_UPDATE_READY; + break; default: break; } @@ -253,6 +256,23 @@ send_request: adev->virt.fw_reserve.checksum_key = RREG32_NO_KIQ(mmMAILBOX_MSGBUF_RCV_DW2); } + + /* Retrieve PTL response from mailbox */ + if (req == IDH_REQ_PTL_UPDATE) { + u32 dw1 = RREG32_NO_KIQ(mmMAILBOX_MSGBUF_RCV_DW1); + u32 dw2 = RREG32_NO_KIQ(mmMAILBOX_MSGBUF_RCV_DW2); + u32 status = AMD_SRIOV_PTL_UNPACK_STATUS(dw1); + + if (status == AMD_SRIOV_RESP_UNSUPPORTED) { + r = -EOPNOTSUPP; + } else if (status == AMD_SRIOV_RESP_FAIL) { + r = -EIO; + } else { + adev->virt.ptl_state = AMD_SRIOV_PTL_UNPACK_STATE(dw1); + adev->virt.ptl_pref_format1 = AMD_SRIOV_PTL_UNPACK_FMT1(dw2); + adev->virt.ptl_pref_format2 = AMD_SRIOV_PTL_UNPACK_FMT2(dw2); + } + } } out: @@ -597,6 +617,13 @@ static int xgpu_nv_req_remote_ras_cmd(struct amdgpu_device *adev, adev, IDH_REQ_RAS_REMOTE_CMD, param1, param2, param3); } +static int xgpu_nv_req_ptl_update(struct amdgpu_device *adev, + u32 req_code, u32 ptl_state, u32 fmt1, u32 fmt2) +{ + return xgpu_nv_send_access_requests_with_param(adev, IDH_REQ_PTL_UPDATE, + req_code, ptl_state, AMD_SRIOV_PTL_PACK_FORMATS(fmt1, fmt2)); +} + const struct amdgpu_virt_ops xgpu_nv_virt_ops = { .req_full_gpu = xgpu_nv_request_full_gpu_access, .rel_full_gpu = xgpu_nv_release_full_gpu_access, @@ -612,4 +639,5 @@ const struct amdgpu_virt_ops xgpu_nv_virt_ops = { .req_bad_pages = xgpu_nv_req_ras_bad_pages, .req_ras_chk_criti = xgpu_nv_check_vf_critical_region, .req_remote_ras_cmd = xgpu_nv_req_remote_ras_cmd, + .req_ptl_update = xgpu_nv_req_ptl_update, }; diff --git a/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.h b/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.h index dc57a4f697ee..5023113e7600 100644 --- a/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.h +++ b/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.h @@ -45,6 +45,7 @@ enum idh_request { IDH_REQ_RAS_BAD_PAGES = 205, IDH_REQ_RAS_CHK_CRITI = 206, IDH_REQ_RAS_REMOTE_CMD = 207, + IDH_REQ_PTL_UPDATE = 208 }; enum idh_event { @@ -66,6 +67,7 @@ enum idh_event { IDH_UNRECOV_ERR_NOTIFICATION = 17, IDH_REQ_RAS_CHK_CRITI_READY = 18, IDH_REQ_RAS_REMOTE_CMD_READY = 19, + IDH_PTL_UPDATE_READY = 20, IDH_TEXT_MESSAGE = 255, }; diff --git a/drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c b/drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c index c78f0598637f..000516b5845a 100644 --- a/drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c +++ b/drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c @@ -30,70 +30,76 @@ #include "ivsrcid/nbio/irqsrcs_nbif_7_4.h" #include -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL_nbif_4_10 0x4f0aeb -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL1_nbif_4_10 0x4f0aec -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL_nbif_4_10 0x4f0aed -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL1_nbif_4_10 0x4f0aee -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL_nbif_4_10 0x4f0aef -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL1_nbif_4_10 0x4f0af0 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL_nbif_4_10 0x4f0af1 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL1_nbif_4_10 0x4f0af2 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL_nbif_4_10 0x4f0af3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL1_nbif_4_10 0x4f0af4 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL_nbif_4_10 0x4f0af5 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL1_nbif_4_10 0x4f0af6 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL1_nbif_4_10_BASE_IDX 3 -#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10 0x0021 -#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10_BASE_IDX 2 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL_nbif_4_10 0x4f0aeb +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL1_nbif_4_10 0x4f0aec +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL_nbif_4_10 0x4f0aed +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL1_nbif_4_10 0x4f0aee +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL_nbif_4_10 0x4f0aef +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL1_nbif_4_10 0x4f0af0 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL_nbif_4_10 0x4f0af1 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL1_nbif_4_10 0x4f0af2 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL_nbif_4_10 0x4f0af3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL1_nbif_4_10 0x4f0af4 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL_nbif_4_10 0x4f0af5 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL1_nbif_4_10 0x4f0af6 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL1_nbif_4_10_BASE_IDX 3 +#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10 0x0021 +#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10_BASE_IDX 2 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_HIGH_nbio_7_11_5 0x8e13 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_HIGH_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_LOW_nbio_7_11_5 0x8e14 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_LOW_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_CNTL_nbio_7_11_5 0x8e15 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_CNTL_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_HIGH_nbio_7_11_5 0x8e13 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_HIGH_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_LOW_nbio_7_11_5 0x8e14 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_LOW_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_CNTL_nbio_7_11_5 0x8e15 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_CNTL_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX0_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5 0x8e4d -#define regBIF_BX0_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX0_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5 0x8e4e -#define regBIF_BX0_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX1_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5 0x012d +#define regBIF_BX1_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 2 +#define regBIF_BX1_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5 0x012e +#define regBIF_BX1_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 2 -#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5 0xd000 -#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5_BASE_IDX 5 +#define regRCC_STRAP1_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5 0x0021 +#define regRCC_STRAP1_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5_BASE_IDX 2 -#define regBIF_BX0_BIF_FB_EN_nbio_7_11_5 0x8e20 -#define regBIF_BX0_BIF_FB_EN_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX1_BIF_FB_EN_nbio_7_11_5 0x0100 +#define regBIF_BX1_BIF_FB_EN_nbio_7_11_5_BASE_IDX 2 -#define regBIF_BX0_INTERRUPT_CNTL_nbio_7_11_5 0x8e11 -#define regBIF_BX0_INTERRUPT_CNTL_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX0_INTERRUPT_CNTL2_nbio_7_11_5 0x8e12 -#define regBIF_BX0_INTERRUPT_CNTL2_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX1_INTERRUPT_CNTL_nbio_7_11_5 0x00f1 +#define regBIF_BX1_INTERRUPT_CNTL_nbio_7_11_5_BASE_IDX 2 +#define regBIF_BX1_INTERRUPT_CNTL2_nbio_7_11_5 0x00f2 +#define regBIF_BX1_INTERRUPT_CNTL2_nbio_7_11_5_BASE_IDX 2 -#define regBIF_BX_PF0_GPU_HDP_FLUSH_REQ_nbio_7_11_5 0x8e26 -#define regBIF_BX_PF0_GPU_HDP_FLUSH_REQ_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX_PF0_GPU_HDP_FLUSH_DONE_nbio_7_11_5 0x8e27 -#define regBIF_BX_PF0_GPU_HDP_FLUSH_DONE_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX_PF1_GPU_HDP_FLUSH_REQ_nbio_7_11_5 0x0106 +#define regBIF_BX_PF1_GPU_HDP_FLUSH_REQ_nbio_7_11_5_BASE_IDX 2 +#define regBIF_BX_PF1_GPU_HDP_FLUSH_DONE_nbio_7_11_5 0x0107 +#define regBIF_BX_PF1_GPU_HDP_FLUSH_DONE_nbio_7_11_5_BASE_IDX 2 -#define regBIF_BX_PF0_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5 0x8e17 -#define regBIF_BX_PF0_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX_PF1_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5 0x00f7 +#define regBIF_BX_PF1_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 2 + +//BIF_BX1_BIF_FB_EN +#define BIF_BX1_BIF_FB_EN__FB_READ_EN__SHIFT_nbio_7_11_5 0x0 +#define BIF_BX1_BIF_FB_EN__FB_WRITE_EN__SHIFT_nbio_7_11_5 0x1 +#define BIF_BX1_BIF_FB_EN__FB_READ_EN_MASK_nbio_7_11_5 0x00000001L +#define BIF_BX1_BIF_FB_EN__FB_WRITE_EN_MASK_nbio_7_11_5 0x00000002L static void nbif_v6_3_1_remap_hdp_registers(struct amdgpu_device *adev) { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) { - WREG32_SOC15(NBIO, 0, regBIF_BX0_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5, + WREG32_SOC15(NBIO, 0, regBIF_BX1_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5, adev->rmmio_remap.reg_offset + KFD_MMIO_REMAP_HDP_MEM_FLUSH_CNTL); - WREG32_SOC15(NBIO, 0, regBIF_BX0_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5, + WREG32_SOC15(NBIO, 0, regBIF_BX1_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5, adev->rmmio_remap.reg_offset + KFD_MMIO_REMAP_HDP_REG_FLUSH_CNTL); } else { WREG32_SOC15(NBIO, 0, regBIF_BX0_REMAP_HDP_MEM_FLUSH_CNTL, @@ -110,7 +116,7 @@ static u32 nbif_v6_3_1_get_rev_id(struct amdgpu_device *adev) if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 4)) tmp = RREG32_SOC15(NBIO, 0, regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10); else if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - tmp = RREG32_SOC15(NBIO, 0, regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5); + tmp = RREG32_SOC15(NBIO, 0, regRCC_STRAP1_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5); else tmp = RREG32_SOC15(NBIO, 0, regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0); @@ -124,11 +130,11 @@ static void nbif_v6_3_1_mc_access_enable(struct amdgpu_device *adev, bool enable { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) { if (enable) - WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_FB_EN_nbio_7_11_5, - BIF_BX0_BIF_FB_EN__FB_READ_EN_MASK | - BIF_BX0_BIF_FB_EN__FB_WRITE_EN_MASK); + WREG32_SOC15(NBIO, 0, regBIF_BX1_BIF_FB_EN_nbio_7_11_5, + BIF_BX1_BIF_FB_EN__FB_READ_EN_MASK_nbio_7_11_5 | + BIF_BX1_BIF_FB_EN__FB_WRITE_EN_MASK_nbio_7_11_5); else - WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_FB_EN_nbio_7_11_5, 0); + WREG32_SOC15(NBIO, 0, regBIF_BX1_BIF_FB_EN_nbio_7_11_5, 0); } else { if (enable) WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_FB_EN, @@ -383,12 +389,12 @@ static void nbif_v6_3_1_ih_control(struct amdgpu_device *adev) /* setup interrupt control */ if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - WREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL2_nbio_7_11_5, + WREG32_SOC15(NBIO, 0, regBIF_BX1_INTERRUPT_CNTL2_nbio_7_11_5, adev->dummy_page_addr >> 8); else WREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL2, adev->dummy_page_addr >> 8); - interrupt_cntl = RREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL_nbio_7_11_5); + interrupt_cntl = RREG32_SOC15(NBIO, 0, regBIF_BX1_INTERRUPT_CNTL_nbio_7_11_5); /* * BIF_BX0_INTERRUPT_CNTL__IH_DUMMY_RD_OVERRIDE_MASK=0 - dummy read disabled with msi, enabled without msi * BIF_BX0_INTERRUPT_CNTL__IH_DUMMY_RD_OVERRIDE_MASK=1 - dummy read controlled by IH_DUMMY_RD_EN @@ -401,7 +407,7 @@ static void nbif_v6_3_1_ih_control(struct amdgpu_device *adev) IH_REQ_NONSNOOP_EN, 0); if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - WREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL_nbio_7_11_5, interrupt_cntl); + WREG32_SOC15(NBIO, 0, regBIF_BX1_INTERRUPT_CNTL_nbio_7_11_5, interrupt_cntl); else WREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL, interrupt_cntl); } @@ -427,7 +433,7 @@ nbif_v6_3_1_get_clockgating_state(struct amdgpu_device *adev, static u32 nbif_v6_3_1_get_hdp_flush_req_offset(struct amdgpu_device *adev) { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_GPU_HDP_FLUSH_REQ_nbio_7_11_5); + return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF1_GPU_HDP_FLUSH_REQ_nbio_7_11_5); else return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_GPU_HDP_FLUSH_REQ); } @@ -435,7 +441,7 @@ static u32 nbif_v6_3_1_get_hdp_flush_req_offset(struct amdgpu_device *adev) static u32 nbif_v6_3_1_get_hdp_flush_done_offset(struct amdgpu_device *adev) { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_GPU_HDP_FLUSH_DONE_nbio_7_11_5); + return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF1_GPU_HDP_FLUSH_DONE_nbio_7_11_5); else return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_GPU_HDP_FLUSH_DONE); } @@ -622,7 +628,7 @@ static void nbif_v6_3_1_set_reg_remap(struct amdgpu_device *adev) } else { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) adev->rmmio_remap.reg_offset = SOC15_REG_OFFSET(NBIO, 0, - regBIF_BX_PF0_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5) << 2; + regBIF_BX_PF1_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5) << 2; else adev->rmmio_remap.reg_offset = SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_HDP_MEM_COHERENCY_FLUSH_CNTL) << 2; diff --git a/drivers/gpu/drm/amd/amdgpu/psp_gfx_if.h b/drivers/gpu/drm/amd/amdgpu/psp_gfx_if.h index ac34bac3c839..ad618b78685f 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_gfx_if.h +++ b/drivers/gpu/drm/amd/amdgpu/psp_gfx_if.h @@ -110,6 +110,7 @@ enum psp_gfx_cmd_id GFX_CMD_ID_PERF_HW = 0x0000004C, /* performance monitor */ GFX_CMD_ID_FB_FW_RESERV_ADDR = 0x00000050, /* Query FW reservation addr */ GFX_CMD_ID_FB_FW_RESERV_EXT_ADDR = 0x00000051, /* Query FW reservation extended addr */ + GFX_CMD_ID_SET_MMHUB_ECO_SEC_LEVEL = 0x0000005D, /* Set MMHUB ECO sec lvls on VCN block */ }; /* PSP boot config sub-commands */ diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v11_0.c b/drivers/gpu/drm/amd/amdgpu/psp_v11_0.c index 479690c44f0d..bd3bb3bfb668 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v11_0.c @@ -136,7 +136,9 @@ static int psp_v11_0_init_microcode(struct psp_context *psp) err = psp_init_toc_microcode(psp, ucode_prefix); break; default: - BUG(); + dev_warn(adev->dev, "Unsupported MP0 version 0x%08x\n", + amdgpu_ip_version(adev, MP0_HWIP, 0)); + return -EINVAL; } return err; diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v13_0.c b/drivers/gpu/drm/amd/amdgpu/psp_v13_0.c index 00b4a34e6601..f9a3fbfc0634 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v13_0.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v13_0.c @@ -133,7 +133,9 @@ static int psp_v13_0_init_microcode(struct psp_context *psp) return err; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported MP0 version 0x%08x\n", + amdgpu_ip_version(adev, MP0_HWIP, 0)); + return -EINVAL; } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v13_0_4.c b/drivers/gpu/drm/amd/amdgpu/psp_v13_0_4.c index 3d5e26b3fa00..e1acdbe01833 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v13_0_4.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v13_0_4.c @@ -50,7 +50,9 @@ static int psp_v13_0_4_init_microcode(struct psp_context *psp) return err; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported MP0 version 0x%08x\n", + amdgpu_ip_version(adev, MP0_HWIP, 0)); + return -EINVAL; } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v14_0.c b/drivers/gpu/drm/amd/amdgpu/psp_v14_0.c index 040a61aefa86..888c747d6c5c 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v14_0.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v14_0.c @@ -85,7 +85,9 @@ static int psp_v14_0_init_microcode(struct psp_context *psp) return err; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported MP0 version 0x%08x\n", + amdgpu_ip_version(adev, MP0_HWIP, 0)); + return -EINVAL; } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c b/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c index 2a4d91368ac6..abaa40b6def9 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c @@ -33,9 +33,22 @@ MODULE_FIRMWARE("amdgpu/psp_15_0_0_toc.bin"); MODULE_FIRMWARE("amdgpu/psp_15_0_0_ta.bin"); +MODULE_FIRMWARE("amdgpu/psp_15_0_5_toc.bin"); +MODULE_FIRMWARE("amdgpu/psp_15_0_5_ta.bin"); MODULE_FIRMWARE("amdgpu/psp_15_0_9_toc.bin"); MODULE_FIRMWARE("amdgpu/psp_15_0_9_ta.bin"); +#define regMPASP_PCRU0_MPASP_C2PMSG_64 0x4280 +#define regMPASP_PCRU0_MPASP_C2PMSG_64_BASE_IDX 2 +#define regMPASP_PCRU0_MPASP_C2PMSG_67 0x4283 +#define regMPASP_PCRU0_MPASP_C2PMSG_67_BASE_IDX 2 +#define regMPASP_PCRU0_MPASP_C2PMSG_69 0x4285 +#define regMPASP_PCRU0_MPASP_C2PMSG_69_BASE_IDX 2 +#define regMPASP_PCRU0_MPASP_C2PMSG_70 0x4286 +#define regMPASP_PCRU0_MPASP_C2PMSG_70_BASE_IDX 2 +#define regMPASP_PCRU0_MPASP_C2PMSG_71 0x4287 +#define regMPASP_PCRU0_MPASP_C2PMSG_71_BASE_IDX 2 + static int psp_v15_0_0_init_microcode(struct psp_context *psp) { struct amdgpu_device *adev = psp->adev; @@ -71,14 +84,25 @@ static int psp_v15_0_0_ring_stop(struct psp_context *psp, ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_SMN_C2PMSG_101), 0x80000000, 0x80000000, false); } else { - /* Write the ring destroy command*/ - WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64, - GFX_CTRL_CMD_ID_DESTROY_RINGS); - /* there might be handshake issue with hardware which needs delay */ - mdelay(20); - /* Wait for response flag (bit 31) */ - ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), - 0x80000000, 0x80000000, false); + if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 5)) { + /* Write the ring destroy command*/ + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64, + GFX_CTRL_CMD_ID_DESTROY_RINGS); + /* there might be handshake issue with hardware which needs delay */ + mdelay(20); + /* Wait for response flag (bit 31) */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64), + 0x80000000, 0x80000000, false); + } else { + /* Write the ring destroy command*/ + WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64, + GFX_CTRL_CMD_ID_DESTROY_RINGS); + /* there might be handshake issue with hardware which needs delay */ + mdelay(20); + /* Wait for response flag (bit 31) */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), + 0x80000000, 0x80000000, false); + } } return ret; @@ -118,14 +142,44 @@ static int psp_v15_0_0_ring_create(struct psp_context *psp, 0x80000000, 0x8000FFFF, false); } else { - /* Wait for sOS ready for ring creation */ - ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), - 0x80000000, 0x80000000, false); + if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 5)) { + /* Wait for sOS ready for ring creation */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64), + 0x80000000, 0x80000000, false); if (ret) { DRM_ERROR("Failed to wait for trust OS ready for ring creation\n"); return ret; } + /* Write low address of the ring to C2PMSG_69 */ + psp_ring_reg = lower_32_bits(ring->ring_mem_mc_addr); + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_69, psp_ring_reg); + /* Write high address of the ring to C2PMSG_70 */ + psp_ring_reg = upper_32_bits(ring->ring_mem_mc_addr); + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_70, psp_ring_reg); + /* Write size of ring to C2PMSG_71 */ + psp_ring_reg = ring->ring_size; + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_71, psp_ring_reg); + /* Write the ring initialization command to C2PMSG_64 */ + psp_ring_reg = ring_type; + psp_ring_reg = psp_ring_reg << 16; + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64, psp_ring_reg); + + /* there might be handshake issue with hardware which needs delay */ + mdelay(20); + + /* Wait for response flag (bit 31) in C2PMSG_64 */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64), + 0x80000000, 0x8000FFFF, false); + } else { + /* Wait for sOS ready for ring creation */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), + 0x80000000, 0x80000000, false); + if (ret) { + DRM_ERROR("Failed to wait for trust OS ready for ring creation\n"); + return ret; + } + /* Write low address of the ring to C2PMSG_69 */ psp_ring_reg = lower_32_bits(ring->ring_mem_mc_addr); WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_69, psp_ring_reg); @@ -146,6 +200,7 @@ static int psp_v15_0_0_ring_create(struct psp_context *psp, /* Wait for response flag (bit 31) in C2PMSG_64 */ ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), 0x80000000, 0x8000FFFF, false); + } } return ret; @@ -176,8 +231,12 @@ static uint32_t psp_v15_0_0_ring_get_wptr(struct psp_context *psp) if (amdgpu_sriov_vf(adev)) data = RREG32_SOC15(MP0, 0, regMPASP_SMN_C2PMSG_102); - else - data = RREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_67); + else { + if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 5)) + data = RREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_67); + else + data = RREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_67); + } return data; } @@ -190,8 +249,12 @@ static void psp_v15_0_0_ring_set_wptr(struct psp_context *psp, uint32_t value) WREG32_SOC15(MP0, 0, regMPASP_SMN_C2PMSG_102, value); WREG32_SOC15(MP0, 0, regMPASP_SMN_C2PMSG_101, GFX_CTRL_CMD_ID_CONSUME_CMD); - } else - WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_67, value); + } else { + if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 5)) + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_67, value); + else + WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_67, value); + } } static const struct psp_funcs psp_v15_0_0_funcs = { diff --git a/drivers/gpu/drm/amd/amdgpu/soc21.c b/drivers/gpu/drm/amd/amdgpu/soc21.c index 713746a75d1f..1f9a9c46377a 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc21.c +++ b/drivers/gpu/drm/amd/amdgpu/soc21.c @@ -407,6 +407,7 @@ soc21_asic_reset_method(struct amdgpu_device *adev) case IP_VERSION(14, 0, 4): case IP_VERSION(14, 0, 5): case IP_VERSION(15, 0, 0): + case IP_VERSION(15, 0, 5): case IP_VERSION(15, 0, 9): return AMD_RESET_METHOD_MODE2; default: diff --git a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c index 8bb9592b0981..bbd0e05d39e5 100644 --- a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c @@ -217,8 +217,9 @@ static int uvd_v6_0_enc_get_create_msg(struct amdgpu_ring *ring, uint32_t handle int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; @@ -282,8 +283,9 @@ static int uvd_v6_0_enc_get_destroy_msg(struct amdgpu_ring *ring, int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/uvd_v7_0.c b/drivers/gpu/drm/amd/amdgpu/uvd_v7_0.c index df2c83348315..46222fc30be6 100644 --- a/drivers/gpu/drm/amd/amdgpu/uvd_v7_0.c +++ b/drivers/gpu/drm/amd/amdgpu/uvd_v7_0.c @@ -225,8 +225,9 @@ static int uvd_v7_0_enc_get_create_msg(struct amdgpu_ring *ring, u32 handle, int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; @@ -289,8 +290,9 @@ static int uvd_v7_0_enc_get_destroy_msg(struct amdgpu_ring *ring, u32 handle, int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_0.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_0.c index 45580e9c4e0c..886e2c2074a9 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_0.c @@ -795,6 +795,27 @@ static int vcn_v5_0_0_start_dpg_mode(struct amdgpu_vcn_inst *vinst, return 0; } +/** + * vcn_v5_0_0_set_mmhub_eco_sec_level - set vcn sec lvl reg + * + * @adev: amdgpu_device pointer + * + * request psp to set sec lvl + */ +static int vcn_v5_0_0_set_mmhub_eco_sec_level(struct amdgpu_device *adev) +{ + int r = 0; + + if (amdgpu_ip_version(adev, VCN_HWIP, 0) == IP_VERSION(5, 3, 0)) { + if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) { + /* Request to PSP to program VCN secure lvl */ + r = psp_set_mmhub_eco_sec_level(adev); + } + } + + return r; +} + /** * vcn_v5_0_0_start - VCN start * @@ -819,6 +840,11 @@ static int vcn_v5_0_0_start(struct amdgpu_vcn_inst *vinst) fw_shared = adev->vcn.inst[i].fw_shared.cpu_addr; + /* program VCN secure lvl register */ + r = vcn_v5_0_0_set_mmhub_eco_sec_level(adev); + if (r) + return r; + if (adev->pg_flags & AMD_PG_SUPPORT_VCN_DPG) return vcn_v5_0_0_start_dpg_mode(vinst, adev->vcn.inst[i].indirect_sram); diff --git a/drivers/gpu/drm/amd/amdgpu/vi.c b/drivers/gpu/drm/amd/amdgpu/vi.c index 514715793f78..352c27103305 100644 --- a/drivers/gpu/drm/amd/amdgpu/vi.c +++ b/drivers/gpu/drm/amd/amdgpu/vi.c @@ -1387,10 +1387,30 @@ static uint64_t vi_get_pcie_replay_count(struct amdgpu_device *adev) return (nak_r + nak_g); } +struct vi_reset_quirk { + u16 device; + u16 subsystem_vendor; + u16 subsystem_device; + u8 revision; +}; + +static const struct vi_reset_quirk vi_reset_quirks[] = { + { 0x67ef, PCI_VENDOR_ID_APPLE, 0x0190, 0xe3 }, /* Radeon Pro 555X */ + { 0x67ef, PCI_VENDOR_ID_APPLE, 0x018f, 0xc2 }, /* Radeon Pro 560X */ +}; + static bool vi_need_reset_on_init(struct amdgpu_device *adev) { + unsigned int i; u32 clock_cntl, pc; + for (i = 0; i < ARRAY_SIZE(vi_reset_quirks); i++) + if (adev->pdev->device == vi_reset_quirks[i].device && + adev->pdev->subsystem_vendor == vi_reset_quirks[i].subsystem_vendor && + adev->pdev->subsystem_device == vi_reset_quirks[i].subsystem_device && + adev->pdev->revision == vi_reset_quirks[i].revision) + return true; + if (adev->flags & AMD_IS_APU) return false; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 2c1a936459ac..309510e23315 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1783,6 +1783,9 @@ static int kfd_ptl_control(struct kfd_process_device *pdd, bool enable) uint32_t ptl_state = enable ? 1 : 0; int ret; + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) + return -EOPNOTSUPP; + if (!pdd->dev->kfd2kgd || !pdd->dev->kfd2kgd->ptl_ctrl) return -EOPNOTSUPP; @@ -1801,7 +1804,7 @@ int kfd_ptl_disable_request(struct kfd_process_device *pdd, struct amdgpu_ptl *ptl = &adev->psp.ptl; int ret = 0; - if (!ptl->hw_supported) + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return -EOPNOTSUPP; mutex_lock(&ptl->mutex); @@ -1833,7 +1836,7 @@ int kfd_ptl_disable_release(struct kfd_process_device *pdd, struct amdgpu_ptl *ptl = &adev->psp.ptl; int ret = 0; - if (!ptl->hw_supported) + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return -EOPNOTSUPP; mutex_lock(&ptl->mutex); @@ -3112,10 +3115,14 @@ static int kfd_ioctl_set_debug_trap(struct file *filep, struct kfd_process *p, v goto out; } - /* Check if target is still PTRACED. */ + /* + * Verify debugger has permission to debug target process. + * For cross-process debugging, require active ptrace relationship. + * This applies to ALL operations to prevent unauthorized interference. + */ rcu_read_lock(); - if (target != p && args->op != KFD_IOC_DBG_TRAP_DISABLE - && ptrace_parent(target->lead_thread) != current) { + if (target != p && ptrace_parent(target->lead_thread) != current + && target->debugger_process != p) { pr_err("PID %i is not PTRACED and cannot be debugged\n", args->pid); r = -EPERM; } @@ -3349,7 +3356,7 @@ static inline uint32_t profile_lock_device(struct kfd_process *p, kfd->profiler_process = p; status = 0; mutex_unlock(&kfd->profiler_lock); - if (ptl->hw_supported) { + if (ptl->hw_supported_state == AMDGPU_PTL_HW_SUPPORTED) { status = kfd_ptl_disable_request(pdd, p); if (status != 0) dev_err(kfd_device, @@ -3367,7 +3374,7 @@ static inline uint32_t profile_lock_device(struct kfd_process *p, status = 0; mutex_unlock(&kfd->profiler_lock); - if (ptl->hw_supported) { + if (ptl->hw_supported_state == AMDGPU_PTL_HW_SUPPORTED) { status = kfd_ptl_disable_release(pdd, p); if (status) dev_err(kfd_device, @@ -3715,10 +3722,10 @@ static int kfd_mmio_mmap(struct kfd_node *dev, struct kfd_process *process, vma->vm_page_prot); } - static int kfd_mmap(struct file *filep, struct vm_area_struct *vma) { struct kfd_process *process; + struct kfd_process_device *pdd; struct kfd_node *dev = NULL; unsigned long mmap_offset; unsigned int gpu_id; @@ -3732,8 +3739,10 @@ static int kfd_mmap(struct file *filep, struct vm_area_struct *vma) mmap_offset = vma->vm_pgoff << PAGE_SHIFT; gpu_id = KFD_MMAP_GET_GPU_ID(mmap_offset); - if (gpu_id) - dev = kfd_device_by_id(gpu_id); + + pdd = kfd_process_device_data_by_id(process, gpu_id); + if (pdd) + dev = pdd->dev; switch (mmap_offset & KFD_MMAP_TYPE_MASK) { case KFD_MMAP_TYPE_DOORBELL: diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c index 2a239f45fc24..6e0df685503d 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c @@ -1412,6 +1412,15 @@ int kfd_parse_crat_table(void *crat_image, struct list_head *device_list, break; } + /* Validate subtype fits within remaining image */ + if ((char *)sub_type_hdr + sub_type_hdr->length > + (char *)crat_image + image_len) { + pr_warn("CRAT subtype length %u exceeds image bounds\n", + sub_type_hdr->length); + ret = -EINVAL; + break; + } + if (sub_type_hdr->flags & CRAT_SUBTYPE_FLAGS_ENABLED) { ret = kfd_parse_subtype(sub_type_hdr, device_list); if (ret) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debugfs.c b/drivers/gpu/drm/amd/amdkfd/kfd_debugfs.c index 464836f2a53f..33e905d59dd9 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_debugfs.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debugfs.c @@ -211,10 +211,6 @@ int kfd_debugfs_add_process(struct kfd_process *p) entry->proc_dentry = debugfs_create_dir(name, primary_entry->proc_dentry); } - if (IS_ERR_OR_NULL(entry->proc_dentry)) { - ret = entry->proc_dentry ? PTR_ERR(entry->proc_dentry) : -ENOMEM; - goto err_free_entry; - } list_add(&entry->list, &procs); kfd_debugfs_create_pasid_files(p, entry->proc_dentry); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index cc42feb24277..51ee9c39104b 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -448,6 +448,9 @@ int kfd_reset_queue_mes(struct device_queue_manager *dqm, int queue_type, static int reset_queues_mes(struct device_queue_manager *dqm, struct queue *q) { struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; + struct drm_wedge_task_info *info = NULL; + struct amdgpu_task_info *ti = NULL; + struct kfd_process_device *pdd; unsigned int num_hung = 0; int r = 0; struct mes_remove_queue_input queue_input; @@ -476,13 +479,27 @@ static int reset_queues_mes(struct device_queue_manager *dqm, struct queue *q) r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, &num_hung, &queue_input); if (r) goto fail; + pdd = kfd_get_process_device_data(q->device, q->process); + if (pdd) { + ti = amdgpu_vm_get_task_info_pasid(adev, pdd->pasid); + if (ti) { + amdgpu_vm_print_task_info(adev, ti); + info = &ti->task; + } + } dqm->detect_hang_count = num_hung; /* When MES doesn't detect any queue hang, no reset happens. Don't signal reset * event. */ - if (dqm->detect_hang_count) + if (dqm->detect_hang_count) { kfd_signal_reset_event(dqm->dev); + if (pdd && pdd->has_reset_queue) { + atomic_inc(&adev->gpu_reset_counter); + drm_dev_wedged_event(adev_to_drm(adev), DRM_WEDGE_RECOVERY_NONE, info); + } + } + amdgpu_vm_put_task_info(ti); fail: dqm->detect_hang_count = 0; @@ -3071,6 +3088,7 @@ static void deallocate_hiq_sdma_mqd(struct kfd_node *dev, struct device_queue_manager *device_queue_manager_init(struct kfd_node *dev) { struct device_queue_manager *dqm; + int i; pr_debug("Loading device queue manager\n"); @@ -3199,6 +3217,9 @@ struct device_queue_manager *device_queue_manager_init(struct kfd_node *dev) deallocate_hiq_sdma_mqd(dev, &dqm->hiq_sdma_mqd); out_free: + for (i = 0; i < KFD_MQD_TYPE_MAX; i++) + kfree(dqm->mqd_mgrs[i]); + kfree(dqm); return NULL; } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_events.c index f5fd78f0df7d..6e32a892d56f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.c @@ -452,6 +452,9 @@ int kfd_criu_restore_event(struct file *devkfd, ret = create_other_event(p, ev, &ev_priv->event_id); break; + default: + ret = -EINVAL; + break; } mutex_unlock(&p->event_mutex); @@ -473,15 +476,27 @@ int kfd_criu_checkpoint_events(struct kfd_process *p, int ret = 0; struct kfd_event *ev; uint32_t ev_id; + uint32_t num_events; - uint32_t num_events = kfd_get_num_events(p); + /* Serialize the count and the walk below against concurrent event + * create/destroy. Those paths take only p->event_mutex, not the + * p->mutex held by the CRIU checkpoint caller, so without this the + * event_idr can grow between kfd_get_num_events() and the loop and the + * walk writes past the ev_privs allocation. + */ + mutex_lock(&p->event_mutex); - if (!num_events) + num_events = kfd_get_num_events(p); + if (!num_events) { + mutex_unlock(&p->event_mutex); return 0; + } ev_privs = kvzalloc(num_events * sizeof(*ev_privs), GFP_KERNEL); - if (!ev_privs) + if (!ev_privs) { + mutex_unlock(&p->event_mutex); return -ENOMEM; + } idr_for_each_entry(&p->event_idr, ev, ev_id) { @@ -522,6 +537,8 @@ int kfd_criu_checkpoint_events(struct kfd_process *p, i++; } + mutex_unlock(&p->event_mutex); + ret = copy_to_user(user_priv_data + *priv_data_offset, ev_privs, num_events * sizeof(*ev_privs)); if (ret) { @@ -1147,6 +1164,8 @@ void kfd_signal_reset_event(struct kfd_node *dev) struct kfd_event *ev; unsigned int temp; uint32_t id, idx; + int user_gpu_id; + struct kfd_process_device *pdd; int reset_cause = atomic_read(&dev->sram_ecc_flag) ? KFD_HW_EXCEPTION_ECC : KFD_HW_EXCEPTION_GPU_HANG; @@ -1162,17 +1181,14 @@ void kfd_signal_reset_event(struct kfd_node *dev) idx = srcu_read_lock(&kfd_processes_srcu); hash_for_each_rcu(kfd_processes_table, temp, p, kfd_processes) { - int user_gpu_id = kfd_process_get_user_gpu_id(p, dev->id); - struct kfd_process_device *pdd = kfd_get_process_device_data(dev, p); + pdd = kfd_get_process_device_data(dev, p); + if (!pdd) + /* no process is using this device */ + continue; + user_gpu_id = kfd_process_get_user_gpu_id(p, dev->id); if (unlikely(user_gpu_id == -EINVAL)) { - WARN_ONCE(1, "Could not get user_gpu_id from dev->id:%x\n", dev->id); - continue; - } - - if (unlikely(!pdd)) { - WARN_ONCE(1, "Could not get device data from process pid:%d\n", - p->lead_thread->pid); + WARN_ONCE(1, "Could not get user_gpu_id from dev->id:%d\n", dev->id); continue; } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c index 7cd236c1ff75..f5af1dd3b70e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c @@ -68,8 +68,8 @@ svm_migrate_gart_map(struct amdgpu_ring *ring, AMDGPU_FENCE_OWNER_UNDEFINED, num_dw * 4 + num_bytes, AMDGPU_IB_POOL_DELAYED, - &job, - AMDGPU_KERNEL_JOB_ID_KFD_GART_MAP); + AMDGPU_KERNEL_JOB_ID_KFD_GART_MAP, + &job); if (r) return r; @@ -954,12 +954,12 @@ static vm_fault_t svm_migrate_to_ram(struct vm_fault *vmf) pr_debug("failed get device page at addr 0x%lx\n", addr); return VM_FAULT_SIGBUS; } - if (!mmget_not_zero(svm_bo->eviction_fence->mm)) { + if (!mmget_not_zero(svm_bo->mm)) { pr_debug("addr 0x%lx of process mm is destroyed\n", addr); return VM_FAULT_SIGBUS; } - mm = svm_bo->eviction_fence->mm; + mm = svm_bo->mm; if (mm != vmf->vma->vm_mm) pr_debug("addr 0x%lx is COW mapping in child process\n", addr); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager.c index b1a6eb349bb3..85bf1790a6c8 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager.c @@ -269,9 +269,19 @@ build_runlist_ib: } pm->is_over_subscription = !!is_over_subscription; - for (i = 0; i < alloc_size_bytes / sizeof(uint32_t); i++) - pr_debug("0x%2X ", rl_buffer[i]); - pr_debug("\n"); + pr_debug("Runlist dump:"); + for (i = 0; i < alloc_size_bytes / sizeof(uint32_t); i += 8) { + char buf[128]; + int j, len = 0; + + /* Dump 8 entries per line with an index for each line */ + len += scnprintf(buf + len, sizeof(buf) - len, "%4u:", i); + + for (j = 0; j < 8 && (i + j) < alloc_size_bytes / sizeof(uint32_t); j++) + len += scnprintf(buf + len, sizeof(buf) - len, " 0x%08x", rl_buffer[i + j]); + + pr_debug("%s\n", buf); + } return retval; } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager_v9.c index 3d2375817c3e..54868b81086e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager_v9.c @@ -309,6 +309,67 @@ static inline void pm_build_dequeue_wait_counts_packet_info(struct packet_manage reg_data); } +/* pm_grace_period_0_supported - whether firmware tolerates a CWSR grace + * period of 0 on this ASIC. + * + * The debugger may request a grace period of 0 via AMDKFD_IOC_DBG_TRAP. + * Most firmware revisions locked up on an infinite (0) grace period, so + * an earlier change clamped 0 to 1 for all ASICs. Firmware has since been + * fixed on most ASICs; return true only where the running MEC firmware is + * known to handle 0. The +32768 offset on IP_VERSION(9, 0, 1) accounts for + * the SR-IOV firmware version encoding. + * + * Navi3x (gfx11) and MI350 (IP_VERSION(9, 5, 0)) support a grace period of + * 0 in every firmware revision and need no version check. MI100 + * (IP_VERSION(9, 4, 1)) never received the fix and is intentionally kept + * clamped. Any unlisted or future ASIC defaults to the safe behaviour + * (clamp). + */ +static bool pm_grace_period_0_supported(struct packet_manager *pm) +{ + struct kfd_node *dev = pm->dqm->dev; + uint32_t mec_fw_version = dev->kfd->mec_fw_version; + + /* Navi3x (gfx11) always supports a grace period of 0. */ + if (KFD_GC_VERSION(dev) >= IP_VERSION(11, 0, 0) && + KFD_GC_VERSION(dev) < IP_VERSION(12, 0, 0)) + return true; + + switch (KFD_GC_VERSION(dev)) { + case IP_VERSION(9, 0, 1): + return mec_fw_version >= 461 + 32768; + case IP_VERSION(9, 1, 0): + case IP_VERSION(9, 2, 1): + case IP_VERSION(9, 2, 2): + case IP_VERSION(9, 3, 0): + case IP_VERSION(9, 4, 0): + return mec_fw_version >= 461; + /* MI100/Arcturus never received the firmware fix; keep clamped. */ + case IP_VERSION(9, 4, 1): + return false; + case IP_VERSION(9, 4, 2): + return mec_fw_version >= 63; + case IP_VERSION(9, 4, 3): + case IP_VERSION(9, 4, 4): + return mec_fw_version >= 96; + /* MI350 supports a grace period of 0 in every firmware revision. */ + case IP_VERSION(9, 5, 0): + return true; + case IP_VERSION(10, 1, 10): + case IP_VERSION(10, 1, 2): + case IP_VERSION(10, 1, 1): + return mec_fw_version >= 146; + case IP_VERSION(10, 3, 0): + case IP_VERSION(10, 3, 2): + case IP_VERSION(10, 3, 1): + case IP_VERSION(10, 3, 4): + case IP_VERSION(10, 3, 5): + return mec_fw_version >= 93; + default: + return false; + } +} + /* pm_config_dequeue_wait_counts_v9: Builds WRITE_DATA packet with * register/value for configuring dequeue wait counts * @@ -357,11 +418,12 @@ static int pm_config_dequeue_wait_counts_v9(struct packet_manager *pm, break; case KFD_DEQUEUE_WAIT_SET_SCH_WAVE: - /* The CP cannot handle value 0 and it will result in - * an infinite grace period being set so set to 1 to prevent this. Also - * avoid debugger API breakage as it sets 0 and expects a low value. + /* A grace period of 0 requests an infinite CWSR grace period. + * Older firmware locks up on this, so clamp to 1 unless the ASIC + * firmware is known to handle 0. Also avoid debugger API breakage + * as it sets 0 and expects a low value. */ - if (!value) + if (!value && !pm_grace_period_0_supported(pm)) value = 1; pm_build_dequeue_wait_counts_packet_info(pm, value, 0, ®_offset, ®_data); break; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c index 97e77a5a35e5..8054e4fe0381 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c @@ -383,7 +383,7 @@ int pqm_create_queue(struct process_queue_manager *pqm, false); if (retval) { dev_err(dev->adev->dev, "failed to allocate process context bo\n"); - return retval; + goto err_allocate_pqn; } memset(pdd->proc_ctx_cpu_ptr, 0, AMDGPU_MES_PROC_CTX_SIZE); } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index 28354a4e5dd5..b249e7d1af48 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c @@ -23,6 +23,7 @@ */ #include +#include #include "kfd_priv.h" #include "kfd_topology.h" #include "kfd_svm.h" @@ -235,7 +236,7 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope struct kfd_topology_device *topo_dev; u64 expected_queue_size; struct amdgpu_vm *vm; - u32 total_cwsr_size; + u64 total_cwsr_size; int err; topo_dev = kfd_topology_device_by_id(pdd->dev->id); @@ -287,7 +288,7 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope } err = kfd_queue_buffer_get(vm, (void *)properties->eop_ring_buffer_address, &properties->eop_buf_bo, - ALIGN(properties->eop_ring_buffer_size, PAGE_SIZE)); + ALIGN((u64)properties->eop_ring_buffer_size, PAGE_SIZE)); if (err) goto out_err_unreserve; } @@ -308,8 +309,14 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope goto out_err_unreserve; } - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); + total_cwsr_size = (u64)properties->ctx_save_restore_area_size + + topo_dev->node_props.debug_memory_size; + if (check_mul_overflow(total_cwsr_size, + NUM_XCC(pdd->dev->xcc_mask), + &total_cwsr_size)) { + err = -EINVAL; + goto out_err_unreserve; + } total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); err = kfd_queue_buffer_get(vm, (void *)properties->ctx_save_restore_area_address, @@ -344,7 +351,7 @@ out_err_release: int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_properties *properties) { struct kfd_topology_device *topo_dev; - u32 total_cwsr_size; + u64 total_cwsr_size; kfd_queue_buffer_put(&properties->wptr_bo); kfd_queue_buffer_put(&properties->rptr_bo); @@ -355,8 +362,12 @@ int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_prope topo_dev = kfd_topology_device_by_id(pdd->dev->id); if (!topo_dev) return -EINVAL; - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); + total_cwsr_size = (u64)properties->ctx_save_restore_area_size + + topo_dev->node_props.debug_memory_size; + if (check_mul_overflow(total_cwsr_size, + NUM_XCC(pdd->dev->xcc_mask), + &total_cwsr_size)) + return -EINVAL; total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); kfd_queue_buffer_svm_put(pdd, properties->ctx_save_restore_area_address, total_cwsr_size); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index 30ad10bbd47e..18abef8918ce 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -69,7 +69,6 @@ struct criu_svm_metadata { struct kfd_criu_svm_range_priv_data data; }; -static void svm_range_evict_svm_bo_worker(struct work_struct *work); static bool svm_range_cpu_invalidate_pagetables(struct mmu_interval_notifier *mni, const struct mmu_notifier_range *range, @@ -97,7 +96,7 @@ static void svm_range_unlink(struct svm_range *prange) if (prange->svm_bo) { spin_lock(&prange->svm_bo->list_lock); - list_del(&prange->svm_bo_list); + list_del_init(&prange->svm_bo_list); spin_unlock(&prange->svm_bo->list_lock); } @@ -286,6 +285,17 @@ static void svm_range_free(struct svm_range *prange, bool do_unmap) pr_debug("svms 0x%p prange 0x%p [0x%lx 0x%lx]\n", prange->svms, prange, prange->start, prange->last); + /* Unlink from range_list; no-op if already unlinked. */ + if (prange->svm_bo) { + spin_lock(&prange->svm_bo->list_lock); + list_del_init(&prange->svm_bo_list); + spin_unlock(&prange->svm_bo->list_lock); + } + + /* Wait for any in-flight eviction of this range to finish. */ + mutex_lock(&prange->migrate_mutex); + mutex_unlock(&prange->migrate_mutex); + svm_range_vram_node_free(prange); if (do_unmap) svm_range_dma_unmap(prange); @@ -380,8 +390,10 @@ static bool svm_bo_ref_unless_zero(struct svm_range_bo *svm_bo) static void svm_range_bo_release(struct kref *kref) { struct svm_range_bo *svm_bo; + struct amdgpu_bo *bo; svm_bo = container_of(kref, struct svm_range_bo, kref); + bo = &svm_bo->bo; pr_debug("svm_bo 0x%p\n", svm_bo); spin_lock(&svm_bo->list_lock); @@ -407,12 +419,12 @@ static void svm_range_bo_release(struct kref *kref) } spin_unlock(&svm_bo->list_lock); - if (mmget_not_zero(svm_bo->eviction_fence->mm)) { + if (mmget_not_zero(svm_bo->mm)) { struct kfd_process_device *pdd; struct kfd_process *p; struct mm_struct *mm; - mm = svm_bo->eviction_fence->mm; + mm = svm_bo->mm; /* * The forked child process takes svm_bo device pages ref, svm_bo could be * released after parent process is gone. @@ -421,18 +433,13 @@ static void svm_range_bo_release(struct kref *kref) if (p) { pdd = kfd_get_process_device_data(svm_bo->node, p); if (pdd) - atomic64_sub(amdgpu_bo_size(svm_bo->bo), &pdd->vram_usage); + atomic64_sub(amdgpu_bo_size(bo), &pdd->vram_usage); kfd_unref_process(p); } mmput(mm); } - if (!dma_fence_is_signaled(&svm_bo->eviction_fence->base)) - /* We're not in the eviction worker. Signal the fence. */ - dma_fence_signal(&svm_bo->eviction_fence->base); - dma_fence_put(&svm_bo->eviction_fence->base); - amdgpu_bo_unref(&svm_bo->bo); - kfree(svm_bo); + amdgpu_bo_unref(&bo); } static void svm_range_bo_wq_release(struct work_struct *work) @@ -494,20 +501,11 @@ svm_range_validate_svm_bo(struct kfd_node *node, struct svm_range *prange) return false; } if (READ_ONCE(prange->svm_bo->evicting)) { - struct dma_fence *f; - struct svm_range_bo *svm_bo; /* The BO is getting evicted, * we need to get a new one */ mutex_unlock(&prange->lock); - svm_bo = prange->svm_bo; - f = dma_fence_get(&svm_bo->eviction_fence->base); svm_range_bo_unref(prange->svm_bo); - /* wait for the fence to avoid long spin-loop - * at list_empty_careful - */ - dma_fence_wait(f, false); - dma_fence_put(f); } else { /* The BO was still around and we got * a new reference to it @@ -516,7 +514,7 @@ svm_range_validate_svm_bo(struct kfd_node *node, struct svm_range *prange) pr_debug("reuse old bo svms 0x%p [0x%lx 0x%lx]\n", prange->svms, prange->start, prange->last); - prange->ttm_res = prange->svm_bo->bo->tbo.resource; + prange->ttm_res = prange->svm_bo->bo.tbo.resource; return true; } @@ -535,19 +533,22 @@ svm_range_validate_svm_bo(struct kfd_node *node, struct svm_range *prange) return false; } -static struct svm_range_bo *svm_range_bo_new(void) +#define to_svm_range_bo(bo) container_of((bo), struct svm_range_bo, bo) + +void svm_range_bo_destroy(struct ttm_buffer_object *tbo) { - struct svm_range_bo *svm_bo; + struct amdgpu_bo *bo = ttm_to_amdgpu_bo(tbo); + struct svm_range_bo *svm_bo = to_svm_range_bo(bo); - svm_bo = kzalloc_obj(*svm_bo); - if (!svm_bo) - return NULL; - - kref_init(&svm_bo->kref); - INIT_LIST_HEAD(&svm_bo->range_list); - spin_lock_init(&svm_bo->list_lock); - - return svm_bo; + drm_gem_object_release(&bo->tbo.base); + /* + * svm_bo->mm is only set once the BO is fully created. If + * ttm_bo_init_reserved() fails (e.g. no VRAM could be evicted), it + * calls this destroy callback with mm still NULL, so guard the drop. + */ + if (svm_bo->mm) + mmdrop(svm_bo->mm); + kvfree(svm_bo); } int @@ -557,7 +558,6 @@ svm_range_vram_node_new(struct kfd_node *node, struct svm_range *prange, struct kfd_process_device *pdd; struct amdgpu_bo_param bp; struct svm_range_bo *svm_bo; - struct amdgpu_bo_user *ubo; struct amdgpu_bo *bo; struct kfd_process *p; struct mm_struct *mm; @@ -571,27 +571,16 @@ svm_range_vram_node_new(struct kfd_node *node, struct svm_range *prange, if (svm_range_validate_svm_bo(node, prange)) return 0; - svm_bo = svm_range_bo_new(); - if (!svm_bo) { - pr_debug("failed to alloc svm bo\n"); - return -ENOMEM; - } mm = get_task_mm(p->lead_thread); if (!mm) { pr_debug("failed to get mm\n"); - kfree(svm_bo); return -ESRCH; } - svm_bo->node = node; - svm_bo->eviction_fence = - amdgpu_amdkfd_fence_create(dma_fence_context_alloc(1), - mm, - svm_bo, p->context_id); - mmput(mm); - INIT_WORK(&svm_bo->eviction_work, svm_range_evict_svm_bo_worker); - svm_bo->evicting = 0; + memset(&bp, 0, sizeof(bp)); bp.size = prange->npages * PAGE_SIZE; + bp.bo_ptr_size = sizeof(struct svm_range_bo); + bp.destroy = svm_range_bo_destroy; bp.byte_align = PAGE_SIZE; bp.domain = AMDGPU_GEM_DOMAIN_VRAM; bp.flags = AMDGPU_GEM_CREATE_NO_CPU_ACCESS; @@ -602,12 +591,23 @@ svm_range_vram_node_new(struct kfd_node *node, struct svm_range *prange, if (node->xcp) bp.xcp_id_plus1 = node->xcp->id + 1; - r = amdgpu_bo_create_user(node->adev, &bp, &ubo); + r = amdgpu_bo_create(node->adev, &bp, &bo); if (r) { pr_debug("failed %d to create bo\n", r); + mmput(mm); goto create_bo_failed; } - bo = &ubo->bo; + + svm_bo = to_svm_range_bo(bo); + svm_bo->evicting = 0; + kref_init(&svm_bo->kref); + INIT_LIST_HEAD(&svm_bo->range_list); + spin_lock_init(&svm_bo->list_lock); + + svm_bo->node = node; + svm_bo->mm = mm; + mmgrab(svm_bo->mm); + mmput(mm); pr_debug("alloc bo at offset 0x%lx size 0x%lx on partition %d\n", bo->tbo.resource->start << PAGE_SHIFT, bp.size, @@ -628,16 +628,8 @@ svm_range_vram_node_new(struct kfd_node *node, struct svm_range *prange, } } - r = dma_resv_reserve_fences(bo->tbo.base.resv, TTM_NUM_MOVE_FENCES); - if (r) { - amdgpu_bo_unreserve(bo); - goto reserve_bo_failed; - } - amdgpu_bo_fence(bo, &svm_bo->eviction_fence->base, true); - amdgpu_bo_unreserve(bo); - svm_bo->bo = bo; prange->svm_bo = svm_bo; prange->ttm_res = bo->tbo.resource; prange->offset = 0; @@ -655,9 +647,6 @@ svm_range_vram_node_new(struct kfd_node *node, struct svm_range *prange, reserve_bo_failed: amdgpu_bo_unref(&bo); create_bo_failed: - dma_fence_put(&svm_bo->eviction_fence->base); - kfree(svm_bo); - prange->ttm_res = NULL; return r; } @@ -3631,39 +3620,37 @@ svm_range_trigger_migration(struct mm_struct *mm, struct svm_range *prange, return 0; } -int svm_range_schedule_evict_svm_bo(struct amdgpu_amdkfd_fence *fence) +int svm_range_evict_svm_bo(struct amdgpu_bo *bo) { - /* Dereferencing fence->svm_bo is safe here because the fence hasn't - * signaled yet and we're under the protection of the fence->lock. - * After the fence is signaled in svm_range_bo_release, we cannot get - * here any more. - * - * Reference is dropped in svm_range_evict_svm_bo_worker. - */ - if (svm_bo_ref_unless_zero(fence->svm_bo)) { - WRITE_ONCE(fence->svm_bo->evicting, 1); - schedule_work(&fence->svm_bo->eviction_work); - } - - return 0; -} - -static void svm_range_evict_svm_bo_worker(struct work_struct *work) -{ - struct svm_range_bo *svm_bo; + struct svm_range_bo *svm_bo = to_svm_range_bo(bo); struct mm_struct *mm; int r = 0; - svm_bo = container_of(work, struct svm_range_bo, eviction_work); + if (!svm_bo_ref_unless_zero(svm_bo)) + return 0; - if (mmget_not_zero(svm_bo->eviction_fence->mm)) { - mm = svm_bo->eviction_fence->mm; - } else { + if (!mmget_not_zero(svm_bo->mm)) { svm_range_bo_unref(svm_bo); - return; + return 0; + } + mm = svm_bo->mm; + + /* + * Called with the BO reserved; lock order is mmap_lock -> BO + * reservation. Only trylock mmap to invert that order safely: a + * trylock never blocks, so it cannot deadlock against the reservation + * and lockdep records no reverse dependency. On contention return + * -EBUSY so TTM skips this BO. + */ + if (!mmap_read_trylock(mm)) { + pr_debug("skip eviction, contended to take mmap_read lock\n"); + mmput_async(mm); + svm_range_bo_unref(svm_bo); + return -EBUSY; } - mmap_read_lock(mm); + WRITE_ONCE(svm_bo->evicting, 1); + spin_lock(&svm_bo->list_lock); while (!list_empty(&svm_bo->range_list) && !r) { struct svm_range *prange = @@ -3671,13 +3658,24 @@ static void svm_range_evict_svm_bo_worker(struct work_struct *work) struct svm_range, svm_bo_list); int retries = 3; + /* + * Trylock migrate_mutex under list_lock, before unlinking the + * range, so svm_range_free() cannot free it under us. On + * contention the owner is migrating this range; skip the BO. + */ + if (!mutex_trylock(&prange->migrate_mutex)) { + pr_debug("skip eviction, contended migrate_mutex\n"); + /* Clear evicting so the BO keeps being reused. */ + WRITE_ONCE(svm_bo->evicting, 0); + r = -EBUSY; + break; + } list_del_init(&prange->svm_bo_list); spin_unlock(&svm_bo->list_lock); pr_debug("svms 0x%p [0x%lx 0x%lx]\n", prange->svms, prange->start, prange->last); - mutex_lock(&prange->migrate_mutex); do { /* migrate all vram pages in this prange to sys ram * after that prange->actual_loc should be zero @@ -3701,15 +3699,16 @@ static void svm_range_evict_svm_bo_worker(struct work_struct *work) } spin_unlock(&svm_bo->list_lock); mmap_read_unlock(mm); - mmput(mm); - - dma_fence_signal(&svm_bo->eviction_fence->base); + /* Defer mmput: exit_mmap() must not run under the BO reservation. */ + mmput_async(mm); /* This is the last reference to svm_bo, after svm_range_vram_node_free * has been called in svm_migrate_vram_to_ram */ WARN_ONCE(!r && kref_read(&svm_bo->kref) != 1, "This was not the last reference\n"); svm_range_bo_unref(svm_bo); + + return r; } static int diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h index a63dfc95b602..284c4cd5aa0f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h @@ -39,12 +39,11 @@ ((adev)->hive ? (void *)(adev)->hive : (void *)(adev)) struct svm_range_bo { - struct amdgpu_bo *bo; + struct amdgpu_bo bo; struct kref kref; struct list_head range_list; /* all svm ranges shared this bo */ spinlock_t list_lock; - struct amdgpu_amdkfd_fence *eviction_fence; - struct work_struct eviction_work; + struct mm_struct *mm; uint32_t evicting; struct work_struct release_work; struct kfd_node *node; @@ -169,13 +168,15 @@ struct svm_range *svm_range_from_addr(struct svm_range_list *svms, struct svm_range **parent); struct kfd_node *svm_range_get_node_by_id(struct svm_range *prange, uint32_t gpu_id); +void svm_range_bo_destroy(struct ttm_buffer_object *tbo); int svm_range_vram_node_new(struct kfd_node *node, struct svm_range *prange, bool clear); void svm_range_vram_node_free(struct svm_range *prange); int svm_range_restore_pages(struct amdgpu_device *adev, unsigned int pasid, uint32_t vmid, uint32_t node_id, uint64_t addr, uint64_t ts, bool write_fault); -int svm_range_schedule_evict_svm_bo(struct amdgpu_amdkfd_fence *fence); +int svm_range_evict_svm_bo(struct amdgpu_bo *bo); + void svm_range_add_list_work(struct svm_range_list *svms, struct svm_range *prange, struct mm_struct *mm, enum svm_work_list_ops op); @@ -229,11 +230,13 @@ static inline int svm_range_restore_pages(struct amdgpu_device *adev, return -EFAULT; } -static inline int svm_range_schedule_evict_svm_bo( - struct amdgpu_amdkfd_fence *fence) +static inline void svm_range_bo_destroy(struct ttm_buffer_object *tbo) { - WARN_ONCE(1, "SVM eviction fence triggered, but SVM is disabled"); - return -EINVAL; +} + +static inline int svm_range_evict_svm_bo(struct amdgpu_bo *bo) +{ + return 0; } static inline void svm_range_get_info(struct kfd_process *p, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index d83878e35b61..d1faf2d8370f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -45,7 +45,9 @@ AMDGPUDM = \ amdgpu_dm_backlight.o \ amdgpu_dm_audio.o \ amdgpu_dm_dmub.o \ - amdgpu_dm_connector.o + amdgpu_dm_connector.o \ + amdgpu_dm_freesync.o \ + amdgpu_dm_cursor.o ifdef CONFIG_DRM_AMD_DC_FP AMDGPUDM += dc_fpu.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 67b825cbb88f..e421c3c81778 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -70,7 +70,9 @@ #include "amdgpu_dm_audio.h" #include "amdgpu_dm_dmub.h" #include "amdgpu_dm_connector.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "amdgpu_dm_pp_smu.h" +#include "amdgpu_dm_freesync.h" +#include "amdgpu_dm_cursor.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -127,7 +129,6 @@ MODULE_FIRMWARE(FIRMWARE_NAVI12_DMCU); /* basic init/fini API */ static int amdgpu_dm_init(struct amdgpu_device *adev); static void amdgpu_dm_fini(struct amdgpu_device *adev); -static void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); /* * initializes drm_device display related structures, based on the information @@ -142,15 +143,11 @@ static void amdgpu_dm_destroy_drm_device(struct amdgpu_display_manager *dm); static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_commit *state); static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state); -static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context); +STATIC_IFN_KUNIT void dm_enable_per_frame_crtc_master_sync(struct dc_state *context); static int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); -STATIC_IFN_KUNIT bool -is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, - struct drm_crtc_state *new_crtc_state); - static inline void amdgpu_dm_exit_ips_for_hw_access(struct dc *dc) { if (dc->ctx->dmub_srv && !dc->ctx->dmub_srv->idle_exit_counter) @@ -170,7 +167,7 @@ static inline void amdgpu_dm_exit_ips_for_hw_access(struct dc *dc) * @return * Counter for vertical blanks */ -static u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc) +STATIC_IFN_KUNIT u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc) { struct amdgpu_crtc *acrtc = NULL; @@ -187,9 +184,10 @@ static u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc) return dc_stream_get_vblank_counter(acrtc->dm_irq_params.stream); } +EXPORT_IF_KUNIT(dm_vblank_get_counter); -static int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, - u32 *vbl, u32 *position) +STATIC_IFN_KUNIT int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, + u32 *vbl, u32 *position) { u32 v_blank_start = 0, v_blank_end = 0, h_position = 0, v_position = 0; struct amdgpu_crtc *acrtc = NULL; @@ -224,38 +222,28 @@ static int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, return 0; } +EXPORT_IF_KUNIT(dm_crtc_get_scanoutpos); -static bool dm_is_idle(struct amdgpu_ip_block *ip_block) +STATIC_IFN_KUNIT bool dm_is_idle(struct amdgpu_ip_block *ip_block) { /* XXX todo */ return true; } +EXPORT_IF_KUNIT(dm_is_idle); -static int dm_wait_for_idle(struct amdgpu_ip_block *ip_block) +STATIC_IFN_KUNIT int dm_wait_for_idle(struct amdgpu_ip_block *ip_block) { /* XXX todo */ return 0; } +EXPORT_IF_KUNIT(dm_wait_for_idle); -static int dm_soft_reset(struct amdgpu_ip_block *ip_block) +STATIC_IFN_KUNIT int dm_soft_reset(struct amdgpu_ip_block *ip_block) { /* XXX todo */ return 0; } - -STATIC_IFN_KUNIT bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, - struct dm_crtc_state *new_state) -{ - if (new_state->stream->adjust.timing_adjust_pending) - return true; - if (new_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED) - return true; - else if (amdgpu_dm_crtc_vrr_active(old_state) != amdgpu_dm_crtc_vrr_active(new_state)) - return true; - else - return false; -} -EXPORT_IF_KUNIT(is_dc_timing_adjust_needed); +EXPORT_IF_KUNIT(dm_soft_reset); /* * DC will program planes with their z-order determined by their ordering @@ -311,17 +299,19 @@ static inline bool update_planes_and_stream_adapter(struct dc *dc, stream_update); } -static int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block, - enum amd_clockgating_state state) +STATIC_IFN_KUNIT int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block, + enum amd_clockgating_state state) { return 0; } +EXPORT_IF_KUNIT(dm_set_clockgating_state); -static int dm_set_powergating_state(struct amdgpu_ip_block *ip_block, - enum amd_powergating_state state) +STATIC_IFN_KUNIT int dm_set_powergating_state(struct amdgpu_ip_block *ip_block, + enum amd_powergating_state state) { return 0; } +EXPORT_IF_KUNIT(dm_set_powergating_state); /* Prototypes of private functions */ static int dm_early_init(struct amdgpu_ip_block *ip_block); @@ -404,91 +394,6 @@ static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_ } -struct amdgpu_stutter_quirk { - u16 chip_vendor; - u16 chip_device; - u16 subsys_vendor; - u16 subsys_device; - u8 revision; -}; - -static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = { - /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */ - { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 }, - { 0, 0, 0, 0, 0 }, -}; - -static bool dm_should_disable_stutter(struct pci_dev *pdev) -{ - const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list; - - while (p && p->chip_device != 0) { - if (pdev->vendor == p->chip_vendor && - pdev->device == p->chip_device && - pdev->subsystem_vendor == p->subsys_vendor && - pdev->subsystem_device == p->subsys_device && - pdev->revision == p->revision) { - return true; - } - ++p; - } - return false; -} - - -void* -dm_allocate_gpu_mem( - struct amdgpu_device *adev, - enum dc_gpu_mem_alloc_type type, - size_t size, - long long *addr) -{ - struct dal_allocation *da; - u32 domain = (type == DC_MEM_ALLOC_TYPE_GART) ? - AMDGPU_GEM_DOMAIN_GTT : AMDGPU_GEM_DOMAIN_VRAM; - int ret; - - da = kzalloc_obj(struct dal_allocation); - if (!da) - return NULL; - - ret = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE, - domain, &da->bo, - &da->gpu_addr, &da->cpu_ptr); - - *addr = da->gpu_addr; - - if (ret) { - kfree(da); - return NULL; - } - - /* add da to list in dm */ - list_add(&da->list, &adev->dm.da_list); - - return da->cpu_ptr; -} - -void -dm_free_gpu_mem( - struct amdgpu_device *adev, - enum dc_gpu_mem_alloc_type type, - void *pvMem) -{ - struct dal_allocation *da; - - /* walk the da list in DM */ - list_for_each_entry(da, &adev->dm.da_list, list) { - if (pvMem == da->cpu_ptr) { - amdgpu_bo_free_kernel(&da->bo, &da->gpu_addr, &da->cpu_ptr); - list_del(&da->list); - kfree(da); - break; - } - } - -} - static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) { struct mod_power_init_params init_data[MAX_NUM_EDP]; @@ -779,6 +684,14 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) dc_hardware_init(adev->dm.dc); + /* GOP/vBIOS may leave an OPTC enabled for a display present at power-on + * but no longer driven (e.g. an external DP unplugged at boot). Such a + * dangling pipe keeps DCN out of idle and blocks s0i3. Power it down + * here when nothing needs to be preserved. + */ + if (adev->flags & AMD_IS_APU) + dc_disable_dangling_timing_generators(adev->dm.dc); + adev->dm.hpd_rx_offload_wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev); if (!adev->dm.hpd_rx_offload_wq) { drm_err(adev_to_drm(adev), "failed to create hpd rx offload workqueue.\n"); @@ -1010,14 +923,11 @@ static void amdgpu_dm_fini(struct amdgpu_device *adev) adev->dm.hpd_rx_offload_wq = NULL; } + amdgpu_dm_irq_fini(adev); + /* DC Destroy TODO: Replace destroy DAL */ if (adev->dm.dc) dc_destroy(&adev->dm.dc); - /* - * TODO: pageflip, vlank interrupt - * - * amdgpu_dm_irq_fini(adev); - */ if (adev->dm.cgs_device) { amdgpu_cgs_destroy_device(adev->dm.cgs_device); @@ -1393,57 +1303,6 @@ static void s3_handle_mst(struct drm_device *dev, bool suspend) drm_connector_list_iter_end(&iter); } -static int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev) -{ - int ret = 0; - - /* This interface is for dGPU Navi1x.Linux dc-pplib interface depends - * on window driver dc implementation. - * For Navi1x, clock settings of dcn watermarks are fixed. the settings - * should be passed to smu during boot up and resume from s3. - * boot up: dc calculate dcn watermark clock settings within dc_create, - * dcn20_resource_construct - * then call pplib functions below to pass the settings to smu: - * smu_set_watermarks_for_clock_ranges - * smu_set_watermarks_table - * navi10_set_watermarks_table - * smu_write_watermarks_table - * - * For Renoir, clock settings of dcn watermark are also fixed values. - * dc has implemented different flow for window driver: - * dc_hardware_init / dc_set_power_state - * dcn10_init_hw - * notify_wm_ranges - * set_wm_ranges - * -- Linux - * smu_set_watermarks_for_clock_ranges - * renoir_set_watermarks_table - * smu_write_watermarks_table - * - * For Linux, - * dc_hardware_init -> amdgpu_dm_init - * dc_set_power_state --> dm_resume - * - * therefore, this function apply to navi10/12/14 but not Renoir - * * - */ - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(2, 0, 2): - case IP_VERSION(2, 0, 0): - break; - default: - return 0; - } - - ret = amdgpu_dpm_write_watermarks_table(adev); - if (ret) { - drm_err(adev_to_drm(adev), "Failed to update WMTABLE!\n"); - return ret; - } - - return 0; -} - static int dm_oem_i2c_hw_init(struct amdgpu_device *adev) { struct amdgpu_display_manager *dm = &adev->dm; @@ -1496,17 +1355,26 @@ static int dm_hw_init(struct amdgpu_ip_block *ip_block) struct amdgpu_device *adev = ip_block->adev; int r; + adev->dm.i2c_devres_group = devres_open_group(adev->dev, NULL, GFP_KERNEL); + if (!adev->dm.i2c_devres_group) + return -ENOMEM; + /* Create DAL display manager */ r = amdgpu_dm_init(adev); if (r) - return r; + goto err_release_i2c; amdgpu_dm_hpd_init(adev); r = dm_oem_i2c_hw_init(adev); if (r) drm_info(adev_to_drm(adev), "Failed to add OEM i2c bus\n"); + devres_close_group(adev->dev, adev->dm.i2c_devres_group); return 0; + +err_release_i2c: + devres_release_group(adev->dev, adev->dm.i2c_devres_group); + return r; } /** @@ -1521,9 +1389,11 @@ static int dm_hw_fini(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; + if (adev->dm.i2c_devres_group) + devres_release_group(adev->dev, adev->dm.i2c_devres_group); + amdgpu_dm_hpd_fini(adev); - amdgpu_dm_irq_fini(adev); amdgpu_dm_fini(adev); return 0; } @@ -1541,7 +1411,8 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, acrtc = amdgpu_dm_get_crtc_by_otg_inst( adev, state->stream_status[i].primary_otg_inst); - if (acrtc && state->stream_status[i].plane_count != 0) { + if (acrtc && state->stream_status[i].plane_count != 0 && + amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; if (rc) @@ -1569,6 +1440,13 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, */ if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); + + } else if (acrtc && state->stream_status[i].plane_count != 0) { + /* DCN only needs to toggle VUPDATE_NO_LOCK */ + rc = amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, enable); + if (rc) + drm_warn(adev_to_drm(adev), "Failed to %sable vupdate interrupt\n", + enable ? "en" : "dis"); } } @@ -1647,7 +1525,7 @@ static void dm_destroy_cached_state(struct amdgpu_device *adev) for_each_new_crtc_in_state(dm->cached_state, crtc, new_crtc_state, i) { new_crtc_state->active_changed = true; dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); - reset_freesync_config_for_crtc(dm_new_crtc_state); + amdgpu_dm_reset_freesync_config_for_crtc(dm_new_crtc_state); } /* @@ -1886,6 +1764,7 @@ void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, ppatch->wait_after_dpcd_poweroff_ms / 1000); } } +EXPORT_IF_KUNIT(amdgpu_dm_apply_delay_after_dpcd_poweroff); /** * amdgpu_dm_dump_links_and_sinks - Debug dump of all DC links and their sinks @@ -2254,7 +2133,7 @@ int dm_atomic_get_state(struct drm_atomic_commit *state, return 0; } -static struct dm_atomic_state * +STATIC_IFN_KUNIT struct dm_atomic_state * dm_atomic_get_new_state(struct drm_atomic_commit *state) { struct drm_device *dev = state->dev; @@ -2271,6 +2150,7 @@ dm_atomic_get_new_state(struct drm_atomic_commit *state) return NULL; } +EXPORT_IF_KUNIT(dm_atomic_get_new_state); static struct drm_private_state * dm_atomic_duplicate_state(struct drm_private_obj *obj) @@ -2296,8 +2176,8 @@ dm_atomic_duplicate_state(struct drm_private_obj *obj) return &new_state->base; } -static void dm_atomic_destroy_state(struct drm_private_obj *obj, - struct drm_private_state *state) +STATIC_IFN_KUNIT void dm_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) { struct dm_atomic_state *dm_state = to_dm_atomic_state(state); @@ -2306,6 +2186,7 @@ static void dm_atomic_destroy_state(struct drm_private_obj *obj, kfree(dm_state); } +EXPORT_IF_KUNIT(dm_atomic_destroy_state); static struct drm_private_state * dm_atomic_create_state(struct drm_private_obj *obj) @@ -2774,10 +2655,11 @@ static void amdgpu_dm_destroy_drm_device(struct amdgpu_display_manager *dm) * * Calculate and program the display watermarks and line buffer allocation. */ -static void dm_bandwidth_update(struct amdgpu_device *adev) +STATIC_IFN_KUNIT void dm_bandwidth_update(struct amdgpu_device *adev) { /* TODO: implement later */ } +EXPORT_IF_KUNIT(dm_bandwidth_update); static const struct amdgpu_display_funcs dm_display_funcs = { .bandwidth_update = dm_bandwidth_update, /* called unconditionally */ @@ -3035,7 +2917,6 @@ EXPORT_IF_KUNIT(fill_plane_color_attributes); static int fill_dc_plane_info_and_addr(struct amdgpu_device *adev, const struct drm_plane_state *plane_state, - const u64 tiling_flags, struct dc_plane_info *plane_info, struct dc_plane_address *address, bool tmz_surface) @@ -3133,7 +3014,7 @@ fill_dc_plane_info_and_addr(struct amdgpu_device *adev, return ret; ret = amdgpu_dm_plane_fill_plane_buffer_attributes(adev, afb, plane_info->format, - plane_info->rotation, tiling_flags, + plane_info->rotation, &plane_info->tiling_info, &plane_info->plane_size, &plane_info->dcc, address, @@ -3169,7 +3050,6 @@ static int fill_dc_plane_attributes(struct amdgpu_device *adev, dc_plane_state->scaling_quality = scaling_info.scaling_quality; ret = fill_dc_plane_info_and_addr(adev, plane_state, - afb->tiling_flags, &plane_info, &dc_plane_state->address, afb->tmz_surface); @@ -3369,62 +3249,6 @@ ffu: &flip_addrs->dirty_rect_count, true); } -void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev, - const struct drm_display_mode *mode, - const struct dm_connector_state *dm_state, - struct dc_stream_state *stream) -{ - enum amdgpu_rmx_type rmx_type; - - struct rect src = { 0 }; /* viewport in composition space*/ - struct rect dst = { 0 }; /* stream addressable area */ - - /* no mode. nothing to be done */ - if (!mode) - return; - - /* Full screen scaling by default */ - src.width = mode->hdisplay; - src.height = mode->vdisplay; - dst.width = stream->timing.h_addressable; - dst.height = stream->timing.v_addressable; - - if (dm_state) { - rmx_type = dm_state->scaling; - if (rmx_type == RMX_ASPECT || rmx_type == RMX_OFF) { - if (src.width * dst.height < - src.height * dst.width) { - /* height needs less upscaling/more downscaling */ - dst.width = src.width * - dst.height / src.height; - } else { - /* width needs less upscaling/more downscaling */ - dst.height = src.height * - dst.width / src.width; - } - } else if (rmx_type == RMX_CENTER) { - dst = src; - } - - dst.x = (stream->timing.h_addressable - dst.width) / 2; - dst.y = (stream->timing.v_addressable - dst.height) / 2; - - if (dm_state->underscan_enable) { - dst.x += dm_state->underscan_hborder / 2; - dst.y += dm_state->underscan_vborder / 2; - dst.width -= dm_state->underscan_hborder; - dst.height -= dm_state->underscan_vborder; - } - } - - stream->src = src; - stream->dst = dst; - - drm_dbg_kms(dev, "Destination Rectangle x:%d y:%d width:%d height:%d\n", - dst.x, dst.y, dst.width, dst.height); - -} - static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_commit *state, struct dc_state *dc_state, struct dsc_mst_fairness_vars *vars) @@ -3522,21 +3346,9 @@ static void manage_dm_interrupts(struct amdgpu_device *adev, if (acrtc_state) { timing = &acrtc_state->stream->timing; - if (amdgpu_ip_version(adev, DCE_HWIP, 0) >= - IP_VERSION(3, 2, 0) && - !(adev->flags & AMD_IS_APU)) { - /* - * DGPUs NV3x and newer that support idle optimizations - * experience intermittent flip-done timeouts on cursor - * updates. Restore 5s offdelay behavior for now. - * - * Discussion on the issue: - * https://lore.kernel.org/amd-gfx/20260217191632.1243826-1-sysdadmin@m1k.cloud/ - */ - config.offdelay_ms = 5000; - config.disable_immediate = false; - } else if (amdgpu_ip_version(adev, DCE_HWIP, 0) < - IP_VERSION(3, 5, 0)) { + if (amdgpu_ip_version(adev, DCE_HWIP, 0) < + IP_VERSION(3, 5, 0) || + !(adev->flags & AMD_IS_APU)) { /* * Older HW and DGPU have issues with instant off; * use a 2 frame offdelay. @@ -3555,14 +3367,22 @@ static void manage_dm_interrupts(struct amdgpu_device *adev, drm_crtc_vblank_on_config(&acrtc->base, &config); - /* Allow RX6xxx, RX7700, RX7800 GPUs to call amdgpu_irq_get.*/ + /* + * Since pflip_high_irq is no longer registered for DCN, grab an + * extra reference to vupdate irq instead to workaround this + * issue: + * https://gitlab.freedesktop.org/drm/amd/-/work_items/3936 + * + * The callbacks to drm_vblank_on/off should really take care of + * this though. + */ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(3, 0, 0): case IP_VERSION(3, 0, 2): case IP_VERSION(3, 0, 3): case IP_VERSION(3, 2, 0): - if (amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type)) - drm_err(dev, "DM_IRQ: Cannot get pageflip irq!\n"); + if (amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type)) + drm_err(dev, "DM_IRQ: Cannot get vupdate irq!\n"); #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) if (amdgpu_irq_get(adev, &adev->vline0_irq, irq_type)) drm_err(dev, "DM_IRQ: Cannot get vline0 irq!\n"); @@ -3580,8 +3400,8 @@ static void manage_dm_interrupts(struct amdgpu_device *adev, if (amdgpu_irq_put(adev, &adev->vline0_irq, irq_type)) drm_err(dev, "DM_IRQ: Cannot put vline0 irq!\n"); #endif - if (amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type)) - drm_err(dev, "DM_IRQ: Cannot put pageflip irq!\n"); + if (amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type)) + drm_err(dev, "DM_IRQ: Cannot put vupdate irq!\n"); } drm_crtc_vblank_off(&acrtc->base); @@ -3594,6 +3414,10 @@ static void dm_update_pflip_irq_state(struct amdgpu_device *adev, int irq_type = amdgpu_display_crtc_idx_to_irq_type(adev, acrtc->crtc_id); + /* GRPH_PFLIP is not used on DCN; nothing to reapply. */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) + return; + /** * This reads the current state for the IRQ and force reapplies * the setting to hardware. @@ -3620,12 +3444,13 @@ is_scaling_state_different(const struct dm_connector_state *dm_state, } EXPORT_IF_KUNIT(is_scaling_state_different); -static bool is_content_protection_different(struct drm_crtc_state *new_crtc_state, - struct drm_crtc_state *old_crtc_state, - struct drm_connector_state *new_conn_state, - struct drm_connector_state *old_conn_state, - const struct drm_connector *connector, - struct hdcp_workqueue *hdcp_w) +STATIC_IFN_KUNIT bool +is_content_protection_different(struct drm_crtc_state *new_crtc_state, + struct drm_crtc_state *old_crtc_state, + struct drm_connector_state *new_conn_state, + struct drm_connector_state *old_conn_state, + const struct drm_connector *connector, + struct hdcp_workqueue *hdcp_w) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state); @@ -3739,6 +3564,7 @@ static bool is_content_protection_different(struct drm_crtc_state *new_crtc_stat pr_debug("[HDCP_DM] DESIRED->ENABLED %s :false\n", __func__); return false; } +EXPORT_IF_KUNIT(is_content_protection_different); static void remove_stream(struct amdgpu_device *adev, struct amdgpu_crtc *acrtc, @@ -3750,226 +3576,6 @@ static void remove_stream(struct amdgpu_device *adev, acrtc->enabled = false; } -static void prepare_flip_isr(struct amdgpu_crtc *acrtc) -{ - - assert_spin_locked(&acrtc->base.dev->event_lock); - WARN_ON(acrtc->event); - - acrtc->event = acrtc->base.state->event; - - /* Set the flip status */ - acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED; - - /* Mark this event as consumed */ - acrtc->base.state->event = NULL; - - drm_dbg_state(acrtc->base.dev, - "crtc:%d, pflip_stat:AMDGPU_FLIP_SUBMITTED\n", - acrtc->crtc_id); -} - -static void update_freesync_state_on_stream( - struct amdgpu_display_manager *dm, - struct dm_crtc_state *new_crtc_state, - struct dc_stream_state *new_stream, - struct dc_plane_state *surface, - u32 flip_timestamp_in_us) -{ - struct mod_vrr_params vrr_params; - struct dc_info_packet vrr_infopacket = {0}; - struct amdgpu_device *adev = dm->adev; - struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc); - unsigned long flags; - bool pack_sdp_v1_3 = false; - struct amdgpu_dm_connector *aconn; - enum vrr_packet_type packet_type = PACKET_TYPE_VRR; - - if (!new_stream) - return; - - /* - * TODO: Determine why min/max totals and vrefresh can be 0 here. - * For now it's sufficient to just guard against these conditions. - */ - - if (!new_stream->timing.h_total || !new_stream->timing.v_total) - return; - - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - vrr_params = acrtc->dm_irq_params.vrr_params; - - if (surface) { - mod_freesync_handle_preflip( - dm->freesync_module, - surface, - new_stream, - flip_timestamp_in_us, - &vrr_params); - - if (adev->family < AMDGPU_FAMILY_AI && - amdgpu_dm_crtc_vrr_active(new_crtc_state)) { - mod_freesync_handle_v_update(dm->freesync_module, - new_stream, &vrr_params); - - /* Need to call this before the frame ends. */ - dc_stream_adjust_vmin_vmax(dm->dc, - new_crtc_state->stream, - &vrr_params.adjust); - } - } - - aconn = (struct amdgpu_dm_connector *)new_stream->dm_stream_context; - - if (aconn && (aconn->as_type == FREESYNC_TYPE_PCON_IN_WHITELIST || aconn->vsdb_info.replay_mode)) { - pack_sdp_v1_3 = aconn->pack_sdp_v1_3; - - if (aconn->vsdb_info.amd_vsdb_version == 1) - packet_type = PACKET_TYPE_FS_V1; - else if (aconn->vsdb_info.amd_vsdb_version == 2) - packet_type = PACKET_TYPE_FS_V2; - else if (aconn->vsdb_info.amd_vsdb_version == 3) - packet_type = PACKET_TYPE_FS_V3; - - mod_build_adaptive_sync_infopacket(new_stream, aconn->as_type, NULL, - &new_stream->adaptive_sync_infopacket); - } - - mod_freesync_build_vrr_infopacket( - dm->freesync_module, - new_stream, - &vrr_params, - packet_type, - TRANSFER_FUNC_UNKNOWN, - &vrr_infopacket, - pack_sdp_v1_3); - - new_crtc_state->freesync_vrr_info_changed |= - (memcmp(&new_crtc_state->vrr_infopacket, - &vrr_infopacket, - sizeof(vrr_infopacket)) != 0); - - acrtc->dm_irq_params.vrr_params = vrr_params; - new_crtc_state->vrr_infopacket = vrr_infopacket; - - new_stream->vrr_infopacket = vrr_infopacket; - new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); - - if (new_crtc_state->freesync_vrr_info_changed) - drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d", - new_crtc_state->base.crtc->base.id, - (int)new_crtc_state->base.vrr_enabled, - (int)vrr_params.state); - - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); -} - -static void update_stream_irq_parameters( - struct amdgpu_display_manager *dm, - struct dm_crtc_state *new_crtc_state) -{ - struct dc_stream_state *new_stream = new_crtc_state->stream; - struct mod_vrr_params vrr_params; - struct mod_freesync_config config = new_crtc_state->freesync_config; - struct amdgpu_device *adev = dm->adev; - struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc); - unsigned long flags; - - if (!new_stream) - return; - - /* - * TODO: Determine why min/max totals and vrefresh can be 0 here. - * For now it's sufficient to just guard against these conditions. - */ - if (!new_stream->timing.h_total || !new_stream->timing.v_total) - return; - - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - vrr_params = acrtc->dm_irq_params.vrr_params; - - if (new_crtc_state->vrr_supported && - config.min_refresh_in_uhz && - config.max_refresh_in_uhz) { - /* - * if freesync compatible mode was set, config.state will be set - * in atomic check - */ - if (config.state == VRR_STATE_ACTIVE_FIXED && config.fixed_refresh_in_uhz && - (!drm_atomic_crtc_needs_modeset(&new_crtc_state->base) || - new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED)) { - vrr_params.max_refresh_in_uhz = config.max_refresh_in_uhz; - vrr_params.min_refresh_in_uhz = config.min_refresh_in_uhz; - vrr_params.fixed_refresh_in_uhz = config.fixed_refresh_in_uhz; - vrr_params.state = VRR_STATE_ACTIVE_FIXED; - } else { - config.state = new_crtc_state->base.vrr_enabled ? - VRR_STATE_ACTIVE_VARIABLE : - VRR_STATE_INACTIVE; - } - } else { - config.state = VRR_STATE_UNSUPPORTED; - } - - mod_freesync_build_vrr_params(dm->freesync_module, - new_stream, - &config, &vrr_params); - - new_crtc_state->freesync_config = config; - /* Copy state for access from DM IRQ handler */ - acrtc->dm_irq_params.freesync_config = config; - acrtc->dm_irq_params.active_planes = new_crtc_state->active_planes; - acrtc->dm_irq_params.vrr_params = vrr_params; - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); -} - -static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, - struct dm_crtc_state *old_state, - struct dm_crtc_state *new_state) -{ - bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state); - bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state); - - if (!old_vrr_active && new_vrr_active) { - /* Transition VRR inactive -> active: - * While VRR is active, we must not disable vblank irq, as a - * reenable after disable would compute bogus vblank/pflip - * timestamps if it likely happened inside display front-porch. - * - * We also need vupdate irq for the actual core vblank handling - * at end of vblank. - */ - WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0); - WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0); - drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n", - __func__, new_state->base.crtc->base.id); - - scoped_guard(mutex, &dm->dc_lock) { - dc_exit_ips_for_hw_access(dm->dc); - amdgpu_dm_psr_set_event(dm, new_state->stream, true, - psr_event_vrr_transition, true); - amdgpu_dm_replay_set_event(dm, new_state->stream, true, - replay_event_vrr, true); - } - } else if (old_vrr_active && !new_vrr_active) { - /* Transition VRR active -> inactive: - * Allow vblank irq disable again for fixed refresh rate. - */ - WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0); - drm_crtc_vblank_put(new_state->base.crtc); - drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n", - __func__, new_state->base.crtc->base.id); - - scoped_guard(mutex, &dm->dc_lock) { - dc_exit_ips_for_hw_access(dm->dc); - amdgpu_dm_psr_set_event(dm, new_state->stream, false, - psr_event_vrr_transition, false); - amdgpu_dm_replay_set_event(dm, new_state->stream, false, - replay_event_vrr, false); - } - } -} - static void amdgpu_dm_commit_cursors(struct drm_atomic_commit *state) { struct drm_plane *plane; @@ -4104,6 +3710,58 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_display_manager *dm, } } +static void dm_arm_vblank_event(struct amdgpu_crtc *acrtc, + struct dm_crtc_state *acrtc_state, + bool pflip_update, + bool cursor_update) +{ + assert_spin_locked(&acrtc->base.dev->event_lock); + + if (!acrtc->base.state->event || acrtc_state->active_planes == 0) + return; + + if (pflip_update) { + WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE); + WARN_ON(acrtc->event); + + acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED; + acrtc->event = acrtc->base.state->event; + acrtc->base.state->event = NULL; + + drm_dbg_state(acrtc->base.dev, + "crtc:%d, pflip_stat:AMDGPU_FLIP_SUBMITTED\n", + acrtc->crtc_id); + } else if (cursor_update) { + acrtc->event = acrtc->base.state->event; + acrtc->base.state->event = NULL; + } +} + +/** + * dm_arm_vblank_event_pre_programming - Prepare for programming + * @acrtc: The amdgpu CRTC to prepare + * @acrtc_state: The new CRTC state + * @pflip_update: Whether a page flip is being programmed + * @cursor_update: Whether a cursor update is being programmed + * + * Grab a reference on the vblank counter if a page flip or cursor update is to + * be programmed. Do this before programming so the HW is not in any + * idle-optimized state (such as PSR). + */ +static void dm_arm_vblank_event_pre_programming(struct amdgpu_crtc *acrtc, + struct dm_crtc_state *acrtc_state, + bool pflip_update, + bool cursor_update) +{ + assert_spin_locked(&acrtc->base.dev->event_lock); + + if (!acrtc->base.state->event || acrtc_state->active_planes == 0) + return; + + if (pflip_update || cursor_update) + drm_crtc_vblank_get(&acrtc->base); +} + static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, struct drm_device *dev, struct amdgpu_display_manager *dm, @@ -4126,6 +3784,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, bool vrr_active = amdgpu_dm_crtc_vrr_active(acrtc_state); bool cursor_update = false; bool pflip_present = false; + bool immediate_flip = false; + bool flip_latched_during_prog = false; bool dirty_rects_changed = false; bool updated_planes_and_streams = false; struct { @@ -4227,7 +3887,6 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, fill_dc_plane_info_and_addr( dm->adev, new_plane_state, - afb->tiling_flags, &bundle->plane_infos[planes_count], &bundle->flip_addrs[planes_count].address, afb->tmz_surface); @@ -4288,6 +3947,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, acrtc_state->update_type == UPDATE_TYPE_FAST && get_mem_type(old_plane_state->fb) == get_mem_type(fb); + immediate_flip |= bundle->flip_addrs[planes_count].flip_immediate; + timestamp_ns = ktime_get_ns(); bundle->flip_addrs[planes_count].flip_timestamp_in_us = div_u64(timestamp_ns, 1000); bundle->surface_updates[planes_count].flip_addr = &bundle->flip_addrs[planes_count]; @@ -4300,7 +3961,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, } if (plane == pcrtc->primary) - update_freesync_state_on_stream( + amdgpu_dm_update_freesync_state_on_stream( dm, acrtc_state, acrtc_state->stream, @@ -4356,39 +4017,27 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, usleep_range(1000, 1100); } - /** - * Prepare the flip event for the pageflip interrupt to handle. - * - * This only works in the case where we've already turned on the - * appropriate hardware blocks (eg. HUBP) so in the transition case - * from 0 -> n planes we have to skip a hardware generated event - * and rely on sending it from software. - */ - if (acrtc_attach->base.state->event && - acrtc_state->active_planes > 0) { - drm_crtc_vblank_get(pcrtc); - - spin_lock_irqsave(&pcrtc->dev->event_lock, flags); - - WARN_ON(acrtc_attach->pflip_status != AMDGPU_FLIP_NONE); - prepare_flip_isr(acrtc_attach); - - spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); - } - if (acrtc_state->stream) { if (acrtc_state->freesync_vrr_info_changed) bundle->stream_update.vrr_infopacket = &acrtc_state->stream->vrr_infopacket; } - } else if (cursor_update && acrtc_state->active_planes > 0) { - spin_lock_irqsave(&pcrtc->dev->event_lock, flags); - if (acrtc_attach->base.state->event) { - drm_crtc_vblank_get(pcrtc); - acrtc_attach->event = acrtc_attach->base.state->event; - acrtc_attach->base.state->event = NULL; + } + + scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { + dm_arm_vblank_event_pre_programming(acrtc_attach, acrtc_state, + pflip_present, + cursor_update); + /* + * DCE depends on a combination of GRPH_FLIP, VLINE0, and + * VUPDATE for event delivery. Only GRPH_FLIP handler can send + * pflip events, and it only fires if HW latched to the flip. + * Maintain legacy behavior by arming event before programming. + */ + if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) { + dm_arm_vblank_event(acrtc_attach, acrtc_state, + pflip_present, cursor_update); } - spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); } /* Update the planes if changed or disable if we don't have any. */ @@ -4433,7 +4082,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, * re-adjust the min/max bounds now that DC doesn't handle this * as part of commit. */ - if (is_dc_timing_adjust_needed(dm_old_crtc_state, acrtc_state)) { + if (amdgpu_dm_is_dc_timing_adjust_needed(dm_old_crtc_state, acrtc_state)) { spin_lock_irqsave(&pcrtc->dev->event_lock, flags); dc_stream_adjust_vmin_vmax( dm->dc, acrtc_state->stream, @@ -4480,6 +4129,115 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, acrtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE) amdgpu_dm_commit_cursors(state); + /* + * DCN specific vblank handling + * ============================ + * + * With the event_lock held, arm the vblank event, and determine whether + * deliver it immediately, or in VUPDATE_NO_LOCK IRQ (i.e. HW latch + * point) handler. Do this *after* programming so that the IRQ handler + * will not deliver the event before HW laches onto the programmed + * values: + * + * Commit thread IRQ handler HW + * ----------------------------------------------------------------- + * arm_vblank_event() + * vupdate() + * vupdate_handler() + * cook_timestamp() + * # prev flip already latched, + * # so flip_latched == true. + * if event_armed && flip_latched: + * send_vblank_event() + * # sent before latch, **BAD!** + * hw_program() + * vupdate() + * **latch** + * + * There's a consequence of arming after: it's possible for HW to latch + * between start of HW programming and acrtc->event/pflip_status arming. + * When this happens, the IRQ handler will send the event on the next + * immediate latch point, even though HW has already latched. This is + * handled by optimistically checking for HW latch after programming, + * and if latched, send the event immediately: + * + * Commit thread IRQ handler HW + * ----------------------------------------------------------------- + * hw_program() + * vupdate() + * **latch** + * vupdate_handler() + * cook_timestamp() + * # event_armed == false + * # **no event sent!** + * arm_vblank_event() + * if flip_latched: + * **send_vblank_event()** + * disarm_vblank_event() + * + * The IRQ handler is expected to cook the timestamp, but we need to + * cook the timestamp before optimistic sending as well. That's because + * the following sequence is possible: + * + * Commit thread IRQ handler HW + * ----------------------------------------------------------------- + * hw_program() + * arm_vblank_event() + * vupdate() + * **latch** + * if flip_latched: + * # Need cook before send! + * **cook_timestamp()** + * send_vblank_event() + * disarm_vblank_event() + * vupdate_handler() + * cook_timestamp() + * # event_armed == false + * # no event sent! + * + * Cooking twice is OK, since DRM scanout accurate timestamps report A) + * the previous vactive start if currently in vactive, or B) the next + * vactive start if currently in vblank (see &get_vblank_counter). 'A)' + * is what we want for the optimistic send, and for 'B)', we'll cook a + * timestamp no later than the next IRQ handler run. + * + * The more correct fix is to wrap programming and arming with the + * event_lock and thus serializing it with the IRQ handler. However, + * there are various sleep-waits within + * update_planes_and_stream_adapter() that makes spin locking illegal. + * And on full updates, it can take 1-2 frame-times to return (see + * commit_planes_for_stream). + * + * On DCE, GRPH_PFLIP IRQ is used and takes care of this. + */ + if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { + spin_lock_irqsave(&pcrtc->dev->event_lock, flags); + + if (updated_planes_and_streams) { + flip_latched_during_prog = + !dc_get_flip_pending_on_otg(dm->dc, acrtc_attach->otg_inst); + } + + dm_arm_vblank_event(acrtc_attach, acrtc_state, + pflip_present, cursor_update); + + /* + * Deliver the event immediately on immediate flip, or on a + * update that has already latched. + */ + if ((immediate_flip || flip_latched_during_prog) && + acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc_attach->event) { + drm_crtc_accurate_vblank_count(&acrtc_attach->base); + drm_crtc_send_vblank_event(&acrtc_attach->base, + acrtc_attach->event); + acrtc_attach->event = NULL; + drm_crtc_vblank_put(&acrtc_attach->base); + acrtc_attach->pflip_status = AMDGPU_FLIP_NONE; + } + spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); + } + cleanup: kfree(bundle); } @@ -4523,6 +4281,7 @@ bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc) spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); pending = acrtc->wb_pending; acrtc->wb_pending = false; + acrtc->wb_frame_done = false; spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); if (!pending) @@ -4533,6 +4292,7 @@ bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc) return true; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_complete_writeback); static void dm_clear_writeback(struct amdgpu_display_manager *dm, struct amdgpu_crtc *acrtc, @@ -4990,6 +4750,7 @@ static void dm_set_writeback(struct amdgpu_display_manager *dm, * cannot run its matching vblank_put before this get. */ WARN_ON(drm_crtc_vblank_get(&acrtc->base)); + acrtc->wb_frame_done = false; acrtc->wb_pending = true; } @@ -5180,7 +4941,7 @@ STATIC_IFN_KUNIT void set_master_stream(struct dc_stream_state *stream_set[], } EXPORT_IF_KUNIT(set_master_stream); -static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) +STATIC_IFN_KUNIT void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) { int i = 0; struct dc_stream_state *stream; @@ -5208,6 +4969,7 @@ static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) set_multisync_trigger_params(stream); } } +EXPORT_IF_KUNIT(dm_enable_per_frame_crtc_master_sync); /** * amdgpu_dm_atomic_commit_tail() - AMDgpu DM's commit tail implementation. @@ -5371,7 +5133,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) dm_old_crtc_state = to_dm_crtc_state(old_crtc_state); /* For freesync config update on crtc state and params for irq */ - update_stream_irq_parameters(dm, dm_new_crtc_state); + amdgpu_dm_update_stream_irq_parameters(dm, dm_new_crtc_state); #ifdef CONFIG_DEBUG_FS spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); @@ -5571,106 +5333,6 @@ static int do_aquire_global_lock(struct drm_device *dev, return ret < 0 ? ret : 0; } -static void get_freesync_config_for_crtc( - struct dm_crtc_state *new_crtc_state, - struct dm_connector_state *new_con_state) -{ - struct mod_freesync_config config = {0}; - struct amdgpu_dm_connector *aconnector; - struct drm_display_mode *mode = &new_crtc_state->base.mode; - int vrefresh = drm_mode_vrefresh(mode); - bool fs_vid_mode = false; - - if (new_con_state->base.connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - return; - - aconnector = to_amdgpu_dm_connector(new_con_state->base.connector); - - new_crtc_state->vrr_supported = new_con_state->freesync_capable && - vrefresh >= aconnector->min_vfreq && - vrefresh <= aconnector->max_vfreq; - - if (new_crtc_state->vrr_supported) { - new_crtc_state->stream->ignore_msa_timing_param = true; - fs_vid_mode = new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED; - - config.min_refresh_in_uhz = aconnector->min_vfreq * 1000000; - config.max_refresh_in_uhz = aconnector->max_vfreq * 1000000; - config.vsif_supported = true; - config.btr = true; - - if (fs_vid_mode) { - config.state = VRR_STATE_ACTIVE_FIXED; - config.fixed_refresh_in_uhz = new_crtc_state->freesync_config.fixed_refresh_in_uhz; - goto out; - } else if (new_crtc_state->base.vrr_enabled) { - config.state = VRR_STATE_ACTIVE_VARIABLE; - } else { - config.state = VRR_STATE_INACTIVE; - } - } else { - config.state = VRR_STATE_UNSUPPORTED; - } -out: - new_crtc_state->freesync_config = config; -} - -static void reset_freesync_config_for_crtc( - struct dm_crtc_state *new_crtc_state) -{ - new_crtc_state->vrr_supported = false; - - memset(&new_crtc_state->vrr_infopacket, 0, - sizeof(new_crtc_state->vrr_infopacket)); -} - -STATIC_IFN_KUNIT bool -is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, - struct drm_crtc_state *new_crtc_state) -{ - const struct drm_display_mode *old_mode, *new_mode; - - if (!old_crtc_state || !new_crtc_state) - return false; - - old_mode = &old_crtc_state->mode; - new_mode = &new_crtc_state->mode; - - if (old_mode->clock == new_mode->clock && - old_mode->hdisplay == new_mode->hdisplay && - old_mode->vdisplay == new_mode->vdisplay && - old_mode->htotal == new_mode->htotal && - old_mode->vtotal != new_mode->vtotal && - old_mode->hsync_start == new_mode->hsync_start && - old_mode->vsync_start != new_mode->vsync_start && - old_mode->hsync_end == new_mode->hsync_end && - old_mode->vsync_end != new_mode->vsync_end && - old_mode->hskew == new_mode->hskew && - old_mode->vscan == new_mode->vscan && - (old_mode->vsync_end - old_mode->vsync_start) == - (new_mode->vsync_end - new_mode->vsync_start)) - return true; - - return false; -} -EXPORT_IF_KUNIT(is_timing_unchanged_for_freesync); - -STATIC_IFN_KUNIT void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state) -{ - u64 num, den, res; - struct drm_crtc_state *new_crtc_state = &dm_new_crtc_state->base; - - dm_new_crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; - - num = (unsigned long long)new_crtc_state->mode.clock * 1000 * 1000000; - den = (unsigned long long)new_crtc_state->mode.htotal * - (unsigned long long)new_crtc_state->mode.vtotal; - - res = div_u64(num, den); - dm_new_crtc_state->freesync_config.fixed_refresh_in_uhz = res; -} -EXPORT_IF_KUNIT(set_freesync_fixed_config); - static int dm_update_crtc_state(struct amdgpu_display_manager *dm, struct drm_atomic_commit *state, struct drm_crtc *crtc, @@ -5767,7 +5429,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, */ if (amdgpu_freesync_vid_mode && dm_new_crtc_state->stream && - is_timing_unchanged_for_freesync(new_crtc_state, old_crtc_state)) + amdgpu_dm_is_timing_unchanged_for_freesync(new_crtc_state, old_crtc_state)) goto skip_modeset; if (dm_new_crtc_state->stream && @@ -5809,14 +5471,14 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, if (amdgpu_freesync_vid_mode && dm_new_crtc_state->stream && dc_is_stream_unchanged(new_stream, dm_old_crtc_state->stream) && dc_is_stream_scaling_unchanged(new_stream, dm_old_crtc_state->stream) && - is_timing_unchanged_for_freesync(new_crtc_state, + amdgpu_dm_is_timing_unchanged_for_freesync(new_crtc_state, old_crtc_state)) { new_crtc_state->mode_changed = false; drm_dbg_driver(adev_to_drm(adev), "Mode change not required for front porch change, setting mode_changed to %d", new_crtc_state->mode_changed); - set_freesync_fixed_config(dm_new_crtc_state); + amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state); goto skip_modeset; } else if (amdgpu_freesync_vid_mode && aconnector && @@ -5826,7 +5488,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false); if (!drm_mode_equal(&new_crtc_state->mode, high_mode)) - set_freesync_fixed_config(dm_new_crtc_state); + amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state); } ret = dm_atomic_get_state(state, &dm_state); @@ -5848,7 +5510,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, dc_stream_release(dm_old_crtc_state->stream); dm_new_crtc_state->stream = NULL; - reset_freesync_config_for_crtc(dm_new_crtc_state); + amdgpu_dm_reset_freesync_config_for_crtc(dm_new_crtc_state); *lock_and_validation_needed = true; @@ -5896,6 +5558,7 @@ skip_modeset: /* Release extra reference */ if (new_stream) dc_stream_release(new_stream); + new_stream = NULL; /* * We want to do dc stream updates that do not require a @@ -5935,7 +5598,7 @@ skip_modeset: } /* Update Freesync settings. */ - get_freesync_config_for_crtc(dm_new_crtc_state, + amdgpu_dm_get_freesync_config_for_crtc(dm_new_crtc_state, dm_new_conn_state); return ret; @@ -6099,140 +5762,13 @@ static bool should_reset_plane(struct drm_atomic_commit *state, new_afb = (struct amdgpu_framebuffer *)new_other_state->fb; /* Tiling and DCC changes also require bandwidth updates. */ - if (old_afb->tiling_flags != new_afb->tiling_flags || - old_afb->base.modifier != new_afb->base.modifier) + if (old_afb->base.modifier != new_afb->base.modifier) return true; } return false; } -static int dm_check_cursor_fb(struct amdgpu_crtc *new_acrtc, - struct drm_plane_state *new_plane_state, - struct drm_framebuffer *fb) -{ - struct amdgpu_device *adev = drm_to_adev(new_acrtc->base.dev); - struct amdgpu_framebuffer *afb = to_amdgpu_framebuffer(fb); - unsigned int pitch; - bool linear; - - if (fb->width > new_acrtc->max_cursor_width || - fb->height > new_acrtc->max_cursor_height) { - drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB size %dx%d\n", - new_plane_state->fb->width, - new_plane_state->fb->height); - return -EINVAL; - } - if (new_plane_state->src_w != fb->width << 16 || - new_plane_state->src_h != fb->height << 16) { - drm_dbg_atomic(adev_to_drm(adev), "Cropping not supported for cursor plane\n"); - return -EINVAL; - } - - /* Pitch in pixels */ - pitch = fb->pitches[0] / fb->format->cpp[0]; - - if (fb->width != pitch) { - drm_dbg_atomic(adev_to_drm(adev), "Cursor FB width %d doesn't match pitch %d", - fb->width, pitch); - return -EINVAL; - } - - switch (pitch) { - case 64: - case 128: - case 256: - /* FB pitch is supported by cursor plane */ - break; - default: - drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB pitch %d px\n", pitch); - return -EINVAL; - } - - /* Core DRM takes care of checking FB modifiers, so we only need to - * check tiling flags when the FB doesn't have a modifier. - */ - if (!(fb->flags & DRM_MODE_FB_MODIFIERS)) { - if (adev->family == AMDGPU_FAMILY_GC_12_0_0) { - linear = AMDGPU_TILING_GET(afb->tiling_flags, GFX12_SWIZZLE_MODE) == 0; - } else if (adev->family >= AMDGPU_FAMILY_AI) { - linear = AMDGPU_TILING_GET(afb->tiling_flags, SWIZZLE_MODE) == 0; - } else { - linear = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_2D_TILED_THIN1 && - AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_1D_TILED_THIN1 && - AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE) == 0; - } - if (!linear) { - drm_dbg_atomic(adev_to_drm(adev), "Cursor FB not linear"); - return -EINVAL; - } - } - - return 0; -} - -/* - * Helper function for checking the cursor in native mode - */ -static int dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc, - struct drm_plane *plane, - struct drm_plane_state *new_plane_state, - bool enable) -{ - - struct amdgpu_crtc *new_acrtc; - int ret; - - if (!enable || !new_plane_crtc || - drm_atomic_plane_disabling(plane->state, new_plane_state)) - return 0; - - new_acrtc = to_amdgpu_crtc(new_plane_crtc); - - if (new_plane_state->src_x != 0 || new_plane_state->src_y != 0) { - drm_dbg_atomic(new_plane_crtc->dev, "Cropping not supported for cursor plane\n"); - return -EINVAL; - } - - if (new_plane_state->fb) { - ret = dm_check_cursor_fb(new_acrtc, new_plane_state, - new_plane_state->fb); - if (ret) - return ret; - } - - return 0; -} - -static bool dm_should_update_native_cursor(struct drm_atomic_commit *state, - struct drm_crtc *old_plane_crtc, - struct drm_crtc *new_plane_crtc, - bool enable) -{ - struct drm_crtc_state *old_crtc_state, *new_crtc_state; - struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state; - - if (!enable) { - if (old_plane_crtc == NULL) - return true; - - old_crtc_state = drm_atomic_get_old_crtc_state( - state, old_plane_crtc); - dm_old_crtc_state = to_dm_crtc_state(old_crtc_state); - - return dm_old_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE; - } else { - if (new_plane_crtc == NULL) - return true; - - new_crtc_state = drm_atomic_get_new_crtc_state( - state, new_plane_crtc); - dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); - - return dm_new_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE; - } -} - static int dm_update_plane_state(struct dc *dc, struct drm_atomic_commit *state, struct drm_plane *plane, @@ -6257,14 +5793,14 @@ static int dm_update_plane_state(struct dc *dc, dm_new_plane_state = to_dm_plane_state(new_plane_state); dm_old_plane_state = to_dm_plane_state(old_plane_state); - update_native_cursor = dm_should_update_native_cursor(state, - old_plane_crtc, - new_plane_crtc, - enable); + update_native_cursor = amdgpu_dm_should_update_native_cursor(state, + old_plane_crtc, + new_plane_crtc, + enable); if (plane->type == DRM_PLANE_TYPE_CURSOR && update_native_cursor) { - ret = dm_check_native_cursor_state(new_plane_crtc, plane, - new_plane_state, enable); + ret = amdgpu_dm_check_native_cursor_state(new_plane_crtc, plane, + new_plane_state, enable); if (ret) return ret; @@ -6394,8 +5930,8 @@ static int dm_update_plane_state(struct dc *dc, out: /* If enabling cursor overlay failed, attempt fallback to native mode */ if (enable && ret == -EINVAL && plane->type == DRM_PLANE_TYPE_CURSOR) { - ret = dm_check_native_cursor_state(new_plane_crtc, plane, - new_plane_state, enable); + ret = amdgpu_dm_check_native_cursor_state(new_plane_crtc, plane, + new_plane_state, enable); if (ret) return ret; @@ -6405,37 +5941,6 @@ out: return ret; } -STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, - int *src_w, int *src_h) -{ - switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) { - case DRM_MODE_ROTATE_90: - case DRM_MODE_ROTATE_270: - *src_w = plane_state->src_h >> 16; - *src_h = plane_state->src_w >> 16; - break; - case DRM_MODE_ROTATE_0: - case DRM_MODE_ROTATE_180: - default: - *src_w = plane_state->src_w >> 16; - *src_h = plane_state->src_h >> 16; - break; - } -} -EXPORT_IF_KUNIT(dm_get_oriented_plane_size); - -STATIC_IFN_KUNIT void -dm_get_plane_scale(struct drm_plane_state *plane_state, - int *out_plane_scale_w, int *out_plane_scale_h) -{ - int plane_src_w, plane_src_h; - - dm_get_oriented_plane_size(plane_state, &plane_src_w, &plane_src_h); - *out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 / plane_src_w : 0; - *out_plane_scale_h = plane_src_h ? plane_state->crtc_h * 1000 / plane_src_h : 0; -} -EXPORT_IF_KUNIT(dm_get_plane_scale); - /* * The normalized_zpos value cannot be used by this iterator directly. It's only * calculated for enabled planes, potentially causing normalized_zpos collisions @@ -6443,7 +5948,7 @@ EXPORT_IF_KUNIT(dm_get_plane_scale); * so that the iterator will not generate the same object twice, or loop * indefinitely. */ -static inline struct __drm_planes_state *__get_next_zpos( +struct __drm_planes_state *amdgpu_dm_get_next_zpos( struct drm_atomic_commit *state, struct __drm_planes_state *prev) { @@ -6481,19 +5986,6 @@ static inline struct __drm_planes_state *__get_next_zpos( return &state->planes[highest_i]; } -/* - * Use the uniqueness of the plane's (zpos, drm obj ID) combination to iterate - * by descending zpos, as read from the new plane state. This is the same - * ordering as defined by drm_atomic_normalize_zpos(). - */ -#define for_each_oldnew_plane_in_descending_zpos(__state, plane, old_plane_state, new_plane_state) \ - for (struct __drm_planes_state *__i = __get_next_zpos((__state), NULL); \ - __i != NULL; __i = __get_next_zpos((__state), __i)) \ - for_each_if(((plane) = __i->ptr, \ - (void)(plane) /* Only to avoid unused-but-set-variable warning */, \ - (old_plane_state) = __i->old_state, \ - (new_plane_state) = __i->new_state, 1)) - static int add_affected_mst_dsc_crtcs(struct drm_atomic_commit *state, struct drm_crtc *crtc) { struct drm_connector *connector; @@ -6524,214 +6016,6 @@ static int add_affected_mst_dsc_crtcs(struct drm_atomic_commit *state, struct dr return drm_dp_mst_add_affected_dsc_crtcs(state, &aconnector->mst_root->mst_mgr); } -/** - * DOC: Cursor Modes - Native vs Overlay - * - * In native mode, the cursor uses a integrated cursor pipe within each DCN hw - * plane. It does not require a dedicated hw plane to enable, but it is - * subjected to the same z-order and scaling as the hw plane. It also has format - * restrictions, a RGB cursor in native mode cannot be enabled within a non-RGB - * hw plane. - * - * In overlay mode, the cursor uses a separate DCN hw plane, and thus has its - * own scaling and z-pos. It also has no blending restrictions. It lends to a - * cursor behavior more akin to a DRM client's expectations. However, it does - * occupy an extra DCN plane, and therefore will only be used if a DCN plane is - * available. - */ - -/** - * dm_plane_color_pipeline_active() - Check if a plane's color pipeline active. - * @state: DRM atomic state - * @plane: DRM plane to check - * @use_old: if true, inspect the old colorop states; otherwise the new ones - * - * A color pipeline may be selected (color_pipeline != NULL) but still is - * inactive if every colorop in the chain is bypassed. Only return - * true when at least one colorop has bypass == false, meaning the cursor - * would be subjected to the transformation in native mode. - * - * Return: true if the pipeline modifies pixels, false otherwise. - */ -static bool dm_plane_color_pipeline_active(struct drm_atomic_commit *state, - struct drm_plane *plane, - bool use_old) -{ - struct drm_colorop *colorop; - struct drm_colorop_state *old_colorop_state, *new_colorop_state; - int i; - - for_each_oldnew_colorop_in_state(state, colorop, old_colorop_state, new_colorop_state, i) { - struct drm_colorop_state *cstate = use_old ? old_colorop_state : new_colorop_state; - - if (cstate->colorop->plane != plane) - continue; - if (!cstate->bypass) - return true; - } - return false; -} - -/** - * dm_crtc_get_cursor_mode() - Determine the required cursor mode on crtc - * @adev: amdgpu device - * @state: DRM atomic state - * @dm_crtc_state: amdgpu state for the CRTC containing the cursor - * @cursor_mode: Returns the required cursor mode on dm_crtc_state - * - * Get whether the cursor should be enabled in native mode, or overlay mode, on - * the dm_crtc_state. - * - * The cursor should be enabled in overlay mode if there exists an underlying - * plane - on which the cursor may be blended - that is either YUV formatted, - * scaled differently from the cursor, or has a color pipeline active. - * - * Since zpos info is required, drm_atomic_normalize_zpos must be called before - * calling this function. - * - * Return: 0 on success, or an error code if getting the cursor plane state - * failed. - */ -static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, - struct drm_atomic_commit *state, - struct dm_crtc_state *dm_crtc_state, - enum amdgpu_dm_cursor_mode *cursor_mode) -{ - struct drm_plane_state *old_plane_state, *plane_state, *cursor_state; - struct drm_crtc_state *crtc_state = &dm_crtc_state->base; - struct drm_plane *plane; - bool consider_mode_change = false; - bool entire_crtc_covered = false; - bool cursor_changed = false; - int underlying_scale_w, underlying_scale_h; - int cursor_scale_w, cursor_scale_h; - int i; - - /* Overlay cursor not supported on HW before DCN - * DCN401/420 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions - * as previous DCN generations, so enable native mode on DCN401/420 - */ - if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) || - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) || - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1)) { - *cursor_mode = DM_CURSOR_NATIVE_MODE; - return 0; - } - - /* Init cursor_mode to be the same as current */ - *cursor_mode = dm_crtc_state->cursor_mode; - - /* - * Cursor mode can change if a plane's format changes, scale changes, is - * enabled/disabled, z-order changes, or color management properties change. - */ - for_each_oldnew_plane_in_state(state, plane, old_plane_state, plane_state, i) { - int new_scale_w, new_scale_h, old_scale_w, old_scale_h; - - /* Only care about planes on this CRTC */ - if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0) - continue; - - if (plane->type == DRM_PLANE_TYPE_CURSOR) - cursor_changed = true; - - if (drm_atomic_plane_enabling(old_plane_state, plane_state) || - drm_atomic_plane_disabling(old_plane_state, plane_state) || - old_plane_state->fb->format != plane_state->fb->format) { - consider_mode_change = true; - break; - } - - dm_get_plane_scale(plane_state, &new_scale_w, &new_scale_h); - dm_get_plane_scale(old_plane_state, &old_scale_w, &old_scale_h); - if (new_scale_w != old_scale_w || new_scale_h != old_scale_h) { - consider_mode_change = true; - break; - } - - if (dm_plane_color_pipeline_active(state, plane, true) != - dm_plane_color_pipeline_active(state, plane, false)) { - consider_mode_change = true; - break; - } - } - - if (!consider_mode_change && !crtc_state->zpos_changed) - return 0; - - /* - * If no cursor change on this CRTC, and not enabled on this CRTC, then - * no need to set cursor mode. This avoids needlessly locking the cursor - * state. - */ - if (!cursor_changed && - !(drm_plane_mask(crtc_state->crtc->cursor) & crtc_state->plane_mask)) { - return 0; - } - - cursor_state = drm_atomic_get_plane_state(state, - crtc_state->crtc->cursor); - if (IS_ERR(cursor_state)) - return PTR_ERR(cursor_state); - - /* Cursor is disabled */ - if (!cursor_state->fb) - return 0; - - /* For all planes in descending z-order (all of which are below cursor - * as per zpos definitions), check their scaling and format - */ - for_each_oldnew_plane_in_descending_zpos(state, plane, old_plane_state, plane_state) { - - /* Only care about non-cursor planes on this CRTC */ - if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0 || - plane->type == DRM_PLANE_TYPE_CURSOR) - continue; - - /* Underlying plane is YUV format - use overlay cursor */ - if (amdgpu_dm_plane_is_video_format(plane_state->fb->format->format)) { - *cursor_mode = DM_CURSOR_OVERLAY_MODE; - return 0; - } - - /* Underlying plane has an active color pipeline - cursor would be transformed */ - if (dm_plane_color_pipeline_active(state, plane, false)) { - *cursor_mode = DM_CURSOR_OVERLAY_MODE; - return 0; - } - - dm_get_plane_scale(plane_state, - &underlying_scale_w, &underlying_scale_h); - dm_get_plane_scale(cursor_state, - &cursor_scale_w, &cursor_scale_h); - - /* Underlying plane has different scale - use overlay cursor */ - if (cursor_scale_w != underlying_scale_w && - cursor_scale_h != underlying_scale_h) { - *cursor_mode = DM_CURSOR_OVERLAY_MODE; - return 0; - } - - /* If this plane covers the whole CRTC, no need to check planes underneath */ - if (plane_state->crtc_x <= 0 && plane_state->crtc_y <= 0 && - plane_state->crtc_x + plane_state->crtc_w >= crtc_state->mode.hdisplay && - plane_state->crtc_y + plane_state->crtc_h >= crtc_state->mode.vdisplay) { - entire_crtc_covered = true; - break; - } - } - - /* If planes do not cover the entire CRTC, use overlay mode to enable - * cursor over holes - */ - if (entire_crtc_covered) - *cursor_mode = DM_CURSOR_NATIVE_MODE; - else - *cursor_mode = DM_CURSOR_OVERLAY_MODE; - - return 0; -} - static bool amdgpu_dm_crtc_mem_type_changed(struct drm_device *dev, struct drm_atomic_commit *state, struct drm_crtc_state *crtc_state) @@ -6804,7 +6088,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = drm_atomic_helper_check_modeset(dev, state); if (ret) { - drm_dbg_atomic(dev, "drm_atomic_helper_check_modeset() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_helper_check_modeset() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -6819,7 +6103,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, new_crtc_state = drm_atomic_get_crtc_state(state, new_con_state->crtc); if (IS_ERR(new_crtc_state)) { - drm_dbg_atomic(dev, "drm_atomic_get_crtc_state() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_get_crtc_state() failed: %pe\n", new_crtc_state); ret = PTR_ERR(new_crtc_state); goto fail; } @@ -6839,7 +6123,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, if (drm_atomic_crtc_needs_modeset(new_crtc_state)) { ret = add_affected_mst_dsc_crtcs(state, crtc); if (ret) { - drm_dbg_atomic(dev, "add_affected_mst_dsc_crtcs() failed\n"); + drm_dbg_atomic(dev, "add_affected_mst_dsc_crtcs() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -6856,7 +6140,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = amdgpu_dm_verify_lut_sizes(new_crtc_state); if (ret) { - drm_dbg_atomic(dev, "amdgpu_dm_verify_lut_sizes() failed\n"); + drm_dbg_atomic(dev, "amdgpu_dm_verify_lut_sizes() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -6865,13 +6149,13 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = drm_atomic_add_affected_connectors(state, crtc); if (ret) { - drm_dbg_atomic(dev, "drm_atomic_add_affected_connectors() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_add_affected_connectors() failed: %pe\n", ERR_PTR(ret)); goto fail; } ret = drm_atomic_add_affected_planes(state, crtc); if (ret) { - drm_dbg_atomic(dev, "drm_atomic_add_affected_planes() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_add_affected_planes() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -6910,7 +6194,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, if (IS_ERR(new_plane_state)) { ret = PTR_ERR(new_plane_state); - drm_dbg_atomic(dev, "new_plane_state is BAD\n"); + drm_dbg_atomic(dev, "new_plane_state is BAD: %pe\n", new_plane_state); goto fail; } } @@ -6924,7 +6208,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, */ ret = drm_atomic_normalize_zpos(dev, state); if (ret) { - drm_dbg(dev, "drm_atomic_normalize_zpos() failed\n"); + drm_dbg(dev, "drm_atomic_normalize_zpos() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -6935,10 +6219,10 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); - ret = dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state, - &dm_new_crtc_state->cursor_mode); + ret = amdgpu_dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state, + &dm_new_crtc_state->cursor_mode); if (ret) { - drm_dbg(dev, "Failed to determine cursor mode\n"); + drm_dbg(dev, "Failed to determine cursor mode: %pe\n", ERR_PTR(ret)); goto fail; } @@ -6970,7 +6254,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, &lock_and_validation_needed, &is_top_most_overlay); if (ret) { - drm_dbg_atomic(dev, "dm_update_plane_state() failed\n"); + drm_dbg_atomic(dev, "dm_update_plane_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -6983,7 +6267,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, false, &lock_and_validation_needed); if (ret) { - drm_dbg_atomic(dev, "DISABLE: dm_update_crtc_state() failed\n"); + drm_dbg_atomic(dev, "DISABLE: dm_update_crtc_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -6996,7 +6280,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, true, &lock_and_validation_needed); if (ret) { - drm_dbg_atomic(dev, "ENABLE: dm_update_crtc_state() failed\n"); + drm_dbg_atomic(dev, "ENABLE: dm_update_crtc_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -7010,7 +6294,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, &lock_and_validation_needed, &is_top_most_overlay); if (ret) { - drm_dbg_atomic(dev, "dm_update_plane_state() failed\n"); + drm_dbg_atomic(dev, "dm_update_plane_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } } @@ -7026,7 +6310,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, /* Run this here since we want to validate the streams we created */ ret = drm_atomic_helper_check_planes(dev, state); if (ret) { - drm_dbg_atomic(dev, "drm_atomic_helper_check_planes() failed\n"); + drm_dbg_atomic(dev, "drm_atomic_helper_check_planes() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -7070,8 +6354,8 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, } /* If HW can only do native cursor, check restrictions again */ - ret = dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state, - &required_cursor_mode); + ret = amdgpu_dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state, + &required_cursor_mode); if (ret) { drm_dbg_driver(crtc->dev, "[CRTC:%d:%s] Checking cursor mode failed\n", @@ -7164,13 +6448,13 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, if (lock_and_validation_needed) { ret = dm_atomic_get_state(state, &dm_state); if (ret) { - drm_dbg_atomic(dev, "dm_atomic_get_state() failed\n"); + drm_dbg_atomic(dev, "dm_atomic_get_state() failed: %pe\n", ERR_PTR(ret)); goto fail; } ret = do_aquire_global_lock(dev, state); if (ret) { - drm_dbg_atomic(dev, "do_aquire_global_lock() failed\n"); + drm_dbg_atomic(dev, "do_aquire_global_lock() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -7178,7 +6462,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, if (dc_resource_is_dsc_encoding_supported(dc)) { ret = compute_mst_dsc_configs_for_state(state, dm_state->context, vars); if (ret) { - drm_dbg_atomic(dev, "MST_DSC compute_mst_dsc_configs_for_state() failed\n"); + drm_dbg_atomic(dev, "MST_DSC compute_mst_dsc_configs_for_state() failed: %pe\n", ERR_PTR(ret)); ret = -EINVAL; goto fail; } @@ -7187,7 +6471,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = dm_update_mst_vcpi_slots_for_dsc(state, dm_state->context, vars); if (ret) { - drm_dbg_atomic(dev, "dm_update_mst_vcpi_slots_for_dsc() failed\n"); + drm_dbg_atomic(dev, "dm_update_mst_vcpi_slots_for_dsc() failed: %pe\n", ERR_PTR(ret)); goto fail; } @@ -7199,7 +6483,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, */ ret = drm_dp_mst_atomic_check(state); if (ret) { - drm_dbg_atomic(dev, "MST drm_dp_mst_atomic_check() failed\n"); + drm_dbg_atomic(dev, "MST drm_dp_mst_atomic_check() failed: %pe\n", ERR_PTR(ret)); goto fail; } status = dc_validate_global_state(dc, dm_state->context, DC_VALIDATE_MODE_ONLY); @@ -7288,7 +6572,7 @@ fail: else if (ret == -EINTR || ret == -EAGAIN || ret == -ERESTARTSYS) drm_dbg_atomic(dev, "Atomic check stopped due to signal.\n"); else - drm_dbg_atomic(dev, "Atomic check failed with err: %d\n", ret); + drm_dbg_atomic(dev, "Atomic check failed: %pe\n", ERR_PTR(ret)); trace_amdgpu_dm_atomic_check_finish(state, ret); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 91affbdb2d6c..1ddf5bc88a03 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -324,6 +324,8 @@ struct hpd_rx_irq_offload_work { * @ddev: DRM base driver structure * @display_indexes_num: Max number of display streams supported * @irq_handler_list_table_lock: Synchronizes access to IRQ tables + * @irq_wq: Dedicated high-priority unbound workqueue for deferred IRQ work + * @vmin_vmax_wq: Dedicated unbound workqueue for deferred vmin/vmax updates * @backlight_dev: Backlight control device * @backlight_link: Link on which to control backlight * @backlight_caps: Capabilities of the backlight device @@ -563,6 +565,8 @@ struct amdgpu_display_manager { dmub_outbox_params[1]; spinlock_t irq_handler_list_table_lock; + struct workqueue_struct *irq_wq; + struct workqueue_struct *vmin_vmax_wq; struct backlight_device *backlight_dev[AMDGPU_DM_MAX_NUM_EDP]; @@ -688,6 +692,13 @@ struct amdgpu_display_manager { */ void *bb_from_dmub; + /** + * @i2c_devres_group: + * + * Devres group for DM i2c adapter lifetime management. + */ + void *i2c_devres_group; + /** * @oem_i2c: * @@ -1123,30 +1134,62 @@ bool amdgpu_dm_is_headless(struct amdgpu_device *adev); bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc); void retrieve_dmi_info(struct amdgpu_display_manager *dm); +struct pci_dev; +bool dm_should_disable_stutter(struct pci_dev *pdev); void amdgpu_dm_emulated_link_detect(struct dc_link *link); void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, struct dc_sink *sink); +struct __drm_planes_state *amdgpu_dm_get_next_zpos(struct drm_atomic_commit *state, + struct __drm_planes_state *prev); + +/* + * Use the uniqueness of the plane's (zpos, drm obj ID) combination to iterate + * by descending zpos, as read from the new plane state. This is the same + * ordering as defined by drm_atomic_normalize_zpos(). + */ +#define for_each_oldnew_plane_in_descending_zpos(__state, plane, old_plane_state, new_plane_state) \ + for (struct __drm_planes_state *__i = amdgpu_dm_get_next_zpos((__state), NULL); \ + __i != NULL; __i = amdgpu_dm_get_next_zpos((__state), __i)) \ + for_each_if(((plane) = __i->ptr, \ + (void)(plane) /* Only to avoid unused-but-set-variable warning */, \ + (old_plane_state) = __i->old_state, \ + (new_plane_state) = __i->new_state, 1)) + #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct amdgpu_ip_block; +bool dm_is_idle(struct amdgpu_ip_block *ip_block); +int dm_wait_for_idle(struct amdgpu_ip_block *ip_block); +int dm_soft_reset(struct amdgpu_ip_block *ip_block); +int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block, + enum amd_clockgating_state state); +int dm_set_powergating_state(struct amdgpu_ip_block *ip_block, + enum amd_powergating_state state); +void dm_bandwidth_update(struct amdgpu_device *adev); +u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc); +int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, + u32 *vbl, u32 *position); +struct dm_atomic_state *dm_atomic_get_new_state(struct drm_atomic_commit *state); +void dm_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state); int dm_plane_layer_index_cmp(const void *a, const void *b); int fill_plane_color_attributes(const struct drm_plane_state *plane_state, const enum surface_pixel_format format, enum dc_color_space *color_space); bool modereset_required(struct drm_crtc_state *crtc_state); -void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, - int *src_w, int *src_h); -void dm_get_plane_scale(struct drm_plane_state *plane_state, - int *out_plane_scale_w, int *out_plane_scale_h); bool is_scaling_state_different(const struct dm_connector_state *dm_state, const struct dm_connector_state *old_dm_state); -bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, - struct drm_crtc_state *new_crtc_state); -void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state); -bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, - struct dm_crtc_state *new_state); void set_multisync_trigger_params(struct dc_stream_state *stream); void set_master_stream(struct dc_stream_state *stream_set[], int stream_count); +void dm_enable_per_frame_crtc_master_sync(struct dc_state *context); +struct hdcp_workqueue; +bool is_content_protection_different(struct drm_crtc_state *new_crtc_state, + struct drm_crtc_state *old_crtc_state, + struct drm_connector_state *new_conn_state, + struct drm_connector_state *old_conn_state, + const struct drm_connector *connector, + struct hdcp_workqueue *hdcp_w); #endif #endif /* __AMDGPU_DM_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c index 13c9a9d145ba..199f339839b2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c @@ -26,7 +26,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_audio.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "dc.h" #include @@ -116,8 +116,31 @@ static const struct component_ops amdgpu_dm_audio_component_bind_ops = { .unbind = amdgpu_dm_audio_component_unbind, }; +STATIC_IFN_KUNIT +void amdgpu_dm_audio_init_pins(struct amdgpu_device *adev, int audio_count, + const unsigned int *inst_array) +{ + int i; + + adev->mode_info.audio.num_pins = audio_count; + + for (i = 0; i < audio_count; i++) { + adev->mode_info.audio.pin[i].channels = -1; + adev->mode_info.audio.pin[i].rate = -1; + adev->mode_info.audio.pin[i].bits_per_sample = -1; + adev->mode_info.audio.pin[i].status_bits = 0; + adev->mode_info.audio.pin[i].category_code = 0; + adev->mode_info.audio.pin[i].connected = false; + adev->mode_info.audio.pin[i].id = inst_array[i]; + adev->mode_info.audio.pin[i].offset = 0; + } +} +EXPORT_IF_KUNIT(amdgpu_dm_audio_init_pins); + int amdgpu_dm_audio_init(struct amdgpu_device *adev) { + unsigned int inst_array[MAX_AUDIOS]; + int audio_count; int i, ret; if (!amdgpu_audio) @@ -125,19 +148,10 @@ int amdgpu_dm_audio_init(struct amdgpu_device *adev) adev->mode_info.audio.enabled = true; - adev->mode_info.audio.num_pins = adev->dm.dc->res_pool->audio_count; - - for (i = 0; i < adev->mode_info.audio.num_pins; i++) { - adev->mode_info.audio.pin[i].channels = -1; - adev->mode_info.audio.pin[i].rate = -1; - adev->mode_info.audio.pin[i].bits_per_sample = -1; - adev->mode_info.audio.pin[i].status_bits = 0; - adev->mode_info.audio.pin[i].category_code = 0; - adev->mode_info.audio.pin[i].connected = false; - adev->mode_info.audio.pin[i].id = - adev->dm.dc->res_pool->audios[i]->inst; - adev->mode_info.audio.pin[i].offset = 0; - } + audio_count = adev->dm.dc->res_pool->audio_count; + for (i = 0; i < audio_count; i++) + inst_array[i] = adev->dm.dc->res_pool->audios[i]->inst; + amdgpu_dm_audio_init_pins(adev, audio_count, inst_array); ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops); if (ret < 0) @@ -168,7 +182,7 @@ void amdgpu_dm_audio_fini(struct amdgpu_device *adev) } EXPORT_IF_KUNIT(amdgpu_dm_audio_fini); -STATIC_IFN_KUNIT void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) +void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) { struct drm_audio_component *acomp = adev->dm.audio_component; @@ -307,6 +321,7 @@ notify: amdgpu_dm_audio_eld_notify(adev, inst); } } +EXPORT_IF_KUNIT(amdgpu_dm_commit_audio); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) int amdgpu_dm_audio_get_param(void) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h index 7acfc5ef69b3..e57e2ed20d56 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h @@ -40,6 +40,7 @@ void amdgpu_dm_commit_audio(struct drm_device *dev, void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, const struct drm_connector *drm_connector, const struct dc_sink *dc_sink); +void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) struct device; @@ -48,9 +49,10 @@ int amdgpu_dm_audio_component_bind(struct device *kdev, struct device *hda_kdev, void *data); void amdgpu_dm_audio_component_unbind(struct device *kdev, struct device *hda_kdev, void *data); -void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin); int amdgpu_dm_audio_get_param(void); void amdgpu_dm_audio_set_param(int val); +void amdgpu_dm_audio_init_pins(struct amdgpu_device *adev, int audio_count, + const unsigned int *inst_array); #endif #endif /* __AMDGPU_DM_AUDIO_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 33f4be403a65..b66ca60e697d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -47,7 +47,7 @@ #include "amdgpu_dm_trace.h" #include "amd_shared.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, int bl_idx) @@ -219,7 +219,8 @@ u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, } EXPORT_IF_KUNIT(convert_brightness_to_user); -static struct dc_stream_state *dm_find_stream_with_link( +STATIC_IFN_KUNIT +struct dc_stream_state *dm_find_stream_with_link( struct amdgpu_display_manager *dm, struct dc_link *link) { @@ -235,6 +236,7 @@ static struct dc_stream_state *dm_find_stream_with_link( return NULL; } +EXPORT_IF_KUNIT(dm_find_stream_with_link); STATIC_IFN_KUNIT int amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm, @@ -346,8 +348,10 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, if (rc) dm->actual_brightness[bl_idx] = user_brightness; } +EXPORT_IF_KUNIT(amdgpu_dm_backlight_set_level); -static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) +STATIC_IFN_KUNIT +int amdgpu_dm_backlight_update_status(struct backlight_device *bd) { struct amdgpu_display_manager *dm = bl_get_data(bd); int i = amdgpu_dm_backlight_get_device_index(dm, bd); @@ -356,9 +360,10 @@ static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_backlight_update_status); -static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, - int bl_idx) +STATIC_IFN_KUNIT +u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx) { int ret; struct amdgpu_dm_backlight_caps caps; @@ -382,14 +387,17 @@ static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, return convert_brightness_to_user(&caps, ret); } +EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_level); -static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) +STATIC_IFN_KUNIT +int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) { struct amdgpu_display_manager *dm = bl_get_data(bd); int i = amdgpu_dm_backlight_get_device_index(dm, bd); return amdgpu_dm_backlight_get_level(dm, i); } +EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_brightness); static const struct backlight_ops amdgpu_dm_backlight_ops = { .options = BL_CORE_SUSPENDRESUME, @@ -407,12 +415,12 @@ void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps, if (get_brightness_range(caps, &min, &max)) { if (is_system_supplied) - props->brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, + props->brightness = DIV_ROUND_CLOSEST(max * caps->ac_level, 100); else - props->brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, + props->brightness = DIV_ROUND_CLOSEST(max * caps->dc_level, 100); - props->max_brightness = max - min; + props->max_brightness = max; } else { props->brightness = MAX_BACKLIGHT_LEVEL; props->max_brightness = MAX_BACKLIGHT_LEVEL; @@ -486,6 +494,7 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name); } } +EXPORT_IF_KUNIT(amdgpu_dm_register_backlight_device); void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) { @@ -508,6 +517,8 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps; caps->aux_support = false; + panel_backlight_quirk = drm_get_panel_backlight_quirk(aconnector->drm_edid); + if (caps->ext_caps->bits.oled == 1 /* * || @@ -520,6 +531,9 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) caps->aux_support = false; else if (amdgpu_backlight == 1) caps->aux_support = true; + else if (!IS_ERR_OR_NULL(panel_backlight_quirk) && + panel_backlight_quirk->force_pwm) + caps->aux_support = false; if (caps->aux_support) aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; @@ -535,8 +549,6 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) else caps->aux_min_input_signal = 1; - panel_backlight_quirk = - drm_get_panel_backlight_quirk(aconnector->drm_edid); if (!IS_ERR_OR_NULL(panel_backlight_quirk)) { if (panel_backlight_quirk->min_brightness) { caps->min_input_signal = @@ -559,7 +571,6 @@ EXPORT_IF_KUNIT(amdgpu_dm_update_connector_ext_caps); void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconnector) { - struct amdgpu_dm_backlight_caps *caps; struct dc_link *link = aconnector->dc_link; int bl_idx = dm->num_of_edps; @@ -579,10 +590,12 @@ void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, dm->num_of_edps++; amdgpu_dm_update_connector_ext_caps(aconnector); - caps = &dm->backlight_caps[aconnector->bl_idx]; - /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */ - if (caps->ext_caps && !caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0) + /* Offer ABM property when user didn't turn off by module parameter. + * OLED panels are included to support CACP (Content Adaptive + * Contrast and Power) feature via set_abm_level. + */ + if (amdgpu_dm_abm_level < 0) drm_object_attach_property(&aconnector->base.base, dm->adev->mode_info.abm_level_property, ABM_SYSFS_CONTROL); @@ -601,9 +614,10 @@ EXPORT_IF_KUNIT(amdgpu_dm_setup_backlight_device); * carefully. */ -static ssize_t panel_power_savings_show(struct device *device, - struct device_attribute *attr, - char *buf) +STATIC_IFN_KUNIT +ssize_t panel_power_savings_show(struct device *device, + struct device_attribute *attr, + char *buf) { struct drm_connector *connector = dev_get_drvdata(device); struct drm_device *dev = connector->dev; @@ -617,10 +631,12 @@ static ssize_t panel_power_savings_show(struct device *device, return sysfs_emit(buf, "%u\n", val); } +EXPORT_IF_KUNIT(panel_power_savings_show); -static ssize_t panel_power_savings_store(struct device *device, - struct device_attribute *attr, - const char *buf, size_t count) +STATIC_IFN_KUNIT +ssize_t panel_power_savings_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) { struct drm_connector *connector = dev_get_drvdata(device); struct drm_device *dev = connector->dev; @@ -650,6 +666,7 @@ static ssize_t panel_power_savings_store(struct device *device, return count; } +EXPORT_IF_KUNIT(panel_power_savings_store); static DEVICE_ATTR_RW(panel_power_savings); @@ -666,22 +683,16 @@ const struct attribute_group amdgpu_group = { bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector) { + struct dc_link *link = amdgpu_dm_connector->dc_link; + if (amdgpu_dm_abm_level >= 0) return false; if (amdgpu_dm_connector->base.connector_type != DRM_MODE_CONNECTOR_eDP) return false; - /* check for OLED panels */ - if (amdgpu_dm_connector->bl_idx >= 0) { - struct drm_device *drm = amdgpu_dm_connector->base.dev; - struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; - struct amdgpu_dm_backlight_caps *caps; - - caps = &dm->backlight_caps[amdgpu_dm_connector->bl_idx]; - if (caps->aux_support) - return false; - } + if (link->panel_type != PANEL_TYPE_LCD && !link->panel_config.cacp.cacp_supported) + return false; return true; } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index 98d612c60ae9..07b75064847c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -28,6 +28,10 @@ struct amdgpu_display_manager; struct amdgpu_dm_connector; struct backlight_device; struct backlight_properties; +struct dc_link; +struct dc_stream_state; +struct device; +struct device_attribute; struct drm_connector; struct attribute_group; @@ -49,6 +53,17 @@ bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *aconnector); extern const struct attribute_group amdgpu_group; #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct dc_stream_state *dm_find_stream_with_link(struct amdgpu_display_manager *dm, + struct dc_link *link); +int amdgpu_dm_backlight_update_status(struct backlight_device *bd); +u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx); +int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd); +ssize_t panel_power_savings_show(struct device *device, + struct device_attribute *attr, + char *buf); +ssize_t panel_power_savings_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count); int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, unsigned int *min, unsigned int *max); void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 357c7c5c85cf..7b68c6846039 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -33,7 +33,7 @@ #include "amdgpu_dm_colorop.h" #include "dc.h" #include "modules/color/color_gamma.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" /** @@ -172,6 +172,7 @@ void amdgpu_dm_init_color_mod(void) { setup_x_points_distribution(); } +EXPORT_IF_KUNIT(amdgpu_dm_init_color_mod); STATIC_IFN_KUNIT INLINE_IFN_KUNIT struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x) @@ -617,9 +618,10 @@ EXPORT_IF_KUNIT(__drm_ctm_3x4_to_dc_matrix); * Returns: * 0 in case of success, -ENOMEM if fails */ -static int __set_legacy_tf(struct dc_transfer_func *func, - const struct drm_color_lut *lut, uint32_t lut_size, - bool has_rom) +STATIC_IFN_KUNIT int +__set_legacy_tf(struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size, + bool has_rom) { struct dc_gamma *gamma = NULL; struct calculate_buffer cal_buffer = {0}; @@ -644,6 +646,7 @@ static int __set_legacy_tf(struct dc_transfer_func *func, return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_legacy_tf); /** * __set_output_tf - calculates the output transfer function based on expected input space. @@ -655,9 +658,10 @@ static int __set_legacy_tf(struct dc_transfer_func *func, * Returns: * 0 in case of success. -ENOMEM if fails. */ -static int __set_output_tf(struct dc_transfer_func *func, - const struct drm_color_lut *lut, uint32_t lut_size, - bool has_rom) +STATIC_IFN_KUNIT int +__set_output_tf(struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size, + bool has_rom) { struct dc_gamma *gamma = NULL; struct calculate_buffer cal_buffer = {0}; @@ -702,6 +706,7 @@ static int __set_output_tf(struct dc_transfer_func *func, return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_output_tf); /** * __set_output_tf_32 - calculates the output transfer function based on expected input space. @@ -713,9 +718,10 @@ static int __set_output_tf(struct dc_transfer_func *func, * Returns: * 0 in case of success. -ENOMEM if fails. */ -static int __set_output_tf_32(struct dc_transfer_func *func, - const struct drm_color_lut32 *lut, uint32_t lut_size, - bool has_rom) +STATIC_IFN_KUNIT int +__set_output_tf_32(struct dc_transfer_func *func, + const struct drm_color_lut32 *lut, uint32_t lut_size, + bool has_rom) { struct dc_gamma *gamma = NULL; struct calculate_buffer cal_buffer = {0}; @@ -758,6 +764,7 @@ static int __set_output_tf_32(struct dc_transfer_func *func, return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_output_tf_32); STATIC_IFN_KUNIT void __set_tf_bypass(struct dc_transfer_func *tf) { @@ -819,8 +826,9 @@ EXPORT_IF_KUNIT(amdgpu_dm_set_atomic_regamma); * Returns: * 0 in case of success. -ENOMEM if fails. */ -static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *func, - const struct drm_color_lut *lut, uint32_t lut_size) +STATIC_IFN_KUNIT int __set_input_tf(struct dc_color_caps *caps, + struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size) { struct dc_gamma *gamma = NULL; bool res; @@ -843,6 +851,7 @@ static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *f return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_input_tf); /** * __set_input_tf_32 - calculates the input transfer function based on expected @@ -855,8 +864,9 @@ static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *f * Returns: * 0 in case of success. -ENOMEM if fails. */ -static int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func *func, - const struct drm_color_lut32 *lut, uint32_t lut_size) +STATIC_IFN_KUNIT int __set_input_tf_32(struct dc_color_caps *caps, + struct dc_transfer_func *func, + const struct drm_color_lut32 *lut, uint32_t lut_size) { struct dc_gamma *gamma = NULL; bool res; @@ -879,6 +889,7 @@ static int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_input_tf_32); STATIC_IFN_KUNIT enum dc_transfer_func_predefined @@ -1177,6 +1188,7 @@ int amdgpu_dm_verify_lut3d_size(struct amdgpu_device *adev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_verify_lut3d_size); /** * amdgpu_dm_verify_lut_sizes - verifies if DRM luts match the hw supported sizes @@ -1321,6 +1333,7 @@ int amdgpu_dm_check_crtc_color_mgmt(struct dm_crtc_state *crtc, return r; } +EXPORT_IF_KUNIT(amdgpu_dm_check_crtc_color_mgmt); /** * amdgpu_dm_update_crtc_color_mgmt: Maps DRM color management to DC stream. @@ -1379,6 +1392,7 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_update_crtc_color_mgmt); static int map_crtc_degamma_to_dc_plane(struct dm_crtc_state *crtc, @@ -1545,7 +1559,7 @@ __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state, } EXPORT_IF_KUNIT(__set_colorop_in_tf_1d_curve); -static int +STATIC_IFN_KUNIT int __set_dm_plane_colorop_degamma(struct drm_plane_state *plane_state, struct dc_plane_state *dc_plane_state, struct drm_colorop *colorop) @@ -1571,8 +1585,9 @@ __set_dm_plane_colorop_degamma(struct drm_plane_state *plane_state, return __set_colorop_in_tf_1d_curve(dc_plane_state, colorop_state); } +EXPORT_IF_KUNIT(__set_dm_plane_colorop_degamma); -static int +STATIC_IFN_KUNIT int __set_dm_plane_colorop_3x4_matrix(struct drm_plane_state *plane_state, struct dc_plane_state *dc_plane_state, struct drm_colorop *colorop) @@ -1612,8 +1627,9 @@ __set_dm_plane_colorop_3x4_matrix(struct drm_plane_state *plane_state, return 0; } +EXPORT_IF_KUNIT(__set_dm_plane_colorop_3x4_matrix); -static int +STATIC_IFN_KUNIT int __set_dm_plane_colorop_multiplier(struct drm_plane_state *plane_state, struct dc_plane_state *dc_plane_state, struct drm_colorop *colorop) @@ -1641,6 +1657,7 @@ __set_dm_plane_colorop_multiplier(struct drm_plane_state *plane_state, return 0; } +EXPORT_IF_KUNIT(__set_dm_plane_colorop_multiplier); static int __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, @@ -2125,3 +2142,4 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, return amdgpu_dm_plane_set_color_properties(plane_state, dc_plane_state); } +EXPORT_IF_KUNIT(amdgpu_dm_update_plane_color_mgmt); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h index 8dbbcb3ab156..3e8a3d3fc855 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h @@ -44,6 +44,7 @@ struct dc_plane_state; struct fixed31_32; struct tetrahedral_params; struct dc_transfer_func; +struct dc_3dlut; #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) /* @@ -63,6 +64,20 @@ void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, struct fixed31_32 *matrix); void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, struct fixed31_32 *matrix); +int __set_legacy_tf(struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size, + bool has_rom); +int __set_output_tf(struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size, + bool has_rom); +int __set_output_tf_32(struct dc_transfer_func *func, + const struct drm_color_lut32 *lut, uint32_t lut_size, + bool has_rom); +struct dc_color_caps; +int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size); +int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func *func, + const struct drm_color_lut32 *lut, uint32_t lut_size); enum dc_transfer_func_predefined amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf); enum dc_transfer_func_predefined @@ -113,6 +128,17 @@ int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, struct dc_plane_cm *cm); int __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state, struct drm_colorop_state *colorop_state); +struct drm_plane_state; +struct drm_colorop; +int __set_dm_plane_colorop_degamma(struct drm_plane_state *plane_state, + struct dc_plane_state *dc_plane_state, + struct drm_colorop *colorop); +int __set_dm_plane_colorop_3x4_matrix(struct drm_plane_state *plane_state, + struct dc_plane_state *dc_plane_state, + struct drm_colorop *colorop); +int __set_dm_plane_colorop_multiplier(struct drm_plane_state *plane_state, + struct dc_plane_state *dc_plane_state, + struct drm_colorop *colorop); #endif #endif /* __AMDGPU_DM_COLOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c index 056a76b88f43..fae9007b9998 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c @@ -31,7 +31,7 @@ #include "amdgpu.h" #include "amdgpu_dm_colorop.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "dc.h" const u64 amdgpu_dm_supported_degam_tfs = diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 88f938a8f24f..db211884616c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -42,7 +42,6 @@ #include "amdgpu_display.h" #include "amdgpu_dm.h" #include "amdgpu_dm_connector.h" -#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_plane.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_wb.h" @@ -91,11 +90,12 @@ static const struct drm_encoder_funcs amdgpu_dm_encoder_funcs = { .destroy = amdgpu_dm_encoder_destroy, }; -static void dm_encoder_helper_disable(struct drm_encoder *encoder) +STATIC_IFN_KUNIT void dm_encoder_helper_disable(struct drm_encoder *encoder) { } +EXPORT_IF_KUNIT(dm_encoder_helper_disable); -static int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, +STATIC_IFN_KUNIT int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { @@ -165,6 +165,7 @@ static int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, } return 0; } +EXPORT_IF_KUNIT(dm_encoder_helper_atomic_check); const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = { .disable = dm_encoder_helper_disable, @@ -235,7 +236,7 @@ STATIC_IFN_KUNIT enum drm_mode_subconnector get_subconnector_type(struct dc_link } EXPORT_IF_KUNIT(get_subconnector_type); -static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void update_subconnector_property(struct amdgpu_dm_connector *aconnector) { struct dc_link *link = aconnector->dc_link; struct drm_connector *connector = &aconnector->base; @@ -251,10 +252,11 @@ static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) connector->dev->mode_config.dp_subconnector_property, subconnector); } +EXPORT_IF_KUNIT(update_subconnector_property); static int amdgpu_dm_connector_get_modes(struct drm_connector *connector); -static void amdgpu_dm_fbc_init(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_fbc_init(struct drm_connector *connector) { struct amdgpu_device *adev = drm_to_adev(connector->dev); struct dm_compressor_info *compressor = &adev->dm.compressor; @@ -292,6 +294,7 @@ static void amdgpu_dm_fbc_init(struct drm_connector *connector) } } +EXPORT_IF_KUNIT(amdgpu_dm_fbc_init); int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev) @@ -329,8 +332,9 @@ int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev) return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_detect_mst_link_for_all_connectors); -static void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) { struct cec_notifier *n = aconnector->notifier; @@ -339,6 +343,7 @@ static void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) cec_notifier_phys_addr_invalidate(n); } +EXPORT_IF_KUNIT(hdmi_cec_unset_edid); void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector) { @@ -351,6 +356,7 @@ void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector) cec_notifier_set_phys_addr(n, connector->display_info.source_physical_address); } +EXPORT_IF_KUNIT(amdgpu_dm_hdmi_cec_set_edid); void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend) { @@ -371,6 +377,7 @@ void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend) } drm_connector_list_iter_end(&conn_iter); } +EXPORT_IF_KUNIT(amdgpu_dm_s3_handle_hdmi_cec); struct drm_connector * @@ -391,44 +398,44 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, return NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_find_first_crtc_matching_connector); -static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; struct drm_display_info *display_info = &connector->display_info; struct dc_link *link = aconnector->dc_link; struct amdgpu_device *adev; + enum dc_panel_type panel_type = PANEL_TYPE_NONE; adev = drm_to_adev(connector->dev); - link->panel_type = PANEL_TYPE_NONE; - switch (display_info->amd_vsdb.panel_type) { case AMD_VSDB_PANEL_TYPE_OLED: - link->panel_type = PANEL_TYPE_OLED; + panel_type = PANEL_TYPE_OLED; break; case AMD_VSDB_PANEL_TYPE_MINILED: - link->panel_type = PANEL_TYPE_MINILED; + panel_type = PANEL_TYPE_MINILED; break; } /* If VSDB didn't determine panel type, check DPCD ext caps */ - if (link->panel_type == PANEL_TYPE_NONE) { + if (panel_type == PANEL_TYPE_NONE) { if (link->dpcd_sink_ext_caps.bits.miniled == 1) - link->panel_type = PANEL_TYPE_MINILED; + panel_type = PANEL_TYPE_MINILED; if (link->dpcd_sink_ext_caps.bits.oled == 1) - link->panel_type = PANEL_TYPE_OLED; + panel_type = PANEL_TYPE_OLED; } /* If VSDB and DPCD didn't determine panel type, check DID */ - if (link->panel_type == PANEL_TYPE_NONE) { + if (panel_type == PANEL_TYPE_NONE) { if (display_info->panel_type == DRM_MODE_PANEL_TYPE_LCD) - link->panel_type = PANEL_TYPE_LCD; + panel_type = PANEL_TYPE_LCD; else if (display_info->panel_type == DRM_MODE_PANEL_TYPE_OLED) - link->panel_type = PANEL_TYPE_OLED; + panel_type = PANEL_TYPE_OLED; } - if (link->panel_type == PANEL_TYPE_NONE) { + if (panel_type == PANEL_TYPE_NONE) { struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb; u32 lum1_max = vsdb->luminance_range1.max_luminance; u32 lum2_max = vsdb->luminance_range2.max_luminance; @@ -437,9 +444,14 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) link->local_sink->edid_caps.manufacturer_id == DDC_MANUFACTURERNAME_SAMSUNG && lum1_max >= ((lum2_max * 3) / 2)) - link->panel_type = PANEL_TYPE_MINILED; + panel_type = PANEL_TYPE_MINILED; } + if (panel_type != PANEL_TYPE_NONE) + link->panel_type = panel_type; + else + link->panel_type = PANEL_TYPE_LCD; + if (link->panel_type == PANEL_TYPE_OLED) drm_object_property_set_value(&connector->base, adev_to_drm(adev)->mode_config.panel_type_property, @@ -455,6 +467,34 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); } +EXPORT_IF_KUNIT(amdgpu_dm_set_panel_type); + +STATIC_IFN_KUNIT void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector) +{ + struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev); + struct dc_link *link = aconnector->dc_link; + + link->panel_config.cacp.cacp_supported = true; + + if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 1, 4) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(3, 1, 6)) { + link->panel_config.cacp.cacp_supported = false; + return; + } + + if (link->connector_signal != SIGNAL_TYPE_EDP && + link->connector_signal != SIGNAL_TYPE_LVDS) { + link->panel_config.cacp.cacp_supported = false; + return; + } + + if (link->panel_type == PANEL_TYPE_LCD) + link->panel_config.cacp.cacp_supported = false; + + drm_dbg_kms(aconnector->base.dev, "cacp_supported: %d\n", + link->panel_config.cacp.cacp_supported); +} +EXPORT_IF_KUNIT(amdgpu_dm_update_cacp_caps); DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) @@ -464,6 +504,8 @@ void amdgpu_dm_update_connector_after_detect( struct drm_connector *connector = &aconnector->base; struct dc_sink *sink __free(sink_release) = NULL; struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + int inst; /* MST handled by drm_mst framework */ if (aconnector->mst_mgr.mst_state) @@ -581,7 +623,8 @@ void amdgpu_dm_update_connector_after_detect( amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); amdgpu_dm_update_connector_ext_caps(aconnector); - dm_set_panel_type(aconnector); + amdgpu_dm_set_panel_type(aconnector); + amdgpu_dm_update_cacp_caps(aconnector); if (aconnector->hdmi_comp_auto) { if (sink->sink_signal != SIGNAL_TYPE_HDMI_FRL) @@ -601,6 +644,13 @@ void amdgpu_dm_update_connector_after_detect( /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */ if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + + mutex_lock(&adev->dm.audio_lock); + inst = aconnector->audio_inst; + aconnector->audio_inst = -1; + mutex_unlock(&adev->dm.audio_lock); + if (inst != -1) + amdgpu_dm_audio_eld_notify(adev, inst); } update_subconnector_property(aconnector); @@ -609,6 +659,7 @@ void amdgpu_dm_update_connector_after_detect( if (!drm_kms_helper_is_poll_worker()) mutex_unlock(&dev->mode_config.mutex); } +EXPORT_IF_KUNIT(amdgpu_dm_update_connector_after_detect); enum dc_color_depth amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *connector, @@ -798,7 +849,7 @@ STATIC_IFN_KUNIT bool adjust_colour_depth_from_display_info( } EXPORT_IF_KUNIT(adjust_colour_depth_from_display_info); -static void fill_stream_properties_from_drm_display_mode( +STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode( struct dc_stream_state *stream, const struct drm_display_mode *mode_in, const struct drm_connector *connector, @@ -938,8 +989,9 @@ static void fill_stream_properties_from_drm_display_mode( stream->output_color_space = amdgpu_dm_get_output_color_space(timing_out, connector_state); stream->content_type = get_output_content_type(connector_state); } +EXPORT_IF_KUNIT(fill_stream_properties_from_drm_display_mode); -static void +STATIC_IFN_KUNIT void copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, struct drm_display_mode *dst_mode) { @@ -958,6 +1010,7 @@ copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, dst_mode->crtc_vsync_end = src_mode->crtc_vsync_end; dst_mode->crtc_vtotal = src_mode->crtc_vtotal; } +EXPORT_IF_KUNIT(copy_crtc_timing_for_drm_display_mode); STATIC_IFN_KUNIT void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, @@ -1227,6 +1280,9 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, drm_connector->display_info.max_dsc_bpp; struct dc_dsc_config_options dsc_options = {0}; + if (!aconnector->dc_link) + return; + dc_dsc_get_default_config_option(dc, &dsc_options); dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; @@ -1288,7 +1344,7 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON"); } } - } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { + } else if (sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { struct dc_dsc_policy dsc_policy = {0}; frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link); @@ -1333,7 +1389,64 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, } #endif -static struct dc_stream_state * +void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev, + const struct drm_display_mode *mode, + const struct dm_connector_state *dm_state, + struct dc_stream_state *stream) +{ + enum amdgpu_rmx_type rmx_type; + + struct rect src = { 0 }; /* viewport in composition space*/ + struct rect dst = { 0 }; /* stream addressable area */ + + /* no mode. nothing to be done */ + if (!mode) + return; + + /* Full screen scaling by default */ + src.width = mode->hdisplay; + src.height = mode->vdisplay; + dst.width = stream->timing.h_addressable; + dst.height = stream->timing.v_addressable; + + if (dm_state) { + rmx_type = dm_state->scaling; + if (rmx_type == RMX_ASPECT || rmx_type == RMX_OFF) { + if (src.width * dst.height < + src.height * dst.width) { + /* height needs less upscaling/more downscaling */ + dst.width = src.width * + dst.height / src.height; + } else { + /* width needs less upscaling/more downscaling */ + dst.height = src.height * + dst.width / src.width; + } + } else if (rmx_type == RMX_CENTER) { + dst = src; + } + + dst.x = (stream->timing.h_addressable - dst.width) / 2; + dst.y = (stream->timing.v_addressable - dst.height) / 2; + + if (dm_state->underscan_enable) { + dst.x += dm_state->underscan_hborder / 2; + dst.y += dm_state->underscan_vborder / 2; + dst.width -= dm_state->underscan_hborder; + dst.height -= dm_state->underscan_vborder; + } + } + + stream->src = src; + stream->dst = dst; + + drm_dbg_kms(dev, "Destination Rectangle x:%d y:%d width:%d height:%d\n", + dst.x, dst.y, dst.width, dst.height); + +} +EXPORT_IF_KUNIT(amdgpu_dm_update_stream_scaling_settings); + +STATIC_IFN_KUNIT struct dc_stream_state * create_stream_for_sink(struct drm_connector *connector, const struct drm_display_mode *drm_mode, const struct dm_connector_state *dm_state, @@ -1517,6 +1630,7 @@ finish: return stream; } +EXPORT_IF_KUNIT(create_stream_for_sink); /** * amdgpu_dm_connector_poll - Poll a connector to see if it's connected to a display @@ -1532,7 +1646,7 @@ finish: * * Return: The probed connector status (connected/disconnected/unknown). */ -static enum drm_connector_status +STATIC_IFN_KUNIT enum drm_connector_status amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) { struct drm_connector *connector = &aconnector->base; @@ -1584,6 +1698,40 @@ amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) mutex_unlock(&aconnector->hpd_lock); return status; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_poll); + +/* + * Apple Studio Display exposes two SST DP links for a 2x1 tiled panel. + * The primary tile advertises the full 5120x2880 mode (with DSC on the + * bandwidth-sufficient link) while the secondary carries a per-tile + * 2560x2880 timing on a insufficient bandwidth link. Hide the secondary + * connector from userspace so compositors configure a single 5K stream + * on the primary link only. + */ +static bool amdgpu_dm_hide_secondary_tile_from_userspace(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + + if (!aconnector->dc_sink) + return false; + + if (!aconnector->dc_sink->edid_caps.panel_patch.disable_second_tile) + return false; + + drm_edid_connector_update(connector, aconnector->drm_edid); + + if (!connector->has_tile) + return false; + + if (!connector->tile_h_loc && !connector->tile_v_loc) + return false; + + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] hiding secondary Apple Studio Display tile from userspace\n", + connector->base.id, connector->name); + + return true; +} /** * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display @@ -1607,7 +1755,7 @@ amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) * Return: The connector status (connected, disconnected, or unknown). * */ -static enum drm_connector_status +STATIC_IFN_KUNIT enum drm_connector_status amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); @@ -1628,9 +1776,13 @@ amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) (!aconnector->dc_sink || aconnector->dc_sink->edid_caps.analog)) return amdgpu_dm_connector_poll(aconnector, force); + if (amdgpu_dm_hide_secondary_tile_from_userspace(connector)) + return connector_status_disconnected; + return (aconnector->dc_sink ? connector_status_connected : connector_status_disconnected); } +EXPORT_IF_KUNIT(amdgpu_dm_connector_detect); int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, struct drm_connector_state *connector_state, @@ -1749,7 +1901,7 @@ int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, } EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_get_property); -static void amdgpu_dm_connector_unregister(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_connector_unregister(struct drm_connector *connector) { struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); @@ -1759,8 +1911,9 @@ static void amdgpu_dm_connector_unregister(struct drm_connector *connector) cec_notifier_conn_unregister(amdgpu_dm_connector->notifier); drm_dp_aux_unregister(&amdgpu_dm_connector->dm_dp_aux.aux); } +EXPORT_IF_KUNIT(amdgpu_dm_connector_unregister); -static void amdgpu_dm_connector_destroy(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_connector_destroy(struct drm_connector *connector) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); struct amdgpu_device *adev = drm_to_adev(connector->dev); @@ -1801,6 +1954,7 @@ static void amdgpu_dm_connector_destroy(struct drm_connector *connector) kfree(connector); } +EXPORT_IF_KUNIT(amdgpu_dm_connector_destroy); void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) { @@ -1862,7 +2016,7 @@ amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) } EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_duplicate_state); -static int +STATIC_IFN_KUNIT int amdgpu_dm_connector_late_register(struct drm_connector *connector) { struct amdgpu_dm_connector *amdgpu_dm_connector = @@ -1892,8 +2046,9 @@ amdgpu_dm_connector_late_register(struct drm_connector *connector) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_late_register); -static void amdgpu_dm_connector_funcs_force(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_connector_funcs_force(struct drm_connector *connector) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); struct dc_link *dc_link = aconnector->dc_link; @@ -1929,6 +2084,7 @@ static void amdgpu_dm_connector_funcs_force(struct drm_connector *connector) &dc_em_sink->edid_caps); } } +EXPORT_IF_KUNIT(amdgpu_dm_connector_funcs_force); static const struct drm_connector_funcs amdgpu_dm_connector_funcs = { .reset = amdgpu_dm_connector_funcs_reset, @@ -1949,7 +2105,7 @@ static int get_modes(struct drm_connector *connector) return amdgpu_dm_connector_get_modes(connector); } -static void create_eml_sink(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void create_eml_sink(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; struct dc_link *dc_link = aconnector->dc_link; @@ -1994,8 +2150,9 @@ static void create_eml_sink(struct amdgpu_dm_connector *aconnector) dc_sink_retain(aconnector->dc_sink); } } +EXPORT_IF_KUNIT(create_eml_sink); -static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) { struct dc_link *link = (struct dc_link *)aconnector->dc_link; @@ -2010,8 +2167,9 @@ static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) create_eml_sink(aconnector); } +EXPORT_IF_KUNIT(handle_edid_mgmt); -static enum dc_status dm_validate_stream_and_context(struct dc *dc, +STATIC_IFN_KUNIT enum dc_status dm_validate_stream_and_context(struct dc *dc, struct dc_stream_state *stream) { enum dc_status dc_result = DC_ERROR_UNEXPECTED; @@ -2073,6 +2231,7 @@ cleanup: return dc_result; } +EXPORT_IF_KUNIT(dm_validate_stream_and_context); static enum dc_status dm_validate_stream_color_format(const struct drm_connector_state *drm_state, @@ -2211,6 +2370,7 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector, return stream; } +EXPORT_IF_KUNIT(amdgpu_dm_create_validate_stream_for_sink); enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, const struct drm_display_mode *mode) @@ -2264,6 +2424,7 @@ fail: /* TODO: error handling*/ return result; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_mode_valid); int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state, struct dc_info_packet *out) @@ -2333,7 +2494,7 @@ amdgpu_dm_connector_atomic_check(struct drm_connector *conn, drm_atomic_get_new_connector_state(state, conn); struct drm_connector_state *old_con_state = drm_atomic_get_old_connector_state(state, conn); - struct drm_crtc *crtc = new_con_state->crtc; + struct drm_crtc *crtc; struct drm_crtc_state *new_crtc_state; struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn); int ret; @@ -2349,6 +2510,7 @@ amdgpu_dm_connector_atomic_check(struct drm_connector *conn, return ret; } + crtc = new_con_state->crtc; if (!crtc) return 0; @@ -2477,7 +2639,7 @@ STATIC_IFN_KUNIT int to_drm_connector_type(enum signal_type st, uint32_t connect } EXPORT_IF_KUNIT(to_drm_connector_type); -static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) +STATIC_IFN_KUNIT struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) { struct drm_encoder *encoder; @@ -2487,8 +2649,9 @@ static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector * return NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_to_encoder); -static void amdgpu_dm_get_native_mode(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_get_native_mode(struct drm_connector *connector) { struct drm_encoder *encoder; struct amdgpu_encoder *amdgpu_encoder; @@ -2516,8 +2679,9 @@ static void amdgpu_dm_get_native_mode(struct drm_connector *connector) } } +EXPORT_IF_KUNIT(amdgpu_dm_get_native_mode); -static struct drm_display_mode * +STATIC_IFN_KUNIT struct drm_display_mode * amdgpu_dm_create_common_mode(struct drm_encoder *encoder, const char *name, int hdisplay, int vdisplay) @@ -2540,6 +2704,7 @@ amdgpu_dm_create_common_mode(struct drm_encoder *encoder, return mode; } +EXPORT_IF_KUNIT(amdgpu_dm_create_common_mode); static const struct amdgpu_dm_mode_size { char name[DRM_DISPLAY_MODE_LEN]; @@ -2559,7 +2724,7 @@ static const struct amdgpu_dm_mode_size { {"1920x1200", 1920, 1200} }; -static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, +STATIC_IFN_KUNIT void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, struct drm_connector *connector) { struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); @@ -2607,6 +2772,7 @@ static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, amdgpu_dm_connector->num_modes++; } } +EXPORT_IF_KUNIT(amdgpu_dm_connector_add_common_modes); void amdgpu_set_panel_orientation(struct drm_connector *connector) { @@ -2638,7 +2804,48 @@ void amdgpu_set_panel_orientation(struct drm_connector *connector) native_mode->vdisplay); } -static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, +/* + * The Apple Studio Display primary tile advertises both the full 5120x2880 + * mode and the per-tile 2560x2880 timing. As the secondary tile is hidden from + * userspace (see amdgpu_dm_hide_secondary_tile_from_userspace()), drop the + * per-tile timing from the primary connector so compositors only pick the full + * 5K mode. + */ +static void amdgpu_dm_prune_primary_tile_modes(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + struct drm_display_mode *mode, *t; + + if (!aconnector->dc_sink) + return; + + if (!aconnector->dc_sink->edid_caps.panel_patch.disable_second_tile) + return; + + if (!connector->has_tile) + return; + + /* Only prune the per-tile timing from the primary tile. */ + if (connector->tile_h_loc || connector->tile_v_loc) + return; + + list_for_each_entry_safe(mode, t, &connector->probed_modes, head) { + if (mode->hdisplay != connector->tile_h_size || + mode->vdisplay != connector->tile_v_size) + continue; + + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] pruning per-tile %dx%d timing from primary Apple Studio Display tile\n", + connector->base.id, connector->name, + mode->hdisplay, mode->vdisplay); + + list_del(&mode->head); + drm_mode_destroy(connector->dev, mode); + aconnector->num_modes--; + } +} + +STATIC_IFN_KUNIT void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, const struct drm_edid *drm_edid) { struct amdgpu_dm_connector *amdgpu_dm_connector = @@ -2650,6 +2857,8 @@ static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, amdgpu_dm_connector->num_modes = drm_edid_connector_add_modes(connector); + amdgpu_dm_prune_primary_tile_modes(connector); + /* sorting the probed modes before calling function * amdgpu_dm_get_native_mode() since EDID can have * more than one preferred mode. The modes that are @@ -2670,6 +2879,7 @@ static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, amdgpu_dm_connector->num_modes = 0; } } +EXPORT_IF_KUNIT(amdgpu_dm_connector_ddc_get_modes); STATIC_IFN_KUNIT bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, struct drm_display_mode *mode) @@ -2685,7 +2895,7 @@ STATIC_IFN_KUNIT bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, } EXPORT_IF_KUNIT(is_duplicate_mode); -static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT uint add_fs_modes(struct amdgpu_dm_connector *aconnector) { const struct drm_display_mode *m; struct drm_display_mode *new_mode; @@ -2760,8 +2970,9 @@ static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) out: return new_modes_count; } +EXPORT_IF_KUNIT(add_fs_modes); -static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, +STATIC_IFN_KUNIT void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, const struct drm_edid *drm_edid) { struct amdgpu_dm_connector *amdgpu_dm_connector = @@ -2784,6 +2995,7 @@ static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connect amdgpu_dm_connector->num_modes += add_fs_modes(amdgpu_dm_connector); } +EXPORT_IF_KUNIT(amdgpu_dm_connector_add_freesync_modes); static int amdgpu_dm_connector_get_modes(struct drm_connector *connector) { @@ -3016,7 +3228,7 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, } } -static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, +STATIC_IFN_KUNIT int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) { struct amdgpu_i2c_adapter *i2c = i2c_get_adapdata(i2c_adap); @@ -3060,11 +3272,13 @@ static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, kfree(cmd.payloads); return result; } +EXPORT_IF_KUNIT(amdgpu_dm_i2c_xfer); -static u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap) +STATIC_IFN_KUNIT u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } +EXPORT_IF_KUNIT(amdgpu_dm_i2c_func); static const struct i2c_algorithm amdgpu_dm_i2c_algo = { .master_xfer = amdgpu_dm_i2c_xfer, @@ -3425,7 +3639,7 @@ static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector, return ret; } -static void parse_edid_displayid_vrr(struct drm_connector *connector, +STATIC_IFN_KUNIT void parse_edid_displayid_vrr(struct drm_connector *connector, const struct edid *edid) { u8 *edid_ext = NULL; @@ -3467,8 +3681,9 @@ static void parse_edid_displayid_vrr(struct drm_connector *connector, j++; } } +EXPORT_IF_KUNIT(parse_edid_displayid_vrr); -static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, struct amdgpu_hdmi_vsdb_info *vsdb_info) { struct drm_connector *connector = &aconnector->base; @@ -3478,8 +3693,9 @@ static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, return connector->display_info.amd_vsdb.version != 0; } +EXPORT_IF_KUNIT(get_amd_vsdb); -static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, const struct edid *edid, struct amdgpu_hdmi_vsdb_info *vsdb_info) { @@ -3510,6 +3726,7 @@ static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, return valid_vsdb_found ? i : -ENODEV; } +EXPORT_IF_KUNIT(parse_hdmi_amd_vsdb); /** * amdgpu_dm_update_freesync_caps - Update Freesync capabilities diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index c5b8b13f8f06..13c54229d72c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -146,7 +146,63 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, uint32_t link_index); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num); +u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap); +void parse_edid_displayid_vrr(struct drm_connector *connector, + const struct edid *edid); +int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, + struct amdgpu_hdmi_vsdb_info *vsdb_info); +int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, + const struct edid *edid, + struct amdgpu_hdmi_vsdb_info *vsdb_info); +void amdgpu_dm_connector_funcs_force(struct drm_connector *connector); +enum dc_status dm_validate_stream_and_context(struct dc *dc, + struct dc_stream_state *stream); +struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector); +void amdgpu_dm_get_native_mode(struct drm_connector *connector); +struct drm_display_mode *amdgpu_dm_create_common_mode(struct drm_encoder *encoder, + const char *name, + int hdisplay, int vdisplay); +void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, + struct drm_connector *connector); +void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, + const struct drm_edid *drm_edid); +uint add_fs_modes(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, + const struct drm_edid *drm_edid); +void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector); +void create_eml_sink(struct amdgpu_dm_connector *aconnector); +void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector); +void dm_encoder_helper_disable(struct drm_encoder *encoder); +int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state); enum drm_mode_subconnector get_subconnector_type(struct dc_link *link); +void update_subconnector_property(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_fbc_init(struct drm_connector *connector); +void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector); +void fill_stream_properties_from_drm_display_mode( + struct dc_stream_state *stream, + const struct drm_display_mode *mode_in, + const struct drm_connector *connector, + const struct drm_connector_state *connector_state, + const struct dc_stream_state *old_stream, + int requested_bpc); +struct dc_stream_state * +create_stream_for_sink(struct drm_connector *connector, + const struct drm_display_mode *drm_mode, + const struct dm_connector_state *dm_state, + const struct dc_stream_state *old_stream, + int requested_bpc); +enum drm_connector_status +amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force); +enum drm_connector_status +amdgpu_dm_connector_detect(struct drm_connector *connector, bool force); +void amdgpu_dm_connector_unregister(struct drm_connector *connector); +int amdgpu_dm_connector_late_register(struct drm_connector *connector); +void amdgpu_dm_connector_destroy(struct drm_connector *connector); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, @@ -155,8 +211,12 @@ bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, int to_drm_connector_type(enum signal_type st, uint32_t connector_id); bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, struct drm_display_mode *mode); enum dc_aspect_ratio get_aspect_ratio(const struct drm_display_mode *mode_in); +void copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, + struct drm_display_mode *dst_mode); void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, const struct drm_display_mode *native_mode, bool scale_enabled); +void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector); #endif #endif /* __AMDGPU_DM_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index 970490c401e9..05387bedf24a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -33,7 +33,7 @@ #include "amdgpu_securedisplay.h" #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" static const char *const pipe_crc_sources[] = { "none", @@ -152,6 +152,7 @@ const char *const *amdgpu_dm_crtc_get_crc_sources(struct drm_crtc *crtc, *count = ARRAY_SIZE(pipe_crc_sources); return pipe_crc_sources; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_get_crc_sources); #ifdef CONFIG_DRM_AMD_SECURE_DISPLAY static void update_phy_id_mapping(struct amdgpu_device *adev) @@ -564,6 +565,7 @@ amdgpu_dm_crtc_verify_crc_source(struct drm_crtc *crtc, const char *src_name, *values_cnt = 3; return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_verify_crc_source); int amdgpu_dm_crtc_configure_crc_source(struct drm_crtc *crtc, struct dm_crtc_state *dm_crtc_state, @@ -636,6 +638,7 @@ unlock: return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_configure_crc_source); int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) { @@ -813,6 +816,7 @@ cleanup: return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_set_crc_source); /** * amdgpu_dm_crtc_handle_crc_irq: Report to DRM the CRC on given CRTC. @@ -870,6 +874,7 @@ void amdgpu_dm_crtc_handle_crc_irq(struct drm_crtc *crtc) drm_crtc_accurate_vblank_count(crtc), crcs); } } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_handle_crc_irq); #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) void amdgpu_dm_crtc_handle_crc_window_irq(struct drm_crtc *crtc) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 0ad7704800d9..079d4ccc88da 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -34,7 +34,7 @@ #include "amdgpu_dm_plane.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_debugfs.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "modules/inc/mod_power.h" #define HPD_DETECTION_PERIOD_uS 2000000 @@ -281,9 +281,25 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable) drm_crtc_vblank_restore(crtc); } - if (dc_supports_vrr(dm->dc->ctx->dce_version)) { + /* + * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver + * vblank and pageflip completion events, so enable it whenever vblank + * is enabled. On DCE, vupdate is only needed in VRR mode. + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) { if (enable) { - /* vblank irq on -> Only need vupdate irq in vrr mode */ + rc = amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Get vupdate_irq ret=%d\n", rc); + } else { + rc = amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Put vupdate_irq ret=%d\n", rc); + } + } else if (dc_supports_vrr(dm->dc->ctx->dce_version)) { + if (enable) { + /* vblank irq on -> Only need vupdate irq in vrr mode + * Not ref-counted since we need explicit enable/disable + * for DCE VRR handling + */ if (amdgpu_dm_crtc_vrr_active(acrtc_state)) rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, true); } else { @@ -292,39 +308,46 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable) } } - if (rc) - return rc; - - /* crtc vblank or vstartup interrupt */ - if (enable) { - rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc); - } else { - rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc); - } - if (rc) return rc; /* - * hubp surface flip interrupt - * - * We have no guarantee that the frontend index maps to the same - * backend index - some even map to more than one. - * - * TODO: Use a different interrupt or check DC itself for the mapping. + * VLINE0 (crtc_irq) and GRPH_PFLIP (pageflip_irq) are only used on + * DCE. On DCN, vblank and pageflip completion are delivered from + * VUPDATE_NO_LOCK (enabled above), so don't touch them here. */ - if (enable) { - rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc); - } else { - rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc); - } + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { + /* crtc vblank or vstartup interrupt */ + if (enable) { + rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc); + } else { + rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc); + } - if (rc) - return rc; + if (rc) + return rc; + + /* + * hubp surface flip interrupt + * + * We have no guarantee that the frontend index maps to the same + * backend index - some even map to more than one. + * + * TODO: Use a different interrupt or check DC itself for the mapping. + */ + if (enable) { + rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc); + } else { + rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc); + } + + if (rc) + return rc; + } #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) /* crtc vline0 interrupt, only available on DCN+ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c new file mode 100644 index 000000000000..ab50d58896ab --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dc.h" +#include "dal_asic_id.h" +#include "amdgpu.h" +#include "amdgpu_display.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_plane.h" +#include "amdgpu_dm_cursor.h" +#include "dm_helpers.h" + +static int dm_check_cursor_fb(struct amdgpu_crtc *new_acrtc, + struct drm_plane_state *new_plane_state, + struct drm_framebuffer *fb) +{ + struct amdgpu_device *adev = drm_to_adev(new_acrtc->base.dev); + struct amdgpu_framebuffer *afb = to_amdgpu_framebuffer(fb); + unsigned int pitch; + bool linear; + + if (fb->width > new_acrtc->max_cursor_width || + fb->height > new_acrtc->max_cursor_height) { + drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB size %dx%d\n", + new_plane_state->fb->width, + new_plane_state->fb->height); + return -EINVAL; + } + if (new_plane_state->src_w != fb->width << 16 || + new_plane_state->src_h != fb->height << 16) { + drm_dbg_atomic(adev_to_drm(adev), "Cropping not supported for cursor plane\n"); + return -EINVAL; + } + + /* Pitch in pixels */ + pitch = fb->pitches[0] / fb->format->cpp[0]; + + if (fb->width != pitch) { + drm_dbg_atomic(adev_to_drm(adev), "Cursor FB width %d doesn't match pitch %d", + fb->width, pitch); + return -EINVAL; + } + + switch (pitch) { + case 64: + case 128: + case 256: + /* FB pitch is supported by cursor plane */ + break; + default: + drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB pitch %d px\n", pitch); + return -EINVAL; + } + + /* Core DRM takes care of checking FB modifiers, so we only need to + * check tiling flags when the FB doesn't have a modifier. + */ + if (!(fb->flags & DRM_MODE_FB_MODIFIERS)) { +#if defined(CONFIG_DRM_AMD_DC_DCN6_0) || defined(CONFIG_DRM_AMD_DC_DCN5_0) + if (adev->family == AMDGPU_FAMILY_GC_12_0_0 + || adev->family == AMDGPU_FAMILY_GC_13_0_1) { +#else + if (adev->family == AMDGPU_FAMILY_GC_12_0_0) { +#endif + linear = AMDGPU_TILING_GET(afb->tiling_flags, GFX12_SWIZZLE_MODE) == 0; + } else if (adev->family >= AMDGPU_FAMILY_AI) { + linear = AMDGPU_TILING_GET(afb->tiling_flags, SWIZZLE_MODE) == 0; + } else { + linear = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_2D_TILED_THIN1 && + AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_1D_TILED_THIN1 && + AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE) == 0; + } + if (!linear) { + drm_dbg_atomic(adev_to_drm(adev), "Cursor FB not linear"); + return -EINVAL; + } + } + + return 0; +} + +/* + * Helper function for checking the cursor in native mode + */ +int amdgpu_dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc, + struct drm_plane *plane, + struct drm_plane_state *new_plane_state, + bool enable) +{ + + struct amdgpu_crtc *new_acrtc; + int ret; + + if (!enable || !new_plane_crtc || + drm_atomic_plane_disabling(plane->state, new_plane_state)) + return 0; + + new_acrtc = to_amdgpu_crtc(new_plane_crtc); + + if (new_plane_state->src_x != 0 || new_plane_state->src_y != 0) { + drm_dbg_atomic(new_plane_crtc->dev, "Cropping not supported for cursor plane\n"); + return -EINVAL; + } + + if (new_plane_state->fb) { + ret = dm_check_cursor_fb(new_acrtc, new_plane_state, + new_plane_state->fb); + if (ret) + return ret; + } + + return 0; +} + +bool amdgpu_dm_should_update_native_cursor(struct drm_atomic_commit *state, + struct drm_crtc *old_plane_crtc, + struct drm_crtc *new_plane_crtc, + bool enable) +{ + struct drm_crtc_state *old_crtc_state, *new_crtc_state; + struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state; + + if (!enable) { + if (old_plane_crtc == NULL) + return true; + + old_crtc_state = drm_atomic_get_old_crtc_state( + state, old_plane_crtc); + dm_old_crtc_state = to_dm_crtc_state(old_crtc_state); + + return dm_old_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE; + } + + if (new_plane_crtc == NULL) + return true; + + new_crtc_state = drm_atomic_get_new_crtc_state( + state, new_plane_crtc); + dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); + + return dm_new_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE; +} +EXPORT_IF_KUNIT(amdgpu_dm_should_update_native_cursor); + +STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, + int *src_w, int *src_h) +{ + switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) { + case DRM_MODE_ROTATE_90: + case DRM_MODE_ROTATE_270: + *src_w = plane_state->src_h >> 16; + *src_h = plane_state->src_w >> 16; + break; + case DRM_MODE_ROTATE_0: + case DRM_MODE_ROTATE_180: + default: + *src_w = plane_state->src_w >> 16; + *src_h = plane_state->src_h >> 16; + break; + } +} +EXPORT_IF_KUNIT(dm_get_oriented_plane_size); + +STATIC_IFN_KUNIT void +dm_get_plane_scale(struct drm_plane_state *plane_state, + int *out_plane_scale_w, int *out_plane_scale_h) +{ + int plane_src_w, plane_src_h; + + dm_get_oriented_plane_size(plane_state, &plane_src_w, &plane_src_h); + *out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 / plane_src_w : 0; + *out_plane_scale_h = plane_src_h ? plane_state->crtc_h * 1000 / plane_src_h : 0; +} +EXPORT_IF_KUNIT(dm_get_plane_scale); + +/** + * DOC: Cursor Modes - Native vs Overlay + * + * In native mode, the cursor uses a integrated cursor pipe within each DCN hw + * plane. It does not require a dedicated hw plane to enable, but it is + * subjected to the same z-order and scaling as the hw plane. It also has format + * restrictions, a RGB cursor in native mode cannot be enabled within a non-RGB + * hw plane. + * + * In overlay mode, the cursor uses a separate DCN hw plane, and thus has its + * own scaling and z-pos. It also has no blending restrictions. It lends to a + * cursor behavior more akin to a DRM client's expectations. However, it does + * occupy an extra DCN plane, and therefore will only be used if a DCN plane is + * available. + */ + +/** + * dm_plane_color_pipeline_active() - Check if a plane's color pipeline active. + * @state: DRM atomic state + * @plane: DRM plane to check + * @use_old: if true, inspect the old colorop states; otherwise the new ones + * + * A color pipeline may be selected (color_pipeline != NULL) but still is + * inactive if every colorop in the chain is bypassed. Only return + * true when at least one colorop has bypass == false, meaning the cursor + * would be subjected to the transformation in native mode. + * + * Return: true if the pipeline modifies pixels, false otherwise. + */ +static bool dm_plane_color_pipeline_active(struct drm_atomic_commit *state, + struct drm_plane *plane, + bool use_old) +{ + struct drm_colorop *colorop; + struct drm_colorop_state *old_colorop_state, *new_colorop_state; + int i; + + for_each_oldnew_colorop_in_state(state, colorop, old_colorop_state, new_colorop_state, i) { + struct drm_colorop_state *cstate = use_old ? old_colorop_state : new_colorop_state; + + if (cstate->colorop->plane != plane) + continue; + if (!cstate->bypass) + return true; + } + return false; +} + +/** + * amdgpu_dm_crtc_get_cursor_mode() - Determine the required cursor mode on crtc + * @adev: amdgpu device + * @state: DRM atomic state + * @dm_crtc_state: amdgpu state for the CRTC containing the cursor + * @cursor_mode: Returns the required cursor mode on dm_crtc_state + * + * Get whether the cursor should be enabled in native mode, or overlay mode, on + * the dm_crtc_state. + * + * The cursor should be enabled in overlay mode if there exists an underlying + * plane - on which the cursor may be blended - that is either YUV formatted, + * scaled differently from the cursor, or has a color pipeline active. + * + * Since zpos info is required, drm_atomic_normalize_zpos must be called before + * calling this function. + * + * Return: 0 on success, or an error code if getting the cursor plane state + * failed. + */ +int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev, + struct drm_atomic_commit *state, + struct dm_crtc_state *dm_crtc_state, + enum amdgpu_dm_cursor_mode *cursor_mode) +{ + struct drm_plane_state *old_plane_state, *plane_state, *cursor_state; + struct drm_crtc_state *crtc_state = &dm_crtc_state->base; + struct drm_plane *plane; + bool consider_mode_change = false; + bool entire_crtc_covered = false; + bool cursor_changed = false; + int underlying_scale_w, underlying_scale_h; + int cursor_scale_w, cursor_scale_h; + int i; + + /* Overlay cursor not supported on HW before DCN + * DCN401/420 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions + * as previous DCN generations, so enable native mode on DCN401/420 + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) || +#if defined(CONFIG_DRM_AMD_DC_DCN6_0) + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(6, 0, 0)) { +#else + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1)) { +#endif + *cursor_mode = DM_CURSOR_NATIVE_MODE; + return 0; + } + + /* Init cursor_mode to be the same as current */ + *cursor_mode = dm_crtc_state->cursor_mode; + + /* + * Cursor mode can change if a plane's format changes, scale changes, is + * enabled/disabled, z-order changes, or color management properties change. + */ + for_each_oldnew_plane_in_state(state, plane, old_plane_state, plane_state, i) { + int new_scale_w, new_scale_h, old_scale_w, old_scale_h; + + /* Only care about planes on this CRTC */ + if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0) + continue; + + if (plane->type == DRM_PLANE_TYPE_CURSOR) + cursor_changed = true; + + if (drm_atomic_plane_enabling(old_plane_state, plane_state) || + drm_atomic_plane_disabling(old_plane_state, plane_state) || + old_plane_state->fb->format != plane_state->fb->format) { + consider_mode_change = true; + break; + } + + dm_get_plane_scale(plane_state, &new_scale_w, &new_scale_h); + dm_get_plane_scale(old_plane_state, &old_scale_w, &old_scale_h); + if (new_scale_w != old_scale_w || new_scale_h != old_scale_h) { + consider_mode_change = true; + break; + } + + /* + * A non-cursor plane moving or resizing (without a scale change) + * changes how much of the CRTC it covers. This can create or + * remove a hole under the cursor and thus flip the required + * cursor mode (native vs overlay), so its destination rect must + * be re-evaluated too. + * + * The cursor plane itself is deliberately excluded: the cursor + * mode depends on the underlying planes' coverage, not on the + * cursor's position (see the entire_crtc_covered logic below). + * Triggering on cursor movement would force every legacy cursor + * update off its fast path, and in a cursor-only commit - where + * the underlying planes are not part of the state - the coverage + * loop would see no covering plane and misevaluate the mode as + * overlay, regressing flip-vs-cursor-legacy. + */ + if (plane->type != DRM_PLANE_TYPE_CURSOR && + (old_plane_state->crtc_x != plane_state->crtc_x || + old_plane_state->crtc_y != plane_state->crtc_y || + old_plane_state->crtc_w != plane_state->crtc_w || + old_plane_state->crtc_h != plane_state->crtc_h)) { + consider_mode_change = true; + break; + } + + if (dm_plane_color_pipeline_active(state, plane, true) != + dm_plane_color_pipeline_active(state, plane, false)) { + consider_mode_change = true; + break; + } + } + + if (!consider_mode_change && !crtc_state->zpos_changed) + return 0; + + /* + * If no cursor change on this CRTC, and not enabled on this CRTC, then + * no need to set cursor mode. This avoids needlessly locking the cursor + * state. + */ + if (!cursor_changed && + !(drm_plane_mask(crtc_state->crtc->cursor) & crtc_state->plane_mask)) { + return 0; + } + + cursor_state = drm_atomic_get_plane_state(state, + crtc_state->crtc->cursor); + if (IS_ERR(cursor_state)) + return PTR_ERR(cursor_state); + + /* Cursor is disabled */ + if (!cursor_state->fb) + return 0; + + /* For all planes in descending z-order (all of which are below cursor + * as per zpos definitions), check their scaling and format + */ + for_each_oldnew_plane_in_descending_zpos(state, plane, old_plane_state, plane_state) { + + /* Only care about non-cursor planes on this CRTC */ + if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0 || + plane->type == DRM_PLANE_TYPE_CURSOR) + continue; + + /* Underlying plane is YUV format - use overlay cursor */ + if (amdgpu_dm_plane_is_video_format(plane_state->fb->format->format)) { + *cursor_mode = DM_CURSOR_OVERLAY_MODE; + return 0; + } + + /* Underlying plane has an active color pipeline - cursor would be transformed */ + if (dm_plane_color_pipeline_active(state, plane, false)) { + *cursor_mode = DM_CURSOR_OVERLAY_MODE; + return 0; + } + + dm_get_plane_scale(plane_state, + &underlying_scale_w, &underlying_scale_h); + dm_get_plane_scale(cursor_state, + &cursor_scale_w, &cursor_scale_h); + + /* Underlying plane has different scale - use overlay cursor */ + if (cursor_scale_w != underlying_scale_w && + cursor_scale_h != underlying_scale_h) { + *cursor_mode = DM_CURSOR_OVERLAY_MODE; + return 0; + } + + /* If this plane covers the whole CRTC, no need to check planes underneath */ + if (plane_state->crtc_x <= 0 && plane_state->crtc_y <= 0 && + plane_state->crtc_x + plane_state->crtc_w >= crtc_state->mode.hdisplay && + plane_state->crtc_y + plane_state->crtc_h >= crtc_state->mode.vdisplay) { + entire_crtc_covered = true; + break; + } + } + + /* If planes do not cover the entire CRTC, use overlay mode to enable + * cursor over holes + */ + if (entire_crtc_covered) + *cursor_mode = DM_CURSOR_NATIVE_MODE; + else + *cursor_mode = DM_CURSOR_OVERLAY_MODE; + + return 0; +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h new file mode 100644 index 000000000000..5f619e4474aa --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#ifndef __AMDGPU_DM_CURSOR_H__ +#define __AMDGPU_DM_CURSOR_H__ + +int amdgpu_dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc, + struct drm_plane *plane, + struct drm_plane_state *new_plane_state, + bool enable); + +bool amdgpu_dm_should_update_native_cursor(struct drm_atomic_commit *state, + struct drm_crtc *old_plane_crtc, + struct drm_crtc *new_plane_crtc, + bool enable); + +int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev, + struct drm_atomic_commit *state, + struct dm_crtc_state *dm_crtc_state, + enum amdgpu_dm_cursor_mode *cursor_mode); + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, + int *src_w, int *src_h); +void dm_get_plane_scale(struct drm_plane_state *plane_state, + int *out_plane_scale_w, int *out_plane_scale_h); +#endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */ + +#endif /* __AMDGPU_DM_CURSOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 2692d1890ba2..8e35d9ec22d6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -38,7 +38,7 @@ #include "amdgpu_ucode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_dmub.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include #include @@ -147,7 +147,7 @@ int dm_dmub_hw_init(struct amdgpu_device *adev) struct dmub_srv_hw_params hw_params; enum dmub_status status; const unsigned char *fw_inst_const, *fw_bss_data; - u32 i, fw_inst_const_size, fw_bss_data_size; + u32 fw_inst_const_size, fw_bss_data_size; bool has_hw_support; if (!dmub_srv) @@ -243,8 +243,7 @@ int dm_dmub_hw_init(struct amdgpu_device *adev) if (dmcu) hw_params.psp_version = dmcu->psp_version; - for (i = 0; i < fb_info->num_fb; ++i) - hw_params.fb[i] = &fb_info->fb[i]; + hw_params.fb_info = fb_info; /* Enable usb4 dpia in the FW APU */ if (dc->caps.is_apu && @@ -386,7 +385,7 @@ dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev, return DMUB_STATUS_TIMEOUT; } -static void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev) +STATIC_IFN_KUNIT void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev) { void *bb; long long addr; @@ -441,6 +440,7 @@ free_bb: return NULL; } +EXPORT_IF_KUNIT(dm_dmub_get_vbios_bounding_box); enum dmub_ips_disable_type dm_get_default_ips_mode( struct amdgpu_device *adev) @@ -507,6 +507,7 @@ int dm_dmub_sw_init(struct amdgpu_device *adev) DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_CURSOR_OFFLOAD */ }; int r; + int mem_domain = AMDGPU_GEM_DOMAIN_GTT; switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(2, 1, 0): @@ -640,13 +641,19 @@ int dm_dmub_sw_init(struct amdgpu_device *adev) return -EINVAL; } + /* Limit to allocate dmub to GTT on DCN32/1 + * TODO: Other asics with GDDR7 may have worse latency + */ + if (dmub_asic != DMUB_ASIC_DCN32 && + dmub_asic != DMUB_ASIC_DCN321) + mem_domain |= AMDGPU_GEM_DOMAIN_VRAM; + /* * Allocate a framebuffer based on the total size of all the regions. * TODO: Move this into GART. */ r = amdgpu_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE, - AMDGPU_GEM_DOMAIN_VRAM | - AMDGPU_GEM_DOMAIN_GTT, + mem_domain, &adev->dm.dmub_bo, &adev->dm.dmub_bo_gpu_addr, &adev->dm.dmub_bo_cpu_addr); @@ -823,8 +830,9 @@ out: mutex_unlock(&adev->dm.dpia_aux_lock); return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_process_dmub_aux_transfer_sync); -static void abort_fused_io( +STATIC_IFN_KUNIT void abort_fused_io( struct dc_context *ctx, const struct dmub_cmd_fused_request *request ) @@ -838,6 +846,7 @@ static void abort_fused_io( io->request = *request; dm_execute_dmub_cmd(ctx, &command, DM_DMUB_WAIT_TYPE_NO_WAIT); } +EXPORT_IF_KUNIT(abort_fused_io); static bool execute_fused_io( struct amdgpu_device *dev, @@ -925,6 +934,7 @@ int amdgpu_dm_process_dmub_set_config_sync( mutex_unlock(&adev->dm.dpia_aux_lock); return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_process_dmub_set_config_sync); bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) { @@ -933,6 +943,7 @@ bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, e guard(spinlock_irqsave)(&adev->dm.dmub_lock); return dc_dmub_srv_cmd_run(ctx->dmub_srv, cmd, wait_type); } +EXPORT_IF_KUNIT(dm_execute_dmub_cmd); bool dm_execute_dmub_cmd_list(const struct dc_context *ctx, unsigned int count, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h index a4a03e40ec37..c53728d1c131 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h @@ -65,4 +65,13 @@ int dm_init_microcode(struct amdgpu_device *adev); #define FIRMWARE_RAVEN_DMCU "amdgpu/raven_dmcu.bin" #define FIRMWARE_NAVI12_DMCU "amdgpu/navi12_dmcu.bin" +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct dc_context; +struct dmub_cmd_fused_request; + +void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev); +void abort_fused_io(struct dc_context *ctx, + const struct dmub_cmd_fused_request *request); +#endif + #endif /* AMDGPU_DM_AMDGPU_DM_DMUB_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c new file mode 100644 index 000000000000..7e484adb4120 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_psr.h" +#include "amdgpu_dm_replay.h" +#include "amdgpu_dm_freesync.h" +#include "dm_helpers.h" +#include "modules/inc/mod_freesync.h" + +bool amdgpu_dm_is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state) +{ + if (new_state->stream->adjust.timing_adjust_pending) + return true; + if (new_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED) + return true; + else if (amdgpu_dm_crtc_vrr_active(old_state) != amdgpu_dm_crtc_vrr_active(new_state)) + return true; + else + return false; +} +EXPORT_IF_KUNIT(amdgpu_dm_is_dc_timing_adjust_needed); + +bool +amdgpu_dm_is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, + struct drm_crtc_state *new_crtc_state) +{ + const struct drm_display_mode *old_mode, *new_mode; + + if (!old_crtc_state || !new_crtc_state) + return false; + + old_mode = &old_crtc_state->mode; + new_mode = &new_crtc_state->mode; + + if (old_mode->clock == new_mode->clock && + old_mode->hdisplay == new_mode->hdisplay && + old_mode->vdisplay == new_mode->vdisplay && + old_mode->htotal == new_mode->htotal && + old_mode->vtotal != new_mode->vtotal && + old_mode->hsync_start == new_mode->hsync_start && + old_mode->vsync_start != new_mode->vsync_start && + old_mode->hsync_end == new_mode->hsync_end && + old_mode->vsync_end != new_mode->vsync_end && + old_mode->hskew == new_mode->hskew && + old_mode->vscan == new_mode->vscan && + (old_mode->vsync_end - old_mode->vsync_start) == + (new_mode->vsync_end - new_mode->vsync_start)) + return true; + + return false; +} +EXPORT_IF_KUNIT(amdgpu_dm_is_timing_unchanged_for_freesync); + +void amdgpu_dm_set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state) +{ + u64 num, den, res; + struct drm_crtc_state *new_crtc_state = &dm_new_crtc_state->base; + + dm_new_crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + + num = (unsigned long long)new_crtc_state->mode.clock * 1000 * 1000000; + den = (unsigned long long)new_crtc_state->mode.htotal * + (unsigned long long)new_crtc_state->mode.vtotal; + + res = div_u64(num, den); + dm_new_crtc_state->freesync_config.fixed_refresh_in_uhz = res; +} +EXPORT_IF_KUNIT(amdgpu_dm_set_freesync_fixed_config); + +void amdgpu_dm_reset_freesync_config_for_crtc( + struct dm_crtc_state *new_crtc_state) +{ + new_crtc_state->vrr_supported = false; + + memset(&new_crtc_state->vrr_infopacket, 0, + sizeof(new_crtc_state->vrr_infopacket)); +} +EXPORT_IF_KUNIT(amdgpu_dm_reset_freesync_config_for_crtc); + +void amdgpu_dm_get_freesync_config_for_crtc( + struct dm_crtc_state *new_crtc_state, + struct dm_connector_state *new_con_state) +{ + struct mod_freesync_config config = {0}; + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode *mode = &new_crtc_state->base.mode; + int vrefresh = drm_mode_vrefresh(mode); + bool fs_vid_mode = false; + + if (new_con_state->base.connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + return; + + aconnector = to_amdgpu_dm_connector(new_con_state->base.connector); + + new_crtc_state->vrr_supported = new_con_state->freesync_capable && + vrefresh >= aconnector->min_vfreq && + vrefresh <= aconnector->max_vfreq; + + if (new_crtc_state->vrr_supported) { + new_crtc_state->stream->ignore_msa_timing_param = true; + fs_vid_mode = new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED; + + config.min_refresh_in_uhz = aconnector->min_vfreq * 1000000; + config.max_refresh_in_uhz = aconnector->max_vfreq * 1000000; + config.vsif_supported = true; + config.btr = true; + + if (fs_vid_mode) { + config.state = VRR_STATE_ACTIVE_FIXED; + config.fixed_refresh_in_uhz = new_crtc_state->freesync_config.fixed_refresh_in_uhz; + goto out; + } else if (new_crtc_state->base.vrr_enabled) { + config.state = VRR_STATE_ACTIVE_VARIABLE; + } else { + config.state = VRR_STATE_INACTIVE; + } + } else { + config.state = VRR_STATE_UNSUPPORTED; + } +out: + new_crtc_state->freesync_config = config; +} +EXPORT_IF_KUNIT(amdgpu_dm_get_freesync_config_for_crtc); + +void amdgpu_dm_update_freesync_state_on_stream( + struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state, + struct dc_stream_state *new_stream, + struct dc_plane_state *surface, + u32 flip_timestamp_in_us) +{ + struct mod_vrr_params vrr_params; + struct dc_info_packet vrr_infopacket = {0}; + struct amdgpu_device *adev = dm->adev; + struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc); + unsigned long flags; + bool pack_sdp_v1_3 = false; + struct amdgpu_dm_connector *aconn; + enum vrr_packet_type packet_type = PACKET_TYPE_VRR; + + if (!new_stream) + return; + + /* + * TODO: Determine why min/max totals and vrefresh can be 0 here. + * For now it's sufficient to just guard against these conditions. + */ + + if (!new_stream->timing.h_total || !new_stream->timing.v_total) + return; + + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + vrr_params = acrtc->dm_irq_params.vrr_params; + + if (surface) { + mod_freesync_handle_preflip( + dm->freesync_module, + surface, + new_stream, + flip_timestamp_in_us, + &vrr_params); + + if (adev->family < AMDGPU_FAMILY_AI && + amdgpu_dm_crtc_vrr_active(new_crtc_state)) { + mod_freesync_handle_v_update(dm->freesync_module, + new_stream, &vrr_params); + + /* Need to call this before the frame ends. */ + dc_stream_adjust_vmin_vmax(dm->dc, + new_crtc_state->stream, + &vrr_params.adjust); + } + } + + aconn = (struct amdgpu_dm_connector *)new_stream->dm_stream_context; + + if (aconn && (aconn->as_type == FREESYNC_TYPE_PCON_IN_WHITELIST || aconn->vsdb_info.replay_mode)) { + pack_sdp_v1_3 = aconn->pack_sdp_v1_3; + + if (aconn->vsdb_info.amd_vsdb_version == 1) + packet_type = PACKET_TYPE_FS_V1; + else if (aconn->vsdb_info.amd_vsdb_version == 2) + packet_type = PACKET_TYPE_FS_V2; + else if (aconn->vsdb_info.amd_vsdb_version == 3) + packet_type = PACKET_TYPE_FS_V3; + + mod_build_adaptive_sync_infopacket(new_stream, aconn->as_type, NULL, + &new_stream->adaptive_sync_infopacket); + } + + mod_freesync_build_vrr_infopacket( + dm->freesync_module, + new_stream, + &vrr_params, + packet_type, + TRANSFER_FUNC_UNKNOWN, + &vrr_infopacket, + pack_sdp_v1_3); + + new_crtc_state->freesync_vrr_info_changed |= + (memcmp(&new_crtc_state->vrr_infopacket, + &vrr_infopacket, + sizeof(vrr_infopacket)) != 0); + + acrtc->dm_irq_params.vrr_params = vrr_params; + new_crtc_state->vrr_infopacket = vrr_infopacket; + + new_stream->vrr_infopacket = vrr_infopacket; + new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); + + if (new_crtc_state->freesync_vrr_info_changed) + drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d", + new_crtc_state->base.crtc->base.id, + (int)new_crtc_state->base.vrr_enabled, + (int)vrr_params.state); + + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); +} + +void amdgpu_dm_update_stream_irq_parameters( + struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state) +{ + struct dc_stream_state *new_stream = new_crtc_state->stream; + struct mod_vrr_params vrr_params; + struct mod_freesync_config config = new_crtc_state->freesync_config; + struct amdgpu_device *adev = dm->adev; + struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc); + unsigned long flags; + + if (!new_stream) + return; + + /* + * TODO: Determine why min/max totals and vrefresh can be 0 here. + * For now it's sufficient to just guard against these conditions. + */ + if (!new_stream->timing.h_total || !new_stream->timing.v_total) + return; + + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + vrr_params = acrtc->dm_irq_params.vrr_params; + + if (new_crtc_state->vrr_supported && + config.min_refresh_in_uhz && + config.max_refresh_in_uhz) { + /* + * if freesync compatible mode was set, config.state will be set + * in atomic check + */ + if (config.state == VRR_STATE_ACTIVE_FIXED && config.fixed_refresh_in_uhz && + (!drm_atomic_crtc_needs_modeset(&new_crtc_state->base) || + new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED)) { + vrr_params.max_refresh_in_uhz = config.max_refresh_in_uhz; + vrr_params.min_refresh_in_uhz = config.min_refresh_in_uhz; + vrr_params.fixed_refresh_in_uhz = config.fixed_refresh_in_uhz; + vrr_params.state = VRR_STATE_ACTIVE_FIXED; + } else { + config.state = new_crtc_state->base.vrr_enabled ? + VRR_STATE_ACTIVE_VARIABLE : + VRR_STATE_INACTIVE; + } + } else { + config.state = VRR_STATE_UNSUPPORTED; + } + + mod_freesync_build_vrr_params(dm->freesync_module, + new_stream, + &config, &vrr_params); + + new_crtc_state->freesync_config = config; + /* Copy state for access from DM IRQ handler */ + acrtc->dm_irq_params.freesync_config = config; + acrtc->dm_irq_params.active_planes = new_crtc_state->active_planes; + acrtc->dm_irq_params.vrr_params = vrr_params; + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); +} + +void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, + struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state) +{ + struct amdgpu_device *adev = dm->adev; + bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state); + bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state); + + /* Only DCE gates vupdate on VRR, keep it enabled for DCN */ + bool vrr_gates_vupdate = amdgpu_ip_version(adev, DCE_HWIP, 0) == 0; + + if (!old_vrr_active && new_vrr_active) { + /* Transition VRR inactive -> active: + * While VRR is active, we must not disable vblank irq, as a + * reenable after disable would compute bogus vblank/pflip + * timestamps if it likely happened inside display front-porch. + * + * We also need vupdate irq for the actual core vblank handling + * at end of vblank. + */ + if (vrr_gates_vupdate) + WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0); + WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0); + drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n", + __func__, new_state->base.crtc->base.id); + + scoped_guard(mutex, &dm->dc_lock) { + dc_exit_ips_for_hw_access(dm->dc); + amdgpu_dm_psr_set_event(dm, new_state->stream, true, + psr_event_vrr_transition, true); + amdgpu_dm_replay_set_event(dm, new_state->stream, true, + replay_event_vrr, true); + } + } else if (old_vrr_active && !new_vrr_active) { + /* Transition VRR active -> inactive: + * Allow vblank irq disable again for fixed refresh rate. + */ + if (vrr_gates_vupdate) + WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0); + drm_crtc_vblank_put(new_state->base.crtc); + drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n", + __func__, new_state->base.crtc->base.id); + + scoped_guard(mutex, &dm->dc_lock) { + dc_exit_ips_for_hw_access(dm->dc); + amdgpu_dm_psr_set_event(dm, new_state->stream, false, + psr_event_vrr_transition, false); + amdgpu_dm_replay_set_event(dm, new_state->stream, false, + replay_event_vrr, false); + } + } +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h new file mode 100644 index 000000000000..24c410306284 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#ifndef __AMDGPU_DM_FREESYNC_H__ +#define __AMDGPU_DM_FREESYNC_H__ + +#include + +struct amdgpu_display_manager; +struct dm_crtc_state; +struct dm_connector_state; +struct dc_stream_state; +struct dc_plane_state; +struct drm_crtc_state; + +bool amdgpu_dm_is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state); + +bool amdgpu_dm_is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, + struct drm_crtc_state *new_crtc_state); + +void amdgpu_dm_set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state); + +void amdgpu_dm_reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); + +void amdgpu_dm_get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state, + struct dm_connector_state *new_con_state); + +void amdgpu_dm_update_freesync_state_on_stream(struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state, + struct dc_stream_state *new_stream, + struct dc_plane_state *surface, + u32 flip_timestamp_in_us); + +void amdgpu_dm_update_stream_irq_parameters(struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state); + +void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, + struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state); + +#endif /* __AMDGPU_DM_FREESYNC_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 5dbeb1e017d4..c887a70ae74d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -31,7 +31,6 @@ #include "dm_helpers.h" #include #include "hdcp_psp.h" -#include "amdgpu_dm_kunit_helpers.h" /* * If the SRM version being loaded is less than or equal to the @@ -39,8 +38,7 @@ */ #define PSP_SRM_VERSION_MAX 0xFFFF -static bool -lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size) +STATIC_IFN_KUNIT bool lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size) { struct dc_link *link = handle; struct i2c_payload i2c_payloads[] = {{true, address, size, (void *)data} }; @@ -49,9 +47,9 @@ lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size) return dm_helpers_submit_i2c(link->ctx, link, &cmd); } +EXPORT_IF_KUNIT(lp_write_i2c); -static bool -lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size) +STATIC_IFN_KUNIT bool lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size) { struct dc_link *link = handle; @@ -62,52 +60,56 @@ lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint3 return dm_helpers_submit_i2c(link->ctx, link, &cmd); } +EXPORT_IF_KUNIT(lp_read_i2c); -static bool -lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size) +STATIC_IFN_KUNIT bool lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size) { struct dc_link *link = handle; return dm_helpers_dp_write_dpcd(link->ctx, link, address, data, size); } +EXPORT_IF_KUNIT(lp_write_dpcd); -static bool -lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size) +STATIC_IFN_KUNIT bool lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size) { struct dc_link *link = handle; return dm_helpers_dp_read_dpcd(link->ctx, link, address, data, size); } +EXPORT_IF_KUNIT(lp_read_dpcd); -static bool lp_atomic_write_poll_read_i2c( - void *handle, - const struct mod_hdcp_atomic_op_i2c *write, - const struct mod_hdcp_atomic_op_i2c *poll, - struct mod_hdcp_atomic_op_i2c *read, - uint32_t poll_timeout_us, - uint8_t poll_mask_msb +STATIC_IFN_KUNIT bool lp_atomic_write_poll_read_i2c( + void *handle, + const struct mod_hdcp_atomic_op_i2c *write, + const struct mod_hdcp_atomic_op_i2c *poll, + struct mod_hdcp_atomic_op_i2c *read, + uint32_t poll_timeout_us, + uint8_t poll_mask_msb ) { struct dc_link *link = handle; return dm_atomic_write_poll_read_i2c(link, write, poll, read, poll_timeout_us, poll_mask_msb); } +EXPORT_IF_KUNIT(lp_atomic_write_poll_read_i2c); -static bool lp_atomic_write_poll_read_aux( - void *handle, - const struct mod_hdcp_atomic_op_aux *write, - const struct mod_hdcp_atomic_op_aux *poll, - struct mod_hdcp_atomic_op_aux *read, - uint32_t poll_timeout_us, - uint8_t poll_mask_msb +STATIC_IFN_KUNIT bool lp_atomic_write_poll_read_aux( + void *handle, + const struct mod_hdcp_atomic_op_aux *write, + const struct mod_hdcp_atomic_op_aux *poll, + struct mod_hdcp_atomic_op_aux *read, + uint32_t poll_timeout_us, + uint8_t poll_mask_msb ) { struct dc_link *link = handle; return dm_atomic_write_poll_read_aux(link, write, poll, read, poll_timeout_us, poll_mask_msb); } +EXPORT_IF_KUNIT(lp_atomic_write_poll_read_aux); -static uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size) +STATIC_IFN_KUNIT +uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size) { struct ta_hdcp_shared_memory *hdcp_cmd; @@ -130,8 +132,10 @@ static uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint return hdcp_cmd->out_msg.hdcp_get_srm.srm_buf; } +EXPORT_IF_KUNIT(psp_get_srm); -static int psp_set_srm(struct psp_context *psp, +STATIC_IFN_KUNIT +int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version) { struct ta_hdcp_shared_memory *hdcp_cmd; @@ -158,6 +162,7 @@ static int psp_set_srm(struct psp_context *psp, *srm_version = hdcp_cmd->out_msg.hdcp_set_srm.srm_version; return 0; } +EXPORT_IF_KUNIT(psp_set_srm); STATIC_IFN_KUNIT void process_output(struct hdcp_workqueue *hdcp_work) @@ -246,7 +251,8 @@ void hdcp_get_link_display_adjustments( } EXPORT_IF_KUNIT(hdcp_get_link_display_adjustments); -static void link_lock(struct hdcp_workqueue *work, bool lock) +STATIC_IFN_KUNIT +void link_lock(struct hdcp_workqueue *work, bool lock) { int i = 0; @@ -257,6 +263,31 @@ static void link_lock(struct hdcp_workqueue *work, bool lock) mutex_unlock(&work[i].mutex); } } +EXPORT_IF_KUNIT(link_lock); + +STATIC_IFN_KUNIT +void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, + struct hdcp_workqueue *hdcp_w, + unsigned int conn_index, + bool enable_encryption) +{ + if (enable_encryption) { + /* Explicitly set the saved SRM as sysfs call will be after we already enabled hdcp + * (s3 resume case) + */ + if (hdcp_work->srm_size > 0) + psp_set_srm(hdcp_work->hdcp.config.psp.handle, hdcp_work->srm, + hdcp_work->srm_size, + &hdcp_work->srm_version); + + schedule_delayed_work(&hdcp_w->property_validate_dwork, + msecs_to_jiffies(DRM_HDCP_CHECK_PERIOD_MS)); + } else { + hdcp_w->encryption_status[conn_index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; + cancel_delayed_work(&hdcp_w->property_validate_dwork); + } +} +EXPORT_IF_KUNIT(hdcp_update_display_encryption_control); void hdcp_update_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, @@ -281,29 +312,17 @@ void hdcp_update_display(struct hdcp_workqueue *hdcp_work, dc->debug.hdcp_lc_force_fw_enable, dc->debug.hdcp_lc_enable_sw_fallback, &link_adjust, &display_adjust); - - if (enable_encryption) { - /* Explicitly set the saved SRM as sysfs call will be after we already enabled hdcp - * (s3 resume case) - */ - if (hdcp_work->srm_size > 0) - psp_set_srm(hdcp_work->hdcp.config.psp.handle, hdcp_work->srm, - hdcp_work->srm_size, - &hdcp_work->srm_version); - - schedule_delayed_work(&hdcp_w->property_validate_dwork, - msecs_to_jiffies(DRM_HDCP_CHECK_PERIOD_MS)); - } else { - hdcp_w->encryption_status[conn_index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; - cancel_delayed_work(&hdcp_w->property_validate_dwork); - } + hdcp_update_display_encryption_control(hdcp_work, hdcp_w, conn_index, + enable_encryption); mod_hdcp_update_display(&hdcp_w->hdcp, conn_index, &link_adjust, &display_adjust, &hdcp_w->output); process_output(hdcp_w); } +EXPORT_IF_KUNIT(hdcp_update_display); -static void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, +STATIC_IFN_KUNIT +void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, struct amdgpu_dm_connector *aconnector) { @@ -333,6 +352,7 @@ static void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, } process_output(hdcp_w); } +EXPORT_IF_KUNIT(hdcp_remove_display); void hdcp_reset_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index) { @@ -356,6 +376,7 @@ void hdcp_reset_display(struct hdcp_workqueue *hdcp_work, unsigned int link_inde process_output(hdcp_w); } +EXPORT_IF_KUNIT(hdcp_reset_display); void hdcp_handle_cpirq(struct hdcp_workqueue *hdcp_work, unsigned int link_index) { @@ -363,8 +384,10 @@ void hdcp_handle_cpirq(struct hdcp_workqueue *hdcp_work, unsigned int link_index schedule_work(&hdcp_w->cpirq_work); } +EXPORT_IF_KUNIT(hdcp_handle_cpirq); -static void event_callback(struct work_struct *work) +STATIC_IFN_KUNIT +void event_callback(struct work_struct *work) { struct hdcp_workqueue *hdcp_work; @@ -380,8 +403,10 @@ static void event_callback(struct work_struct *work) process_output(hdcp_work); } +EXPORT_IF_KUNIT(event_callback); -static void event_property_update(struct work_struct *work) +STATIC_IFN_KUNIT +void event_property_update(struct work_struct *work) { struct hdcp_workqueue *hdcp_work = container_of(work, struct hdcp_workqueue, property_update_work); @@ -440,8 +465,10 @@ static void event_property_update(struct work_struct *work) drm_modeset_unlock(&dev->mode_config.connection_mutex); } } +EXPORT_IF_KUNIT(event_property_update); -static void event_property_validate(struct work_struct *work) +STATIC_IFN_KUNIT +void event_property_validate(struct work_struct *work) { struct hdcp_workqueue *hdcp_work = container_of(to_delayed_work(work), struct hdcp_workqueue, property_validate_dwork); @@ -490,8 +517,10 @@ static void event_property_validate(struct work_struct *work) } } } +EXPORT_IF_KUNIT(event_property_validate); -static void event_watchdog_timer(struct work_struct *work) +STATIC_IFN_KUNIT +void event_watchdog_timer(struct work_struct *work) { struct hdcp_workqueue *hdcp_work; @@ -509,8 +538,10 @@ static void event_watchdog_timer(struct work_struct *work) process_output(hdcp_work); } +EXPORT_IF_KUNIT(event_watchdog_timer); -static void event_cpirq(struct work_struct *work) +STATIC_IFN_KUNIT +void event_cpirq(struct work_struct *work) { struct hdcp_workqueue *hdcp_work; @@ -522,6 +553,7 @@ static void event_cpirq(struct work_struct *work) process_output(hdcp_work); } +EXPORT_IF_KUNIT(event_cpirq); void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *hdcp_work) { @@ -538,8 +570,10 @@ void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *hdcp_work) kfree(hdcp_work->srm_temp); kfree(hdcp_work); } +EXPORT_IF_KUNIT(hdcp_destroy); -static bool enable_assr(void *handle, struct dc_link *link) +STATIC_IFN_KUNIT +bool enable_assr(void *handle, struct dc_link *link) { struct hdcp_workqueue *hdcp_work = handle; struct mod_hdcp hdcp = hdcp_work->hdcp; @@ -572,19 +606,33 @@ static bool enable_assr(void *handle, struct dc_link *link) return true; } +EXPORT_IF_KUNIT(enable_assr); -static void update_config(void *handle, struct cp_psp_stream_config *config) +STATIC_IFN_KUNIT +void update_config(void *handle, struct cp_psp_stream_config *config) { struct hdcp_workqueue *hdcp_work = handle; - struct amdgpu_dm_connector *aconnector = config->dm_stream_ctx; - int link_index = aconnector->dc_link->link_index; - unsigned int conn_index = aconnector->base.index; - struct mod_hdcp_display *display = &hdcp_work[link_index].display; - struct mod_hdcp_link *link = &hdcp_work[link_index].link; - struct hdcp_workqueue *hdcp_w = &hdcp_work[link_index]; + struct amdgpu_dm_connector *aconnector; + const struct dc *dc; + int link_index; + unsigned int conn_index; + struct mod_hdcp_display *display; + struct mod_hdcp_link *link; + struct hdcp_workqueue *hdcp_w; struct dc_sink *sink = NULL; bool link_is_hdcp14 = false; - const struct dc *dc = aconnector->dc_link->dc; + + aconnector = config->dm_stream_ctx; + if (!aconnector || !aconnector->dc_link) + return; + + link_index = aconnector->dc_link->link_index; + display = &hdcp_work[link_index].display; + link = &hdcp_work[link_index].link; + hdcp_w = &hdcp_work[link_index]; + + conn_index = aconnector->base.index; + dc = aconnector->dc_link->dc; if (config->dpms_off) { hdcp_remove_display(hdcp_work, link_index, aconnector); @@ -622,7 +670,7 @@ static void update_config(void *handle, struct cp_psp_stream_config *config) link->dp.mst_enabled = config->mst_enabled; link->dp.dp2_enabled = config->dp2_enabled; link->dp.usb4_enabled = config->usb4_enabled; - if (aconnector->dc_sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) + if (sink && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) link->hdmi.frl_enabled = config->frl_enabled; display->adjust.disable = MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION; link->adjust.auth_delay = 2; @@ -647,6 +695,7 @@ static void update_config(void *handle, struct cp_psp_stream_config *config) hdcp_w->aconnector[conn_index] = aconnector; process_output(hdcp_w); } +EXPORT_IF_KUNIT(update_config); /** * DOC: Add sysfs interface for set/get srm @@ -696,7 +745,7 @@ static void update_config(void *handle, struct cp_psp_stream_config *config) * -if we try to "1. SET" a newer version and PSP rejects it. That means the format is * incorrect/corrupted and we should correct our SRM by getting it from PSP */ -static ssize_t srm_data_write(struct file *filp, struct kobject *kobj, +STATIC_IFN_KUNIT ssize_t srm_data_write(struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count) { @@ -719,8 +768,9 @@ static ssize_t srm_data_write(struct file *filp, struct kobject *kobj, return count; } +EXPORT_IF_KUNIT(srm_data_write); -static ssize_t srm_data_read(struct file *filp, struct kobject *kobj, +STATIC_IFN_KUNIT ssize_t srm_data_read(struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count) { @@ -756,6 +806,7 @@ ret: link_lock(work, false); return ret; } +EXPORT_IF_KUNIT(srm_data_read); /* From the hdcp spec (5.Renewability) SRM needs to be stored in a non-volatile memory. * @@ -872,4 +923,5 @@ fail_alloc_context: return NULL; } +EXPORT_IF_KUNIT(hdcp_create_workqueue); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 3ba5823aed9f..665fa4839a25 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -44,6 +44,12 @@ struct mod_hdcp_link; struct mod_hdcp_display; struct cp_psp; struct amdgpu_device; +struct psp_context; +struct file; +struct kobject; +struct bin_attribute; +struct mod_hdcp_atomic_op_i2c; +struct mod_hdcp_atomic_op_aux; struct hdcp_workqueue { struct work_struct cpirq_work; @@ -108,6 +114,42 @@ void hdcp_get_link_display_adjustments( bool hdcp_lc_enable_sw_fallback, struct mod_hdcp_link_adjustment *link_adjust, struct mod_hdcp_display_adjustment *display_adjust); +void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, + struct hdcp_workqueue *hdcp_w, + unsigned int conn_index, + bool enable_encryption); +void event_property_update(struct work_struct *work); +void event_property_validate(struct work_struct *work); +void event_callback(struct work_struct *work); +void event_watchdog_timer(struct work_struct *work); +void event_cpirq(struct work_struct *work); +void link_lock(struct hdcp_workqueue *work, bool lock); +void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, + struct amdgpu_dm_connector *aconnector); +uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size); +int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version); +bool enable_assr(void *handle, struct dc_link *link); +void update_config(void *handle, struct cp_psp_stream_config *config); +ssize_t srm_data_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buffer, + loff_t pos, size_t count); +ssize_t srm_data_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buffer, + loff_t pos, size_t count); +bool lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size); +bool lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size); +bool lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size); +bool lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size); +bool lp_atomic_write_poll_read_i2c(void *handle, + const struct mod_hdcp_atomic_op_i2c *write, + const struct mod_hdcp_atomic_op_i2c *poll, + struct mod_hdcp_atomic_op_i2c *read, + uint32_t poll_timeout_us, uint8_t poll_mask_msb); +bool lp_atomic_write_poll_read_aux(void *handle, + const struct mod_hdcp_atomic_op_aux *write, + const struct mod_hdcp_atomic_op_aux *poll, + struct mod_hdcp_atomic_op_aux *read, + uint32_t poll_timeout_us, uint8_t poll_mask_msb); #endif #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 71e2627f9a9d..c720c319e795 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -48,7 +48,6 @@ #include "dm_helpers.h" #include "ddc_service_types.h" #include "clk_mgr.h" -#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_helpers.h" #define MCCS_DEST_ADDR (0x6E >> 1) @@ -98,7 +97,7 @@ STATIC_IFN_KUNIT u32 edid_extract_panel_id(struct edid *edid) } EXPORT_IF_KUNIT(edid_extract_panel_id); -static void apply_edid_quirks(struct dc_link *link, struct edid *edid, +STATIC_IFN_KUNIT void apply_edid_quirks(struct dc_link *link, struct edid *edid, struct dc_edid_caps *edid_caps) { struct amdgpu_dm_connector *aconnector = link->priv; @@ -137,10 +136,22 @@ static void apply_edid_quirks(struct dc_link *link, struct edid *edid, drm_dbg_driver(dev, "Skip PHY SSC reduction on panel id %X\n", panel_id); link->wa_flags.skip_phy_ssc_reduction = true; break; + /* + * Workaround for Apple Studio Display which exposes a 2x1 tiled panel + * over two SST DP links. Hide the secondary tile from userspace so + * compositors drive a single 5K stream on the primary link only. + */ + case drm_edid_encode_panel_id('A', 'P', 'P', 0xAE3A): + case drm_edid_encode_panel_id('A', 'P', 'P', 0xAE42): + case drm_edid_encode_panel_id('A', 'P', 'P', 0xAE46): + drm_dbg_driver(dev, "Hiding secondary tile on panel id %X\n", panel_id); + edid_caps->panel_patch.disable_second_tile = true; + break; default: return; } } +EXPORT_IF_KUNIT(apply_edid_quirks); /** * dm_helpers_parse_edid_caps() - Parse edid caps @@ -237,8 +248,9 @@ enum dc_edid_status dm_helpers_parse_edid_caps( return result; } +EXPORT_IF_KUNIT(dm_helpers_parse_edid_caps); -static void +STATIC_IFN_KUNIT void fill_dc_mst_payload_table_from_drm(struct dc_link *link, bool enable, struct drm_dp_mst_atomic_payload *target_payload, @@ -288,13 +300,15 @@ fill_dc_mst_payload_table_from_drm(struct dc_link *link, /* Overwrite the old table */ *table = new_table; } +EXPORT_IF_KUNIT(fill_dc_mst_payload_table_from_drm); void dm_helpers_dp_update_branch_info( struct dc_context *ctx, const struct dc_link *link) {} +EXPORT_IF_KUNIT(dm_helpers_dp_update_branch_info); -static void dm_helpers_construct_old_payload( +STATIC_IFN_KUNIT void dm_helpers_construct_old_payload( struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_topology_state *mst_state, struct drm_dp_mst_atomic_payload *new_payload, @@ -325,6 +339,7 @@ static void dm_helpers_construct_old_payload( old_payload->time_slots = allocated_time_slots; old_payload->pbn = allocated_time_slots * pbn_per_slot; } +EXPORT_IF_KUNIT(dm_helpers_construct_old_payload); /* * Writes payload allocation table in immediate downstream device. @@ -377,6 +392,7 @@ bool dm_helpers_dp_mst_write_payload_allocation_table( return true; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_write_payload_allocation_table); /* * poll pending down reply @@ -385,6 +401,7 @@ void dm_helpers_dp_mst_poll_pending_down_reply( struct dc_context *ctx, const struct dc_link *link) {} +EXPORT_IF_KUNIT(dm_helpers_dp_mst_poll_pending_down_reply); /* * Clear payload allocation table before enable MST DP link. @@ -393,6 +410,7 @@ void dm_helpers_dp_mst_clear_payload_allocation_table( struct dc_context *ctx, const struct dc_link *link) {} +EXPORT_IF_KUNIT(dm_helpers_dp_mst_clear_payload_allocation_table); /* * Polls for ACT (allocation change trigger) handled and sends @@ -423,6 +441,7 @@ enum act_return_status dm_helpers_dp_mst_poll_for_allocation_change_trigger( return ACT_SUCCESS; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_poll_for_allocation_change_trigger); void dm_helpers_dp_mst_send_payload_allocation( struct dc_context *ctx, @@ -457,6 +476,7 @@ void dm_helpers_dp_mst_send_payload_allocation( clr_flag, false); } } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_send_payload_allocation); void dm_helpers_dp_mst_update_mst_mgr_for_deallocation( struct dc_context *ctx, @@ -485,6 +505,7 @@ void dm_helpers_dp_mst_update_mst_mgr_for_deallocation( amdgpu_dm_set_mst_status(&aconnector->mst_status, set_flag, true); amdgpu_dm_set_mst_status(&aconnector->mst_status, clr_flag, false); } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_update_mst_mgr_for_deallocation); void dm_dtn_log_begin(struct dc_context *ctx, struct dc_log_buffer_ctx *log_ctx) @@ -704,6 +725,7 @@ bool dm_helpers_submit_i2c( return result; } +EXPORT_IF_KUNIT(dm_helpers_submit_i2c); bool dm_helpers_execute_fused_io( struct dc_context *ctx, @@ -717,8 +739,9 @@ bool dm_helpers_execute_fused_io( return amdgpu_dm_execute_fused_io(dev, link, commands, count, timeout_us); } +EXPORT_IF_KUNIT(dm_helpers_execute_fused_io); -static bool execute_synaptics_rc_command(struct drm_dp_aux *aux, +STATIC_IFN_KUNIT bool execute_synaptics_rc_command(struct drm_dp_aux *aux, bool is_write_cmd, unsigned char cmd, unsigned int length, @@ -790,8 +813,9 @@ err: DRM_ERROR("%s: write cmd ..., err = %d\n", __func__, ret); return false; } +EXPORT_IF_KUNIT(execute_synaptics_rc_command); -static void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux) +STATIC_IFN_KUNIT void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux) { unsigned char data[16] = {0}; @@ -855,11 +879,12 @@ static void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux) drm_dbg_dp(aux->drm_dev, "Done\n"); } +EXPORT_IF_KUNIT(apply_synaptics_fifo_reset_wa); /* MST Dock */ static const uint8_t SYNAPTICS_DEVICE_ID[] = "SYNA"; -static uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst( +STATIC_IFN_KUNIT uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst( struct drm_dp_aux *aux, const struct dc_stream_state *stream, bool enable) @@ -895,6 +920,7 @@ static uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst( return ret; } +EXPORT_IF_KUNIT(write_dsc_enable_synaptics_non_virtual_dpcd_mst); bool dm_helpers_dp_write_dsc_enable( struct dc_context *ctx, @@ -978,6 +1004,21 @@ bool dm_helpers_dp_write_dsc_enable( return ret; } +EXPORT_IF_KUNIT(dm_helpers_dp_write_dsc_enable); + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +uint dm_helpers_get_dc_debug_mask(void) +{ + return amdgpu_dc_debug_mask; +} +EXPORT_IF_KUNIT(dm_helpers_get_dc_debug_mask); + +void dm_helpers_set_dc_debug_mask(uint debug_mask) +{ + amdgpu_dc_debug_mask = debug_mask; +} +EXPORT_IF_KUNIT(dm_helpers_set_dc_debug_mask); +#endif bool dm_helpers_dp_write_hblank_reduction(struct dc_context *ctx, const struct dc_stream_state *stream) { @@ -1001,8 +1042,9 @@ bool dm_helpers_is_dp_sink_present(struct dc_link *link) mutex_unlock(&aconnector->dm_dp_aux.aux.hw_mutex); return dp_sink_present; } +EXPORT_IF_KUNIT(dm_helpers_is_dp_sink_present); -static int +STATIC_IFN_KUNIT int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len) { struct drm_connector *connector = data; @@ -1040,8 +1082,9 @@ cleanup: return r; } +EXPORT_IF_KUNIT(dm_helpers_probe_acpi_edid); -static const struct drm_edid * +STATIC_IFN_KUNIT const struct drm_edid * dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; @@ -1062,8 +1105,9 @@ dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector) return drm_edid_read_custom(connector, dm_helpers_probe_acpi_edid, connector); } +EXPORT_IF_KUNIT(dm_helpers_read_acpi_edid); -static const struct drm_edid * +STATIC_IFN_KUNIT const struct drm_edid * dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_connector *aconnector) { struct dc_bios *bios = link->ctx->dc_bios; @@ -1101,6 +1145,7 @@ dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_conn return edid; } +EXPORT_IF_KUNIT(dm_helpers_read_vbios_hardcoded_edid); STATIC_IFN_KUNIT uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) { @@ -1125,7 +1170,7 @@ STATIC_IFN_KUNIT uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_pe } EXPORT_IF_KUNIT(get_max_frl_rate); -static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) +STATIC_IFN_KUNIT uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) { uint8_t dsc_max_slices; @@ -1148,6 +1193,7 @@ static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) return dsc_max_slices; } +EXPORT_IF_KUNIT(get_dsc_max_slices); void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hdmi, struct dc_edid_caps *edid_caps) { @@ -1220,11 +1266,25 @@ enum dc_edid_status dm_helpers_read_local_edid( continue; edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() - if (!edid || - edid->extensions >= sizeof(sink->dc_edid.raw_edid) / EDID_LENGTH) + /* + * Use the length of the EDID property blob populated by + * drm_edid_connector_update() above. It reflects the true number + * of EDID blocks, including any HDMI Forum EDID Extension Override + * Data Block (HF-EEODB) count, which the raw byte 0x7e extension + * count can hide (e.g. HDMI 8K sinks). + */ + if (!edid || !connector->edid_blob_ptr || + connector->edid_blob_ptr->length > sizeof(sink->dc_edid.raw_edid)) return EDID_BAD_INPUT; - sink->dc_edid.length = EDID_LENGTH * (edid->extensions + 1); + /* + * FIXME: amdgpu_dm today does not consider the HF-EEODB, which + * may contain additional mode info for sinks. This is a + * workaround until dc_edid is refactored out from DC into + * amdgpu_dm's ownership, allowing amdgpu_dm to use drm_edid + * directly + */ + sink->dc_edid.length = connector->edid_blob_ptr->length; memmove(sink->dc_edid.raw_edid, (uint8_t *)edid, sink->dc_edid.length); /* We don't need the original edid anymore */ @@ -1286,6 +1346,7 @@ int dm_helper_dmub_aux_transfer_sync( return amdgpu_dm_process_dmub_aux_transfer_sync(ctx, link->link_index, payload, operation_result); } +EXPORT_IF_KUNIT(dm_helper_dmub_aux_transfer_sync); int dm_helpers_dmub_set_config_sync(struct dc_context *ctx, const struct dc_link *link, @@ -1300,18 +1361,21 @@ void dm_set_dcn_clocks(struct dc_context *ctx, struct dc_clocks *clks) { /* TODO: something */ } +EXPORT_IF_KUNIT(dm_set_dcn_clocks); void dm_helpers_dmu_timeout(struct dc_context *ctx) { // TODO: //amdgpu_device_gpu_recover(dc_context->driver-context, NULL); } +EXPORT_IF_KUNIT(dm_helpers_dmu_timeout); void dm_helpers_smu_timeout(struct dc_context *ctx, unsigned int msg_id, unsigned int param, unsigned int timeout_us) { // TODO: //amdgpu_device_gpu_recover(dc_context->driver-context, NULL); } +EXPORT_IF_KUNIT(dm_helpers_smu_timeout); void dm_helpers_init_panel_settings( struct dc_context *ctx, @@ -1330,6 +1394,7 @@ void dm_helpers_init_panel_settings( panel_config->dsc.disable_dsc_edp = false; panel_config->dsc.force_dsc_edp_policy = 0; } +EXPORT_IF_KUNIT(dm_helpers_init_panel_settings); void dm_helpers_override_panel_settings( struct dc_context *ctx, @@ -1347,6 +1412,7 @@ void dm_helpers_override_panel_settings( link->panel_config.psr.disallow_replay = true; } } +EXPORT_IF_KUNIT(dm_helpers_override_panel_settings); void *dm_helpers_allocate_gpu_mem( struct dc_context *ctx, @@ -1382,6 +1448,7 @@ bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enabl enable ? "en" : "dis", ret); return ret; } +EXPORT_IF_KUNIT(dm_helpers_dmub_outbox_interrupt_control); void dm_helpers_mst_enable_stream_features(const struct dc_stream_state *stream) { @@ -1407,6 +1474,7 @@ void dm_helpers_mst_enable_stream_features(const struct dc_stream_state *stream) &new_downspread.raw, sizeof(new_downspread)); } +EXPORT_IF_KUNIT(dm_helpers_mst_enable_stream_features); bool dm_helpers_dp_handle_test_pattern_request( struct dc_context *ctx, @@ -1545,11 +1613,13 @@ bool dm_helpers_dp_handle_test_pattern_request( return false; } +EXPORT_IF_KUNIT(dm_helpers_dp_handle_test_pattern_request); void dm_set_phyd32clk(struct dc_context *ctx, int freq_khz) { // TODO } +EXPORT_IF_KUNIT(dm_set_phyd32clk); void dm_helpers_enable_periodic_detection(struct dc_context *ctx, bool enable) { @@ -1561,6 +1631,7 @@ void dm_helpers_enable_periodic_detection(struct dc_context *ctx, bool enable) schedule_work(&adev->dm.idle_workqueue->work); } } +EXPORT_IF_KUNIT(dm_helpers_enable_periodic_detection); void dm_helpers_dp_mst_update_branch_bandwidth( struct dc_context *ctx, @@ -1568,6 +1639,7 @@ void dm_helpers_dp_mst_update_branch_bandwidth( { // TODO } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_update_branch_bandwidth); STATIC_IFN_KUNIT const uint32_t dm_freesync_pcon_whitelist[] = { DP_BRANCH_DEVICE_ID_0060AD, @@ -1754,6 +1826,7 @@ void dm_helpers_read_mccs_caps(struct dc_context *ctx, struct dc_link *link, } } } +EXPORT_IF_KUNIT(dm_helpers_read_mccs_caps); static int mccs_operation_vcp_set(unsigned int vcp_code, struct dc_link *link, uint16_t value) { @@ -1828,4 +1901,5 @@ void dm_helpers_mccs_vcp_set(struct dc_context *ctx, struct dc_link *link, drm_dbg_driver(dev, "%s: Failed to set VCP code %d", __func__, sink->edid_caps.freesync_vcp_code); } +EXPORT_IF_KUNIT(dm_helpers_mccs_vcp_set); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h index 2ac9762895ec..0eab820757c1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -9,12 +9,53 @@ #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) #include +struct amdgpu_dm_connector; +struct dc_link; +struct dc_context; +struct dc_stream_state; +struct dc_edid_caps; +struct dc_dp_mst_stream_allocation_table; +struct drm_dp_aux; +struct drm_dp_mst_atomic_payload; +struct drm_dp_mst_topology_mgr; +struct drm_dp_mst_topology_state; + /* Exported for KUnit testing */ u32 edid_extract_panel_id(struct edid *edid); +void apply_edid_quirks(struct dc_link *link, struct edid *edid, + struct dc_edid_caps *edid_caps); uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane); +uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice); bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id); extern const uint32_t dm_freesync_pcon_whitelist[]; uint32_t dm_freesync_pcon_whitelist_count(void); +void fill_dc_mst_payload_table_from_drm(struct dc_link *link, + bool enable, + struct drm_dp_mst_atomic_payload *target_payload, + struct dc_dp_mst_stream_allocation_table *table); +void dm_helpers_construct_old_payload(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_topology_state *mst_state, + struct drm_dp_mst_atomic_payload *new_payload, + struct drm_dp_mst_atomic_payload *old_payload); +bool dm_helpers_dp_write_dsc_enable(struct dc_context *ctx, + const struct dc_stream_state *stream, + bool enable); +uint dm_helpers_get_dc_debug_mask(void); +void dm_helpers_set_dc_debug_mask(uint debug_mask); +int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len); +const struct drm_edid *dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector); +const struct drm_edid *dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, + struct amdgpu_dm_connector *aconnector); +bool execute_synaptics_rc_command(struct drm_dp_aux *aux, + bool is_write_cmd, + unsigned char cmd, + unsigned int length, + unsigned int offset, + unsigned char *data); +void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux); +uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst(struct drm_dp_aux *aux, + const struct dc_stream_state *stream, + bool enable); #endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */ #endif /* __AMDGPU_DM_HELPERS_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 551901c7598a..9be63996b062 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -33,7 +33,7 @@ #include "amdgpu_display.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_hdcp.h" #include "amdgpu_dm_mst_types.h" @@ -195,6 +195,9 @@ static struct list_head *remove_irq_handler(struct amdgpu_device *adev, return NULL; } + if (int_params->int_context == INTERRUPT_LOW_IRQ_CONTEXT) + cancel_work_sync(&handler->work); + kfree(handler); DRM_DEBUG_KMS( @@ -204,55 +207,6 @@ static struct list_head *remove_irq_handler(struct amdgpu_device *adev, return hnd_list; } -/** - * unregister_all_irq_handlers() - Cleans up handlers from the DM IRQ table - * @adev: The base driver device containing the DM device - * - * Go through low and high context IRQ tables and deallocate handlers. - */ -static void unregister_all_irq_handlers(struct amdgpu_device *adev) -{ - struct list_head *hnd_list_low; - struct list_head *hnd_list_high; - struct list_head *entry, *tmp; - struct amdgpu_dm_irq_handler_data *handler; - unsigned long irq_table_flags; - int i; - - DM_IRQ_TABLE_LOCK(adev, irq_table_flags); - - for (i = 0; i < DAL_IRQ_SOURCES_NUMBER; i++) { - hnd_list_low = &adev->dm.irq_handler_list_low_tab[i]; - hnd_list_high = &adev->dm.irq_handler_list_high_tab[i]; - - list_for_each_safe(entry, tmp, hnd_list_low) { - - handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, - list); - - if (handler == NULL || handler->handler == NULL) - continue; - - list_del(&handler->list); - kfree(handler); - } - - list_for_each_safe(entry, tmp, hnd_list_high) { - - handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, - list); - - if (handler == NULL || handler->handler == NULL) - continue; - - list_del(&handler->list); - kfree(handler); - } - } - - DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); -} - static bool validate_irq_registration_params(struct dc_interrupt_params *int_params, void (*ih)(void *)) @@ -443,6 +397,21 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) spin_lock_init(&adev->dm.irq_handler_list_table_lock); + adev->dm.irq_wq = alloc_workqueue("amdgpu_dm_irq", + WQ_UNBOUND | WQ_HIGHPRI, 0); + + if (!adev->dm.irq_wq) + return -ENOMEM; + + adev->dm.vmin_vmax_wq = alloc_workqueue("amdgpu_dm_vmin_vmax", + WQ_UNBOUND, 0); + + if (!adev->dm.vmin_vmax_wq) { + destroy_workqueue(adev->dm.irq_wq); + adev->dm.irq_wq = NULL; + return -ENOMEM; + } + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { /* low context handler list init */ lh = &adev->dm.irq_handler_list_low_tab[src]; @@ -459,38 +428,81 @@ EXPORT_IF_KUNIT(amdgpu_dm_irq_init); * amdgpu_dm_irq_fini() - Tear down DM IRQ management * @adev: The base driver device containing the DM device * - * Flush all work within the low context IRQ table. + * Removes all handlers from the IRQ tables under the spinlock, cancels + * pending work items, and deallocates all handler data. */ void amdgpu_dm_irq_fini(struct amdgpu_device *adev) { int src; - struct list_head *lh; + LIST_HEAD(low_handlers); + LIST_HEAD(high_handlers); struct list_head *entry, *tmp; struct amdgpu_dm_irq_handler_data *handler; unsigned long irq_table_flags; DRM_DEBUG_KMS("DM_IRQ: releasing resources.\n"); + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { DM_IRQ_TABLE_LOCK(adev, irq_table_flags); - /* The handler was removed from the table, - * it means it is safe to flush all the 'work' - * (because no code can schedule a new one). + + /* + * Move all handlers from the low and high context tables to + * temporary lists under the lock. This prevents the ISR from + * finding them while we process them outside the lock. */ - lh = &adev->dm.irq_handler_list_low_tab[src]; + list_splice_init(&adev->dm.irq_handler_list_low_tab[src], + &low_handlers); + list_splice_init(&adev->dm.irq_handler_list_high_tab[src], + &high_handlers); + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); - if (!list_empty(lh)) { - list_for_each_safe(entry, tmp, lh) { - handler = list_entry( - entry, - struct amdgpu_dm_irq_handler_data, - list); - flush_work(&handler->work); - } + /* + * Cancel all pending work for the low-context handlers + * outside the lock. cancel_work_sync() may sleep and waits + * until any running work completes, preventing UAF. + */ + list_for_each_safe(entry, tmp, &low_handlers) { + handler = list_entry(entry, + struct amdgpu_dm_irq_handler_data, + list); + cancel_work_sync(&handler->work); } + + /* + * High-context handlers are executed synchronously within ISR + * context (see amdgpu_dm_irq_immediate_work()) and have no + * work_struct, so there is no pending work to cancel here. + * They will be freed along with low_handlers after the loop. + */ + } + + /* Deallocate all handlers. */ + list_for_each_safe(entry, tmp, &low_handlers) { + handler = list_entry(entry, + struct amdgpu_dm_irq_handler_data, + list); + list_del(&handler->list); + kfree(handler); + } + + list_for_each_safe(entry, tmp, &high_handlers) { + handler = list_entry(entry, + struct amdgpu_dm_irq_handler_data, + list); + list_del(&handler->list); + kfree(handler); + } + + if (adev->dm.vmin_vmax_wq) { + destroy_workqueue(adev->dm.vmin_vmax_wq); + adev->dm.vmin_vmax_wq = NULL; + } + + if (adev->dm.irq_wq) { + destroy_workqueue(adev->dm.irq_wq); + adev->dm.irq_wq = NULL; } - /* Deallocate handlers from the table. */ - unregister_all_irq_handlers(adev); } EXPORT_IF_KUNIT(amdgpu_dm_irq_fini); @@ -498,7 +510,6 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) { struct drm_device *dev = adev_to_drm(adev); int src; - struct list_head *hnd_list_h; struct list_head *hnd_list_l; unsigned long irq_table_flags; struct list_head *entry, *tmp; @@ -511,12 +522,15 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) /** * Disable HW interrupt for HPD and HPDRX only since FLIP and VBLANK * will be disabled from manage_dm_interrupts on disable CRTC. + * + * Disable the HW interrupt first, then flush any pending work. Since + * the HW interrupt is disabled under the lock, no new IRQ can be + * generated after the disable completes. Any work already queued by an + * in-flight ISR will be flushed below. */ for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6RX; src++) { hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; - hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; - if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, false); + dc_interrupt_set(adev->dm.dc, src, false); DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -537,6 +551,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) if (dev->mode_config.poll_enabled) drm_kms_helper_poll_disable(dev); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_suspend); void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) { @@ -558,6 +573,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_early); void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) { @@ -586,23 +602,27 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) if (dev->mode_config.poll_enabled) drm_kms_helper_poll_enable(dev); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_late); /* * amdgpu_dm_irq_schedule_work - schedule all work items registered for the * "irq_source". */ -static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, enum dc_irq_source irq_source) { struct list_head *handler_list = &adev->dm.irq_handler_list_low_tab[irq_source]; struct amdgpu_dm_irq_handler_data *handler_data; bool work_queued = false; + unsigned long irq_table_flags; + + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); if (list_empty(handler_list)) - return; + goto out_unlock; list_for_each_entry(handler_data, handler_list, list) { - if (queue_work(system_highpri_wq, &handler_data->work)) { + if (queue_work(adev->dm.irq_wq, &handler_data->work)) { work_queued = true; break; } @@ -617,7 +637,7 @@ static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, handler_data_add = kzalloc_obj(*handler_data, GFP_ATOMIC); if (!handler_data_add) { DRM_ERROR("DM_IRQ: failed to allocate irq handler!\n"); - return; + goto out_unlock; } /*copy new amdgpu_dm_irq_handler_data members from handler_data*/ @@ -630,7 +650,7 @@ static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, INIT_WORK(&handler_data_add->work, dm_irq_work_func); - if (queue_work(system_highpri_wq, &handler_data_add->work)) + if (queue_work(adev->dm.irq_wq, &handler_data_add->work)) DRM_DEBUG("Queued work for handling interrupt from " "display for IRQ source %d\n", irq_source); @@ -639,13 +659,17 @@ static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, "from display for IRQ source %d\n", irq_source); } + +out_unlock: + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_schedule_work); /* * amdgpu_dm_irq_immediate_work * Callback high irq work immediately, don't send to work queue */ -static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, enum dc_irq_source irq_source) { struct amdgpu_dm_irq_handler_data *handler_data; @@ -664,6 +688,7 @@ static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work); /** * amdgpu_dm_irq_handler - Generic DM IRQ handler @@ -674,7 +699,7 @@ static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, * Calls all registered high irq work immediately, and schedules work for low * irq. The DM IRQ table is used to find the corresponding handlers. */ -static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_irq_handler(struct amdgpu_device *adev, struct amdgpu_irq_src *source, struct amdgpu_iv_entry *entry) { @@ -694,6 +719,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_irq_handler); STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) { @@ -716,7 +742,7 @@ STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int } EXPORT_IF_KUNIT(amdgpu_dm_hpd_to_dal_irq_source); -static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int type, enum amdgpu_interrupt_state state) @@ -727,6 +753,7 @@ static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, dc_interrupt_set(adev->dm.dc, src, st); return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_set_hpd_irq_state); static inline int dm_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, @@ -762,7 +789,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, return 0; } -static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -775,8 +802,9 @@ static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, IRQ_TYPE_PFLIP, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_pflip_irq_state); -static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -789,8 +817,9 @@ static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, IRQ_TYPE_VBLANK, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_crtc_irq_state); -static int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -803,8 +832,9 @@ static int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, IRQ_TYPE_VLINE0, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_vline0_irq_state); -static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -815,8 +845,9 @@ static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, dc_interrupt_set(adev->dm.dc, irq_source, st); return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_outbox_irq_state); -static int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -829,8 +860,9 @@ static int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, IRQ_TYPE_VUPDATE, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_vupdate_irq_state); -static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int type, enum amdgpu_interrupt_state state) @@ -841,6 +873,7 @@ static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, dc_interrupt_set(adev->dm.dc, irq_source, st); return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_trace_irq_state); static const struct amdgpu_irq_src_funcs dm_crtc_irq_funcs = { .set = amdgpu_dm_set_crtc_irq_state, @@ -900,12 +933,15 @@ void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev) adev->hpd_irq.num_types = adev->mode_info.num_hpd; adev->hpd_irq.funcs = &dm_hpd_irq_funcs; } +EXPORT_IF_KUNIT(amdgpu_dm_set_irq_funcs); + void amdgpu_dm_outbox_init(struct amdgpu_device *adev) { dc_interrupt_set(adev->dm.dc, DC_IRQ_SOURCE_DMCUB_OUTBOX, true); } +EXPORT_IF_KUNIT(amdgpu_dm_outbox_init); /** * amdgpu_dm_hpd_init - hpd setup callback. @@ -988,6 +1024,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) if (use_polling) drm_kms_helper_poll_init(dev); } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_init); /** * amdgpu_dm_hpd_fini - hpd tear down callback. @@ -1041,6 +1078,7 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) if (dev->mode_config.poll_enabled) drm_kms_helper_poll_fini(dev); } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_fini); /* ========== HPD handling ========== */ static void force_connector_state( @@ -1058,7 +1096,7 @@ static void force_connector_state( mutex_unlock(&aconnector->hpd_lock); } -static void dm_handle_hpd_rx_offload_work(struct work_struct *work) +STATIC_IFN_KUNIT void dm_handle_hpd_rx_offload_work(struct work_struct *work) { struct hpd_rx_irq_offload_work *offload_work; struct amdgpu_dm_connector *aconnector; @@ -1151,6 +1189,7 @@ skip: kfree(offload_work); } +EXPORT_IF_KUNIT(dm_handle_hpd_rx_offload_work); struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev) { @@ -1187,6 +1226,7 @@ out_err: kfree(hpd_rx_offload_wq); return NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_rx_irq_create_workqueue); void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) { @@ -1197,6 +1237,7 @@ void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) flush_workqueue(dm->hpd_rx_offload_wq[i].wq); } } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_rx_irq_work_suspend); STATIC_IFN_KUNIT bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) { @@ -1290,8 +1331,9 @@ void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work) dc_allow_idle_optimizations(dc, true); } } +EXPORT_IF_KUNIT(amdgpu_dm_hdmi_hpd_debounce_work); -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, enum dc_detect_reason reason) { struct drm_connector *connector = &aconnector->base; @@ -1389,16 +1431,18 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, } } } +EXPORT_IF_KUNIT(handle_hpd_irq_helper); -static void handle_hpd_irq(void *param) +STATIC_IFN_KUNIT void handle_hpd_irq(void *param) { struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD); } +EXPORT_IF_KUNIT(handle_hpd_irq); -static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, +STATIC_IFN_KUNIT void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, union hpd_irq_data hpd_irq_data) { struct hpd_rx_irq_offload_work *offload_work = kzalloc_obj(*offload_work); @@ -1416,8 +1460,9 @@ static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_ queue_work(offload_wq->wq, &offload_work->work); drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work"); } +EXPORT_IF_KUNIT(schedule_hpd_rx_offload_work); -static void handle_hpd_rx_irq(void *param) +STATIC_IFN_KUNIT void handle_hpd_rx_irq(void *param) { struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; struct drm_connector *connector = &aconnector->base; @@ -1550,6 +1595,7 @@ out: mutex_unlock(&aconnector->hpd_lock); } +EXPORT_IF_KUNIT(handle_hpd_rx_irq); /** * dmub_hpd_callback - DMUB HPD interrupt processing callback. @@ -1559,7 +1605,7 @@ out: * Dmub Hpd interrupt processing callback. Gets displayindex through the * ink index and calls helper to do the processing. */ -static void dmub_hpd_callback(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void dmub_hpd_callback(struct amdgpu_device *adev, struct dmub_notification *notify) { struct amdgpu_dm_connector *aconnector; @@ -1625,6 +1671,7 @@ static void dmub_hpd_callback(struct amdgpu_device *adev, } } } +EXPORT_IF_KUNIT(dmub_hpd_callback); /** * dmub_hpd_sense_callback - DMUB HPD sense processing callback. @@ -1634,11 +1681,12 @@ static void dmub_hpd_callback(struct amdgpu_device *adev, * HPD sense changes can occur during low power states and need to be * notified from firmware to driver. */ -static void dmub_hpd_sense_callback(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void dmub_hpd_sense_callback(struct amdgpu_device *adev, struct dmub_notification *notify) { drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n"); } +EXPORT_IF_KUNIT(dmub_hpd_sense_callback); int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) { @@ -1716,6 +1764,7 @@ int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) } return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_register_hpd_handlers); /* ========== IRQ handlers ========== */ struct amdgpu_crtc * @@ -1747,7 +1796,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_get_crtc_by_otg_inst); * Handles the pageflip interrupt by notifying all interested parties * that the pageflip has been completed. */ -static void dm_pflip_high_irq(void *interrupt_params) +STATIC_IFN_KUNIT void dm_pflip_high_irq(void *interrupt_params) { struct amdgpu_crtc *amdgpu_crtc; struct common_irq_params *irq_params = interrupt_params; @@ -1843,8 +1892,9 @@ static void dm_pflip_high_irq(void *interrupt_params) "crtc:%d[%p], pflip_stat:AMDGPU_FLIP_NONE, vrr[%d]-fp %d\n", amdgpu_crtc->crtc_id, amdgpu_crtc, vrr_active, (int)!e); } +EXPORT_IF_KUNIT(dm_pflip_high_irq); -static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) +STATIC_IFN_KUNIT void dm_handle_vmin_vmax_update(struct work_struct *offload_work) { struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work); struct amdgpu_device *adev = work->adev; @@ -1859,6 +1909,7 @@ static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) kfree(work->adjust); kfree(work); } +EXPORT_IF_KUNIT(dm_handle_vmin_vmax_update); static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, struct dc_stream_state *stream, @@ -1887,110 +1938,48 @@ static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, offload_work->stream = stream; offload_work->adjust = adjust_copy; - queue_work(system_percpu_wq, &offload_work->work); -} - -static void dm_vupdate_high_irq(void *interrupt_params) -{ - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_crtc *acrtc; - struct drm_device *drm_dev; - struct drm_vblank_crtc *vblank; - ktime_t frame_duration_ns, previous_timestamp; - unsigned long flags; - int vrr_active; - - acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); - - if (acrtc) { - vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); - drm_dev = acrtc->base.dev; - vblank = drm_crtc_vblank_crtc(&acrtc->base); - previous_timestamp = atomic64_read(&irq_params->previous_timestamp); - frame_duration_ns = vblank->time - previous_timestamp; - - if (frame_duration_ns > 0) { - trace_amdgpu_refresh_rate_track(acrtc->base.index, - frame_duration_ns, - ktime_divns(NSEC_PER_SEC, frame_duration_ns)); - atomic64_set(&irq_params->previous_timestamp, vblank->time); - } - - drm_dbg_vbl(drm_dev, - "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, - vrr_active); - - /* Core vblank handling is done here after end of front-porch in - * vrr mode, as vblank timestamping will give valid results - * while now done after front-porch. This will also deliver - * page-flip completion events that have been queued to us - * if a pageflip happened inside front-porch. - */ - if (vrr_active && acrtc->dm_irq_params.stream) { - bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; - bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; - bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state - == VRR_STATE_ACTIVE_VARIABLE; - - amdgpu_dm_crtc_handle_vblank(acrtc); - - /* BTR processing for pre-DCE12 ASICs */ - if (adev->family < AMDGPU_FAMILY_AI) { - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - mod_freesync_handle_v_update( - adev->dm.freesync_module, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params); - - if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { - schedule_dc_vmin_vmax(adev, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params.adjust); - } - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); - } - } - } + queue_work(adev->dm.vmin_vmax_wq, &offload_work->work); } /** - * dm_crtc_high_irq() - Handles CRTC interrupt - * @interrupt_params: used for determining the CRTC instance + * dm_crtc_high_irq_handler() - Common OTG vblank/flip event handling + * @adev: amdgpu device + * @acrtc: the CRTC to service + * Performs writeback completion, vblank event handling, CRC processing, VRR BTR + * updates and pageflip completion delivery. * - * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK - * event handler. + * On DCN this is driven by VUPDATE_NO_LOCK (the register latch point) from + * dm_vupdate_high_irq(); on DCE it is driven by VLINE0 at the start of vblank + * from dm_crtc_high_irq(). */ -static void dm_crtc_high_irq(void *interrupt_params) +static void dm_crtc_high_irq_handler(struct amdgpu_device *adev, + struct amdgpu_crtc *acrtc) { - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_crtc *acrtc; unsigned long flags; int vrr_active; - - acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); - if (!acrtc) - return; + bool is_dcn = amdgpu_ip_version(adev, DCE_HWIP, 0) != 0; if (acrtc->wb_conn && acrtc->wb_pending) { - struct dc_stream_state *stream = acrtc->dm_irq_params.stream; - unsigned int v_total, refresh_hz; - - v_total = stream->adjust.v_total_max ? - stream->adjust.v_total_max : stream->timing.v_total; - refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz * - 100LL, (v_total * stream->timing.h_total)); - mdelay(1000 / refresh_hz); - - /* - * Completion (signalling the out fence and releasing the vblank - * reference taken in dm_set_writeback()) is handled by the shared - * helper, which is also used by the teardown path. - */ - if (amdgpu_dm_crtc_complete_writeback(acrtc)) + if (acrtc->wb_frame_done) { + /* + * Second vblank: the DMA for the captured frame has + * had a full frame period to flush to memory. Signal + * the out fence now. + */ + amdgpu_dm_crtc_complete_writeback(acrtc); + } else { + /* + * First vblank after arming: the frame has been + * scanned out and the DMA is finishing. Disable + * writeback immediately to prevent the hardware from + * starting a new capture that would overwrite the + * buffer. Signal completion on the next vblank to + * ensure the DMA is fully flushed to memory. + */ dc_stream_fc_disable_writeback(adev->dm.dc, acrtc->dm_irq_params.stream, 0); + acrtc->wb_frame_done = true; + } } vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); @@ -2000,12 +1989,17 @@ static void dm_crtc_high_irq(void *interrupt_params) vrr_active, acrtc->dm_irq_params.active_planes); /** - * Core vblank handling at start of front-porch is only possible - * in non-vrr mode, as only there vblank timestamping will give - * valid results while done in front-porch. Otherwise defer it - * to dm_vupdate_high_irq after end of front-porch. + * Core vblank handling. + * + * On DCN this handler runs at VUPDATE_NO_LOCK, the register latch + * point, which is the correct place to timestamp both VRR and non-VRR + * vblanks. + * + * On DCE this handler runs at the start of front-porch, where only + * non-VRR timestamping is valid; VRR vblank is deferred to + * dm_vupdate_high_irq() after end of front-porch. */ - if (!vrr_active) + if (is_dcn || !vrr_active) amdgpu_dm_crtc_handle_vblank(acrtc); /** @@ -2038,18 +2032,33 @@ static void dm_crtc_high_irq(void *interrupt_params) } /* - * If there aren't any active_planes then DCH HUBP may be clock-gated. - * In that case, pageflip completion interrupts won't fire and pageflip - * completion events won't get delivered. Prevent this by sending - * pending pageflip events from here if a flip is still pending. + * Deliver pageflip completion events (DCN only). * - * If any planes are enabled, use dm_pflip_high_irq() instead, to - * avoid race conditions between flip programming and completion, - * which could cause too early flip completion events. + * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch + * point. Deliver any pending pageflip completion event from here, + * once HW has consumed the new address (the OTG no longer reports a + * pending flip). + * + * Also handle the case here where there aren't any active planes and + * DCN HUBP may be clock-gated, so the flip-pending status may be + * undefined. */ - if (adev->family >= AMDGPU_FAMILY_RV && - acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && - acrtc->dm_irq_params.active_planes == 0) { + if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc->event) { + + if (!dc_get_flip_pending_on_otg(adev->dm.dc, acrtc->otg_inst)) { + drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); + acrtc->event = NULL; + drm_crtc_vblank_put(&acrtc->base); + acrtc->pflip_status = AMDGPU_FLIP_NONE; + } + /* + * If the flip is still pending, leave it armed and + * retry on the next vupdate. + */ + } else if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc->dm_irq_params.active_planes == 0) { + if (acrtc->event) { drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); acrtc->event = NULL; @@ -2061,6 +2070,106 @@ static void dm_crtc_high_irq(void *interrupt_params) spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); } +STATIC_IFN_KUNIT void dm_vupdate_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + struct drm_device *drm_dev; + struct drm_vblank_crtc *vblank; + ktime_t frame_duration_ns, previous_timestamp; + unsigned long flags; + int vrr_active; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); + if (!acrtc) + return; + + vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); + drm_dev = acrtc->base.dev; + vblank = drm_crtc_vblank_crtc(&acrtc->base); + previous_timestamp = atomic64_read(&irq_params->previous_timestamp); + frame_duration_ns = vblank->time - previous_timestamp; + + if (frame_duration_ns > 0) { + trace_amdgpu_refresh_rate_track(acrtc->base.index, + frame_duration_ns, + ktime_divns(NSEC_PER_SEC, frame_duration_ns)); + atomic64_set(&irq_params->previous_timestamp, vblank->time); + } + + drm_dbg_vbl(drm_dev, + "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, + vrr_active); + + /* + * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver + * vblank and pageflip completion events; VSTARTUP and GRPH_PFLIP are + * not used. Run the full handler here. + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) { + dm_crtc_high_irq_handler(adev, acrtc); + return; + } + + /* DCE only below. */ + + /* Core vblank handling is done here after end of front-porch in + * vrr mode, as vblank timestamping will give valid results + * while now done after front-porch. This will also deliver + * page-flip completion events that have been queued to us + * if a pageflip happened inside front-porch. + */ + if (vrr_active && acrtc->dm_irq_params.stream) { + bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; + bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; + bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state + == VRR_STATE_ACTIVE_VARIABLE; + + amdgpu_dm_crtc_handle_vblank(acrtc); + + /* BTR processing for pre-DCE12 ASICs */ + if (adev->family < AMDGPU_FAMILY_AI) { + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + mod_freesync_handle_v_update( + adev->dm.freesync_module, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params); + + if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { + schedule_dc_vmin_vmax(adev, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params.adjust); + } + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); + } + } +} +EXPORT_IF_KUNIT(dm_vupdate_high_irq); + +/** + * dm_crtc_high_irq() - Handles CRTC interrupt + * @interrupt_params: used for determining the CRTC instance + * + * Handles the CRTC/VSYNC interrupt by notifying DRM's VBLANK event handler. + * + * Used on DCE (VLINE0, set to vblank start). On DCN the equivalent handling is + * driven by VUPDATE_NO_LOCK in dm_vupdate_high_irq(). + */ +STATIC_IFN_KUNIT void dm_crtc_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); + if (!acrtc) + return; + + dm_crtc_high_irq_handler(adev, acrtc); +} +EXPORT_IF_KUNIT(dm_crtc_high_irq); + #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) /** * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for @@ -2084,7 +2193,7 @@ static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params) } #endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */ -static void dm_handle_hpd_work(struct work_struct *work) +STATIC_IFN_KUNIT void dm_handle_hpd_work(struct work_struct *work) { struct dmub_hpd_work *dmub_hpd_wrk; @@ -2104,6 +2213,7 @@ static void dm_handle_hpd_work(struct work_struct *work) kfree(dmub_hpd_wrk); } +EXPORT_IF_KUNIT(dm_handle_hpd_work); STATIC_IFN_KUNIT const char *dmub_notification_type_str(enum dmub_notification_type e) { @@ -2138,7 +2248,7 @@ EXPORT_IF_KUNIT(dmub_notification_type_str); * Handles the Outbox Interrupt * event handler. */ -static void dm_dmub_outbox1_low_irq(void *interrupt_params) +STATIC_IFN_KUNIT void dm_dmub_outbox1_low_irq(void *interrupt_params) { struct dmub_notification notify = {0}; struct common_irq_params *irq_params = interrupt_params; @@ -2202,6 +2312,7 @@ static void dm_dmub_outbox1_low_irq(void *interrupt_params) } while (notify.pending_notification); } } +EXPORT_IF_KUNIT(dm_dmub_outbox1_low_irq); /* Register IRQ sources and initialize IRQ callbacks */ int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev) @@ -2340,6 +2451,7 @@ int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev) return r; } +EXPORT_IF_KUNIT(amdgpu_dm_dce110_register_irq_handlers); /* Register IRQ sources and initialize IRQ callbacks */ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) @@ -2375,38 +2487,6 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) * for acknowledging and handling. */ - /* Use VSTARTUP interrupt */ - for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP; - i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq); - - if (r) { - drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || - int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { - drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_crtc_high_irq, c_irq_params)) - return -ENOMEM; - } - /* Use otg vertical line interrupt */ #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) { @@ -2478,37 +2558,6 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) return -ENOMEM; } - /* Use GRPH_PFLIP interrupt */ - for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT; - i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || - int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { - drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_pflip_high_irq, c_irq_params)) - return -ENOMEM; - } - /* HPD */ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT, &adev->hpd_irq); @@ -2521,6 +2570,7 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) return r; } +EXPORT_IF_KUNIT(amdgpu_dm_dcn10_register_irq_handlers); /* Register Outbox IRQ sources and initialize IRQ callbacks */ int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev) @@ -2558,3 +2608,4 @@ int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_register_outbox_irq_handlers); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index bccb5d354a9f..4c200a9614a7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -123,9 +123,66 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev); int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct amdgpu_irq_src; +struct amdgpu_iv_entry; +enum amdgpu_interrupt_state; + enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type); bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2); const char *dmub_notification_type_str(enum dmub_notification_type e); +int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int type, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int type, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, + enum dc_irq_source irq_source); +void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, + enum dc_irq_source irq_source); +void dm_handle_hpd_rx_offload_work(struct work_struct *work); +void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, + enum dc_detect_reason reason); +void handle_hpd_irq(void *param); +void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, + struct hpd_rx_irq_offload_work_queue *offload_wq, + union hpd_irq_data hpd_irq_data); +void handle_hpd_rx_irq(void *param); +void dmub_hpd_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); +void dmub_hpd_sense_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); +void dm_pflip_high_irq(void *interrupt_params); +void dm_vupdate_high_irq(void *interrupt_params); +void dm_crtc_high_irq(void *interrupt_params); +void dm_handle_hpd_work(struct work_struct *work); +void dm_dmub_outbox1_low_irq(void *interrupt_params); +int amdgpu_dm_irq_handler(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + struct amdgpu_iv_entry *entry); +void dm_handle_vmin_vmax_update(struct work_struct *offload_work); #endif #endif /* __AMDGPU_DM_IRQ_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c index 32391b56097e..4e57572e12b6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c @@ -32,7 +32,7 @@ #include "amdgpu_dm_ism.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_trace.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" /** @@ -502,6 +502,7 @@ void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism, } while (next_event < DM_ISM_NUM_EVENTS); } +EXPORT_IF_KUNIT(amdgpu_dm_ism_commit_event); static void dm_ism_delayed_work_func(struct work_struct *work) @@ -568,6 +569,7 @@ void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm) disable_delayed_work_sync(&ism->sso_delayed_work); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_disable); /** * amdgpu_dm_ism_force_full_power - Force every CRTC's ISM FSM to FULL_POWER @@ -603,6 +605,7 @@ void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm) DM_ISM_EVENT_EXIT_IDLE_REQUESTED); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_force_full_power); /** * amdgpu_dm_ism_enable - enable the ISM @@ -626,6 +629,7 @@ void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm) enable_delayed_work(&ism->sso_delayed_work); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_enable); void amdgpu_dm_ism_init(struct amdgpu_dm_ism *ism, struct amdgpu_dm_ism_config *config) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h deleted file mode 100644 index 1f910a6a00c0..000000000000 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* - * Copyright 2026 Advanced Micro Devices, Inc. - */ - -#ifndef AMDGPU_DM_KUNIT_HELPERS_H -#define AMDGPU_DM_KUNIT_HELPERS_H - -#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) -#define STATIC_IFN_KUNIT -#define INLINE_IFN_KUNIT inline -#define EXPORT_IF_KUNIT(symbol) EXPORT_SYMBOL(symbol) - -#else -#define STATIC_IFN_KUNIT static -#define INLINE_IFN_KUNIT -#define EXPORT_IF_KUNIT(symbol) -#endif - -#endif /* AMDGPU_DM_KUNIT_HELPERS_H */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index 0546efea5de1..e001d868db80 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -40,7 +40,6 @@ #include "dc.h" #include "dm_helpers.h" -#include "amdgpu_dm_kunit_helpers.h" #include "ddc_service_types.h" #include "dpcd_defs.h" @@ -862,10 +861,11 @@ void dm_handle_mst_sideband_msg_ready_event( } EXPORT_IF_KUNIT(dm_handle_mst_sideband_msg_ready_event); -static void dm_handle_mst_down_rep_msg_ready(struct drm_dp_mst_topology_mgr *mgr) +STATIC_IFN_KUNIT void dm_handle_mst_down_rep_msg_ready(struct drm_dp_mst_topology_mgr *mgr) { dm_handle_mst_sideband_msg_ready_event(mgr, DOWN_REP_MSG_RDY_EVENT); } +EXPORT_IF_KUNIT(dm_handle_mst_down_rep_msg_ready); static const struct drm_dp_mst_topology_cbs dm_mst_cbs = { .add_connector = dm_dp_add_mst_connector, @@ -900,6 +900,7 @@ void amdgpu_dm_initialize_dp_connector(struct amdgpu_display_manager *dm, drm_connector_attach_dp_subconnector_property(&aconnector->base); } +EXPORT_IF_KUNIT(amdgpu_dm_initialize_dp_connector); uint32_t dm_mst_get_pbn_divider(struct dc_link *link) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h index fecf108a9216..2ae9d4b88888 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h @@ -111,6 +111,7 @@ void dm_dp_aux_fill_payload_flags(u8 request, struct aux_payload *payload); ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg); u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type); void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, u8 *dpcd_bytes_to_read); +void dm_handle_mst_down_rep_msg_ready(struct drm_dp_mst_topology_mgr *mgr); struct drm_encoder *dm_mst_atomic_best_encoder(struct drm_connector *connector, struct drm_atomic_commit *state); int dm_dp_mst_atomic_check(struct drm_connector *connector, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index 1b564cfe2120..0c6f0cac666e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -37,7 +37,7 @@ #include "amdgpu_display.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_plane.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "amdgpu_dm_colorop.h" #include "gc/gc_11_0_0_offset.h" #include "gc/gc_11_0_0_sh_mask.h" @@ -180,6 +180,22 @@ STATIC_IFN_KUNIT void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *si } EXPORT_IF_KUNIT(amdgpu_dm_plane_add_modifier); +STATIC_IFN_KUNIT void amdgpu_dm_plane_add_modifier_dedup(uint64_t **mods, uint64_t *size, + uint64_t *cap, uint64_t mod) +{ + uint64_t i; + + if (!*mods) + return; + + for (i = 0; i < *size; ++i) + if ((*mods)[i] == mod) + return; + + amdgpu_dm_plane_add_modifier(mods, size, cap, mod); +} +EXPORT_IF_KUNIT(amdgpu_dm_plane_add_modifier_dedup); + STATIC_IFN_KUNIT bool amdgpu_dm_plane_modifier_has_dcc(uint64_t modifier) { return IS_AMD_FMT_MOD(modifier) && AMD_FMT_MOD_GET(DCC, modifier); @@ -195,38 +211,40 @@ STATIC_IFN_KUNIT unsigned int amdgpu_dm_plane_modifier_gfx9_swizzle_mode(uint64_ } EXPORT_IF_KUNIT(amdgpu_dm_plane_modifier_gfx9_swizzle_mode); -STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_info *tiling_info, - uint64_t tiling_flags) +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier( + struct dc_tiling_info *tiling_info, + uint64_t modifier) { - /* Fill GFX8 params */ - if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) == DC_ARRAY_2D_TILED_THIN1) { - unsigned int bankw, bankh, mtaspect, tile_split, num_banks; - bankw = AMDGPU_TILING_GET(tiling_flags, BANK_WIDTH); - bankh = AMDGPU_TILING_GET(tiling_flags, BANK_HEIGHT); - mtaspect = AMDGPU_TILING_GET(tiling_flags, MACRO_TILE_ASPECT); - tile_split = AMDGPU_TILING_GET(tiling_flags, TILE_SPLIT); - num_banks = AMDGPU_TILING_GET(tiling_flags, NUM_BANKS); - + if (modifier == DRM_FORMAT_MOD_LINEAR) { + tiling_info->gfx8.array_mode = DC_ARRAY_LINEAR_GENERAL; tiling_info->gfxversion = DcGfxVersion8; - /* XXX fix me for VI */ - tiling_info->gfx8.num_banks = num_banks; - tiling_info->gfx8.array_mode = - DC_ARRAY_2D_TILED_THIN1; - tiling_info->gfx8.tile_split = tile_split; - tiling_info->gfx8.bank_width = bankw; - tiling_info->gfx8.bank_height = bankh; - tiling_info->gfx8.tile_aspect = mtaspect; - tiling_info->gfx8.tile_mode = - DC_ADDR_SURF_MICRO_TILING_DISPLAY; - } else if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) - == DC_ARRAY_1D_TILED_THIN1) { - tiling_info->gfx8.array_mode = DC_ARRAY_1D_TILED_THIN1; + + return 0; } - tiling_info->gfx8.pipe_config = - AMDGPU_TILING_GET(tiling_flags, PIPE_CONFIG); + if (!IS_AMD_FMT_MOD(modifier)) + return -EINVAL; + + if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) != AMD_FMT_MOD_TILE_VER_GFX6) + return -EINVAL; + + tiling_info->gfx8.array_mode = AMD_FMT_MOD_GET(TILE, modifier); + tiling_info->gfxversion = DcGfxVersion8; + tiling_info->gfx8.tile_mode = AMD_FMT_MOD_GET(MICROTILE, modifier); + + if (AMD_FMT_MOD_GET(TILE, modifier) < AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) + return 0; + + tiling_info->gfx8.pipe_config = AMD_FMT_MOD_GET(PIPE_CONFIG, modifier); + tiling_info->gfx8.tile_split = AMD_FMT_MOD_GET(TILE_SPLIT, modifier); + tiling_info->gfx8.bank_width = AMD_FMT_MOD_GET(BANK_WIDTH, modifier); + tiling_info->gfx8.bank_height = AMD_FMT_MOD_GET(BANK_HEIGHT, modifier); + tiling_info->gfx8.tile_aspect = AMD_FMT_MOD_GET(MACRO_TILE_ASPECT, modifier); + tiling_info->gfx8.num_banks = AMD_FMT_MOD_GET(NUM_BANKS, modifier); + + return 0; } -EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags); +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier); STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgpu_device *adev, struct dc_tiling_info *tiling_info) @@ -328,7 +346,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, } EXPORT_IF_KUNIT(amdgpu_dm_plane_validate_dcc); -STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, @@ -374,13 +392,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(s ret = amdgpu_dm_plane_validate_dcc(adev, format, rotation, tiling_info, dcc, address, plane_size); if (ret) - drm_dbg_kms(adev_to_drm(adev), "amdgpu_dm_plane_validate_dcc: returned error: %d\n", ret); + drm_dbg_kms(adev_to_drm(adev), "amdgpu_dm_plane_validate_dcc: returned error: %pe\n", ERR_PTR(ret)); return ret; } -EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers); +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers); -STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, @@ -415,11 +433,11 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers( /* TODO: This seems wrong because there is no DCC plane on GFX12. */ ret = amdgpu_dm_plane_validate_dcc(adev, format, rotation, tiling_info, dcc, address, plane_size); if (ret) - drm_dbg_kms(adev_to_drm(adev), "amdgpu_dm_plane_validate_dcc: returned error: %d\n", ret); + drm_dbg_kms(adev_to_drm(adev), "amdgpu_dm_plane_validate_dcc: returned: %pe\n", ERR_PTR(ret)); return ret; } -EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers); +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers); static void amdgpu_dm_plane_add_gfx10_1_modifiers(const struct amdgpu_device *adev, uint64_t **mods, @@ -468,6 +486,249 @@ static void amdgpu_dm_plane_add_gfx10_1_modifiers(const struct amdgpu_device *ad AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9)); } +/** + * amdgpu_dm_plane_get_gfx6_tile_idx() - Get tile mode index on GFX6-8 + * + * @adev: amdgpu_device pointer + * @bpp: bits per pixel in the image + * @arr: array mode (aka. tile mode) of the image layout + * + * Select which tiling mode from the table is suitable for the given bits per pixel + * using the given array mode, assuming the displayable micro tile mode is used. + * + * On GFX6-8, the GFX block can only use pre-programmed tiling modes from GB_TILE_MODEn + * registers which are programmed by the kernel according to the tiling mode table. + * Note that the tiling table is uAPI and userspace relies on specific modes being + * present at specific indices. + * See SiLib::HwlSetupTileInfo() and CiLib::HwlSetupTileInfo() in addrlib. + */ +static u32 amdgpu_dm_plane_get_gfx6_tile_idx(const struct amdgpu_device *adev, + const u32 bpp, + const enum array_mode_values arr) +{ + /* Assume that the microtile mode is DISPLAY. */ + + if (arr == DC_ARRAY_1D_TILED_THIN1) + return 9; + + ASSERT(arr == DC_ARRAY_2D_TILED_THIN1); + + if (adev->family > AMDGPU_FAMILY_SI) + return 10; + + switch (bpp) { + case 8: + return 10; + case 16: + return 11; + default: + return 12; + } +} + +/** + * amdgpu_dm_plane_calc_gfx7_tile_split() - Calculate tile split on GFX7-8 + * + * @adev: amdgpu_device pointer + * @bpp: bits per pixel in the image + * @gb_tile_mode: GB_TILE_MODEn register value for the current tiling mode + * + * Calculate the actual tile split value on GFX7-8, assuming 2D_TILED_THIN1 array mode + * with a non-depth micro tile mode. + * + * On GFX7-8, SAMPLE_SPLIT holds a factor from which the actual tile split bytes + * can be calculated. The TILE_SPLIT field is only used for the depth micro tile mode. + * See CiLib::HwlComputeMacroModeIndex() in addrlib. + */ +static u32 amdgpu_dm_plane_calc_gfx7_tile_split(const struct amdgpu_device *adev, + const u32 bpp, + const u32 gb_tile_mode) +{ + /* Assume 2D_TILED_THIN1 mode with non-DEPTH microtiles */ + const u32 sample_split = (gb_tile_mode >> 25) & 0x3; + const u32 thickness = 1; + const u32 tile_size_pixels = 8 * 8; + const u32 tile_bytes_1x = tile_size_pixels * bpp * thickness / 8; + const u32 sample_split_factor = 1 << sample_split; + + return clamp(tile_bytes_1x * sample_split_factor, + 256, + adev->gfx.config.mem_row_size_in_kb * 1024); +} + +/** + * amdgpu_dm_plane_get_gfx7_macro_tile_idx() - Get macro tile mode index on GFX7-8 + * + * @bpp: bits per pixel in the image + * @tile_split_bytes: actual tile split bytes, see amdgpu_dm_plane_calc_gfx7_tile_split() + * + * Select which macro tiling mode from the table is suitable for the given bits per pixel, + * assuming 2D_TILED_THIN1 array mode and DISPLAY micro tile mode (and no multisampling). + * Note that the tiling table is uAPI and userspace relies on specific modes being + * present at specific indices. + * See CiLib::HwlComputeMacroModeIndex() in addrlib. + */ +static u32 amdgpu_dm_plane_get_gfx7_macro_tile_idx(const u32 bpp, const u32 tile_split_bytes) +{ + const u32 thickness = 1; + const u32 tile_size_pixels = 8 * 8; + const u32 tile_bytes_1x = thickness * tile_size_pixels * bpp / 8; + const u32 tile_bytes = clamp(tile_bytes_1x, 64, tile_split_bytes); + const u32 macro_tile_idx = ilog2(tile_bytes / 64); + + WARN_ON(macro_tile_idx >= 16); + + return macro_tile_idx; +} + +/** + * amdgpu_dm_plane_calc_gfx6_mod() - Calculate a DRM format modifier for GFX6-8 + * + * @adev: amdgpu_device pointer + * @bpp: bits per pixel in the image + * @arr: array mode (aka. tile mode) of the image layout + * + * Select suitable micro and macro tile modes for the given bits per pixel, + * and calculate the corresponding DRM format modifier. + */ +static u64 amdgpu_dm_plane_calc_gfx6_mod(const struct amdgpu_device *adev, + const u32 bpp, + const enum array_mode_values arr) +{ + u32 array_mode, micro_tile_mode, tile_split_bytes; + u32 gb_macrotile_mode, macrotile_idx; + u32 gb_tile_mode, tile_idx; + + u64 modifier_base = + AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX6) | + AMD_FMT_MOD_SET(TILE, arr) | + AMD_FMT_MOD_SET(MICROTILE, AMD_FMT_MOD_MICROTILE_DISPLAY); + + if (arr < DC_ARRAY_2D_TILED_THIN1) + return modifier_base; + + tile_idx = amdgpu_dm_plane_get_gfx6_tile_idx(adev, bpp, arr); + gb_tile_mode = adev->gfx.config.tile_mode_array[tile_idx]; + array_mode = (gb_tile_mode >> 2) & 0xf; + + if (adev->family == AMDGPU_FAMILY_SI) { + micro_tile_mode = (gb_tile_mode >> 0) & 0x3; + tile_split_bytes = 64 << ((gb_tile_mode >> 11) & 0x7); + } else { + micro_tile_mode = (gb_tile_mode >> 22) & 0x7; + tile_split_bytes = amdgpu_dm_plane_calc_gfx7_tile_split(adev, bpp, gb_tile_mode); + } + + ASSERT(array_mode == arr); + ASSERT(micro_tile_mode == AMD_FMT_MOD_MICROTILE_DISPLAY); + + modifier_base |= + AMD_FMT_MOD_SET(PIPE_CONFIG, (gb_tile_mode >> 6) & 0x1f) | + AMD_FMT_MOD_SET(TILE_SPLIT, ilog2(tile_split_bytes / 64)); + + if (adev->family == AMDGPU_FAMILY_SI) + return modifier_base | + AMD_FMT_MOD_SET(BANK_WIDTH, (gb_tile_mode >> 14) & 0x3) | + AMD_FMT_MOD_SET(BANK_HEIGHT, (gb_tile_mode >> 16) & 0x3) | + AMD_FMT_MOD_SET(MACRO_TILE_ASPECT, (gb_tile_mode >> 18) & 0x3) | + AMD_FMT_MOD_SET(NUM_BANKS, (gb_tile_mode >> 20) & 0x3); + + macrotile_idx = amdgpu_dm_plane_get_gfx7_macro_tile_idx(bpp, tile_split_bytes); + gb_macrotile_mode = adev->gfx.config.macrotile_mode_array[macrotile_idx]; + + return modifier_base | + AMD_FMT_MOD_SET(BANK_WIDTH, (gb_macrotile_mode >> 0) & 0x3) | + AMD_FMT_MOD_SET(BANK_HEIGHT, (gb_macrotile_mode >> 2) & 0x3) | + AMD_FMT_MOD_SET(MACRO_TILE_ASPECT, (gb_macrotile_mode >> 4) & 0x3) | + AMD_FMT_MOD_SET(NUM_BANKS, (gb_macrotile_mode >> 6) & 0x3); +} + +/** + * amdgpu_dm_plane_gfx6_format_mod_supported() - Check if a modifier is supported on GFX6-8 + * + * @adev: amdgpu_device pointer + * @bpp: bits per pixel in the image + * @modifier: the modifier whose support we check + * + * On GFX6-8, not all DRM format modifier can be used with all image formats. + * Check whether the specified modifier is supported with the given bits per pixel value. + */ +static bool amdgpu_dm_plane_gfx6_format_mod_supported(const struct amdgpu_device *adev, + const u32 bpp, + const u64 modifier) +{ + const u32 array_mode = AMD_FMT_MOD_GET(TILE, modifier); + const u32 micro_tile_mode = AMD_FMT_MOD_GET(MICROTILE, modifier); + + if (!IS_AMD_FMT_MOD(modifier)) + return false; + + /* GFX9 and newer format modifiers are not supported on GFX6-8. */ + if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) != AMD_FMT_MOD_TILE_VER_GFX6) + return false; + + /* GFX8 and older format modifiers are not supported on GFX9+. */ + if (adev->family >= AMDGPU_FAMILY_AI) + return false; + + /* GFX6-7 doesn't have any DCC, GFX8 doesn't support displayable DCC. */ + if (AMD_FMT_MOD_GET(DCC, modifier)) + return false; + + /* + * For now, only expose 1D and 2D tiles THIN array modes. + * Linear is already exposed through DRM_FORMAT_MOD_LINEAR. + */ + if (array_mode != AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1 && + array_mode != AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) + return false; + + /* + * For now, only allow DISPLAY micro tile mode. + * THIN, DEPTH and THICK modes are not displayable. + * ROTATED has never been supported by Linux or Mesa. + */ + if (micro_tile_mode != AMD_FMT_MOD_MICROTILE_DISPLAY) + return false; + + /* Verify that the modifier is the same that we'd expose for this bpp */ + return amdgpu_dm_plane_calc_gfx6_mod(adev, bpp, array_mode) == modifier; +} + +/** + * amdgpu_dm_plane_add_gfx6_modifiers() - Expose modifiers for GFX6-8 + * + * @adev: amdgpu_device pointer + * @mods: Pointer to array of format modifiers + * @size: Pointer to size of the array + * @capacity: Pointer to capacity of the array + * + * Calculate a DRM format modifier for macro tiled modes for each supported + * bits per pixel value. Use de-duplication because on some GPUs it may happen + * that different bpp results in the exact same macro tiling mode, depending + * on the tiling table. + * + * Also expose a micro tiled only mode. This is less optimal, but supported. + */ +static void amdgpu_dm_plane_add_gfx6_modifiers(const struct amdgpu_device *adev, + u64 **mods, + u64 *size, + u64 *capacity) +{ + /* 2D tiled displayable */ + amdgpu_dm_plane_add_modifier(mods, size, capacity, + amdgpu_dm_plane_calc_gfx6_mod(adev, 16, DC_ARRAY_2D_TILED_THIN1)); + amdgpu_dm_plane_add_modifier_dedup(mods, size, capacity, + amdgpu_dm_plane_calc_gfx6_mod(adev, 32, DC_ARRAY_2D_TILED_THIN1)); + amdgpu_dm_plane_add_modifier_dedup(mods, size, capacity, + amdgpu_dm_plane_calc_gfx6_mod(adev, 64, DC_ARRAY_2D_TILED_THIN1)); + + /* 1D tiled displayable */ + amdgpu_dm_plane_add_modifier(mods, size, capacity, + amdgpu_dm_plane_calc_gfx6_mod(adev, 0, DC_ARRAY_1D_TILED_THIN1)); +} + static void amdgpu_dm_plane_add_gfx9_modifiers(const struct amdgpu_device *adev, uint64_t **mods, uint64_t *size, @@ -749,12 +1010,6 @@ static void amdgpu_dm_plane_add_gfx12_modifiers(struct amdgpu_device *adev, STATIC_IFN_KUNIT int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, unsigned int plane_type, uint64_t **mods) { uint64_t size = 0, capacity = 128; - *mods = NULL; - - /* We have not hooked up any pre-GFX9 modifiers. */ - if (adev->family < AMDGPU_FAMILY_AI) - return 0; - *mods = kmalloc_array(capacity, sizeof(uint64_t), GFP_KERNEL); if (plane_type == DRM_PLANE_TYPE_CURSOR) { @@ -764,6 +1019,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *a } switch (adev->family) { + case AMDGPU_FAMILY_SI: + case AMDGPU_FAMILY_CI: + case AMDGPU_FAMILY_KV: + case AMDGPU_FAMILY_VI: + case AMDGPU_FAMILY_CZ: + amdgpu_dm_plane_add_gfx6_modifiers(adev, mods, &size, &capacity); + break; case AMDGPU_FAMILY_AI: case AMDGPU_FAMILY_RV: amdgpu_dm_plane_add_gfx9_modifiers(adev, mods, &size, &capacity); @@ -865,7 +1127,6 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, - const uint64_t tiling_flags, struct dc_tiling_info *tiling_info, struct plane_size *plane_size, struct dc_plane_dcc_param *dcc, @@ -927,21 +1188,24 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, } if (adev->family == AMDGPU_FAMILY_GC_12_0_0) { - ret = amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(adev, afb, format, + ret = amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers(adev, afb, format, rotation, plane_size, tiling_info, dcc, address); if (ret) return ret; } else if (adev->family >= AMDGPU_FAMILY_AI) { - ret = amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(adev, afb, format, + ret = amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers(adev, afb, format, rotation, plane_size, tiling_info, dcc, address); if (ret) return ret; } else { - amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(tiling_info, tiling_flags); + ret = amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier(tiling_info, + afb->base.modifier); + if (ret) + return ret; } return 0; @@ -975,13 +1239,15 @@ static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, adev = amdgpu_ttm_adev(rbo->tbo.bdev); r = amdgpu_bo_reserve(rbo, true); if (r) { - drm_err(adev_to_drm(adev), "fail to reserve bo (%d)\n", r); + drm_err(adev_to_drm(adev), "fail to reserve bo: %pe\n", ERR_PTR(r)); return r; } r = dma_resv_reserve_fences(rbo->tbo.base.resv, TTM_NUM_MOVE_FENCES); - if (r) + if (r) { + drm_err(adev_to_drm(adev), "reserving fence slot failed: %pe\n", ERR_PTR(r)); goto error_unlock; + } if (plane->type != DRM_PLANE_TYPE_CURSOR) domain = amdgpu_display_supported_domains(adev, rbo->flags); @@ -992,13 +1258,13 @@ static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, r = amdgpu_bo_pin(rbo, domain); if (unlikely(r != 0)) { if (r != -ERESTARTSYS) - DRM_ERROR("Failed to pin framebuffer with error %d\n", r); + DRM_ERROR("Failed to pin framebuffer: %pe\n", ERR_PTR(r)); goto error_unlock; } r = amdgpu_ttm_alloc_gart(&rbo->tbo); if (unlikely(r != 0)) { - DRM_ERROR("%p bind failed\n", rbo); + DRM_ERROR("%p bind failed: %pe\n", rbo, ERR_PTR(r)); goto error_unpin; } @@ -1030,7 +1296,6 @@ static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, amdgpu_dm_plane_fill_plane_buffer_attributes( adev, afb, plane_state->format, plane_state->rotation, - afb->tiling_flags, &plane_state->tiling_info, &plane_state->plane_size, &plane_state->dcc, &plane_state->address, afb->tmz_surface); @@ -1058,7 +1323,7 @@ static void amdgpu_dm_plane_helper_cleanup_fb(struct drm_plane *plane, rbo = gem_to_amdgpu_bo(old_state->fb->obj[0]); r = amdgpu_bo_reserve(rbo, false); if (unlikely(r)) { - DRM_ERROR("failed to reserve rbo before unpin\n"); + DRM_ERROR("failed to reserve rbo before unpin: %pe\n", ERR_PTR(r)); return; } @@ -1254,8 +1519,8 @@ int amdgpu_dm_plane_fill_dc_scaling_info(struct amdgpu_device *adev, } EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_dc_scaling_info); -static int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, - struct drm_atomic_commit *state) +STATIC_IFN_KUNIT int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) { struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); @@ -1300,9 +1565,10 @@ static int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_atomic_check); -static int amdgpu_dm_plane_atomic_async_check(struct drm_plane *plane, - struct drm_atomic_commit *state, bool flip) +STATIC_IFN_KUNIT int amdgpu_dm_plane_atomic_async_check(struct drm_plane *plane, + struct drm_atomic_commit *state, bool flip) { struct drm_crtc_state *new_crtc_state; struct drm_plane_state *new_plane_state; @@ -1324,6 +1590,7 @@ static int amdgpu_dm_plane_atomic_async_check(struct drm_plane *plane, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_atomic_async_check); int amdgpu_dm_plane_get_cursor_position(struct drm_plane *plane, struct drm_crtc *crtc, struct dc_cursor_position *position) @@ -1483,7 +1750,7 @@ static void amdgpu_dm_plane_atomic_async_update(struct drm_plane *plane, amdgpu_dm_plane_handle_cursor_update(plane, old_state); } -static void amdgpu_dm_plane_panic_flush(struct drm_plane *plane) +STATIC_IFN_KUNIT void amdgpu_dm_plane_panic_flush(struct drm_plane *plane) { struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane->state); struct drm_framebuffer *fb = plane->state->fb; @@ -1496,6 +1763,7 @@ static void amdgpu_dm_plane_panic_flush(struct drm_plane *plane) dc_plane_force_dcc_and_tiling_disable(dc_plane_state, fb->modifier ? true : false); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_panic_flush); static const struct drm_plane_helper_funcs dm_plane_helper_funcs = { .prepare_fb = amdgpu_dm_plane_helper_prepare_fb, @@ -1515,7 +1783,7 @@ static const struct drm_plane_helper_funcs dm_primary_plane_helper_funcs = { .panic_flush = amdgpu_dm_plane_panic_flush, }; -static void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane) +STATIC_IFN_KUNIT void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane) { struct dm_plane_state *amdgpu_state; @@ -1532,8 +1800,10 @@ static void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane) amdgpu_state->shaper_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; amdgpu_state->blend_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_drm_plane_reset); -static struct drm_plane_state *amdgpu_dm_plane_drm_plane_duplicate_state(struct drm_plane *plane) +STATIC_IFN_KUNIT struct drm_plane_state * +amdgpu_dm_plane_drm_plane_duplicate_state(struct drm_plane *plane) { struct dm_plane_state *dm_plane_state, *old_dm_plane_state; @@ -1572,6 +1842,7 @@ static struct drm_plane_state *amdgpu_dm_plane_drm_plane_duplicate_state(struct return &dm_plane_state->base; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_drm_plane_duplicate_state); STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, uint32_t format, @@ -1579,6 +1850,7 @@ STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *pla { struct amdgpu_device *adev = drm_to_adev(plane->dev); const struct drm_format_info *info = drm_format_info(format); + const u32 bpp = drm_format_info_bpp(info, 0); int i; if (!info) @@ -1602,6 +1874,13 @@ STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *pla if (i == plane->modifier_count) return false; + if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) == AMD_FMT_MOD_TILE_VER_GFX6) + return amdgpu_dm_plane_gfx6_format_mod_supported(adev, bpp, modifier); + + /* GFX9+ modifers are not supported on GFX8 and older. */ + if (adev->family < AMDGPU_FAMILY_AI) + return false; + /* GFX12 doesn't have these limitations. */ if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) <= AMD_FMT_MOD_TILE_VER_GFX11) { enum dm_micro_swizzle microtile = amdgpu_dm_plane_modifier_gfx9_swizzle_mode(modifier) & 3; @@ -1636,8 +1915,8 @@ STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *pla } EXPORT_IF_KUNIT(amdgpu_dm_plane_format_mod_supported); -static void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, - struct drm_plane_state *state) +STATIC_IFN_KUNIT void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) { struct dm_plane_state *dm_plane_state = to_dm_plane_state(state); @@ -1657,6 +1936,7 @@ static void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, drm_atomic_helper_plane_destroy_state(plane, state); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_drm_plane_destroy_state); #ifdef AMD_PRIVATE_COLOR static void @@ -1861,8 +2141,8 @@ dm_plane_init_colorops(struct drm_plane *plane) if (dc->ctx->dce_version >= DCN_VERSION_3_0) { ret = amdgpu_dm_initialize_default_pipeline(plane, &pipelines[len]); if (ret) { - drm_err(plane->dev, "Failed to create color pipeline for plane %d: %d\n", - plane->base.id, ret); + drm_err(plane->dev, "Failed to create color pipeline for plane %d: %pe\n", + plane->base.id, ERR_PTR(ret)); goto out; } len++; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h index 911fb2d73e22..092ade738ce9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h @@ -48,7 +48,6 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, - const uint64_t tiling_flags, struct dc_tiling_info *tiling_info, struct plane_size *plane_size, struct dc_plane_dcc_param *dcc, @@ -71,8 +70,6 @@ bool amdgpu_dm_plane_is_video_format(uint32_t format); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *size, uint64_t *cap, uint64_t mod); -void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_info *tiling_info, - uint64_t tiling_flags); void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgpu_device *adev, struct dc_tiling_info *tiling_info); void amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(const struct amdgpu_device *adev, @@ -92,7 +89,7 @@ int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, const struct dc_plane_cap *plane_cap, uint32_t *formats, int max_formats); -int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, +int amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, @@ -100,7 +97,7 @@ int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_devi struct dc_tiling_info *tiling_info, struct dc_plane_dcc_param *dcc, struct dc_plane_address *address); -int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, +int amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, @@ -115,5 +112,18 @@ void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, struct drm_framebuffer *fb, int *min_downscale, int *max_upscale); +int amdgpu_dm_plane_atomic_async_check(struct drm_plane *plane, + struct drm_atomic_commit *state, bool flip); +int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state); +void amdgpu_dm_plane_panic_flush(struct drm_plane *plane); +void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane); +struct drm_plane_state *amdgpu_dm_plane_drm_plane_duplicate_state(struct drm_plane *plane); +void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state); +void amdgpu_dm_plane_add_modifier_dedup(uint64_t **mods, uint64_t *size, + uint64_t *cap, uint64_t mod); +int amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier(struct dc_tiling_info *tiling_info, + uint64_t modifier); #endif #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index 0d2e5294d062..3280bfadc6a8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -33,7 +33,7 @@ #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "dm_pp_smu.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "amdgpu_dm_pp_smu.h" STATIC_IFN_KUNIT void build_pm_display_cfg( @@ -792,3 +792,55 @@ void dm_pp_get_funcs( } } EXPORT_IF_KUNIT(dm_pp_get_funcs); + +int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev) +{ + int ret = 0; + + /* This interface is for dGPU Navi1x.Linux dc-pplib interface depends + * on window driver dc implementation. + * For Navi1x, clock settings of dcn watermarks are fixed. the settings + * should be passed to smu during boot up and resume from s3. + * boot up: dc calculate dcn watermark clock settings within dc_create, + * dcn20_resource_construct + * then call pplib functions below to pass the settings to smu: + * smu_set_watermarks_for_clock_ranges + * smu_set_watermarks_table + * navi10_set_watermarks_table + * smu_write_watermarks_table + * + * For Renoir, clock settings of dcn watermark are also fixed values. + * dc has implemented different flow for window driver: + * dc_hardware_init / dc_set_power_state + * dcn10_init_hw + * notify_wm_ranges + * set_wm_ranges + * -- Linux + * smu_set_watermarks_for_clock_ranges + * renoir_set_watermarks_table + * smu_write_watermarks_table + * + * For Linux, + * dc_hardware_init -> amdgpu_dm_init + * dc_set_power_state --> dm_resume + * + * therefore, this function apply to navi10/12/14 but not Renoir + * * + */ + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(2, 0, 2): + case IP_VERSION(2, 0, 0): + break; + default: + return 0; + } + + ret = amdgpu_dpm_write_watermarks_table(adev); + if (ret) { + drm_err(adev_to_drm(adev), "Failed to update WMTABLE!\n"); + return ret; + } + + return 0; +} +EXPORT_IF_KUNIT(amdgpu_dm_smu_write_watermarks_table); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h index f918eb71f0d1..1d153d0e2f03 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h @@ -11,6 +11,9 @@ struct amd_pp_display_configuration; struct pp_smu_wm_range_sets; struct dm_pp_wm_sets_with_clock_ranges_soc15; +struct amdgpu_device; + +int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) void build_pm_display_cfg(struct amd_pp_display_configuration *pm_display_cfg, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c index f87de3d18ac0..dbad0a6d3a2b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c @@ -30,7 +30,7 @@ #include "dc.h" #include "amdgpu_dm.h" #include "modules/power/power_helpers.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" STATIC_IFN_KUNIT bool link_supports_psrsu(struct dc_link *link) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c index cf28d50c3b5e..259966703cef 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c @@ -28,7 +28,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" struct amdgpu_dm_quirks { bool aux_hpd_discon; @@ -178,3 +178,35 @@ void retrieve_dmi_info(struct amdgpu_display_manager *dm) } } EXPORT_IF_KUNIT(retrieve_dmi_info); + +struct amdgpu_stutter_quirk { + u16 chip_vendor; + u16 chip_device; + u16 subsys_vendor; + u16 subsys_device; + u8 revision; +}; + +static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = { + /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */ + { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 }, + { 0, 0, 0, 0, 0 }, +}; + +bool dm_should_disable_stutter(struct pci_dev *pdev) +{ + const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list; + + while (p && p->chip_device != 0) { + if (pdev->vendor == p->chip_vendor && + pdev->device == p->chip_device && + pdev->subsystem_vendor == p->subsys_vendor && + pdev->subsystem_device == p->subsys_device && + pdev->revision == p->revision) { + return true; + } + ++p; + } + return false; +} +EXPORT_IF_KUNIT(dm_should_disable_stutter); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c index 42e17119461d..ee2e4754b089 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c @@ -31,7 +31,7 @@ #include "modules/power/power_helpers.h" #include "dmub/inc/dmub_cmd.h" #include "dc/inc/link_service.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" /* diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c index 6c0464754ed8..6dbc8d1b4ecd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c @@ -36,7 +36,7 @@ #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "amdgpu_dm_trace.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" unsigned long long dm_get_elapse_time_in_ns(struct dc_context *ctx, @@ -96,3 +96,56 @@ bool dm_query_extended_brightness_caps(struct dc_context *ctx, return true; } EXPORT_IF_KUNIT(dm_query_extended_brightness_caps); + +void* +dm_allocate_gpu_mem( + struct amdgpu_device *adev, + enum dc_gpu_mem_alloc_type type, + size_t size, + long long *addr) +{ + struct dal_allocation *da; + u32 domain = (type == DC_MEM_ALLOC_TYPE_GART) ? + AMDGPU_GEM_DOMAIN_GTT : AMDGPU_GEM_DOMAIN_VRAM; + int ret; + + da = kzalloc_obj(*da); + if (!da) + return NULL; + + ret = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE, + domain, &da->bo, + &da->gpu_addr, &da->cpu_ptr); + + *addr = da->gpu_addr; + + if (ret) { + kfree(da); + return NULL; + } + + /* add da to list in dm */ + list_add(&da->list, &adev->dm.da_list); + + return da->cpu_ptr; +} + +void +dm_free_gpu_mem( + struct amdgpu_device *adev, + enum dc_gpu_mem_alloc_type type, + void *pvMem) +{ + struct dal_allocation *da; + + /* walk the da list in DM */ + list_for_each_entry(da, &adev->dm.da_list, list) { + if (pvMem == da->cpu_ptr) { + amdgpu_bo_free_kernel(&da->bo, &da->gpu_addr, &da->cpu_ptr); + list_del(&da->list); + kfree(da); + break; + } + } + +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c index 0bf82e46f773..a7594012a0b2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c @@ -29,7 +29,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_wb.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "amdgpu_display.h" #include "dc.h" @@ -85,7 +85,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_wb_connector_get_modes(struct drm_connector *conn } EXPORT_IF_KUNIT(amdgpu_dm_wb_connector_get_modes); -static int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector, +STATIC_IFN_KUNIT int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector, struct drm_writeback_job *job) { struct amdgpu_framebuffer *afb; @@ -107,13 +107,15 @@ static int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector r = amdgpu_bo_reserve(rbo, true); if (r) { - drm_err(adev_to_drm(adev), "fail to reserve bo (%d)\n", r); + drm_err(adev_to_drm(adev), "fail to reserve bo: %pe\n", ERR_PTR(r)); return r; } r = dma_resv_reserve_fences(rbo->tbo.base.resv, TTM_NUM_MOVE_FENCES); - if (r) + if (r) { + drm_err(adev_to_drm(adev), "reserving fence slot failed: %pe\n", ERR_PTR(r)); goto error_unlock; + } domain = amdgpu_display_supported_domains(adev, rbo->flags); @@ -121,13 +123,13 @@ static int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector r = amdgpu_bo_pin(rbo, domain); if (unlikely(r != 0)) { if (r != -ERESTARTSYS) - DRM_ERROR("Failed to pin framebuffer with error %d\n", r); + DRM_ERROR("Failed to pin framebuffer: %pe\n", ERR_PTR(r)); goto error_unlock; } r = amdgpu_ttm_alloc_gart(&rbo->tbo); if (unlikely(r != 0)) { - DRM_ERROR("%p bind failed\n", rbo); + DRM_ERROR("%p bind failed: %pe\n", rbo, ERR_PTR(r)); goto error_unpin; } @@ -146,8 +148,9 @@ error_unlock: amdgpu_bo_unreserve(rbo); return r; } +EXPORT_IF_KUNIT(amdgpu_dm_wb_prepare_job); -static void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector, +STATIC_IFN_KUNIT void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector, struct drm_writeback_job *job) { struct amdgpu_bo *rbo; @@ -159,7 +162,7 @@ static void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector, rbo = gem_to_amdgpu_bo(job->fb->obj[0]); r = amdgpu_bo_reserve(rbo, false); if (unlikely(r)) { - DRM_ERROR("failed to reserve rbo before unpin\n"); + DRM_ERROR("failed to reserve rbo before unpin: %pe\n", ERR_PTR(r)); return; } @@ -167,6 +170,7 @@ static void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector, amdgpu_bo_unreserve(rbo); amdgpu_bo_unref(&rbo); } +EXPORT_IF_KUNIT(amdgpu_dm_wb_cleanup_job); static const struct drm_encoder_helper_funcs amdgpu_dm_wb_encoder_helper_funcs = { .atomic_check = amdgpu_dm_wb_encoder_atomic_check, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h index 7e9fd7a036fa..5fd616bc43b5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h @@ -44,6 +44,10 @@ int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state); int amdgpu_dm_wb_connector_get_modes(struct drm_connector *connector); +int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector, + struct drm_writeback_job *job); +void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector, + struct drm_writeback_job *job); #endif #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 1592e8dae1a9..3a51fccc2a5e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -29,6 +29,8 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_plane_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_freesync_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_cursor_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c index 79ff5d9b3fa5..040a7a9c0335 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c @@ -6,14 +6,64 @@ */ #include +#include +#include +#include #include +#include +#include +#include +#include +#include #include "dc.h" +#include "dc/inc/core_types.h" +#include "dc/inc/hw/audio.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_audio.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +static const struct drm_connector_funcs dm_test_audio_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, +}; + +static void dm_test_audio_connector_cleanup(void *data) +{ + drm_connector_cleanup(data); +} + +static struct drm_atomic_commit *dm_test_audio_alloc_atomic_state(struct kunit *test, + unsigned int num_connector, + unsigned int num_crtc) +{ + struct drm_atomic_commit *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->num_connector = num_connector; + if (num_connector) { + state->connectors = kunit_kcalloc(test, num_connector, + sizeof(*state->connectors), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->connectors); + } + + if (num_crtc) { + state->crtcs = kunit_kcalloc(test, num_crtc, sizeof(*state->crtcs), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->crtcs); + } + + return state; +} /* Tests for amdgpu_dm_audio_init() */ @@ -38,6 +88,57 @@ static void dm_test_audio_init_disabled(struct kunit *test) amdgpu_dm_audio_set_param(saved_audio); } +/** + * dm_test_audio_init_enabled_success - Test init deeper path when audio is enabled + * @test: The KUnit test context + */ +static void dm_test_audio_init_enabled_success(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct resource_pool *res_pool; + struct audio *audio0; + struct audio *audio1; + struct device *dev; + int saved_audio = amdgpu_dm_audio_get_param(); + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); + audio0 = kunit_kzalloc(test, sizeof(*audio0), GFP_KERNEL); + audio1 = kunit_kzalloc(test, sizeof(*audio1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio1); + + dev = root_device_register("kunit-dm-audio-init"); + KUNIT_ASSERT_FALSE(test, IS_ERR(dev)); + + audio0->inst = 2; + audio1->inst = 6; + res_pool->audio_count = 2; + res_pool->audios[0] = audio0; + res_pool->audios[1] = audio1; + dc->res_pool = res_pool; + adev->dm.dc = dc; + adev->dev = dev; + + amdgpu_dm_audio_set_param(1); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_audio_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->mode_info.audio.enabled); + KUNIT_EXPECT_TRUE(test, adev->dm.audio_registered); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.num_pins, 2); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[0].id, 2U); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[1].id, 6U); + + amdgpu_dm_audio_fini(adev); + root_device_unregister(dev); + amdgpu_dm_audio_set_param(saved_audio); +} + /* Tests for amdgpu_dm_audio_fini() */ /** @@ -338,6 +439,22 @@ static void dm_test_pin_eld_notify(void *audio_ptr, int port, int pipe) dm_test_eld_notify_ptr = audio_ptr; } +static void dm_test_audio_setup_notify_component(struct kunit *test, + struct amdgpu_device *adev) +{ + struct drm_audio_component *acomp; + struct drm_audio_component_audio_ops *audio_ops; + + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acomp); + KUNIT_ASSERT_NOT_NULL(test, audio_ops); + + audio_ops->pin_eld_notify = dm_test_pin_eld_notify; + acomp->audio_ops = audio_ops; + adev->dm.audio_component = acomp; +} + /** * dm_test_eld_notify_invokes_callback - Test ELD notify forwards to hda driver * @test: The KUnit test context @@ -455,9 +572,648 @@ static void dm_test_eld_notify_null_callback(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); } +/* Tests for amdgpu_dm_audio_init_pins() */ + +/** + * dm_test_audio_init_pins_sets_defaults - pin entries are initialised to default values + * @test: The KUnit test context + * + * amdgpu_dm_audio_init_pins() must set num_pins from audio_count, reset every + * pin to the sentinel defaults (-1 for rate/channels/bits, 0 for the rest) + * and copy each pin's hardware instance index from res_pool->audios[i]->inst. + */ +static void dm_test_audio_init_pins_sets_defaults(struct kunit *test) +{ + struct amdgpu_device *adev; + const unsigned int inst_array[] = {3, 7}; + int i; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + amdgpu_dm_audio_init_pins(adev, 2, inst_array); + + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.num_pins, 2); + + for (i = 0; i < 2; i++) { + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].channels, -1); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].rate, -1); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].bits_per_sample, -1); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].status_bits, 0); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].category_code, 0); + KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.pin[i].connected); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[i].offset, 0); + } + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[0].id, 3U); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[1].id, 7U); +} + +/** + * dm_test_audio_init_pins_zero_count - zero audio_count leaves num_pins at zero + * @test: The KUnit test context + * + * When res_pool->audio_count is 0, num_pins must be 0 and no pins touched. + */ +static void dm_test_audio_init_pins_zero_count(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* Pre-fill a sentinel so we can confirm the loop never ran. */ + adev->mode_info.audio.pin[0].channels = 99; + + amdgpu_dm_audio_init_pins(adev, 0, NULL); + + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.num_pins, 0); + KUNIT_EXPECT_EQ(test, adev->mode_info.audio.pin[0].channels, 99); +} + +/* End of tests for amdgpu_dm_audio_init_pins() */ + +/* Tests for amdgpu_dm_audio_component_get_eld() */ + +/** + * dm_test_audio_component_get_eld_copies_matching_connector - Test ELD lookup + * @test: The KUnit test context + * + * The bound component callback should find the connector whose audio instance + * matches the requested port, report it enabled, and copy the connector ELD. + */ +static void dm_test_audio_component_get_eld_copies_matching_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + struct drm_connector *wb_connector; + struct drm_audio_component *acomp; + struct device *kdev; + unsigned char buf[DRM_ELD_HEADER_BLOCK_SIZE + 8] = {0}; + bool enabled = false; + int ret; + + kdev = adev->ddev.dev; + KUNIT_ASSERT_NOT_NULL(test, kdev); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acomp); + wb_connector = kunit_kzalloc(test, sizeof(*wb_connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, wb_connector); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wb_connector, + &dm_test_audio_connector_funcs, + DRM_MODE_CONNECTOR_WRITEBACK), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_audio_connector_cleanup, wb_connector), 0); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconnector->base, + &dm_test_audio_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_audio_connector_cleanup, &aconnector->base), 0); + + mutex_init(&adev->dm.audio_lock); + aconnector->audio_inst = 3; + aconnector->base.eld[DRM_ELD_BASELINE_ELD_LEN] = 2; + aconnector->base.eld[DRM_ELD_VER] = DRM_ELD_VER_CEA861D; + aconnector->base.eld[DRM_ELD_SPEAKER] = DRM_ELD_SPEAKER_FLR; + dev_set_drvdata(kdev, &adev->ddev); + + KUNIT_ASSERT_EQ(test, amdgpu_dm_audio_component_bind(kdev, NULL, acomp), 0); + ret = acomp->ops->get_eld(kdev, 3, 0, &enabled, buf, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, DRM_ELD_HEADER_BLOCK_SIZE + 8); + KUNIT_EXPECT_TRUE(test, enabled); + KUNIT_EXPECT_EQ(test, memcmp(buf, aconnector->base.eld, ret), 0); +} + +/** + * dm_test_audio_component_get_eld_no_match - Test ELD lookup miss + * @test: The KUnit test context + * + * A non-matching connector should leave the audio port disabled and return an + * empty ELD size without copying data. + */ +static void dm_test_audio_component_get_eld_no_match(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + struct drm_audio_component *acomp; + struct device *kdev; + unsigned char buf[DRM_ELD_HEADER_BLOCK_SIZE] = {0x5a}; + bool enabled = true; + int ret; + + kdev = adev->ddev.dev; + KUNIT_ASSERT_NOT_NULL(test, kdev); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acomp); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconnector->base, + &dm_test_audio_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_audio_connector_cleanup, &aconnector->base), 0); + + mutex_init(&adev->dm.audio_lock); + aconnector->audio_inst = 4; + dev_set_drvdata(kdev, &adev->ddev); + + KUNIT_ASSERT_EQ(test, amdgpu_dm_audio_component_bind(kdev, NULL, acomp), 0); + ret = acomp->ops->get_eld(kdev, 3, 0, &enabled, buf, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_FALSE(test, enabled); + KUNIT_EXPECT_EQ(test, buf[0], 0x5a); +} + +/* Tests for amdgpu_dm_commit_audio() */ + +/** + * dm_test_commit_audio_notifies_removed_connector - Test removal notification + * @test: The KUnit test context + * + * When a connector loses its CRTC, commit_audio should clear its audio + * instance and notify the audio component for the old pin. + */ +static void dm_test_commit_audio_notifies_removed_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + + state = dm_test_audio_alloc_atomic_state(test, 1, 0); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 5; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + old_conn_state->crtc = crtc; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + + dm_test_eld_notify_count = 0; + dm_test_eld_notify_port = -1; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, -1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 5); +} + +/** + * dm_test_commit_audio_notifies_added_connector - Test addition notification + * @test: The KUnit test context + * + * When a modeset enables a connector with a stream status, commit_audio should + * store the stream audio instance and notify the audio component for that pin. + */ +static void dm_test_commit_audio_notifies_added_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + struct dc_stream_state *stream; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, stream); + + adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc); + adev->dm.dc->current_state = kunit_kzalloc(test, + sizeof(*adev->dm.dc->current_state), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc->current_state); + stream->ctx = kunit_kzalloc(test, sizeof(*stream->ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream->ctx); + stream->ctx->dc = adev->dm.dc; + adev->dm.dc->current_state->stream_count = 1; + adev->dm.dc->current_state->streams[0] = stream; + adev->dm.dc->current_state->stream_status[0].audio_inst = 8; + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = -1; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + dm_crtc_state->base.mode_changed = true; + dm_crtc_state->stream = stream; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + dm_test_eld_notify_port = -1; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 8); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 2); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 8); +} + +/** + * dm_test_commit_audio_skips_writeback_removal - Test writeback removal skip + * @test: The KUnit test context + * + * Writeback connectors do not represent an HDA audio pin, so removal-style + * notifications should leave their audio instance untouched. + */ +static void dm_test_commit_audio_skips_writeback_removal(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + + state = dm_test_audio_alloc_atomic_state(test, 1, 0); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 9; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + old_conn_state->crtc = crtc; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 9); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_commit_audio_skips_without_new_crtc_state - Test missing CRTC state + * @test: The KUnit test context + * + * A connector still attached to the same CRTC should not notify if the atomic + * state does not contain a new CRTC state for that CRTC. + */ +static void dm_test_commit_audio_skips_without_new_crtc_state(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 4; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + old_conn_state->crtc = crtc; + new_conn_state->crtc = crtc; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 4); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_commit_audio_skips_without_stream_status - Test missing DC status + * @test: The KUnit test context + * + * If the new CRTC stream is absent from the current DC state, additions should + * be skipped because there is no audio instance to publish. + */ +static void dm_test_commit_audio_skips_without_stream_status(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + struct dc_stream_state *stream; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, stream); + + adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc); + adev->dm.dc->current_state = kunit_kzalloc(test, + sizeof(*adev->dm.dc->current_state), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc->current_state); + stream->ctx = kunit_kzalloc(test, sizeof(*stream->ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream->ctx); + stream->ctx->dc = adev->dm.dc; + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = -1; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + dm_crtc_state->base.mode_changed = true; + dm_crtc_state->stream = stream; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, -1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1); +} + +/** + * dm_test_commit_audio_skips_detached_connector - Test detached connector skip + * @test: The KUnit test context + * + * A connector that remains detached across the commit should not notify or + * alter the stored audio instance. + */ +static void dm_test_commit_audio_skips_detached_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + + state = dm_test_audio_alloc_atomic_state(test, 1, 0); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + + aconnector->audio_inst = 11; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 11); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_commit_audio_skips_without_modeset - Test no-modeset skip + * @test: The KUnit test context + * + * A connector that stays on the same CRTC should not notify if that CRTC does + * not need a modeset. + */ +static void dm_test_commit_audio_skips_without_modeset(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + aconnector->audio_inst = 12; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + old_conn_state->crtc = crtc; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 12); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_commit_audio_skips_addition_without_stream - Test NULL stream skip + * @test: The KUnit test context + * + * A modeset still sends the removal-side ELD notification, but the addition + * side must skip if the new DM CRTC state has no stream. + */ +static void dm_test_commit_audio_skips_addition_without_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 13; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + old_conn_state->crtc = crtc; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + dm_crtc_state->base.mode_changed = true; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + dm_test_eld_notify_port = -1; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, -1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 13); +} + +/** + * dm_test_commit_audio_skips_writeback_addition - Test writeback addition skip + * @test: The KUnit test context + * + * Even with a valid modeset and stream status, writeback connectors must not + * publish an HDA audio instance. + */ +static void dm_test_commit_audio_skips_writeback_addition(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + struct dc_stream_state *stream; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, stream); + + adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc); + adev->dm.dc->current_state = kunit_kzalloc(test, + sizeof(*adev->dm.dc->current_state), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc->current_state); + stream->ctx = kunit_kzalloc(test, sizeof(*stream->ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream->ctx); + stream->ctx->dc = adev->dm.dc; + adev->dm.dc->current_state->stream_count = 1; + adev->dm.dc->current_state->streams[0] = stream; + adev->dm.dc->current_state->stream_status[0].audio_inst = 14; + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 15; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + crtc->index = 0; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + dm_crtc_state->base.mode_changed = true; + dm_crtc_state->stream = stream; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 15); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + static struct kunit_case dm_audio_test_cases[] = { /* amdgpu_dm_audio_init */ KUNIT_CASE(dm_test_audio_init_disabled), + KUNIT_CASE(dm_test_audio_init_enabled_success), + /* amdgpu_dm_audio_init_pins */ + KUNIT_CASE(dm_test_audio_init_pins_sets_defaults), + KUNIT_CASE(dm_test_audio_init_pins_zero_count), /* amdgpu_dm_audio_fini */ KUNIT_CASE(dm_test_audio_fini_without_enabled_audio), /* amdgpu_dm_fill_audio_info */ @@ -475,6 +1231,19 @@ static struct kunit_case dm_audio_test_cases[] = { KUNIT_CASE(dm_test_eld_notify_no_component), KUNIT_CASE(dm_test_eld_notify_null_audio_ops), KUNIT_CASE(dm_test_eld_notify_null_callback), + /* amdgpu_dm_audio_component_get_eld */ + KUNIT_CASE(dm_test_audio_component_get_eld_copies_matching_connector), + KUNIT_CASE(dm_test_audio_component_get_eld_no_match), + /* amdgpu_dm_commit_audio */ + KUNIT_CASE(dm_test_commit_audio_notifies_removed_connector), + KUNIT_CASE(dm_test_commit_audio_notifies_added_connector), + KUNIT_CASE(dm_test_commit_audio_skips_writeback_removal), + KUNIT_CASE(dm_test_commit_audio_skips_without_new_crtc_state), + KUNIT_CASE(dm_test_commit_audio_skips_without_stream_status), + KUNIT_CASE(dm_test_commit_audio_skips_detached_connector), + KUNIT_CASE(dm_test_commit_audio_skips_without_modeset), + KUNIT_CASE(dm_test_commit_audio_skips_addition_without_stream), + KUNIT_CASE(dm_test_commit_audio_skips_writeback_addition), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index fff50c1325c6..7ca17f803f9d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -8,13 +8,21 @@ #include #include +#include +#include +#include +#include + #include "dc.h" +#include "dc_dmub_srv.h" #include "amdgpu.h" +#include "amdgpu_display.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_backlight.h" #include "amdgpu_dm_kunit_test_helpers.h" #include "amd_shared.h" +#include "link_service.h" #include "dc/inc/hw/panel_cntl.h" struct dm_backlight_connector_fixture { @@ -23,6 +31,12 @@ struct dm_backlight_connector_fixture { struct dc_link *link; }; +static const struct drm_connector_funcs dm_backlight_test_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + static void setup_test_connector(struct kunit *test, struct dm_backlight_connector_fixture *fixture, int bl_idx, enum signal_type signal) @@ -40,6 +54,631 @@ static void setup_test_connector(struct kunit *test, fixture->link->connector_signal = signal; } +static void setup_test_dm_ddev(struct kunit *test, struct amdgpu_display_manager *dm) +{ + dm->ddev = dm_kunit_alloc_drm_with_connector_list(test); +} + +/* Tests for dm_find_stream_with_link() */ + +/** + * dm_test_find_stream_with_link_returns_match - Test matching stream lookup + * @test: The KUnit test context + */ +static void dm_test_find_stream_with_link_returns_match(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *other_link = dm_kunit_alloc_link(test); + struct dc_link *target_link = dm_kunit_alloc_link(test); + struct dc_stream_state *stream; + + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, other_link); + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 1, target_link); + stream = dm_find_stream_with_link(dm, target_link); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_PTR_EQ(test, stream->link, target_link); +} + +/** + * dm_test_find_stream_with_link_missing - Test missing stream lookup + * @test: The KUnit test context + */ +static void dm_test_find_stream_with_link_missing(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *stream_link = dm_kunit_alloc_link(test); + struct dc_link *missing_link = dm_kunit_alloc_link(test); + + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, stream_link); + + KUNIT_EXPECT_NULL(test, dm_find_stream_with_link(dm, missing_link)); +} + +/* Tests for amdgpu_dm_backlight_set_level() */ + +/** + * dm_test_backlight_set_level_connector_off - Test connector-off cache path + * @test: The KUnit test context + * + * If the matching connector has no encoder, set_level() must cache the + * requested brightness and return before touching DC or backlight hardware. + */ +static void dm_test_backlight_set_level_connector_off(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct amdgpu_dm_connector *aconnector; + + setup_test_dm_ddev(test, dm); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + INIT_LIST_HEAD(&aconnector->base.head); + aconnector->bl_idx = 1; + aconnector->base.encoder = NULL; + list_add_tail(&aconnector->base.head, &dm->ddev->mode_config.connector_list); + + amdgpu_dm_backlight_set_level(dm, 1, 1234); + + KUNIT_EXPECT_EQ(test, dm->brightness[1], 1234U); + KUNIT_EXPECT_EQ(test, dm->actual_brightness[1], 0U); +} + +/** + * dm_test_backlight_set_level_no_stream - Test no-stream early return + * @test: The KUnit test context + * + * With no stream for the backlight link, set_level() records the requested + * brightness and exits before calling the power-module programming path. + */ +static void dm_test_backlight_set_level_no_stream(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + + setup_test_dm_ddev(test, dm); + dm->backlight_caps[1].caps_valid = true; + dm->backlight_caps[1].min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + dm->backlight_caps[1].max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + dm->backlight_link[1] = link; + + amdgpu_dm_backlight_set_level(dm, 1, 2000); + + KUNIT_EXPECT_EQ(test, dm->brightness[1], 2000U); + KUNIT_EXPECT_EQ(test, dm->actual_brightness[1], 0U); +} + +/** + * dm_test_backlight_set_level_aux_programs_power_module - Test AUX programming path + * @test: The KUnit test context + * + * With a matching stream present, set_level() walks into the DC programming + * path. A NULL power_module makes mod_power_set_backlight_nits() a safe + * early-false, and ips_support disabled leaves idle optimizations untouched. + * A non-matching connector exercises the connector-list skip, and a non-zero + * brightness_mask exercises the quirk-OR path. + */ +static void dm_test_backlight_set_level_aux_programs_power_module(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct amdgpu_dm_connector *other; + + setup_test_dm_ddev(test, dm); + mutex_init(&dm->dc_lock); + dm->power_module = NULL; + + /* Non-matching connector exercises the bl_idx skip (continue). */ + other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, other); + INIT_LIST_HEAD(&other->base.head); + other->bl_idx = 0; + list_add_tail(&other->base.head, &dm->ddev->mode_config.connector_list); + + dm->backlight_caps[1].caps_valid = true; + dm->backlight_caps[1].aux_support = true; + dm->backlight_caps[1].brightness_mask = 0x3; + dm->backlight_caps[1].aux_min_input_signal = 1; + dm->backlight_caps[1].aux_max_input_signal = 512; + dm->backlight_caps[1].min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + dm->backlight_caps[1].max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + dm->backlight_link[1] = link; + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, link); + + amdgpu_dm_backlight_set_level(dm, 1, 2000); + + /* power_module is NULL so programming fails; actual stays unchanged. */ + KUNIT_EXPECT_EQ(test, dm->brightness[1], 2000U); + KUNIT_EXPECT_EQ(test, dm->actual_brightness[1], 0U); +} + +/** + * dm_test_backlight_set_level_pwm_programs_power_module - Test PWM programming path + * @test: The KUnit test context + * + * With aux_support cleared, set_level() takes the millipercent branch: + * get_brightness_range() + mod_power_set_backlight_percent(). A NULL + * power_module keeps the call a safe early-false. + */ +static void dm_test_backlight_set_level_pwm_programs_power_module(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + + setup_test_dm_ddev(test, dm); + mutex_init(&dm->dc_lock); + dm->power_module = NULL; + + dm->backlight_caps[1].caps_valid = true; + dm->backlight_caps[1].aux_support = false; + dm->backlight_caps[1].min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + dm->backlight_caps[1].max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + dm->backlight_link[1] = link; + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, link); + + amdgpu_dm_backlight_set_level(dm, 1, 2000); + + KUNIT_EXPECT_EQ(test, dm->brightness[1], 2000U); + KUNIT_EXPECT_EQ(test, dm->actual_brightness[1], 0U); +} + +/** + * dm_test_backlight_set_level_reallows_idle - Test idle-optimization toggle path + * @test: The KUnit test context + * + * When ips_support is set and dmub idle is allowed, set_level() disables idle + * optimizations around the programming call and re-enables them afterwards. + * disable_idle_power_optimizations keeps dc_allow_idle_optimizations() a safe + * early return, and ctx->logger is wired because DC_LOG_* dereferences it. + */ +static void dm_test_backlight_set_level_reallows_idle(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct dc_dmub_srv *dmub_srv; + struct dal_logger *logger; + struct dc_context *ctx; + + setup_test_dm_ddev(test, dm); + mutex_init(&dm->dc_lock); + dm->power_module = NULL; + + /* dm_kunit_alloc_dm() leaves dc->ctx NULL; the idle path dereferences it. */ + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + dm->dc->ctx = ctx; + + logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, logger); + logger->dev = &adev->ddev; + dm->dc->ctx->logger = logger; + + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + dmub_srv->idle_allowed = true; + dm->dc->ctx->dmub_srv = dmub_srv; + dm->dc->caps.ips_support = true; + /* Keep dc_allow_idle_optimizations() a safe early return. */ + dm->dc->debug.disable_idle_power_optimizations = true; + + dm->backlight_caps[1].caps_valid = true; + dm->backlight_caps[1].aux_support = true; + dm->backlight_caps[1].aux_min_input_signal = 1; + dm->backlight_caps[1].aux_max_input_signal = 512; + dm->backlight_caps[1].min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + dm->backlight_caps[1].max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + dm->backlight_link[1] = link; + dm_kunit_add_stream_to_state(test, dm->dc->current_state, 0, link); + + amdgpu_dm_backlight_set_level(dm, 1, 2000); + + KUNIT_EXPECT_EQ(test, dm->brightness[1], 2000U); +} + +/** + * dm_test_backlight_update_status_no_stream - Test update_status wrapper + * @test: The KUnit test context + */ +static void dm_test_backlight_update_status_no_stream(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct backlight_device *bd; + + setup_test_dm_ddev(test, dm); + bd = kunit_kzalloc(test, sizeof(*bd), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bd); + dev_set_drvdata(&bd->dev, dm); + bd->props.brightness = 3456; + dm->num_of_edps = 2; + dm->backlight_dev[1] = bd; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_update_status(bd), 0); + KUNIT_EXPECT_EQ(test, dm->brightness[1], 3456U); +} + +static void setup_test_link_service(struct kunit *test, struct dc_link *link) +{ + struct link_service *link_srv; + struct dc_context *ctx; + struct dc *dc; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; +} + +static int dm_test_get_backlight_level_mid(const struct dc_link *link) +{ + return (0x101 * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) + 1000; +} + +static int dm_test_get_backlight_level_error(const struct dc_link *link) +{ + return DC_ERROR_UNEXPECTED; +} + +static void dm_test_unregister_backlight_device(void *data) +{ + backlight_device_unregister(data); +} + +static bool dm_test_get_backlight_level_nits(struct dc_link *link, + uint32_t *avg, + uint32_t *peak) +{ + *avg = 250000; + *peak = 300000; + + return true; +} + +static bool dm_test_get_backlight_level_nits_fail(struct dc_link *link, + uint32_t *avg, + uint32_t *peak) +{ + return false; +} + +/* Tests for amdgpu_dm_backlight_get_level()/get_brightness() */ + +/** + * dm_test_backlight_get_level_pwm_success - Test PWM brightness readback + * @test: The KUnit test context + */ +static void dm_test_backlight_get_level_pwm_success(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + u32 hw_level = dm_test_get_backlight_level_mid(link); + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_mid; + dm->backlight_link[0] = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), + convert_brightness_to_user(&dm->backlight_caps[0], hw_level)); +} + +/** + * dm_test_backlight_get_level_pwm_error - Test PWM readback fallback + * @test: The KUnit test context + */ +static void dm_test_backlight_get_level_pwm_error(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_error; + dm->brightness[0] = 4321; + dm->backlight_link[0] = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), 4321U); +} + +/** + * dm_test_backlight_get_level_aux_success - Test AUX brightness readback + * @test: The KUnit test context + */ +static void dm_test_backlight_get_level_aux_success(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level_nits = dm_test_get_backlight_level_nits; + dm->backlight_link[0] = link; + caps->caps_valid = true; + caps->aux_support = true; + caps->aux_min_input_signal = 1; + caps->aux_max_input_signal = 512; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), + convert_brightness_to_user(caps, 250000)); +} + +/** + * dm_test_backlight_get_level_aux_error - Test AUX readback fallback + * @test: The KUnit test context + */ +static void dm_test_backlight_get_level_aux_error(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level_nits = dm_test_get_backlight_level_nits_fail; + dm->brightness[0] = 6789; + dm->backlight_link[0] = link; + caps->caps_valid = true; + caps->aux_support = true; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), 6789U); +} + +/** + * dm_test_backlight_get_brightness_uses_device_index - Test get_brightness wrapper + * @test: The KUnit test context + */ +static void dm_test_backlight_get_brightness_uses_device_index(struct kunit *test) +{ + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); + struct dc_link *link = dm_kunit_alloc_link(test); + struct backlight_device *bd; + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_error; + bd = kunit_kzalloc(test, sizeof(*bd), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bd); + dev_set_drvdata(&bd->dev, dm); + dm->num_of_edps = 2; + dm->backlight_dev[1] = bd; + dm->brightness[1] = 2468; + dm->backlight_link[1] = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_brightness(bd), 2468); +} + +/* Tests for amdgpu_dm_register_backlight_device() */ + +/** + * dm_test_register_backlight_device_negative_index - Test invalid index no-op + * @test: The KUnit test context + */ +static void dm_test_register_backlight_device_negative_index(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + aconnector->bl_idx = -1; + + amdgpu_dm_register_backlight_device(aconnector); + KUNIT_EXPECT_NULL(test, adev->dm.backlight_dev[0]); +} + +/** + * dm_test_register_backlight_device_success - Test native backlight registration + * @test: The KUnit test context + * + * A native backlight device must be registered with the calculated + * properties, and its initial readback must retain the initial brightness. + */ +static void dm_test_register_backlight_device_success(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + struct amdgpu_dm_backlight_caps *caps; + struct dc_link *link = dm_kunit_alloc_link(test); + struct drm_minor *primary; + unsigned int max; + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_error; + primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary); + adev->ddev.primary = primary; + adev->dm.backlight_link[0] = link; + + aconnector = dm_kunit_alloc_connector(test, adev, link); + aconnector->bl_idx = 0; + aconnector->base.kdev = adev->ddev.dev; + caps = &adev->dm.backlight_caps[0]; + caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps->ac_level = 50; + caps->dc_level = 25; + max = 0x101 * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + + amdgpu_dm_register_backlight_device(aconnector); + + KUNIT_ASSERT_NOT_NULL(test, adev->dm.backlight_dev[0]); + KUNIT_EXPECT_EQ(test, adev->dm.backlight_dev[0]->props.max_brightness, max); + KUNIT_EXPECT_EQ(test, adev->dm.backlight_dev[0]->props.brightness, + DIV_ROUND_CLOSEST(max * caps->ac_level, 100)); + KUNIT_EXPECT_EQ(test, adev->dm.brightness[0], + adev->dm.backlight_dev[0]->props.brightness); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_unregister_backlight_device, + adev->dm.backlight_dev[0]), 0); +} + +static struct drm_connector *setup_panel_power_savings_connector(struct kunit *test, + struct device **device_out, + struct dm_connector_state **state_out) +{ + struct dm_connector_state *state; + struct drm_connector *connector; + struct amdgpu_device *adev; + struct device *device; + int ret; + + adev = dm_kunit_alloc_adev(test); + ret = drmm_mode_config_init(&adev->ddev); + KUNIT_ASSERT_EQ(test, ret, 0); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + device = kunit_kzalloc(test, sizeof(*device), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, device); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + + connector->dev = &adev->ddev; + connector->state = &state->base; + dev_set_drvdata(device, connector); + *device_out = device; + *state_out = state; + + return connector; +} + +static void dm_test_free_sysfs_buf(void *data) +{ + free_page((unsigned long)data); +} + +static char *dm_test_alloc_sysfs_buf(struct kunit *test) +{ + char *buf; + + buf = (char *)get_zeroed_page(GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, buf); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_sysfs_buf, buf), 0); + + return buf; +} + +/* Tests for panel_power_savings_show()/panel_power_savings_store() */ + +/** + * dm_test_panel_power_savings_show_maps_disable_to_zero - Test show output + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_show_maps_disable_to_zero(struct kunit *test) +{ + struct dm_connector_state *state; + struct device *device; + char *buf; + + setup_panel_power_savings_connector(test, &device, &state); + buf = dm_test_alloc_sysfs_buf(test); + state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + + KUNIT_EXPECT_EQ(test, panel_power_savings_show(device, NULL, buf), 2); + KUNIT_EXPECT_STREQ(test, buf, "0\n"); +} + +/** + * dm_test_panel_power_savings_show_reports_level - Test show output for active level + * @test: The KUnit test context + * + * When abm_level is not the immediate-disable sentinel, show() reports the + * raw level value. + */ +static void dm_test_panel_power_savings_show_reports_level(struct kunit *test) +{ + struct dm_connector_state *state; + struct device *device; + char *buf; + + setup_panel_power_savings_connector(test, &device, &state); + buf = dm_test_alloc_sysfs_buf(test); + state->abm_level = 3; + + KUNIT_EXPECT_EQ(test, panel_power_savings_show(device, NULL, buf), 2); + KUNIT_EXPECT_STREQ(test, buf, "3\n"); +} + +/** + * dm_test_panel_power_savings_store_sets_disable - Test zero maps to disable + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_store_sets_disable(struct kunit *test) +{ + struct dm_connector_state *state; + struct device *device; + size_t count = strlen("0"); + + setup_panel_power_savings_connector(test, &device, &state); + + KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "0", count), + (ssize_t)count); + KUNIT_EXPECT_EQ(test, state->abm_level, ABM_LEVEL_IMMEDIATE_DISABLE); +} + +/** + * dm_test_panel_power_savings_store_forbidden - Test forbidden update + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_store_forbidden(struct kunit *test) +{ + struct dm_connector_state *state; + struct device *device; + + setup_panel_power_savings_connector(test, &device, &state); + state->abm_sysfs_forbidden = true; + + KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "1", 1), -EBUSY); +} + +/** + * dm_test_panel_power_savings_store_rejects_invalid_text - Test parse failure + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_store_rejects_invalid_text(struct kunit *test) +{ + struct drm_connector *connector; + struct drm_device *drm; + struct device *device; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + drm = kunit_kzalloc(test, sizeof(*drm), GFP_KERNEL); + device = kunit_kzalloc(test, sizeof(*device), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, device); + + connector->dev = drm; + dev_set_drvdata(device, connector); + + KUNIT_EXPECT_LT(test, panel_power_savings_store(device, NULL, "bad", 3), 0); +} + +/** + * dm_test_panel_power_savings_store_rejects_out_of_range - Test range failure + * @test: The KUnit test context + */ +static void dm_test_panel_power_savings_store_rejects_out_of_range(struct kunit *test) +{ + struct drm_connector *connector; + struct drm_device *drm; + struct device *device; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + drm = kunit_kzalloc(test, sizeof(*drm), GFP_KERNEL); + device = kunit_kzalloc(test, sizeof(*device), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, device); + + connector->dev = drm; + dev_set_drvdata(device, connector); + + KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "5", 1), -EINVAL); +} + /* Tests for amdgpu_dm_backlight_get_device_index() */ /** @@ -799,8 +1438,8 @@ static void dm_test_backlight_fill_props_ac_linear(struct kunit *test) amdgpu_dm_backlight_fill_props(&caps, true, false, &props); KUNIT_EXPECT_EQ(test, props.brightness, - DIV_ROUND_CLOSEST((max - min) * caps.ac_level, 100)); - KUNIT_EXPECT_EQ(test, props.max_brightness, max - min); + DIV_ROUND_CLOSEST(max * caps.ac_level, 100)); + KUNIT_EXPECT_EQ(test, props.max_brightness, max); KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_LINEAR); KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); } @@ -825,8 +1464,8 @@ static void dm_test_backlight_fill_props_dc_nonlinear(struct kunit *test) amdgpu_dm_backlight_fill_props(&caps, false, true, &props); KUNIT_EXPECT_EQ(test, props.brightness, - DIV_ROUND_CLOSEST((max - min) * caps.dc_level, 100)); - KUNIT_EXPECT_EQ(test, props.max_brightness, max - min); + DIV_ROUND_CLOSEST(max * caps.dc_level, 100)); + KUNIT_EXPECT_EQ(test, props.max_brightness, max); KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_NON_LINEAR); KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); } @@ -1026,6 +1665,7 @@ static void dm_test_should_create_sysfs_no_backlight_index(struct kunit *test) amdgpu_dm_set_abm_level_param(-1); setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.link->panel_type = PANEL_TYPE_LCD; KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1033,10 +1673,13 @@ static void dm_test_should_create_sysfs_no_backlight_index(struct kunit *test) } /** - * dm_test_should_create_sysfs_aux_backlight - Test AUX backlight disables sysfs + * dm_test_should_create_sysfs_oled_no_cacp - Test OLED without CACP disables sysfs * @test: The KUnit test context + * + * A non-LCD panel that does not support CACP must not expose the sysfs + * backlight interface. */ -static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) +static void dm_test_should_create_sysfs_oled_no_cacp(struct kunit *test) { struct dm_backlight_connector_fixture fixture = {}; int saved_abm_level = amdgpu_dm_get_abm_level_param(); @@ -1044,7 +1687,8 @@ static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) amdgpu_dm_set_abm_level_param(-1); setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; - fixture.adev->dm.backlight_caps[0].aux_support = true; + fixture.link->panel_type = PANEL_TYPE_OLED; + fixture.link->panel_config.cacp.cacp_supported = false; KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1052,10 +1696,13 @@ static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) } /** - * dm_test_should_create_sysfs_pwm_backlight - Test PWM backlight enables sysfs + * dm_test_should_create_sysfs_oled_cacp - Test OLED with CACP enables sysfs * @test: The KUnit test context + * + * An OLED panel that supports CACP must expose the sysfs backlight + * interface so the ABM/CACP level can be controlled. */ -static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) +static void dm_test_should_create_sysfs_oled_cacp(struct kunit *test) { struct dm_backlight_connector_fixture fixture = {}; int saved_abm_level = amdgpu_dm_get_abm_level_param(); @@ -1063,7 +1710,27 @@ static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) amdgpu_dm_set_abm_level_param(-1); setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; - fixture.adev->dm.backlight_caps[0].aux_support = false; + fixture.link->panel_type = PANEL_TYPE_OLED; + fixture.link->panel_config.cacp.cacp_supported = true; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_lcd_panel - Test LCD eDP panel enables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_lcd_panel(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.link->panel_type = PANEL_TYPE_LCD; KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1147,11 +1814,13 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) struct dm_backlight_connector_fixture fixture = {}; struct amdgpu_display_manager *dm; int saved_backlight = amdgpu_dm_get_backlight_param(); + int saved_abm_level = amdgpu_dm_get_abm_level_param(); amdgpu_dm_set_backlight_param(-1); + /* Skip ABM property attach (requires full DRM object setup) */ + amdgpu_dm_set_abm_level_param(0); setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); fixture.link->type = dc_connection_single; - /* OLED panel avoids the ABM property attach path */ fixture.link->dpcd_sink_ext_caps.bits.oled = 1; dm = &fixture.adev->dm; dm->adev = fixture.adev; @@ -1166,9 +1835,91 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) KUNIT_EXPECT_TRUE(test, dm->backlight_caps[0].aux_support); amdgpu_dm_set_backlight_param(saved_backlight); + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_setup_backlight_device_attaches_abm_property - Test ABM property path + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_attaches_abm_property(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_display_manager *dm; + struct amdgpu_device *adev; + struct drm_property *prop; + struct dc_link *link; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + int saved_backlight = amdgpu_dm_get_backlight_param(); + int old_count; + int ret; + + amdgpu_dm_set_abm_level_param(-1); + amdgpu_dm_set_backlight_param(-1); + adev = dm_kunit_alloc_adev(test); + ret = drmm_mode_config_init(&adev->ddev); + KUNIT_ASSERT_EQ(test, ret, 0); + + prop = drm_property_create_range(&adev->ddev, 0, "abm level", 0, 4); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prop); + adev->mode_info.abm_level_property = prop; + + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + ret = drmm_connector_init(&adev->ddev, &aconnector->base, + &dm_backlight_test_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + link = dm_kunit_alloc_link(test); + link->connector_signal = SIGNAL_TYPE_EDP; + link->type = dc_connection_single; + aconnector->dc_link = link; + aconnector->bl_idx = -1; + dm = &adev->dm; + dm->adev = adev; + dm->ddev = &adev->ddev; + old_count = aconnector->base.base.properties->count; + + amdgpu_dm_setup_backlight_device(dm, aconnector); + + KUNIT_EXPECT_EQ(test, dm->num_of_edps, 1); + KUNIT_EXPECT_EQ(test, aconnector->bl_idx, 0); + KUNIT_EXPECT_EQ(test, aconnector->base.base.properties->count, old_count + 1); + KUNIT_EXPECT_PTR_EQ(test, aconnector->base.base.properties->properties[old_count], prop); + KUNIT_EXPECT_EQ(test, aconnector->base.base.properties->values[old_count], + (uint64_t)ABM_SYSFS_CONTROL); + + amdgpu_dm_set_backlight_param(saved_backlight); + amdgpu_dm_set_abm_level_param(saved_abm_level); } static struct kunit_case dm_backlight_test_cases[] = { + /* dm_find_stream_with_link */ + KUNIT_CASE(dm_test_find_stream_with_link_returns_match), + KUNIT_CASE(dm_test_find_stream_with_link_missing), + /* amdgpu_dm_backlight_set_level / update_status */ + KUNIT_CASE(dm_test_backlight_set_level_connector_off), + KUNIT_CASE(dm_test_backlight_set_level_no_stream), + KUNIT_CASE(dm_test_backlight_set_level_aux_programs_power_module), + KUNIT_CASE(dm_test_backlight_set_level_pwm_programs_power_module), + KUNIT_CASE(dm_test_backlight_set_level_reallows_idle), + KUNIT_CASE(dm_test_backlight_update_status_no_stream), + /* amdgpu_dm_backlight_get_level / get_brightness */ + KUNIT_CASE(dm_test_backlight_get_level_pwm_success), + KUNIT_CASE(dm_test_backlight_get_level_pwm_error), + KUNIT_CASE(dm_test_backlight_get_level_aux_success), + KUNIT_CASE(dm_test_backlight_get_level_aux_error), + KUNIT_CASE(dm_test_backlight_get_brightness_uses_device_index), + /* amdgpu_dm_register_backlight_device */ + KUNIT_CASE(dm_test_register_backlight_device_negative_index), + KUNIT_CASE(dm_test_register_backlight_device_success), + /* panel_power_savings_show / store */ + KUNIT_CASE(dm_test_panel_power_savings_show_maps_disable_to_zero), + KUNIT_CASE(dm_test_panel_power_savings_show_reports_level), + KUNIT_CASE(dm_test_panel_power_savings_store_sets_disable), + KUNIT_CASE(dm_test_panel_power_savings_store_forbidden), + KUNIT_CASE(dm_test_panel_power_savings_store_rejects_invalid_text), + KUNIT_CASE(dm_test_panel_power_savings_store_rejects_out_of_range), /* amdgpu_dm_backlight_get_device_index */ KUNIT_CASE(dm_test_backlight_device_index_matches_second), KUNIT_CASE(dm_test_backlight_device_index_missing_fallback), @@ -1220,13 +1971,15 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_should_create_sysfs_abm_forced), KUNIT_CASE(dm_test_should_create_sysfs_non_edp), KUNIT_CASE(dm_test_should_create_sysfs_no_backlight_index), - KUNIT_CASE(dm_test_should_create_sysfs_aux_backlight), - KUNIT_CASE(dm_test_should_create_sysfs_pwm_backlight), + KUNIT_CASE(dm_test_should_create_sysfs_oled_no_cacp), + KUNIT_CASE(dm_test_should_create_sysfs_oled_cacp), + KUNIT_CASE(dm_test_should_create_sysfs_lcd_panel), /* amdgpu_dm_setup_backlight_device */ KUNIT_CASE(dm_test_setup_backlight_device_non_edp), KUNIT_CASE(dm_test_setup_backlight_device_connection_none), KUNIT_CASE(dm_test_setup_backlight_device_max_edps), KUNIT_CASE(dm_test_setup_backlight_device_oled_success), + KUNIT_CASE(dm_test_setup_backlight_device_attaches_abm_property), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index d64c7da20f2c..8c2f7ab5f8d3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -7,14 +7,17 @@ #include #include +#include #include #include #include #include "dc.h" +#include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_color.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* ---- Tests for amdgpu_dm_fixpt_from_s3132 ---- */ @@ -1372,6 +1375,146 @@ static void dm_test_set_tf_distributed_points_pq(struct kunit *test) KUNIT_EXPECT_EQ(test, tf->sdr_ref_white_level, 80U); } +/** + * dm_test_set_legacy_tf_identity - Legacy identity LUT uses the sRGB ROM path + * @test: KUnit test context + */ +static void dm_test_set_legacy_tf_identity(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_transfer_func *tf; + int i; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + lut = kunit_kcalloc(test, MAX_COLOR_LEGACY_LUT_ENTRIES, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + for (i = 0; i < MAX_COLOR_LEGACY_LUT_ENTRIES; i++) { + u16 value = i * MAX_DRM_LUT_VALUE / (MAX_COLOR_LEGACY_LUT_ENTRIES - 1); + + lut[i].red = value; + lut[i].green = value; + lut[i].blue = value; + } + + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_SRGB; + + KUNIT_EXPECT_EQ(test, + __set_legacy_tf(tf, lut, MAX_COLOR_LEGACY_LUT_ENTRIES, true), + 0); +} + +/** + * dm_test_set_output_tf_linear - Linear output without a LUT calculates degamma + * @test: KUnit test context + */ +static void dm_test_set_output_tf_linear(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_LINEAR; + + KUNIT_EXPECT_EQ(test, __set_output_tf(tf, NULL, 0, false), 0); +} + +/** + * dm_test_set_output_tf_32_srgb_rom - sRGB output uses the no-LUT ROM path + * @test: KUnit test context + */ +static void dm_test_set_output_tf_32_srgb_rom(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_SRGB; + + KUNIT_EXPECT_EQ(test, __set_output_tf_32(tf, NULL, 0, true), 0); +} + +/** + * dm_test_set_input_tf_srgb - Predefined sRGB input needs no generated curve + * @test: KUnit test context + */ +static void dm_test_set_input_tf_srgb(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_SRGB; + + KUNIT_EXPECT_EQ(test, __set_input_tf(NULL, tf, NULL, 0), 0); +} + +/** + * dm_test_set_input_tf_32_srgb - 32-bit input wrapper accepts predefined sRGB + * @test: KUnit test context + */ +static void dm_test_set_input_tf_32_srgb(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_SRGB; + + KUNIT_EXPECT_EQ(test, __set_input_tf_32(NULL, tf, NULL, 0), 0); +} + +/** + * dm_test_set_transfer_funcs_with_luts - LUT-backed transfer functions succeed + * @test: KUnit test context + */ +static void dm_test_set_transfer_funcs_with_luts(struct kunit *test) +{ + struct drm_color_lut32 *lut32; + struct drm_color_lut *lut; + struct dc_transfer_func *tf; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + lut32 = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, sizeof(*lut32), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut32); + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = TRANSFER_FUNCTION_LINEAR; + KUNIT_EXPECT_EQ(test, + __set_output_tf(tf, lut, MAX_COLOR_LUT_ENTRIES, false), + 0); + + memset(tf, 0, sizeof(*tf)); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = TRANSFER_FUNCTION_LINEAR; + KUNIT_EXPECT_EQ(test, + __set_output_tf_32(tf, lut32, MAX_COLOR_LUT_ENTRIES, false), + 0); + + memset(tf, 0, sizeof(*tf)); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = TRANSFER_FUNCTION_SRGB; + KUNIT_EXPECT_EQ(test, + __set_input_tf(NULL, tf, lut, MAX_COLOR_LUT_ENTRIES), + 0); + + memset(tf, 0, sizeof(*tf)); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = TRANSFER_FUNCTION_SRGB; + KUNIT_EXPECT_EQ(test, + __set_input_tf_32(NULL, tf, lut32, MAX_COLOR_LUT_ENTRIES), + 0); +} + /** * dm_test_set_atomic_regamma_bypass - No LUT and linear TF: must take bypass path * @test: KUnit test context @@ -1392,6 +1535,23 @@ static void dm_test_set_atomic_regamma_bypass(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)out_tf->tf, (int)TRANSFER_FUNCTION_LINEAR); } +/** + * dm_test_set_atomic_regamma_srgb - Non-linear TF enables generated regamma + * @test: KUnit test context + */ +static void dm_test_set_atomic_regamma_srgb(struct kunit *test) +{ + struct dc_transfer_func *out_tf; + + out_tf = kunit_kzalloc(test, sizeof(*out_tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, out_tf); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_atomic_regamma(out_tf, NULL, 0, true, TRANSFER_FUNCTION_SRGB), + 0); + KUNIT_EXPECT_EQ(test, (int)out_tf->type, (int)TF_TYPE_DISTRIBUTED_POINTS); +} + /** * dm_test_atomic_shaper_lut_bypass - No LUT and linear TF: must take bypass path * @test: KUnit test context @@ -1434,6 +1594,28 @@ static void dm_test_atomic_blend_lut_bypass(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)cm->blend_func.tf, (int)TRANSFER_FUNCTION_LINEAR); } +/** + * dm_test_atomic_shaper_blend_srgb - Non-linear TFs enable shaper and blend + * @test: KUnit test context + */ +static void dm_test_atomic_shaper_blend_srgb(struct kunit *test) +{ + struct dc_plane_cm *cm; + + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_atomic_shaper_lut(NULL, true, TRANSFER_FUNCTION_SRGB, 0, cm), + 0); + KUNIT_EXPECT_TRUE(test, cm->flags.bits.shaper_enable); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_atomic_blend_lut(NULL, false, TRANSFER_FUNCTION_SRGB, 0, cm), + 0); + KUNIT_EXPECT_TRUE(test, cm->flags.bits.blend_enable); +} + /* ---- Tests for __set_colorop_in_tf_1d_curve ---- */ /** @@ -1527,6 +1709,1175 @@ static void dm_test_set_colorop_in_tf_1d_curve_bypass(struct kunit *test) (int)TRANSFER_FUNCTION_LINEAR); } +/* ---- Tests for amdgpu_dm_init_color_mod ---- */ + +/** + * dm_test_init_color_mod - Smoke test: must initialize x-points without crashing + * @test: KUnit test context + */ +static void dm_test_init_color_mod(struct kunit *test) +{ + amdgpu_dm_init_color_mod(); + KUNIT_SUCCEED(test); +} + +/* ---- Tests for amdgpu_dm_verify_lut3d_size ---- */ + +/** + * dm_test_verify_lut3d_alloc_plane - Allocate a dm_plane_state for lut3d tests + * @test: KUnit test context + * + * Returns: a drm_plane_state pointer embedded in a zeroed dm_plane_state. + */ +static struct drm_plane_state * +dm_test_verify_lut3d_alloc_plane(struct kunit *test) +{ + struct dm_plane_state *dm_plane_state; + + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + + return &dm_plane_state->base; +} + +/** + * dm_test_verify_lut3d_alloc_adev - Allocate adev with a DC and given 3D LUT cap + * @test: KUnit test context + * @has_3dlut: value to program into caps.color.dpp.hw_3d_lut + * + * Returns: an amdgpu_device with adev->dm.dc allocated. + */ +static struct amdgpu_device * +dm_test_verify_lut3d_alloc_adev(struct kunit *test, bool has_3dlut) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + + adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc); + adev->dm.dc->caps.color.dpp.hw_3d_lut = has_3dlut; + + return adev; +} + +/** + * dm_test_verify_lut3d_no_luts - No shaper/3D LUT blobs: must succeed + * @test: KUnit test context + */ +static void dm_test_verify_lut3d_no_luts(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_verify_lut3d_alloc_adev(test, false); + struct drm_plane_state *plane_state = dm_test_verify_lut3d_alloc_plane(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut3d_size(adev, plane_state), 0); +} + +/** + * dm_test_verify_lut3d_bad_shaper - Shaper LUT with wrong size: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_verify_lut3d_bad_shaper(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_verify_lut3d_alloc_adev(test, true); + struct drm_plane_state *plane_state = dm_test_verify_lut3d_alloc_plane(test); + struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); + + /* has_3dlut => expected shaper size is MAX_COLOR_LUT_ENTRIES */ + dm_plane_state->shaper_lut = dm_test_make_lut_blob(test, 128); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut3d_size(adev, plane_state), -EINVAL); +} + +/** + * dm_test_verify_lut3d_bad_lut3d - 3D LUT with wrong size: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_verify_lut3d_bad_lut3d(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_verify_lut3d_alloc_adev(test, true); + struct drm_plane_state *plane_state = dm_test_verify_lut3d_alloc_plane(test); + struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); + + /* Valid shaper, but wrong 3D LUT size */ + dm_plane_state->shaper_lut = dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + dm_plane_state->lut3d = dm_test_make_lut_blob(test, 128); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut3d_size(adev, plane_state), -EINVAL); +} + +/** + * dm_test_verify_lut3d_valid - Correct shaper and 3D LUT sizes: must succeed + * @test: KUnit test context + */ +static void dm_test_verify_lut3d_valid(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_verify_lut3d_alloc_adev(test, true); + struct drm_plane_state *plane_state = dm_test_verify_lut3d_alloc_plane(test); + struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); + const uint32_t lut3d_entries = + MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE; + + dm_plane_state->shaper_lut = dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + dm_plane_state->lut3d = dm_test_make_lut_blob(test, lut3d_entries); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut3d_size(adev, plane_state), 0); +} + +/* ---- Tests for plane colorop helpers ---- */ + +/** + * struct dm_test_colorop_fixture - shared state for plane colorop walk tests + * @adev: backing amdgpu device (provides a real DRM device) + * @state: fabricated atomic state with a single colorop slot + * @colorop: the colorop under test + * @colorop_state: the new state attached to @colorop + * @plane_state: plane state whose ->state points at @state + * @dc_plane_state: DC plane state written by the helpers + */ +struct dm_test_colorop_fixture { + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct drm_colorop *colorop; + struct drm_colorop_state *colorop_state; + struct drm_plane_state *plane_state; + struct dc_plane_state *dc_plane_state; +}; + +/** + * dm_test_colorop_setup - build a single-colorop atomic state fixture + * @test: KUnit test context + * @type: colorop type to assign + * + * Fabricates a minimal drm_atomic_commit with one colorop slot so that + * for_each_new_colorop_in_state() finds exactly the colorop under test. + * + * Returns: a populated fixture (by value). + */ +static struct dm_test_colorop_fixture +dm_test_colorop_setup(struct kunit *test, enum drm_colorop_type type) +{ + struct dm_test_colorop_fixture f = {0}; + struct __drm_colorops_state *colorops; + struct dm_plane_state *dm_plane_state; + + f.adev = dm_kunit_alloc_adev(test); + f.adev->ddev.mode_config.num_colorop = 1; + + f.colorop = kunit_kzalloc(test, sizeof(*f.colorop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.colorop); + f.colorop->dev = &f.adev->ddev; + f.colorop->type = type; + + f.colorop_state = kunit_kzalloc(test, sizeof(*f.colorop_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.colorop_state); + f.colorop_state->colorop = f.colorop; + + colorops = kunit_kcalloc(test, 1, sizeof(*colorops), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorops); + colorops[0].ptr = f.colorop; + colorops[0].new_state = f.colorop_state; + + f.state = kunit_kzalloc(test, sizeof(*f.state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.state); + f.state->dev = &f.adev->ddev; + f.state->colorops = colorops; + + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + f.plane_state = &dm_plane_state->base; + f.plane_state->state = f.state; + + f.dc_plane_state = kunit_kzalloc(test, sizeof(*f.dc_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.dc_plane_state); + + return f; +} + +/** + * dm_test_colorop_multiplier_applied - Multiplier colorop programs hdr_mult + * @test: KUnit test context + */ +static void dm_test_colorop_multiplier_applied(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_MULTIPLIER); + + /* 1.0 in S31.32 sign-magnitude */ + f.colorop_state->multiplier = 1ULL << 32; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_multiplier(f.plane_state, f.dc_plane_state, f.colorop), + 0); + KUNIT_EXPECT_EQ(test, f.dc_plane_state->hdr_mult.value, (long long)(1ULL << 32)); +} + +/** + * dm_test_colorop_multiplier_no_match - Non-multiplier colorop leaves hdr_mult untouched + * @test: KUnit test context + */ +static void dm_test_colorop_multiplier_no_match(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_1D_CURVE); + + f.colorop_state->multiplier = 1ULL << 32; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_multiplier(f.plane_state, f.dc_plane_state, f.colorop), + 0); + KUNIT_EXPECT_EQ(test, f.dc_plane_state->hdr_mult.value, 0LL); +} + +/** + * dm_test_colorop_3x4_matrix_applied - CTM 3x4 colorop enables gamut remap + * @test: KUnit test context + */ +static void dm_test_colorop_3x4_matrix_applied(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_CTM_3X4); + struct drm_property_blob *blob; + struct drm_color_ctm_3x4 *ctm; + + ctm = kunit_kzalloc(test, sizeof(*ctm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctm); + ctm->matrix[0] = 1ULL << 32; /* identity diagonal */ + ctm->matrix[5] = 1ULL << 32; + ctm->matrix[10] = 1ULL << 32; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->data = ctm; + blob->length = sizeof(struct drm_color_ctm_3x4); + f.colorop_state->data = blob; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_3x4_matrix(f.plane_state, f.dc_plane_state, f.colorop), + 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->gamut_remap_matrix.enable_remap); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->input_csc_color_matrix.enable_adjustment); +} + +/** + * dm_test_colorop_3x4_matrix_bad_length - Wrong blob length: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_colorop_3x4_matrix_bad_length(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_CTM_3X4); + struct drm_property_blob *blob; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->data = kunit_kzalloc(test, 8, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob->data); + blob->length = 7; /* not sizeof(struct drm_color_ctm_3x4) */ + f.colorop_state->data = blob; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_3x4_matrix(f.plane_state, f.dc_plane_state, f.colorop), + -EINVAL); +} + +/** + * dm_test_colorop_degamma_predefined - Degamma 1D curve programs predefined TF + * @test: KUnit test context + */ +static void dm_test_colorop_degamma_predefined(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_1D_CURVE); + + /* SRGB_EOTF is part of amdgpu_dm_supported_degam_tfs */ + f.colorop_state->curve_1d_type = DRM_COLOROP_1D_CURVE_SRGB_EOTF; + f.colorop_state->bypass = false; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_degamma(f.plane_state, f.dc_plane_state, f.colorop), + 0); + KUNIT_EXPECT_EQ(test, + (int)f.dc_plane_state->in_transfer_func.type, + (int)TF_TYPE_PREDEFINED); + KUNIT_EXPECT_EQ(test, + (int)f.dc_plane_state->in_transfer_func.tf, + (int)TRANSFER_FUNCTION_SRGB); +} + +/** + * dm_test_colorop_degamma_no_match - Unsupported degamma curve: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_colorop_degamma_no_match(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_1D_CURVE); + + /* SRGB_INV_EOTF is a shaper TF, not in amdgpu_dm_supported_degam_tfs */ + f.colorop_state->curve_1d_type = DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_degamma(f.plane_state, f.dc_plane_state, f.colorop), + -EINVAL); +} + +/* ---- Tests for CRTC and plane color management update paths ---- */ + +/** + * struct dm_test_color_update_fixture - minimal color update fixture + * @adev: backing amdgpu device + * @state: DRM atomic state with @adev's DRM device + * @crtc_state: DM CRTC state under test + * @stream: DC stream referenced by @crtc_state + * @dm_plane_state: DM plane state under test + * @plane: DRM plane referenced by @dm_plane_state + * @dc_plane_state: DC plane state under test + */ +struct dm_test_color_update_fixture { + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct dm_crtc_state *crtc_state; + struct dc_stream_state *stream; + struct dm_plane_state *dm_plane_state; + struct drm_plane *plane; + struct dc_plane_state *dc_plane_state; +}; + +/** + * dm_test_color_update_setup - allocate a minimal color update fixture + * @test: KUnit test context + * + * Returns: a populated fixture with all large DC/DRM state heap-allocated. + */ +static struct dm_test_color_update_fixture +dm_test_color_update_setup(struct kunit *test) +{ + struct dm_test_color_update_fixture f = {0}; + + f.adev = dm_kunit_alloc_adev(test); + f.adev->dm.dc = kunit_kzalloc(test, sizeof(*f.adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.adev->dm.dc); + + f.state = kunit_kzalloc(test, sizeof(*f.state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.state); + f.state->dev = &f.adev->ddev; + + f.stream = kunit_kzalloc(test, sizeof(*f.stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.stream); + + f.crtc_state = kunit_kzalloc(test, sizeof(*f.crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.crtc_state); + f.crtc_state->base.state = f.state; + f.crtc_state->stream = f.stream; + + f.plane = kunit_kzalloc(test, sizeof(*f.plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.plane); + f.plane->dev = &f.adev->ddev; + + f.dm_plane_state = kunit_kzalloc(test, sizeof(*f.dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.dm_plane_state); + f.dm_plane_state->base.state = f.state; + f.dm_plane_state->base.plane = f.plane; + + f.dc_plane_state = kunit_kzalloc(test, sizeof(*f.dc_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.dc_plane_state); + + return f; +} + +/** + * dm_test_make_ctm_blob - Allocate a fake drm_property_blob for CTM data + * @test: KUnit test context + * @data: CTM data pointer + * @size: CTM data size in bytes + * + * Returns: a fake property blob pointing at @data. + */ +static struct drm_property_blob * +dm_test_make_ctm_blob(struct kunit *test, void *data, size_t size) +{ + struct drm_property_blob *blob; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->data = data; + blob->length = size; + + return blob; +} + +/** + * dm_test_check_crtc_color_mgmt_no_luts - No CRTC LUTs check succeeds + * @test: KUnit test context + */ +static void dm_test_check_crtc_color_mgmt_no_luts(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.crtc_state->cm_has_degamma = true; + f.crtc_state->cm_is_degamma_srgb = true; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_check_crtc_color_mgmt(f.crtc_state, true), 0); + KUNIT_EXPECT_FALSE(test, f.crtc_state->cm_has_degamma); + KUNIT_EXPECT_FALSE(test, f.crtc_state->cm_is_degamma_srgb); +} + +/** + * dm_test_check_crtc_color_mgmt_legacy_regamma - Legacy LUT uses sRGB regamma + * @test: KUnit test context + */ +static void dm_test_check_crtc_color_mgmt_legacy_regamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.crtc_state->base.gamma_lut = dm_test_make_lut_blob(test, MAX_COLOR_LEGACY_LUT_ENTRIES); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_check_crtc_color_mgmt(f.crtc_state, true), 0); + KUNIT_EXPECT_TRUE(test, f.crtc_state->cm_is_degamma_srgb); +} + +/** + * dm_test_check_crtc_color_mgmt_degamma - Atomic degamma is mapped to the plane + * @test: KUnit test context + */ +static void dm_test_check_crtc_color_mgmt_degamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.crtc_state->base.degamma_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_check_crtc_color_mgmt(f.crtc_state, true), 0); + KUNIT_EXPECT_TRUE(test, f.crtc_state->cm_has_degamma); + KUNIT_EXPECT_FALSE(test, f.crtc_state->cm_is_degamma_srgb); +} + +/** + * dm_test_update_crtc_color_mgmt_no_ctm - No CRTC CTM leaves remap bypassed + * @test: KUnit test context + */ +static void dm_test_update_crtc_color_mgmt_no_ctm(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.stream->gamut_remap_matrix.enable_remap = true; + f.stream->csc_color_matrix.enable_adjustment = true; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_update_crtc_color_mgmt(f.crtc_state), 0); + KUNIT_EXPECT_FALSE(test, f.stream->gamut_remap_matrix.enable_remap); + KUNIT_EXPECT_FALSE(test, f.stream->csc_color_matrix.enable_adjustment); +} + +/** + * dm_test_update_crtc_color_mgmt_ctm - CRTC CTM enables stream gamut remap + * @test: KUnit test context + */ +static void dm_test_update_crtc_color_mgmt_ctm(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_color_ctm *ctm; + + ctm = kunit_kzalloc(test, sizeof(*ctm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctm); + ctm->matrix[0] = 1ULL << 32; + ctm->matrix[4] = 1ULL << 32; + ctm->matrix[8] = 1ULL << 32; + f.crtc_state->base.ctm = dm_test_make_ctm_blob(test, ctm, sizeof(*ctm)); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_update_crtc_color_mgmt(f.crtc_state), 0); + KUNIT_EXPECT_TRUE(test, f.stream->gamut_remap_matrix.enable_remap); + KUNIT_EXPECT_FALSE(test, f.stream->csc_color_matrix.enable_adjustment); +} + +/** + * dm_test_update_plane_color_mgmt_bad_lut3d - Bad 3D LUT size fails early + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_bad_lut3d(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->lut3d = dm_test_make_lut_blob(test, 128); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, + f.dc_plane_state), + -EINVAL); +} + +/** + * dm_test_update_plane_color_mgmt_fallback_defaults - Legacy fallback default path + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_fallback_defaults(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_EQ(test, f.dc_plane_state->hdr_mult.value, 0LL); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->gamut_remap_matrix.enable_remap); +} + +/** + * dm_test_update_plane_color_mgmt_maps_crtc_degamma - CRTC implicit degamma path + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_maps_crtc_degamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.crtc_state->cm_is_degamma_srgb = true; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_EQ(test, + (int)f.dc_plane_state->in_transfer_func.type, + (int)TF_TYPE_PREDEFINED); + KUNIT_EXPECT_EQ(test, + (int)f.dc_plane_state->in_transfer_func.tf, + (int)TRANSFER_FUNCTION_SRGB); +} + +/** + * dm_test_update_plane_color_mgmt_maps_crtc_degamma_lut - CRTC LUT maps to plane degamma + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_maps_crtc_degamma_lut(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + int ret; + + f.crtc_state->cm_has_degamma = true; + f.crtc_state->cm_is_degamma_srgb = true; + f.crtc_state->base.degamma_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.tf, + (int)TRANSFER_FUNCTION_SRGB); +} + +/** + * dm_test_update_plane_color_mgmt_maps_bt709_degamma - Video uses BT.709 degamma + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_maps_bt709_degamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + int ret; + + f.crtc_state->cm_is_degamma_srgb = true; + f.dc_plane_state->format = SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.tf, + (int)TRANSFER_FUNCTION_BT709); +} + +/** + * dm_test_update_plane_color_mgmt_plane_degamma_lut - Non-linear plane LUT succeeds + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_plane_degamma_lut(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_color_lut *lut; + int ret; + + f.dm_plane_state->degamma_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + lut = f.dm_plane_state->degamma_lut->data; + lut[0].red = MAX_DRM_LUT_VALUE; + lut[0].green = MAX_DRM_LUT_VALUE; + lut[0].blue = MAX_DRM_LUT_VALUE; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); +} + +/** + * dm_test_update_plane_color_mgmt_rejects_dual_degamma - Reject two degamma stages + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_rejects_dual_degamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_crtc *crtc; + int ret; + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + crtc->dev = &f.adev->ddev; + f.crtc_state->base.crtc = crtc; + f.crtc_state->cm_has_degamma = true; + f.dm_plane_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_update_plane_color_mgmt_uses_color_caps - DC context provides color caps + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_uses_color_caps(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct dc_context *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->dc = f.adev->dm.dc; + f.dc_plane_state->ctx = ctx; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); +} + +/** + * dm_test_update_plane_color_mgmt_legacy_luts - Legacy shaper and blend LUTs succeed + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_legacy_luts(struct kunit *test) +{ + const u32 lut3d_entries = + MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + int ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->shaper_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + f.dm_plane_state->lut3d = dm_test_make_lut_blob(test, lut3d_entries); + f.dm_plane_state->blend_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.blend_enable); +} + +/** + * dm_test_update_plane_color_mgmt_plane_ctm - Plane CTM maps to gamut remap + * @test: KUnit test context + * + * When the plane has a CTM blob, the update path programs the DPP gamut + * remap matrix and enables remapping (and disables the input CSC). + */ +static void dm_test_update_plane_color_mgmt_plane_ctm(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_color_ctm_3x4 *ctm; + + ctm = kunit_kzalloc(test, sizeof(*ctm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctm); + /* Identity-ish entries; values are irrelevant to the branch taken. */ + ctm->matrix[0] = 0x100000000ULL; + ctm->matrix[5] = 0x100000000ULL; + ctm->matrix[10] = 0x100000000ULL; + + f.dm_plane_state->ctm = dm_test_make_ctm_blob(test, ctm, sizeof(*ctm)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->gamut_remap_matrix.enable_remap); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->input_csc_color_matrix.enable_adjustment); +} + +/** + * dm_test_colorop_pipeline_add - append one colorop to a fabricated pipeline + * @test: KUnit test context + * @f: color update fixture that owns the atomic state + * @index: colorop array index to populate + * @type: colorop type + * @curve_1d_type: 1D curve type for DRM_COLOROP_1D_CURVE states + * @bypass: bypass flag for the new colorop state + * + * Returns: the newly allocated colorop. + */ +static struct drm_colorop * +dm_test_colorop_pipeline_add(struct kunit *test, + struct dm_test_color_update_fixture *f, + int index, enum drm_colorop_type type, + enum drm_colorop_curve_1d_type curve_1d_type, + bool bypass) +{ + struct drm_colorop_state *colorop_state; + struct drm_colorop *colorop; + + colorop = kunit_kzalloc(test, sizeof(*colorop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop); + colorop->dev = &f->adev->ddev; + colorop->type = type; + colorop->size = MAX_COLOR_LUT_ENTRIES; + + colorop_state = kunit_kzalloc(test, sizeof(*colorop_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop_state); + colorop_state->colorop = colorop; + colorop_state->curve_1d_type = curve_1d_type; + colorop_state->bypass = bypass; + + f->state->colorops[index].ptr = colorop; + f->state->colorops[index].new_state = colorop_state; + + return colorop; +} + +/** + * dm_test_colorop_pipeline_setup - build a linked colorop pipeline prefix + * @test: KUnit test context + * @f: color update fixture that owns the atomic state + * @types: colorop types to create + * @curves: curve type for each colorop state + * @bypass: bypass flag for each colorop state + * @count: number of colorops to create + * + * Returns: the first colorop in the linked pipeline. + */ +static struct drm_colorop * +dm_test_colorop_pipeline_setup(struct kunit *test, + struct dm_test_color_update_fixture *f, + const enum drm_colorop_type *types, + const enum drm_colorop_curve_1d_type *curves, + const bool *bypass, + int count) +{ + struct drm_colorop **colorops; + int i; + + f->state->colorops = kunit_kcalloc(test, count, sizeof(*f->state->colorops), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f->state->colorops); + f->adev->ddev.mode_config.num_colorop = count; + + colorops = kunit_kcalloc(test, count, sizeof(*colorops), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorops); + + for (i = 0; i < count; i++) + colorops[i] = dm_test_colorop_pipeline_add(test, f, i, types[i], curves[i], bypass[i]); + + for (i = 0; i < count - 1; i++) + colorops[i]->next = colorops[i + 1]; + + return colorops[0]; +} + +/** + * dm_test_update_plane_color_mgmt_colorop_bypass_pipeline - bypassed pipeline succeeds + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_bypass_pipeline(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, true, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, ARRAY_SIZE(types)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_missing_multiplier - missing second op falls back + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_missing_multiplier(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { DRM_COLOROP_1D_CURVE }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { true }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, ARRAY_SIZE(types)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_missing_3x4 - missing third op falls back + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_missing_3x4(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { true, true }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, ARRAY_SIZE(types)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_truncated - truncated pipelines fall back + * @test: KUnit test context + * + * Covers each pipeline exit after the 3x4 matrix while all present colorops + * remain bypassed, avoiding the floating-point color calculation paths. + */ +static void dm_test_update_plane_color_mgmt_colorop_truncated(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct dc_plane_state *dc_plane_state = f.dc_plane_state; + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct dm_crtc_state *crtc_state = f.crtc_state; + int count, ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + + for (count = 3; count <= ARRAY_SIZE(types); count++) { + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, count); + memset(f.dc_plane_state, 0, sizeof(*f.dc_plane_state)); + + ret = amdgpu_dm_update_plane_color_mgmt(crtc_state, plane_state, dc_plane_state); + KUNIT_EXPECT_EQ_MSG(test, ret, 0, "pipeline length %d should fall back", count); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); + } +} + +/** + * dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw - no 3D LUT skips 3D ops + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, false, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = false; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, + f.dc_plane_state), + 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_shaper - enabled shaper TF and LUT succeed + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_shaper(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, false, false, true, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_colorop_state *shaper_lut_state; + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_property_blob *blob; + int ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + shaper_lut_state = f.state->colorops[4].new_state; + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->length = MAX_COLOR_LUT_ENTRIES * sizeof(struct drm_color_lut32); + blob->data = kunit_kzalloc(test, blob->length, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob->data); + shaper_lut_state->data = blob; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.shaper_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_3dlut - enabled 3D LUT succeeds + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_3dlut(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, false, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_colorop_state *lut3d_state; + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_property_blob *blob; + int ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + lut3d_state = f.state->colorops[5].new_state; + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->length = 5 * sizeof(struct drm_color_lut32); + blob->data = kunit_kzalloc(test, blob->length, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob->data); + lut3d_state->data = blob; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.shaper_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.shaper_func.tf, + (int)TRANSFER_FUNCTION_LINEAR); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_3dlut_no_data - empty 3D LUT falls back + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_3dlut_no_data(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, false, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + int ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_blend - enabled blend TF and LUT succeed + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_blend(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { true, true, true, false, false }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_colorop_state *blend_lut_state; + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_property_blob *blob; + int ret; + + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + blend_lut_state = f.state->colorops[4].new_state; + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->length = MAX_COLOR_LUT_ENTRIES * sizeof(struct drm_color_lut32); + blob->data = kunit_kzalloc(test, blob->length, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob->data); + blend_lut_state->data = blob; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.blend_enable); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.blend_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); +} + static struct kunit_case dm_color_test_cases[] = { /* amdgpu_dm_fixpt_from_s3132 */ KUNIT_CASE(dm_test_fixpt_from_s3132_zero), @@ -1614,16 +2965,66 @@ static struct kunit_case dm_color_test_cases[] = { /* __set_tf_distributed_points */ KUNIT_CASE(dm_test_set_tf_distributed_points_srgb), KUNIT_CASE(dm_test_set_tf_distributed_points_pq), + /* Transfer-function calculation helpers */ + KUNIT_CASE(dm_test_set_legacy_tf_identity), + KUNIT_CASE(dm_test_set_output_tf_linear), + KUNIT_CASE(dm_test_set_output_tf_32_srgb_rom), + KUNIT_CASE(dm_test_set_input_tf_srgb), + KUNIT_CASE(dm_test_set_input_tf_32_srgb), + KUNIT_CASE(dm_test_set_transfer_funcs_with_luts), /* amdgpu_dm_set_atomic_regamma */ KUNIT_CASE(dm_test_set_atomic_regamma_bypass), + KUNIT_CASE(dm_test_set_atomic_regamma_srgb), /* amdgpu_dm_atomic_shaper_lut */ KUNIT_CASE(dm_test_atomic_shaper_lut_bypass), /* amdgpu_dm_atomic_blend_lut */ KUNIT_CASE(dm_test_atomic_blend_lut_bypass), + KUNIT_CASE(dm_test_atomic_shaper_blend_srgb), /* __set_colorop_in_tf_1d_curve */ KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_invalid_type), KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_unsupported_curve), KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_bypass), + /* amdgpu_dm_init_color_mod */ + KUNIT_CASE(dm_test_init_color_mod), + /* amdgpu_dm_verify_lut3d_size */ + KUNIT_CASE(dm_test_verify_lut3d_no_luts), + KUNIT_CASE(dm_test_verify_lut3d_bad_shaper), + KUNIT_CASE(dm_test_verify_lut3d_bad_lut3d), + KUNIT_CASE(dm_test_verify_lut3d_valid), + /* __set_dm_plane_colorop_multiplier */ + KUNIT_CASE(dm_test_colorop_multiplier_applied), + KUNIT_CASE(dm_test_colorop_multiplier_no_match), + /* __set_dm_plane_colorop_3x4_matrix */ + KUNIT_CASE(dm_test_colorop_3x4_matrix_applied), + KUNIT_CASE(dm_test_colorop_3x4_matrix_bad_length), + /* __set_dm_plane_colorop_degamma */ + KUNIT_CASE(dm_test_colorop_degamma_predefined), + KUNIT_CASE(dm_test_colorop_degamma_no_match), + /* CRTC and plane color management update paths */ + KUNIT_CASE(dm_test_check_crtc_color_mgmt_no_luts), + KUNIT_CASE(dm_test_check_crtc_color_mgmt_legacy_regamma), + KUNIT_CASE(dm_test_check_crtc_color_mgmt_degamma), + KUNIT_CASE(dm_test_update_crtc_color_mgmt_no_ctm), + KUNIT_CASE(dm_test_update_crtc_color_mgmt_ctm), + KUNIT_CASE(dm_test_update_plane_color_mgmt_bad_lut3d), + KUNIT_CASE(dm_test_update_plane_color_mgmt_fallback_defaults), + KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_crtc_degamma), + KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_crtc_degamma_lut), + KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_bt709_degamma), + KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_degamma_lut), + KUNIT_CASE(dm_test_update_plane_color_mgmt_rejects_dual_degamma), + KUNIT_CASE(dm_test_update_plane_color_mgmt_uses_color_caps), + KUNIT_CASE(dm_test_update_plane_color_mgmt_legacy_luts), + KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_ctm), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_bypass_pipeline), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_multiplier), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_3x4), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_truncated), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_shaper), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_3dlut), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_3dlut_no_data), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_blend), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index aa451064b30c..1a8f02c78c4d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -6,11 +6,19 @@ */ #include +#include #include #include +#include #include +#include #include +#include +#include +#include +#include #include +#include #include "dc.h" #include "amdgpu.h" @@ -20,6 +28,7 @@ #include "amdgpu_dm_connector.h" #include "amdgpu_dm_backlight.h" #include "include/grph_object_id.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* Tests for get_subconnector_type() */ @@ -290,6 +299,30 @@ static void dm_test_adjust_colour_depth_420_halves_clk(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); } +/** + * dm_test_adjust_colour_depth_420_reduces - Test Adjust colour depth 420 reduces + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_420_reduces(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* 4K @ 594000 KHz = 5940000 in 100Hz units */ + timing.pix_clk_100hz = 5940000; + timing.display_color_depth = COLOR_DEPTH_121212; + timing.pixel_encoding = PIXEL_ENCODING_YCBCR420; + /* + * With 420: effective = 594000/2 = 297000. + * 12bpc = 297000*36/24 = 445500 (exceeds limit), + * 10bpc = 297000*30/24 = 371250 (fits). + */ + info.max_tmds_clock = 400000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); +} + /** * dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc - Test Adjust colour * depth reduces 12bpc to 10bpc @@ -550,6 +583,78 @@ static void dm_test_output_color_space_bt2020_ycc(struct kunit *test) (int)COLOR_SPACE_2020_YCBCR_LIMITED); } +/** + * dm_test_output_color_space_default_ycbcr709_y_only - Test Output color space + * default ycbcr709 limited via Y_ONLY at high pixel clock + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_ycbcr709_y_only(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + timing.pix_clk_100hz = 300000; + timing.flags.Y_ONLY = 1; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR709_LIMITED); +} + +/** + * dm_test_output_color_space_default_ycbcr601 - Test Output color space default + * ycbcr601 full range at low pixel clock + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_ycbcr601(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + timing.pix_clk_100hz = 270300; + timing.flags.Y_ONLY = 0; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR601); +} + +/** + * dm_test_output_color_space_bt2020_ycc_rgb_encoding - Test Output color space + * bt2020 ycc with rgb pixel encoding falls back to full range rgb + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_ycc_rgb_encoding(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_FULLRANGE); +} + +/** + * dm_test_output_color_space_bt2020_rgb_ycc_encoding - Test Output color space + * bt2020 rgb with non-rgb pixel encoding falls back to limited ycbcr + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_rgb_ycc_encoding(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_YCBCR_LIMITED); +} + /* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc() */ /** @@ -834,6 +939,17 @@ static void dm_test_to_connector_type_dvi_dvid(struct kunit *test) KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVID); } +/** + * dm_test_to_connector_type_dual_link_dvid - Test To connector type dual link dvid + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dual_link_dvid(struct kunit *test) +{ + int type = to_drm_connector_type(SIGNAL_TYPE_DVI_DUAL_LINK, CONNECTOR_ID_DUAL_LINK_DVID); + + KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVID); +} + /** * dm_test_to_connector_type_virtual - Test To connector type virtual * @test: The KUnit test context @@ -1124,6 +1240,76 @@ static void dm_test_aspect_ratio_256_135(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_256_135); } +/* Tests for copy_crtc_timing_for_drm_display_mode() */ + +/** + * dm_test_copy_crtc_timing_copies_all_fields - Test all crtc timing fields copied + * @test: The KUnit test context + */ +static void dm_test_copy_crtc_timing_copies_all_fields(struct kunit *test) +{ + struct drm_display_mode src = {}; + struct drm_display_mode dst = {}; + + src.crtc_hdisplay = 1920; + src.crtc_vdisplay = 1080; + src.crtc_clock = 148500; + src.crtc_hblank_start = 1920; + src.crtc_hblank_end = 2200; + src.crtc_hsync_start = 2008; + src.crtc_hsync_end = 2052; + src.crtc_htotal = 2200; + src.crtc_hskew = 1; + src.crtc_vblank_start = 1080; + src.crtc_vblank_end = 1125; + src.crtc_vsync_start = 1084; + src.crtc_vsync_end = 1089; + src.crtc_vtotal = 1125; + + copy_crtc_timing_for_drm_display_mode(&src, &dst); + + KUNIT_EXPECT_EQ(test, dst.crtc_hdisplay, 1920); + KUNIT_EXPECT_EQ(test, dst.crtc_vdisplay, 1080); + KUNIT_EXPECT_EQ(test, dst.crtc_clock, 148500); + KUNIT_EXPECT_EQ(test, dst.crtc_hblank_start, 1920); + KUNIT_EXPECT_EQ(test, dst.crtc_hblank_end, 2200); + KUNIT_EXPECT_EQ(test, dst.crtc_hsync_start, 2008); + KUNIT_EXPECT_EQ(test, dst.crtc_hsync_end, 2052); + KUNIT_EXPECT_EQ(test, dst.crtc_htotal, 2200); + KUNIT_EXPECT_EQ(test, dst.crtc_hskew, 1); + KUNIT_EXPECT_EQ(test, dst.crtc_vblank_start, 1080); + KUNIT_EXPECT_EQ(test, dst.crtc_vblank_end, 1125); + KUNIT_EXPECT_EQ(test, dst.crtc_vsync_start, 1084); + KUNIT_EXPECT_EQ(test, dst.crtc_vsync_end, 1089); + KUNIT_EXPECT_EQ(test, dst.crtc_vtotal, 1125); +} + +/** + * dm_test_copy_crtc_timing_leaves_non_crtc_fields - Test non-crtc fields untouched + * @test: The KUnit test context + */ +static void dm_test_copy_crtc_timing_leaves_non_crtc_fields(struct kunit *test) +{ + struct drm_display_mode src = {}; + struct drm_display_mode dst = {}; + + src.crtc_hdisplay = 1280; + src.crtc_vdisplay = 720; + + /* Non-crtc geometry on dst must be preserved by the copy */ + dst.hdisplay = 1920; + dst.vdisplay = 1080; + dst.clock = 148500; + + copy_crtc_timing_for_drm_display_mode(&src, &dst); + + KUNIT_EXPECT_EQ(test, dst.crtc_hdisplay, 1280); + KUNIT_EXPECT_EQ(test, dst.crtc_vdisplay, 720); + KUNIT_EXPECT_EQ(test, dst.hdisplay, 1920); + KUNIT_EXPECT_EQ(test, dst.vdisplay, 1080); + KUNIT_EXPECT_EQ(test, dst.clock, 148500); +} + /* Tests for decide_crtc_timing_for_drm_display_mode() */ /** @@ -1268,7 +1454,7 @@ static void dm_test_funcs_reset_sets_defaults(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_test_connector_funcs, @@ -1307,7 +1493,7 @@ static void dm_test_funcs_reset_edp_abm_level(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_test_connector_funcs, @@ -1345,7 +1531,7 @@ static void dm_test_funcs_reset_edp_abm_disabled(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_test_connector_funcs, @@ -1387,7 +1573,7 @@ static void dm_test_atomic_dup_state_copies_fields(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_test_connector_funcs, @@ -2015,6 +2201,3067 @@ static void dm_test_is_freesync_video_mode_no_match(struct kunit *test) KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector)); } +/* Tests for amdgpu_dm_update_cacp_caps() */ + +struct dm_cacp_fixture { + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; +}; + +static void setup_cacp_fixture(struct kunit *test, + struct dm_cacp_fixture *fixture, + enum signal_type signal, + enum dc_panel_type panel_type) +{ + fixture->adev = kunit_kzalloc(test, sizeof(*fixture->adev), GFP_KERNEL); + fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), + GFP_KERNEL); + fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link); + + fixture->aconnector->dc_link = fixture->link; + fixture->aconnector->base.dev = &fixture->adev->ddev; + fixture->link->connector_signal = signal; + fixture->link->panel_type = panel_type; +} + +/** + * dm_test_cacp_caps_unsupported_ip - Test CACP disabled on old DCE IP + * @test: The KUnit test context + * + * A DCE IP version below 3.1.4 does not support CACP, so cacp_supported + * must remain false regardless of signal or panel type. + */ +static void dm_test_cacp_caps_unsupported_ip(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 2); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_excluded_ip_316 - Test CACP disabled on DCE IP 3.1.6 + * @test: The KUnit test context + * + * DCE IP version 3.1.6 is explicitly excluded from CACP support. + */ +static void dm_test_cacp_caps_excluded_ip_316(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 6); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_edp_oled_supported - Test CACP enabled on eDP OLED + * @test: The KUnit test context + * + * A supported DCE IP version on an eDP OLED panel must enable CACP. + */ +static void dm_test_cacp_caps_edp_oled_supported(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lvds_oled_supported - Test CACP enabled on LVDS OLED + * @test: The KUnit test context + * + * LVDS is an accepted connector signal for CACP support. + */ +static void dm_test_cacp_caps_lvds_oled_supported(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_LVDS, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_non_edp_signal - Test CACP disabled on non-eDP/LVDS signal + * @test: The KUnit test context + * + * External DisplayPort is neither eDP nor LVDS, so CACP must be disabled. + */ +static void dm_test_cacp_caps_non_edp_signal(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_DISPLAY_PORT, + PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lcd_panel - Test CACP disabled on LCD panel + * @test: The KUnit test context + * + * Plain LCD panels do not benefit from CACP, so it must be disabled even + * on a supported IP version and eDP signal. + */ +static void dm_test_cacp_caps_lcd_panel(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_LCD); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/* Tests for amdgpu_dm_set_panel_type() */ + +struct dm_panel_type_fixture { + struct amdgpu_device *adev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + struct dc_sink *sink; +}; + +static void setup_panel_type_fixture(struct kunit *test, + struct dm_panel_type_fixture *fixture) +{ + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + fixture->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*fixture->adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->drm); + fixture->adev = drm_to_adev(fixture->drm); + + fixture->aconnector = drmm_kzalloc(fixture->drm, sizeof(*fixture->aconnector), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector); + fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link); + fixture->sink = kunit_kzalloc(test, sizeof(*fixture->sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->sink); + + fixture->aconnector->dc_link = fixture->link; + drmm_connector_init(fixture->drm, &fixture->aconnector->base, + &dm_test_connector_funcs, DRM_MODE_CONNECTOR_eDP, + NULL); +} + +/** + * dm_test_set_panel_type_vsdb_oled - Test VSDB OLED maps to PANEL_TYPE_OLED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_vsdb_oled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.amd_vsdb.panel_type = + AMD_VSDB_PANEL_TYPE_OLED; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_OLED); +} + +/** + * dm_test_set_panel_type_vsdb_miniled - Test VSDB MINILED maps to PANEL_TYPE_MINILED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_vsdb_miniled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.amd_vsdb.panel_type = + AMD_VSDB_PANEL_TYPE_MINILED; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_dpcd_oled - Test DPCD oled bit maps to PANEL_TYPE_OLED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_dpcd_oled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.link->dpcd_sink_ext_caps.bits.oled = 1; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_OLED); +} + +/** + * dm_test_set_panel_type_dpcd_miniled - Test DPCD miniled bit maps to PANEL_TYPE_MINILED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_dpcd_miniled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.link->dpcd_sink_ext_caps.bits.miniled = 1; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_did_oled - Test DID OLED maps to PANEL_TYPE_OLED + * @test: The KUnit test context + * + * When VSDB and DPCD do not identify the panel, a DID panel type of + * DRM_MODE_PANEL_TYPE_OLED must map to PANEL_TYPE_OLED. + */ +static void dm_test_set_panel_type_did_oled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.panel_type = + DRM_MODE_PANEL_TYPE_OLED; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_OLED); +} + +/** + * dm_test_set_panel_type_did_lcd - Test DID LCD maps to PANEL_TYPE_LCD + * @test: The KUnit test context + * + * When VSDB and DPCD do not identify the panel, a DID panel type of + * DRM_MODE_PANEL_TYPE_LCD must map to PANEL_TYPE_LCD. + */ +static void dm_test_set_panel_type_did_lcd(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.panel_type = + DRM_MODE_PANEL_TYPE_LCD; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_LCD); +} + +/** + * dm_test_set_panel_type_vendor_lum_heuristic - Test vendor luminance heuristic maps to MINILED + * @test: The KUnit test context + * + * A panel from the specific vendor whose first luminance range is at least + * 1.5x the second is treated as a mini-LED panel. + */ +static void dm_test_set_panel_type_vendor_lum_heuristic(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + struct drm_amd_vsdb_info *vsdb; + + setup_panel_type_fixture(test, &fixture); + fixture.link->local_sink = fixture.sink; + fixture.sink->edid_caps.manufacturer_id = DDC_MANUFACTURERNAME_SAMSUNG; + + vsdb = &fixture.aconnector->base.display_info.amd_vsdb; + vsdb->version = 1; + vsdb->luminance_range1.max_luminance = 3000; + vsdb->luminance_range2.max_luminance = 1000; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_defaults_to_lcd - Test undetermined panel defaults to LCD + * @test: The KUnit test context + * + * When no source identifies the panel, the type now defaults to + * PANEL_TYPE_LCD instead of remaining PANEL_TYPE_NONE. + */ +static void dm_test_set_panel_type_defaults_to_lcd(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_LCD); +} + +/* Tests for update_subconnector_property() */ + +/** + * dm_test_update_subconnector_dp_with_sink - Test subconnector property is set + * from the dongle type for a DisplayPort connector with a sink + * @test: The KUnit test context + */ +static void dm_test_update_subconnector_dp_with_sink(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + uint64_t val = 0; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + link = drmm_kzalloc(drm, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + drmm_connector_init(drm, &aconnector->base, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL); + drm_connector_attach_dp_subconnector_property(&aconnector->base); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER; + aconnector->dc_link = link; + /* Any non-NULL sink enables dongle-type resolution */ + aconnector->dc_sink = kunit_kzalloc(test, sizeof(*aconnector->dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector->dc_sink); + + update_subconnector_property(aconnector); + + KUNIT_EXPECT_EQ(test, drm_object_property_get_value(&aconnector->base.base, + aconnector->base.dev->mode_config.dp_subconnector_property, + &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SUBCONNECTOR_VGA); +} + +/** + * dm_test_update_subconnector_dp_no_sink - Test subconnector property stays + * unknown for a DisplayPort connector without a sink + * @test: The KUnit test context + */ +static void dm_test_update_subconnector_dp_no_sink(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + uint64_t val = 0; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + link = drmm_kzalloc(drm, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + drmm_connector_init(drm, &aconnector->base, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL); + drm_connector_attach_dp_subconnector_property(&aconnector->base); + + /* Dongle type is set, but no sink means it must not be consulted */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + aconnector->dc_link = link; + aconnector->dc_sink = NULL; + + update_subconnector_property(aconnector); + + KUNIT_EXPECT_EQ(test, drm_object_property_get_value(&aconnector->base.base, + aconnector->base.dev->mode_config.dp_subconnector_property, + &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SUBCONNECTOR_Unknown); +} + +/** + * dm_test_update_subconnector_non_dp_noop - Test non-DisplayPort connector is + * left untouched (early return) + * @test: The KUnit test context + */ +static void dm_test_update_subconnector_non_dp_noop(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + uint64_t val = 0; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + link = drmm_kzalloc(drm, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + drmm_connector_init(drm, &aconnector->base, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, NULL); + drm_connector_attach_dp_subconnector_property(&aconnector->base); + + /* Pre-seed the property to a non-default value */ + drm_object_property_set_value(&aconnector->base.base, + aconnector->base.dev->mode_config.dp_subconnector_property, + DRM_MODE_SUBCONNECTOR_VGA); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + aconnector->dc_link = link; + aconnector->dc_sink = drmm_kzalloc(drm, sizeof(*aconnector->dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector->dc_sink); + + update_subconnector_property(aconnector); + + /* Non-DP connector: value must remain what we seeded */ + KUNIT_EXPECT_EQ(test, drm_object_property_get_value(&aconnector->base.base, + aconnector->base.dev->mode_config.dp_subconnector_property, + &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SUBCONNECTOR_VGA); +} + +/* Tests for amdgpu_dm_fbc_init() */ + +/* + * Build an amdgpu_dm_connector wired to a kunit-allocated amdgpu_device so + * that drm_to_adev() and to_amdgpu_dm_connector() resolve correctly, with a + * dc, dc_link and an empty modes list ready for amdgpu_dm_fbc_init(). + */ +struct dm_test_fbc_ctx { + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc *dc; + struct dc_link *link; +}; + +static struct dm_test_fbc_ctx *dm_test_fbc_ctx_alloc(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->adev = kunit_kzalloc(test, sizeof(*ctx->adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->adev); + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc); + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + + ctx->aconnector->base.dev = &ctx->adev->ddev; + INIT_LIST_HEAD(&ctx->aconnector->base.modes); + ctx->adev->dm.dc = ctx->dc; + ctx->aconnector->dc_link = ctx->link; + + /* Default to the fully-enabled path so each test only flips one knob */ + ctx->link->connector_signal = SIGNAL_TYPE_EDP; + ctx->dc->fbc_compressor = + (struct compressor *)kunit_kzalloc(test, sizeof(void *), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc->fbc_compressor); + + return ctx; +} + +/** + * dm_test_fbc_init_no_compressor - Test fbc_init is a no-op without a compressor + * @test: The KUnit test context + */ +static void dm_test_fbc_init_no_compressor(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx = dm_test_fbc_ctx_alloc(test); + + ctx->dc->fbc_compressor = NULL; + + amdgpu_dm_fbc_init(&ctx->aconnector->base); + + KUNIT_EXPECT_NULL(test, ctx->adev->dm.compressor.bo_ptr); +} + +/** + * dm_test_fbc_init_non_edp - Test fbc_init is a no-op for non-eDP links + * @test: The KUnit test context + */ +static void dm_test_fbc_init_non_edp(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx = dm_test_fbc_ctx_alloc(test); + + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + amdgpu_dm_fbc_init(&ctx->aconnector->base); + + KUNIT_EXPECT_NULL(test, ctx->adev->dm.compressor.bo_ptr); +} + +/** + * dm_test_fbc_init_already_allocated - Test fbc_init keeps an existing buffer + * @test: The KUnit test context + */ +static void dm_test_fbc_init_already_allocated(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx = dm_test_fbc_ctx_alloc(test); + struct amdgpu_bo *existing; + + existing = kunit_kzalloc(test, sizeof(void *), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, existing); + ctx->adev->dm.compressor.bo_ptr = existing; + + amdgpu_dm_fbc_init(&ctx->aconnector->base); + + /* Buffer already present → left untouched, no reallocation */ + KUNIT_EXPECT_PTR_EQ(test, ctx->adev->dm.compressor.bo_ptr, existing); +} + +/** + * dm_test_fbc_init_no_modes - Test fbc_init skips allocation with no modes + * @test: The KUnit test context + */ +static void dm_test_fbc_init_no_modes(struct kunit *test) +{ + struct dm_test_fbc_ctx *ctx = dm_test_fbc_ctx_alloc(test); + + /* All prerequisites met but the modes list is empty → max_size 0 */ + amdgpu_dm_fbc_init(&ctx->aconnector->base); + + KUNIT_EXPECT_NULL(test, ctx->adev->dm.compressor.bo_ptr); +} + +/* Tests for amdgpu_dm_detect_mst_link_for_all_connectors() */ + +/* Allocate a bare drm_device suitable for registering connectors against. */ +static struct drm_device *dm_test_alloc_drm(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + return drm; +} + +/* + * Allocate an amdgpu_dm_connector and register its embedded drm_connector with + * @drm so that drm_for_each_connector_iter() and to_amdgpu_dm_connector() both + * resolve to it. + */ +static struct amdgpu_dm_connector *dm_test_add_connector(struct kunit *test, + struct drm_device *drm, int connector_type) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + KUNIT_ASSERT_EQ(test, + drmm_connector_init(drm, &aconnector->base, + &dm_test_connector_funcs, connector_type, + NULL), 0); + + return aconnector; +} + +/** + * dm_test_detect_mst_no_connectors - Test the no-op path on an empty device + * @test: The KUnit test context + */ +static void dm_test_detect_mst_no_connectors(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + + /* No connectors registered → iteration body never runs */ + KUNIT_EXPECT_EQ(test, + amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); +} + +/** + * dm_test_detect_mst_skips_writeback - Test writeback connectors are skipped + * @test: The KUnit test context + * + * A writeback connector is hit by the early ``continue`` before its dc_link is + * ever dereferenced, so leaving dc_link NULL must not crash. + */ +static void dm_test_detect_mst_skips_writeback(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_test_add_connector(test, drm, + DRM_MODE_CONNECTOR_WRITEBACK); + /* dc_link intentionally left NULL: it must not be touched */ + aconnector->dc_link = NULL; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); +} + +/** + * dm_test_detect_mst_non_mst_link - Test a non-MST link starts no topology + * @test: The KUnit test context + */ +static void dm_test_detect_mst_non_mst_link(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + aconnector = dm_test_add_connector(test, drm, + DRM_MODE_CONNECTOR_DisplayPort); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Not an MST branch → the topology manager is never started */ + link->type = dc_connection_single; + aconnector->dc_link = link; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); +} + +/** + * dm_test_detect_mst_branch_without_aux - Test an MST branch with no aux is + * skipped + * @test: The KUnit test context + * + * The condition short-circuits on a NULL mst_mgr.aux, so the real + * drm_dp_mst_topology_mgr_set_mst() path is never reached. + */ +static void dm_test_detect_mst_branch_without_aux(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + aconnector = dm_test_add_connector(test, drm, + DRM_MODE_CONNECTOR_DisplayPort); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + link->type = dc_connection_mst_branch; + aconnector->dc_link = link; + /* mst_mgr.aux is NULL (kzalloc) → second half of the && is false */ + KUNIT_ASSERT_NULL(test, aconnector->mst_mgr.aux); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_detect_mst_link_for_all_connectors(drm), 0); +} + +/* Tests for amdgpu_dm_find_first_crtc_matching_connector() */ + +/* + * Build a minimal drm_atomic_commit holding @count connector slots. The + * function under test only reads num_connector, connectors[i].ptr and + * connectors[i].new_state, so a hand-rolled state is sufficient. + */ +static struct drm_atomic_commit * +dm_test_alloc_atomic_state(struct kunit *test, int count) +{ + struct drm_atomic_commit *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->num_connector = count; + if (count) { + state->connectors = kunit_kcalloc(test, count, + sizeof(*state->connectors), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->connectors); + } + + return state; +} + +/** + * dm_test_find_first_crtc_match - Test find_first_crtc returns matching connector + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_match(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 1); + struct drm_connector *connector; + struct drm_connector_state *con_state; + struct drm_crtc *crtc; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + con_state = kunit_kzalloc(test, sizeof(*con_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, con_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + con_state->crtc = crtc; + state->connectors[0].ptr = connector; + state->connectors[0].new_state = con_state; + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc), + connector); +} + +/** + * dm_test_find_first_crtc_no_match - Test find_first_crtc returns NULL when no crtc matches + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_no_match(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 1); + struct drm_connector *connector; + struct drm_connector_state *con_state; + struct drm_crtc *crtc; + struct drm_crtc *other_crtc; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + con_state = kunit_kzalloc(test, sizeof(*con_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, con_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + other_crtc = kunit_kzalloc(test, sizeof(*other_crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, other_crtc); + + con_state->crtc = other_crtc; + state->connectors[0].ptr = connector; + state->connectors[0].new_state = con_state; + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc)); +} + +/** + * dm_test_find_first_crtc_empty_state - Test find_first_crtc returns NULL with no connectors + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_empty_state(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 0); + struct drm_crtc *crtc; + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc)); +} + +/** + * dm_test_find_first_crtc_skips_null_ptr - Test find_first_crtc skips empty connector slots + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_skips_null_ptr(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 2); + struct drm_connector *connector; + struct drm_connector_state *con_state; + struct drm_crtc *crtc; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + con_state = kunit_kzalloc(test, sizeof(*con_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, con_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + /* Slot 0 has no connector (ptr == NULL) and must be skipped. */ + con_state->crtc = crtc; + state->connectors[1].ptr = connector; + state->connectors[1].new_state = con_state; + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc), + connector); +} + +/** + * dm_test_find_first_crtc_returns_first - Test find_first_crtc returns the first match + * @test: The KUnit test context + */ +static void dm_test_find_first_crtc_returns_first(struct kunit *test) +{ + struct drm_atomic_commit *state = dm_test_alloc_atomic_state(test, 2); + struct drm_connector *first; + struct drm_connector *second; + struct drm_connector_state *first_state; + struct drm_connector_state *second_state; + struct drm_crtc *crtc; + + first = kunit_kzalloc(test, sizeof(*first), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, first); + second = kunit_kzalloc(test, sizeof(*second), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, second); + first_state = kunit_kzalloc(test, sizeof(*first_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, first_state); + second_state = kunit_kzalloc(test, sizeof(*second_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, second_state); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + /* Both connectors target the same crtc; the first one must win. */ + first_state->crtc = crtc; + second_state->crtc = crtc; + state->connectors[0].ptr = first; + state->connectors[0].new_state = first_state; + state->connectors[1].ptr = second; + state->connectors[1].new_state = second_state; + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_find_first_crtc_matching_connector(state, crtc), + first); +} + +/* Tests for amdgpu_dm_set_panel_type() */ + +/* + * Build an amdgpu_dm_connector registered against a real kunit drm_device that + * is embedded in an amdgpu_device, so drm_to_adev()/adev_to_drm() resolve and + * the panel_type property can be created, attached and updated for real. + */ +struct dm_test_panel_ctx { + struct amdgpu_device *adev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + struct drm_display_info *display_info; +}; + +static struct dm_test_panel_ctx *dm_test_panel_ctx_alloc(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx; + struct drm_property *prop; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*ctx->adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); + ctx->adev = drm_to_adev(ctx->drm); + + /* The function under test writes through this property. */ + prop = drm_property_create_range(ctx->drm, DRM_MODE_PROP_IMMUTABLE, + "panel_type", 0, 0xff); + KUNIT_ASSERT_NOT_NULL(test, prop); + ctx->drm->mode_config.panel_type_property = prop; + + ctx->aconnector = drmm_kzalloc(ctx->drm, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(ctx->drm, &ctx->aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL), 0); + drm_object_attach_property(&ctx->aconnector->base.base, prop, + DRM_MODE_PANEL_TYPE_UNKNOWN); + + ctx->link = drmm_kzalloc(ctx->drm, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->aconnector->dc_link = ctx->link; + + ctx->display_info = &ctx->aconnector->base.display_info; + + return ctx; +} + +static uint64_t dm_test_panel_prop_value(struct kunit *test, + struct dm_test_panel_ctx *ctx) +{ + uint64_t val = ~0ULL; + + KUNIT_EXPECT_EQ(test, + drm_object_property_get_value(&ctx->aconnector->base.base, + ctx->drm->mode_config.panel_type_property, &val), 0); + return val; +} + +/** + * dm_test_set_panel_type_samsung_miniled - Test Samsung luminance heuristic + * @test: The KUnit test context + * + * When no VSDB or DPCD hint is present, a Samsung sink whose first luminance + * range is at least 1.5x the second is treated as mini-LED. + */ +static void dm_test_set_panel_type_samsung_miniled(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx = dm_test_panel_ctx_alloc(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + sink->edid_caps.manufacturer_id = DDC_MANUFACTURERNAME_SAMSUNG; + ctx->link->local_sink = sink; + + ctx->display_info->amd_vsdb.version = 1; + ctx->display_info->amd_vsdb.luminance_range1.max_luminance = 1500; + ctx->display_info->amd_vsdb.luminance_range2.max_luminance = 1000; + + amdgpu_dm_set_panel_type(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->panel_type, (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_samsung_below_threshold - Test Samsung sink below the + * mini-LED luminance threshold falls back to LCD + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_samsung_below_threshold(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx = dm_test_panel_ctx_alloc(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + sink->edid_caps.manufacturer_id = DDC_MANUFACTURERNAME_SAMSUNG; + ctx->link->local_sink = sink; + + ctx->display_info->amd_vsdb.version = 1; + ctx->display_info->amd_vsdb.luminance_range1.max_luminance = 1000; + ctx->display_info->amd_vsdb.luminance_range2.max_luminance = 1000; + + amdgpu_dm_set_panel_type(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->panel_type, (int)PANEL_TYPE_LCD); +} + +/** + * dm_test_set_panel_type_default_lcd - Test default fallback is LCD + * @test: The KUnit test context + * + * With no VSDB, DPCD or DID hints the panel type defaults to LCD. + */ +static void dm_test_set_panel_type_default_lcd(struct kunit *test) +{ + struct dm_test_panel_ctx *ctx = dm_test_panel_ctx_alloc(test); + + amdgpu_dm_set_panel_type(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->panel_type, (int)PANEL_TYPE_LCD); + KUNIT_EXPECT_EQ(test, dm_test_panel_prop_value(test, ctx), + (uint64_t)DRM_MODE_PANEL_TYPE_LCD); +} + +/* Tests for amdgpu_dm_update_cacp_caps() */ + +/* + * Build an amdgpu_dm_connector wired to a real kunit drm_device embedded in an + * amdgpu_device, so drm_to_adev() resolves and drm_dbg_kms() has a valid + * device. Defaults are seeded to the fully-supported configuration so each + * test only flips a single knob. + */ +struct dm_test_cacp_ctx { + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; +}; + +static struct dm_test_cacp_ctx *dm_test_cacp_ctx_alloc(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx; + struct drm_device *drm; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*ctx->adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + ctx->adev = drm_to_adev(drm); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->aconnector->base.dev = drm; + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->aconnector->dc_link = ctx->link; + + /* Fully-supported defaults: new enough DCE, eDP, non-LCD panel. */ + ctx->adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + ctx->link->connector_signal = SIGNAL_TYPE_EDP; + ctx->link->panel_type = PANEL_TYPE_OLED; + + return ctx; +} + +/** + * dm_test_cacp_caps_edp_supported - Test CACP supported on a new eDP panel + * @test: The KUnit test context + */ +static void dm_test_cacp_caps_edp_supported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_TRUE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lvds_supported - Test CACP supported on an LVDS panel + * @test: The KUnit test context + */ +static void dm_test_cacp_caps_lvds_supported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->link->connector_signal = SIGNAL_TYPE_LVDS; + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_TRUE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_old_ip_unsupported - Test CACP unsupported on old DCE + * @test: The KUnit test context + * + * DCE versions older than 3.1.4 do not support CACP. + */ +static void dm_test_cacp_caps_old_ip_unsupported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 3); + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_FALSE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_ip_3_1_6_unsupported - Test CACP unsupported on DCE 3.1.6 + * @test: The KUnit test context + * + * DCE 3.1.6 is explicitly excluded even though it is newer than 3.1.4. + */ +static void dm_test_cacp_caps_ip_3_1_6_unsupported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 6); + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_FALSE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_non_edp_lvds_unsupported - Test CACP unsupported on a + * non-eDP/LVDS signal + * @test: The KUnit test context + */ +static void dm_test_cacp_caps_non_edp_lvds_unsupported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_FALSE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lcd_unsupported - Test CACP unsupported on an LCD panel + * @test: The KUnit test context + */ +static void dm_test_cacp_caps_lcd_unsupported(struct kunit *test) +{ + struct dm_test_cacp_ctx *ctx = dm_test_cacp_ctx_alloc(test); + + ctx->link->panel_type = PANEL_TYPE_LCD; + + amdgpu_dm_update_cacp_caps(ctx->aconnector); + + KUNIT_EXPECT_FALSE(test, ctx->link->panel_config.cacp.cacp_supported); +} + +/* Tests for fill_stream_properties_from_drm_display_mode() */ + +/* + * Build the inputs for fill_stream_properties_from_drm_display_mode(). The + * connector is registered against a real kunit drm_device so that + * to_amdgpu_dm_connector(), connector->display_info and the drm debug helpers + * all resolve. Large structs are heap-allocated to keep the stack small. + * + * The stream signal defaults to DisplayPort so the HDMI infoframe paths are + * skipped, keeping the exercised behaviour deterministic. + */ +struct dm_test_fill_ctx { + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *conn_state; + struct dc_stream_state *stream; + struct drm_display_mode *mode; +}; + +static struct dm_test_fill_ctx *dm_test_fill_ctx_alloc(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*ctx->drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); + + ctx->aconnector = drmm_kzalloc(ctx->drm, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(ctx->drm, &ctx->aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL), 0); + + ctx->conn_state = drmm_kzalloc(ctx->drm, sizeof(*ctx->conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); + ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->stream); + ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->mode); + + ctx->stream->signal = SIGNAL_TYPE_DISPLAY_PORT; + + return ctx; +} + +/** + * dm_test_fill_stream_borders_zeroed - Test the timing borders are cleared + * @test: The KUnit test context + */ +static void dm_test_fill_stream_borders_zeroed(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + /* Pre-seed nonzero borders to prove they get reset. */ + timing->h_border_left = 5; + timing->h_border_right = 6; + timing->v_border_top = 7; + timing->v_border_bottom = 8; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->h_border_left, 0); + KUNIT_EXPECT_EQ(test, (int)timing->h_border_right, 0); + KUNIT_EXPECT_EQ(test, (int)timing->v_border_top, 0); + KUNIT_EXPECT_EQ(test, (int)timing->v_border_bottom, 0); +} + +/** + * dm_test_fill_stream_rgb_defaults - Test the default RGB/sRGB output + * @test: The KUnit test context + * + * A plain DisplayPort sink with no YCbCr color formats produces RGB encoding + * and the sRGB color space, with a predefined sRGB transfer function. + */ +static void dm_test_fill_stream_rgb_defaults(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, (int)PIXEL_ENCODING_RGB); + KUNIT_EXPECT_EQ(test, (int)timing->timing_3d_format, + (int)TIMING_3D_FORMAT_NONE); + KUNIT_EXPECT_EQ(test, (int)timing->scan_type, (int)SCANNING_TYPE_NODATA); + KUNIT_EXPECT_EQ(test, (int)ctx->stream->output_color_space, + (int)COLOR_SPACE_SRGB); + KUNIT_EXPECT_EQ(test, (int)ctx->stream->out_transfer_func.type, + (int)TF_TYPE_PREDEFINED); + KUNIT_EXPECT_EQ(test, (int)ctx->stream->out_transfer_func.tf, + (int)TRANSFER_FUNCTION_SRGB); +} + +/** + * dm_test_fill_stream_sync_polarity_positive - Test sync polarity from mode flags + * @test: The KUnit test context + */ +static void dm_test_fill_stream_sync_polarity_positive(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1); + KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 1); +} + +/** + * dm_test_fill_stream_sync_polarity_negative - Test negative sync polarity default + * @test: The KUnit test context + */ +static void dm_test_fill_stream_sync_polarity_negative(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + /* No sync flags set on the mode → polarity stays negative (0). */ + ctx->mode->flags = 0; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 0); + KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 0); +} + +/** + * dm_test_fill_stream_inherits_old_stream - Test vic/polarity copied from old stream + * @test: The KUnit test context + * + * When an old stream is supplied its vic and sync polarities are reused instead + * of being derived from the mode. + */ +static void dm_test_fill_stream_inherits_old_stream(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + struct dc_stream_state *old_stream; + + old_stream = kunit_kzalloc(test, sizeof(*old_stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_stream); + old_stream->timing.vic = 16; + old_stream->timing.flags.HSYNC_POSITIVE_POLARITY = 1; + old_stream->timing.flags.VSYNC_POSITIVE_POLARITY = 0; + + /* Mode flags would force positive polarity if the old stream were ignored. */ + ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, old_stream, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->vic, 16); + KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1); + KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 0); +} + +/** + * dm_test_fill_stream_timing_from_crtc - Test timing taken from crtc_* fields + * @test: The KUnit test context + * + * Without a freesync video match the function uses the mode's crtc_* timing + * fields and scales the pixel clock to 100Hz units. + */ +static void dm_test_fill_stream_timing_from_crtc(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->mode->crtc_hdisplay = 1920; + ctx->mode->crtc_htotal = 2200; + ctx->mode->crtc_hsync_start = 2008; + ctx->mode->crtc_hsync_end = 2052; + ctx->mode->crtc_vdisplay = 1080; + ctx->mode->crtc_vtotal = 1125; + ctx->mode->crtc_vsync_start = 1084; + ctx->mode->crtc_vsync_end = 1089; + ctx->mode->crtc_clock = 148500; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920); + KUNIT_EXPECT_EQ(test, (int)timing->h_total, 2200); + KUNIT_EXPECT_EQ(test, (int)timing->h_sync_width, 44); + KUNIT_EXPECT_EQ(test, (int)timing->h_front_porch, 88); + KUNIT_EXPECT_EQ(test, (int)timing->v_addressable, 1080); + KUNIT_EXPECT_EQ(test, (int)timing->v_total, 1125); + KUNIT_EXPECT_EQ(test, (int)timing->v_sync_width, 5); + KUNIT_EXPECT_EQ(test, (int)timing->v_front_porch, 4); + KUNIT_EXPECT_EQ(test, (int)timing->pix_clk_100hz, 1485000); +} + +/** + * dm_test_fill_stream_color_depth_requested_bpc - Test bpc capping + * @test: The KUnit test context + * + * The requested bpc caps the display bpc and is rounded down to an even value. + */ +static void dm_test_fill_stream_color_depth_requested_bpc(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->aconnector->base.display_info.bpc = 12; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 10); + + KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth, + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_fill_stream_content_type - Test content type forwarded from state + * @test: The KUnit test context + */ +static void dm_test_fill_stream_content_type(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + + ctx->conn_state->content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)ctx->stream->content_type, + (int)DISPLAY_CONTENT_TYPE_GRAPHICS); +} + +/** + * dm_test_fill_stream_aspect_ratio - Test aspect ratio mapped from the mode + * @test: The KUnit test context + */ +static void dm_test_fill_stream_aspect_ratio(struct kunit *test) +{ + struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test); + struct dc_crtc_timing *timing = &ctx->stream->timing; + + ctx->mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9; + + fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode, + &ctx->aconnector->base, ctx->conn_state, NULL, 8); + + KUNIT_EXPECT_EQ(test, (int)timing->aspect_ratio, + (int)ASPECT_RATIO_16_9); +} + +/* Tests for create_stream_for_sink() */ + +/* + * Build the inputs for create_stream_for_sink(). The connector is registered + * against a real kunit drm_device so that to_amdgpu_dm_connector() and the drm + * debug helpers resolve. The DC link carries a zeroed dc_context so that + * dc_create_stream_for_sink() can allocate and construct a stream. + * + * By default no dc_sink is attached, so create_stream_for_sink() builds a fake + * VIRTUAL sink. The VIRTUAL signal keeps the DSC, audio and DP/HDMI infoframe + * paths as no-ops, making the exercised behaviour deterministic. + */ +struct dm_test_stream_ctx { + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_context *dc_ctx; + struct dc_link *link; + struct dm_connector_state *dm_state; + struct drm_display_mode *mode; +}; + +static struct dm_test_stream_ctx *dm_test_stream_ctx_alloc(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*ctx->drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(ctx->drm, &ctx->aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL), 0); + + ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->link->ctx = ctx->dc_ctx; + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + ctx->aconnector->dc_link = ctx->link; + ctx->aconnector->dc_sink = NULL; + + ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + ctx->dm_state->scaling = RMX_OFF; + + ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->mode); + ctx->mode->hdisplay = 1920; + ctx->mode->vdisplay = 1080; + ctx->mode->clock = 148500; + + return ctx; +} + +/** + * dm_test_create_stream_fake_sink_success - Test a stream is built from a fake sink + * @test: The KUnit test context + */ +static void dm_test_create_stream_fake_sink_success(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_stream_state *stream; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + dc_stream_release(stream); +} + +/** + * dm_test_create_stream_sets_dm_context - Test dm_stream_context points to aconnector + * @test: The KUnit test context + */ +static void dm_test_create_stream_sets_dm_context(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_stream_state *stream; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_PTR_EQ(test, stream->dm_stream_context, ctx->aconnector); + dc_stream_release(stream); +} + +/** + * dm_test_create_stream_virtual_signal - Test the fake sink yields a VIRTUAL signal + * @test: The KUnit test context + */ +static void dm_test_create_stream_virtual_signal(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_stream_state *stream; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_EQ(test, (int)stream->signal, (int)SIGNAL_TYPE_VIRTUAL); + dc_stream_release(stream); +} + +/** + * dm_test_create_stream_scaling_src - Test the source rect follows the mode + * @test: The KUnit test context + * + * With scaling off the full-screen source viewport matches the requested mode. + */ +static void dm_test_create_stream_scaling_src(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_stream_state *stream; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_EQ(test, (int)stream->src.width, 1920); + KUNIT_EXPECT_EQ(test, (int)stream->src.height, 1080); + dc_stream_release(stream); +} + +/** + * dm_test_create_stream_existing_sink - Test the existing-sink retain path + * @test: The KUnit test context + * + * When the connector already has a dc_sink, create_stream_for_sink() reuses it + * instead of building a fake sink. + */ +static void dm_test_create_stream_existing_sink(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_sink_init_data sink_init = { 0 }; + struct dc_stream_state *stream; + struct dc_sink *sink; + + sink_init.link = ctx->link; + sink_init.sink_signal = SIGNAL_TYPE_VIRTUAL; + sink = dc_sink_create(&sink_init); + KUNIT_ASSERT_NOT_NULL(test, sink); + sink->sink_signal = SIGNAL_TYPE_VIRTUAL; + + ctx->aconnector->dc_sink = sink; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_PTR_EQ(test, stream->sink, sink); + + dc_stream_release(stream); + dc_sink_release(sink); +} + +/* Tests for amdgpu_dm_connector_detect() */ + +/* + * A non-DisplayPort connector keeps update_subconnector_property() a no-op and, + * because the kunit thread is not the poll worker, the analog poll branch is + * skipped. That leaves the forced-state and dc_sink presence branches as the + * deterministic behaviour to exercise. + */ +static struct amdgpu_dm_connector *dm_test_detect_connector(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + + return dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); +} + +/** + * dm_test_detect_force_on - Test DRM_FORCE_ON reports connected + * @test: The KUnit test context + */ +static void dm_test_detect_force_on(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + + aconnector->base.force = DRM_FORCE_ON; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_connected); +} + +/** + * dm_test_detect_force_on_digital - Test DRM_FORCE_ON_DIGITAL reports connected + * @test: The KUnit test context + */ +static void dm_test_detect_force_on_digital(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + + aconnector->base.force = DRM_FORCE_ON_DIGITAL; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_connected); +} + +/** + * dm_test_detect_force_off - Test DRM_FORCE_OFF reports disconnected + * @test: The KUnit test context + */ +static void dm_test_detect_force_off(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + + aconnector->base.force = DRM_FORCE_OFF; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_disconnected); +} + +/** + * dm_test_detect_sink_present - Test a present dc_sink reports connected + * @test: The KUnit test context + */ +static void dm_test_detect_sink_present(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + aconnector->base.force = DRM_FORCE_UNSPECIFIED; + aconnector->dc_sink = sink; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_connected); +} + +/** + * dm_test_detect_no_sink - Test a missing dc_sink reports disconnected + * @test: The KUnit test context + */ +static void dm_test_detect_no_sink(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + + aconnector->base.force = DRM_FORCE_UNSPECIFIED; + aconnector->dc_sink = NULL; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_disconnected); +} + +/* Tests for amdgpu_dm_connector_poll() */ + +/** + * dm_test_poll_dac_load_returns_cached - Test the DAC load detection shortcut + * @test: The KUnit test context + * + * When the previous connection was established by analog DAC load detection and + * polling is not forced, the connector is not re-detected and its cached status + * is returned unchanged. The connector is embedded in an amdgpu_device so that + * drm_to_adev() resolves. + */ +static void dm_test_poll_dac_load_returns_cached(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + struct dc_sink *local_sink; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(drm, &aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_VGA, NULL), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + local_sink = kunit_kzalloc(test, sizeof(*local_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, local_sink); + + link->local_sink = local_sink; + link->type = dc_connection_analog_load; + aconnector->dc_link = link; + + /* The cached status that the shortcut must return unchanged. */ + aconnector->base.status = connector_status_connected; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_poll(aconnector, false), + (int)connector_status_connected); +} + +/* Tests for amdgpu_dm_connector_late_register() and _unregister() */ + +/* + * Build an amdgpu_dm_connector embedded in an amdgpu_device so drm_to_adev() + * resolves. A VGA connector keeps amdgpu_dm_should_create_sysfs() false (sysfs + * and DP AUX branches skipped) and bl_idx == -1 turns backlight registration + * into a no-op, leaving the register/unregister bookkeeping safe to exercise. + */ +static struct amdgpu_dm_connector *dm_test_reg_connector(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(drm, &aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_VGA, NULL), 0); + + aconnector->bl_idx = -1; + + return aconnector; +} + +/** + * dm_test_late_register_non_dp_succeeds - Test late_register on a plain connector + * @test: The KUnit test context + * + * With sysfs, backlight and DP AUX registration all skipped, late_register + * completes successfully. + */ +static void dm_test_late_register_non_dp_succeeds(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_reg_connector(test); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_connector_late_register(&aconnector->base), 0); +} + +/** + * dm_test_unregister_non_dp_noop - Test unregister tolerates an unregistered connector + * @test: The KUnit test context + * + * No sysfs group was created, the CEC notifier is NULL and the DP AUX channel + * was never registered, so unregister must be a safe no-op. + */ +static void dm_test_unregister_non_dp_noop(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_reg_connector(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(aconnector)); + + amdgpu_dm_connector_unregister(&aconnector->base); +} + +/* Tests for amdgpu_dm_connector_destroy() */ + +/* + * amdgpu_dm_connector_destroy() ends with drm_connector_cleanup() followed by + * kfree(connector), so the connector must be initialised with the unmanaged + * drm_connector_init() and allocated with kzalloc() (the function frees it, so + * kunit_kzalloc() would double free at teardown). It is embedded in an + * amdgpu_device so drm_to_adev() resolves and a dc_link carries a dc_context so + * dc_sink_create() works for the sink-release branches. + */ +struct dm_test_destroy_ctx { + struct drm_device *drm; + struct dc_context *dc_ctx; + struct dc_link *link; +}; + +static struct dm_test_destroy_ctx *dm_test_destroy_ctx_alloc(struct kunit *test) +{ + struct dm_test_destroy_ctx *ctx; + struct amdgpu_device *adev; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); + + ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->link->ctx = ctx->dc_ctx; + + return ctx; +} + +/* + * Allocate a connector the destroy path can free. Uses kzalloc() (not + * kunit_kzalloc) and the unmanaged drm_connector_init() because the function + * under test calls drm_connector_cleanup() + kfree(connector). + * + * drm_connector_init() requires funcs->destroy to be set, so a dedicated funcs + * table wires it to amdgpu_dm_connector_destroy() (the test invokes it + * directly; the connector is removed from the device before teardown). + */ +static const struct drm_connector_funcs dm_test_destroy_funcs = { + .reset = amdgpu_dm_connector_funcs_reset, + .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .destroy = amdgpu_dm_connector_destroy, +}; + +static struct amdgpu_dm_connector * +dm_test_destroy_connector(struct kunit *test, struct drm_device *drm) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kzalloc(sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + KUNIT_ASSERT_EQ(test, + drm_connector_init(drm, &aconnector->base, + &dm_test_destroy_funcs, + DRM_MODE_CONNECTOR_VGA), 0); + aconnector->bl_idx = -1; + + return aconnector; +} + +/** + * dm_test_destroy_minimal - Test destroy tears down a bare connector + * @test: The KUnit test context + * + * With no MST, backlight, sinks or registered AUX/CEC, destroy must clean up + * and free the connector without crashing. + */ +static void dm_test_destroy_minimal(struct kunit *test) +{ + struct dm_test_destroy_ctx *ctx = dm_test_destroy_ctx_alloc(test); + struct amdgpu_dm_connector *aconnector = + dm_test_destroy_connector(test, ctx->drm); + + amdgpu_dm_connector_destroy(&aconnector->base); +} + +/** + * dm_test_destroy_releases_dc_sink - Test destroy releases the dc_sink + * @test: The KUnit test context + */ +static void dm_test_destroy_releases_dc_sink(struct kunit *test) +{ + struct dm_test_destroy_ctx *ctx = dm_test_destroy_ctx_alloc(test); + struct amdgpu_dm_connector *aconnector = + dm_test_destroy_connector(test, ctx->drm); + struct dc_sink_init_data sink_init = { 0 }; + struct dc_sink *sink; + + sink_init.link = ctx->link; + sink_init.sink_signal = SIGNAL_TYPE_VIRTUAL; + sink = dc_sink_create(&sink_init); + KUNIT_ASSERT_NOT_NULL(test, sink); + + /* Extra reference so the sink survives destroy for inspection. */ + dc_sink_retain(sink); + aconnector->dc_sink = sink; + + amdgpu_dm_connector_destroy(&aconnector->base); + + KUNIT_EXPECT_EQ(test, (int)kref_read(&sink->refcount), 1); + dc_sink_release(sink); +} + +/** + * dm_test_destroy_releases_dc_em_sink - Test destroy releases the emulated sink + * @test: The KUnit test context + */ +static void dm_test_destroy_releases_dc_em_sink(struct kunit *test) +{ + struct dm_test_destroy_ctx *ctx = dm_test_destroy_ctx_alloc(test); + struct amdgpu_dm_connector *aconnector = + dm_test_destroy_connector(test, ctx->drm); + struct dc_sink_init_data sink_init = { 0 }; + struct dc_sink *sink; + + sink_init.link = ctx->link; + sink_init.sink_signal = SIGNAL_TYPE_VIRTUAL; + sink = dc_sink_create(&sink_init); + KUNIT_ASSERT_NOT_NULL(test, sink); + + dc_sink_retain(sink); + aconnector->dc_em_sink = sink; + + amdgpu_dm_connector_destroy(&aconnector->base); + + KUNIT_EXPECT_EQ(test, (int)kref_read(&sink->refcount), 1); + dc_sink_release(sink); +} + +/* Tests for dm_encoder_helper_disable() */ + +/** + * dm_test_encoder_disable_noop - Test the disable hook is a no-op + * @test: The KUnit test context + * + * dm_encoder_helper_disable() has an empty body; calling it must neither touch + * the encoder nor crash. + */ +static void dm_test_encoder_disable_noop(struct kunit *test) +{ + struct drm_encoder *encoder; + + encoder = kunit_kzalloc(test, sizeof(*encoder), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, encoder); + + dm_encoder_helper_disable(encoder); +} + +/* Tests for dm_encoder_helper_atomic_check() */ + +/* + * dm_encoder_helper_atomic_check() reads back through to_amdgpu_encoder(), + * to_amdgpu_dm_connector() and to_dm_connector_state(), so the encoder, + * connector and connector-state are stacked in their containers and wired + * together through conn_state->connector. + */ +struct dm_test_atomic_check_ctx { + struct drm_device *drm; + struct amdgpu_encoder *aenc; + struct amdgpu_dm_connector *aconnector; + struct dm_connector_state *dm_state; + struct drm_crtc_state *crtc_state; +}; + +static struct dm_test_atomic_check_ctx * +dm_test_atomic_check_ctx_alloc(struct kunit *test, int connector_type) +{ + struct dm_test_atomic_check_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->drm = dm_test_alloc_drm(test); + + ctx->aenc = kunit_kzalloc(test, sizeof(*ctx->aenc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aenc); + ctx->aenc->base.dev = ctx->drm; + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->aconnector->base.connector_type = connector_type; + + ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + ctx->dm_state->base.connector = &ctx->aconnector->base; + + ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state); + + return ctx; +} + +/** + * dm_test_atomic_check_edp_native_keeps_scaling - Test native eDP mode is left alone + * @test: The KUnit test context + * + * On an eDP connector whose adjusted mode matches the panel's native mode, + * drm_crtc_helper_mode_valid_fixed() returns MODE_OK so scaling is untouched. + */ +static void dm_test_atomic_check_edp_native_keeps_scaling(struct kunit *test) +{ + struct dm_test_atomic_check_ctx *ctx = + dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + + ctx->aenc->native_mode.hdisplay = 1920; + ctx->aenc->native_mode.vdisplay = 1080; + ctx->crtc_state->adjusted_mode.hdisplay = 1920; + ctx->crtc_state->adjusted_mode.vdisplay = 1080; + ctx->dm_state->scaling = RMX_OFF; + + KUNIT_EXPECT_EQ(test, + dm_encoder_helper_atomic_check(&ctx->aenc->base, + ctx->crtc_state, + &ctx->dm_state->base), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->dm_state->scaling, (int)RMX_OFF); +} + +/** + * dm_test_atomic_check_lvds_non_native_enables_scaling - Test non-native LVDS turns on scaling + * @test: The KUnit test context + * + * On an LVDS connector whose adjusted mode differs from the native mode and is + * currently RMX_OFF, the check enables RMX_ASPECT scaling and still returns 0. + */ +static void dm_test_atomic_check_lvds_non_native_enables_scaling(struct kunit *test) +{ + struct dm_test_atomic_check_ctx *ctx = + dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_LVDS); + + ctx->aenc->native_mode.hdisplay = 1920; + ctx->aenc->native_mode.vdisplay = 1080; + ctx->crtc_state->adjusted_mode.hdisplay = 1280; + ctx->crtc_state->adjusted_mode.vdisplay = 720; + ctx->dm_state->scaling = RMX_OFF; + + KUNIT_EXPECT_EQ(test, + dm_encoder_helper_atomic_check(&ctx->aenc->base, + ctx->crtc_state, + &ctx->dm_state->base), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->dm_state->scaling, (int)RMX_ASPECT); +} + +/** + * dm_test_atomic_check_non_mst_returns_zero - Test non-MST connectors short-circuit + * @test: The KUnit test context + * + * A non-eDP/LVDS connector with no MST output port hits the early ``return 0`` + * before any topology state is touched. + */ +static void dm_test_atomic_check_non_mst_returns_zero(struct kunit *test) +{ + struct dm_test_atomic_check_ctx *ctx = + dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA); + + ctx->aconnector->mst_output_port = NULL; + + KUNIT_EXPECT_EQ(test, + dm_encoder_helper_atomic_check(&ctx->aenc->base, + ctx->crtc_state, + &ctx->dm_state->base), 0); +} + +/* Tests for hdmi_cec_unset_edid() */ + +/** + * dm_test_hdmi_cec_unset_edid_no_notifier - Test the no-notifier no-op path + * @test: The KUnit test context + * + * With aconnector->notifier NULL the function returns early and must not crash. + */ +static void dm_test_hdmi_cec_unset_edid_no_notifier(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + hdmi_cec_unset_edid(aconnector); +} + +/* Tests for create_eml_sink() and handle_edid_mgmt() */ + +/* + * create_eml_sink() reads EDID off the connector's DDC. Forcing the connector + * DRM_FORCE_OFF makes drm_edid_read_ddc() return NULL before touching any i2c + * adapter, exercising the "no EDID" branch without real hardware. aux_mode is + * set so the embedded DP AUX ddc is selected (no i2c adapter pointer needed). + */ +struct dm_test_edid_ctx { + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; +}; + +static struct dm_test_edid_ctx * +dm_test_edid_ctx_alloc(struct kunit *test, int connector_type) +{ + struct dm_test_edid_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->drm = dm_test_alloc_drm(test); + ctx->aconnector = dm_test_add_connector(test, ctx->drm, connector_type); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->link->aux_mode = true; + ctx->aconnector->dc_link = ctx->link; + + ctx->aconnector->base.force = DRM_FORCE_OFF; + + return ctx; +} + +/** + * dm_test_create_eml_sink_no_edid - Test the no-EDID branch creates no sink + * @test: The KUnit test context + * + * When no EDID can be read the function logs an error and returns without + * allocating an emulated sink. + */ +static void dm_test_create_eml_sink_no_edid(struct kunit *test) +{ + struct dm_test_edid_ctx *ctx = + dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort); + + create_eml_sink(ctx->aconnector); + + KUNIT_EXPECT_NULL(test, ctx->aconnector->dc_em_sink); +} + +/** + * dm_test_handle_edid_mgmt_dp_sets_link_caps - Test DP seeds verified link caps + * @test: The KUnit test context + * + * For a DisplayPort link the function primes verified_link_cap before reading + * EDID so a headless force-on connector can still modeset. + */ +static void dm_test_handle_edid_mgmt_dp_sets_link_caps(struct kunit *test) +{ + struct dm_test_edid_ctx *ctx = + dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort); + + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + handle_edid_mgmt(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->verified_link_cap.lane_count, + (int)LANE_COUNT_FOUR); + KUNIT_EXPECT_EQ(test, (int)ctx->link->verified_link_cap.link_rate, + (int)LINK_RATE_HIGH2); + KUNIT_EXPECT_NULL(test, ctx->aconnector->dc_em_sink); +} + +/** + * dm_test_handle_edid_mgmt_non_dp_leaves_caps - Test non-DP links keep zeroed caps + * @test: The KUnit test context + * + * A non-DisplayPort link skips the verified_link_cap seeding entirely. + */ +static void dm_test_handle_edid_mgmt_non_dp_leaves_caps(struct kunit *test) +{ + struct dm_test_edid_ctx *ctx = + dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA); + + ctx->link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + + handle_edid_mgmt(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->verified_link_cap.lane_count, 0); + KUNIT_EXPECT_EQ(test, (int)ctx->link->verified_link_cap.link_rate, 0); +} + +/* + * Context for the connector funcs / modes tests: a managed DRM device with a + * registered connector and a managed encoder attached to it, so helpers that + * walk connector->encoder relationships resolve correctly. + */ +struct dm_test_modes_ctx { + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_encoder *aenc; +}; + +static struct dm_test_modes_ctx * +dm_test_modes_ctx_alloc(struct kunit *test, int connector_type) +{ + struct dm_test_modes_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->drm = dm_test_alloc_drm(test); + ctx->aconnector = dm_test_add_connector(test, ctx->drm, connector_type); + + ctx->aenc = kunit_kzalloc(test, sizeof(*ctx->aenc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aenc); + KUNIT_ASSERT_EQ(test, + drmm_encoder_init(ctx->drm, &ctx->aenc->base, NULL, + DRM_MODE_ENCODER_TMDS, NULL), 0); + KUNIT_ASSERT_EQ(test, + drm_connector_attach_encoder(&ctx->aconnector->base, + &ctx->aenc->base), 0); + + return ctx; +} + +/** + * dm_test_funcs_force_no_edid - Test force() leaves drm_edid NULL when no EDID + * @test: The KUnit test context + * + * A headless force-on DisplayPort connector reads no EDID, so the cached + * drm_edid pointer must stay NULL after the force callback runs. + */ +static void dm_test_funcs_force_no_edid(struct kunit *test) +{ + struct dm_test_edid_ctx *ctx = + dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort); + + amdgpu_dm_connector_funcs_force(&ctx->aconnector->base); + + KUNIT_EXPECT_NULL(test, ctx->aconnector->drm_edid); +} + +/** + * dm_test_validate_stream_null_stream - Test NULL stream returns unexpected + * @test: The KUnit test context + * + * With a NULL stream the validation jumps straight to cleanup without ever + * dereferencing the dc handle and reports DC_ERROR_UNEXPECTED. + */ +static void dm_test_validate_stream_null_stream(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, + (int)dm_validate_stream_and_context(NULL, NULL), + (int)DC_ERROR_UNEXPECTED); +} + +/** + * dm_test_to_encoder_no_encoder - Test connector with no encoder returns NULL + * @test: The KUnit test context + */ +static void dm_test_to_encoder_no_encoder(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector = + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_connector_to_encoder(&aconnector->base)); +} + +/** + * dm_test_to_encoder_returns_attached - Test the attached encoder is returned + * @test: The KUnit test context + */ +static void dm_test_to_encoder_returns_attached(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA); + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_connector_to_encoder(&ctx->aconnector->base), + &ctx->aenc->base); +} + +/** + * dm_test_native_mode_no_encoder - Test native mode resolution is a no-op + * @test: The KUnit test context + * + * Without an encoder there is nothing to copy into, so the call must return + * cleanly without dereferencing a NULL encoder. + */ +static void dm_test_native_mode_no_encoder(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector = + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + amdgpu_dm_get_native_mode(&aconnector->base); +} + +/** + * dm_test_native_mode_empty_probed_zeroes_clock - Test empty probed list clears mode + * @test: The KUnit test context + * + * With no probed modes there is no preferred mode to copy, so the encoder's + * native mode is memset to zero (clock becomes 0). + */ +static void dm_test_native_mode_empty_probed_zeroes_clock(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + + ctx->aenc->native_mode.clock = 148500; + + amdgpu_dm_get_native_mode(&ctx->aconnector->base); + + KUNIT_EXPECT_EQ(test, ctx->aenc->native_mode.clock, 0); +} + +/** + * dm_test_native_mode_copies_preferred - Test the preferred mode is copied + * @test: The KUnit test context + * + * The preferred probed mode is duplicated into the encoder's native mode. + */ +static void dm_test_native_mode_copies_preferred(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + struct drm_display_mode *mode; + + mode = drm_mode_create(ctx->drm); + KUNIT_ASSERT_NOT_NULL(test, mode); + mode->type = DRM_MODE_TYPE_PREFERRED; + mode->clock = 148500; + mode->hdisplay = 1920; + mode->vdisplay = 1080; + drm_mode_probed_add(&ctx->aconnector->base, mode); + + amdgpu_dm_get_native_mode(&ctx->aconnector->base); + + KUNIT_EXPECT_EQ(test, ctx->aenc->native_mode.hdisplay, 1920); + KUNIT_EXPECT_EQ(test, ctx->aenc->native_mode.vdisplay, 1080); + KUNIT_EXPECT_EQ(test, ctx->aenc->native_mode.clock, 148500); +} + +/** + * dm_test_create_common_mode_overrides - Test common mode inherits native timing + * @test: The KUnit test context + * + * A new common mode takes its pixel clock and porches from the encoder's + * native mode but overrides the visible resolution and clears PREFERRED. + */ +static void dm_test_create_common_mode_overrides(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + struct drm_display_mode *mode; + + ctx->aenc->native_mode.clock = 148500; + ctx->aenc->native_mode.htotal = 2200; + ctx->aenc->native_mode.type = DRM_MODE_TYPE_PREFERRED; + + mode = amdgpu_dm_create_common_mode(&ctx->aenc->base, "800x600", + 800, 600); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_EXPECT_EQ(test, mode->hdisplay, 800); + KUNIT_EXPECT_EQ(test, mode->vdisplay, 600); + KUNIT_EXPECT_EQ(test, mode->clock, 148500); + KUNIT_EXPECT_EQ(test, mode->htotal, 2200); + KUNIT_EXPECT_FALSE(test, mode->type & DRM_MODE_TYPE_PREFERRED); + KUNIT_EXPECT_STREQ(test, mode->name, "800x600"); + + drm_mode_destroy(ctx->drm, mode); +} + +/** + * dm_test_add_common_modes_non_edp_noop - Test non-eDP/LVDS adds no modes + * @test: The KUnit test context + * + * Common scaled modes are only added for eDP/LVDS panels; an HDMI connector + * is left untouched. + */ +static void dm_test_add_common_modes_non_edp_noop(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA); + + ctx->aenc->native_mode.hdisplay = 1920; + ctx->aenc->native_mode.vdisplay = 1200; + + amdgpu_dm_connector_add_common_modes(&ctx->aenc->base, + &ctx->aconnector->base); + + KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 0); +} + +/** + * dm_test_add_common_modes_edp_adds - Test eDP adds the smaller common modes + * @test: The KUnit test context + * + * For an eDP panel with a 1920x1200 native mode every common mode strictly + * smaller than the native one is added (10 of the 11 entries). + */ +static void dm_test_add_common_modes_edp_adds(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + + ctx->aenc->native_mode.hdisplay = 1920; + ctx->aenc->native_mode.vdisplay = 1200; + + amdgpu_dm_connector_add_common_modes(&ctx->aenc->base, + &ctx->aconnector->base); + + KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 10); +} + +/** + * dm_test_ddc_get_modes_null_edid - Test a NULL EDID resets the mode count + * @test: The KUnit test context + */ +static void dm_test_ddc_get_modes_null_edid(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector = + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + aconnector->num_modes = 5; + + amdgpu_dm_connector_ddc_get_modes(&aconnector->base, NULL); + + KUNIT_EXPECT_EQ(test, aconnector->num_modes, 0); +} + +/** + * dm_test_add_fs_modes_no_preferred_mode - Test no preferred mode yields no modes + * @test: The KUnit test context + * + * A writeback connector has no highest-refresh-rate mode, so add_fs_modes() + * cannot build any FreeSync video modes and returns 0. + */ +static void dm_test_add_fs_modes_no_preferred_mode(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + + KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0); +} + +/** + * dm_test_add_freesync_modes_null_edid_noop - Test NULL EDID adds no modes + * @test: The KUnit test context + * + * Without an EDID the FreeSync video modes cannot be derived, so the mode + * count is left unchanged. + */ +static void dm_test_add_freesync_modes_null_edid_noop(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->num_modes = 7; + + amdgpu_dm_connector_add_freesync_modes(&aconnector->base, NULL); + + KUNIT_EXPECT_EQ(test, aconnector->num_modes, 7); +} + +/* EDID extension block tag values (avoids pulling in private drm headers). */ +#define DM_TEST_CEA_EXT 0x02 +#define DM_TEST_DISPLAYID_EXT 0x70 + +/** + * dm_test_i2c_func_returns_flags - Test the i2c functionality flags + * @test: The KUnit test context + * + * The algorithm advertises plain I2C plus emulated SMBUS regardless of the + * adapter argument, which it never dereferences. + */ +static void dm_test_i2c_func_returns_flags(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_func(NULL), + I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL); +} + +/** + * dm_test_i2c_xfer_no_ddc_pin - Test transfers without a DDC pin are rejected + * @test: The KUnit test context + * + * When the backing ddc_service has no ddc_pin the transfer bails out early + * with -EIO before touching the message buffers or the dc handle. + */ +static void dm_test_i2c_xfer_no_ddc_pin(struct kunit *test) +{ + struct amdgpu_i2c_adapter *i2c; + struct ddc_service *ddc; + + i2c = kunit_kzalloc(test, sizeof(*i2c), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, i2c); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc); + + i2c->ddc_service = ddc; + i2c_set_adapdata(&i2c->base, i2c); + + /* ddc->ddc_pin is NULL -> transfer is rejected with -EIO. */ + KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_xfer(&i2c->base, NULL, 0), -EIO); +} + +/** + * dm_test_get_amd_vsdb_unsupported - Test a zero VSDB version reports no support + * @test: The KUnit test context + */ +static void dm_test_get_amd_vsdb_unsupported(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.display_info.amd_vsdb.version = 0; + aconnector->base.display_info.amd_vsdb.replay_mode = false; + + KUNIT_EXPECT_EQ(test, get_amd_vsdb(aconnector, &vsdb_info), 0); + KUNIT_EXPECT_EQ(test, vsdb_info.amd_vsdb_version, 0); +} + +/** + * dm_test_get_amd_vsdb_supported - Test a non-zero VSDB version is reported + * @test: The KUnit test context + * + * The display info's VSDB version and replay mode are copied out and a + * non-zero version reports support. + */ +static void dm_test_get_amd_vsdb_supported(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.display_info.amd_vsdb.version = 2; + aconnector->base.display_info.amd_vsdb.replay_mode = true; + + KUNIT_EXPECT_EQ(test, get_amd_vsdb(aconnector, &vsdb_info), 1); + KUNIT_EXPECT_EQ(test, vsdb_info.amd_vsdb_version, 2); + KUNIT_EXPECT_TRUE(test, vsdb_info.replay_mode); +} + +/** + * dm_test_parse_hdmi_amd_vsdb_null_edid - Test NULL EDID returns -ENODEV + * @test: The KUnit test context + */ +static void dm_test_parse_hdmi_amd_vsdb_null_edid(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + KUNIT_EXPECT_EQ(test, + parse_hdmi_amd_vsdb(aconnector, NULL, &vsdb_info), + -ENODEV); +} + +/** + * dm_test_parse_hdmi_amd_vsdb_no_extensions - Test EDID without extensions + * @test: The KUnit test context + * + * An EDID that declares no extension blocks has no CEA block to parse. + */ +static void dm_test_parse_hdmi_amd_vsdb_no_extensions(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + struct edid *edid; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid); + + edid->extensions = 0; + + KUNIT_EXPECT_EQ(test, + parse_hdmi_amd_vsdb(aconnector, edid, &vsdb_info), + -ENODEV); +} + +/** + * dm_test_parse_hdmi_amd_vsdb_no_cea_ext - Test EDID with no CEA extension + * @test: The KUnit test context + * + * An extension block that is not a CEA block leaves no VSDB to parse. + */ +static void dm_test_parse_hdmi_amd_vsdb_no_cea_ext(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + struct edid *edid; + u8 *raw; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + /* Base block + one extension block that is NOT a CEA extension. */ + raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, raw); + edid = (struct edid *)raw; + edid->extensions = 1; + raw[EDID_LENGTH] = DM_TEST_DISPLAYID_EXT; + + KUNIT_EXPECT_EQ(test, + parse_hdmi_amd_vsdb(aconnector, edid, &vsdb_info), + -ENODEV); +} + +/** + * dm_test_parse_displayid_vrr_null_edid - Test NULL EDID leaves range untouched + * @test: The KUnit test context + */ +static void dm_test_parse_displayid_vrr_null_edid(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + parse_edid_displayid_vrr(connector, NULL); + + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 0); + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.min_vfreq, 0); +} + +/** + * dm_test_parse_displayid_vrr_no_displayid - Test EDID without a DisplayID ext + * @test: The KUnit test context + * + * Without a DisplayID extension block there is no dynamic range to extract. + */ +static void dm_test_parse_displayid_vrr_no_displayid(struct kunit *test) +{ + struct drm_connector *connector; + struct edid *edid; + u8 *raw; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, raw); + edid = (struct edid *)raw; + edid->extensions = 1; + raw[EDID_LENGTH] = DM_TEST_CEA_EXT; + + parse_edid_displayid_vrr(connector, edid); + + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 0); +} + +/** + * dm_test_parse_displayid_vrr_sets_range - Test a DisplayID VRR block is parsed + * @test: The KUnit test context + * + * A DisplayID dynamic video timing range descriptor populates the connector's + * monitor refresh range. + */ +static void dm_test_parse_displayid_vrr_sets_range(struct kunit *test) +{ + struct drm_connector *connector; + struct edid *edid; + u8 *raw, *ext; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, raw); + edid = (struct edid *)raw; + edid->extensions = 1; + + ext = raw + EDID_LENGTH; + ext[0] = DM_TEST_DISPLAYID_EXT; + /* + * DisplayID dynamic video timing range descriptor, parsed from offset + * 1: tag 0x25, flags 0 (single-byte max), payload length 9, then the + * min/max vfreq bytes. + */ + ext[1] = 0x25; + ext[2] = 0x00; + ext[3] = 9; + ext[10] = 40; + ext[11] = 144; + + parse_edid_displayid_vrr(connector, edid); + + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.min_vfreq, 40); + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 144); +} + +/** + * dm_test_mode_valid_interlace_rejected - Test interlaced modes are rejected + * @test: The KUnit test context + * + * Interlaced modes are rejected up front with MODE_ERROR before any sink or + * stream validation is attempted. + */ +static void dm_test_mode_valid_interlace_rejected(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode *mode; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mode); + + mode->flags = DRM_MODE_FLAG_INTERLACE; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_connector_mode_valid(&aconnector->base, mode), + MODE_ERROR); +} + +/** + * dm_test_mode_valid_dblscan_rejected - Test doublescan modes are rejected + * @test: The KUnit test context + * + * Doublescan modes are rejected up front with MODE_ERROR. + */ +static void dm_test_mode_valid_dblscan_rejected(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode *mode; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mode); + + mode->flags = DRM_MODE_FLAG_DBLSCAN; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_connector_mode_valid(&aconnector->base, mode), + MODE_ERROR); +} + +/** + * dm_test_hdmi_cec_set_edid_no_notifier - Test the no-notifier no-op path + * @test: The KUnit test context + * + * With aconnector->notifier NULL the function returns early and must not crash. + */ +static void dm_test_hdmi_cec_set_edid_no_notifier(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + amdgpu_dm_hdmi_cec_set_edid(aconnector); +} + +/** + * dm_test_s3_handle_hdmi_cec_suspend - Test the suspend pass over connectors + * @test: The KUnit test context + * + * Suspend iterates all connectors, skipping writeback ones and calling the + * unset path on the rest; with NULL notifiers this is a crash-free no-op. + */ +static void dm_test_s3_handle_hdmi_cec_suspend(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_WRITEBACK); + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + amdgpu_dm_s3_handle_hdmi_cec(drm, true); +} + +/** + * dm_test_s3_handle_hdmi_cec_resume - Test the resume pass over connectors + * @test: The KUnit test context + * + * Resume iterates all connectors, skipping writeback ones and calling the set + * path on the rest; with NULL notifiers this is a crash-free no-op. + */ +static void dm_test_s3_handle_hdmi_cec_resume(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_WRITEBACK); + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + amdgpu_dm_s3_handle_hdmi_cec(drm, false); +} + +/** + * dm_test_create_validate_stream_null_dm_state - Test NULL state returns NULL + * @test: The KUnit test context + * + * Without a connector state there is nothing to validate against, so the + * helper bails out with NULL before touching the dc handle. + */ +static void dm_test_create_validate_stream_null_dm_state(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_create_validate_stream_for_sink(&aconnector->base, + NULL, NULL, NULL)); +} + +/** + * dm_test_update_after_detect_mst_noop - Test MST connectors are left to drm_mst + * @test: The KUnit test context + * + * An MST connector is handled by the drm_mst framework, so the function + * returns immediately and never dereferences the (NULL) dc_link. + */ +static void dm_test_update_after_detect_mst_noop(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->mst_mgr.mst_state = true; + + amdgpu_dm_update_connector_after_detect(aconnector); +} + +/** + * dm_test_update_after_detect_sink_unchanged - Test the short-pulse no-op path + * @test: The KUnit test context + * + * When the link reports no local sink and the connector already has no + * dc_sink, the "sink didn't change" path returns without touching DC. + */ +static void dm_test_update_after_detect_sink_unchanged(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + aconnector = dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + aconnector->dc_link = link; + + /* link->local_sink and aconnector->dc_sink are both NULL. */ + amdgpu_dm_update_connector_after_detect(aconnector); + + KUNIT_EXPECT_NULL(test, aconnector->dc_sink); +} + +/* Tests for amdgpu_dm_update_stream_scaling_settings() */ + +/** + * dm_test_update_scaling_null_mode - Test NULL mode leaves the stream rects untouched + * @test: The KUnit test context + */ +static void dm_test_update_scaling_null_mode(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, NULL, NULL, stream); + + /* NULL mode: early return before touching src/dst */ + KUNIT_EXPECT_EQ(test, stream->src.width, 0); + KUNIT_EXPECT_EQ(test, stream->dst.width, 0); +} + +/** + * dm_test_update_scaling_fullscreen_default - Test full-screen default with no dm_state + * @test: The KUnit test context + */ +static void dm_test_update_scaling_fullscreen_default(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct drm_display_mode mode = { 0 }; + + mode.hdisplay = 1920; + mode.vdisplay = 1080; + stream->timing.h_addressable = 2560; + stream->timing.v_addressable = 1440; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, NULL, stream); + + /* src = mode, dst = timing addressable, no centering without dm_state */ + KUNIT_EXPECT_EQ(test, stream->src.width, 1920); + KUNIT_EXPECT_EQ(test, stream->src.height, 1080); + KUNIT_EXPECT_EQ(test, stream->dst.width, 2560); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1440); + KUNIT_EXPECT_EQ(test, stream->dst.x, 0); + KUNIT_EXPECT_EQ(test, stream->dst.y, 0); +} + +/** + * dm_test_update_scaling_rmx_full - Test RMX_FULL keeps a full-size, centered dst + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_full(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + mode.hdisplay = 1280; + mode.vdisplay = 720; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_FULL; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* RMX_FULL: dst stays full addressable, offset 0 */ + KUNIT_EXPECT_EQ(test, stream->dst.width, 1920); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1080); + KUNIT_EXPECT_EQ(test, stream->dst.x, 0); + KUNIT_EXPECT_EQ(test, stream->dst.y, 0); +} + +/** + * dm_test_update_scaling_rmx_aspect_pillarbox - Test RMX_ASPECT preserves aspect ratio + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_aspect_pillarbox(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + /* 4:3 source on a 16:9 panel -> pillarboxed */ + mode.hdisplay = 1024; + mode.vdisplay = 768; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_ASPECT; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* + * src.width*dst.height (1024*1080) < src.height*dst.width (768*1920): + * width scaled to src.width*dst.height/src.height = 1440, height stays + * 1080, centered horizontally at (1920-1440)/2 = 240. + */ + KUNIT_EXPECT_EQ(test, stream->dst.width, 1440); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1080); + KUNIT_EXPECT_EQ(test, stream->dst.x, 240); + KUNIT_EXPECT_EQ(test, stream->dst.y, 0); +} + +/** + * dm_test_update_scaling_rmx_aspect_letterbox - Test RMX_ASPECT letterboxes wide sources + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_aspect_letterbox(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + /* 16:9 source on a 4:3 panel -> letterboxed */ + mode.hdisplay = 1920; + mode.vdisplay = 1080; + stream->timing.h_addressable = 1024; + stream->timing.v_addressable = 768; + dm_state->scaling = RMX_ASPECT; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + KUNIT_EXPECT_EQ(test, stream->dst.width, 1024); + KUNIT_EXPECT_EQ(test, stream->dst.height, 576); + KUNIT_EXPECT_EQ(test, stream->dst.x, 0); + KUNIT_EXPECT_EQ(test, stream->dst.y, 96); +} + +/** + * dm_test_update_scaling_rmx_center - Test RMX_CENTER centers a 1:1 dst + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_center(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + mode.hdisplay = 1280; + mode.vdisplay = 720; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_CENTER; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* RMX_CENTER: dst = src, centered on the addressable area */ + KUNIT_EXPECT_EQ(test, stream->dst.width, 1280); + KUNIT_EXPECT_EQ(test, stream->dst.height, 720); + KUNIT_EXPECT_EQ(test, stream->dst.x, 320); + KUNIT_EXPECT_EQ(test, stream->dst.y, 180); +} + +/** + * dm_test_update_scaling_underscan - Test underscan borders shrink and offset dst + * @test: The KUnit test context + */ +static void dm_test_update_scaling_underscan(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + mode.hdisplay = 1920; + mode.vdisplay = 1080; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_FULL; + dm_state->underscan_enable = true; + dm_state->underscan_hborder = 64; + dm_state->underscan_vborder = 32; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* Full dst, then underscan: x/y += border/2, width/height -= border */ + KUNIT_EXPECT_EQ(test, stream->dst.x, 32); + KUNIT_EXPECT_EQ(test, stream->dst.y, 16); + KUNIT_EXPECT_EQ(test, stream->dst.width, 1856); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1048); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -2037,6 +5284,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_adjust_colour_depth_reduces_to_888), KUNIT_CASE(dm_test_adjust_colour_depth_10bpc_passes), KUNIT_CASE(dm_test_adjust_colour_depth_420_halves_clk), + KUNIT_CASE(dm_test_adjust_colour_depth_420_reduces), KUNIT_CASE(dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc), KUNIT_CASE(dm_test_adjust_colour_depth_16bpc_no_fallback), KUNIT_CASE(dm_test_adjust_colour_depth_none_fits), @@ -2053,6 +5301,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_output_color_space_oprgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb), KUNIT_CASE(dm_test_output_color_space_bt2020_ycc), + KUNIT_CASE(dm_test_output_color_space_default_ycbcr709_y_only), + KUNIT_CASE(dm_test_output_color_space_default_ycbcr601), + KUNIT_CASE(dm_test_output_color_space_bt2020_ycc_rgb_encoding), + KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_ycc_encoding), /* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc */ KUNIT_CASE(dm_test_convert_color_depth_bpc_mappings), KUNIT_CASE(dm_test_convert_color_depth_bpc_unknown), @@ -2077,6 +5329,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_to_connector_type_dvi_dvii), KUNIT_CASE(dm_test_to_connector_type_dual_link_dvii), KUNIT_CASE(dm_test_to_connector_type_dvi_dvid), + KUNIT_CASE(dm_test_to_connector_type_dual_link_dvid), KUNIT_CASE(dm_test_to_connector_type_virtual), KUNIT_CASE(dm_test_to_connector_type_unknown), /* is_duplicate_mode */ @@ -2098,6 +5351,9 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_aspect_ratio_16_9), KUNIT_CASE(dm_test_aspect_ratio_64_27), KUNIT_CASE(dm_test_aspect_ratio_256_135), + /* copy_crtc_timing_for_drm_display_mode */ + KUNIT_CASE(dm_test_copy_crtc_timing_copies_all_fields), + KUNIT_CASE(dm_test_copy_crtc_timing_leaves_non_crtc_fields), /* decide_crtc_timing_for_drm_display_mode */ KUNIT_CASE(dm_test_decide_crtc_timing_scale_enabled), KUNIT_CASE(dm_test_decide_crtc_timing_matching_mode), @@ -2143,6 +5399,156 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_is_freesync_video_mode_null_mode), KUNIT_CASE(dm_test_is_freesync_video_mode_match), KUNIT_CASE(dm_test_is_freesync_video_mode_no_match), + /* update_subconnector_property */ + KUNIT_CASE(dm_test_update_subconnector_dp_with_sink), + KUNIT_CASE(dm_test_update_subconnector_dp_no_sink), + KUNIT_CASE(dm_test_update_subconnector_non_dp_noop), + /* amdgpu_dm_update_cacp_caps */ + KUNIT_CASE(dm_test_cacp_caps_unsupported_ip), + KUNIT_CASE(dm_test_cacp_caps_excluded_ip_316), + KUNIT_CASE(dm_test_cacp_caps_edp_oled_supported), + KUNIT_CASE(dm_test_cacp_caps_lvds_oled_supported), + KUNIT_CASE(dm_test_cacp_caps_non_edp_signal), + KUNIT_CASE(dm_test_cacp_caps_lcd_panel), + /* amdgpu_dm_set_panel_type */ + KUNIT_CASE(dm_test_set_panel_type_vsdb_oled), + KUNIT_CASE(dm_test_set_panel_type_vsdb_miniled), + KUNIT_CASE(dm_test_set_panel_type_dpcd_oled), + KUNIT_CASE(dm_test_set_panel_type_dpcd_miniled), + KUNIT_CASE(dm_test_set_panel_type_did_oled), + KUNIT_CASE(dm_test_set_panel_type_did_lcd), + KUNIT_CASE(dm_test_set_panel_type_vendor_lum_heuristic), + KUNIT_CASE(dm_test_set_panel_type_defaults_to_lcd), + /* amdgpu_dm_fbc_init */ + KUNIT_CASE(dm_test_fbc_init_no_compressor), + KUNIT_CASE(dm_test_fbc_init_non_edp), + KUNIT_CASE(dm_test_fbc_init_already_allocated), + KUNIT_CASE(dm_test_fbc_init_no_modes), + /* amdgpu_dm_detect_mst_link_for_all_connectors */ + KUNIT_CASE(dm_test_detect_mst_no_connectors), + KUNIT_CASE(dm_test_detect_mst_skips_writeback), + KUNIT_CASE(dm_test_detect_mst_non_mst_link), + KUNIT_CASE(dm_test_detect_mst_branch_without_aux), + /* amdgpu_dm_find_first_crtc_matching_connector */ + KUNIT_CASE(dm_test_find_first_crtc_match), + KUNIT_CASE(dm_test_find_first_crtc_no_match), + KUNIT_CASE(dm_test_find_first_crtc_empty_state), + KUNIT_CASE(dm_test_find_first_crtc_skips_null_ptr), + KUNIT_CASE(dm_test_find_first_crtc_returns_first), + /* amdgpu_dm_set_panel_type */ + KUNIT_CASE(dm_test_set_panel_type_samsung_miniled), + KUNIT_CASE(dm_test_set_panel_type_samsung_below_threshold), + KUNIT_CASE(dm_test_set_panel_type_default_lcd), + /* amdgpu_dm_update_cacp_caps */ + KUNIT_CASE(dm_test_cacp_caps_edp_supported), + KUNIT_CASE(dm_test_cacp_caps_lvds_supported), + KUNIT_CASE(dm_test_cacp_caps_old_ip_unsupported), + KUNIT_CASE(dm_test_cacp_caps_ip_3_1_6_unsupported), + KUNIT_CASE(dm_test_cacp_caps_non_edp_lvds_unsupported), + KUNIT_CASE(dm_test_cacp_caps_lcd_unsupported), + /* fill_stream_properties_from_drm_display_mode */ + KUNIT_CASE(dm_test_fill_stream_borders_zeroed), + KUNIT_CASE(dm_test_fill_stream_rgb_defaults), + KUNIT_CASE(dm_test_fill_stream_sync_polarity_positive), + KUNIT_CASE(dm_test_fill_stream_sync_polarity_negative), + KUNIT_CASE(dm_test_fill_stream_inherits_old_stream), + KUNIT_CASE(dm_test_fill_stream_timing_from_crtc), + KUNIT_CASE(dm_test_fill_stream_color_depth_requested_bpc), + KUNIT_CASE(dm_test_fill_stream_content_type), + KUNIT_CASE(dm_test_fill_stream_aspect_ratio), + /* create_stream_for_sink */ + KUNIT_CASE(dm_test_create_stream_fake_sink_success), + KUNIT_CASE(dm_test_create_stream_sets_dm_context), + KUNIT_CASE(dm_test_create_stream_virtual_signal), + KUNIT_CASE(dm_test_create_stream_scaling_src), + KUNIT_CASE(dm_test_create_stream_existing_sink), + /* amdgpu_dm_connector_detect */ + KUNIT_CASE(dm_test_detect_force_on), + KUNIT_CASE(dm_test_detect_force_on_digital), + KUNIT_CASE(dm_test_detect_force_off), + KUNIT_CASE(dm_test_detect_sink_present), + KUNIT_CASE(dm_test_detect_no_sink), + /* amdgpu_dm_connector_poll */ + KUNIT_CASE(dm_test_poll_dac_load_returns_cached), + /* amdgpu_dm_connector_late_register */ + KUNIT_CASE(dm_test_late_register_non_dp_succeeds), + /* amdgpu_dm_connector_unregister */ + KUNIT_CASE(dm_test_unregister_non_dp_noop), + /* amdgpu_dm_connector_destroy */ + KUNIT_CASE(dm_test_destroy_minimal), + KUNIT_CASE(dm_test_destroy_releases_dc_sink), + KUNIT_CASE(dm_test_destroy_releases_dc_em_sink), + /* dm_encoder_helper_disable */ + KUNIT_CASE(dm_test_encoder_disable_noop), + /* dm_encoder_helper_atomic_check */ + KUNIT_CASE(dm_test_atomic_check_edp_native_keeps_scaling), + KUNIT_CASE(dm_test_atomic_check_lvds_non_native_enables_scaling), + KUNIT_CASE(dm_test_atomic_check_non_mst_returns_zero), + /* hdmi_cec_unset_edid */ + KUNIT_CASE(dm_test_hdmi_cec_unset_edid_no_notifier), + /* create_eml_sink */ + KUNIT_CASE(dm_test_create_eml_sink_no_edid), + /* handle_edid_mgmt */ + KUNIT_CASE(dm_test_handle_edid_mgmt_dp_sets_link_caps), + KUNIT_CASE(dm_test_handle_edid_mgmt_non_dp_leaves_caps), + /* amdgpu_dm_connector_funcs_force */ + KUNIT_CASE(dm_test_funcs_force_no_edid), + /* dm_validate_stream_and_context */ + KUNIT_CASE(dm_test_validate_stream_null_stream), + /* amdgpu_dm_connector_to_encoder */ + KUNIT_CASE(dm_test_to_encoder_no_encoder), + KUNIT_CASE(dm_test_to_encoder_returns_attached), + /* amdgpu_dm_get_native_mode */ + KUNIT_CASE(dm_test_native_mode_no_encoder), + KUNIT_CASE(dm_test_native_mode_empty_probed_zeroes_clock), + KUNIT_CASE(dm_test_native_mode_copies_preferred), + /* amdgpu_dm_create_common_mode */ + KUNIT_CASE(dm_test_create_common_mode_overrides), + /* amdgpu_dm_connector_add_common_modes */ + KUNIT_CASE(dm_test_add_common_modes_non_edp_noop), + KUNIT_CASE(dm_test_add_common_modes_edp_adds), + /* amdgpu_dm_connector_ddc_get_modes */ + KUNIT_CASE(dm_test_ddc_get_modes_null_edid), + /* add_fs_modes */ + KUNIT_CASE(dm_test_add_fs_modes_no_preferred_mode), + /* amdgpu_dm_connector_add_freesync_modes */ + KUNIT_CASE(dm_test_add_freesync_modes_null_edid_noop), + /* amdgpu_dm_i2c_func */ + KUNIT_CASE(dm_test_i2c_func_returns_flags), + /* amdgpu_dm_i2c_xfer */ + KUNIT_CASE(dm_test_i2c_xfer_no_ddc_pin), + /* get_amd_vsdb */ + KUNIT_CASE(dm_test_get_amd_vsdb_unsupported), + KUNIT_CASE(dm_test_get_amd_vsdb_supported), + /* parse_hdmi_amd_vsdb */ + KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_null_edid), + KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_no_extensions), + KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_no_cea_ext), + /* parse_edid_displayid_vrr */ + KUNIT_CASE(dm_test_parse_displayid_vrr_null_edid), + KUNIT_CASE(dm_test_parse_displayid_vrr_no_displayid), + KUNIT_CASE(dm_test_parse_displayid_vrr_sets_range), + /* amdgpu_dm_connector_mode_valid */ + KUNIT_CASE(dm_test_mode_valid_interlace_rejected), + KUNIT_CASE(dm_test_mode_valid_dblscan_rejected), + /* amdgpu_dm_hdmi_cec_set_edid */ + KUNIT_CASE(dm_test_hdmi_cec_set_edid_no_notifier), + /* amdgpu_dm_s3_handle_hdmi_cec */ + KUNIT_CASE(dm_test_s3_handle_hdmi_cec_suspend), + KUNIT_CASE(dm_test_s3_handle_hdmi_cec_resume), + /* amdgpu_dm_create_validate_stream_for_sink */ + KUNIT_CASE(dm_test_create_validate_stream_null_dm_state), + /* amdgpu_dm_update_connector_after_detect */ + KUNIT_CASE(dm_test_update_after_detect_mst_noop), + KUNIT_CASE(dm_test_update_after_detect_sink_unchanged), + /* amdgpu_dm_update_stream_scaling_settings */ + KUNIT_CASE(dm_test_update_scaling_null_mode), + KUNIT_CASE(dm_test_update_scaling_fullscreen_default), + KUNIT_CASE(dm_test_update_scaling_rmx_full), + KUNIT_CASE(dm_test_update_scaling_rmx_aspect_pillarbox), + KUNIT_CASE(dm_test_update_scaling_rmx_aspect_letterbox), + KUNIT_CASE(dm_test_update_scaling_rmx_center), + KUNIT_CASE(dm_test_update_scaling_underscan), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c index a6fd3a6fd803..29a513dab9cd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c @@ -7,7 +7,185 @@ #include +#include +#include +#include +#include +#include + +#include "dc.h" +#include "core_types.h" +#include "logger_types.h" +#include "opp.h" +#include "timing_generator.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_crc.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +struct dm_test_crc_dc_fixture { + struct dc *dc; + struct dc_context *dc_ctx; + struct dc_state *dc_state; + struct dc_stream_state *stream; + struct dc_link *link; + struct timing_generator *tg; + struct output_pixel_processor *opp; + struct dal_logger *logger; + struct dm_crtc_state *dm_state; + struct crc_params crc_params; + enum dc_dynamic_expansion dyn_expansion; + enum dc_dither_option dither_option; + uint32_t crc_r; + uint32_t crc_g; + uint32_t crc_b; + bool configure_crc_called; + bool dyn_expansion_called; + bool bit_depth_reduction_called; + bool configure_crc_return; + bool get_crc_called; + bool get_crc_return; +}; + +static struct dm_test_crc_dc_fixture *dm_test_crc_dc_ctx; + +static bool dm_test_configure_crc(struct timing_generator *tg, + const struct crc_params *params) +{ + if (!dm_test_crc_dc_ctx) + return false; + + dm_test_crc_dc_ctx->configure_crc_called = true; + dm_test_crc_dc_ctx->crc_params = *params; + + return dm_test_crc_dc_ctx->configure_crc_return; +} + +static bool dm_test_get_crc(struct timing_generator *tg, uint8_t idx, + uint32_t *r_cr, uint32_t *g_y, uint32_t *b_cb) +{ + if (!dm_test_crc_dc_ctx) + return false; + + dm_test_crc_dc_ctx->get_crc_called = true; + *r_cr = dm_test_crc_dc_ctx->crc_r; + *g_y = dm_test_crc_dc_ctx->crc_g; + *b_cb = dm_test_crc_dc_ctx->crc_b; + + return dm_test_crc_dc_ctx->get_crc_return; +} + +static void dm_test_opp_set_dyn_expansion(struct output_pixel_processor *opp, + enum dc_color_space color_sp, + enum dc_color_depth color_dpth, + enum signal_type signal) +{ + if (!dm_test_crc_dc_ctx) + return; + + dm_test_crc_dc_ctx->dyn_expansion_called = true; + dm_test_crc_dc_ctx->dyn_expansion = opp->dyn_expansion; +} + +static void dm_test_opp_program_bit_depth_reduction(struct output_pixel_processor *opp, + const struct bit_depth_reduction_params *params) +{ + if (!dm_test_crc_dc_ctx) + return; + + dm_test_crc_dc_ctx->bit_depth_reduction_called = true; +} + +static const struct timing_generator_funcs dm_test_tg_funcs = { + .configure_crc = dm_test_configure_crc, + .get_crc = dm_test_get_crc, +}; + +static const struct opp_funcs dm_test_opp_funcs = { + .opp_set_dyn_expansion = dm_test_opp_set_dyn_expansion, + .opp_program_bit_depth_reduction = dm_test_opp_program_bit_depth_reduction, +}; + +static struct dm_test_crc_dc_fixture *dm_test_alloc_crc_dc_fixture(struct kunit *test, + struct amdgpu_device *adev) +{ + struct dm_test_crc_dc_fixture *fixture; + struct pipe_ctx *pipe; + + fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture); + + fixture->dm_state = kunit_kzalloc(test, sizeof(*fixture->dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->dm_state); + fixture->dc = kunit_kzalloc(test, sizeof(*fixture->dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->dc); + fixture->dc_ctx = kunit_kzalloc(test, sizeof(*fixture->dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->dc_ctx); + fixture->dc_state = kunit_kzalloc(test, sizeof(*fixture->dc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->dc_state); + fixture->tg = kunit_kzalloc(test, sizeof(*fixture->tg), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->tg); + fixture->opp = kunit_kzalloc(test, sizeof(*fixture->opp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->opp); + fixture->logger = kunit_kzalloc(test, sizeof(*fixture->logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->logger); + fixture->link = dm_kunit_alloc_link(test); + fixture->stream = dm_kunit_alloc_stream(test, fixture->link); + + mutex_init(&adev->dm.dc_lock); + adev->dm.dc = fixture->dc; + fixture->dc->ctx = fixture->dc_ctx; + fixture->dc->current_state = fixture->dc_state; + fixture->dc_ctx->dc = fixture->dc; + fixture->dc_ctx->logger = fixture->logger; + fixture->link->dc = fixture->dc; + fixture->stream->ctx = fixture->dc_ctx; + fixture->stream->link = fixture->link; + fixture->stream->timing.h_addressable = 1920; + fixture->stream->timing.v_addressable = 1080; + fixture->configure_crc_return = true; + fixture->tg->funcs = &dm_test_tg_funcs; + fixture->opp->funcs = &dm_test_opp_funcs; + fixture->dm_state->stream = fixture->stream; + + pipe = &fixture->dc_state->res_ctx.pipe_ctx[0]; + pipe->stream = fixture->stream; + pipe->pipe_idx = 0; + pipe->stream_res.tg = fixture->tg; + pipe->stream_res.opp = fixture->opp; + + return fixture; +} + +static struct amdgpu_crtc *dm_test_alloc_crc_crtc(struct kunit *test, + struct amdgpu_device *adev) +{ + struct amdgpu_crtc *acrtc; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + acrtc->base.dev = &adev->ddev; + drm_modeset_lock_init(&acrtc->base.mutex); + spin_lock_init(&acrtc->base.commit_lock); + INIT_LIST_HEAD(&acrtc->base.commit_list); + + return acrtc; +} + +static const struct drm_connector_funcs dm_test_crc_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, +}; + +static void dm_test_crc_connector_cleanup(void *data) +{ + drm_connector_cleanup(data); +} static void dm_test_parse_crc_source_none(struct kunit *test) { @@ -95,6 +273,532 @@ static void dm_test_is_valid_crc_source(struct kunit *test) KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); } +/** + * dm_test_crtc_get_crc_sources() - Test available CRC source strings. + * @test: KUnit test context. + * + * Verifies that amdgpu_dm_crtc_get_crc_sources() returns the static source + * list and reports all debugfs source names. + */ +static void dm_test_crtc_get_crc_sources(struct kunit *test) +{ + const char *const *sources; + size_t count = 0; + + sources = amdgpu_dm_crtc_get_crc_sources(NULL, &count); + + KUNIT_ASSERT_NOT_NULL(test, sources); + KUNIT_EXPECT_EQ(test, count, 6); + KUNIT_EXPECT_STREQ(test, sources[0], "none"); + KUNIT_EXPECT_STREQ(test, sources[1], "crtc"); + KUNIT_EXPECT_STREQ(test, sources[2], "crtc dither"); + KUNIT_EXPECT_STREQ(test, sources[3], "dprx"); + KUNIT_EXPECT_STREQ(test, sources[4], "dprx dither"); + KUNIT_EXPECT_STREQ(test, sources[5], "auto"); +} + +/** + * dm_test_crtc_verify_crc_source_valid() - Test valid CRC source verification. + * @test: KUnit test context. + * + * Verifies that valid source strings return success and request three CRC + * values. + */ +static void dm_test_crtc_verify_crc_source_valid(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + size_t values_cnt = 0; + int ret; + + ret = amdgpu_dm_crtc_verify_crc_source(&acrtc->base, "crtc", &values_cnt); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, values_cnt, 3); +} + +/** + * dm_test_crtc_verify_crc_source_invalid() - Test invalid CRC source verification. + * @test: KUnit test context. + * + * Verifies that invalid source strings are rejected without changing the + * caller-provided values count. + */ +static void dm_test_crtc_verify_crc_source_invalid(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + size_t values_cnt = 7; + int ret; + + ret = amdgpu_dm_crtc_verify_crc_source(&acrtc->base, "bad", &values_cnt); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, values_cnt, 7); +} + +/** + * dm_test_crtc_configure_crc_source_no_stream() - Test missing stream handling. + * @test: KUnit test context. + * + * Verifies that configuration is deferred/rejected before any DC access when + * the CRTC state does not have a stream. + */ +static void dm_test_crtc_configure_crc_source_no_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + int ret; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_crtc_configure_crc_source_dprx() - Test DPRX configure path. + * @test: KUnit test context. + * + * Verifies that a DPRX source can be configured with an empty DC resource + * state, covering the non-CRTC path that only updates dither/dynamic expansion. + */ +static void dm_test_crtc_configure_crc_source_dprx(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_FALSE(test, fixture->configure_crc_called); + KUNIT_EXPECT_TRUE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_EQ(test, fixture->dyn_expansion, DYN_EXPANSION_DISABLE); + KUNIT_EXPECT_TRUE(test, fixture->bit_depth_reduction_called); +} + +/** + * dm_test_crtc_configure_crc_source_dprx_dither() - Test DPRX dither path. + * @test: KUnit test context. + * + * Verifies that a DPRX dither source reaches the default dither/dynamic + * expansion path without requiring timing-generator callbacks. + */ +static void dm_test_crtc_configure_crc_source_dprx_dither(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_FALSE(test, fixture->configure_crc_called); + KUNIT_EXPECT_TRUE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_EQ(test, fixture->dyn_expansion, DYN_EXPANSION_AUTO); + KUNIT_EXPECT_TRUE(test, fixture->bit_depth_reduction_called); +} + +/** + * dm_test_crtc_configure_crc_source_crtc() - Test CRTC enable path. + * @test: KUnit test context. + * + * Verifies that a CRTC source enables DC CRC capture and disables dither. + */ +static void dm_test_crtc_configure_crc_source_crtc(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, fixture->configure_crc_called); + KUNIT_EXPECT_TRUE(test, fixture->crc_params.enable); + KUNIT_EXPECT_TRUE(test, fixture->crc_params.continuous_mode); + KUNIT_EXPECT_TRUE(test, fixture->crc_params.reset); + KUNIT_EXPECT_EQ(test, fixture->crc_params.windowa_x_end, 1920); + KUNIT_EXPECT_EQ(test, fixture->crc_params.windowa_y_end, 1080); + KUNIT_EXPECT_TRUE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_EQ(test, fixture->dyn_expansion, DYN_EXPANSION_DISABLE); + KUNIT_EXPECT_TRUE(test, fixture->bit_depth_reduction_called); +} + +/** + * dm_test_crtc_configure_crc_source_crtc_dcn36_poly() - Test CRC poly select. + * @test: KUnit test context. + * + * Verifies that DCN3.6+ configurations use the CRTC-selected CRC polynomial. + */ +static void dm_test_crtc_configure_crc_source_crtc_dcn36_poly(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 6, 0); + acrtc->dm_irq_params.crc_poly_mode = CRC_POLY_MODE_32; + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, fixture->configure_crc_called); + KUNIT_EXPECT_EQ(test, fixture->crc_params.crc_poly_mode, CRC_POLY_MODE_32); +} + +/** + * dm_test_crtc_configure_crc_source_crtc_configure_fails() - Test failure path. + * @test: KUnit test context. + * + * Verifies that a DC CRC configuration failure is reported as -EINVAL and + * stops before dither/dynamic expansion programming. + */ +static void dm_test_crtc_configure_crc_source_crtc_configure_fails(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + fixture->configure_crc_return = false; + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_TRUE(test, fixture->configure_crc_called); + KUNIT_EXPECT_FALSE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_FALSE(test, fixture->bit_depth_reduction_called); +} + +/** + * dm_test_crtc_configure_crc_source_none() - Test CRC disable path. + * @test: KUnit test context. + * + * Verifies that source NONE disables DC CRC capture and restores default + * dither/dynamic expansion. + */ +static void dm_test_crtc_configure_crc_source_none(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, fixture->configure_crc_called); + KUNIT_EXPECT_FALSE(test, fixture->crc_params.enable); + KUNIT_EXPECT_FALSE(test, fixture->crc_params.continuous_mode); + KUNIT_EXPECT_TRUE(test, fixture->crc_params.reset); + KUNIT_EXPECT_TRUE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_EQ(test, fixture->dyn_expansion, DYN_EXPANSION_AUTO); + KUNIT_EXPECT_TRUE(test, fixture->bit_depth_reduction_called); +} + +/** + * dm_test_crtc_set_crc_source_invalid() - Test invalid source guard. + * @test: KUnit test context. + * + * Verifies that amdgpu_dm_crtc_set_crc_source() rejects invalid source names + * before taking modeset locks, vblank references, or touching DC state. + */ +static void dm_test_crtc_set_crc_source_invalid(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + int ret; + + ret = amdgpu_dm_crtc_set_crc_source(&acrtc->base, "invalid"); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_crtc_set_crc_source_none_no_stream() - Test valid source no-stream exit. + * @test: KUnit test context. + * + * Verifies that a valid NONE request enters the set-source body, reads the + * current CRC state, and exits cleanly when configuration is deferred because + * no stream is attached. + */ +static void dm_test_crtc_set_crc_source_none_no_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + int ret; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_NONE; + + ret = amdgpu_dm_crtc_set_crc_source(&acrtc->base, "none"); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, acrtc->dm_irq_params.crc_src, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 0); +} + +/** + * dm_test_crtc_set_crc_source_none_commit() - Test set-source with pending commit. + * @test: KUnit test context. + * + * Verifies that a pending CRTC commit is acquired and waited on (already + * completed here so the wait returns immediately), then released during + * cleanup. Configuration is still deferred because no stream is attached. + */ +static void dm_test_crtc_set_crc_source_none_commit(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + struct drm_crtc_commit *commit; + int ret; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_NONE; + + commit = kunit_kzalloc(test, sizeof(*commit), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, commit); + kref_init(&commit->ref); + init_completion(&commit->hw_done); + /* Mark the commit finished so the wait returns immediately. */ + complete_all(&commit->hw_done); + list_add_tail(&commit->commit_entry, &acrtc->base.commit_list); + + ret = amdgpu_dm_crtc_set_crc_source(&acrtc->base, "none"); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 0); +} + +/** + * dm_test_crtc_set_crc_source_dprx_no_connector() - Test DPRX with no match. + * @test: KUnit test context. + * + * Verifies that requesting a DPRX source walks the connector list and returns + * -EINVAL when no matching DP connector is attached to the CRTC. A stateless + * connector and a writeback connector exercise both connector filter branches. + */ +static void dm_test_crtc_set_crc_source_dprx_no_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct drm_connector *dp_conn; + struct drm_connector *wb_conn; + struct dm_crtc_state *dm_state; + int ret; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_NONE; + + /* Stateless DP connector: skipped by the !state filter. */ + dp_conn = kunit_kzalloc(test, sizeof(*dp_conn), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dp_conn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, dp_conn, + &dm_test_crc_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crc_connector_cleanup, dp_conn), 0); + + /* Writeback connector bound to this CRTC: skipped by the WB filter. */ + wb_conn = kunit_kzalloc(test, sizeof(*wb_conn), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, wb_conn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wb_conn, + &dm_test_crc_connector_funcs, + DRM_MODE_CONNECTOR_WRITEBACK), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crc_connector_cleanup, wb_conn), 0); + drm_atomic_helper_connector_reset(wb_conn); + KUNIT_ASSERT_NOT_NULL(test, wb_conn->state); + wb_conn->state->crtc = &acrtc->base; + /* + * __drm_atomic_helper_connector_destroy_state() drops a connector + * reference when state->crtc is set. Balance it here since the CRTC is + * assigned directly rather than via drm_atomic_set_crtc_for_connector(), + * otherwise cleanup would drop the connector to zero and schedule an + * async free on the system workqueue. + */ + drm_connector_get(wb_conn); + + ret = amdgpu_dm_crtc_set_crc_source(&acrtc->base, "dprx"); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, acrtc->dm_irq_params.crc_src, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE); +} + +/** + * dm_test_crtc_handle_crc_irq_early_returns() - Test null/missing state exits. + * @test: KUnit test context. + * + * Verifies that the CRC IRQ handler safely ignores incomplete CRTC objects. + */ +static void dm_test_crtc_handle_crc_irq_early_returns(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + + amdgpu_dm_crtc_handle_crc_irq(NULL); + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + acrtc->base.state = &dm_state->base; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 0); +} + +/** + * dm_test_crtc_handle_crc_irq_disabled_source() - Test disabled source exit. + * @test: KUnit test context. + * + * Verifies that a present stream does not advance the skip counter when CRC + * capture is disabled. + */ +static void dm_test_crtc_handle_crc_irq_disabled_source(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + dm_state->stream = dm_kunit_alloc_stream(test, NULL); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_NONE; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 0); +} + +/** + * dm_test_crtc_handle_crc_irq_skips_initial_frames() - Test initial skip logic. + * @test: KUnit test context. + * + * Verifies that the first two enabled CRC IRQs only increment crc_skip_count, + * avoiding the later DC CRC read path. + */ +static void dm_test_crtc_handle_crc_irq_skips_initial_frames(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + dm_state->stream = dm_kunit_alloc_stream(test, NULL); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_CRTC; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 1); + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 2); +} + +/** + * dm_test_crtc_handle_crc_irq_dprx_after_skip() - Test DPRX post-skip exit. + * @test: KUnit test context. + * + * Verifies that enabled non-CRTC CRC sources do not call into DC CRC reads + * after the initial skip window. + */ +static void dm_test_crtc_handle_crc_irq_dprx_after_skip(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + fixture->dm_state->crc_skip_count = 2; + acrtc->base.state = &fixture->dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_DPRX; + dm_test_crc_dc_ctx = fixture; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_FALSE(test, fixture->get_crc_called); + KUNIT_EXPECT_EQ(test, fixture->dm_state->crc_skip_count, 2); +} + +/** + * dm_test_crtc_handle_crc_irq_get_crc_fails() - Test failed DC CRC read. + * @test: KUnit test context. + * + * Verifies that the IRQ handler exits after dc_stream_get_crc() returns false, + * before attempting to deliver a DRM CRC entry. + */ +static void dm_test_crtc_handle_crc_irq_get_crc_fails(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + fixture->dm_state->crc_skip_count = 2; + fixture->get_crc_return = false; + acrtc->base.state = &fixture->dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_CRTC; + dm_test_crc_dc_ctx = fixture; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_TRUE(test, fixture->get_crc_called); + KUNIT_EXPECT_EQ(test, fixture->dm_state->crc_skip_count, 2); +} + /** * dm_test_need_dp_aux() - Test dm_need_dp_aux(). * @test: KUnit test context. @@ -222,6 +926,30 @@ static struct kunit_case dm_crc_test_cases[] = { KUNIT_CASE(dm_test_need_crc_dither), /* amdgpu_dm_is_valid_crc_source() */ KUNIT_CASE(dm_test_is_valid_crc_source), + /* amdgpu_dm_crtc_get_crc_sources() */ + KUNIT_CASE(dm_test_crtc_get_crc_sources), + /* amdgpu_dm_crtc_verify_crc_source() */ + KUNIT_CASE(dm_test_crtc_verify_crc_source_valid), + KUNIT_CASE(dm_test_crtc_verify_crc_source_invalid), + /* amdgpu_dm_crtc_configure_crc_source() */ + KUNIT_CASE(dm_test_crtc_configure_crc_source_no_stream), + KUNIT_CASE(dm_test_crtc_configure_crc_source_dprx), + KUNIT_CASE(dm_test_crtc_configure_crc_source_dprx_dither), + KUNIT_CASE(dm_test_crtc_configure_crc_source_crtc), + KUNIT_CASE(dm_test_crtc_configure_crc_source_crtc_dcn36_poly), + KUNIT_CASE(dm_test_crtc_configure_crc_source_crtc_configure_fails), + KUNIT_CASE(dm_test_crtc_configure_crc_source_none), + /* amdgpu_dm_crtc_set_crc_source() */ + KUNIT_CASE(dm_test_crtc_set_crc_source_invalid), + KUNIT_CASE(dm_test_crtc_set_crc_source_none_no_stream), + KUNIT_CASE(dm_test_crtc_set_crc_source_none_commit), + KUNIT_CASE(dm_test_crtc_set_crc_source_dprx_no_connector), + /* amdgpu_dm_crtc_handle_crc_irq() */ + KUNIT_CASE(dm_test_crtc_handle_crc_irq_early_returns), + KUNIT_CASE(dm_test_crtc_handle_crc_irq_disabled_source), + KUNIT_CASE(dm_test_crtc_handle_crc_irq_skips_initial_frames), + KUNIT_CASE(dm_test_crtc_handle_crc_irq_dprx_after_skip), + KUNIT_CASE(dm_test_crtc_handle_crc_irq_get_crc_fails), /* dm_need_dp_aux() */ KUNIT_CASE(dm_test_need_dp_aux), /* dm_crc_source_should_start_dprx() */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c index 0edaf969f16b..ac21bbec92ab 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -296,13 +296,9 @@ static void dm_test_crtc_is_headless_null_adev(struct kunit *test) static void dm_test_crtc_is_headless_no_connectors(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev)); @@ -315,15 +311,11 @@ static void dm_test_crtc_is_headless_no_connectors(struct kunit *test) static void dm_test_crtc_is_headless_writeback_only(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK, @@ -339,15 +331,11 @@ static void dm_test_crtc_is_headless_writeback_only(struct kunit *test) static void dm_test_crtc_is_headless_disconnected_display(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA, @@ -363,15 +351,11 @@ static void dm_test_crtc_is_headless_disconnected_display(struct kunit *test) static void dm_test_crtc_is_headless_connected_display(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA, @@ -387,17 +371,13 @@ static void dm_test_crtc_is_headless_connected_display(struct kunit *test) static void dm_test_crtc_is_headless_mixed_connectors(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL); struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c new file mode 100644 index 000000000000..3da76794ece9 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_cursor.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_cursor.h" + +/* Tests for amdgpu_dm_should_update_native_cursor() */ + +/** + * dm_test_should_update_native_cursor_without_crtc - Test NULL crtc cases update native cursor + * @test: The KUnit test context + */ +static void dm_test_should_update_native_cursor_without_crtc(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_update_native_cursor(NULL, NULL, NULL, false)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_update_native_cursor(NULL, NULL, NULL, true)); +} + +/** + * dm_test_should_update_native_cursor_disable_native - Test disable path reads old crtc cursor mode + * @test: The KUnit test context + */ +static void dm_test_should_update_native_cursor_disable_native(struct kunit *test) +{ + struct dm_crtc_state *dm_crtc_state; + struct drm_atomic_commit *state; + struct drm_crtc *crtc; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->crtcs); + + crtc->index = 0; + dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE; + state->crtcs[0].old_state = &dm_crtc_state->base; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_should_update_native_cursor(state, crtc, NULL, false)); +} + +/** + * dm_test_should_update_native_cursor_enable_overlay - Test enable path reads new crtc cursor mode + * @test: The KUnit test context + */ +static void dm_test_should_update_native_cursor_enable_overlay(struct kunit *test) +{ + struct dm_crtc_state *dm_crtc_state; + struct drm_atomic_commit *state; + struct drm_crtc *crtc; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->crtcs); + + crtc->index = 0; + dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE; + state->crtcs[0].new_state = &dm_crtc_state->base; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_should_update_native_cursor(state, NULL, crtc, true)); +} + +/* Tests for dm_get_oriented_plane_size() */ + +/** + * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_0(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_0; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1920); + KUNIT_EXPECT_EQ(test, src_h, 1080); +} + +/** + * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate 90 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_90(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_90; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1080); + KUNIT_EXPECT_EQ(test, src_h, 1920); +} + +/** + * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate 180 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_180(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_180; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1920); + KUNIT_EXPECT_EQ(test, src_h, 1080); +} + +/** + * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate 270 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_270(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_270; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1080); + KUNIT_EXPECT_EQ(test, src_h, 1920); +} + +/* Tests for dm_get_plane_scale() */ + +/** + * dm_test_get_plane_scale_identity - Test Get plane scale identity + * @test: The KUnit test context + */ +static void dm_test_get_plane_scale_identity(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int scale_w = 0; + int scale_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_0; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + plane_state.crtc_w = 1920; + plane_state.crtc_h = 1080; + + dm_get_plane_scale(&plane_state, &scale_w, &scale_h); + + KUNIT_EXPECT_EQ(test, scale_w, 1000); + KUNIT_EXPECT_EQ(test, scale_h, 1000); +} + +/** + * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate 90 identity + * @test: The KUnit test context + */ +static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int scale_w = 0; + int scale_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_90; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + plane_state.crtc_w = 1080; + plane_state.crtc_h = 1920; + + dm_get_plane_scale(&plane_state, &scale_w, &scale_h); + + KUNIT_EXPECT_EQ(test, scale_w, 1000); + KUNIT_EXPECT_EQ(test, scale_h, 1000); +} + +/** + * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src width + * @test: The KUnit test context + */ +static void dm_test_get_plane_scale_zero_src_width(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int scale_w = 0; + int scale_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_0; + plane_state.src_w = 0; + plane_state.src_h = 1080 << 16; + plane_state.crtc_w = 100; + plane_state.crtc_h = 200; + + dm_get_plane_scale(&plane_state, &scale_w, &scale_h); + + KUNIT_EXPECT_EQ(test, scale_w, 0); + KUNIT_EXPECT_EQ(test, scale_h, 185); +} + +static struct kunit_case amdgpu_dm_cursor_tests[] = { + /* amdgpu_dm_should_update_native_cursor */ + KUNIT_CASE(dm_test_should_update_native_cursor_without_crtc), + KUNIT_CASE(dm_test_should_update_native_cursor_disable_native), + KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay), + /* dm_get_oriented_plane_size */ + KUNIT_CASE(dm_test_oriented_plane_size_rotate_0), + KUNIT_CASE(dm_test_oriented_plane_size_rotate_90), + KUNIT_CASE(dm_test_oriented_plane_size_rotate_180), + KUNIT_CASE(dm_test_oriented_plane_size_rotate_270), + /* dm_get_plane_scale */ + KUNIT_CASE(dm_test_get_plane_scale_identity), + KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity), + KUNIT_CASE(dm_test_get_plane_scale_zero_src_width), + {} +}; + +static struct kunit_suite amdgpu_dm_cursor_test_suite = { + .name = "amdgpu_dm_cursor", + .test_cases = amdgpu_dm_cursor_tests, +}; + +kunit_test_suite(amdgpu_dm_cursor_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_cursor"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c index bf90ccfbf431..ead43eeb38c7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c @@ -6,14 +6,20 @@ */ #include +#include #include "dc.h" #include "dc/inc/core_types.h" +#include "dc/inc/hw/dmcu.h" +#include "dc/inc/hw/abm.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" +#include "dm_services.h" #include "dmub/dmub_srv.h" #include "amdgpu_dm_dmub.h" +#define DM_TEST_FW_SIZE 512 + /* Tests for dm_register_dmub_notify_callback() */ static void dummy_callback(struct amdgpu_device *adev, @@ -21,6 +27,99 @@ static void dummy_callback(struct amdgpu_device *adev, { } +static bool dm_test_dmub_supported(struct dmub_srv *dmub) +{ + return true; +} + +static bool dm_test_dmub_unsupported(struct dmub_srv *dmub) +{ + return false; +} + +static bool dm_test_dmub_hw_initialized(struct dmub_srv *dmub) +{ + return true; +} + +static union dmub_fw_boot_status dm_test_dmub_fw_ready(struct dmub_srv *dmub) +{ + union dmub_fw_boot_status status = { 0 }; + + status.bits.dal_fw = 1; + status.bits.mailbox_rdy = 1; + return status; +} + +static union dmub_fw_boot_status dm_test_dmub_fw_not_ready(struct dmub_srv *dmub) +{ + union dmub_fw_boot_status status = { 0 }; + + return status; +} + +static void dm_test_dmub_init_reg_offsets(struct dmub_srv *dmub, + struct dc_context *ctx) +{ +} + +static bool dm_test_dmcu_init(struct dmcu *dmcu) +{ + return true; +} + +static bool dm_test_dmcu_is_initialized(struct dmcu *dmcu) +{ + return true; +} + +static const struct dmcu_funcs dm_test_dmcu_funcs = { + .dmcu_init = dm_test_dmcu_init, + .is_dmcu_initialized = dm_test_dmcu_is_initialized, +}; + +static struct dmub_srv *dm_test_alloc_dmub_srv(struct kunit *test) +{ + struct dmub_srv *dmub_srv; + + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + + dmub_srv->sw_init = true; + dmub_srv->hw_init = true; + dmub_srv->power_state = DMUB_POWER_STATE_D0; + dmub_srv->hw_funcs.is_supported = dm_test_dmub_supported; + dmub_srv->hw_funcs.is_hw_init = dm_test_dmub_hw_initialized; + dmub_srv->hw_funcs.get_fw_status = dm_test_dmub_fw_ready; + dmub_srv->hw_funcs.init_reg_offsets = dm_test_dmub_init_reg_offsets; + + return dmub_srv; +} + +static const struct firmware *dm_test_alloc_dmub_fw(struct kunit *test) +{ + struct dmcub_firmware_header_v1_0 *hdr; + struct firmware *fw; + u8 *data; + + fw = kunit_kzalloc(test, sizeof(*fw), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fw); + + data = kunit_kzalloc(test, DM_TEST_FW_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, data); + + hdr = (struct dmcub_firmware_header_v1_0 *)data; + hdr->header.ucode_array_offset_bytes = cpu_to_le32(0); + hdr->header.ucode_version = cpu_to_le32(DMUB_FW_VERSION(9, 9, 9)); + hdr->inst_const_bytes = cpu_to_le32(PSP_HEADER_BYTES_256); + hdr->bss_data_bytes = cpu_to_le32(0); + + fw->size = DM_TEST_FW_SIZE; + fw->data = data; + + return fw; +} + /** * dm_test_register_dmub_notify_callback_null_callback - Test null callback is rejected * @test: The KUnit test context @@ -297,6 +396,22 @@ static void dm_test_dmub_aux_fused_io_callback_max_ddc_line(struct kunit *test) KUNIT_EXPECT_EQ(test, reply_ddc_line, notify_ddc_line); } +/** + * dm_test_dmub_aux_fused_io_callback_null_args - Test the NULL-argument guard + * @test: The KUnit test context + * + * Passing a NULL device triggers the defensive guard (an ASSERT that maps to + * WARN_ON_ONCE in this build) and returns early without dereferencing the + * arguments. The call must not crash. + */ +static void dm_test_dmub_aux_fused_io_callback_null_args(struct kunit *test) +{ + struct dmub_notification notify = {}; + + /* Must not crash; guard hits ASSERT (WARN_ON_ONCE) and returns. */ + dm_dmub_aux_fused_io_callback(NULL, ¬ify); +} + /* Tests for dm_get_default_ips_mode() */ /** @@ -395,6 +510,7 @@ static void dm_test_get_default_ips_mode_newer_default(struct kunit *test) static struct amdgpu_device *dm_test_alloc_adev_with_dc(struct kunit *test) { struct amdgpu_device *adev; + struct dc_context *ctx; struct dc *dc; struct resource_pool *res_pool; @@ -407,12 +523,47 @@ static struct amdgpu_device *dm_test_alloc_adev_with_dc(struct kunit *test) res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + dc->res_pool = res_pool; + dc->ctx = ctx; + ctx->dc = dc; + ctx->driver_context = adev; adev->dm.dc = dc; return adev; } +static struct amdgpu_device *dm_test_alloc_adev_with_dmub(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_srv_fb_info *fb_info; + int i; + + adev = dm_test_alloc_adev_with_dc(test); + + fb_info = kunit_kzalloc(test, sizeof(*fb_info), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb_info); + + fb_info->num_fb = DMUB_WINDOW_TOTAL; + for (i = 0; i < DMUB_WINDOW_TOTAL; i++) { + fb_info->fb[i].size = PAGE_SIZE; + fb_info->fb[i].cpu_addr = kunit_kzalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb_info->fb[i].cpu_addr); + } + + adev->dm.dmub_srv = dm_test_alloc_dmub_srv(test); + adev->dm.dmub_fb_info = fb_info; + adev->dm.dmub_fw = dm_test_alloc_dmub_fw(test); + adev->bios = kunit_kzalloc(test, 4, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->bios); + adev->bios_size = 4; + adev->dm.fw_inst_size = 0; + + return adev; +} + /** * dm_test_dmub_hw_init_no_dmub_srv - Test hw init returns 0 when DMUB unsupported * @test: The KUnit test context @@ -476,6 +627,177 @@ static void dm_test_dmub_hw_init_no_firmware(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL); } +/** + * dm_test_dmub_hw_init_success_fake_dmub - Test hw init with a fake DMUB service + * @test: The KUnit test context + * + * With fake DMUB callbacks and preallocated framebuffer windows, the init path + * should reach DMUB service initialization without real register access. + */ +static void dm_test_dmub_hw_init_success_fake_dmub(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dmub_srv->hw_init); + KUNIT_EXPECT_NOT_NULL(test, adev->dm.dc->ctx->dmub_srv); +} + +/** + * dm_test_dmub_hw_init_no_hw_support - Test hw init returns 0 when HW is unsupported + * @test: The KUnit test context + * + * When the DMUB service reports no hardware support, dm_dmub_hw_init() should + * log and return 0 without initializing the DMUB hardware. + */ +static void dm_test_dmub_hw_init_no_hw_support(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_srv->hw_funcs.is_supported = dm_test_dmub_unsupported; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_NULL(test, adev->dm.dc->ctx->dmub_srv); +} + +/** + * dm_test_dmub_hw_init_bss_data - Test hw init copies BSS data into FB memory + * @test: The KUnit test context + * + * When the DMUB firmware declares a non-zero BSS data size, dm_dmub_hw_init() + * should copy that region into the BSS framebuffer window. + */ +static void dm_test_dmub_hw_init_bss_data(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + struct dmcub_firmware_header_v1_0 *hdr; + + hdr = (struct dmcub_firmware_header_v1_0 *)adev->dm.dmub_fw->data; + hdr->bss_data_bytes = cpu_to_le32(16); + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dmub_srv->hw_init); +} + +/** + * dm_test_dmub_hw_init_hw_init_fails - Test hw init returns -EINVAL on DMUB init failure + * @test: The KUnit test context + * + * A framebuffer-info window count below the required total makes + * dmub_srv_hw_init() reject the request, so dm_dmub_hw_init() logs and + * returns -EINVAL. (The rejection path emits a one-time WARN via ASSERT.) + */ +static void dm_test_dmub_hw_init_hw_init_fails(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_fb_info->num_fb = 0; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL); +} + +/** + * dm_test_dmub_hw_init_auto_load_timeout - Test hw init tolerates an auto-load timeout + * @test: The KUnit test context + * + * When the DMUB firmware never reports ready, dmub_srv_wait_for_auto_load() + * times out; dm_dmub_hw_init() only warns and still completes successfully. + */ +static void dm_test_dmub_hw_init_auto_load_timeout(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_srv->hw_funcs.get_fw_status = dm_test_dmub_fw_not_ready; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_NOT_NULL(test, adev->dm.dc->ctx->dmub_srv); +} + +/** + * dm_test_dmub_hw_init_apu_dpia_dcn35 - Test hw init APU DPIA and DCN35 hw params + * @test: The KUnit test context + * + * On a DCN3.5 APU with a USB4 DPIA link, dm_dmub_hw_init() should populate the + * DPIA hw params and the DCN3.5 IPS-sequential hw params before initializing + * the fake DMUB service. + */ +static void dm_test_dmub_hw_init_apu_dpia_dcn35(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0); + adev->dm.dc->caps.is_apu = true; + adev->dm.dc->res_pool->usb4_dpia_count = 1; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dmub_srv->hw_init); +} + +/** + * dm_test_dmub_hw_init_sanity_checks_dcn31 - Test hw init enables DCN31 sanity checks + * @test: The KUnit test context + * + * On DCN3.1.2 with a DMCUB firmware version in the affected range, + * dm_dmub_hw_init() should enable the DC sanity-check debug flag. + */ +static void dm_test_dmub_hw_init_sanity_checks_dcn31(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 2); + adev->dm.dmcub_fw_version = DMUB_FW_VERSION(4, 0, 10); + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dc->debug.sanity_checks); +} + +/** + * dm_test_dmub_hw_init_sanity_checks_dcn314 - Test hw init enables DCN314 sanity checks + * @test: The KUnit test context + * + * On DCN3.1.4 with a DMCUB firmware version in the affected range, + * dm_dmub_hw_init() should enable the DC sanity-check debug flag. + */ +static void dm_test_dmub_hw_init_sanity_checks_dcn314(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + adev->dm.dmcub_fw_version = DMUB_FW_VERSION(4, 0, 10); + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dc->debug.sanity_checks); +} + +/** + * dm_test_dmub_hw_init_dmcu_abm - Test hw init initializes DMCU and ABM when present + * @test: The KUnit test context + * + * When the resource pool exposes a DMCU and ABM, dm_dmub_hw_init() should + * program the PSP version, invoke the DMCU init callback, and record the + * running state reported by the DMCU. + */ +static void dm_test_dmub_hw_init_dmcu_abm(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + struct dmcu *dmcu; + struct abm *abm; + + dmcu = kunit_kzalloc(test, sizeof(*dmcu), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmcu); + + abm = kunit_kzalloc(test, sizeof(*abm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, abm); + + dmcu->funcs = &dm_test_dmcu_funcs; + dmcu->psp_version = 0x12345678; + adev->dm.dc->res_pool->dmcu = dmcu; + adev->dm.dc->res_pool->abm = abm; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, abm->dmcu_is_running); +} + /* Tests for dm_dmub_hw_resume() */ /** @@ -495,6 +817,79 @@ static void dm_test_dmub_hw_resume_no_dmub_srv(struct kunit *test) dm_dmub_hw_resume(adev); } +/** + * dm_test_dmub_hw_resume_initialized_dmub - Test resume waits for initialized DMUB + * @test: The KUnit test context + * + * When the fake DMUB service reports hardware already initialized, resume + * should only wait for firmware readiness and skip full reinitialization. + */ +static void dm_test_dmub_hw_resume_initialized_dmub(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + + adev->dm.dmub_srv = dm_test_alloc_dmub_srv(test); + + /* Must not crash. */ + dm_dmub_hw_resume(adev); +} + +/** + * dm_test_dmub_hw_resume_full_init - Test resume performs full init when uninitialized + * @test: The KUnit test context + * + * When the fake DMUB service reports hardware not yet initialized, resume + * should continue into a full dm_dmub_hw_init() and create the DC DMUB + * server. + */ +static void dm_test_dmub_hw_resume_full_init(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_srv->hw_init = false; + + dm_dmub_hw_resume(adev); + + KUNIT_EXPECT_NOT_NULL(test, adev->dm.dc->ctx->dmub_srv); +} + +/** + * dm_test_dmub_hw_resume_init_check_failed - Test resume handles a failed init check + * @test: The KUnit test context + * + * When the DMUB service is not software-initialized, the init-state query + * fails and resume continues into dm_dmub_hw_init(), which also fails; the + * call must warn and return without crashing. + */ +static void dm_test_dmub_hw_resume_init_check_failed(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_srv->sw_init = false; + + /* Must not crash. */ + dm_dmub_hw_resume(adev); +} + +/** + * dm_test_dmub_hw_resume_auto_load_timeout - Test resume tolerates an auto-load timeout + * @test: The KUnit test context + * + * When the DMUB reports hardware already initialized but the firmware never + * signals ready, resume's auto-load wait times out and only warns; the call + * must not crash. + */ +static void dm_test_dmub_hw_resume_auto_load_timeout(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + + adev->dm.dmub_srv = dm_test_alloc_dmub_srv(test); + adev->dm.dmub_srv->hw_funcs.get_fw_status = dm_test_dmub_fw_not_ready; + + /* Must not crash; auto-load times out and only warns. */ + dm_dmub_hw_resume(adev); +} + /* Tests for dm_dmub_sw_init() */ /** @@ -537,6 +932,421 @@ static void dm_test_init_microcode_unsupported_asic(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_init_microcode(adev), 0); } +/* Tests for dm_dmub_get_vbios_bounding_box() */ + +/** + * dm_test_dmub_get_vbios_bounding_box_default_null - Test default IP version returns NULL + * @test: The KUnit test context + * + * For an IP version without a bounding-box size mapping, the switch falls + * through to the default case and dm_dmub_get_vbios_bounding_box() returns + * NULL without allocating GPU memory or issuing GPINT commands. + */ +static void dm_test_dmub_get_vbios_bounding_box_default_null(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0); + + KUNIT_EXPECT_NULL(test, dm_dmub_get_vbios_bounding_box(adev)); +} + +/* Tests for dm_execute_dmub_cmd() */ + +/** + * dm_test_execute_dmub_cmd_null_dmub_srv - Test command execution fails without DMUB service + * @test: The KUnit test context + * + * With no DC DMUB service on the context, dc_dmub_srv_cmd_run() returns false + * and dm_execute_dmub_cmd() propagates that failure. + */ +static void dm_test_execute_dmub_cmd_null_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + union dmub_rb_cmd *cmd; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + cmd = kunit_kzalloc(test, sizeof(*cmd), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cmd); + + spin_lock_init(&adev->dm.dmub_lock); + ctx->driver_context = adev; + ctx->dmub_srv = NULL; + + KUNIT_EXPECT_FALSE(test, + dm_execute_dmub_cmd(ctx, cmd, DM_DMUB_WAIT_TYPE_NO_WAIT)); +} + +/* Tests for amdgpu_dm_process_dmub_aux_transfer_sync() */ + +/** + * dm_test_process_dmub_aux_transfer_sync_engine_acquire - Test AUX transfer engine-acquire failure + * @test: The KUnit test context + * + * With dc->link_count == 0, dc_process_dmub_aux_transfer_async() rejects the + * link index and amdgpu_dm_process_dmub_aux_transfer_sync() reports an + * engine-acquire error and returns -1. + */ +static void dm_test_process_dmub_aux_transfer_sync_engine_acquire(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc *dc; + struct aux_payload *payload; + struct dmub_notification *notify; + enum aux_return_code_type result = AUX_RET_SUCCESS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + dc->link_count = 0; + ctx->dc = dc; + ctx->driver_context = adev; + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + + ret = amdgpu_dm_process_dmub_aux_transfer_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, -1); + KUNIT_EXPECT_EQ(test, result, AUX_RET_ERROR_ENGINE_ACQUIRE); +} + +/** + * dm_test_process_dmub_aux_transfer_sync_protocol_error - Test AUX protocol error result + * @test: The KUnit test context + * + * With the completion pre-signaled and a fake DC DMUB service that rejects the + * command after construction, the sync helper should propagate the notification + * result without waiting for real firmware. + */ +static void dm_test_process_dmub_aux_transfer_sync_protocol_error(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_context *dc_ctx; + struct dc *dc; + struct dc_link *link; + struct ddc_service *ddc; + struct aux_payload *payload; + struct dmub_notification *notify; + enum aux_return_code_type result = AUX_RET_SUCCESS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddc); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + link->ddc = ddc; + dc->ctx = dc_ctx; + dc->link_count = 1; + dc->links[0] = link; + dc_ctx->dc = dc; + dc_ctx->driver_context = adev; + dc_ctx->dmub_srv = NULL; + ctx->dc = dc; + ctx->driver_context = adev; + spin_lock_init(&adev->dm.dmub_lock); + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + complete(&adev->dm.dmub_aux_transfer_done); + notify->result = AUX_RET_ERROR_PROTOCOL_ERROR; + + ret = amdgpu_dm_process_dmub_aux_transfer_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, -1); + KUNIT_EXPECT_EQ(test, result, AUX_RET_ERROR_PROTOCOL_ERROR); +} + +/** + * dm_test_process_dmub_aux_transfer_sync_copies_data - Test AUX reply data copy + * @test: The KUnit test context + * + * On a successful notification, the sync helper should copy the bounded reply + * data and report the high-nibble command reply when present. + */ +static void dm_test_process_dmub_aux_transfer_sync_copies_data(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_context *dc_ctx; + struct dc *dc; + struct dc_link *link; + struct ddc_service *ddc; + struct aux_payload *payload; + struct dmub_notification *notify; + enum aux_return_code_type result = AUX_RET_ERROR_UNKNOWN; + u8 data[4] = { 0 }; + u8 reply = 0; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddc); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + link->ddc = ddc; + dc->ctx = dc_ctx; + dc->link_count = 1; + dc->links[0] = link; + dc_ctx->dc = dc; + dc_ctx->driver_context = adev; + dc_ctx->dmub_srv = NULL; + ctx->dc = dc; + ctx->driver_context = adev; + spin_lock_init(&adev->dm.dmub_lock); + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + complete(&adev->dm.dmub_aux_transfer_done); + payload->data = data; + payload->reply = &reply; + payload->length = sizeof(data); + notify->result = AUX_RET_SUCCESS; + notify->aux_reply.command = 0xA4; + notify->aux_reply.length = 3; + notify->aux_reply.data[0] = 0x11; + notify->aux_reply.data[1] = 0x22; + notify->aux_reply.data[2] = 0x33; + + ret = amdgpu_dm_process_dmub_aux_transfer_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, 3); + KUNIT_EXPECT_EQ(test, result, AUX_RET_SUCCESS); + KUNIT_EXPECT_EQ(test, reply, 0xA); + KUNIT_EXPECT_EQ(test, data[0], 0x11); + KUNIT_EXPECT_EQ(test, data[1], 0x22); + KUNIT_EXPECT_EQ(test, data[2], 0x33); +} + +/** + * dm_test_process_dmub_aux_transfer_sync_zero_length - Test AUX reply with no data + * @test: The KUnit test context + * + * On a successful notification whose reply carries no data, the sync helper + * takes the zero-length branch and returns the reply length (0) without + * copying any payload data. + */ +static void dm_test_process_dmub_aux_transfer_sync_zero_length(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_context *dc_ctx; + struct dc *dc; + struct dc_link *link; + struct ddc_service *ddc; + struct aux_payload *payload; + struct dmub_notification *notify; + enum aux_return_code_type result = AUX_RET_ERROR_UNKNOWN; + u8 data[4] = { 0 }; + u8 reply = 0; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddc); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + link->ddc = ddc; + dc->ctx = dc_ctx; + dc->link_count = 1; + dc->links[0] = link; + dc_ctx->dc = dc; + dc_ctx->driver_context = adev; + dc_ctx->dmub_srv = NULL; + ctx->dc = dc; + ctx->driver_context = adev; + spin_lock_init(&adev->dm.dmub_lock); + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + complete(&adev->dm.dmub_aux_transfer_done); + payload->data = data; + payload->reply = &reply; + payload->length = sizeof(data); + notify->result = AUX_RET_SUCCESS; + notify->aux_reply.command = 0x03; + notify->aux_reply.length = 0; + + ret = amdgpu_dm_process_dmub_aux_transfer_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, result, AUX_RET_SUCCESS); + KUNIT_EXPECT_EQ(test, reply, 0x3); +} + +/* Tests for amdgpu_dm_process_dmub_set_config_sync() */ + +/** + * dm_test_process_dmub_set_config_sync_unknown_error - Test SET_CONFIG completes with unknown error + * @test: The KUnit test context + * + * With no DC DMUB service, dc_process_dmub_set_config_async() cannot reach the + * firmware and reports the command as completed with SET_CONFIG_UNKNOWN_ERROR, + * so amdgpu_dm_process_dmub_set_config_sync() returns 0 with that status. + */ +static void dm_test_process_dmub_set_config_sync_unknown_error(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_context *dc_ctx; + struct dc *dc; + struct dc_link *link; + struct set_config_cmd_payload *payload; + struct dmub_notification *notify; + enum set_config_status result = SET_CONFIG_PENDING; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + dc->ctx = dc_ctx; + dc_ctx->dmub_srv = NULL; + dc->links[0] = link; + ctx->dc = dc; + ctx->driver_context = adev; + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + + ret = amdgpu_dm_process_dmub_set_config_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, result, SET_CONFIG_UNKNOWN_ERROR); +} + +/* Tests for abort_fused_io() */ + +/** + * dm_test_abort_fused_io_no_dmub_srv - Test fused IO abort is a safe no-op without DMUB service + * @test: The KUnit test context + * + * abort_fused_io() builds an abort command and submits it via + * dm_execute_dmub_cmd(); with no DC DMUB service the submission fails + * silently and the call must not crash. + */ +static void dm_test_abort_fused_io_no_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dmub_cmd_fused_request *req; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + req = kunit_kzalloc(test, sizeof(*req), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, req); + + spin_lock_init(&adev->dm.dmub_lock); + ctx->driver_context = adev; + ctx->dmub_srv = NULL; + + /* Must not crash. */ + abort_fused_io(ctx, req); +} + static struct kunit_case amdgpu_dm_dmub_tests[] = { /* dm_register_dmub_notify_callback() */ KUNIT_CASE(dm_test_register_dmub_notify_callback_null_callback), @@ -551,6 +1361,7 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { /* dm_dmub_aux_fused_io_callback() */ KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes), KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_max_ddc_line), + KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_null_args), /* dm_get_default_ips_mode() */ KUNIT_CASE(dm_test_get_default_ips_mode_dcn35), KUNIT_CASE(dm_test_get_default_ips_mode_dcn351), @@ -561,12 +1372,38 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { KUNIT_CASE(dm_test_dmub_hw_init_no_dmub_srv), KUNIT_CASE(dm_test_dmub_hw_init_no_fb_info), KUNIT_CASE(dm_test_dmub_hw_init_no_firmware), + KUNIT_CASE(dm_test_dmub_hw_init_success_fake_dmub), + KUNIT_CASE(dm_test_dmub_hw_init_no_hw_support), + KUNIT_CASE(dm_test_dmub_hw_init_bss_data), + KUNIT_CASE(dm_test_dmub_hw_init_hw_init_fails), + KUNIT_CASE(dm_test_dmub_hw_init_auto_load_timeout), + KUNIT_CASE(dm_test_dmub_hw_init_apu_dpia_dcn35), + KUNIT_CASE(dm_test_dmub_hw_init_sanity_checks_dcn31), + KUNIT_CASE(dm_test_dmub_hw_init_sanity_checks_dcn314), + KUNIT_CASE(dm_test_dmub_hw_init_dmcu_abm), /* dm_dmub_hw_resume() */ KUNIT_CASE(dm_test_dmub_hw_resume_no_dmub_srv), + KUNIT_CASE(dm_test_dmub_hw_resume_initialized_dmub), + KUNIT_CASE(dm_test_dmub_hw_resume_full_init), + KUNIT_CASE(dm_test_dmub_hw_resume_init_check_failed), + KUNIT_CASE(dm_test_dmub_hw_resume_auto_load_timeout), /* dm_dmub_sw_init() */ KUNIT_CASE(dm_test_dmub_sw_init_unsupported_asic), /* dm_init_microcode() */ KUNIT_CASE(dm_test_init_microcode_unsupported_asic), + /* dm_dmub_get_vbios_bounding_box() */ + KUNIT_CASE(dm_test_dmub_get_vbios_bounding_box_default_null), + /* dm_execute_dmub_cmd() */ + KUNIT_CASE(dm_test_execute_dmub_cmd_null_dmub_srv), + /* amdgpu_dm_process_dmub_aux_transfer_sync() */ + KUNIT_CASE(dm_test_process_dmub_aux_transfer_sync_engine_acquire), + KUNIT_CASE(dm_test_process_dmub_aux_transfer_sync_protocol_error), + KUNIT_CASE(dm_test_process_dmub_aux_transfer_sync_copies_data), + KUNIT_CASE(dm_test_process_dmub_aux_transfer_sync_zero_length), + /* amdgpu_dm_process_dmub_set_config_sync() */ + KUNIT_CASE(dm_test_process_dmub_set_config_sync_unknown_error), + /* abort_fused_io() */ + KUNIT_CASE(dm_test_abort_fused_io_no_dmub_srv), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c new file mode 100644 index 000000000000..0ae636dcbbe4 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_freesync.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include + +#include "dc.h" +#include "inc/core_types.h" +#include "amd_shared.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_freesync.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +/* Tests for amdgpu_dm_is_timing_unchanged_for_freesync() */ + +/** + * dm_test_timing_unchanged_null_args - Test NULL crtc states return false + * @test: The KUnit test context + */ +static void dm_test_timing_unchanged_null_args(struct kunit *test) +{ + struct drm_crtc_state crtc_state = { 0 }; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(NULL, &crtc_state)); + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(&crtc_state, NULL)); +} + +/** + * dm_test_timing_unchanged_identical_modes - Test identical modes are not "unchanged" + * @test: The KUnit test context + * + * The helper only returns true when vtotal/vsync shift (vrr) while the rest + * of the timing stays fixed, so identical modes must return false. + */ +static void dm_test_timing_unchanged_identical_modes(struct kunit *test) +{ + struct drm_crtc_state old_state = { 0 }; + struct drm_crtc_state new_state = { 0 }; + + old_state.mode.clock = 148500; + old_state.mode.hdisplay = 1920; + old_state.mode.vdisplay = 1080; + old_state.mode.htotal = 2200; + old_state.mode.vtotal = 1125; + new_state.mode = old_state.mode; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(&old_state, &new_state)); +} + +/** + * dm_test_timing_unchanged_vrr_shift - Test vrr-style vtotal/vsync shift is detected + * @test: The KUnit test context + */ +static void dm_test_timing_unchanged_vrr_shift(struct kunit *test) +{ + struct drm_crtc_state old_state = { 0 }; + struct drm_crtc_state new_state = { 0 }; + + old_state.mode.clock = 148500; + old_state.mode.hdisplay = 1920; + old_state.mode.vdisplay = 1080; + old_state.mode.htotal = 2200; + old_state.mode.vtotal = 1125; + old_state.mode.hsync_start = 2008; + old_state.mode.vsync_start = 1084; + old_state.mode.hsync_end = 2052; + old_state.mode.vsync_end = 1089; + + /* Same horizontal timing, vertical totals/sync shifted by 125 lines */ + new_state.mode = old_state.mode; + new_state.mode.vtotal = 1250; + new_state.mode.vsync_start = 1209; + new_state.mode.vsync_end = 1214; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(&old_state, &new_state)); +} + +/** + * dm_test_timing_unchanged_clock_changed - Test pixel clock change returns false + * @test: The KUnit test context + */ +static void dm_test_timing_unchanged_clock_changed(struct kunit *test) +{ + struct drm_crtc_state old_state = { 0 }; + struct drm_crtc_state new_state = { 0 }; + + old_state.mode.clock = 148500; + old_state.mode.htotal = 2200; + old_state.mode.vtotal = 1125; + old_state.mode.vsync_start = 1084; + old_state.mode.vsync_end = 1089; + + new_state.mode = old_state.mode; + new_state.mode.clock = 297000; + new_state.mode.vtotal = 1250; + new_state.mode.vsync_start = 1209; + new_state.mode.vsync_end = 1214; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(&old_state, &new_state)); +} + +/* Tests for amdgpu_dm_set_freesync_fixed_config() */ + +/** + * dm_test_set_freesync_fixed_config_60hz - Test fixed refresh computed for 1080p60 + * @test: The KUnit test context + */ +static void dm_test_set_freesync_fixed_config_60hz(struct kunit *test) +{ + struct dm_crtc_state dm_crtc_state = { 0 }; + + dm_crtc_state.base.mode.clock = 148500; + dm_crtc_state.base.mode.htotal = 2200; + dm_crtc_state.base.mode.vtotal = 1125; + + amdgpu_dm_set_freesync_fixed_config(&dm_crtc_state); + + KUNIT_EXPECT_EQ(test, (int)dm_crtc_state.freesync_config.state, + (int)VRR_STATE_ACTIVE_FIXED); + /* 148500 kHz / (2200 * 1125) = 60 Hz = 60000000 uHz */ + KUNIT_EXPECT_EQ(test, dm_crtc_state.freesync_config.fixed_refresh_in_uhz, + 60000000U); +} + +/* Tests for amdgpu_dm_is_dc_timing_adjust_needed() */ + +/** + * dm_test_dc_timing_adjust_pending - Test a pending hw timing adjust forces true + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_pending(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + stream->adjust.timing_adjust_pending = 1; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state)); +} + +/** + * dm_test_dc_timing_adjust_active_fixed - Test VRR active-fixed forces true + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_active_fixed(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + new_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state)); +} + +/** + * dm_test_dc_timing_adjust_vrr_toggle - Test a change in vrr active state forces true + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_vrr_toggle(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + old_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + new_state->freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state)); +} + +/** + * dm_test_dc_timing_adjust_not_needed - Test steady-state timing needs no adjust + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_not_needed(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + old_state->freesync_config.state = VRR_STATE_INACTIVE; + new_state->freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state)); +} + +/* Tests for amdgpu_dm_get_freesync_config_for_crtc() */ + +struct dm_test_freesync_ctx { + struct amdgpu_dm_connector *aconnector; + struct dm_crtc_state *crtc_state; + struct dm_connector_state *conn_state; + struct dc_stream_state *stream; +}; + +static struct dm_test_freesync_ctx *dm_test_freesync_ctx_alloc(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state); + ctx->conn_state = kunit_kzalloc(test, sizeof(*ctx->conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); + ctx->stream = dm_kunit_alloc_stream(test, NULL); + + ctx->conn_state->base.connector = &ctx->aconnector->base; + ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_DisplayPort; + ctx->crtc_state->stream = ctx->stream; + + /* 1080p60 timing so drm_mode_vrefresh() == 60 */ + ctx->crtc_state->base.mode.clock = 148500; + ctx->crtc_state->base.mode.htotal = 2200; + ctx->crtc_state->base.mode.vtotal = 1125; + + return ctx; +} + +/** + * dm_test_freesync_config_writeback - Test writeback connector is left untouched + * @test: The KUnit test context + */ +static void dm_test_freesync_config_writeback(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + ctx->crtc_state->vrr_supported = true; /* sentinel: must stay set */ + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + /* Writeback: early return leaves vrr_supported sentinel untouched */ + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); +} + +/** + * dm_test_freesync_config_not_capable - Test a non-freesync sink reports UNSUPPORTED + * @test: The KUnit test context + */ +static void dm_test_freesync_config_not_capable(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = false; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_UNSUPPORTED); +} + +/** + * dm_test_freesync_config_out_of_range - Test a refresh outside the range is UNSUPPORTED + * @test: The KUnit test context + */ +static void dm_test_freesync_config_out_of_range(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 90; /* 60 < 90 -> out of range */ + ctx->aconnector->max_vfreq = 120; + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_UNSUPPORTED); +} + +/** + * dm_test_freesync_config_active_variable - Test vrr_enabled yields ACTIVE_VARIABLE + * @test: The KUnit test context + */ +static void dm_test_freesync_config_active_variable(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + ctx->crtc_state->base.vrr_enabled = true; + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_TRUE(test, ctx->stream->ignore_msa_timing_param); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_ACTIVE_VARIABLE); + KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.min_refresh_in_uhz, + 48000000U); + KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.max_refresh_in_uhz, + 120000000U); + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.vsif_supported); + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.btr); +} + +/** + * dm_test_freesync_config_inactive - Test supported-but-off yields INACTIVE + * @test: The KUnit test context + */ +static void dm_test_freesync_config_inactive(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + ctx->crtc_state->base.vrr_enabled = false; + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_INACTIVE); +} + +/** + * dm_test_freesync_config_active_fixed - Test freesync-video mode yields ACTIVE_FIXED + * @test: The KUnit test context + */ +static void dm_test_freesync_config_active_fixed(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + /* Pre-set fixed state selects the freesync-video (fixed) path */ + ctx->crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + ctx->crtc_state->freesync_config.fixed_refresh_in_uhz = 60000000; + ctx->crtc_state->base.vrr_enabled = true; /* ignored on the fixed path */ + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_ACTIVE_FIXED); + KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.fixed_refresh_in_uhz, + 60000000U); +} + +/* Tests for amdgpu_dm_reset_freesync_config_for_crtc() */ + +/** + * dm_test_reset_freesync_config - Test reset clears vrr support and info packet + * @test: The KUnit test context + */ +static void dm_test_reset_freesync_config(struct kunit *test) +{ + struct dm_crtc_state *crtc_state; + + crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc_state); + + crtc_state->vrr_supported = true; + crtc_state->vrr_infopacket.valid = true; + + amdgpu_dm_reset_freesync_config_for_crtc(crtc_state); + + KUNIT_EXPECT_FALSE(test, crtc_state->vrr_supported); + KUNIT_EXPECT_FALSE(test, crtc_state->vrr_infopacket.valid); +} + +static struct kunit_case amdgpu_dm_freesync_tests[] = { + /* amdgpu_dm_is_timing_unchanged_for_freesync */ + KUNIT_CASE(dm_test_timing_unchanged_null_args), + KUNIT_CASE(dm_test_timing_unchanged_identical_modes), + KUNIT_CASE(dm_test_timing_unchanged_vrr_shift), + KUNIT_CASE(dm_test_timing_unchanged_clock_changed), + /* amdgpu_dm_set_freesync_fixed_config */ + KUNIT_CASE(dm_test_set_freesync_fixed_config_60hz), + /* amdgpu_dm_is_dc_timing_adjust_needed */ + KUNIT_CASE(dm_test_dc_timing_adjust_pending), + KUNIT_CASE(dm_test_dc_timing_adjust_active_fixed), + KUNIT_CASE(dm_test_dc_timing_adjust_vrr_toggle), + KUNIT_CASE(dm_test_dc_timing_adjust_not_needed), + /* amdgpu_dm_get_freesync_config_for_crtc */ + KUNIT_CASE(dm_test_freesync_config_writeback), + KUNIT_CASE(dm_test_freesync_config_not_capable), + KUNIT_CASE(dm_test_freesync_config_out_of_range), + KUNIT_CASE(dm_test_freesync_config_active_variable), + KUNIT_CASE(dm_test_freesync_config_inactive), + KUNIT_CASE(dm_test_freesync_config_active_fixed), + /* amdgpu_dm_reset_freesync_config_for_crtc */ + KUNIT_CASE(dm_test_reset_freesync_config), + {} +}; + +static struct kunit_suite amdgpu_dm_freesync_test_suite = { + .name = "amdgpu_dm_freesync", + .test_cases = amdgpu_dm_freesync_tests, +}; + +kunit_test_suite(amdgpu_dm_freesync_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_freesync"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index 619b4a80c82b..cfdcc44581b1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -7,8 +7,18 @@ #include #include +#include +#include +#include +#include +#include + +#include "amdgpu.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_hdcp.h" +#include "amdgpu_dm_kunit_test_helpers.h" +#include "hdcp_psp.h" static void dummy_work_fn(struct work_struct *work) {} @@ -402,8 +412,2105 @@ static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *te cancel_delayed_work_sync(&work->watchdog_timer_dwork); cancel_delayed_work_sync(&work->property_validate_dwork); } + +/** + * dm_test_process_output_callback_stop_and_needed_requeues - stop+needed requeues callback work + * @test: KUnit test context + * + * When callback_stop and callback_needed are both set, process_output() + * should cancel the previous callback_dwork and queue it again. + */ +static void dm_test_process_output_callback_stop_and_needed_requeues(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + work->output.callback_stop = true; + work->output.callback_needed = true; + work->output.callback_delay = 300; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_process_output_watchdog_stop_and_needed_requeues - stop+needed requeues watchdog work + * @test: KUnit test context + * + * When watchdog_timer_stop and watchdog_timer_needed are both set, + * process_output() should cancel previous watchdog work and queue it again. + */ +static void dm_test_process_output_watchdog_stop_and_needed_requeues(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + schedule_delayed_work(&work->watchdog_timer_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + work->output.watchdog_timer_stop = true; + work->output.watchdog_timer_needed = true; + work->output.watchdog_timer_delay = 700; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} /* End of tests for process_output() */ +/* Tests for event_property_update() */ + +/** + * alloc_test_workqueue_for_property_update - allocate minimal workqueue for callback tests + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with property_update_work initialised + * so event_property_update() can resolve container_of() safely. The mutex is + * initialised as well because the connected path takes guard(mutex). + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_property_update(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + INIT_WORK(&work->property_update_work, dummy_work_fn); + + return work; +} + +/** + * alloc_update_connector - connector for event_property_update() tests + * @test: KUnit test context for managed allocation + * @status: drm connector detection status to assign + * @conn_state: drm_connector_state to attach (may be NULL) + * @dev: drm_device to attach as connector->dev (may be NULL) + * + * Allocates an amdgpu_dm_connector wired for the traversal in + * event_property_update(). The caller supplies the state and device so the + * various skip branches (disconnected, no state, no device) and the fully + * connected path can all be exercised. + */ +static struct amdgpu_dm_connector *alloc_update_connector(struct kunit *test, + enum drm_connector_status status, + struct drm_connector_state *conn_state, + struct drm_device *dev) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.status = status; + aconnector->base.state = conn_state; + aconnector->base.dev = dev; + + return aconnector; +} + +/** + * dm_test_event_property_update_skips_null_connector - null connector is ignored + * @test: KUnit test context + * + * If aconnector entry is NULL, event_property_update() should skip it + * without modifying encryption_status. + */ +static void dm_test_event_property_update_skips_null_connector(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + enum mod_hdcp_encryption_status before; + + work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON; + before = work->encryption_status[0]; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[0], before); +} + +/** + * dm_test_event_property_update_skips_disconnected - disconnected is skipped + * @test: KUnit test context + * + * A connector whose status is not connector_status_connected must be skipped + * before any modeset lock is taken, leaving encryption_status untouched. + */ +static void dm_test_event_property_update_skips_disconnected(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + work->aconnector[1] = alloc_update_connector(test, connector_status_disconnected, + conn_state, NULL); + work->encryption_status[1] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[1], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_update_skips_null_state - missing state is skipped + * @test: KUnit test context + * + * A connected connector without a drm_connector_state must be skipped before + * the modeset lock, leaving encryption_status unchanged. + */ +static void dm_test_event_property_update_skips_null_state(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + + work->aconnector[2] = alloc_update_connector(test, connector_status_connected, + NULL, NULL); + work->encryption_status[2] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[2], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_update_skips_null_dev - missing device is skipped + * @test: KUnit test context + * + * A connected connector with state but no drm_device must be skipped before + * the modeset lock, leaving encryption_status unchanged. + */ +static void dm_test_event_property_update_skips_null_dev(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + work->aconnector[3] = alloc_update_connector(test, connector_status_connected, + conn_state, NULL); + work->encryption_status[3] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[3], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_update_desired_when_off - HDCP off maps to DESIRED + * @test: KUnit test context + * + * A fully connected display with HDCP_OFF encryption drives the connected + * path: the modeset lock is taken, hdcp_get_content_protection_from_status() + * reports DRM_MODE_CONTENT_PROTECTION_DESIRED and + * drm_hdcp_update_content_protection() is called. The connector state is + * pre-set to DESIRED so the value is unchanged (no sysfs event) and the deep + * path completes cleanly. + */ +static void dm_test_event_property_update_desired_when_off(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + + work->aconnector[0] = alloc_update_connector(test, connector_status_connected, + conn_state, &adev->ddev); + work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, conn_state->content_protection, + (unsigned int)DRM_MODE_CONTENT_PROTECTION_DESIRED); + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); +} + +/** + * dm_test_event_property_update_enabled_when_encrypted - encrypted maps to ENABLED + * @test: KUnit test context + * + * A fully connected display with TYPE0 content and HDCP1 encryption drives + * the connected path where hdcp_get_content_protection_from_status() reports + * DRM_MODE_CONTENT_PROTECTION_ENABLED. The connector state is pre-set to + * ENABLED so drm_hdcp_update_content_protection() leaves it unchanged. + */ +static void dm_test_event_property_update_enabled_when_encrypted(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + conn_state->hdcp_content_type = DRM_MODE_HDCP_CONTENT_TYPE0; + conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + + work->aconnector[0] = alloc_update_connector(test, connector_status_connected, + conn_state, &adev->ddev); + work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP1_ON; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, conn_state->content_protection, + (unsigned int)DRM_MODE_CONTENT_PROTECTION_ENABLED); + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP1_ON); +} + +/* End of tests for event_property_update() */ + +/* Tests for event_callback() */ + +/** + * alloc_test_workqueue_for_callback - workqueue ready for event_callback() + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with its mutex and the three delayed + * works initialised, as required by the guard(mutex), cancel_delayed_work() + * and process_output() usage inside event_callback(). + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_callback(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + return work; +} + +/** + * dm_test_event_callback_cancels_callback_dwork - callback work is cancelled + * @test: KUnit test context + * + * event_callback() must cancel a previously scheduled callback_dwork. With + * no active hdcp display, mod_hdcp_process_event() leaves output cleared so + * the callback is not requeued and callback_dwork ends up not pending. + */ +static void dm_test_event_callback_cancels_callback_dwork(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test); + + /* Pre-schedule callback_dwork with a long delay so it won't fire. */ + schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + event_callback(&work->callback_dwork.work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_event_callback_schedules_property_validate - process_output() runs + * @test: KUnit test context + * + * event_callback() finishes by calling process_output(), which always + * enqueues property_validate_dwork with delay=0. Verifying it is pending + * proves event_callback() reached process_output() and released the mutex. + */ +static void dm_test_event_callback_schedules_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test); + + event_callback(&work->callback_dwork.work); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + /* Mutex must be released after the guard scope exits. */ + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* End of tests for event_callback() */ + +/* Tests for event_property_validate() */ + +/** + * alloc_test_workqueue_for_property_validate - workqueue for validate tests + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with its mutex and property_update_work + * initialised, as required by the guard(mutex) and schedule_work() usage + * inside event_property_validate(). + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + INIT_WORK(&work->property_update_work, dummy_work_fn); + + return work; +} + +/** + * alloc_validate_connector - connector for event_property_validate() tests + * @test: KUnit test context for managed allocation + * @index: drm connector index to assign + * @status: drm connector detection status to assign + * @with_state: whether to attach a zeroed drm_connector_state + * + * Allocates an amdgpu_dm_connector sufficient for the traversal in + * event_property_validate(). mod_hdcp_query_display() has no active display + * so it returns early, leaving the pre-set HDCP_OFF query untouched. + */ +static struct amdgpu_dm_connector *alloc_validate_connector(struct kunit *test, + unsigned int index, + enum drm_connector_status status, + bool with_state) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.index = index; + aconnector->base.status = status; + + if (with_state) { + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + aconnector->base.state = conn_state; + } + + return aconnector; +} + +/** + * dm_test_event_property_validate_skips_null_connector - null entries ignored + * @test: KUnit test context + * + * With every aconnector entry NULL, event_property_validate() must not + * schedule property_update_work and must leave encryption_status untouched. + */ +static void dm_test_event_property_validate_skips_null_connector(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work)); + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_validate_skips_disconnected - disconnected is skipped + * @test: KUnit test context + * + * A connector whose status is not connector_status_connected must be + * skipped, leaving encryption_status unchanged and no work scheduled. + */ +static void dm_test_event_property_validate_skips_disconnected(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->aconnector[1] = alloc_validate_connector(test, 1, + connector_status_disconnected, true); + work->encryption_status[1] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work)); + KUNIT_EXPECT_EQ(test, work->encryption_status[1], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_validate_skips_null_state - missing state is skipped + * @test: KUnit test context + * + * A connected connector without a drm_connector_state must be skipped + * before the query, leaving encryption_status unchanged. + */ +static void dm_test_event_property_validate_skips_null_state(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->aconnector[2] = alloc_validate_connector(test, 2, + connector_status_connected, false); + work->encryption_status[2] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work)); + KUNIT_EXPECT_EQ(test, work->encryption_status[2], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_validate_updates_on_status_change - change triggers update + * @test: KUnit test context + * + * For a connected connector with state, the query returns HDCP_OFF (no + * active hdcp display). When the stored encryption_status differs, it must + * be updated to HDCP_OFF and property_update_work must be scheduled. + */ +static void dm_test_event_property_validate_updates_on_status_change(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->aconnector[3] = alloc_validate_connector(test, 3, + connector_status_connected, true); + work->encryption_status[3] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[3], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_update_work)); + + cancel_work_sync(&work->property_update_work); +} + +/** + * dm_test_event_property_validate_no_update_when_unchanged - no change, no work + * @test: KUnit test context + * + * For a connected connector with state whose stored encryption_status is + * already HDCP_OFF (matching the query result), event_property_validate() + * must not reschedule property_update_work. + */ +static void dm_test_event_property_validate_no_update_when_unchanged(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->aconnector[4] = alloc_validate_connector(test, 4, + connector_status_connected, true); + work->encryption_status[4] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[4], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work)); +} + +/* End of tests for event_property_validate() */ + +/* Tests for event_watchdog_timer() */ + +/** + * dm_test_event_watchdog_timer_cancels_watchdog_dwork - watchdog work is cancelled + * @test: KUnit test context + * + * event_watchdog_timer() must cancel a previously scheduled + * watchdog_timer_dwork. With no active hdcp display, + * mod_hdcp_process_event() leaves output cleared so the watchdog is not + * requeued and watchdog_timer_dwork ends up not pending. + */ +static void dm_test_event_watchdog_timer_cancels_watchdog_dwork(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test); + + /* Pre-schedule watchdog_timer_dwork with a long delay so it won't fire. */ + schedule_delayed_work(&work->watchdog_timer_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + event_watchdog_timer(&work->watchdog_timer_dwork.work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_event_watchdog_timer_schedules_property_validate - process_output() runs + * @test: KUnit test context + * + * event_watchdog_timer() finishes by calling process_output(), which always + * enqueues property_validate_dwork with delay=0. Verifying it is pending + * proves the handler reached process_output() and released the mutex. + */ +static void dm_test_event_watchdog_timer_schedules_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test); + + event_watchdog_timer(&work->watchdog_timer_dwork.work); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + /* Mutex must be released after the guard scope exits. */ + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* End of tests for event_watchdog_timer() */ + +/* Tests for event_cpirq() */ + +/** + * alloc_test_workqueue_for_cpirq - workqueue ready for event_cpirq() + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with its mutex, cpirq_work and the + * three delayed works initialised, as required by the guard(mutex) and + * process_output() usage inside event_cpirq(). + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_cpirq(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + INIT_WORK(&work->cpirq_work, dummy_work_fn); + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + return work; +} + +/** + * dm_test_event_cpirq_schedules_property_validate - process_output() runs + * @test: KUnit test context + * + * event_cpirq() finishes by calling process_output(), which always + * enqueues property_validate_dwork with delay=0. Verifying it is pending + * proves the handler reached process_output() and released the mutex. + */ +static void dm_test_event_cpirq_schedules_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_cpirq(test); + + event_cpirq(&work->cpirq_work); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + /* Mutex must be released after the guard scope exits. */ + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_event_cpirq_leaves_callback_and_watchdog_idle - only validate is queued + * @test: KUnit test context + * + * With no active hdcp display, mod_hdcp_process_event() leaves output + * cleared, so event_cpirq() must not schedule callback_dwork or + * watchdog_timer_dwork; only property_validate_dwork is enqueued. + */ +static void dm_test_event_cpirq_leaves_callback_and_watchdog_idle(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_cpirq(test); + + event_cpirq(&work->cpirq_work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork)); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* End of tests for event_cpirq() */ + +/* Tests for hdcp_handle_cpirq() */ + +/** + * dm_test_hdcp_handle_cpirq_schedules_work - cpirq handler queues cpirq_work + * @test: KUnit test context + * + * hdcp_handle_cpirq() should schedule cpirq_work for the selected link. + */ +static void dm_test_hdcp_handle_cpirq_schedules_work(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_WORK(&work->cpirq_work, dummy_work_fn); + + hdcp_handle_cpirq(work, 0); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->cpirq_work)); + + cancel_work_sync(&work->cpirq_work); +} + +/** + * dm_test_hdcp_handle_cpirq_selects_link_index - only selected link work is queued + * @test: KUnit test context + * + * hdcp_handle_cpirq() should schedule cpirq_work for the selected index + * and not queue unrelated links. + */ +static void dm_test_hdcp_handle_cpirq_selects_link_index(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kcalloc(test, 2, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_WORK(&work[0].cpirq_work, dummy_work_fn); + INIT_WORK(&work[1].cpirq_work, dummy_work_fn); + + hdcp_handle_cpirq(work, 1); + + KUNIT_EXPECT_FALSE(test, work_pending(&work[0].cpirq_work)); + KUNIT_EXPECT_TRUE(test, work_pending(&work[1].cpirq_work)); + + cancel_work_sync(&work[1].cpirq_work); +} + +/* End of tests for hdcp_handle_cpirq() */ + +/* Tests for hdcp_update_display() helper logic */ + +/** + * dm_test_hdcp_update_display_enable_schedules_property_validate - enable path queues validate work + * @test: KUnit test context + * + * hdcp_update_display() should schedule property_validate_dwork when + * encryption is enabled. + */ +static void dm_test_hdcp_update_display_enable_schedules_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + work->srm_size = 0; + + hdcp_update_display_encryption_control(work, work, 0, true); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->property_validate_dwork)); + + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate - disable path state update + * @test: KUnit test context + * + * hdcp_update_display() should set encryption_status to HDCP_OFF and + * cancel property_validate_dwork when encryption is disabled. + */ +static void dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + schedule_delayed_work(&work->property_validate_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->property_validate_dwork)); + + work->encryption_status[3] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + hdcp_update_display_encryption_control(work, work, 3, false); + + KUNIT_EXPECT_EQ(test, work->encryption_status[3], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->property_validate_dwork)); +} + +/* End of tests for hdcp_update_display() helper logic */ + +/* Tests for hdcp_create_workqueue() */ + +/** + * dm_test_hdcp_create_workqueue_zero_max_links_returns_null - zero-link creation fails early + * @test: KUnit test context + * + * When dc->caps.max_links is zero, hdcp_create_workqueue() should fail + * the initial allocation path and return NULL. + */ +static void dm_test_hdcp_create_workqueue_zero_max_links_returns_null(struct kunit *test) +{ + struct cp_psp cp_psp = {0}; + struct dc *dc; + struct hdcp_workqueue *work; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + dc->caps.max_links = 0; + + work = hdcp_create_workqueue(NULL, &cp_psp, dc); + + KUNIT_EXPECT_PTR_EQ(test, work, NULL); +} + +/** + * dm_test_hdcp_create_workqueue_initializes_work - success path wires everything up + * @test: KUnit test context + * + * With a single link, hdcp_create_workqueue() must allocate the workqueue and + * both SRM buffers, record max_link, publish the cp_psp callbacks and handle, + * and point every link's psp handle at the device psp. A non-matching + * dce_version must leave dtm_v3_supported clear. hdcp_destroy() is used to + * tear the workqueue down (it also removes the SRM sysfs file created on the + * device kobject). + */ +static void dm_test_hdcp_create_workqueue_initializes_work(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct cp_psp cp_psp = {0}; + struct dc *dc = dm_kunit_alloc_dc_with_ctx(test); + struct hdcp_workqueue *work; + + /* sysfs_create_bin_file() needs a real device kobject. */ + adev->dev = adev->ddev.dev; + dc->caps.max_links = 1; + /* DCE_VERSION_UNKNOWN (0) is not in the DTM v3 list. */ + dc->ctx->dce_version = DCE_VERSION_UNKNOWN; + + work = hdcp_create_workqueue(adev, &cp_psp, dc); + + KUNIT_ASSERT_NOT_NULL(test, work); + KUNIT_EXPECT_EQ(test, work->max_link, 1); + KUNIT_EXPECT_NOT_NULL(test, work->srm); + KUNIT_EXPECT_NOT_NULL(test, work->srm_temp); + KUNIT_EXPECT_PTR_EQ(test, (void *)cp_psp.funcs.update_stream_config, + (void *)update_config); + KUNIT_EXPECT_PTR_EQ(test, (void *)cp_psp.funcs.enable_assr, + (void *)enable_assr); + KUNIT_EXPECT_PTR_EQ(test, cp_psp.handle, work); + KUNIT_EXPECT_PTR_EQ(test, work[0].hdcp.config.psp.handle, &adev->psp); + KUNIT_EXPECT_EQ(test, work[0].hdcp.config.psp.caps.dtm_v3_supported, 0); + + hdcp_destroy(&adev->dev->kobj, work); +} + +/** + * dm_test_hdcp_create_workqueue_sets_dtm_v3_for_dcn31 - DCN 3.1 enables DTM v3 + * @test: KUnit test context + * + * On a DCN 3.1 device, hdcp_create_workqueue() must set the per-link + * dtm_v3_supported cap; an unmatched dce_version must leave it clear. + */ +static void dm_test_hdcp_create_workqueue_sets_dtm_v3_for_dcn31(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct cp_psp cp_psp = {0}; + struct dc *dc = dm_kunit_alloc_dc_with_ctx(test); + struct hdcp_workqueue *work; + + adev->dev = adev->ddev.dev; + dc->caps.max_links = 1; + dc->ctx->dce_version = DCN_VERSION_3_1; + + work = hdcp_create_workqueue(adev, &cp_psp, dc); + + KUNIT_ASSERT_NOT_NULL(test, work); + KUNIT_EXPECT_EQ(test, work[0].hdcp.config.psp.caps.dtm_v3_supported, 1); + + hdcp_destroy(&adev->dev->kobj, work); +} + +/** + * dm_test_hdcp_create_workqueue_initializes_all_links - loop covers every link + * @test: KUnit test context + * + * With more than one link, hdcp_create_workqueue() must run its init loop for + * every entry: each link records max_link, points its psp handle at the device + * psp and gets the DTM v3 cap set for a matching dce_version. + */ +static void dm_test_hdcp_create_workqueue_initializes_all_links(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct cp_psp cp_psp = {0}; + struct dc *dc = dm_kunit_alloc_dc_with_ctx(test); + struct hdcp_workqueue *work; + int i; + + adev->dev = adev->ddev.dev; + dc->caps.max_links = 3; + dc->ctx->dce_version = DCN_VERSION_3_1; + + work = hdcp_create_workqueue(adev, &cp_psp, dc); + + KUNIT_ASSERT_NOT_NULL(test, work); + KUNIT_EXPECT_EQ(test, work->max_link, 3); + for (i = 0; i < 3; i++) { + KUNIT_EXPECT_PTR_EQ(test, work[i].hdcp.config.psp.handle, + &adev->psp); + KUNIT_EXPECT_EQ(test, work[i].hdcp.config.psp.caps.dtm_v3_supported, 1); + } + + hdcp_destroy(&adev->dev->kobj, work); +} + +/* End of tests for hdcp_create_workqueue() */ + +/* Tests for hdcp_destroy() */ + +static ssize_t test_srm_bin_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buffer, + loff_t pos, size_t count) +{ + return 0; +} + +static ssize_t test_srm_bin_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buffer, + loff_t pos, size_t count) +{ + return count; +} + +/** + * setup_destroy_sysfs - create a kobject with the SRM bin file attached + * @test: KUnit test context + * @work: workqueue whose attr will be registered + * + * hdcp_destroy() calls sysfs_remove_bin_file() on the first entry's attr, so + * a real kobject with the bin file created is required. Returns the kobject, + * which the caller must kobject_put() after hdcp_destroy() has run. + */ +static struct kobject *setup_destroy_sysfs(struct kunit *test, + struct hdcp_workqueue *work) +{ + struct kobject *kobj; + int ret; + + kobj = kobject_create_and_add("amdgpu_dm_hdcp_test", NULL); + KUNIT_ASSERT_NOT_NULL(test, kobj); + + sysfs_bin_attr_init(&work->attr); + work->attr.attr.name = "hdcp_srm"; + work->attr.attr.mode = 0664; + work->attr.size = 16; + work->attr.read = test_srm_bin_read; + work->attr.write = test_srm_bin_write; + + ret = sysfs_create_bin_file(kobj, &work->attr); + KUNIT_ASSERT_EQ(test, ret, 0); + + return kobj; +} + +/** + * dm_test_hdcp_destroy_frees_and_removes_sysfs - full teardown path + * @test: KUnit test context + * + * hdcp_destroy() must cancel every link's delayed works, remove the SRM + * sysfs bin file and free srm, srm_temp and the workqueue itself. The + * workqueue and SRM buffers use kzalloc() (not kunit-managed) because + * hdcp_destroy() frees them; KASAN/kmemleak validate there is no leak or + * use-after-free. + */ +static void dm_test_hdcp_destroy_frees_and_removes_sysfs(struct kunit *test) +{ + struct hdcp_workqueue *work; + struct kobject *kobj; + + work = kzalloc_obj(*work); + KUNIT_ASSERT_NOT_NULL(test, work); + + work->max_link = 1; + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + work->srm = kzalloc(16, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work->srm); + work->srm_temp = kzalloc(16, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work->srm_temp); + + kobj = setup_destroy_sysfs(test, work); + + /* Pre-schedule a delayed work to exercise the cancel path. */ + schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + hdcp_destroy(kobj, work); + + /* work is freed by hdcp_destroy(); only the kobject remains. */ + kobject_put(kobj); +} + +/** + * dm_test_hdcp_destroy_zero_links_null_srm - teardown with no links or SRM + * @test: KUnit test context + * + * With max_link == 0 the cancel loop is skipped, and NULL srm/srm_temp make + * the kfree() calls no-ops. hdcp_destroy() must still remove the sysfs bin + * file and free the workqueue without crashing. + */ +static void dm_test_hdcp_destroy_zero_links_null_srm(struct kunit *test) +{ + struct hdcp_workqueue *work; + struct kobject *kobj; + + work = kzalloc_obj(*work); + KUNIT_ASSERT_NOT_NULL(test, work); + + work->max_link = 0; + work->srm = NULL; + work->srm_temp = NULL; + + kobj = setup_destroy_sysfs(test, work); + + hdcp_destroy(kobj, work); + + kobject_put(kobj); +} + +/* End of tests for hdcp_destroy() */ + +/* Tests for link_lock() */ + +/** + * dm_test_link_lock_locks_and_unlocks_all_links - lock/unlock spans every link + * @test: KUnit test context + * + * link_lock() should acquire the mutex of every entry from 0 to max_link + * when locking, and release all of them when unlocking. A subsequent + * lock/unlock cycle must succeed, proving the mutexes were left released. + */ +static void dm_test_link_lock_locks_and_unlocks_all_links(struct kunit *test) +{ + const int num_links = 3; + struct hdcp_workqueue *work; + int i; + + work = kunit_kcalloc(test, num_links, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + /* max_link is read from the first element. */ + work[0].max_link = num_links; + for (i = 0; i < num_links; i++) + mutex_init(&work[i].mutex); + + link_lock(work, true); + for (i = 0; i < num_links; i++) + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&work[i].mutex)); + + link_lock(work, false); + for (i = 0; i < num_links; i++) + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work[i].mutex)); + + /* Mutexes must be re-acquirable after being released. */ + link_lock(work, true); + for (i = 0; i < num_links; i++) + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&work[i].mutex)); + link_lock(work, false); +} + +/** + * dm_test_link_lock_zero_links_is_noop - zero max_link touches no mutexes + * @test: KUnit test context + * + * When max_link is zero, link_lock() must not touch any mutex and simply + * return, leaving the (single) entry's mutex unlocked. + */ +static void dm_test_link_lock_zero_links_is_noop(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + work->max_link = 0; + + link_lock(work, true); + + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex)); +} + +/* End of tests for link_lock() */ + +/* Tests for psp_get_srm() and psp_set_srm() */ + +/** + * dm_test_psp_get_srm_uninitialized_returns_null - GET fails when TA not initialized + * @test: KUnit test context + * + * When the HDCP TA context is not initialized, psp_get_srm() must take the + * guard path and return NULL without touching the output parameters or + * invoking the (real) firmware path. + */ +static void dm_test_psp_get_srm_uninitialized_returns_null(struct kunit *test) +{ + struct psp_context *psp; + uint32_t srm_version = 0xdead; + uint32_t srm_size = 0xbeef; + uint8_t *srm; + + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + /* kzalloc leaves hdcp_context.context.initialized == false */ + srm = psp_get_srm(psp, &srm_version, &srm_size); + + KUNIT_EXPECT_PTR_EQ(test, srm, NULL); + /* Output parameters must be left untouched on the guard path. */ + KUNIT_EXPECT_EQ(test, srm_version, 0xdead); + KUNIT_EXPECT_EQ(test, srm_size, 0xbeef); +} + +/** + * dm_test_psp_set_srm_uninitialized_returns_einval - SET fails when TA not initialized + * @test: KUnit test context + * + * When the HDCP TA context is not initialized, psp_set_srm() must take the + * guard path and return -EINVAL without updating srm_version or invoking + * the (real) firmware path. + */ +static void dm_test_psp_set_srm_uninitialized_returns_einval(struct kunit *test) +{ + struct psp_context *psp; + uint32_t srm_version = 0xdead; + u8 srm_buf[4] = {0}; + int ret; + + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + /* kzalloc leaves hdcp_context.context.initialized == false */ + ret = psp_set_srm(psp, srm_buf, sizeof(srm_buf), &srm_version); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + /* srm_version must be left untouched on the guard path. */ + KUNIT_EXPECT_EQ(test, srm_version, 0xdead); +} + +/** + * dm_test_psp_set_srm_initialized_stages_command - initialized path builds the command + * @test: KUnit test context + * + * With an initialized TA and the SR-IOV VF bypass, psp_hdcp_invoke() is a + * no-op, so the shared command buffer keeps the values psp_set_srm() staged. + * The function must copy the SRM into the SET_SRM in-message, record its size + * and command id, and then fail the response validation (the zeroed reply has + * valid_signature == 0), returning -EINVAL without updating srm_version. + */ +static void dm_test_psp_set_srm_initialized_stages_command(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct ta_hdcp_shared_memory *hdcp_cmd; + struct psp_context *psp; + uint32_t srm_version = 0xdead; + u8 srm_buf[4] = {0x1, 0x2, 0x3, 0x4}; + int ret; + + KUNIT_ASSERT_NOT_NULL(test, adev); + + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + hdcp_cmd = kunit_kzalloc(test, sizeof(*hdcp_cmd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdcp_cmd); + + psp->adev = adev; + psp->hdcp_context.context.initialized = true; + psp->hdcp_context.context.mem_context.shared_buf = (uint8_t *)hdcp_cmd; + + /* SR-IOV VF makes psp_hdcp_invoke() return early without firmware. */ + adev->virt.caps |= AMDGPU_SRIOV_CAPS_IS_VF; + + ret = psp_set_srm(psp, srm_buf, sizeof(srm_buf), &srm_version); + + /* Response validation fails (valid_signature == 0 in the zeroed reply). */ + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, srm_version, 0xdead); + /* The initialized path must have staged the SET_SRM command. */ + KUNIT_EXPECT_EQ(test, hdcp_cmd->cmd_id, TA_HDCP_COMMAND__HDCP_SET_SRM); + KUNIT_EXPECT_EQ(test, hdcp_cmd->in_msg.hdcp_set_srm.srm_buf_size, + (uint32_t)sizeof(srm_buf)); + KUNIT_EXPECT_MEMEQ(test, hdcp_cmd->in_msg.hdcp_set_srm.srm_buf, srm_buf, + sizeof(srm_buf)); +} + +/* End of tests for psp_get_srm() and psp_set_srm() */ + +/* Tests for srm_data_write() and srm_data_read() */ + +/** + * dm_test_srm_data_write_uninitialized_ta_keeps_srm - write with TA not initialized + * @test: KUnit test context + * + * srm_data_write() always copies the incoming buffer into work->srm_temp and + * returns the byte count. When the HDCP TA is not initialized, psp_set_srm() + * fails, so the committed SRM (work->srm / work->srm_size) must stay untouched. + */ +static void dm_test_srm_data_write_uninitialized_ta_keeps_srm(struct kunit *test) +{ + struct hdcp_workqueue *work; + struct psp_context *psp; + u8 buf[4] = {0xAA, 0xBB, 0xCC, 0xDD}; + ssize_t ret; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + work->max_link = 1; + mutex_init(&work->mutex); + /* kzalloc leaves hdcp_context.context.initialized == false */ + work->hdcp.config.psp.handle = psp; + work->srm_temp = kunit_kzalloc(test, PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work->srm_temp); + work->srm = kunit_kzalloc(test, PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work->srm); + + ret = srm_data_write(NULL, NULL, &work->attr, buf, 0, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buf)); + /* Incoming data is always staged into srm_temp. */ + KUNIT_EXPECT_MEMEQ(test, work->srm_temp, buf, sizeof(buf)); + /* psp_set_srm() failed, so the committed SRM must be unchanged. */ + KUNIT_EXPECT_EQ(test, work->srm_size, 0u); +} + +/** + * dm_test_srm_data_read_uninitialized_ta_returns_einval - read with TA not initialized + * @test: KUnit test context + * + * When the HDCP TA is not initialized, psp_get_srm() returns NULL, so + * srm_data_read() must take the error path and return -EINVAL. + */ +static void dm_test_srm_data_read_uninitialized_ta_returns_einval(struct kunit *test) +{ + struct hdcp_workqueue *work; + struct psp_context *psp; + u8 buf[4]; + ssize_t ret; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + work->max_link = 1; + mutex_init(&work->mutex); + /* kzalloc leaves hdcp_context.context.initialized == false */ + work->hdcp.config.psp.handle = psp; + + ret = srm_data_read(NULL, NULL, &work->attr, buf, 0, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)-EINVAL); +} + +/** + * dm_test_srm_data_read_empty_srm_returns_zero - read of an empty SRM + * @test: KUnit test context + * + * With an initialized TA and the SR-IOV VF bypass, psp_hdcp_invoke() is a + * no-op and the zeroed shared buffer yields a SUCCESS status with srm_size 0. + * psp_get_srm() then returns a non-NULL (empty) buffer, so srm_data_read() + * takes the "nothing left to copy" path and returns 0. + */ +static void dm_test_srm_data_read_empty_srm_returns_zero(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct ta_hdcp_shared_memory *hdcp_cmd; + struct hdcp_workqueue *work; + struct psp_context *psp; + u8 buf[4]; + ssize_t ret; + + KUNIT_ASSERT_NOT_NULL(test, adev); + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + hdcp_cmd = kunit_kzalloc(test, sizeof(*hdcp_cmd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdcp_cmd); + + work->max_link = 1; + mutex_init(&work->mutex); + psp->adev = adev; + psp->hdcp_context.context.initialized = true; + psp->hdcp_context.context.mem_context.shared_buf = (uint8_t *)hdcp_cmd; + work->hdcp.config.psp.handle = psp; + + /* SR-IOV VF makes psp_hdcp_invoke() return early without firmware. */ + adev->virt.caps |= AMDGPU_SRIOV_CAPS_IS_VF; + + ret = srm_data_read(NULL, NULL, &work->attr, buf, 0, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/* End of tests for srm_data_write() and srm_data_read() */ + +/* Tests for lp_write_i2c() / lp_read_i2c() / lp_write_dpcd() / lp_read_dpcd() */ + +/* Defined further below with the display helper tests. */ +static struct amdgpu_dm_connector *alloc_test_connector(struct kunit *test, + unsigned int index); + +/* + * Recording fakes for the DDC layer. The lp_* wrappers build i2c/DPCD + * transactions and forward them through dm_helpers_*, which end up calling + * i2c_transfer() / drm_dp_dpcd_*(). These fakes capture the resulting + * messages so the tests can assert what the wrappers built, without touching + * real hardware. KUnit runs cases sequentially, so file-scope capture state + * is reset at the start of each test. + */ +#define FAKE_DDC_MAX_MSGS 4 + +static struct fake_i2c_capture { + int num; + struct i2c_msg msgs[FAKE_DDC_MAX_MSGS]; +} fake_i2c_cap; + +static int fake_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + int i; + + fake_i2c_cap.num = num; + for (i = 0; i < num && i < FAKE_DDC_MAX_MSGS; i++) + fake_i2c_cap.msgs[i] = msgs[i]; + + return num; +} + +static u32 fake_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm fake_i2c_algo = { + .master_xfer = fake_i2c_master_xfer, + .functionality = fake_i2c_functionality, +}; + +static void fake_i2c_lock_bus(struct i2c_adapter *adap, unsigned int flags) {} +static int fake_i2c_trylock_bus(struct i2c_adapter *adap, unsigned int flags) +{ + return 1; +} +static void fake_i2c_unlock_bus(struct i2c_adapter *adap, unsigned int flags) {} + +static const struct i2c_lock_operations fake_i2c_lock_ops = { + .lock_bus = fake_i2c_lock_bus, + .trylock_bus = fake_i2c_trylock_bus, + .unlock_bus = fake_i2c_unlock_bus, +}; + +static struct fake_aux_capture { + int calls; + u8 request; + unsigned int address; + size_t size; +} fake_aux_cap; + +static ssize_t fake_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + fake_aux_cap.calls++; + fake_aux_cap.request = msg->request; + fake_aux_cap.address = msg->address; + fake_aux_cap.size = msg->size; + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + + return msg->size; +} + +/** + * alloc_test_ddc_link - connector/link wired to the recording i2c + aux fakes + * @test: KUnit test context for managed allocation + * + * Builds an amdgpu_dm_connector with a fake i2c adapter and a fake DP aux, and + * points link->priv at the connector so dm_helpers_* find it. Returns the + * dc_link that the lp_* wrappers take as their opaque handle. + */ +static struct dc_link *alloc_test_ddc_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct amdgpu_i2c_adapter *i2c; + struct dc_link *link; + + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + i2c = kunit_kzalloc(test, sizeof(*i2c), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, i2c); + i2c->base.algo = &fake_i2c_algo; + i2c->base.lock_ops = &fake_i2c_lock_ops; + aconnector->i2c = i2c; + + mutex_init(&aconnector->dm_dp_aux.aux.hw_mutex); + aconnector->dm_dp_aux.aux.transfer = fake_aux_transfer; + /* Skip the DPCD "throw away" probe read so we capture only our access. */ + aconnector->dm_dp_aux.aux.dpcd_probe_disabled = true; + + link = aconnector->dc_link; + link->priv = aconnector; + + return link; +} + +/** + * dm_test_lp_write_i2c_builds_single_write_payload - write builds one i2c msg + * @test: KUnit test context + * + * lp_write_i2c() must forward a single write payload carrying the address, + * length and data buffer unchanged. + */ +static void dm_test_lp_write_i2c_builds_single_write_payload(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[3] = {0x11, 0x22, 0x33}; + bool ok; + + memset(&fake_i2c_cap, 0, sizeof(fake_i2c_cap)); + + ok = lp_write_i2c(link, 0x3a, data, sizeof(data)); + + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_ASSERT_EQ(test, fake_i2c_cap.num, 1); + /* write => flags without I2C_M_RD */ + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].flags, 0); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].addr, 0x3a); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].len, (int)sizeof(data)); + KUNIT_EXPECT_PTR_EQ(test, fake_i2c_cap.msgs[0].buf, (void *)data); +} + +/** + * dm_test_lp_read_i2c_builds_offset_then_read - read builds offset + read msgs + * @test: KUnit test context + * + * lp_read_i2c() must build a 1-byte write of the offset followed by a + * size-byte read into the caller buffer, both at the same address. + */ +static void dm_test_lp_read_i2c_builds_offset_then_read(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[4]; + bool ok; + + memset(&fake_i2c_cap, 0, sizeof(fake_i2c_cap)); + + ok = lp_read_i2c(link, 0x50, 0x07, data, sizeof(data)); + + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_ASSERT_EQ(test, fake_i2c_cap.num, 2); + /* first: 1-byte write of the offset */ + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].flags, 0); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].addr, 0x50); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].len, 1); + /* second: size-byte read into the caller buffer */ + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[1].flags, I2C_M_RD); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[1].addr, 0x50); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[1].len, (int)sizeof(data)); + KUNIT_EXPECT_PTR_EQ(test, fake_i2c_cap.msgs[1].buf, (void *)data); +} + +/** + * dm_test_lp_write_dpcd_forwards_native_write - write forwards a native write + * @test: KUnit test context + * + * lp_write_dpcd() must issue a single DP_AUX_NATIVE_WRITE at the requested + * address for the requested size. + */ +static void dm_test_lp_write_dpcd_forwards_native_write(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[2] = {0xDE, 0xAD}; + bool ok; + + memset(&fake_aux_cap, 0, sizeof(fake_aux_cap)); + + ok = lp_write_dpcd(link, 0x68000, data, sizeof(data)); + + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, fake_aux_cap.calls, 1); + KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.request, DP_AUX_NATIVE_WRITE); + KUNIT_EXPECT_EQ(test, fake_aux_cap.address, 0x68000u); + KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.size, (int)sizeof(data)); +} + +/** + * dm_test_lp_read_dpcd_forwards_native_read - read forwards a native read + * @test: KUnit test context + * + * lp_read_dpcd() must issue a single DP_AUX_NATIVE_READ at the requested + * address for the requested size. + */ +static void dm_test_lp_read_dpcd_forwards_native_read(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[4]; + bool ok; + + memset(&fake_aux_cap, 0, sizeof(fake_aux_cap)); + + ok = lp_read_dpcd(link, 0x00220, data, sizeof(data)); + + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, fake_aux_cap.calls, 1); + KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.request, DP_AUX_NATIVE_READ); + KUNIT_EXPECT_EQ(test, fake_aux_cap.address, 0x00220u); + KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.size, (int)sizeof(data)); +} + +/** + * dm_test_lp_write_i2c_no_connector_returns_false - missing connector fails + * @test: KUnit test context + * + * When link->priv has no connector, dm_helpers_submit_i2c() cannot proceed, + * so lp_write_i2c() must report failure without invoking the adapter. + */ +static void dm_test_lp_write_i2c_no_connector_returns_false(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[2] = {0x01, 0x02}; + bool ok; + + link->priv = NULL; + memset(&fake_i2c_cap, 0, sizeof(fake_i2c_cap)); + + ok = lp_write_i2c(link, 0x3a, data, sizeof(data)); + + KUNIT_EXPECT_FALSE(test, ok); + KUNIT_EXPECT_EQ(test, fake_i2c_cap.num, 0); +} + +/** + * dm_test_lp_read_dpcd_no_connector_returns_false - missing connector fails + * @test: KUnit test context + * + * When link->priv has no connector, dm_helpers_dp_read_dpcd() cannot proceed, + * so lp_read_dpcd() must report failure without invoking the aux transfer. + */ +static void dm_test_lp_read_dpcd_no_connector_returns_false(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[4]; + bool ok; + + link->priv = NULL; + memset(&fake_aux_cap, 0, sizeof(fake_aux_cap)); + + ok = lp_read_dpcd(link, 0x00220, data, sizeof(data)); + + KUNIT_EXPECT_FALSE(test, ok); + KUNIT_EXPECT_EQ(test, fake_aux_cap.calls, 0); +} + +/* End of tests for lp_write_i2c() / lp_read_i2c() / lp_write_dpcd() / lp_read_dpcd() */ + +/* + * Tests for lp_atomic_write_poll_read_i2c() / lp_atomic_write_poll_read_aux() + * + * These wrappers cast the opaque handle to a dc_link and forward to the + * dc_fused_io helpers. The success path submits a fused-IO command sequence to + * the DMCUB, which is out of reach for a unit test, so the coverage here is the + * hardware-free early returns: a NULL link and a payload that fails conversion + * (op size larger than the fused request buffer). + */ + +/** + * dm_test_lp_atomic_i2c_null_handle_returns_false - NULL link fails cleanly + * @test: KUnit test context + * + * With a NULL handle the forwarded dc_link is NULL, so the helper must return + * false without dereferencing anything. + */ +static void dm_test_lp_atomic_i2c_null_handle_returns_false(struct kunit *test) +{ + struct mod_hdcp_atomic_op_i2c op = { 0 }; + + KUNIT_EXPECT_FALSE(test, + lp_atomic_write_poll_read_i2c(NULL, &op, &op, &op, 0, 0)); +} + +/** + * dm_test_lp_atomic_i2c_oversized_op_returns_false - bad payload fails conversion + * @test: KUnit test context + * + * An op whose size exceeds the fused request buffer must fail conversion, so + * the helper returns false before any fused-IO submission. no_ddc_pin routes + * the DDC line through aux_hw_inst, avoiding the GPIO pin dereference. + */ +static void dm_test_lp_atomic_i2c_oversized_op_returns_false(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct mod_hdcp_atomic_op_i2c write = { .size = 0x100 }; + struct mod_hdcp_atomic_op_i2c op = { 0 }; + struct dc_link *link; + + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + link = aconnector->dc_link; + link->no_ddc_pin = true; + + KUNIT_EXPECT_FALSE(test, + lp_atomic_write_poll_read_i2c(link, &write, &op, &op, 0, 0)); +} + +/** + * dm_test_lp_atomic_aux_null_handle_returns_false - NULL link fails cleanly + * @test: KUnit test context + * + * With a NULL handle the forwarded dc_link is NULL, so the helper must return + * false without dereferencing anything. + */ +static void dm_test_lp_atomic_aux_null_handle_returns_false(struct kunit *test) +{ + struct mod_hdcp_atomic_op_aux op = { 0 }; + + KUNIT_EXPECT_FALSE(test, + lp_atomic_write_poll_read_aux(NULL, &op, &op, &op, 0, 0)); +} + +/** + * dm_test_lp_atomic_aux_oversized_op_returns_false - bad payload fails conversion + * @test: KUnit test context + * + * The aux helper reads the DDC line from link->ddc->ddc_pin->pin_data before + * converting, so a minimal pin chain is wired up. An op larger than the fused + * request buffer then fails conversion and the helper returns false without + * any fused-IO submission. + */ +static void dm_test_lp_atomic_aux_oversized_op_returns_false(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct mod_hdcp_atomic_op_aux write = { .size = 0x100 }; + struct mod_hdcp_atomic_op_aux op = { 0 }; + struct ddc_service *ddc; + struct ddc *ddc_pin; + struct dc_link *link; + void *pin_data; + + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc); + ddc_pin = kunit_kzalloc(test, sizeof(*ddc_pin), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc_pin); + /* Only ->en is read; over-allocate so struct gpio stays opaque here. */ + pin_data = kunit_kzalloc(test, 128, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pin_data); + + ddc_pin->pin_data = pin_data; + ddc->ddc_pin = ddc_pin; + + link = aconnector->dc_link; + link->ddc = ddc; + + KUNIT_EXPECT_FALSE(test, + lp_atomic_write_poll_read_aux(link, &write, &op, &op, 0, 0)); +} + +/* End of tests for lp_atomic_write_poll_read_i2c() / lp_atomic_write_poll_read_aux() */ + +/* + * Tests for hdcp_update_display() / hdcp_remove_display() / + * hdcp_reset_display(). + */ + +/** + * alloc_test_workqueue_locked - workqueue with dworks and mutex initialised + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with its mutex and the three delayed + * works initialised, as required by the guard(mutex) and process_output() + * usage in the display update/remove/reset helpers. + */ +static struct hdcp_workqueue *alloc_test_workqueue_locked(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + return work; +} + +/** + * alloc_test_connector - minimal amdgpu_dm_connector for display tests + * @test: KUnit test context for managed allocation + * @index: drm connector index to assign + * + * Allocates an amdgpu_dm_connector with an initialised connector refcount + * (so drm_connector_get() is valid) and a dc_link/dc pair. The connector's + * free_cb is left NULL so drm_connector_put() is a safe no-op that never + * releases the object. + */ +static struct amdgpu_dm_connector *alloc_test_connector(struct kunit *test, + unsigned int index) +{ + struct amdgpu_dm_connector *aconnector; + struct dc_link *dc_link; + struct dc *dc; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + dc_link = kunit_kzalloc(test, sizeof(*dc_link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_link); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + kref_init(&aconnector->base.base.refcount); + aconnector->base.index = index; + dc_link->dc = dc; + aconnector->dc_link = dc_link; + + return aconnector; +} + +/** + * dm_test_hdcp_update_display_enable_registers_connector - enable stores the connector + * @test: KUnit test context + * + * hdcp_update_display() with enable_encryption=true should register the + * connector in the per-link aconnector array at its connector index. The + * hdcp has no active display, so mod_hdcp_update_display() is effectively a + * no-op and the call must not touch encryption_status. + */ +static void dm_test_hdcp_update_display_enable_registers_connector(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + + work->srm_size = 0; + + hdcp_update_display(work, 0, aconnector, DRM_MODE_HDCP_CONTENT_TYPE0, true); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], aconnector); + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_hdcp_update_display_disable_sets_status_off - disable clears status + * @test: KUnit test context + * + * hdcp_update_display() with enable_encryption=false should register the + * connector and reset the connector's encryption_status entry to HDCP_OFF + * via hdcp_update_display_encryption_control(). + */ +static void dm_test_hdcp_update_display_disable_sets_status_off(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 2); + + work->encryption_status[2] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + hdcp_update_display(work, 0, aconnector, DRM_MODE_HDCP_CONTENT_TYPE0, false); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[2], aconnector); + KUNIT_EXPECT_EQ(test, work->encryption_status[2], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_hdcp_remove_display_enabled_resets_cp - ENABLED CP reverts to DESIRED + * @test: KUnit test context + * + * hdcp_remove_display() must revert a connector whose content_protection is + * ENABLED back to DESIRED and clear the per-link aconnector entry. + */ +static void dm_test_hdcp_remove_display_enabled_resets_cp(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 1); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + aconnector->base.state = conn_state; + work->aconnector[1] = aconnector; + + hdcp_remove_display(work, 0, aconnector); + + KUNIT_EXPECT_EQ(test, conn_state->content_protection, + DRM_MODE_CONTENT_PROTECTION_DESIRED); + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[1], NULL); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_hdcp_remove_display_null_state_clears_connector - NULL state skips CP change + * @test: KUnit test context + * + * When the connector has no drm_connector_state, hdcp_remove_display() must + * skip the content_protection update path and still clear the per-link + * aconnector entry. + */ +static void dm_test_hdcp_remove_display_null_state_clears_connector(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + + aconnector->base.state = NULL; + work->aconnector[0] = aconnector; + + hdcp_remove_display(work, 0, aconnector); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_hdcp_reset_display_clears_all_state - reset clears status and connectors + * @test: KUnit test context + * + * hdcp_reset_display() must reset every connector's encryption_status to + * HDCP_OFF and clear all per-link aconnector entries. + */ +static void dm_test_hdcp_reset_display_clears_all_state(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 4); + + work->encryption_status[4] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + work->aconnector[4] = aconnector; + + hdcp_reset_display(work, 0); + + KUNIT_EXPECT_EQ(test, work->encryption_status[4], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[4], NULL); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/* + * End of tests for hdcp_update_display() / hdcp_remove_display() / + * hdcp_reset_display(). + */ + +/* Tests for enable_assr() */ + +/** + * alloc_test_workqueue_for_assr - workqueue wired to a psp for enable_assr() + * @test: KUnit test context for managed allocation + * @adev: amdgpu device whose drm_device backs psp->adev (for drm_info()) + * + * Allocates a minimal hdcp_workqueue and a psp_context connected through + * hdcp.config.psp.handle, matching the dereference chain enable_assr() + * performs. The psp is left with dtm_context.context.initialized == false + * (from kzalloc) so enable_assr() takes the "DTM TA not initialized" path. + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_assr(struct kunit *test, + struct amdgpu_device *adev) +{ + struct hdcp_workqueue *work; + struct psp_context *psp; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + psp->adev = adev; + work->hdcp.config.psp.handle = psp; + + return work; +} + +/** + * dm_test_enable_assr_uninitialized_dtm_returns_false - DTM TA not initialized + * @test: KUnit test context + * + * When the DTM TA context is not initialized, enable_assr() must take the + * early-return path, emit the informational message and return false + * without invoking the (real) firmware path. + */ +static void dm_test_enable_assr_uninitialized_dtm_returns_false(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_assr(test, adev); + struct dc_link *link; + bool ret; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* kzalloc leaves dtm_context.context.initialized == false */ + ret = enable_assr(work, link); + + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_enable_assr_uninitialized_dtm_ignores_link - link untouched on failure + * @test: KUnit test context + * + * On the "DTM TA not initialized" path enable_assr() returns before reading + * any field of @link, so a NULL link must be tolerated and the call must + * still return false. + */ +static void dm_test_enable_assr_uninitialized_dtm_ignores_link(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_assr(test, adev); + bool ret; + + /* link is not dereferenced before the initialized check. */ + ret = enable_assr(work, NULL); + + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_enable_assr_initialized_builds_command_and_fails - full body, invoke bypassed + * @test: KUnit test context + * + * With the DTM TA marked initialized and a valid shared buffer, enable_assr() + * runs its full body: it acquires the DTM mutex, clears the shared command, + * fills in the ASSR-enable command from @link and pre-sets the status to + * GENERIC_FAILURE before invoking the TA. + * + * psp_dtm_invoke() is prevented from touching real firmware by marking the + * device as an SR-IOV virtual function, which makes it return early without + * modifying the shared status. The status therefore stays GENERIC_FAILURE, so + * enable_assr() must return false. Inspecting the shared command afterwards + * proves the body executed and consumed @link. + */ +static void dm_test_enable_assr_initialized_builds_command_and_fails(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_assr(test, adev); + struct psp_context *psp = work->hdcp.config.psp.handle; + struct ta_dtm_shared_memory *dtm_cmd; + struct dc_link *link; + bool ret; + + dtm_cmd = kunit_kzalloc(test, sizeof(*dtm_cmd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dtm_cmd); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + link->link_enc_hw_inst = 3; + + /* Wire up an "initialized" DTM TA with a real shared buffer. */ + psp->dtm_context.context.initialized = true; + psp->dtm_context.context.mem_context.shared_buf = (uint8_t *)dtm_cmd; + mutex_init(&psp->dtm_context.mutex); + + /* + * Force the SR-IOV VF early-return in psp_dtm_invoke() so no GPU + * command is submitted; the shared status is left untouched. + */ + adev->virt.caps |= AMDGPU_SRIOV_CAPS_IS_VF; + + ret = enable_assr(work, link); + + /* Status was never advanced to SUCCESS, so the call must fail. */ + KUNIT_EXPECT_FALSE(test, ret); + /* The command body must have populated the shared buffer. */ + KUNIT_EXPECT_EQ(test, dtm_cmd->cmd_id, TA_DTM_COMMAND__TOPOLOGY_ASSR_ENABLE); + KUNIT_EXPECT_EQ(test, + dtm_cmd->dtm_in_message.topology_assr_enable.display_topology_dig_be_index, + link->link_enc_hw_inst); + KUNIT_EXPECT_EQ(test, dtm_cmd->dtm_status, TA_DTM_STATUS__GENERIC_FAILURE); + /* The DTM mutex must be released after the guard scope exits. */ + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&psp->dtm_context.mutex)); +} + +/* End of tests for enable_assr() */ + +/* Tests for update_config() */ + +/** + * dm_test_update_config_null_connector_is_noop - NULL stream ctx returns early + * @test: KUnit test context + * + * When config->dm_stream_ctx is NULL, update_config() must return before + * touching the workqueue, leaving the per-link aconnector array untouched. + */ +static void dm_test_update_config_null_connector_is_noop(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct cp_psp_stream_config config = {0}; + + config.dm_stream_ctx = NULL; + + update_config(work, &config); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL); +} + +/** + * dm_test_update_config_null_dc_link_is_noop - NULL dc_link returns early + * @test: KUnit test context + * + * A connector without a dc_link must cause update_config() to return before + * registering the connector, leaving the aconnector array untouched. + */ +static void dm_test_update_config_null_dc_link_is_noop(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector; + struct cp_psp_stream_config config = {0}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aconnector->dc_link = NULL; + + config.dm_stream_ctx = aconnector; + + update_config(work, &config); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL); +} + +/** + * dm_test_update_config_dpms_off_removes_display - dpms_off path removes display + * @test: KUnit test context + * + * With config->dpms_off set, update_config() must take the removal path: + * hdcp_remove_display() reverts an ENABLED connector to DESIRED and clears + * its per-link aconnector entry. + */ +static void dm_test_update_config_dpms_off_removes_display(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct drm_connector_state *conn_state; + struct cp_psp_stream_config config = {0}; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + aconnector->base.state = conn_state; + work->aconnector[0] = aconnector; + + config.dm_stream_ctx = aconnector; + config.dpms_off = true; + + update_config(work, &config); + + KUNIT_EXPECT_EQ(test, conn_state->content_protection, + DRM_MODE_CONTENT_PROTECTION_DESIRED); + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_update_config_populates_display_and_link - active path fills state + * @test: KUnit test context + * + * With dpms_off clear, update_config() must build the display and link from + * @config, reset the connector's encryption_status to HDCP_OFF, register the + * connector and reach process_output() (which enqueues property_validate). + * + * mod_hdcp_add_display() reaches add_display_to_topology(), which returns + * early because the DTM TA is left uninitialized, so no firmware is touched. + */ +static void dm_test_update_config_populates_display_and_link(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct psp_context *psp; + struct cp_psp_stream_config config = {0}; + + /* add_display_to_topology() dereferences the psp handle. */ + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + work->hdcp.config.psp.handle = psp; + + config.dm_stream_ctx = aconnector; + config.dpms_off = false; + config.otg_inst = 1; + config.dig_fe = 4; + config.dig_be = 5; + config.stream_enc_idx = 6; + config.link_enc_idx = 7; + config.dio_output_idx = 8; + config.phy_idx = 2; + + update_config(work, &config); + + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], aconnector); + + KUNIT_EXPECT_EQ(test, work->display.state, MOD_HDCP_DISPLAY_ACTIVE); + KUNIT_EXPECT_EQ(test, work->display.controller, + CONTROLLER_ID_D0 + config.otg_inst); + KUNIT_EXPECT_EQ(test, work->display.dig_fe, config.dig_fe); + KUNIT_EXPECT_EQ(test, work->display.stream_enc_idx, config.stream_enc_idx); + + KUNIT_EXPECT_EQ(test, work->link.dig_be, config.dig_be); + KUNIT_EXPECT_EQ(test, work->link.link_enc_idx, config.link_enc_idx); + KUNIT_EXPECT_EQ(test, work->link.dio_output_id, config.dio_output_idx); + KUNIT_EXPECT_EQ(test, work->link.phy_idx, config.phy_idx); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/* End of tests for update_config() */ + static struct kunit_case dm_hdcp_test_cases[] = { /* hdcp_get_content_protection_from_status() */ KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired), @@ -423,6 +2530,82 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_process_output_watchdog_needed), KUNIT_CASE(dm_test_process_output_watchdog_stop), KUNIT_CASE(dm_test_process_output_callback_and_watchdog_needed), + KUNIT_CASE(dm_test_process_output_callback_stop_and_needed_requeues), + KUNIT_CASE(dm_test_process_output_watchdog_stop_and_needed_requeues), + /* event_property_update() */ + KUNIT_CASE(dm_test_event_property_update_skips_null_connector), + KUNIT_CASE(dm_test_event_property_update_skips_disconnected), + KUNIT_CASE(dm_test_event_property_update_skips_null_state), + KUNIT_CASE(dm_test_event_property_update_skips_null_dev), + KUNIT_CASE(dm_test_event_property_update_desired_when_off), + KUNIT_CASE(dm_test_event_property_update_enabled_when_encrypted), + /* event_callback() */ + KUNIT_CASE(dm_test_event_callback_cancels_callback_dwork), + KUNIT_CASE(dm_test_event_callback_schedules_property_validate), + /* event_property_validate() */ + KUNIT_CASE(dm_test_event_property_validate_skips_null_connector), + KUNIT_CASE(dm_test_event_property_validate_skips_disconnected), + KUNIT_CASE(dm_test_event_property_validate_skips_null_state), + KUNIT_CASE(dm_test_event_property_validate_updates_on_status_change), + KUNIT_CASE(dm_test_event_property_validate_no_update_when_unchanged), + /* event_watchdog_timer() */ + KUNIT_CASE(dm_test_event_watchdog_timer_cancels_watchdog_dwork), + KUNIT_CASE(dm_test_event_watchdog_timer_schedules_property_validate), + /* event_cpirq() */ + KUNIT_CASE(dm_test_event_cpirq_schedules_property_validate), + KUNIT_CASE(dm_test_event_cpirq_leaves_callback_and_watchdog_idle), + /* hdcp_handle_cpirq() */ + KUNIT_CASE(dm_test_hdcp_handle_cpirq_schedules_work), + KUNIT_CASE(dm_test_hdcp_handle_cpirq_selects_link_index), + /* hdcp_update_display() helper logic */ + KUNIT_CASE(dm_test_hdcp_update_display_enable_schedules_property_validate), + KUNIT_CASE(dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate), + /* hdcp_create_workqueue() */ + KUNIT_CASE(dm_test_hdcp_create_workqueue_zero_max_links_returns_null), + KUNIT_CASE(dm_test_hdcp_create_workqueue_initializes_work), + KUNIT_CASE(dm_test_hdcp_create_workqueue_sets_dtm_v3_for_dcn31), + KUNIT_CASE(dm_test_hdcp_create_workqueue_initializes_all_links), + /* hdcp_destroy() */ + KUNIT_CASE(dm_test_hdcp_destroy_frees_and_removes_sysfs), + KUNIT_CASE(dm_test_hdcp_destroy_zero_links_null_srm), + /* link_lock() */ + KUNIT_CASE(dm_test_link_lock_locks_and_unlocks_all_links), + KUNIT_CASE(dm_test_link_lock_zero_links_is_noop), + /* psp_get_srm() / psp_set_srm() */ + KUNIT_CASE(dm_test_psp_get_srm_uninitialized_returns_null), + KUNIT_CASE(dm_test_psp_set_srm_uninitialized_returns_einval), + KUNIT_CASE(dm_test_psp_set_srm_initialized_stages_command), + /* srm_data_write() / srm_data_read() */ + KUNIT_CASE(dm_test_srm_data_write_uninitialized_ta_keeps_srm), + KUNIT_CASE(dm_test_srm_data_read_uninitialized_ta_returns_einval), + KUNIT_CASE(dm_test_srm_data_read_empty_srm_returns_zero), + /* lp_write_i2c() / lp_read_i2c() / lp_write_dpcd() / lp_read_dpcd() */ + KUNIT_CASE(dm_test_lp_write_i2c_builds_single_write_payload), + KUNIT_CASE(dm_test_lp_read_i2c_builds_offset_then_read), + KUNIT_CASE(dm_test_lp_write_dpcd_forwards_native_write), + KUNIT_CASE(dm_test_lp_read_dpcd_forwards_native_read), + KUNIT_CASE(dm_test_lp_write_i2c_no_connector_returns_false), + KUNIT_CASE(dm_test_lp_read_dpcd_no_connector_returns_false), + /* lp_atomic_write_poll_read_i2c() / lp_atomic_write_poll_read_aux() */ + KUNIT_CASE(dm_test_lp_atomic_i2c_null_handle_returns_false), + KUNIT_CASE(dm_test_lp_atomic_i2c_oversized_op_returns_false), + KUNIT_CASE(dm_test_lp_atomic_aux_null_handle_returns_false), + KUNIT_CASE(dm_test_lp_atomic_aux_oversized_op_returns_false), + /* hdcp_update_display() / hdcp_remove_display() / hdcp_reset_display() */ + KUNIT_CASE(dm_test_hdcp_update_display_enable_registers_connector), + KUNIT_CASE(dm_test_hdcp_update_display_disable_sets_status_off), + KUNIT_CASE(dm_test_hdcp_remove_display_enabled_resets_cp), + KUNIT_CASE(dm_test_hdcp_remove_display_null_state_clears_connector), + KUNIT_CASE(dm_test_hdcp_reset_display_clears_all_state), + /* enable_assr() */ + KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_returns_false), + KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_ignores_link), + KUNIT_CASE(dm_test_enable_assr_initialized_builds_command_and_fails), + /* update_config() */ + KUNIT_CASE(dm_test_update_config_null_connector_is_noop), + KUNIT_CASE(dm_test_update_config_null_dc_link_is_noop), + KUNIT_CASE(dm_test_update_config_dpms_off_removes_display), + KUNIT_CASE(dm_test_update_config_populates_display_and_link), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 33014a2d2222..058e1ad15dfe 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -8,13 +8,18 @@ #include #include #include +#include #include "dc.h" +#include "core_types.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_mst_types.h" +#include "dc_bios_types.h" #include "dm_helpers.h" #include "ddc_service_types.h" +#include "dmub_cmd.h" #include "amdgpu_dm_helpers.h" #include "amdgpu_dm_kunit_test_helpers.h" @@ -61,6 +66,701 @@ static void dm_test_edid_extract_panel_id_zeros(struct kunit *test) KUNIT_EXPECT_EQ(test, edid_extract_panel_id(edid), 0U); } +/* Tests for apply_edid_quirks() */ + +/* + * Build an EDID whose extracted panel id equals @panel_id. Inverse of + * edid_extract_panel_id(): mfg_id holds the top 16 bits, prod_code the + * low 16 bits (prod_code[0] | prod_code[1] << 8). + */ +static struct edid *dm_test_edid_with_panel_id(struct kunit *test, u32 panel_id) +{ + struct edid *edid; + + edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid); + + edid->mfg_id[0] = (panel_id >> 24) & 0xff; + edid->mfg_id[1] = (panel_id >> 16) & 0xff; + edid->prod_code[0] = panel_id & 0xff; + edid->prod_code[1] = (panel_id >> 8) & 0xff; + + return edid; +} + +/* + * Wire a connector-backed link so apply_edid_quirks() can resolve + * link->priv->base.dev for its drm_dbg_driver() calls. + */ +static struct dc_link *dm_test_quirk_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + link = dm_kunit_alloc_link(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + link->priv = aconnector; + + return link; +} + +/** + * dm_test_apply_edid_quirks_dpcd_poweroff_delay - Test GBT 0x3215 delay quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_dpcd_poweroff_delay(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('G', 'B', 'T', 0x3215)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_EQ(test, edid_caps.panel_patch.wait_after_dpcd_poweroff_ms, 10000U); +} + +/** + * dm_test_apply_edid_quirks_disable_fams - Test SAM panel FAMS-disable quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_disable_fams(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('S', 'A', 'M', 0x0E5E)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.disable_fams); +} + +/** + * dm_test_apply_edid_quirks_remove_sink_ext_caps - Test AUO 0x317 clear quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_remove_sink_ext_caps(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('A', 'U', 'O', 0xA7AB)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.remove_sink_ext_caps); +} + +/** + * dm_test_apply_edid_quirks_disable_colorimetry - Test SDC VSC-disable quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_disable_colorimetry(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('S', 'D', 'C', 0x4154)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.disable_colorimetry); +} + +/** + * dm_test_apply_edid_quirks_skip_phy_ssc - Test DEL 0x4147 PHY SSC quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_skip_phy_ssc(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('D', 'E', 'L', 0x4147)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, link->wa_flags.skip_phy_ssc_reduction); +} + +/** + * dm_test_apply_edid_quirks_unknown_noop - Test unknown panel id is a no-op + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_unknown_noop(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('X', 'Y', 'Z', 0x0000)); + + apply_edid_quirks(link, edid, &edid_caps); + + /* default: branch leaves every quirk field untouched */ + KUNIT_EXPECT_EQ(test, edid_caps.panel_patch.wait_after_dpcd_poweroff_ms, 0U); + KUNIT_EXPECT_FALSE(test, edid_caps.panel_patch.disable_fams); + KUNIT_EXPECT_FALSE(test, edid_caps.panel_patch.remove_sink_ext_caps); + KUNIT_EXPECT_FALSE(test, edid_caps.panel_patch.disable_colorimetry); + KUNIT_EXPECT_FALSE(test, link->wa_flags.skip_phy_ssc_reduction); +} + +/* Tests for dm_helpers_parse_edid_caps() */ + +/* + * Build a minimal valid base EDID block into @dc_edid. When @good_checksum is + * false the final checksum byte is corrupted so drm_edid_is_valid() fails. + * + * manufacturer_id = 0xAC10, product_id = 0x1234, serial = 0x12345678, + * week = 10, year (raw) = 30, digital input, no CEA extension. + */ +static void dm_test_fill_base_edid(struct dc_edid *dc_edid, bool good_checksum) +{ + static const u8 header[8] = { + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 + }; + u8 *raw = dc_edid->raw_edid; + int sum = 0; + int i; + + memset(raw, 0, EDID_LENGTH); + memcpy(raw, header, sizeof(header)); + + raw[8] = 0x10; raw[9] = 0xAC; /* mfg_id -> 0xAC10 */ + raw[10] = 0x34; raw[11] = 0x12; /* prod_code -> 0x1234 */ + raw[12] = 0x78; raw[13] = 0x56; /* serial -> 0x12345678 */ + raw[14] = 0x34; raw[15] = 0x12; + raw[16] = 10; /* mfg_week */ + raw[17] = 30; /* mfg_year (raw) */ + raw[18] = 1; /* version */ + raw[19] = 4; /* revision */ + raw[20] = DRM_EDID_INPUT_DIGITAL; /* digital input */ + raw[126] = 0; /* no extensions */ + + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += raw[i]; + raw[127] = (256 - (sum % 256)) % 256; + if (!good_checksum) + raw[127] ^= 0xFF; /* corrupt checksum */ + + dc_edid->length = EDID_LENGTH; +} + +/** + * dm_test_parse_edid_caps_null_edid - Test NULL edid returns EDID_BAD_INPUT + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_null_edid(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, NULL, &edid_caps), EDID_BAD_INPUT); +} + +/** + * dm_test_parse_edid_caps_null_caps - Test NULL edid_caps returns EDID_BAD_INPUT + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_null_caps(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + dm_test_fill_base_edid(dc_edid, true); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, NULL), EDID_BAD_INPUT); +} + +/** + * dm_test_parse_edid_caps_valid - Test field extraction from a valid EDID + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_valid(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_base_edid(dc_edid, true); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_EQ(test, edid_caps->manufacturer_id, 0xAC10); + KUNIT_EXPECT_EQ(test, edid_caps->product_id, 0x1234); + KUNIT_EXPECT_EQ(test, edid_caps->serial_number, 0x12345678U); + KUNIT_EXPECT_EQ(test, edid_caps->manufacture_week, 10); + KUNIT_EXPECT_EQ(test, edid_caps->manufacture_year, 30); + KUNIT_EXPECT_FALSE(test, edid_caps->analog); +} + +/** + * dm_test_parse_edid_caps_bad_checksum - Test bad checksum still parses fields + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_bad_checksum(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_base_edid(dc_edid, false); + + /* Invalid checksum -> EDID_BAD_CHECKSUM but fields are still parsed */ + KUNIT_EXPECT_EQ(test, + dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), + EDID_BAD_CHECKSUM); + KUNIT_EXPECT_EQ(test, edid_caps->manufacturer_id, 0xAC10); + KUNIT_EXPECT_EQ(test, edid_caps->product_id, 0x1234); +} + +/** + * dm_test_parse_edid_caps_hdmi_frl - Test HDMI/FRL branch via connector info + * @test: The KUnit test context + * + * Drives the edid_caps->edid_hdmi path by marking the connector as HDMI and + * providing a fake dc with FRL enabled, so populate_hdmi_info_from_connector() + * is exercised inside dm_helpers_parse_edid_caps(). + */ +static void dm_test_parse_edid_caps_hdmi_frl(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct amdgpu_dm_connector *aconnector = link->priv; + struct drm_connector *connector = &aconnector->base; + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + struct dc *dc; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link->dc = dc; + dc->config.enable_frl = true; + + dm_test_fill_base_edid(dc_edid, true); + + /* Drive the HDMI/FRL branch */ + connector->display_info.is_hdmi = true; + connector->display_info.hdmi.scdc.supported = true; + connector->display_info.hdmi.max_lanes = 4; + connector->display_info.hdmi.max_frl_rate_per_lane = 12; + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_TRUE(test, edid_caps->edid_hdmi); + KUNIT_EXPECT_TRUE(test, edid_caps->scdc_present); + /* max_lanes 4 + max_frl_rate_per_lane 12 -> rate index 6 */ + KUNIT_EXPECT_EQ(test, edid_caps->max_frl_rate, 6); +} + +/** + * dm_test_parse_edid_caps_hdmi_frl_dsc - Test HDMI FRL DSC sub-branch + * @test: The KUnit test context + * + * Sets the connector's HDMI DSC capability so populate_hdmi_info_from_connector() + * reports frl_dsc_support, exercising the frl_dsc_support log path. + */ +static void dm_test_parse_edid_caps_hdmi_frl_dsc(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct amdgpu_dm_connector *aconnector = link->priv; + struct drm_connector *connector = &aconnector->base; + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + struct dc *dc; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link->dc = dc; + dc->config.enable_frl = true; + + dm_test_fill_base_edid(dc_edid, true); + + connector->display_info.is_hdmi = true; + /* Drive the frl_dsc_support sub-branch */ + connector->display_info.hdmi.dsc_cap.v_1p2 = true; + connector->display_info.hdmi.dsc_cap.bpc_supported = 10; + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_TRUE(test, edid_caps->frl_dsc_support); + KUNIT_EXPECT_TRUE(test, edid_caps->frl_dsc_10bpc); +} + +/* + * Build a 2-block EDID: a valid base block plus a CTA-861 extension block + * carrying one LPCM Short Audio Descriptor and, when @with_speaker is set, a + * Speaker Allocation Data Block, so the audio/speaker parsing code is + * exercised. + */ +static void dm_test_fill_cea_edid(struct dc_edid *dc_edid, bool with_speaker) +{ + u8 *raw = dc_edid->raw_edid; + u8 *ext = raw + EDID_LENGTH; + u8 dtd_offset; + int sum = 0; + int i; + + /* Base block, flagged as having one extension */ + dm_test_fill_base_edid(dc_edid, true); + raw[126] = 1; + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += raw[i]; + raw[127] = (256 - (sum % 256)) % 256; + + /* Audio DB spans [4,7]; optional Speaker Alloc DB spans [8,11] */ + dtd_offset = with_speaker ? 12 : 8; + + /* CTA-861 extension block */ + memset(ext, 0, EDID_LENGTH); + ext[0] = 0x02; /* CTA extension tag */ + ext[1] = 0x03; /* revision 3 */ + ext[2] = dtd_offset; /* DTD offset; end of data blocks */ + ext[3] = 0x00; /* no native DTDs / feature flags */ + /* Audio Data Block: tag 1, length 3, one LPCM SAD */ + ext[4] = (1 << 5) | 3; + ext[5] = (1 << 3) | 1; /* format 1 (LPCM), 2 channels */ + ext[6] = 0x07; /* 32 / 44.1 / 48 kHz */ + ext[7] = 0x07; /* 16 / 20 / 24-bit */ + if (with_speaker) { + /* Speaker Allocation Data Block: tag 4, length 3 */ + ext[8] = (4 << 5) | 3; + ext[9] = 0x01; /* front left / front right */ + } + + sum = 0; + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += ext[i]; + ext[127] = (256 - (sum % 256)) % 256; + + dc_edid->length = 2 * EDID_LENGTH; +} + +/** + * dm_test_parse_edid_caps_cea_audio - Test CEA audio/speaker block parsing + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_cea_audio(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_cea_edid(dc_edid, true); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_EQ(test, edid_caps->audio_mode_count, 1); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].format_code, 1); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].channel_count, 2); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].sample_rate, 0x07); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].sample_size, 0x07); + KUNIT_EXPECT_EQ(test, edid_caps->speaker_flags, 0x01); +} + +/** + * dm_test_parse_edid_caps_cea_no_speaker - Test default speaker flags path + * @test: The KUnit test context + * + * Audio data block present but no Speaker Allocation Data Block, so + * speaker_flags falls back to DEFAULT_SPEAKER_LOCATION. + */ +static void dm_test_parse_edid_caps_cea_no_speaker(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_cea_edid(dc_edid, false); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_EQ(test, edid_caps->audio_mode_count, 1); + KUNIT_EXPECT_EQ(test, edid_caps->speaker_flags, DEFAULT_SPEAKER_LOCATION); +} + +/* Tests for ACPI and VBIOS EDID readers */ + +/** + * dm_test_probe_acpi_edid_no_companion - Test ACPI probe without companion + * @test: The KUnit test context + */ +static void dm_test_probe_acpi_edid_no_companion(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + u8 buf[EDID_LENGTH] = {0}; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + + KUNIT_EXPECT_EQ(test, + dm_helpers_probe_acpi_edid(&aconnector->base, buf, 0, sizeof(buf)), + -ENODEV); +} + +/** + * dm_test_read_acpi_edid_debug_mask_disabled - Test debug mask early return + * @test: The KUnit test context + */ +static void dm_test_read_acpi_edid_debug_mask_disabled(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + uint old_debug_mask; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + old_debug_mask = dm_helpers_get_dc_debug_mask(); + + dm_helpers_set_dc_debug_mask(old_debug_mask | DC_DISABLE_ACPI_EDID); + KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector)); + dm_helpers_set_dc_debug_mask(old_debug_mask); +} + +/** + * dm_test_read_acpi_edid_non_panel_connector - Test non-panel connector skip + * @test: The KUnit test context + */ +static void dm_test_read_acpi_edid_non_panel_connector(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + + KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector)); +} + +/** + * dm_test_read_acpi_edid_force_off - Test forced-off connector skip + * @test: The KUnit test context + */ +static void dm_test_read_acpi_edid_force_off(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + aconnector->base.force = DRM_FORCE_OFF; + + KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector)); +} + +struct dm_test_vbios_edid { + struct dc_bios bios; + enum bp_result result; + const u8 *fake_edid; + u16 fake_edid_size; + u16 width_mm; + u16 height_mm; +}; + +static enum bp_result dm_test_get_embedded_panel_info(struct dc_bios *bios, + struct embedded_panel_info *info) +{ + struct dm_test_vbios_edid *fixture; + + fixture = container_of(bios, struct dm_test_vbios_edid, bios); + if (fixture->result != BP_RESULT_OK) + return fixture->result; + + info->fake_edid = fixture->fake_edid; + info->fake_edid_size = fixture->fake_edid_size; + info->panel_width_mm = fixture->width_mm; + info->panel_height_mm = fixture->height_mm; + + return BP_RESULT_OK; +} + +static const struct dc_vbios_funcs dm_test_vbios_edid_funcs = { + .get_embedded_panel_info = dm_test_get_embedded_panel_info, +}; + +static void dm_test_setup_vbios_link(struct kunit *test, + struct dc_link **link_out, + struct amdgpu_dm_connector **aconnector_out, + struct dm_test_vbios_edid **fixture_out) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + link = dm_kunit_alloc_link(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture); + aconnector = dm_kunit_alloc_connector(test, adev, link); + + fixture->bios.funcs = &dm_test_vbios_edid_funcs; + fixture->result = BP_RESULT_OK; + ctx->dc_bios = &fixture->bios; + link->ctx = ctx; + link->priv = aconnector; + link->connector_signal = SIGNAL_TYPE_EDP; + + *link_out = link; + *aconnector_out = aconnector; + *fixture_out = fixture; +} + +/** + * dm_test_read_vbios_edid_non_embedded - Test non-embedded signal skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_non_embedded(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_missing_callback - Test missing callback skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_missing_callback(struct kunit *test) +{ + static const struct dc_vbios_funcs empty_funcs; + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + fixture->bios.funcs = &empty_funcs; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_callback_error - Test callback failure skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_callback_error(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + fixture->result = BP_RESULT_BADINPUT; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_missing_fake_edid - Test missing fake EDID skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_missing_fake_edid(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + fixture->fake_edid = NULL; + fixture->fake_edid_size = 0; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_invalid_fake_edid - Test invalid fake EDID skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_invalid_fake_edid(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_edid *dc_edid; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + dm_test_fill_base_edid(dc_edid, false); + fixture->fake_edid = dc_edid->raw_edid; + fixture->fake_edid_size = EDID_LENGTH; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_valid - Test valid fake EDID updates display size + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_valid(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + const struct drm_edid *edid; + struct dc_edid *dc_edid; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + dm_test_fill_base_edid(dc_edid, true); + fixture->fake_edid = dc_edid->raw_edid; + fixture->fake_edid_size = EDID_LENGTH; + fixture->width_mm = 301; + fixture->height_mm = 201; + + edid = dm_helpers_read_vbios_hardcoded_edid(link, aconnector); + + KUNIT_ASSERT_NOT_NULL(test, edid); + KUNIT_EXPECT_EQ(test, aconnector->base.display_info.width_mm, 301U); + KUNIT_EXPECT_EQ(test, aconnector->base.display_info.height_mm, 201U); + drm_edid_free(edid); +} + /* Tests for dm_is_freesync_pcon_whitelist() */ /** @@ -511,6 +1211,483 @@ static void dm_test_dp_write_dpcd_null_priv(struct kunit *test) dm_helpers_dp_write_dpcd(NULL, link, 0, &data, sizeof(data))); } +/* + * Stub AUX transfer that ACKs every transaction (zero-filling reads), so + * drm_dp_dpcd_read()/drm_dp_dpcd_write() report the full transfer size. + */ +static ssize_t dm_test_dpcd_ack_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_READ) + memset(msg->buffer, 0, msg->size); + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + +/* + * Wire a connector-backed link with a working AUX channel so the DPCD + * read/write helpers can complete a real transaction. + */ +static struct dc_link *dm_test_dpcd_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + link = dm_kunit_alloc_link(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + + aconnector->dm_dp_aux.aux.drm_dev = &adev->ddev; + aconnector->dm_dp_aux.aux.transfer = dm_test_dpcd_ack_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + + link->priv = aconnector; + + return link; +} + +/** + * dm_test_dp_read_dpcd_success - Test DPCD read returns true on ACKed transfer + * @test: The KUnit test context + */ +static void dm_test_dp_read_dpcd_success(struct kunit *test) +{ + struct dc_link *link = dm_test_dpcd_link(test); + uint8_t data = 0; + + KUNIT_EXPECT_TRUE(test, + dm_helpers_dp_read_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/** + * dm_test_dp_write_dpcd_success - Test DPCD write returns true on ACKed transfer + * @test: The KUnit test context + */ +static void dm_test_dp_write_dpcd_success(struct kunit *test) +{ + struct dc_link *link = dm_test_dpcd_link(test); + uint8_t data = 0; + + KUNIT_EXPECT_TRUE(test, + dm_helpers_dp_write_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/* Tests for dm_helpers_execute_fused_io() */ + +/** + * dm_test_execute_fused_io_null_dmub_srv - Test fused IO fails without DMUB service + * @test: The KUnit test context + */ +static void dm_test_execute_fused_io_null_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + union dmub_rb_cmd *commands; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + mutex_init(&adev->dm.dpia_aux_lock); + spin_lock_init(&adev->dm.dmub_lock); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + commands = kunit_kzalloc(test, sizeof(*commands), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, commands); + + ctx->driver_context = adev; + link->ctx = ctx; + commands[0].fused_io.request.u.aux.ddc_line = 0; + + KUNIT_EXPECT_FALSE(test, dm_helpers_execute_fused_io(ctx, link, commands, 1, 1)); +} + +struct dm_test_synaptics_aux { + struct drm_dp_aux aux; + u32 fail_address; + u32 last_dpcd_write_address; + u8 rc_result; + u8 dpcd_read_value; + u8 dpcd_write_value; + u8 last_rc_data[16]; + u8 read_rc_data[16]; + u8 last_rc_command; + u8 rc_commands[32]; + u8 dsc_enable_values[8]; + u32 last_rc_offset; + u32 last_rc_length; + unsigned int rc_data_writes; + unsigned int rc_data_reads; + unsigned int rc_command_reads; + unsigned int rc_result_reads; + unsigned int rc_command_count; + unsigned int downspread_reads; + unsigned int downspread_writes; + unsigned int dsc_enable_writes; +}; + +static ssize_t dm_test_synaptics_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct dm_test_synaptics_aux *fixture; + u8 request; + u8 *buffer; + size_t copy_size; + unsigned int index; + + fixture = container_of(aux, struct dm_test_synaptics_aux, aux); + request = msg->request & ~DP_AUX_I2C_MOT; + buffer = msg->buffer; + + if (fixture->fail_address == msg->address) + return -EIO; + + if (request == DP_AUX_NATIVE_WRITE) { + switch (msg->address) { + case DP_DOWNSPREAD_CTRL: + fixture->last_dpcd_write_address = msg->address; + if (msg->size) + fixture->dpcd_write_value = buffer[0]; + fixture->downspread_writes++; + break; + case SYNAPTICS_RC_DATA: + copy_size = min_t(size_t, msg->size, sizeof(fixture->last_rc_data)); + memset(fixture->last_rc_data, 0, sizeof(fixture->last_rc_data)); + memcpy(fixture->last_rc_data, buffer, copy_size); + fixture->rc_data_writes++; + break; + case SYNAPTICS_RC_OFFSET: + if (msg->size >= 4) + fixture->last_rc_offset = buffer[0] | buffer[1] << 8 | + buffer[2] << 16 | buffer[3] << 24; + break; + case SYNAPTICS_RC_LENGTH: + if (msg->size >= 2) + fixture->last_rc_length = buffer[0] | buffer[1] << 8; + break; + case SYNAPTICS_RC_COMMAND: + fixture->last_rc_command = buffer[0]; + if (fixture->rc_command_count < ARRAY_SIZE(fixture->rc_commands)) { + fixture->rc_commands[fixture->rc_command_count] = buffer[0] & 0x7f; + fixture->rc_command_count++; + } + break; + case DP_DSC_ENABLE: + if (fixture->dsc_enable_writes < ARRAY_SIZE(fixture->dsc_enable_values)) { + fixture->dsc_enable_values[fixture->dsc_enable_writes] = buffer[0]; + fixture->dsc_enable_writes++; + } + break; + } + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + + if (request == DP_AUX_NATIVE_READ) { + memset(buffer, 0, msg->size); + switch (msg->address) { + case DP_DOWNSPREAD_CTRL: + if (msg->size) + buffer[0] = fixture->dpcd_read_value; + fixture->downspread_reads++; + break; + case SYNAPTICS_RC_COMMAND: + if (msg->size) + buffer[0] = fixture->last_rc_command & 0x7f; + fixture->rc_command_reads++; + break; + case SYNAPTICS_RC_RESULT: + if (msg->size) + buffer[0] = fixture->rc_result; + fixture->rc_result_reads++; + break; + case SYNAPTICS_RC_DATA: + copy_size = min_t(size_t, msg->size, sizeof(fixture->read_rc_data)); + for (index = 0; index < copy_size; index++) + buffer[index] = fixture->read_rc_data[index]; + fixture->rc_data_reads++; + break; + } + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + +static struct dm_test_synaptics_aux *dm_test_current_aux_recorder; + +static ssize_t dm_test_current_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + return dm_test_synaptics_aux_transfer(&dm_test_current_aux_recorder->aux, msg); +} + +static struct dm_test_synaptics_aux *dm_test_alloc_synaptics_aux_with_dev(struct kunit *test, + struct drm_device *drm_dev) +{ + struct dm_test_synaptics_aux *fixture; + unsigned int index; + + fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture); + + for (index = 0; index < ARRAY_SIZE(fixture->read_rc_data); index++) + fixture->read_rc_data[index] = 0x03; + + fixture->rc_result = 0; + fixture->aux.drm_dev = drm_dev; + fixture->aux.transfer = dm_test_synaptics_aux_transfer; + drm_dp_aux_init(&fixture->aux); + + return fixture; +} + +static struct dm_test_synaptics_aux *dm_test_alloc_synaptics_aux(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + return dm_test_alloc_synaptics_aux_with_dev(test, &adev->ddev); +} + +static void dm_test_expect_synaptics_commands(struct kunit *test, + struct dm_test_synaptics_aux *fixture, + const u8 *expected_commands, + unsigned int expected_count) +{ + unsigned int index; + + KUNIT_ASSERT_EQ(test, fixture->rc_command_count, expected_count); + + for (index = 0; index < expected_count; index++) + KUNIT_EXPECT_EQ(test, fixture->rc_commands[index], expected_commands[index]); +} + +static void dm_test_setup_synaptics_stream(struct dc_stream_state *stream, + struct dc_link *link) +{ + stream->link = link; + stream->signal = SIGNAL_TYPE_DISPLAY_PORT_MST; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DP_DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 2; + memcpy(link->dpcd_caps.branch_dev_name, "SYNA", 4); +} + +/** + * dm_test_execute_synaptics_rc_command_write_success - Test RC write success + * @test: The KUnit test context + */ +static void dm_test_execute_synaptics_rc_command_write_success(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + u8 data[5] = { 'P', 'R', 'I', 'U', 'S' }; + + fixture = dm_test_alloc_synaptics_aux(test); + + KUNIT_EXPECT_TRUE(test, execute_synaptics_rc_command(&fixture->aux, true, + 0x01, sizeof(data), 0x123456, + data)); + KUNIT_EXPECT_EQ(test, memcmp(fixture->last_rc_data, data, sizeof(data)), 0); + KUNIT_EXPECT_EQ(test, fixture->last_rc_offset, 0x123456U); + KUNIT_EXPECT_EQ(test, fixture->last_rc_length, (u32)sizeof(data)); + KUNIT_EXPECT_EQ(test, fixture->last_rc_command, (u8)0x81); + KUNIT_EXPECT_EQ(test, fixture->rc_command_reads, 1U); + KUNIT_EXPECT_EQ(test, fixture->rc_result_reads, 1U); +} + +/** + * dm_test_execute_synaptics_rc_command_read_success - Test RC read success + * @test: The KUnit test context + */ +static void dm_test_execute_synaptics_rc_command_read_success(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + u8 data[4] = { 0 }; + u8 expected[4] = { 0xa5, 0x5a, 0xc3, 0x3c }; + + fixture = dm_test_alloc_synaptics_aux(test); + memcpy(fixture->read_rc_data, expected, sizeof(expected)); + + KUNIT_EXPECT_TRUE(test, execute_synaptics_rc_command(&fixture->aux, false, + 0x31, sizeof(data), 0x220998, + data)); + KUNIT_EXPECT_EQ(test, memcmp(data, expected, sizeof(expected)), 0); + KUNIT_EXPECT_EQ(test, fixture->rc_data_writes, 0U); + KUNIT_EXPECT_EQ(test, fixture->rc_data_reads, 1U); + KUNIT_EXPECT_EQ(test, fixture->last_rc_offset, 0x220998U); + KUNIT_EXPECT_EQ(test, fixture->last_rc_length, (u32)sizeof(data)); +} + +/** + * dm_test_execute_synaptics_rc_command_write_fail - Test RC write failure + * @test: The KUnit test context + */ +static void dm_test_execute_synaptics_rc_command_write_fail(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + u8 data = 0; + + fixture = dm_test_alloc_synaptics_aux(test); + fixture->fail_address = SYNAPTICS_RC_LENGTH; + + KUNIT_EXPECT_FALSE(test, execute_synaptics_rc_command(&fixture->aux, true, + 0x01, sizeof(data), 0, &data)); + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 0U); +} + +/** + * dm_test_apply_synaptics_fifo_reset_wa_full - Test full FIFO reset sequence + * @test: The KUnit test context + */ +static void dm_test_apply_synaptics_fifo_reset_wa_full(struct kunit *test) +{ + static const u8 expected_commands[] = { + 0x01, 0x31, 0x21, 0x31, 0x21, 0x31, 0x21, + 0x31, 0x21, 0x31, 0x31, 0x21, 0x02, + }; + struct dm_test_synaptics_aux *fixture; + + fixture = dm_test_alloc_synaptics_aux(test); + + apply_synaptics_fifo_reset_wa(&fixture->aux); + + dm_test_expect_synaptics_commands(test, fixture, expected_commands, + ARRAY_SIZE(expected_commands)); + KUNIT_EXPECT_EQ(test, fixture->rc_result_reads, (unsigned int)ARRAY_SIZE(expected_commands)); +} + +/** + * dm_test_apply_synaptics_fifo_reset_wa_first_fail - Test FIFO reset early exit + * @test: The KUnit test context + */ +static void dm_test_apply_synaptics_fifo_reset_wa_first_fail(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + + fixture = dm_test_alloc_synaptics_aux(test); + fixture->rc_result = 0xff; + + apply_synaptics_fifo_reset_wa(&fixture->aux); + + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 1U); + KUNIT_EXPECT_EQ(test, fixture->rc_commands[0], (u8)0x01); +} + +static void dm_test_write_dsc_enable_synaptics(struct kunit *test, + bool link_active, + bool enable, + bool synaptics_branch, + unsigned int expected_dsc_writes, + unsigned int expected_rc_commands) +{ + struct dm_test_synaptics_aux *fixture; + struct dc_stream_state *stream; + struct dc_link *link; + u8 ret; + + fixture = dm_test_alloc_synaptics_aux(test); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + link = dm_kunit_alloc_link(test); + + dm_test_setup_synaptics_stream(stream, link); + link->link_status.link_active = link_active; + if (!synaptics_branch) + memcpy(link->dpcd_caps.branch_dev_name, "ABCD", 4); + + ret = write_dsc_enable_synaptics_non_virtual_dpcd_mst(&fixture->aux, stream, enable); + + KUNIT_EXPECT_EQ(test, ret, expected_dsc_writes ? 1 : 0); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_writes, expected_dsc_writes); + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, expected_rc_commands); + if (expected_dsc_writes) + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_values[0], enable ? 1 : 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_enable_inactive - Test enable plus FIFO reset + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_enable_inactive(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, false, true, true, 1, 13); +} + +/** + * dm_test_write_dsc_enable_synaptics_enable_active - Test enable skips FIFO reset + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_enable_active(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, true, true, true, 1, 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_disable_inactive - Test inactive disable writes DPCD + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_disable_inactive(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, false, false, true, 1, 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_disable_active - Test active disable skips DPCD + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_disable_active(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, true, false, true, 0, 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_enable_non_synaptics - Test non-Synaptics enable + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_enable_non_synaptics(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, false, true, false, 1, 0); +} + +/** + * dm_test_dp_write_dsc_enable_routes_synaptics - Test public DSC helper workaround route + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_routes_synaptics(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + struct dc_link *link; + + fixture = dm_test_alloc_synaptics_aux(test); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + link = dm_kunit_alloc_link(test); + + dm_test_setup_synaptics_stream(stream, link); + stream->dm_stream_context = aconnector; + aconnector->dc_link = link; + aconnector->dsc_aux = &fixture->aux; + + KUNIT_EXPECT_TRUE(test, dm_helpers_dp_write_dsc_enable(NULL, stream, true)); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_writes, 1U); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_values[0], (u8)1); + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 13U); +} + /* Tests for dm_helpers_dp_mst_start_top_mgr() / dm_helpers_dp_mst_stop_top_mgr() */ /** @@ -577,10 +1754,1997 @@ static void dm_test_dp_write_hblank_reduction_false(struct kunit *test) KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_hblank_reduction(NULL, NULL)); } +/* Tests for get_dsc_max_slices() */ + +/** + * dm_test_get_dsc_max_slices_1_340 - Test 1 slice at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_1_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(1, 340), 1); +} + +/** + * dm_test_get_dsc_max_slices_2_340 - Test 2 slices at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_2_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(2, 340), 2); +} + +/** + * dm_test_get_dsc_max_slices_4_340 - Test 4 slices at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_4_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(4, 340), 3); +} + +/** + * dm_test_get_dsc_max_slices_8_340 - Test 8 slices at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_8_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(8, 340), 4); +} + +/** + * dm_test_get_dsc_max_slices_8_400 - Test 8 slices at 400 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_8_400(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(8, 400), 5); +} + +/** + * dm_test_get_dsc_max_slices_12_400 - Test 12 slices at 400 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_12_400(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(12, 400), 6); +} + +/** + * dm_test_get_dsc_max_slices_16_400 - Test 16 slices at 400 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_16_400(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(16, 400), 7); +} + +/** + * dm_test_get_dsc_max_slices_unknown - Test unknown combination returns 0 + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_unknown(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(3, 340), 0); + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(1, 400), 0); +} + +/* Tests for dm_helpers_init_panel_settings() */ + +/** + * dm_test_init_panel_settings_pps - Test panel power sequence settings init + * @test: The KUnit test context + */ +static void dm_test_init_panel_settings_pps(struct kunit *test) +{ + struct dc_panel_config panel_config = {0}; + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->edid_caps.panel_patch.extra_t3_ms = 100; + sink->edid_caps.panel_patch.extra_t7_ms = 200; + sink->edid_caps.panel_patch.extra_delay_backlight_off = 50; + sink->edid_caps.panel_patch.extra_t12_ms = 300; + + dm_helpers_init_panel_settings(NULL, &panel_config, sink); + + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_t3_ms, 100U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_t7_ms, 200U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_delay_backlight_off, 50U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_post_t7_ms, 0U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_pre_t11_ms, 0U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_t12_ms, 300U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_post_OUI_ms, 0U); +} + +/** + * dm_test_init_panel_settings_dsc - Test DSC defaults in panel settings init + * @test: The KUnit test context + */ +static void dm_test_init_panel_settings_dsc(struct kunit *test) +{ + struct dc_panel_config panel_config; + struct dc_sink *sink; + + memset(&panel_config, 0xFF, sizeof(panel_config)); + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + dm_helpers_init_panel_settings(NULL, &panel_config, sink); + + KUNIT_EXPECT_FALSE(test, panel_config.dsc.disable_dsc_edp); + KUNIT_EXPECT_EQ(test, panel_config.dsc.force_dsc_edp_policy, 0U); +} + +/* Tests for dm_helpers_override_panel_settings() */ + +/** + * dm_test_override_panel_settings_debug_mask_disables_dsc - Test DSC mask + * @test: The KUnit test context + */ +static void dm_test_override_panel_settings_debug_mask_disables_dsc(struct kunit *test) +{ + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + uint old_debug_mask; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + link = dm_kunit_alloc_link(test); + ctx->dc = dc; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + old_debug_mask = dm_helpers_get_dc_debug_mask(); + dm_helpers_set_dc_debug_mask(old_debug_mask | DC_DISABLE_DSC); + dm_helpers_override_panel_settings(ctx, link); + dm_helpers_set_dc_debug_mask(old_debug_mask); + + KUNIT_EXPECT_TRUE(test, link->panel_config.dsc.disable_dsc_edp); +} + +/** + * dm_test_override_panel_settings_second_edp_disables_psr - Test eDP index 1 + * @test: The KUnit test context + */ +static void dm_test_override_panel_settings_second_edp_disables_psr(struct kunit *test) +{ + struct dc_context *ctx; + struct dc_link *first_link; + struct dc_link *second_link; + struct dc *dc; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + first_link = dm_kunit_alloc_link(test); + second_link = dm_kunit_alloc_link(test); + + ctx->dc = dc; + dc->link_count = 2; + dc->links[0] = first_link; + dc->links[1] = second_link; + first_link->connector_signal = SIGNAL_TYPE_EDP; + second_link->connector_signal = SIGNAL_TYPE_EDP; + + dm_helpers_override_panel_settings(ctx, second_link); + + KUNIT_EXPECT_TRUE(test, second_link->panel_config.psr.disable_psr); + KUNIT_EXPECT_TRUE(test, second_link->panel_config.psr.disallow_psrsu); + KUNIT_EXPECT_TRUE(test, second_link->panel_config.psr.disallow_replay); +} + +/* Tests for fill_dc_mst_payload_table_from_drm() */ + +/** + * dm_test_fill_mst_payload_table_enable - Test payload table fill on enable + * @test: The KUnit test context + */ +static void dm_test_fill_mst_payload_table_enable(struct kunit *test) +{ + struct dc_link *link; + struct drm_dp_mst_atomic_payload payload = {0}; + struct dc_dp_mst_stream_allocation_table table = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Pre-existing allocation in the link table */ + link->mst_stream_alloc_table.stream_count = 1; + link->mst_stream_alloc_table.stream_allocations[0].vcp_id = 1; + link->mst_stream_alloc_table.stream_allocations[0].slot_count = 4; + + /* New payload to add */ + payload.vcpi = 2; + payload.time_slots = 8; + + fill_dc_mst_payload_table_from_drm(link, true, &payload, &table); + + /* Should contain both the pre-existing and new allocation */ + KUNIT_EXPECT_EQ(test, table.stream_count, 2); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].vcp_id, 1); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 4); + KUNIT_EXPECT_EQ(test, table.stream_allocations[1].vcp_id, 2); + KUNIT_EXPECT_EQ(test, table.stream_allocations[1].slot_count, 8); +} + +/** + * dm_test_fill_mst_payload_table_disable - Test payload table fill on disable + * @test: The KUnit test context + */ +static void dm_test_fill_mst_payload_table_disable(struct kunit *test) +{ + struct dc_link *link; + struct drm_dp_mst_atomic_payload payload = {0}; + struct dc_dp_mst_stream_allocation_table table = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Two existing allocations in the link table */ + link->mst_stream_alloc_table.stream_count = 2; + link->mst_stream_alloc_table.stream_allocations[0].vcp_id = 1; + link->mst_stream_alloc_table.stream_allocations[0].slot_count = 4; + link->mst_stream_alloc_table.stream_allocations[1].vcp_id = 2; + link->mst_stream_alloc_table.stream_allocations[1].slot_count = 8; + + /* Remove vcp_id 1 */ + payload.vcpi = 1; + payload.time_slots = 4; + + fill_dc_mst_payload_table_from_drm(link, false, &payload, &table); + + /* Only vcp_id 2 should remain */ + KUNIT_EXPECT_EQ(test, table.stream_count, 1); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].vcp_id, 2); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 8); +} + +/** + * dm_test_fill_mst_payload_table_empty - Test payload table fill when empty + * @test: The KUnit test context + */ +static void dm_test_fill_mst_payload_table_empty(struct kunit *test) +{ + struct dc_link *link; + struct drm_dp_mst_atomic_payload payload = {0}; + struct dc_dp_mst_stream_allocation_table table = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Enable on an empty table */ + payload.vcpi = 5; + payload.time_slots = 12; + + fill_dc_mst_payload_table_from_drm(link, true, &payload, &table); + + KUNIT_EXPECT_EQ(test, table.stream_count, 1); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].vcp_id, 5); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 12); +} + +/* Tests for dm_helpers_submit_i2c() */ + +/** + * dm_test_submit_i2c_null_priv - Test i2c submit returns false without connector + * @test: The KUnit test context + */ +static void dm_test_submit_i2c_null_priv(struct kunit *test) +{ + struct dc_link *link; + struct i2c_command cmd = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + KUNIT_EXPECT_FALSE(test, dm_helpers_submit_i2c(NULL, link, &cmd)); +} + +struct dm_test_i2c_adapter { + struct i2c_adapter base; + struct kunit *test; + struct i2c_payload *expected_payloads; + int expected_num; + int ret; + int calls; +}; + +static void dm_test_i2c_lock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + rt_mutex_lock(&adapter->bus_lock); +} + +static int dm_test_i2c_trylock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + return rt_mutex_trylock(&adapter->bus_lock); +} + +static void dm_test_i2c_unlock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + rt_mutex_unlock(&adapter->bus_lock); +} + +static const struct i2c_lock_operations dm_test_i2c_lock_ops = { + .lock_bus = dm_test_i2c_lock_bus, + .trylock_bus = dm_test_i2c_trylock_bus, + .unlock_bus = dm_test_i2c_unlock_bus, +}; + +static int dm_test_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, + int num) +{ + struct dm_test_i2c_adapter *fake; + int i; + + fake = container_of(adapter, struct dm_test_i2c_adapter, base); + fake->calls++; + + KUNIT_EXPECT_EQ(fake->test, num, fake->expected_num); + + for (i = 0; i < num; i++) { + KUNIT_EXPECT_EQ(fake->test, msgs[i].flags, + fake->expected_payloads[i].write ? 0 : I2C_M_RD); + KUNIT_EXPECT_EQ(fake->test, msgs[i].addr, + (u16)fake->expected_payloads[i].address); + KUNIT_EXPECT_EQ(fake->test, msgs[i].len, + (u16)fake->expected_payloads[i].length); + KUNIT_EXPECT_PTR_EQ(fake->test, msgs[i].buf, + fake->expected_payloads[i].data); + } + + return fake->ret; +} + +static const struct i2c_algorithm dm_test_i2c_algorithm = { + .master_xfer = dm_test_i2c_master_xfer, +}; + +struct dm_test_mccs_i2c_adapter { + struct i2c_adapter base; + u8 write_data[16]; + u8 read_reply[11]; + int write_ret; + int read_ret; + unsigned int write_len; + unsigned int writes; + unsigned int reads; +}; + +static int dm_test_mccs_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, + int num) +{ + struct dm_test_mccs_i2c_adapter *fake; + struct i2c_msg *msg = msgs; + size_t copy_len; + + fake = container_of(adapter, struct dm_test_mccs_i2c_adapter, base); + + if (num != 1) + return 0; + + if (msg->flags & I2C_M_RD) { + fake->reads++; + if (fake->read_ret != 1) + return fake->read_ret; + + copy_len = min_t(size_t, msg->len, sizeof(fake->read_reply)); + memcpy(msg->buf, fake->read_reply, copy_len); + return 1; + } + + fake->writes++; + if (fake->write_ret != 1) + return fake->write_ret; + + copy_len = min_t(size_t, msg->len, sizeof(fake->write_data)); + memcpy(fake->write_data, msg->buf, copy_len); + fake->write_len = copy_len; + + return 1; +} + +static const struct i2c_algorithm dm_test_mccs_i2c_algorithm = { + .master_xfer = dm_test_mccs_i2c_master_xfer, +}; + +static struct dm_test_mccs_i2c_adapter *dm_test_alloc_mccs_i2c(struct kunit *test) +{ + struct dm_test_mccs_i2c_adapter *fake; + + fake = kunit_kzalloc(test, sizeof(*fake), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fake); + + fake->base.algo = &dm_test_mccs_i2c_algorithm; + fake->base.lock_ops = &dm_test_i2c_lock_ops; + fake->write_ret = 1; + fake->read_ret = 1; + rt_mutex_init(&fake->base.bus_lock); + rt_mutex_init(&fake->base.mux_lock); + + return fake; +} + +static u8 dm_test_mccs_checksum(const u8 *data, unsigned int len) +{ + u8 checksum = 0x6e; + unsigned int i; + + for (i = 0; i < len; i++) + checksum ^= data[i]; + + return checksum; +} + +static void dm_test_submit_i2c_transfer(struct kunit *test, + bool full_transfer) +{ + struct amdgpu_dm_connector *aconnector; + struct dm_test_i2c_adapter *fake; + struct dc_link *link; + u8 write_data[2] = { 0x12, 0x34 }; + u8 read_data[3] = { 0 }; + struct i2c_payload payloads[] = { + { true, 0x50, sizeof(write_data), write_data }, + { false, 0x51, sizeof(read_data), read_data }, + }; + struct i2c_command cmd = { + .payloads = payloads, + .number_of_payloads = ARRAY_SIZE(payloads), + }; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + fake = kunit_kzalloc(test, sizeof(*fake), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fake); + + fake->test = test; + fake->expected_payloads = payloads; + fake->expected_num = ARRAY_SIZE(payloads); + fake->ret = full_transfer ? ARRAY_SIZE(payloads) : 1; + fake->base.algo = &dm_test_i2c_algorithm; + fake->base.lock_ops = &dm_test_i2c_lock_ops; + rt_mutex_init(&fake->base.bus_lock); + rt_mutex_init(&fake->base.mux_lock); + + aconnector->i2c = (struct amdgpu_i2c_adapter *)fake; + link->priv = aconnector; + + KUNIT_EXPECT_EQ(test, dm_helpers_submit_i2c(NULL, link, &cmd), full_transfer); + KUNIT_EXPECT_EQ(test, fake->calls, 1); +} + +/** + * dm_test_submit_i2c_success - Test payloads are mapped to i2c_msg array + * @test: The KUnit test context + */ +static void dm_test_submit_i2c_success(struct kunit *test) +{ + dm_test_submit_i2c_transfer(test, true); +} + +/** + * dm_test_submit_i2c_partial_transfer - Test short i2c transfer returns false + * @test: The KUnit test context + */ +static void dm_test_submit_i2c_partial_transfer(struct kunit *test) +{ + dm_test_submit_i2c_transfer(test, false); +} + +/* Tests for dm_helper_dmub_aux_transfer_sync() */ + +/** + * dm_test_dmub_aux_transfer_sync_hpd_discon - Test aux transfer with HPD disconnected + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_transfer_sync_hpd_discon(struct kunit *test) +{ + struct dc_link *link; + struct dc_context *ctx; + struct aux_payload payload = {0}; + enum aux_return_code_type result = AUX_RET_SUCCESS; + int ret; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + link->hpd_status = false; + + ret = dm_helper_dmub_aux_transfer_sync(ctx, link, &payload, &result); + + KUNIT_EXPECT_EQ(test, ret, -1); + KUNIT_EXPECT_EQ(test, (int)result, (int)AUX_RET_ERROR_HPD_DISCON); +} + +/* Tests for empty stub functions (must not crash) */ + +/** + * dm_test_dp_update_branch_info_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_dp_update_branch_info_no_crash(struct kunit *test) +{ + dm_helpers_dp_update_branch_info(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_poll_pending_down_reply_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_poll_pending_down_reply_no_crash(struct kunit *test) +{ + dm_helpers_dp_mst_poll_pending_down_reply(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_clear_payload_alloc_table_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_clear_payload_alloc_table_no_crash(struct kunit *test) +{ + dm_helpers_dp_mst_clear_payload_allocation_table(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_set_dcn_clocks_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_set_dcn_clocks_no_crash(struct kunit *test) +{ + dm_set_dcn_clocks(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_dmu_timeout_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_dmu_timeout_no_crash(struct kunit *test) +{ + dm_helpers_dmu_timeout(NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_smu_timeout_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_smu_timeout_no_crash(struct kunit *test) +{ + dm_helpers_smu_timeout(NULL, 0, 0, 0); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_set_phyd32clk_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_set_phyd32clk_no_crash(struct kunit *test) +{ + dm_set_phyd32clk(NULL, 0); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_update_branch_bandwidth_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_update_branch_bandwidth_no_crash(struct kunit *test) +{ + dm_helpers_dp_mst_update_branch_bandwidth(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/* Tests for MST functions null-connector early returns */ + +/** + * dm_test_mst_write_payload_alloc_table_null_ctx - Test null connector returns false + * @test: The KUnit test context + */ +static void dm_test_mst_write_payload_alloc_table_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* dm_stream_context is NULL → aconnector is NULL → return false */ + KUNIT_EXPECT_FALSE(test, + dm_helpers_dp_mst_write_payload_allocation_table(NULL, stream, NULL, true)); +} + +/** + * dm_test_mst_poll_for_act_null_ctx - Test null connector returns ACT_FAILED + * @test: The KUnit test context + */ +static void dm_test_mst_poll_for_act_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + KUNIT_EXPECT_EQ(test, + (int)dm_helpers_dp_mst_poll_for_allocation_change_trigger(NULL, stream), + (int)ACT_FAILED); +} + +/** + * dm_test_mst_send_payload_alloc_null_ctx - Test null connector does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_send_payload_alloc_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* Should early-return without crash */ + dm_helpers_dp_mst_send_payload_allocation(NULL, stream); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_update_mgr_dealloc_null_ctx - Test null connector does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_update_mgr_dealloc_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + dm_helpers_dp_mst_update_mst_mgr_for_deallocation(NULL, stream); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_is_dp_sink_present_null_priv - Test null connector returns true + * @test: The KUnit test context + */ +static void dm_test_is_dp_sink_present_null_priv(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* NULL priv → DRM_ERROR + return true */ + KUNIT_EXPECT_TRUE(test, dm_helpers_is_dp_sink_present(link)); +} + +/* Tests for dm_helpers_dmub_outbox_interrupt_control() */ + +/** + * dm_test_dmub_outbox_interrupt_control_null_dc - Test outbox irq control with NULL dc + * @test: The KUnit test context + * + * dc_interrupt_set() is NULL-safe and returns false when dc is NULL, so the + * helper returns false without touching real interrupt hardware. + */ +static void dm_test_dmub_outbox_interrupt_control_null_dc(struct kunit *test) +{ + struct dc_context *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + /* ctx->dc is NULL → dc_interrupt_set returns false */ + KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, true)); + KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, false)); +} + +/* Tests for dm_helpers_mst_enable_stream_features() */ + +/** + * dm_test_mst_enable_stream_features_aux_disabled - Test early return when aux disabled + * @test: The KUnit test context + */ +static void dm_test_mst_enable_stream_features_aux_disabled(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->link = link; + link->aux_access_disabled = true; + + /* aux_access_disabled → early return without DPCD access */ + dm_helpers_mst_enable_stream_features(stream); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_enable_stream_features_writes_downspread - Test MSA ignore write + * @test: The KUnit test context + */ +static void dm_test_mst_enable_stream_features_writes_downspread(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + fixture = dm_test_alloc_synaptics_aux_with_dev(test, &adev->ddev); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + link = dm_kunit_alloc_link(test); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + dm_test_current_aux_recorder = fixture; + aconnector->dm_dp_aux.aux.drm_dev = &adev->ddev; + aconnector->dm_dp_aux.aux.transfer = dm_test_current_aux_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + link->priv = aconnector; + stream->link = link; + stream->ignore_msa_timing_param = true; + + dm_helpers_mst_enable_stream_features(stream); + + KUNIT_EXPECT_EQ(test, fixture->downspread_reads, 1U); + KUNIT_EXPECT_EQ(test, fixture->downspread_writes, 1U); + KUNIT_EXPECT_EQ(test, fixture->last_dpcd_write_address, + DP_DOWNSPREAD_CTRL); + KUNIT_EXPECT_EQ(test, fixture->dpcd_write_value, (u8)BIT(7)); +} + +/* Tests for dm_helpers_enable_periodic_detection() */ + +struct dm_test_idle_work { + struct idle_workqueue base; + bool ran; +}; + +static void dm_test_idle_work_func(struct work_struct *work) +{ + struct dm_test_idle_work *idle_work; + + idle_work = container_of(work, struct dm_test_idle_work, base.work); + idle_work->ran = true; +} + +/** + * dm_test_enable_periodic_detection_no_workqueue - Test no-op without idle workqueue + * @test: The KUnit test context + */ +static void dm_test_enable_periodic_detection_no_workqueue(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + + /* adev->dm.idle_workqueue is NULL → no-op, no crash */ + dm_helpers_enable_periodic_detection(ctx, true); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_enable_periodic_detection_updates_enable - Test idle work enable flag + * @test: The KUnit test context + * + * Keeps idle_workqueue->running set so the helper only updates the enable flag + * and does not queue the idle worker. + */ +static void dm_test_enable_periodic_detection_updates_enable(struct kunit *test) +{ + struct idle_workqueue *idle_work; + struct amdgpu_device *adev; + struct dc_context *ctx; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, idle_work); + + ctx->driver_context = adev; + adev->dm.idle_workqueue = idle_work; + idle_work->running = true; + + dm_helpers_enable_periodic_detection(ctx, true); + KUNIT_EXPECT_TRUE(test, idle_work->enable); + + dm_helpers_enable_periodic_detection(ctx, false); + KUNIT_EXPECT_FALSE(test, idle_work->enable); +} + +/** + * dm_test_enable_periodic_detection_schedules_work - Test headless schedule path + * @test: The KUnit test context + */ +static void dm_test_enable_periodic_detection_schedules_work(struct kunit *test) +{ + struct dm_test_idle_work *idle_work; + struct amdgpu_device *adev; + struct dc_context *ctx; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, idle_work); + + ctx->driver_context = adev; + adev->dm.ddev = &adev->ddev; + adev->dm.idle_workqueue = &idle_work->base; + INIT_WORK(&idle_work->base.work, dm_test_idle_work_func); + + dm_helpers_enable_periodic_detection(ctx, true); + flush_work(&idle_work->base.work); + + KUNIT_EXPECT_TRUE(test, idle_work->base.enable); + KUNIT_EXPECT_TRUE(test, idle_work->ran); +} + +/* Tests for dm_helpers_read_mccs_caps() */ + +/** + * dm_test_read_mccs_caps_null_ctx - Test early return with NULL context + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_null_ctx(struct kunit *test) +{ + /* NULL ctx → early return, no crash */ + dm_helpers_read_mccs_caps(NULL, NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_read_mccs_caps_null_link - Test early return with NULL link + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_null_link(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + /* link is NULL → drm_dbg_driver + return */ + dm_helpers_read_mccs_caps(ctx, NULL, sink); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_read_mccs_caps_no_vcp_code - Test no-vcp-code path clears freesync support + * @test: The KUnit test context + * + * With freesync_vcp_code == 0 the i2c/MCCS path is skipped entirely and the + * function only clears sink->mccs_caps.freesync_supported. + */ +static void dm_test_read_mccs_caps_no_vcp_code(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->edid_caps.freesync_vcp_code = 0; + sink->mccs_caps.freesync_supported = true; /* should be cleared */ + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_FALSE(test, sink->mccs_caps.freesync_supported); +} + +/* + * Allocate and wire the adev/ctx/link/sink/connector/i2c objects shared by the + * MCCS read/set tests so each test only configures the fields it exercises. + */ +struct dm_test_mccs_fixture { + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc_sink *sink; + struct amdgpu_dm_connector *aconnector; + struct dm_test_mccs_i2c_adapter *fake; +}; + +static struct dm_test_mccs_fixture dm_test_alloc_mccs_fixture(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture; + + fixture.adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, fixture.adev); + fixture.ctx = kunit_kzalloc(test, sizeof(*fixture.ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.ctx); + fixture.link = kunit_kzalloc(test, sizeof(*fixture.link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.link); + fixture.sink = kunit_kzalloc(test, sizeof(*fixture.sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.sink); + fixture.aconnector = kunit_kzalloc(test, sizeof(*fixture.aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.aconnector); + fixture.fake = dm_test_alloc_mccs_i2c(test); + + fixture.ctx->driver_context = fixture.adev; + fixture.aconnector->i2c = (struct amdgpu_i2c_adapter *)fixture.fake; + fixture.link->priv = fixture.aconnector; + + return fixture; +} + +/** + * dm_test_read_mccs_caps_i2c_vcp_request - Test MCCS VCP request packet + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_i2c_vcp_request(struct kunit *test) +{ + static const u8 expected_prefix[] = { 0x51, 0x82, 0x01, 0xe3 }; + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + link->dpcd_caps.dpcd_rev.raw = DP_DPCD_REV_14; + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->read_reply[1] = 0x82; + fake->read_reply[9] = 0x01; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_TRUE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 1U); + KUNIT_EXPECT_EQ(test, fake->write_len, (unsigned int)sizeof(expected_prefix) + 1); + KUNIT_EXPECT_EQ(test, memcmp(fake->write_data, expected_prefix, + sizeof(expected_prefix)), 0); + KUNIT_EXPECT_EQ(test, fake->write_data[4], + dm_test_mccs_checksum(expected_prefix, sizeof(expected_prefix))); +} + +/** + * dm_test_read_mccs_caps_hdmi_vcp_request - Test local HDMI MCCS path + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_hdmi_vcp_request(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->read_reply[1] = 0x82; + fake->read_reply[9] = 0x01; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_TRUE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 1U); +} + +/** + * dm_test_read_mccs_caps_legacy_pcon_vcp_request - Test legacy PCON path + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_legacy_pcon_vcp_request(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->read_reply[1] = 0x82; + fake->read_reply[9] = 0x01; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_TRUE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 1U); +} + +/** + * dm_test_read_mccs_caps_i2c_failure - Test VCP request retry failure + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_i2c_failure(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + fixture.aconnector->base.dev = &fixture.adev->ddev; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + link->dpcd_caps.dpcd_rev.raw = DP_DPCD_REV_14; + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->write_ret = 0; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_FALSE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 5U); + KUNIT_EXPECT_EQ(test, fake->reads, 0U); +} + +/* Tests for dm_helpers_mccs_vcp_set() */ + +/** + * dm_test_mccs_vcp_set_null_ctx - Test early return with NULL context + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_null_ctx(struct kunit *test) +{ + /* NULL ctx → early return, no crash */ + dm_helpers_mccs_vcp_set(NULL, NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mccs_vcp_set_not_supported - Test early return when freesync unsupported + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_not_supported(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->mccs_caps.freesync_supported = false; + + /* freesync not supported → early return without i2c */ + dm_helpers_mccs_vcp_set(ctx, link, sink); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mccs_vcp_set_null_link - Test early return with NULL link + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_null_link(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + ctx->driver_context = adev; + dm_helpers_mccs_vcp_set(ctx, NULL, sink); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mccs_vcp_set_i2c_packet - Test MCCS VCP set packet + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_i2c_packet(struct kunit *test) +{ + static const u8 expected_prefix[] = { + 0x51, 0x84, 0x03, 0xe3, 0x01, 0x01, + }; + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + sink->mccs_caps.freesync_supported = true; + sink->edid_caps.freesync_vcp_code = 0xe3; + + dm_helpers_mccs_vcp_set(ctx, link, sink); + + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 0U); + KUNIT_EXPECT_EQ(test, fake->write_len, (unsigned int)sizeof(expected_prefix) + 1); + KUNIT_EXPECT_EQ(test, memcmp(fake->write_data, expected_prefix, + sizeof(expected_prefix)), 0); + KUNIT_EXPECT_EQ(test, fake->write_data[6], + dm_test_mccs_checksum(expected_prefix, sizeof(expected_prefix))); +} + +/** + * dm_test_mccs_vcp_set_i2c_failure - Test VCP set retry failure path + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_i2c_failure(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + sink->mccs_caps.freesync_supported = true; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->write_ret = 0; + + dm_helpers_mccs_vcp_set(ctx, link, sink); + + KUNIT_EXPECT_EQ(test, fake->writes, 5U); + KUNIT_EXPECT_EQ(test, fake->reads, 0U); +} + +/* Tests for dm_helpers_construct_old_payload() */ + +/** + * dm_test_construct_old_payload_empty_list - Test PBN/time-slot calc, empty list + * @test: The KUnit test context + * + * With no other payloads, next_payload_vc_start stays at mgr->next_start_slot, + * so allocated time_slots = next_start_slot - vc_start_slot. + */ +static void dm_test_construct_old_payload_empty_list(struct kunit *test) +{ + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *new_payload; + struct drm_dp_mst_atomic_payload *old_payload; + + mgr = kunit_kzalloc(test, sizeof(*mgr), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mgr); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + new_payload = kunit_kzalloc(test, sizeof(*new_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, new_payload); + old_payload = kunit_kzalloc(test, sizeof(*old_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_payload); + + INIT_LIST_HEAD(&mst_state->payloads); + mgr->next_start_slot = 10; + mst_state->pbn_div.full = 5 << 12; /* dfixed_trunc → 5 PBN/slot */ + new_payload->vc_start_slot = 3; + + dm_helpers_construct_old_payload(mgr, mst_state, new_payload, + old_payload); + + /* 10 - 3 = 7 slots, 7 * 5 = 35 PBN */ + KUNIT_EXPECT_EQ(test, old_payload->time_slots, 7); + KUNIT_EXPECT_EQ(test, old_payload->pbn, 35); +} + +/** + * dm_test_construct_old_payload_intervening - Test calc with an intervening payload + * @test: The KUnit test context + * + * A payload whose vc_start_slot falls between the new payload and the manager's + * next_start_slot narrows the allocated time-slot window. + */ +static void dm_test_construct_old_payload_intervening(struct kunit *test) +{ + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *new_payload; + struct drm_dp_mst_atomic_payload *other_payload; + struct drm_dp_mst_atomic_payload *old_payload; + + mgr = kunit_kzalloc(test, sizeof(*mgr), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mgr); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + new_payload = kunit_kzalloc(test, sizeof(*new_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, new_payload); + other_payload = kunit_kzalloc(test, sizeof(*other_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, other_payload); + old_payload = kunit_kzalloc(test, sizeof(*old_payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_payload); + + INIT_LIST_HEAD(&mst_state->payloads); + mgr->next_start_slot = 10; + mst_state->pbn_div.full = 5 << 12; + new_payload->vc_start_slot = 3; + + /* other payload at slot 6 (between 3 and 10) narrows window to 6 */ + other_payload->vc_start_slot = 6; + list_add_tail(&other_payload->next, &mst_state->payloads); + + dm_helpers_construct_old_payload(mgr, mst_state, new_payload, + old_payload); + + /* 6 - 3 = 3 slots, 3 * 5 = 15 PBN */ + KUNIT_EXPECT_EQ(test, old_payload->time_slots, 3); + KUNIT_EXPECT_EQ(test, old_payload->pbn, 15); +} + +/* Tests for dm_helpers_dp_mst_write_payload_allocation_table() success path */ + +/** + * dm_test_write_payload_alloc_table_success - Exercise the MST success path + * @test: The KUnit test context + * @enable: true for the add-payload path, false for the remove-payload path + * + * Builds a minimal MST topology-state fixture so the helper traverses past the + * early NULL checks and runs the real DRM MST payload helpers + * (drm_dp_add_payload_part1 / drm_dp_remove_payload_part1). With + * mgr->mst_primary == NULL the topology walk fails gracefully, so no remote + * DPCD/AUX traffic is generated, and the helper still returns true. + */ +static void dm_test_write_payload_alloc_table_success(struct kunit *test, + bool enable) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *payload; + struct drm_dp_mst_port *port; + struct dc_stream_state *stream; + struct dc_link *link; + struct dc_dp_mst_stream_allocation_table table = { 0 }; + bool ret; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, payload); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, port); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* aconnector acts as its own MST root for this fixture */ + aconnector->mst_root = aconnector; + aconnector->mst_output_port = port; + + mgr = &aconnector->mst_mgr; + mutex_init(&mgr->lock); + mgr->dev = &adev->ddev; + mgr->mst_primary = NULL; /* topology walk fails gracefully */ + mgr->base.state = &mst_state->base; + + INIT_LIST_HEAD(&mst_state->payloads); + mst_state->pbn_div.full = 5 << 12; /* dfixed_trunc → 5 PBN/slot */ + + /* payload found by drm_atomic_get_mst_payload_state via matching port */ + payload->port = port; + payload->vcpi = 1; + payload->vc_start_slot = 1; + payload->time_slots = 2; + list_add_tail(&payload->next, &mst_state->payloads); + + /* pre-existing HW allocation so the disable path finds a VCPI to clear */ + link->mst_stream_alloc_table.stream_count = 1; + link->mst_stream_alloc_table.stream_allocations[0].vcp_id = 1; + link->mst_stream_alloc_table.stream_allocations[0].slot_count = 2; + + stream->dm_stream_context = aconnector; + stream->link = link; + + ret = dm_helpers_dp_mst_write_payload_allocation_table(NULL, stream, + &table, enable); + + KUNIT_EXPECT_TRUE(test, ret); + + if (enable) + /* add path keeps the old entry and appends the new payload */ + KUNIT_EXPECT_EQ(test, table.stream_count, 2); + else + /* remove path clears the only entry */ + KUNIT_EXPECT_EQ(test, table.stream_count, 0); +} + +/** + * dm_test_write_payload_alloc_table_enable - Test add-payload success path + * @test: The KUnit test context + */ +static void dm_test_write_payload_alloc_table_enable(struct kunit *test) +{ + dm_test_write_payload_alloc_table_success(test, true); +} + +/** + * dm_test_write_payload_alloc_table_disable - Test remove-payload success path + * @test: The KUnit test context + */ +static void dm_test_write_payload_alloc_table_disable(struct kunit *test) +{ + dm_test_write_payload_alloc_table_success(test, false); +} + +/* Tests for dm_helpers_dp_mst_poll_for_allocation_change_trigger() */ + +/* + * Stub AUX transfer that ACKs a DPCD read of the payload-table update status + * with the ACT-handled bit set, so drm_dp_check_act_status() returns 0. + */ +static ssize_t dm_test_act_aux_transfer_handled(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_READ) { + memset(msg->buffer, 0, msg->size); + if (msg->size > 0) + ((u8 *)msg->buffer)[0] = DP_PAYLOAD_ACT_HANDLED; + } + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + +/* + * Stub AUX transfer that fails every transaction, so the ACT status read + * returns an error and drm_dp_check_act_status() returns non-zero. + */ +static ssize_t dm_test_act_aux_transfer_fail(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + return -EIO; +} + +/** + * dm_test_mst_start_top_mgr_set_mst_fail - Test MST start failure path + * @test: The KUnit test context + * + * With a connector-backed link and a failing AUX channel, the non-boot + * path calls drm_dp_mst_topology_mgr_set_mst(true), which fails to read the + * DPCD caps and returns a negative error, so the helper returns false. + */ +static void dm_test_mst_start_top_mgr_set_mst_fail(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aux); + link = dm_kunit_alloc_link(test); + + aux->drm_dev = &adev->ddev; + aux->transfer = dm_test_act_aux_transfer_fail; + drm_dp_aux_init(aux); + + mutex_init(&aconnector->mst_mgr.lock); + aconnector->mst_mgr.dev = &adev->ddev; + aconnector->mst_mgr.aux = aux; + link->priv = aconnector; + + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, false)); +} + +/** + * dm_test_mst_stop_top_mgr_active - Test MST stop on an active topology manager + * @test: The KUnit test context + * + * With mst_state set, the helper calls drm_dp_mst_topology_mgr_set_mst(false) + * to disable MST and clears the link lane count. The helper always returns + * false. + */ +static void dm_test_mst_stop_top_mgr_active(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aux); + link = dm_kunit_alloc_link(test); + + aux->drm_dev = &adev->ddev; + aux->transfer = dm_test_act_aux_transfer_handled; + drm_dp_aux_init(aux); + + mutex_init(&aconnector->mst_mgr.lock); + aconnector->mst_mgr.dev = &adev->ddev; + aconnector->mst_mgr.aux = aux; + aconnector->mst_mgr.mst_state = true; + link->cur_link_settings.lane_count = 4; + link->priv = aconnector; + + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_stop_top_mgr(NULL, link)); + KUNIT_EXPECT_EQ(test, link->cur_link_settings.lane_count, 0); +} + +/** + * dm_test_poll_for_act_no_mst_state - Test ACT poll bails when MST not started + * @test: The KUnit test context + * + * With mst_root set but mst_mgr->mst_state false, the helper returns + * ACT_FAILED before touching the AUX channel. + */ +static void dm_test_poll_for_act_no_mst_state(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + aconnector->mst_root = aconnector; + aconnector->mst_mgr.mst_state = false; + stream->dm_stream_context = aconnector; + + KUNIT_EXPECT_EQ(test, + (int)dm_helpers_dp_mst_poll_for_allocation_change_trigger(NULL, stream), + (int)ACT_FAILED); +} + +/** + * dm_test_poll_for_act_success - Test ACT poll success path + * @test: The KUnit test context + * + * With MST started and the AUX channel reporting ACT handled, + * drm_dp_check_act_status() returns 0 and the helper returns ACT_SUCCESS. + */ +static void dm_test_poll_for_act_success(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + struct dc_stream_state *stream; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aux); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + aux->drm_dev = &adev->ddev; + aux->transfer = dm_test_act_aux_transfer_handled; + drm_dp_aux_init(aux); + + aconnector->mst_root = aconnector; + aconnector->mst_mgr.mst_state = true; + aconnector->mst_mgr.aux = aux; + stream->dm_stream_context = aconnector; + + KUNIT_EXPECT_EQ(test, + (int)dm_helpers_dp_mst_poll_for_allocation_change_trigger(NULL, stream), + (int)ACT_SUCCESS); +} + +/** + * dm_test_poll_for_act_status_failed - Test ACT poll failure path + * @test: The KUnit test context + * + * With MST started but the AUX channel failing, drm_dp_check_act_status() + * returns non-zero and the helper returns ACT_FAILED. + */ +static void dm_test_poll_for_act_status_failed(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + struct dc_stream_state *stream; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aux); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + aux->drm_dev = &adev->ddev; + aux->transfer = dm_test_act_aux_transfer_fail; + drm_dp_aux_init(aux); + + aconnector->mst_root = aconnector; + aconnector->mst_mgr.mst_state = true; + aconnector->mst_mgr.aux = aux; + stream->dm_stream_context = aconnector; + + KUNIT_EXPECT_EQ(test, + (int)dm_helpers_dp_mst_poll_for_allocation_change_trigger(NULL, stream), + (int)ACT_FAILED); +} + +/* Tests for dm_helpers_dp_mst_send_payload_allocation() */ + +/** + * dm_test_mst_send_payload_alloc_part2_fail - Exercise the failure branch + * @test: The KUnit test context + * + * Builds a minimal MST topology-state fixture so the helper runs past the early + * NULL checks and calls drm_dp_add_payload_part2(). The payload's allocation + * status is left at its default (not DRM_DP_MST_PAYLOAD_ALLOCATION_DFP), so + * drm_dp_add_payload_part2() returns -EIO without any remote DPCD/AUX traffic. + * The non-zero return drives the failure branch, which clears the + * MST_ALLOCATE_NEW_PAYLOAD status bit. + */ +static void dm_test_mst_send_payload_alloc_part2_fail(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *payload; + struct drm_dp_mst_port *port; + struct dc_stream_state *stream; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, payload); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, port); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* aconnector acts as its own MST root for this fixture */ + aconnector->mst_root = aconnector; + aconnector->mst_output_port = port; + /* drm_dp_add_payload_part2() logs port->connector->name on failure */ + port->connector = &aconnector->base; + + mgr = &aconnector->mst_mgr; + mgr->dev = &adev->ddev; + mgr->base.state = &mst_state->base; + + INIT_LIST_HEAD(&mst_state->payloads); + + /* payload found by drm_atomic_get_mst_payload_state via matching port */ + payload->port = port; + list_add_tail(&payload->next, &mst_state->payloads); + + /* pre-set the bit so we can observe it being cleared on failure */ + aconnector->mst_status = MST_ALLOCATE_NEW_PAYLOAD; + + stream->dm_stream_context = aconnector; + dm_helpers_dp_mst_send_payload_allocation(NULL, stream); + + KUNIT_EXPECT_EQ(test, + aconnector->mst_status & MST_ALLOCATE_NEW_PAYLOAD, 0); +} + +/* Tests for dm_helpers_dp_mst_update_mst_mgr_for_deallocation() */ + +/** + * dm_test_mst_update_mgr_dealloc_success - Exercise the deallocation path + * @test: The KUnit test context + * + * Builds a minimal MST topology-state fixture so the helper runs past the early + * NULL checks through dm_helpers_construct_old_payload() and + * drm_dp_remove_payload_part2(), both of which are pure list/slot math with no + * remote DPCD/AUX traffic. Afterwards MST_CLEAR_ALLOCATED_PAYLOAD must be set, + * MST_ALLOCATE_NEW_PAYLOAD cleared, and the payload's slot released. + */ +static void dm_test_mst_update_mgr_dealloc_success(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_mst_atomic_payload *payload; + struct drm_dp_mst_port *port; + struct dc_stream_state *stream; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mst_state = kunit_kzalloc(test, sizeof(*mst_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mst_state); + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, payload); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, port); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* aconnector acts as its own MST root for this fixture */ + aconnector->mst_root = aconnector; + aconnector->mst_output_port = port; + + mgr = &aconnector->mst_mgr; + mgr->base.state = &mst_state->base; + mgr->next_start_slot = 10; + mgr->payload_count = 1; + + INIT_LIST_HEAD(&mst_state->payloads); + mst_state->pbn_div.full = 5 << 12; /* dfixed_trunc → 5 PBN/slot */ + + /* payload found by drm_atomic_get_mst_payload_state via matching port */ + payload->port = port; + payload->vc_start_slot = 3; + payload->time_slots = 7; + list_add_tail(&payload->next, &mst_state->payloads); + + /* pre-set the bit so we can observe it being cleared */ + aconnector->mst_status = MST_ALLOCATE_NEW_PAYLOAD; + + stream->dm_stream_context = aconnector; + dm_helpers_dp_mst_update_mst_mgr_for_deallocation(NULL, stream); + + KUNIT_EXPECT_EQ(test, + aconnector->mst_status & MST_CLEAR_ALLOCATED_PAYLOAD, + (int)MST_CLEAR_ALLOCATED_PAYLOAD); + KUNIT_EXPECT_EQ(test, + aconnector->mst_status & MST_ALLOCATE_NEW_PAYLOAD, 0); + /* drm_dp_remove_payload_part2() releases the payload's slot */ + KUNIT_EXPECT_EQ(test, payload->vc_start_slot, -1); +} + +/* Tests for dm_helpers_dp_write_dsc_enable() */ + +/** + * dm_test_dp_write_dsc_enable_mst_no_aux - Test MST early return without dsc_aux + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_no_aux(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->dm_stream_context = aconnector; + stream->signal = SIGNAL_TYPE_DISPLAY_PORT_MST; + aconnector->dsc_aux = NULL; + + /* MST signal with NULL dsc_aux → return false */ + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_dsc_enable(NULL, stream, true)); +} + +/** + * dm_test_dp_write_dsc_enable_non_dp - Test non-DP signal returns false + * @test: The KUnit test context + * + * For an HDMI signal neither the MST nor the DP/eDP block runs, so ret stays 0. + */ +static void dm_test_dp_write_dsc_enable_non_dp(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->dm_stream_context = aconnector; + stream->signal = SIGNAL_TYPE_HDMI_TYPE_A; + + /* Non-DP/MST signal → no DPCD write, ret stays 0 (false) */ + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_dsc_enable(NULL, stream, true)); +} + +struct dm_test_dsc_aux_pair { + struct dm_test_synaptics_aux *main; + struct dm_test_synaptics_aux *passthrough; +}; + +static struct amdgpu_dm_connector *dm_test_alloc_dsc_connector(struct kunit *test, + struct dc_link *link) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aconnector->dc_link = link; + + return aconnector; +} + +static struct dc_stream_state *dm_test_alloc_dsc_stream(struct kunit *test, + struct dc_link *link, + enum signal_type signal) +{ + struct dc_stream_state *stream; + struct dc_sink *sink; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + stream->link = link; + stream->signal = signal; + stream->sink = sink; + sink->link = link; + + return stream; +} + +static struct dm_test_dsc_aux_pair dm_test_dp_write_dsc_enable_mst(struct kunit *test, + bool enable, + bool passthrough) +{ + struct dm_test_dsc_aux_pair aux_pair; + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_port *port; + struct dc_stream_state *stream; + struct dc_link *link; + bool ret; + + link = dm_kunit_alloc_link(test); + aconnector = dm_test_alloc_dsc_connector(test, link); + stream = dm_test_alloc_dsc_stream(test, link, SIGNAL_TYPE_DISPLAY_PORT_MST); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, port); + aux_pair.main = dm_test_alloc_synaptics_aux_with_dev(test, NULL); + aux_pair.passthrough = dm_test_alloc_synaptics_aux_with_dev(test, NULL); + + stream->dm_stream_context = aconnector; + aconnector->dsc_aux = &aux_pair.main->aux; + aconnector->mst_output_port = port; + if (passthrough) + port->passthrough_aux = &aux_pair.passthrough->aux; + + ret = dm_helpers_dp_write_dsc_enable(NULL, stream, enable); + + KUNIT_EXPECT_TRUE(test, ret); + KUNIT_EXPECT_EQ(test, aux_pair.main->dsc_enable_writes, 1U); + KUNIT_EXPECT_EQ(test, aux_pair.main->dsc_enable_values[0], enable ? 1 : 0); + KUNIT_EXPECT_EQ(test, aux_pair.passthrough->dsc_enable_writes, + passthrough ? 1U : 0U); + if (passthrough) + KUNIT_EXPECT_EQ(test, aux_pair.passthrough->dsc_enable_values[0], enable ? 2 : 0); + + return aux_pair; +} + +/** + * dm_test_dp_write_dsc_enable_mst_enable_decode_only - Test MST enable decoding write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_enable_decode_only(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_mst(test, true, false); +} + +/** + * dm_test_dp_write_dsc_enable_mst_enable_passthrough - Test MST enable passthrough write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_enable_passthrough(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_mst(test, true, true); +} + +/** + * dm_test_dp_write_dsc_enable_mst_disable_decode_only - Test MST disable decoding write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_disable_decode_only(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_mst(test, false, false); +} + +/** + * dm_test_dp_write_dsc_enable_mst_disable_passthrough - Test MST disable passthrough write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_mst_disable_passthrough(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_mst(test, false, true); +} + +static void dm_test_dp_write_dsc_enable_sst(struct kunit *test, + enum display_dongle_type dongle_type, + bool enable, + u8 expected_value) +{ + struct dm_test_synaptics_aux *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + struct dc_link *link; + bool ret; + + link = dm_kunit_alloc_link(test); + aconnector = dm_test_alloc_dsc_connector(test, link); + stream = dm_test_alloc_dsc_stream(test, link, SIGNAL_TYPE_DISPLAY_PORT); + fixture = dm_test_alloc_synaptics_aux_with_dev(test, NULL); + + stream->dm_stream_context = aconnector; + link->priv = aconnector; + link->dpcd_caps.dongle_type = dongle_type; + dm_test_current_aux_recorder = fixture; + aconnector->dm_dp_aux.aux.transfer = dm_test_current_aux_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + + ret = dm_helpers_dp_write_dsc_enable(NULL, stream, enable); + + KUNIT_EXPECT_TRUE(test, ret); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_writes, 1U); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_values[0], expected_value); +} + +/** + * dm_test_dp_write_dsc_enable_sst_rx_enable - Test SST RX enable DPCD write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_sst_rx_enable(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_sst(test, DISPLAY_DONGLE_NONE, true, 1); +} + +/** + * dm_test_dp_write_dsc_enable_sst_rx_disable - Test SST RX disable DPCD write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_sst_rx_disable(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_sst(test, DISPLAY_DONGLE_NONE, false, 0); +} + +/** + * dm_test_dp_write_dsc_enable_pcon_enable - Test DP-HDMI PCON enable DPCD write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_pcon_enable(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_sst(test, DISPLAY_DONGLE_DP_HDMI_CONVERTER, true, 1); +} + +/** + * dm_test_dp_write_dsc_enable_pcon_disable - Test DP-HDMI PCON disable DPCD write + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_pcon_disable(struct kunit *test) +{ + dm_test_dp_write_dsc_enable_sst(test, DISPLAY_DONGLE_DP_HDMI_CONVERTER, false, 0); +} + +/* Tests for dm_helpers_dp_handle_test_pattern_request() */ + +/** + * dm_test_dp_handle_test_pattern_no_pipe - Test no matching pipe returns false + * @test: The KUnit test context + */ +static void dm_test_dp_handle_test_pattern_no_pipe(struct kunit *test) +{ + union link_test_pattern test_pattern = {0}; + union test_misc test_params = {0}; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_state *state; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + link = dm_kunit_alloc_link(test); + aconnector = dm_kunit_alloc_connector(test, adev, link); + + ctx->dc = dc; + dc->current_state = state; + link->dc = dc; + link->priv = aconnector; + + KUNIT_EXPECT_FALSE(test, + dm_helpers_dp_handle_test_pattern_request(ctx, link, + test_pattern, + test_params)); +} + static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), KUNIT_CASE(dm_test_edid_extract_panel_id_zeros), + /* apply_edid_quirks */ + KUNIT_CASE(dm_test_apply_edid_quirks_dpcd_poweroff_delay), + KUNIT_CASE(dm_test_apply_edid_quirks_disable_fams), + KUNIT_CASE(dm_test_apply_edid_quirks_remove_sink_ext_caps), + KUNIT_CASE(dm_test_apply_edid_quirks_disable_colorimetry), + KUNIT_CASE(dm_test_apply_edid_quirks_skip_phy_ssc), + KUNIT_CASE(dm_test_apply_edid_quirks_unknown_noop), + /* dm_helpers_parse_edid_caps */ + KUNIT_CASE(dm_test_parse_edid_caps_null_edid), + KUNIT_CASE(dm_test_parse_edid_caps_null_caps), + KUNIT_CASE(dm_test_parse_edid_caps_valid), + KUNIT_CASE(dm_test_parse_edid_caps_bad_checksum), + KUNIT_CASE(dm_test_parse_edid_caps_hdmi_frl), + KUNIT_CASE(dm_test_parse_edid_caps_hdmi_frl_dsc), + KUNIT_CASE(dm_test_parse_edid_caps_cea_audio), + KUNIT_CASE(dm_test_parse_edid_caps_cea_no_speaker), + /* ACPI / VBIOS / local EDID readers */ + KUNIT_CASE(dm_test_probe_acpi_edid_no_companion), + KUNIT_CASE(dm_test_read_acpi_edid_debug_mask_disabled), + KUNIT_CASE(dm_test_read_acpi_edid_non_panel_connector), + KUNIT_CASE(dm_test_read_acpi_edid_force_off), + KUNIT_CASE(dm_test_read_vbios_edid_non_embedded), + KUNIT_CASE(dm_test_read_vbios_edid_missing_callback), + KUNIT_CASE(dm_test_read_vbios_edid_callback_error), + KUNIT_CASE(dm_test_read_vbios_edid_missing_fake_edid), + KUNIT_CASE(dm_test_read_vbios_edid_invalid_fake_edid), + KUNIT_CASE(dm_test_read_vbios_edid_valid), /* dm_is_freesync_pcon_whitelist */ KUNIT_CASE(dm_test_freesync_pcon_whitelist_all_known), KUNIT_CASE(dm_test_freesync_pcon_whitelist_not_in_list), @@ -614,12 +3778,121 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* dm_helpers_dp_read_dpcd / dm_helpers_dp_write_dpcd */ KUNIT_CASE(dm_test_dp_read_dpcd_null_priv), KUNIT_CASE(dm_test_dp_write_dpcd_null_priv), + KUNIT_CASE(dm_test_dp_read_dpcd_success), + KUNIT_CASE(dm_test_dp_write_dpcd_success), + /* dm_helpers_execute_fused_io */ + KUNIT_CASE(dm_test_execute_fused_io_null_dmub_srv), + /* Synaptics RC/FIFO/DSC helpers */ + KUNIT_CASE(dm_test_execute_synaptics_rc_command_write_success), + KUNIT_CASE(dm_test_execute_synaptics_rc_command_read_success), + KUNIT_CASE(dm_test_execute_synaptics_rc_command_write_fail), + KUNIT_CASE(dm_test_apply_synaptics_fifo_reset_wa_full), + KUNIT_CASE(dm_test_apply_synaptics_fifo_reset_wa_first_fail), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_inactive), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_active), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_disable_inactive), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_disable_active), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_non_synaptics), + KUNIT_CASE(dm_test_dp_write_dsc_enable_routes_synaptics), /* dm_helpers_dp_mst_start_top_mgr / dm_helpers_dp_mst_stop_top_mgr */ KUNIT_CASE(dm_test_mst_start_top_mgr_null_priv), KUNIT_CASE(dm_test_mst_stop_top_mgr_null_priv), KUNIT_CASE(dm_test_mst_start_top_mgr_boot), + KUNIT_CASE(dm_test_mst_start_top_mgr_set_mst_fail), + KUNIT_CASE(dm_test_mst_stop_top_mgr_active), /* dm_helpers_dp_write_hblank_reduction */ KUNIT_CASE(dm_test_dp_write_hblank_reduction_false), + /* get_dsc_max_slices */ + KUNIT_CASE(dm_test_get_dsc_max_slices_1_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_2_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_4_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_8_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_8_400), + KUNIT_CASE(dm_test_get_dsc_max_slices_12_400), + KUNIT_CASE(dm_test_get_dsc_max_slices_16_400), + KUNIT_CASE(dm_test_get_dsc_max_slices_unknown), + /* dm_helpers_init_panel_settings */ + KUNIT_CASE(dm_test_init_panel_settings_pps), + KUNIT_CASE(dm_test_init_panel_settings_dsc), + /* dm_helpers_override_panel_settings */ + KUNIT_CASE(dm_test_override_panel_settings_debug_mask_disables_dsc), + KUNIT_CASE(dm_test_override_panel_settings_second_edp_disables_psr), + /* fill_dc_mst_payload_table_from_drm */ + KUNIT_CASE(dm_test_fill_mst_payload_table_enable), + KUNIT_CASE(dm_test_fill_mst_payload_table_disable), + KUNIT_CASE(dm_test_fill_mst_payload_table_empty), + /* dm_helpers_submit_i2c */ + KUNIT_CASE(dm_test_submit_i2c_null_priv), + KUNIT_CASE(dm_test_submit_i2c_success), + KUNIT_CASE(dm_test_submit_i2c_partial_transfer), + /* dm_helper_dmub_aux_transfer_sync */ + KUNIT_CASE(dm_test_dmub_aux_transfer_sync_hpd_discon), + /* Empty stub functions */ + KUNIT_CASE(dm_test_dp_update_branch_info_no_crash), + KUNIT_CASE(dm_test_mst_poll_pending_down_reply_no_crash), + KUNIT_CASE(dm_test_mst_clear_payload_alloc_table_no_crash), + KUNIT_CASE(dm_test_set_dcn_clocks_no_crash), + KUNIT_CASE(dm_test_dmu_timeout_no_crash), + KUNIT_CASE(dm_test_smu_timeout_no_crash), + KUNIT_CASE(dm_test_set_phyd32clk_no_crash), + KUNIT_CASE(dm_test_mst_update_branch_bandwidth_no_crash), + /* dm_helpers_dp_mst_write_payload_allocation_table success path */ + KUNIT_CASE(dm_test_write_payload_alloc_table_enable), + KUNIT_CASE(dm_test_write_payload_alloc_table_disable), + /* MST null-connector early returns */ + KUNIT_CASE(dm_test_mst_write_payload_alloc_table_null_ctx), + KUNIT_CASE(dm_test_mst_poll_for_act_null_ctx), + /* dm_helpers_dp_mst_poll_for_allocation_change_trigger success/fail */ + KUNIT_CASE(dm_test_poll_for_act_no_mst_state), + KUNIT_CASE(dm_test_poll_for_act_success), + KUNIT_CASE(dm_test_poll_for_act_status_failed), + KUNIT_CASE(dm_test_mst_send_payload_alloc_null_ctx), + KUNIT_CASE(dm_test_mst_update_mgr_dealloc_null_ctx), + /* dm_helpers_dp_mst_send_payload_allocation failure path */ + KUNIT_CASE(dm_test_mst_send_payload_alloc_part2_fail), + /* dm_helpers_dp_mst_update_mst_mgr_for_deallocation success path */ + KUNIT_CASE(dm_test_mst_update_mgr_dealloc_success), + /* dm_helpers_is_dp_sink_present */ + KUNIT_CASE(dm_test_is_dp_sink_present_null_priv), + /* dm_helpers_dmub_outbox_interrupt_control */ + KUNIT_CASE(dm_test_dmub_outbox_interrupt_control_null_dc), + /* dm_helpers_mst_enable_stream_features */ + KUNIT_CASE(dm_test_mst_enable_stream_features_aux_disabled), + KUNIT_CASE(dm_test_mst_enable_stream_features_writes_downspread), + /* dm_helpers_enable_periodic_detection */ + KUNIT_CASE(dm_test_enable_periodic_detection_no_workqueue), + KUNIT_CASE(dm_test_enable_periodic_detection_updates_enable), + KUNIT_CASE(dm_test_enable_periodic_detection_schedules_work), + /* dm_helpers_read_mccs_caps */ + KUNIT_CASE(dm_test_read_mccs_caps_null_ctx), + KUNIT_CASE(dm_test_read_mccs_caps_null_link), + KUNIT_CASE(dm_test_read_mccs_caps_no_vcp_code), + KUNIT_CASE(dm_test_read_mccs_caps_i2c_vcp_request), + KUNIT_CASE(dm_test_read_mccs_caps_hdmi_vcp_request), + KUNIT_CASE(dm_test_read_mccs_caps_legacy_pcon_vcp_request), + KUNIT_CASE(dm_test_read_mccs_caps_i2c_failure), + /* dm_helpers_mccs_vcp_set */ + KUNIT_CASE(dm_test_mccs_vcp_set_null_ctx), + KUNIT_CASE(dm_test_mccs_vcp_set_not_supported), + KUNIT_CASE(dm_test_mccs_vcp_set_null_link), + KUNIT_CASE(dm_test_mccs_vcp_set_i2c_packet), + KUNIT_CASE(dm_test_mccs_vcp_set_i2c_failure), + /* dm_helpers_construct_old_payload */ + KUNIT_CASE(dm_test_construct_old_payload_empty_list), + KUNIT_CASE(dm_test_construct_old_payload_intervening), + /* dm_helpers_dp_write_dsc_enable */ + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_no_aux), + KUNIT_CASE(dm_test_dp_write_dsc_enable_non_dp), + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_enable_decode_only), + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_enable_passthrough), + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_disable_decode_only), + KUNIT_CASE(dm_test_dp_write_dsc_enable_mst_disable_passthrough), + KUNIT_CASE(dm_test_dp_write_dsc_enable_sst_rx_enable), + KUNIT_CASE(dm_test_dp_write_dsc_enable_sst_rx_disable), + KUNIT_CASE(dm_test_dp_write_dsc_enable_pcon_enable), + KUNIT_CASE(dm_test_dp_write_dsc_enable_pcon_disable), + /* dm_helpers_dp_handle_test_pattern_request */ + KUNIT_CASE(dm_test_dp_handle_test_pattern_no_pipe), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index a73a6dd146d6..28c36217f6a2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -7,14 +7,24 @@ #include #include +#include +#include #include "dc.h" +#include "inc/core_types.h" +#include "irq/irq_service.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" #include "amdgpu_dm_kunit_test_helpers.h" +#include "dc_dmub_srv.h" +#include "ivsrcid/ivsrcid_vislands30.h" +#include "ivsrcid/dcn/irqsrcs_dcn_1_0.h" +#include "link_service.h" #include "dmub/dmub_srv.h" +#include "dal_asic_id.h" +#include "../../amdgpu/amdgpu_reset.h" static void dm_test_irq_handler(void *arg) { @@ -24,6 +34,305 @@ static void dm_test_irq_handler_alt(void *arg) { } +static void dm_test_irq_handler_count(void *arg) +{ + int *count = arg; + + if (count) + (*count)++; +} + +static bool dm_test_detect_connection_none(struct dc_link *link, + enum dc_connection_type *type) +{ + *type = dc_connection_none; + + return true; +} + +static bool dm_test_detect_link_false(struct dc_link *link, + enum dc_detect_reason reason) +{ + return false; +} + +static bool dm_test_detect_connection_single(struct dc_link *link, + enum dc_connection_type *type) +{ + *type = dc_connection_single; + + return true; +} + +/* Recording stubs for the dm_handle_hpd_rx_offload_work() DP-IRQ branches. */ +static int dm_test_automated_test_count; +static int dm_test_handle_link_loss_count; + +static void dm_test_dp_handle_automated_test(struct dc_link *link) +{ + dm_test_automated_test_count++; +} + +static void dm_test_dp_handle_link_loss(struct dc_link *link) +{ + dm_test_handle_link_loss_count++; +} + +static bool dm_test_dp_parse_link_loss_true(struct dc_link *link, + union hpd_irq_data *hpd_irq_dpcd_data) +{ + return true; +} + +static bool dm_test_dp_should_allow_hpd_rx_irq_true(const struct dc_link *link) +{ + return true; +} + +static enum dc_status dm_test_dp_read_hpd_rx_irq_data_ok(struct dc_link *link, + union hpd_irq_data *irq_data) +{ + return DC_OK; +} + +/* + * Allocate a refcounted dc_sink for tests without pulling in the DC-core + * dc_sink_create()/dc_sink_release() symbols. Production code under test still + * uses dc_sink_retain()/dc_sink_release() (resolved inside the amdgpu module). + * Use kzalloc (not kunit_kzalloc) so the final kref_put frees it exactly once. + */ +static struct dc_sink *dm_test_sink_create(struct dc_link *link) +{ + struct dc_sink *sink = kzalloc(sizeof(*sink), GFP_KERNEL); + + if (!sink) + return NULL; + + sink->link = link; + sink->ctx = link->ctx; + kref_init(&sink->refcount); + + return sink; +} + +static void dm_test_sink_free(struct kref *kref) +{ + struct dc_sink *sink = container_of(kref, struct dc_sink, refcount); + + kfree(sink->dc_container_id); + kfree(sink); +} + +static void dm_test_sink_release(struct dc_sink *sink) +{ + kref_put(&sink->refcount, dm_test_sink_free); +} + +static bool dm_test_handle_hpd_rx_no_work(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = false; + *has_left_work = false; + + return false; +} + +static bool dm_test_handle_hpd_rx_automated(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = false; + *has_left_work = true; + hpd_irq_data->bytes.device_service_irq.bits.AUTOMATED_TEST = 1; + + return false; +} + +static bool dm_test_handle_hpd_rx_msg_rdy(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = false; + *has_left_work = true; + hpd_irq_data->bytes.device_service_irq.bits.UP_REQ_MSG_RDY = 1; + + return false; +} + +static bool dm_test_handle_hpd_rx_link_loss(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = true; + *has_left_work = true; + + return false; +} + +static bool dm_test_allow_hpd_rx_irq_true(const struct dc_link *link) +{ + return true; +} + + +static uint32_t dm_test_dmub_get_outbox0_wptr(struct dmub_srv *dmub) +{ + return 0; +} + +static uint32_t dm_test_dmub_get_outbox1_wptr(struct dmub_srv *dmub) +{ + return 0; +} + +static int dm_test_dmub_notify_count; + +static void dm_test_dmub_notify_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + dm_test_dmub_notify_count++; +} + +static enum dc_irq_source dm_test_to_dal_irq_source_dce110( + struct irq_service *irq_service, + uint32_t src_id, + uint32_t ext_id) +{ + switch (src_id) { + case VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0: + return DC_IRQ_SOURCE_VBLANK1; + case VISLANDS30_IV_SRCID_D1_V_UPDATE_INT: + return DC_IRQ_SOURCE_VUPDATE1; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP: + return DC_IRQ_SOURCE_PFLIP1; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 2: + return DC_IRQ_SOURCE_PFLIP2; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 4: + return DC_IRQ_SOURCE_PFLIP3; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 6: + return DC_IRQ_SOURCE_PFLIP4; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 8: + return DC_IRQ_SOURCE_PFLIP5; + case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP + 10: + return DC_IRQ_SOURCE_PFLIP6; + default: + return DC_IRQ_SOURCE_INVALID; + } +} + +static const struct irq_service_funcs dm_test_irq_service_funcs_dce110 = { + .to_dal_irq_source = dm_test_to_dal_irq_source_dce110 +}; + +static enum dc_irq_source dm_test_to_dal_irq_source_dcn10( + struct irq_service *irq_service, + uint32_t src_id, + uint32_t ext_id) +{ + switch (src_id) { + case DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP: + return DC_IRQ_SOURCE_VBLANK1; + case DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT: + return DC_IRQ_SOURCE_VUPDATE1; + case DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT: + return DC_IRQ_SOURCE_PFLIP1; + case DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT: + return DC_IRQ_SOURCE_DMCUB_OUTBOX; + default: + return DC_IRQ_SOURCE_INVALID; + } +} + +static const struct irq_service_funcs dm_test_irq_service_funcs_dcn10 = { + .to_dal_irq_source = dm_test_to_dal_irq_source_dcn10 +}; + +static bool dm_test_irq_src_set(struct irq_service *irq_service, + const struct irq_source_info *info, bool enable) +{ + return true; +} + +static bool dm_test_irq_src_ack(struct irq_service *irq_service, + const struct irq_source_info *info) +{ + return true; +} + +/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +static struct irq_source_info_funcs dm_test_irq_src_funcs = { + .set = dm_test_irq_src_set, + .ack = dm_test_irq_src_ack, +}; + +static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test, + const struct irq_service_funcs *funcs) +{ + struct irq_source_info *info; + struct resource_pool *res_pool; + struct irq_service *irqs; + struct dc *dc; + int i; + + dc = dm_kunit_alloc_dc_with_ctx(test); + res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); + irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); + + /* + * Populate the per-source info table so dc_interrupt_set()/_ack() + * succeed without touching hardware registers. + */ + info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); + for (i = 0; i < DAL_IRQ_SOURCES_NUMBER; i++) + info[i].funcs = &dm_test_irq_src_funcs; + + irqs->funcs = funcs; + irqs->info = info; + res_pool->irqs = irqs; + dc->res_pool = res_pool; + + return dc; +} + +static void dm_test_free_irq_sources(void *data) +{ + struct amdgpu_device *adev = data; + int i; + + for (i = 0; i < AMDGPU_IRQ_CLIENTID_MAX; i++) { + kfree(adev->irq.client[i].sources); + adev->irq.client[i].sources = NULL; + } + + kfree(adev->crtc_irq.enabled_types); + adev->crtc_irq.enabled_types = NULL; + kfree(adev->vline0_irq.enabled_types); + adev->vline0_irq.enabled_types = NULL; + kfree(adev->vupdate_irq.enabled_types); + adev->vupdate_irq.enabled_types = NULL; + kfree(adev->pageflip_irq.enabled_types); + adev->pageflip_irq.enabled_types = NULL; + kfree(adev->dmub_outbox_irq.enabled_types); + adev->dmub_outbox_irq.enabled_types = NULL; + kfree(adev->dmub_trace_irq.enabled_types); + adev->dmub_trace_irq.enabled_types = NULL; + kfree(adev->hpd_irq.enabled_types); + adev->hpd_irq.enabled_types = NULL; +} + static void dm_test_crtc_list_del(void *data) { struct amdgpu_crtc *acrtc = data; @@ -31,6 +340,35 @@ static void dm_test_crtc_list_del(void *data) list_del_init(&acrtc->base.head); } +struct dm_test_hpd_rx_wq_ctx { + struct hpd_rx_irq_offload_work_queue *wq; + int count; +}; + +static void dm_test_destroy_hpd_rx_wq(void *data) +{ + struct dm_test_hpd_rx_wq_ctx *ctx = data; + int i; + + for (i = 0; i < ctx->count; i++) + if (ctx->wq[i].wq) + destroy_workqueue(ctx->wq[i].wq); + kfree(ctx->wq); +} + +static const struct drm_connector_funcs dm_test_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, +}; + +static void dm_test_connector_cleanup(void *data) +{ + drm_connector_cleanup(data); +} + /* Tests for amdgpu_dm_hpd_to_dal_irq_source() */ /** @@ -794,11 +1132,9 @@ static void dm_test_get_crtc_by_otg_inst_returns_match(struct kunit *test) acrtc_b->otg_inst = 3; list_add_tail(&acrtc_a->base.head, &drm->mode_config.crtc_list); - KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, - acrtc_a), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, acrtc_a), 0); list_add_tail(&acrtc_b->base.head, &drm->mode_config.crtc_list); - KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, - acrtc_b), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, acrtc_b), 0); KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 3), acrtc_b); } @@ -823,8 +1159,7 @@ static void dm_test_get_crtc_by_otg_inst_returns_null(struct kunit *test) acrtc->otg_inst = 2; list_add_tail(&acrtc->base.head, &drm->mode_config.crtc_list); - KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, - acrtc), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, acrtc), 0); KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 5)); } @@ -842,6 +1177,2747 @@ static void dm_test_get_crtc_by_otg_inst_empty_list(struct kunit *test) KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 0)); } +/* Tests for amdgpu_dm_set_irq_funcs() */ + +/** + * dm_test_set_irq_funcs - Test irq src funcs and counts are populated + * @test: The KUnit test context + */ +static void dm_test_set_irq_funcs(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 6; + adev->mode_info.num_hpd = 4; + + amdgpu_dm_set_irq_funcs(adev); + + KUNIT_EXPECT_EQ(test, adev->crtc_irq.num_types, 6); + KUNIT_EXPECT_EQ(test, adev->vline0_irq.num_types, 6); + KUNIT_EXPECT_EQ(test, adev->vupdate_irq.num_types, 6); + KUNIT_EXPECT_EQ(test, adev->pageflip_irq.num_types, 6); + KUNIT_EXPECT_EQ(test, adev->dmub_outbox_irq.num_types, 1); + KUNIT_EXPECT_EQ(test, adev->dmub_trace_irq.num_types, 1); + KUNIT_EXPECT_EQ(test, adev->hpd_irq.num_types, 4); + + KUNIT_EXPECT_TRUE(test, adev->crtc_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->vline0_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->dmub_outbox_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->vupdate_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->dmub_trace_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->pageflip_irq.funcs != NULL); + KUNIT_EXPECT_TRUE(test, adev->hpd_irq.funcs != NULL); +} + +/* Tests for amdgpu_dm_irq_suspend()/resume_early()/resume_late() */ + +/** + * dm_test_irq_suspend_empty - Test suspend walks empty handler tables safely + * @test: The KUnit test context + */ +static void dm_test_irq_suspend_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* + * With no registered handlers the HW dc_interrupt_set() calls are + * skipped, so suspend must complete without touching the (absent) DC. + */ + amdgpu_dm_irq_suspend(adev); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.irq_handler_list_low_tab[src])); + KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.irq_handler_list_high_tab[src])); + } +} + +/** + * dm_test_irq_resume_early_empty - Test early resume walks empty tables safely + * @test: The KUnit test context + */ +static void dm_test_irq_resume_early_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_resume_early(adev); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) + KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.irq_handler_list_high_tab[src])); +} + +/** + * dm_test_irq_resume_late_empty - Test late resume walks empty tables safely + * @test: The KUnit test context + */ +static void dm_test_irq_resume_late_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_resume_late(adev); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) + KUNIT_EXPECT_TRUE(test, list_empty(&adev->dm.irq_handler_list_low_tab[src])); +} + +/** + * dm_test_irq_suspend_registered - Test suspend reaches the dc_interrupt_set path + * @test: The KUnit test context + * + * Registers a low-context HPD handler so the handler list is non-empty, + * forcing amdgpu_dm_irq_suspend() to call dc_interrupt_set() (NULL-safe with + * no DC) and flush_work() on the registered handler. + */ +static void dm_test_irq_suspend_registered(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_device *adev; + void *handler; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_suspend(adev); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); +} + +/** + * dm_test_irq_suspend_disables_polling - Test suspend disables KMS polling + * @test: The KUnit test context + * + * With KMS polling active, suspend must take the poll-disable branch. + * drm_kms_helper_poll_disable() only cancels the poll work; it leaves the + * poll_enabled flag set (cleared later by drm_kms_helper_poll_fini()). + */ +static void dm_test_irq_suspend_disables_polling(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* Enable KMS polling so suspend takes the poll-disable branch. */ + drm_kms_helper_poll_init(&adev->ddev); + KUNIT_ASSERT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + amdgpu_dm_irq_suspend(adev); + + KUNIT_EXPECT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + drm_kms_helper_poll_fini(&adev->ddev); +} + +/** + * dm_test_irq_resume_early_registered - Test early resume reaches dc_interrupt_set + * @test: The KUnit test context + * + * Registers a low-context HPD RX handler so early resume calls + * dc_interrupt_set() for the short-pulse interrupt source. + */ +static void dm_test_irq_resume_early_registered(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_device *adev; + void *handler; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1RX; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_resume_early(adev); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1RX, + dm_test_irq_handler); +} + +/** + * dm_test_irq_resume_late_registered - Test late resume reaches dc_interrupt_set + * @test: The KUnit test context + * + * Registers a low-context HPD handler so late resume calls dc_interrupt_set() + * for the HPD interrupt source. + */ +static void dm_test_irq_resume_late_registered(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_device *adev; + void *handler; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_resume_late(adev); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); +} + +/** + * dm_test_irq_resume_late_enables_polling - Test late resume re-enables polling + * @test: The KUnit test context + * + * With KMS polling active, late resume must take the poll-enable branch and + * leave polling enabled. + */ +static void dm_test_irq_resume_late_enables_polling(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* Enable KMS polling so resume_late takes the poll-enable branch. */ + drm_kms_helper_poll_init(&adev->ddev); + KUNIT_ASSERT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + amdgpu_dm_irq_resume_late(adev); + + KUNIT_EXPECT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + drm_kms_helper_poll_fini(&adev->ddev); +} + +/* Tests for amdgpu_dm_hpd_rx_irq_create_workqueue() */ + +/** + * dm_test_hpd_rx_irq_create_workqueue - Test workqueue array creation + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_irq_create_workqueue(struct kunit *test) +{ + struct dm_test_hpd_rx_wq_ctx *ctx; + struct amdgpu_device *adev; + struct dc *dc; + int i; + + adev = dm_kunit_alloc_adev(test); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + dc->caps.max_links = 4; + adev->dm.dc = dc; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + ctx->wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev); + KUNIT_ASSERT_NOT_NULL(test, ctx->wq); + ctx->count = dc->caps.max_links; + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_destroy_hpd_rx_wq, ctx), 0); + + for (i = 0; i < dc->caps.max_links; i++) + KUNIT_EXPECT_TRUE(test, ctx->wq[i].wq != NULL); +} + +/* Tests for amdgpu_dm_hpd_rx_irq_work_suspend() */ + +/** + * dm_test_hpd_rx_irq_work_suspend_null - Test suspend with no work queue + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_irq_work_suspend_null(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm); + + /* A NULL hpd_rx_offload_wq must be a safe no-op (DC untouched). */ + amdgpu_dm_hpd_rx_irq_work_suspend(dm); +} + +/** + * dm_test_hpd_rx_irq_work_suspend_flushes - Test suspend flushes queues + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_irq_work_suspend_flushes(struct kunit *test) +{ + struct dm_test_hpd_rx_wq_ctx *ctx; + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + dc->caps.max_links = 2; + adev->dm.dc = dc; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + ctx->wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev); + KUNIT_ASSERT_NOT_NULL(test, ctx->wq); + ctx->count = dc->caps.max_links; + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_destroy_hpd_rx_wq, ctx), 0); + + adev->dm.hpd_rx_offload_wq = ctx->wq; + + amdgpu_dm_hpd_rx_irq_work_suspend(&adev->dm); +} + +/* Tests for CRTC-based irq state callbacks (no-CRTC early return) */ + +/** + * dm_test_set_crtc_irq_state_no_crtc - Test crtc irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_crtc_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* mode_info.crtcs[0] is NULL -> returns 0 without dereferencing DC. */ + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_pflip_irq_state_no_crtc - Test pflip irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_pflip_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_pflip_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/** + * dm_test_set_vline0_irq_state_no_crtc - Test vline0 irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_vline0_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vline0_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_vupdate_irq_state_no_crtc - Test vupdate irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_vupdate_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vupdate_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/* Tests for CRTC-based irq state callbacks (dm_irq_state happy path) */ + +/** + * dm_test_set_crtc_irq_state_otg_disabled - Test crtc irq state with disabled OTG + * @test: The KUnit test context + */ +static void dm_test_set_crtc_irq_state_otg_disabled(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + /* otg_inst == -1 short-circuits before computing the irq source. */ + acrtc->otg_inst = -1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_crtc_irq_state_enable - Test crtc irq state reaches DC (enable) + * @test: The KUnit test context + */ +static void dm_test_set_crtc_irq_state_enable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + /* + * otg_inst >= 0 computes the irq source and reaches the NULL-safe + * dc_interrupt_set(); the ips_support branch is skipped (dc == NULL). + */ + acrtc->otg_inst = 3; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_pflip_irq_state_disable - Test pflip irq state reaches DC (disable) + * @test: The KUnit test context + */ +static void dm_test_set_pflip_irq_state_disable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + /* The disable state exercises the st == false path. */ + acrtc->otg_inst = 1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_pflip_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/** + * dm_test_set_vline0_irq_state_enable - Test vline0 irq state reaches DC (enable) + * @test: The KUnit test context + */ +static void dm_test_set_vline0_irq_state_enable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->otg_inst = 0; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vline0_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_vupdate_irq_state_enable - Test vupdate irq state reaches DC (enable) + * @test: The KUnit test context + */ +static void dm_test_set_vupdate_irq_state_enable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->otg_inst = 2; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vupdate_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_crtc_irq_state_allows_idle - Test the idle-optimization branch + * @test: The KUnit test context + * + * With a non-NULL DC that advertises IPS support and currently allows idle + * optimizations, dm_irq_state() must call dc_allow_idle_optimizations() before + * dc_interrupt_set(). disable_idle_power_optimizations makes that call a safe + * early return, and per-source stub funcs let dc_interrupt_set() succeed. + */ +static void dm_test_set_crtc_irq_state_allows_idle(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + struct dal_logger *logger; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dcn10); + + /* DC_LOG_* dereferences ctx->logger->dev, so wire a real drm device. */ + logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, logger); + logger->dev = &adev->ddev; + dc->ctx->logger = logger; + + dc->caps.ips_support = true; + dc->idle_optimizations_allowed = true; + /* Keep dc_allow_idle_optimizations() a safe early return. */ + dc->debug.disable_idle_power_optimizations = true; + adev->dm.dc = dc; + + acrtc->otg_inst = 0; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/* Tests for amdgpu_dm_irq_immediate_work() */ + +/** + * dm_test_irq_immediate_work_empty - Test immediate work on empty high table + * @test: The KUnit test context + */ +static void dm_test_irq_immediate_work_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* No registered high-context handlers: must be a safe no-op. */ + amdgpu_dm_irq_immediate_work(adev, DC_IRQ_SOURCE_HPD1); +} + +/** + * dm_test_irq_immediate_work_invokes_handler - Test immediate work calls handler + * @test: The KUnit test context + */ +static void dm_test_irq_immediate_work_invokes_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + int count = 0; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* High-context handlers are invoked synchronously, in-place. */ + amdgpu_dm_irq_immediate_work(adev, DC_IRQ_SOURCE_HPD1); + KUNIT_EXPECT_EQ(test, count, 1); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, dm_test_irq_handler_count); +} + +/** + * dm_test_irq_immediate_work_invokes_all - Test immediate work calls all handlers + * @test: The KUnit test context + */ +static void dm_test_irq_immediate_work_invokes_all(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + int count = 0; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD2; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* Both registered high-context handlers must run. */ + amdgpu_dm_irq_immediate_work(adev, DC_IRQ_SOURCE_HPD2); + KUNIT_EXPECT_EQ(test, count, 2); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD2, dm_test_irq_handler_count); + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD2, dm_test_irq_handler_count); +} + +/* Tests for amdgpu_dm_irq_schedule_work() */ + +/** + * dm_test_irq_schedule_work_empty - Test schedule work on empty low table + * @test: The KUnit test context + */ +static void dm_test_irq_schedule_work_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + /* Empty handler list: schedule_work returns immediately. */ + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); +} + +/** + * dm_test_irq_schedule_work_queues_handler - Test schedule work runs handler + * @test: The KUnit test context + */ +static void dm_test_irq_schedule_work_queues_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + int count = 0; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); + + /* + * Low-context work runs asynchronously on the DM IRQ workqueue. + * Flush it so the handler completes before we check the count; + * amdgpu_dm_irq_fini() cancels (rather than runs) any work that is + * still pending, so the flush must happen first. + */ + flush_workqueue(adev->dm.irq_wq); + KUNIT_EXPECT_EQ(test, count, 1); + + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_irq_schedule_work_requeue_fallback - Test the re-queue fallback path + * @test: The KUnit test context + * + * The first schedule queues the handler's work item. Issuing a second + * schedule before the work has run makes queue_work() fail for the + * still-pending item, forcing amdgpu_dm_irq_schedule_work() into the fallback + * that allocates and queues a fresh handler copy. Both work items run when + * the DM IRQ workqueue is flushed, so the handler fires twice. + */ +static void dm_test_irq_schedule_work_requeue_fallback(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_device *adev; + int count = 0; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); + + /* + * Flush the DM IRQ workqueue so both work items run before we check + * the count; amdgpu_dm_irq_fini() would cancel any still-pending work + * instead of running it. + */ + flush_workqueue(adev->dm.irq_wq); + KUNIT_EXPECT_EQ(test, count, 2); + + amdgpu_dm_irq_fini(adev); +} + +/* Tests for amdgpu_dm_set_hpd_irq_state() */ + +/** + * dm_test_set_hpd_irq_state_null_dc - Test HPD irq state with no DC + * @test: The KUnit test context + */ +static void dm_test_set_hpd_irq_state_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* dc_interrupt_set() is a no-op when dc is NULL, so both states + * return 0 without dereferencing the (absent) DC. + */ + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, + AMDGPU_IRQ_STATE_ENABLE), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, + AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/* Tests for amdgpu_dm_set_dmub_outbox_irq_state() */ + +/** + * dm_test_set_dmub_outbox_irq_state_null_dc - Test outbox irq state with no DC + * @test: The KUnit test context + */ +static void dm_test_set_dmub_outbox_irq_state_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_outbox_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_ENABLE), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_outbox_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/* Tests for amdgpu_dm_set_dmub_trace_irq_state() */ + +/** + * dm_test_set_dmub_trace_irq_state_null_dc - Test trace irq state with no DC + * @test: The KUnit test context + */ +static void dm_test_set_dmub_trace_irq_state_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_trace_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_ENABLE), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_trace_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/* Tests for amdgpu_dm_outbox_init() */ + +/** + * dm_test_outbox_init_null_dc - Test outbox init is a safe no-op with no DC + * @test: The KUnit test context + */ +static void dm_test_outbox_init_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* Single dc_interrupt_set() call must be skipped when dc is NULL. */ + amdgpu_dm_outbox_init(adev); +} + +/* Tests for amdgpu_dm_hpd_init()/amdgpu_dm_hpd_fini() */ + +/** + * dm_test_hpd_init_empty_connectors - Test HPD init with no connectors + * @test: The KUnit test context + */ +static void dm_test_hpd_init_empty_connectors(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + /* + * With an empty connector list the per-connector loop is skipped and + * the initial clear loop relies on dc_interrupt_set() being a no-op + * for a NULL dc, so init must complete without touching the DC. + */ + amdgpu_dm_hpd_init(adev); +} + +/** + * dm_test_hpd_fini_empty_connectors - Test HPD fini with no connectors + * @test: The KUnit test context + */ +static void dm_test_hpd_fini_empty_connectors(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + /* Empty connector list and disabled polling: fini is a safe no-op. */ + amdgpu_dm_hpd_fini(adev); +} + +/** + * dm_test_hpd_init_fini_with_connectors - Test HPD init/fini walk connectors + * @test: The KUnit test context + */ +static void dm_test_hpd_init_fini_with_connectors(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct drm_connector *wbconn; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + + /* + * num_hpd = 0 forces irq_type >= num_hpd so the loop takes the HW + * fallback (dc_interrupt_set()) instead of amdgpu_irq_get(); with a + * NULL dc that fallback is a safe no-op. + */ + adev->mode_info.num_hpd = 0; + + /* A writeback connector must be skipped by the per-connector loop. */ + wbconn = kunit_kzalloc(test, sizeof(*wbconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wbconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wbconn, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_WRITEBACK), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, wbconn), 0); + + /* A DisplayPort connector with a dc_link exercises the loop body. */ + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1RX; + aconn->dc_link = link; + + /* DC is absent (NULL), so the HW writes are NULL-safe no-ops. */ + amdgpu_dm_hpd_init(adev); + amdgpu_dm_hpd_fini(adev); +} + +/** + * dm_test_hpd_init_fini_analog_connector - Test HPD init/fini analog polling path + * @test: The KUnit test context + */ +static void dm_test_hpd_init_fini_analog_connector(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + adev->mode_info.num_hpd = 0; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_VGA), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1RX; + /* An analog connector id makes the loop request polling. */ + link->link_id.id = CONNECTOR_ID_VGA; + aconn->dc_link = link; + + /* use_polling becomes true, so init must enable KMS polling. */ + amdgpu_dm_hpd_init(adev); + KUNIT_EXPECT_TRUE(test, adev->ddev.mode_config.poll_enabled); + + /* fini must tear the polling back down. */ + amdgpu_dm_hpd_fini(adev); + KUNIT_EXPECT_FALSE(test, adev->ddev.mode_config.poll_enabled); +} + +/** + * dm_test_hpd_init_fini_irq_ref - Test HPD init/fini base-driver irq ref path + * @test: The KUnit test context + */ +static void dm_test_hpd_init_fini_irq_ref(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + + /* + * num_hpd >= 1 makes irq_type (0) < num_hpd, so the loop takes the + * amdgpu_irq_get()/amdgpu_irq_put() branch instead of the + * dc_interrupt_set() fallback. The mock device has irq.installed == + * false, so both calls fail early with -ENOENT (logging an error) + * without touching the base-driver irq state. + */ + adev->mode_info.num_hpd = 1; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_INVALID; + aconn->dc_link = link; + + amdgpu_dm_hpd_init(adev); + amdgpu_dm_hpd_fini(adev); +} + +/* Tests for dm_handle_hpd_rx_offload_work() */ + +/** + * dm_test_hpd_rx_offload_work_no_connector - Test missing connector early exit + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_offload_work_no_connector(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); +} + +/** + * dm_test_hpd_rx_offload_work_no_connection - Test no connection early exit + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_offload_work_no_connection(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + offload_wq->aconnector = aconn; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link_srv->detect_connection_type = dm_test_detect_connection_none; + dc->link_srv = link_srv; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->dc = dc; + aconn->dc_link = link; + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); +} + +/** + * dm_test_hpd_rx_offload_work_automated_test - Test AUTOMATED_TEST branch + * @test: The KUnit test context + * + * With a present connection and the AUTOMATED_TEST service-IRQ bit set, the + * worker runs dc_link_dp_handle_automated_test() (stubbed via link_srv) and + * writes the test response with core_link_write_dpcd(). timing_changed is left + * false so force_connector_state() (which needs a registered DRM device) is + * skipped, and aux_access_disabled makes the DPCD write a safe no-op. + */ +static void dm_test_hpd_rx_offload_work_automated_test(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + dm_test_automated_test_count = 0; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + adev->reset_domain = kunit_kzalloc(test, sizeof(*adev->reset_domain), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->reset_domain); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + spin_lock_init(&offload_wq->offload_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + aconn->timing_changed = false; + offload_wq->aconnector = aconn; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_single; + link_srv->dp_handle_automated_test = dm_test_dp_handle_automated_test; + dc->link_srv = link_srv; + dc->ctx = ctx; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->aux_access_disabled = true; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + aconn->dc_link = link; + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST = 1; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); + + KUNIT_EXPECT_EQ(test, dm_test_automated_test_count, 1); +} + +/** + * dm_test_hpd_rx_offload_work_link_loss - Test link-loss branch + * @test: The KUnit test context + * + * With a present non-eDP connection and no service-IRQ bits set, the worker + * takes the else-if link-loss path: dc_link_check_link_loss_status() and + * dc_link_dp_allow_hpd_rx_irq() gate entry, then dc_link_dp_read_hpd_rx_irq_data() + * returns DC_OK and a second link-loss check triggers dc_link_dp_handle_link_loss(). + * All four are stubbed via link_srv. + */ +static void dm_test_hpd_rx_offload_work_link_loss(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + dm_test_handle_link_loss_count = 0; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + adev->reset_domain = kunit_kzalloc(test, sizeof(*adev->reset_domain), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->reset_domain); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + spin_lock_init(&offload_wq->offload_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + offload_wq->aconnector = aconn; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_single; + link_srv->dp_parse_link_loss_status = dm_test_dp_parse_link_loss_true; + link_srv->dp_should_allow_hpd_rx_irq = dm_test_dp_should_allow_hpd_rx_irq_true; + link_srv->dp_read_hpd_rx_irq_data = dm_test_dp_read_hpd_rx_irq_data_ok; + link_srv->dp_handle_link_loss = dm_test_dp_handle_link_loss; + dc->link_srv = link_srv; + dc->ctx = ctx; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + aconn->dc_link = link; + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); + + KUNIT_EXPECT_EQ(test, dm_test_handle_link_loss_count, 1); +} + +/* Tests for amdgpu_dm_hdmi_hpd_debounce_work() */ + +/** + * dm_test_hdmi_hpd_debounce_detect_false - Test debounce false detect path + * @test: The KUnit test context + */ +static void dm_test_hdmi_hpd_debounce_detect_false(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + amdgpu_dm_hdmi_hpd_debounce_work(&aconn->hdmi_hpd_debounce_work.work); + KUNIT_EXPECT_NULL(test, aconn->hdmi_prev_sink); +} + +/** + * dm_test_hdmi_hpd_debounce_reallow_idle - Test debounce idle/sink-release tail + * @test: The KUnit test context + * + * With detection stubbed to return false, the if (ret) block is skipped, but + * the function tail is still exercised: IPS support plus an idle-allowed dmub + * makes reallow_idle true so dc_allow_idle_optimizations() runs on both entry + * and exit (disable_idle_power_optimizations keeps those safe early returns), + * and a cached hdmi_prev_sink is released and cleared. + */ +static void dm_test_hdmi_hpd_debounce_reallow_idle(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct dc_dmub_srv *dmub_srv; + struct amdgpu_device *adev; + struct dal_logger *logger; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, logger); + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + + link_srv->detect_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + /* DC_LOG_* dereferences ctx->logger->dev, so wire a real drm device. */ + logger->dev = &adev->ddev; + dc->ctx->logger = logger; + + /* Make reallow_idle true and keep dc_allow_idle*() a safe early return. */ + dmub_srv->idle_allowed = true; + dc->ctx->dmub_srv = dmub_srv; + dc->caps.ips_support = true; + dc->debug.disable_idle_power_optimizations = true; + + /* A cached previous sink must be released and cleared. */ + aconn->hdmi_prev_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn->hdmi_prev_sink); + + amdgpu_dm_hdmi_hpd_debounce_work(&aconn->hdmi_hpd_debounce_work.work); + KUNIT_EXPECT_NULL(test, aconn->hdmi_prev_sink); +} + +/* Tests for handle_hpd_irq()/handle_hpd_irq_helper() */ + +/** + * dm_test_handle_hpd_irq_disabled - Test HPD helper returns when disabled + * @test: The KUnit test context + */ +static void dm_test_handle_hpd_irq_disabled(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + adev->dm.disable_hpd_irq = true; + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + dc->ctx = ctx; + ctx->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + aconn->fake_enable = true; + + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + KUNIT_EXPECT_TRUE(test, aconn->fake_enable); + + handle_hpd_irq(aconn); + KUNIT_EXPECT_TRUE(test, aconn->fake_enable); +} + +/** + * dm_test_handle_hpd_irq_helper_debounce_schedule - Test HDMI debounce branch + * @test: The KUnit test context + * + * An HDMI link reporting a disconnect (connection type none) with a non-zero + * debounce delay and a cached local_sink must take the debounce branch: it + * caches local_sink in hdmi_prev_sink and schedules the delayed debounce work + * instead of detecting immediately. + */ +static void dm_test_handle_hpd_irq_helper_debounce_schedule(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_none; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + /* HDMI signal + debounce delay + cached sink -> debounce branch. */ + aconn->hdmi_hpd_debounce_delay_ms = 100; + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + link->local_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link->local_sink); + + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + + /* local_sink is cached for later comparison by the debounce work. */ + KUNIT_EXPECT_PTR_EQ(test, aconn->hdmi_prev_sink, link->local_sink); + + cancel_delayed_work_sync(&aconn->hdmi_hpd_debounce_work); + dm_test_sink_release(aconn->hdmi_prev_sink); + dm_test_sink_release(link->local_sink); +} + +/** + * dm_test_handle_hpd_irq_helper_debounce_release_prev - Test stale prev_sink + * @test: The KUnit test context + * + * When the debounce branch is taken and a stale hdmi_prev_sink is already + * cached from a previous HPD, it must be released before caching the current + * local_sink. This exercises the dc_sink_release() of the previous sink. + */ +static void dm_test_handle_hpd_irq_helper_debounce_release_prev(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_none; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + /* HDMI signal + debounce delay + cached sink -> debounce branch. */ + aconn->hdmi_hpd_debounce_delay_ms = 100; + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + link->local_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link->local_sink); + + /* Stale sink from a previous HPD must be released by the helper. */ + aconn->hdmi_prev_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn->hdmi_prev_sink); + KUNIT_ASSERT_PTR_NE(test, aconn->hdmi_prev_sink, link->local_sink); + + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + + /* Stale sink replaced by the current local_sink. */ + KUNIT_EXPECT_PTR_EQ(test, aconn->hdmi_prev_sink, link->local_sink); + + cancel_delayed_work_sync(&aconn->hdmi_hpd_debounce_work); + dm_test_sink_release(aconn->hdmi_prev_sink); + dm_test_sink_release(link->local_sink); +} + +/** + * dm_test_handle_hpd_irq_helper_detect_false - Test immediate detect branch + * @test: The KUnit test context + * + * With no force, no debounce, and detection stubbed to report no connection, + * the helper takes the else branch: dc_exit_ips_for_hw_access() is a safe + * no-op (no IPS support) and dc_link_detect() returns false, so the connected + * if (ret) block is skipped. fake_enable must still be cleared. + */ +static void dm_test_handle_hpd_irq_helper_detect_false(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_none; + link_srv->detect_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + aconn->fake_enable = true; + + /* No debounce delay and no force -> immediate-detect else branch. */ + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + + KUNIT_EXPECT_FALSE(test, aconn->fake_enable); +} + +/* Tests for handle_hpd_rx_irq()/schedule_hpd_rx_offload_work() */ + +/** + * dm_test_handle_hpd_rx_irq_disabled - Test HPDRX handler returns when disabled + * @test: The KUnit test context + */ +static void dm_test_handle_hpd_rx_irq_disabled(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + adev->dm.disable_hpd_irq = true; + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + adev->dm.hpd_rx_offload_wq = offload_wq; + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_no_work; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->link_index = 0; + aconn->dc_link = link; + + handle_hpd_rx_irq(aconn); +} + +/** + * dm_test_schedule_hpd_rx_offload_work - Test offload work is queued + * @test: The KUnit test context + */ +static void dm_test_schedule_hpd_rx_offload_work(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct amdgpu_device *adev; + union hpd_irq_data data = { 0 }; + + adev = dm_kunit_alloc_adev(test); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + offload_wq->wq = create_singlethread_workqueue("dm_irq_test_hpd_rx"); + KUNIT_ASSERT_NOT_NULL(test, offload_wq->wq); + + schedule_hpd_rx_offload_work(adev, offload_wq, data); + flush_workqueue(offload_wq->wq); + destroy_workqueue(offload_wq->wq); +} + +static void dm_test_destroy_offload_wq(void *data) +{ + struct workqueue_struct *wq = data; + + flush_workqueue(wq); + destroy_workqueue(wq); +} + +/* + * Build an aconnector wired for handle_hpd_rx_irq(): HPD enabled, an MST-root + * connector on an MST-branch link so the post-detect block and drm_dp_cec_irq + * are skipped, and a flushed offload work queue. Caller sets the link_srv + * stubs (dp_handle_hpd_rx_irq and, if needed, dp_should_allow_hpd_rx_irq). + */ +static struct amdgpu_dm_connector *dm_test_setup_hpd_rx_irq(struct kunit *test, + struct link_service **link_srv_out) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + spin_lock_init(&offload_wq->offload_lock); + offload_wq->wq = create_singlethread_workqueue("dm_irq_test_hpd_rx"); + KUNIT_ASSERT_NOT_NULL(test, offload_wq->wq); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_destroy_offload_wq, + offload_wq->wq), 0); + adev->dm.hpd_rx_offload_wq = offload_wq; + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + aconn->mst_mgr.mst_state = true; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->link_index = 0; + link->type = dc_connection_mst_branch; + aconn->dc_link = link; + + *link_srv_out = link_srv; + + return aconn; +} + +/** + * dm_test_handle_hpd_rx_irq_no_left_work - Test HPDRX early out (no left work) + * @test: The KUnit test context + * + * When dc_link_handle_hpd_rx_irq() reports no left-over work, the handler + * jumps to out and returns without scheduling any offload work. + */ +static void dm_test_handle_hpd_rx_irq_no_left_work(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_no_work; + + handle_hpd_rx_irq(aconn); +} + +/** + * dm_test_handle_hpd_rx_irq_automated_test - Test HPDRX automated-test path + * @test: The KUnit test context + * + * The AUTOMATED_TEST device-service bit must schedule offload work and jump to + * out before the allow-hpd-rx-irq checks. + */ +static void dm_test_handle_hpd_rx_irq_automated_test(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_automated; + + handle_hpd_rx_irq(aconn); +} + +/** + * dm_test_handle_hpd_rx_irq_msg_rdy - Test HPDRX MST message-ready path + * @test: The KUnit test context + * + * With HPD RX IRQs allowed and an UP_REQ_MSG_RDY bit set, the handler must take + * the MST message-ready branch, mark is_handling_mst_msg_rdy_event and schedule + * offload work. + */ +static void dm_test_handle_hpd_rx_irq_msg_rdy(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_msg_rdy; + link_srv->dp_should_allow_hpd_rx_irq = dm_test_allow_hpd_rx_irq_true; + + handle_hpd_rx_irq(aconn); + + KUNIT_EXPECT_TRUE(test, drm_to_adev(aconn->base.dev) + ->dm.hpd_rx_offload_wq->is_handling_mst_msg_rdy_event); +} + +/** + * dm_test_handle_hpd_rx_irq_link_loss - Test HPDRX link-loss path + * @test: The KUnit test context + * + * With HPD RX IRQs allowed and a reported link loss (no message-ready bits), + * the handler must take the link-loss branch, mark is_handling_link_loss and + * schedule offload work. + */ +static void dm_test_handle_hpd_rx_irq_link_loss(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_link_loss; + link_srv->dp_should_allow_hpd_rx_irq = dm_test_allow_hpd_rx_irq_true; + + handle_hpd_rx_irq(aconn); + + KUNIT_EXPECT_TRUE(test, drm_to_adev(aconn->base.dev) + ->dm.hpd_rx_offload_wq->is_handling_link_loss); +} + +/* Tests for dmub_hpd_callback()/dmub_hpd_sense_callback() */ + +/** + * dm_test_dmub_hpd_callback_null_inputs - Test DMUB callback null inputs + * @test: The KUnit test context + */ +static void dm_test_dmub_hpd_callback_null_inputs(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + dmub_hpd_callback(NULL, NULL); + dmub_hpd_callback(adev, NULL); +} + +/** + * dm_test_dmub_hpd_callback_invalid_index - Test DMUB callback index check + * @test: The KUnit test context + */ +static void dm_test_dmub_hpd_callback_invalid_index(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + notify.link_index = 1; + + dmub_hpd_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_empty_connectors - Test DMUB callback without match + * @test: The KUnit test context + */ +static void dm_test_dmub_hpd_callback_empty_connectors(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + notify.type = DMUB_NOTIFICATION_HPD_SENSE_NOTIFY; + + dmub_hpd_callback(adev, ¬ify); + dmub_hpd_sense_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_unknown_type_match - Test DMUB callback matched + * connector, unknown type + * @test: The KUnit test context + * + * A writeback connector must be skipped (continue) and a non-writeback + * connector whose dc_link matches the notification link must be selected. + * An unrecognized notification type takes the "unknown" warn branch, and the + * post-loop dispatch is a no-op (neither HPD nor HPD_IRQ), so no real DC/MST + * handler runs. + */ +static void dm_test_dmub_hpd_callback_unknown_type_match(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_dm_connector *aconn; + struct drm_connector *wbconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + dc->links[0] = link; + + /* A writeback connector must be skipped by the loop. */ + wbconn = kunit_kzalloc(test, sizeof(*wbconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wbconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wbconn, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_WRITEBACK), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_connector_cleanup, wbconn), 0); + + /* A DisplayPort connector whose dc_link matches drives the match branch. */ + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + aconn->dc_link = link; + + notify.link_index = 0; + notify.type = DMUB_NOTIFICATION_HPD_SENSE_NOTIFY; + + dmub_hpd_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_hpd_type - Test DMUB callback HPD type dispatch + * @test: The KUnit test context + * + * A matched connector with notification type DMUB_NOTIFICATION_HPD logs the + * HPD callback and dispatches to handle_hpd_irq_helper(). Detection is stubbed + * to report no connection and link detect to fail, so the connected branches + * are skipped and no real DC/DRM hotplug path runs. + */ +static void dm_test_dmub_hpd_callback_hpd_type(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_none; + link_srv->detect_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + dc->links[0] = link; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + aconn->dc_link = link; + + notify.link_index = 0; + notify.type = DMUB_NOTIFICATION_HPD; + + dmub_hpd_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_hpd_irq_type - Test DMUB callback HPD_IRQ dispatch + * @test: The KUnit test context + * + * A matched connector with notification type DMUB_NOTIFICATION_HPD_IRQ logs + * the HPD RX callback and dispatches to handle_hpd_rx_irq(). The HPD RX handler + * stub reports no left work, so the function returns through the early goto + * without scheduling offload work; an MST-branch link skips the trailing + * drm_dp_cec_irq(). + */ +static void dm_test_dmub_hpd_callback_hpd_irq_type(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct dmub_notification notify = { 0 }; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + spin_lock_init(&offload_wq->offload_lock); + adev->dm.hpd_rx_offload_wq = offload_wq; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_no_work; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->link_index = 0; + link->type = dc_connection_mst_branch; + + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + dc->links[0] = link; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + mutex_init(&aconn->hpd_lock); + aconn->mst_mgr.mst_state = true; + aconn->dc_link = link; + + notify.link_index = 0; + notify.type = DMUB_NOTIFICATION_HPD_IRQ; + + dmub_hpd_callback(adev, ¬ify); +} + +/* Tests for amdgpu_dm_register_hpd_handlers() */ + +/** + * dm_test_register_hpd_handlers_empty - Test HPD registration with no connectors + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), 0); +} + +/** + * dm_test_register_hpd_handlers_valid - Test HPD/HPDRX registration succeeds + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_valid(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1RX; + aconn->dc_link = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), 0); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1RX])); + + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_hpd_handlers_invalid_hpd - Test invalid HPD source fails + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_invalid_hpd(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1RX; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_INVALID; + aconn->dc_link = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), -EINVAL); + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_hpd_handlers_invalid_hpd_rx - Test invalid HPDRX source fails + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_invalid_hpd_rx(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_INVALID; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1; + aconn->dc_link = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), -EINVAL); + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_hpd_handlers_dmub_outbox - Test DMUB outbox callbacks register + * @test: The KUnit test context + * + * Enables DMUB outbox support so dc_is_dmub_outbox_supported() returns true, + * exercising the dm_register_dmub_notify_callback() block. Asserts that the + * HPD, HPD_IRQ and HPD_SENSE_NOTIFY callbacks are registered. + */ +static void dm_test_register_hpd_handlers_dmub_outbox(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = dm_kunit_alloc_dc_with_ctx(test); + adev->dm.dc = dc; + + /* Make dc_is_dmub_outbox_supported() return true. */ + dc->caps.dmcub_support = true; + dc->ctx->asic_id.chip_family = AMDGPU_FAMILY_GC_11_0_1; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), 0); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD], + (dmub_notify_interrupt_callback_t)dmub_hpd_callback); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD_IRQ], + (dmub_notify_interrupt_callback_t)dmub_hpd_callback); + KUNIT_EXPECT_PTR_EQ(test, + adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD_SENSE_NOTIFY], + (dmub_notify_interrupt_callback_t)dmub_hpd_sense_callback); + + amdgpu_dm_irq_fini(adev); +} + +/* Tests for CRTC/pflip/vupdate high IRQ callbacks */ + +/** + * dm_test_pflip_high_irq_no_crtc - Test pflip high IRQ with no matching CRTC + * @test: The KUnit test context + */ +static void dm_test_pflip_high_irq_no_crtc(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_PFLIP; + + dm_pflip_high_irq(¶ms); +} + +/** + * dm_test_pflip_high_irq_not_submitted - Test pflip high IRQ early status exit + * @test: The KUnit test context + */ +static void dm_test_pflip_high_irq_not_submitted(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + INIT_LIST_HEAD(&acrtc->base.head); + acrtc->base.dev = &adev->ddev; + acrtc->otg_inst = 0; + acrtc->pflip_status = AMDGPU_FLIP_NONE; + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crtc_list_del, acrtc), 0); + + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_PFLIP; + + dm_pflip_high_irq(¶ms); + KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE); +} + +/** + * dm_test_vupdate_high_irq_no_crtc - Test vupdate high IRQ with no CRTC + * @test: The KUnit test context + */ +static void dm_test_vupdate_high_irq_no_crtc(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_VUPDATE; + + dm_vupdate_high_irq(¶ms); +} + +/** + * dm_test_crtc_high_irq_no_crtc - Test crtc high IRQ with no CRTC + * @test: The KUnit test context + */ +static void dm_test_crtc_high_irq_no_crtc(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_VBLANK; + + dm_crtc_high_irq(¶ms); +} + +/** + * dm_test_crtc_high_irq_vrr_pre_ai - Test crtc high IRQ VRR path on pre-AI ASIC + * @test: The KUnit test context + * + * With a matching CRTC, no writeback, VRR active (so the !vrr_active vblank + * handler is skipped) and a pre-AI family, the handler runs through CRC + * handling and returns before the freesync section. + */ +static void dm_test_crtc_high_irq_vrr_pre_ai(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + INIT_LIST_HEAD(&acrtc->base.head); + acrtc->base.dev = &adev->ddev; + acrtc->otg_inst = 0; + /* VRR active so the !vrr_active vblank handler is skipped. */ + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crtc_list_del, acrtc), 0); + + /* Pre-AI family returns right after CRC handling. */ + adev->family = AMDGPU_FAMILY_SI; + + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_VBLANK; + + dm_crtc_high_irq(¶ms); +} + +/** + * dm_test_crtc_high_irq_vrr_ai_no_stream - Test crtc high IRQ AI path, no stream + * @test: The KUnit test context + * + * On an AI+ family the handler runs the post-CRC freesync section. With no + * stream and no pending flip, both inner blocks are skipped and the handler + * completes through the event-lock critical section, leaving pflip_status + * untouched. + */ +static void dm_test_crtc_high_irq_vrr_ai_no_stream(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + INIT_LIST_HEAD(&acrtc->base.head); + acrtc->base.dev = &adev->ddev; + acrtc->otg_inst = 0; + acrtc->pflip_status = AMDGPU_FLIP_NONE; + /* VRR active so the !vrr_active vblank handler is skipped. */ + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crtc_list_del, acrtc), 0); + + /* AI+ family runs the freesync section; no stream skips it. */ + adev->family = AMDGPU_FAMILY_AI; + + params.adev = adev; + params.irq_src = (enum dc_irq_source)IRQ_TYPE_VBLANK; + + dm_crtc_high_irq(¶ms); + KUNIT_EXPECT_EQ(test, acrtc->pflip_status, AMDGPU_FLIP_NONE); +} + +/* Tests for dm_handle_hpd_work() */ + +/** + * dm_test_handle_hpd_work_out_of_range - Test HPD work frees unknown notification + * @test: The KUnit test context + */ +static void dm_test_handle_hpd_work_out_of_range(struct kunit *test) +{ + struct dmub_hpd_work *hpd_work; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + hpd_work = kzalloc(sizeof(*hpd_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hpd_work); + hpd_work->dmub_notify = kzalloc(sizeof(*hpd_work->dmub_notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hpd_work->dmub_notify); + hpd_work->dmub_notify->type = + (enum dmub_notification_type)ARRAY_SIZE(adev->dm.dmub_callback); + hpd_work->adev = adev; + INIT_WORK(&hpd_work->handle_hpd_work, dm_handle_hpd_work); + + dm_handle_hpd_work(&hpd_work->handle_hpd_work); +} + +/* Tests for dm_dmub_outbox1_low_irq() */ + +/** + * dm_test_dmub_outbox1_low_irq_empty - Test outbox low IRQ with empty trace queue + * @test: The KUnit test context + */ +static void dm_test_dmub_outbox1_low_irq_empty(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct dc_dmub_srv *dc_dmub_srv; + struct amdgpu_device *adev; + struct dmub_srv *dmub; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = dm_kunit_alloc_dc_with_ctx(test); + dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv); + dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub); + + dmub->hw_funcs.get_outbox0_wptr = dm_test_dmub_get_outbox0_wptr; + dc_dmub_srv->dmub = dmub; + dc->ctx->dmub_srv = dc_dmub_srv; + adev->dm.dc = dc; + params.adev = adev; + params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX; + + dm_dmub_outbox1_low_irq(¶ms); +} + +/* + * dm_test_alloc_adev_outbox_notify - Build an adev wired for DMUB outbox + * notification handling. + * + * Configures dc/dc_dmub_srv/dmub so that the trace queue is empty and + * dc_enable_dmub_notifications() returns true, allowing the notification + * handling block of dm_dmub_outbox1_low_irq() to execute. The outbox1 ring + * buffer is left empty, so each notification read returns + * DMUB_NOTIFICATION_NO_DATA with no pending notification. + */ +static struct amdgpu_device *dm_test_alloc_adev_outbox_notify(struct kunit *test) +{ + struct dc_dmub_srv *dc_dmub_srv; + struct amdgpu_device *adev; + struct dmub_srv *dmub; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = dm_kunit_alloc_dc_with_ctx(test); + dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv); + dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub); + + dmub->hw_init = true; + dmub->hw_funcs.get_outbox0_wptr = dm_test_dmub_get_outbox0_wptr; + dmub->hw_funcs.get_outbox1_wptr = dm_test_dmub_get_outbox1_wptr; + dc_dmub_srv->dmub = dmub; + dc->ctx->dmub_srv = dc_dmub_srv; + + /* Make dc_enable_dmub_notifications() return true. */ + dc->caps.dmcub_support = true; + dc->ctx->asic_id.chip_family = AMDGPU_FAMILY_GC_11_0_1; + + adev->dm.dc = dc; + + return adev; +} + +/** + * dm_test_dmub_outbox1_low_irq_no_handler - Test notification with no handler + * @test: The KUnit test context + * + * Notifications are enabled but no callback is registered for the returned + * notification type, exercising the skip-with-warning path. + */ +static void dm_test_dmub_outbox1_low_irq_no_handler(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_test_alloc_adev_outbox_notify(test); + params.adev = adev; + params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX; + + dm_dmub_outbox1_low_irq(¶ms); +} + +/** + * dm_test_dmub_outbox1_low_irq_direct_callback - Test direct callback dispatch + * @test: The KUnit test context + * + * Notifications are enabled with a registered callback and thread offload + * disabled, so the callback is invoked directly from the IRQ handler. + */ +static void dm_test_dmub_outbox1_low_irq_direct_callback(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_test_alloc_adev_outbox_notify(test); + adev->dm.dmub_callback[DMUB_NOTIFICATION_NO_DATA] = dm_test_dmub_notify_callback; + adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_NO_DATA] = false; + params.adev = adev; + params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX; + + dm_test_dmub_notify_count = 0; + + dm_dmub_outbox1_low_irq(¶ms); + + KUNIT_EXPECT_EQ(test, dm_test_dmub_notify_count, 1); +} + +/** + * dm_test_dmub_outbox1_low_irq_offload - Test offloaded callback dispatch + * @test: The KUnit test context + * + * Notifications are enabled with a registered callback and thread offload + * enabled, so the callback is dispatched via the delayed HPD work queue. + */ +static void dm_test_dmub_outbox1_low_irq_offload(struct kunit *test) +{ + struct common_irq_params params = { 0 }; + struct amdgpu_device *adev; + + adev = dm_test_alloc_adev_outbox_notify(test); + adev->dm.dmub_callback[DMUB_NOTIFICATION_NO_DATA] = dm_test_dmub_notify_callback; + adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_NO_DATA] = true; + adev->dm.delayed_hpd_wq = create_singlethread_workqueue("dm_irq_test_outbox"); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.delayed_hpd_wq); + params.adev = adev; + params.irq_src = DC_IRQ_SOURCE_DMCUB_OUTBOX; + + dm_test_dmub_notify_count = 0; + + dm_dmub_outbox1_low_irq(¶ms); + + flush_workqueue(adev->dm.delayed_hpd_wq); + destroy_workqueue(adev->dm.delayed_hpd_wq); + + KUNIT_EXPECT_EQ(test, dm_test_dmub_notify_count, 1); +} + + +/** + * dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources - Test DCE110 error + * @test: The KUnit test context + */ +static void dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = dm_kunit_alloc_dc_with_ctx(test); + adev->dm.dc = dc; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL); +} + +/** + * dm_test_dce110_register_irq_handlers_one_crtc - Test DCE110 with 1 CRTC + * @test: The KUnit test context + * + * Exercises the VBLANK, VUPDATE, PFLIP and HPD for-loop bodies with a + * fake IRQ service that maps source IDs to DC IRQ sources. + */ +static void dm_test_dce110_register_irq_handlers_one_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dce110); + dc->ctx->dce_version = DCE_VERSION_11_0; + adev->dm.dc = dc; + adev->mode_info.num_crtc = 1; + adev->mode_info.num_hpd = 1; + amdgpu_dm_set_irq_funcs(adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), 0); + + /* Verify VBLANK params were populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.vblank_params[0].irq_src, + (int)DC_IRQ_SOURCE_VBLANK1); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.vblank_params[0].adev, adev); + + /* Verify VUPDATE params were populated (VRR supported on DCE 11) */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.vupdate_params[0].irq_src, + (int)DC_IRQ_SOURCE_VUPDATE1); + + /* Verify PFLIP params were populated (6 fixed entries) */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.pflip_params[0].irq_src, + (int)DC_IRQ_SOURCE_PFLIP1); + KUNIT_EXPECT_EQ(test, (int)adev->dm.pflip_params[5].irq_src, + (int)DC_IRQ_SOURCE_PFLIP6); + + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_dcn10_register_irq_handlers_zero_crtc - Test DCN10 zero-CRTC registration + * @test: The KUnit test context + */ +static void dm_test_dcn10_register_irq_handlers_zero_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_kunit_alloc_dc_with_ctx(test); + adev->dm.dc = dc; + adev->mode_info.num_hpd = 1; + amdgpu_dm_set_irq_funcs(adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_dcn10_register_irq_handlers(adev), 0); + KUNIT_ASSERT_NOT_NULL(test, adev->irq.client[SOC15_IH_CLIENTID_DCE].sources); + KUNIT_EXPECT_PTR_EQ(test, + adev->irq.client[SOC15_IH_CLIENTID_DCE].sources[DCN_1_0__SRCID__DC_HPD1_INT], + &adev->hpd_irq); +} + +/** + * dm_test_dcn10_register_irq_handlers_one_crtc - Test DCN10 with 1 CRTC + * @test: The KUnit test context + * + * Exercises the VUPDATE_NO_LOCK for-loop bodies with a fake IRQ service, and + * that it's the only one populated (other than VLINE0, for secure display). + */ +static void dm_test_dcn10_register_irq_handlers_one_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dcn10); + adev->dm.dc = dc; + adev->mode_info.num_crtc = 1; + adev->mode_info.num_hpd = 1; + dc->caps.max_otg_num = 1; + amdgpu_dm_set_irq_funcs(adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_dcn10_register_irq_handlers(adev), 0); + + /* Verify VBLANK params were *not* populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.vblank_params[0].irq_src, 0); + KUNIT_EXPECT_NULL(test, adev->dm.vblank_params[0].adev); + + /* Verify VUPDATE params were populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.vupdate_params[0].irq_src, + (int)DC_IRQ_SOURCE_VUPDATE1); + + /* Verify PFLIP params were *not* populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.pflip_params[0].irq_src, 0); + + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_outbox_irq_handlers_without_dmub - Test outbox registration without DMUB + * @test: The KUnit test context + */ +static void dm_test_register_outbox_irq_handlers_without_dmub(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_kunit_alloc_dc_with_ctx(test); + adev->dm.dc = dc; + amdgpu_dm_set_irq_funcs(adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_outbox_irq_handlers(adev), 0); + KUNIT_ASSERT_NOT_NULL(test, adev->irq.client[SOC15_IH_CLIENTID_DCE].sources); + KUNIT_EXPECT_PTR_EQ(test, + adev->irq.client[SOC15_IH_CLIENTID_DCE].sources[ + DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT], + &adev->dmub_outbox_irq); +} + +/** + * dm_test_register_outbox_irq_handlers_with_dmub - Test outbox registration with DMUB + * @test: The KUnit test context + * + * Exercises the dc->ctx->dmub_srv branch which maps the outbox source and + * registers dm_dmub_outbox1_low_irq in the low IRQ context table. + */ +static void dm_test_register_outbox_irq_handlers_with_dmub(struct kunit *test) +{ + struct dc_dmub_srv *dc_dmub_srv; + struct amdgpu_device *adev; + struct list_head *hnd_list; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, + adev), 0); + dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dcn10); + dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv); + dc->ctx->dmub_srv = dc_dmub_srv; + adev->dm.dc = dc; + amdgpu_dm_set_irq_funcs(adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_outbox_irq_handlers(adev), 0); + + /* Verify the outbox IRQ params were populated */ + KUNIT_EXPECT_EQ(test, (int)adev->dm.dmub_outbox_params[0].irq_src, + (int)DC_IRQ_SOURCE_DMCUB_OUTBOX); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.dmub_outbox_params[0].adev, adev); + + /* Verify a low-context handler was registered for the outbox source */ + hnd_list = &adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_DMCUB_OUTBOX]; + KUNIT_EXPECT_FALSE(test, list_empty(hnd_list)); + + amdgpu_dm_irq_fini(adev); +} + +/* Tests for amdgpu_dm_irq_handler() */ + +/** + * dm_test_irq_handler_dispatches_work - Test the top-level IRQ handler + * @test: The KUnit test context + * + * amdgpu_dm_irq_handler() translates the hardware IRQ entry to a DC IRQ + * source, acknowledges it, then dispatches to the high-context (immediate) + * and low-context (scheduled) handler lists. A fake dc with a stubbed + * irq_service maps the source id to DC_IRQ_SOURCE_VBLANK1 and lets the ack + * succeed without touching hardware registers. + */ +static void dm_test_irq_handler_dispatches_work(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_iv_entry entry = { 0 }; + struct amdgpu_device *adev; + int high_count = 0; + int low_count = 0; + void *handler; + struct dc *dc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + dc = dm_test_alloc_dc_with_irq_service(test, + &dm_test_irq_service_funcs_dce110); + adev->dm.dc = dc; + + /* High-context (immediate) handler on VBLANK1. */ + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_VBLANK1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, + &high_count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* Low-context (scheduled) handler on VBLANK1. */ + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, + &low_count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* src_id maps to DC_IRQ_SOURCE_VBLANK1 via the dce110 stub. */ + entry.src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0; + entry.src_data[0] = 0; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_irq_handler(adev, NULL, &entry), 0); + + /* High-context handler runs synchronously in-place. */ + KUNIT_EXPECT_EQ(test, high_count, 1); + + /* + * Low-context work runs asynchronously; flush the DM IRQ workqueue so + * it completes before we check the count. amdgpu_dm_irq_fini() cancels + * any still-pending work rather than running it. + */ + flush_workqueue(adev->dm.irq_wq); + KUNIT_EXPECT_EQ(test, low_count, 1); + + amdgpu_dm_irq_fini(adev); +} + +/* Tests for dm_handle_vmin_vmax_update() */ + +/** + * dm_test_handle_vmin_vmax_update - Test the deferred vmin/vmax worker + * @test: The KUnit test context + * + * The worker applies the cached timing adjust to the stream via + * dc_stream_adjust_vmin_vmax(), drops the stream reference taken when the + * work was scheduled, and frees the work and its adjust copy. A fake dc with + * a current_state lets the adjust walk an empty pipe list without touching + * hardware. The work and adjust are kmalloc'd because the worker frees them. + */ +static void dm_test_handle_vmin_vmax_update(struct kunit *test) +{ + struct dc_crtc_timing_adjust *adjust; + struct vupdate_offload_work *work; + struct dc_stream_state *stream; + struct amdgpu_device *adev; + struct dc *dc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + mutex_init(&adev->dm.dc_lock); + + dc = dm_kunit_alloc_dc_with_ctx(test); + dc->current_state = kunit_kzalloc(test, sizeof(*dc->current_state), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc->current_state); + adev->dm.dc = dc; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + /* + * Start at two references: the worker's dc_stream_release() drops one, + * leaving the kunit-managed allocation intact (no kfree). + */ + kref_init(&stream->refcount); + kref_get(&stream->refcount); + + /* The worker kfree()s both, so they must come from the slab. */ + work = kzalloc(sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, work); + adjust = kzalloc(sizeof(*adjust), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adjust); + + work->adev = adev; + work->stream = stream; + work->adjust = adjust; + adjust->v_total_min = 1000; + adjust->v_total_max = 1100; + + dm_handle_vmin_vmax_update(&work->work); + + /* The adjust was applied and one stream reference was dropped. */ + KUNIT_EXPECT_EQ(test, stream->adjust.v_total_min, 1000); + KUNIT_EXPECT_EQ(test, stream->adjust.v_total_max, 1100); + KUNIT_EXPECT_EQ(test, kref_read(&stream->refcount), 1); +} + static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_to_dal_irq_source */ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), @@ -894,6 +3970,114 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_match), KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_null), KUNIT_CASE(dm_test_get_crtc_by_otg_inst_empty_list), + /* amdgpu_dm_set_irq_funcs */ + KUNIT_CASE(dm_test_set_irq_funcs), + /* amdgpu_dm_irq_suspend/resume_early/resume_late */ + KUNIT_CASE(dm_test_irq_suspend_empty), + KUNIT_CASE(dm_test_irq_resume_early_empty), + KUNIT_CASE(dm_test_irq_resume_late_empty), + KUNIT_CASE(dm_test_irq_suspend_registered), + KUNIT_CASE(dm_test_irq_suspend_disables_polling), + KUNIT_CASE(dm_test_irq_resume_early_registered), + KUNIT_CASE(dm_test_irq_resume_late_registered), + KUNIT_CASE(dm_test_irq_resume_late_enables_polling), + /* amdgpu_dm_hpd_rx_irq_create_workqueue */ + KUNIT_CASE(dm_test_hpd_rx_irq_create_workqueue), + /* amdgpu_dm_hpd_rx_irq_work_suspend */ + KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_null), + KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_flushes), + /* CRTC-based irq state callbacks (no-CRTC early return) */ + KUNIT_CASE(dm_test_set_crtc_irq_state_no_crtc), + KUNIT_CASE(dm_test_set_pflip_irq_state_no_crtc), + KUNIT_CASE(dm_test_set_vline0_irq_state_no_crtc), + KUNIT_CASE(dm_test_set_vupdate_irq_state_no_crtc), + /* CRTC-based irq state callbacks (dm_irq_state happy path) */ + KUNIT_CASE(dm_test_set_crtc_irq_state_otg_disabled), + KUNIT_CASE(dm_test_set_crtc_irq_state_enable), + KUNIT_CASE(dm_test_set_pflip_irq_state_disable), + KUNIT_CASE(dm_test_set_vline0_irq_state_enable), + KUNIT_CASE(dm_test_set_vupdate_irq_state_enable), + KUNIT_CASE(dm_test_set_crtc_irq_state_allows_idle), + /* amdgpu_dm_irq_immediate_work */ + KUNIT_CASE(dm_test_irq_immediate_work_empty), + KUNIT_CASE(dm_test_irq_immediate_work_invokes_handler), + KUNIT_CASE(dm_test_irq_immediate_work_invokes_all), + /* amdgpu_dm_irq_schedule_work */ + KUNIT_CASE(dm_test_irq_schedule_work_empty), + KUNIT_CASE(dm_test_irq_schedule_work_queues_handler), + KUNIT_CASE(dm_test_irq_schedule_work_requeue_fallback), + /* amdgpu_dm_set_hpd_irq_state */ + KUNIT_CASE(dm_test_set_hpd_irq_state_null_dc), + /* amdgpu_dm_set_dmub_outbox_irq_state */ + KUNIT_CASE(dm_test_set_dmub_outbox_irq_state_null_dc), + /* amdgpu_dm_set_dmub_trace_irq_state */ + KUNIT_CASE(dm_test_set_dmub_trace_irq_state_null_dc), + /* amdgpu_dm_outbox_init */ + KUNIT_CASE(dm_test_outbox_init_null_dc), + /* amdgpu_dm_hpd_init/amdgpu_dm_hpd_fini */ + KUNIT_CASE(dm_test_hpd_init_empty_connectors), + KUNIT_CASE(dm_test_hpd_fini_empty_connectors), + KUNIT_CASE(dm_test_hpd_init_fini_with_connectors), + KUNIT_CASE(dm_test_hpd_init_fini_analog_connector), + KUNIT_CASE(dm_test_hpd_init_fini_irq_ref), + /* dm_handle_hpd_rx_offload_work */ + KUNIT_CASE(dm_test_hpd_rx_offload_work_no_connector), + KUNIT_CASE(dm_test_hpd_rx_offload_work_no_connection), + KUNIT_CASE(dm_test_hpd_rx_offload_work_automated_test), + KUNIT_CASE(dm_test_hpd_rx_offload_work_link_loss), + /* amdgpu_dm_hdmi_hpd_debounce_work */ + KUNIT_CASE(dm_test_hdmi_hpd_debounce_detect_false), + KUNIT_CASE(dm_test_hdmi_hpd_debounce_reallow_idle), + /* handle_hpd_irq/handle_hpd_irq_helper */ + KUNIT_CASE(dm_test_handle_hpd_irq_disabled), + KUNIT_CASE(dm_test_handle_hpd_irq_helper_debounce_schedule), + KUNIT_CASE(dm_test_handle_hpd_irq_helper_debounce_release_prev), + KUNIT_CASE(dm_test_handle_hpd_irq_helper_detect_false), + /* handle_hpd_rx_irq/schedule_hpd_rx_offload_work */ + KUNIT_CASE(dm_test_handle_hpd_rx_irq_disabled), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_no_left_work), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_automated_test), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_msg_rdy), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_link_loss), + KUNIT_CASE(dm_test_schedule_hpd_rx_offload_work), + /* dmub_hpd_callback/dmub_hpd_sense_callback */ + KUNIT_CASE(dm_test_dmub_hpd_callback_null_inputs), + KUNIT_CASE(dm_test_dmub_hpd_callback_invalid_index), + KUNIT_CASE(dm_test_dmub_hpd_callback_empty_connectors), + KUNIT_CASE(dm_test_dmub_hpd_callback_unknown_type_match), + KUNIT_CASE(dm_test_dmub_hpd_callback_hpd_type), + KUNIT_CASE(dm_test_dmub_hpd_callback_hpd_irq_type), + /* amdgpu_dm_register_hpd_handlers */ + KUNIT_CASE(dm_test_register_hpd_handlers_empty), + KUNIT_CASE(dm_test_register_hpd_handlers_valid), + KUNIT_CASE(dm_test_register_hpd_handlers_invalid_hpd), + KUNIT_CASE(dm_test_register_hpd_handlers_invalid_hpd_rx), + KUNIT_CASE(dm_test_register_hpd_handlers_dmub_outbox), + /* pflip/vupdate/crtc high IRQ callbacks */ + KUNIT_CASE(dm_test_pflip_high_irq_no_crtc), + KUNIT_CASE(dm_test_pflip_high_irq_not_submitted), + KUNIT_CASE(dm_test_vupdate_high_irq_no_crtc), + KUNIT_CASE(dm_test_crtc_high_irq_no_crtc), + KUNIT_CASE(dm_test_crtc_high_irq_vrr_pre_ai), + KUNIT_CASE(dm_test_crtc_high_irq_vrr_ai_no_stream), + /* dm_handle_hpd_work */ + KUNIT_CASE(dm_test_handle_hpd_work_out_of_range), + /* dm_dmub_outbox1_low_irq */ + KUNIT_CASE(dm_test_dmub_outbox1_low_irq_empty), + KUNIT_CASE(dm_test_dmub_outbox1_low_irq_no_handler), + KUNIT_CASE(dm_test_dmub_outbox1_low_irq_direct_callback), + KUNIT_CASE(dm_test_dmub_outbox1_low_irq_offload), + /* IRQ handler registration helpers */ + KUNIT_CASE(dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources), + KUNIT_CASE(dm_test_dce110_register_irq_handlers_one_crtc), + KUNIT_CASE(dm_test_dcn10_register_irq_handlers_zero_crtc), + KUNIT_CASE(dm_test_dcn10_register_irq_handlers_one_crtc), + KUNIT_CASE(dm_test_register_outbox_irq_handlers_without_dmub), + KUNIT_CASE(dm_test_register_outbox_irq_handlers_with_dmub), + /* amdgpu_dm_irq_handler */ + KUNIT_CASE(dm_test_irq_handler_dispatches_work), + /* dm_handle_vmin_vmax_update */ + KUNIT_CASE(dm_test_handle_vmin_vmax_update), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c index 7dfb3b351d20..6394b967ac3a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c @@ -8,6 +8,9 @@ #include #include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_ism.h" #include "amdgpu_dm_kunit_test_helpers.h" @@ -848,6 +851,345 @@ static void dm_test_dispatch_next_event_no_action_state(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); } +/* + * Helper: allocate an amdgpu_crtc (which embeds the ISM) wired up to a + * minimally-populated amdgpu_device so that the container_of()/drm_to_adev() + * lookups inside the ISM event machinery resolve correctly. + * + * The amdgpu_device is large, so it must be heap-allocated via kunit_kzalloc. + * acrtc->base.dev points at the embedded &adev->ddev, which is what + * drm_to_adev() expects (it is a container_of of ddev). + */ +static struct amdgpu_crtc *alloc_test_acrtc(struct kunit *test, + struct amdgpu_device **adev_out) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + struct dc *dc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + mutex_init(&adev->dm.dc_lock); + + acrtc->base.dev = &adev->ddev; + + if (adev_out) + *adev_out = adev; + + return acrtc; +} + +/* + * Helper: register an already-allocated amdgpu_crtc into the DRM device's + * crtc_list so that drm_for_each_crtc() iterates it. Only the list linkage is + * required for the ISM enable/disable/force-full-power helpers. + */ +static void register_test_acrtc(struct amdgpu_device *adev, + struct amdgpu_crtc *acrtc) +{ + INIT_LIST_HEAD(&adev->ddev.mode_config.crtc_list); + INIT_LIST_HEAD(&acrtc->base.head); + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); +} + +/* ===== Tests for amdgpu_dm_ism_commit_event ===== */ + +/** + * dm_test_ism_commit_event_no_state - commit_event returns early without a crtc state + * @test: KUnit test context + * + * When the CRTC has no atomic state (base.state == NULL) the function takes + * the NO_STATE early-return path and the FSM is left untouched. + */ +static void dm_test_ism_commit_event_no_state(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct amdgpu_dm_ism_config config = { 0 }; + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = NULL; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_commit_event_cursor_transition - cursor begin/end drive FSM without DC work + * @test: KUnit test context + * + * BEGIN_CURSOR_UPDATE then END_CURSOR_UPDATE from FULL_POWER_RUNNING traverse + * the FULL_POWER_BUSY state. Neither transition reaches the idle-optimization + * commit path, so no DC hardware access occurs and the FSM returns to + * FULL_POWER_RUNNING. + */ +static void dm_test_ism_commit_event_cursor_transition(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_BUSY); + + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_END_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_commit_event_invalid_event - invalid event leaves FSM unchanged + * @test: KUnit test context + * + * EXIT_IDLE_REQUESTED is not a valid event from FULL_POWER_RUNNING, so the + * FSM does not transition and no power-state dispatch occurs. + */ +static void dm_test_ism_commit_event_invalid_event(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for amdgpu_dm_ism_force_full_power ===== */ + +/** + * dm_test_ism_force_full_power - force-full-power sends EXIT_IDLE to every CRTC + * @test: KUnit test context + * + * From the initial FULL_POWER_RUNNING state EXIT_IDLE_REQUESTED is a no-op, so + * the FSM remains in FULL_POWER_RUNNING and no DC work is scheduled. + */ +static void dm_test_ism_force_full_power(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + register_test_acrtc(adev, acrtc); + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_force_full_power(&adev->dm); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for amdgpu_dm_ism_disable / amdgpu_dm_ism_enable ===== */ + +/** + * dm_test_ism_disable_enable_cycle - disable then enable quiesces and re-arms work + * @test: KUnit test context + * + * Walks every CRTC's ISM, disabling its delayed work (synchronously) and then + * re-enabling it. With no work ever scheduled both calls complete without + * touching the FSM state. disable must be called without dc_lock held. + */ +static void dm_test_ism_disable_enable_cycle(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct amdgpu_dm_ism_config config = { 0 }; + + amdgpu_dm_ism_init(&acrtc->ism, &config); + register_test_acrtc(adev, acrtc); + + amdgpu_dm_ism_disable(&adev->dm); + amdgpu_dm_ism_enable(&adev->dm); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for dm_ism_dispatch_power_state (via commit_event) ===== */ + +/* + * Build a config + history that makes dm_ism_get_idle_allow_delay() return a + * non-zero hysteresis delay. A non-zero delay keeps the FSM parked in + * HYSTERESIS_WAITING (the dispatcher returns DM_ISM_NUM_EVENTS instead of an + * immediate follow-up event), preventing the cascade into the DC-dependent + * OPTIMIZED_IDLE / *_SSO states. + */ +static void setup_idle_delay_history(struct amdgpu_dm_ism *ism, + struct dc_stream_state *stream) +{ + uint64_t one_frame_ns; + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, 1485000); + + for (int i = 0; i < 8; i++) { + ism->records[i].duration_ns = one_frame_ns; + ism->records[i].timestamp_ns = 0; + } + ism->next_record_idx = 8; +} + +/** + * dm_test_ism_dispatch_hysteresis_schedule_and_cancel - cover the HYSTERESIS_WAITING dispatch arms + * @test: KUnit test context + * + * ENTER_IDLE_REQUESTED moves FULL_POWER_RUNNING -> HYSTERESIS_WAITING. The + * current-state arm of dm_ism_dispatch_power_state() then records the idle + * timestamp, computes a (non-zero) idle-allow delay and schedules the delayed + * worker. A subsequent BEGIN_CURSOR_UPDATE (-> HYSTERESIS_BUSY) exercises the + * previous-state arm that cancels that pending worker. Neither arm reaches the + * DC-dependent idle-optimization commit path. + */ +static void dm_test_ism_dispatch_hysteresis_schedule_and_cancel(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct dc_stream_state *stream; + struct amdgpu_dm_ism_config config = { + .filter_num_frames = 5, + .filter_entry_count = 3, + .activation_num_delay_frames = 10, + .filter_history_size = 8, + .filter_old_history_threshold = 0, + .sso_num_frames = 0, + }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + stream = dm_kunit_alloc_stream(test, NULL); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + setup_idle_delay_history(&acrtc->ism, stream); + dm_state->stream = stream; + acrtc->base.state = &dm_state->base; + + scoped_guard(mutex, &adev->dm.dc_lock) { + /* Enter HYSTERESIS_WAITING: schedules the idle-allow worker. */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_WAITING); + + /* Cursor update cancels the pending worker (prev-state arm). */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_BUSY); + } + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_dispatch_optimized_idle_defers_sso - cover OPTIMIZED_IDLE dispatch without DC + * @test: KUnit test context + * + * With sso_num_frames < filter_num_frames the OPTIMIZED_IDLE current-state arm + * skips the idle-optimization commit and only schedules the SSO worker. Driving + * HYSTERESIS_WAITING -> OPTIMIZED_IDLE via TIMER_ELAPSED exercises that arm + * (get_sso_delay + the skip branch + mod_delayed_work) without any DC access. + */ +static void dm_test_ism_dispatch_optimized_idle_defers_sso(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct dc_stream_state *stream; + struct amdgpu_dm_ism_config config = { + .filter_num_frames = 5, + .filter_entry_count = 3, + .activation_num_delay_frames = 10, + .filter_history_size = 8, + .filter_old_history_threshold = 0, + .sso_num_frames = 2, + }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + stream = dm_kunit_alloc_stream(test, NULL); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + setup_idle_delay_history(&acrtc->ism, stream); + dm_state->stream = stream; + acrtc->base.state = &dm_state->base; + + scoped_guard(mutex, &adev->dm.dc_lock) { + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_WAITING); + + /* + * Timer fires: HYSTERESIS_WAITING -> OPTIMIZED_IDLE. sso_delay + * is non-zero and sso_num_frames < filter_num_frames, so the + * commit is deferred to the SSO worker and the FSM parks here. + */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_TIMER_ELAPSED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_OPTIMIZED_IDLE); + + /* Cancel the scheduled SSO worker while still holding dc_lock. */ + cancel_delayed_work(&acrtc->ism.sso_delayed_work); + } + + amdgpu_dm_ism_fini(&acrtc->ism); +} + static struct kunit_case dm_ism_test_cases[] = { /* dm_ism_next_state — FULL_POWER_RUNNING */ KUNIT_CASE(dm_test_ism_next_state_running_enter_idle), @@ -910,6 +1252,17 @@ static struct kunit_case dm_ism_test_cases[] = { KUNIT_CASE(dm_test_dispatch_next_event_opt_idle_with_sso_delay), KUNIT_CASE(dm_test_dispatch_next_event_timer_aborted), KUNIT_CASE(dm_test_dispatch_next_event_no_action_state), + /* amdgpu_dm_ism_commit_event */ + KUNIT_CASE(dm_test_ism_commit_event_no_state), + KUNIT_CASE(dm_test_ism_commit_event_cursor_transition), + KUNIT_CASE(dm_test_ism_commit_event_invalid_event), + /* amdgpu_dm_ism_force_full_power */ + KUNIT_CASE(dm_test_ism_force_full_power), + /* amdgpu_dm_ism_disable / amdgpu_dm_ism_enable */ + KUNIT_CASE(dm_test_ism_disable_enable_cycle), + /* dm_ism_dispatch_power_state (via commit_event) */ + KUNIT_CASE(dm_test_ism_dispatch_hysteresis_schedule_and_cancel), + KUNIT_CASE(dm_test_ism_dispatch_optimized_idle_defers_sso), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c index 58615cdbe854..862ada873fbb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "dc.h" #include "core_types.h" @@ -45,23 +46,32 @@ struct dc_link *dm_kunit_alloc_link(struct kunit *test) } EXPORT_SYMBOL(dm_kunit_alloc_link); -struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test) +struct dc *dm_kunit_alloc_dc_with_ctx(struct kunit *test) { - struct dc_link *link; struct dc_context *ctx; struct dc *dc; - link = dm_kunit_alloc_link(test); - - ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx); - dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, dc); - link->ctx = ctx; - ctx->dc = dc; + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + dc->ctx = ctx; + ctx->dc = dc; + + return dc; +} +EXPORT_SYMBOL(dm_kunit_alloc_dc_with_ctx); + +struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test) +{ + struct dc_link *link; + struct dc *dc; + + link = dm_kunit_alloc_link(test); + dc = dm_kunit_alloc_dc_with_ctx(test); + link->ctx = dc->ctx; return link; } @@ -127,7 +137,7 @@ struct amdgpu_dm_connector *dm_kunit_alloc_connector(struct kunit *test, { struct amdgpu_dm_connector *aconnector; - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aconnector = drmm_kzalloc(adev_to_drm(adev), sizeof(*aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, aconnector); if (adev) @@ -138,5 +148,19 @@ struct amdgpu_dm_connector *dm_kunit_alloc_connector(struct kunit *test, } EXPORT_SYMBOL(dm_kunit_alloc_connector); +struct drm_device *dm_kunit_alloc_drm_with_connector_list(struct kunit *test) +{ + struct drm_device *dev; + + dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + INIT_LIST_HEAD(&dev->mode_config.connector_list); + spin_lock_init(&dev->mode_config.connector_list_lock); + + return dev; +} +EXPORT_SYMBOL(dm_kunit_alloc_drm_with_connector_list); + MODULE_LICENSE("Dual MIT/GPL"); MODULE_DESCRIPTION("KUnit test helpers for amdgpu_dm tests"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h index 0f1c48fa2128..49b358681d60 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h @@ -13,14 +13,18 @@ struct amdgpu_device; struct amdgpu_display_manager; struct amdgpu_dm_connector; +struct dc; struct dc_link; struct dc_state; struct dc_stream_state; +struct drm_device; struct amdgpu_device *dm_kunit_alloc_adev(struct kunit *test); +struct dc *dm_kunit_alloc_dc_with_ctx(struct kunit *test); struct dc_link *dm_kunit_alloc_link(struct kunit *test); struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test); struct amdgpu_display_manager *dm_kunit_alloc_dm(struct kunit *test); +struct drm_device *dm_kunit_alloc_drm_with_connector_list(struct kunit *test); struct dc_stream_state *dm_kunit_alloc_stream(struct kunit *test, struct dc_link *link); void dm_kunit_add_stream_to_state(struct kunit *test, struct dc_state *state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index 3a663ee0ca2b..caedad4197bd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -8,7 +8,11 @@ #include #include +#include #include +#include +#include +#include #include #include #include @@ -31,7 +35,9 @@ static u8 dm_mst_test_dpcd[0x10]; static u8 dm_mst_test_desc_dpcd[0x10]; static struct aux_payload dm_mst_test_last_payload; static int dm_mst_test_aux_transfer_raw_result; +static u8 dm_mst_test_aux_transfer_raw_reply; static enum aux_return_code_type dm_mst_test_aux_transfer_raw_operation_result; +static ssize_t dm_mst_test_aux_transfer_override; static int dm_mst_test_aux_transfer_raw(struct ddc_service *ddc, struct aux_payload *payload, @@ -41,6 +47,7 @@ static int dm_mst_test_aux_transfer_raw(struct ddc_service *ddc, dm_mst_test_last_payload = *payload; *operation_result = dm_mst_test_aux_transfer_raw_operation_result; + payload->reply[0] = dm_mst_test_aux_transfer_raw_reply; if (dm_mst_test_aux_transfer_raw_result) return dm_mst_test_aux_transfer_raw_result; @@ -64,6 +71,7 @@ static void dm_mst_test_setup_dm_aux(struct amdgpu_dm_dp_aux *dm_aux, { memset(&dm_mst_test_last_payload, 0, sizeof(dm_mst_test_last_payload)); dm_mst_test_aux_transfer_raw_result = 0; + dm_mst_test_aux_transfer_raw_reply = 0; dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_SUCCESS; link_srv->aux_transfer_raw = dm_mst_test_aux_transfer_raw; dc->link_srv = link_srv; @@ -87,6 +95,11 @@ static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) { size_t i; + ssize_t ret; + + ret = dm_mst_test_aux_transfer_override; + if (ret) + return ret; switch (msg->request & ~DP_AUX_I2C_MOT) { case DP_AUX_NATIVE_READ: @@ -103,6 +116,53 @@ static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, } } +static struct amdgpu_dm_connector *dm_mst_test_alloc_sideband_connector(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct link_service *link_srv; + struct dc_link *link; + struct dc *dc; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + + mutex_init(&aconnector->handle_mst_msg_ready); + link_srv->get_status = dm_mst_test_get_status; + dc->link_srv = link_srv; + link->dc = dc; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->link_status.dpcd_caps = &link->dpcd_caps; + aconnector->dc_link = link; + aconnector->dm_dp_aux.aux.name = "dm_mst_test_sideband_aux"; + aconnector->dm_dp_aux.aux.transfer = dm_mst_test_aux_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + drm_dp_dpcd_set_probe(&aconnector->dm_dp_aux.aux, false); + + memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + dm_mst_test_aux_transfer_override = 0; + + return aconnector; +} + +static uint32_t dm_mst_test_dp_link_bandwidth_kbps( + const struct dc_link *link, + const struct dc_link_settings *link_settings) +{ + return 4320000; +} + +static const struct dc_link_settings *dm_mst_test_dp_get_verified_link_cap( + const struct dc_link *link) +{ + return &link->verified_link_cap; +} + static ssize_t dm_mst_test_desc_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) { @@ -254,6 +314,35 @@ static void dm_mst_test_pbn_divider_null_link(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_mst_get_pbn_divider(NULL), 0U); } +/** + * dm_mst_test_pbn_divider_uses_link_bandwidth - Test pbn_divider with link cap + * @test: KUnit test context + * + * Verify that dm_mst_get_pbn_divider() uses the DC link service to derive the + * fixed-point PBN divider when a link is present. + */ +static void dm_mst_test_pbn_divider_uses_link_bandwidth(struct kunit *test) +{ + struct link_service *link_srv; + struct dc_link *link; + struct dc *dc; + + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link_srv->dp_get_verified_link_cap = dm_mst_test_dp_get_verified_link_cap; + link_srv->dp_link_bandwidth_kbps = dm_mst_test_dp_link_bandwidth_kbps; + dc->link_srv = link_srv; + link->dc = dc; + + KUNIT_EXPECT_EQ(test, dm_mst_get_pbn_divider(link), + (uint32_t)(dfixed_const(1000) / 100)); +} + /* Tests for amdgpu_dm_mst_reset_mst_connector_setting */ /** @@ -331,6 +420,7 @@ static void dm_mst_test_retrieve_downstream_present(struct kunit *test) memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); /* PORT_PRESENT = 1, PORT_TYPE = 2 (0b101) */ dm_mst_test_dpcd[DP_DOWNSTREAMPORT_PRESENT] = 0x05; + dm_mst_test_aux_transfer_override = 0; aux->name = "dm_mst_test_aux"; aux->transfer = dm_mst_test_aux_transfer; @@ -345,6 +435,35 @@ static void dm_mst_test_retrieve_downstream_present(struct kunit *test) (int)aconnector->mst_downstream_port_present.fields.PORT_TYPE, 2); } +/** + * dm_mst_test_retrieve_downstream_aux_error - Test downstream read failure + * @test: KUnit test context + * + * Verify that retrieve_downstream_port_device() returns false when the AUX + * DPCD read fails. + */ +static void dm_mst_test_retrieve_downstream_aux_error(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, aux); + + dm_mst_test_aux_transfer_override = -EIO; + aux->name = "dm_mst_test_aux"; + aux->transfer = dm_mst_test_aux_transfer; + drm_dp_aux_init(aux); + drm_dp_dpcd_set_probe(aux, false); + aconnector->dsc_aux = aux; + + KUNIT_EXPECT_FALSE(test, retrieve_downstream_port_device(aconnector)); + + dm_mst_test_aux_transfer_override = 0; +} + /* Tests for retrieve_branch_specific_data */ /** @@ -710,6 +829,55 @@ static void dm_mst_test_aux_transfer_hpd_discon_quirk(struct kunit *test) DP_SIDEBAND_MSG_DOWN_REQ_BASE); } +/** + * dm_mst_test_aux_transfer_non_ack_reply - non-ACK AUX reply is logged. + * @test: KUnit test context. + * + * A successful read with a nonzero reply byte should still return the backend + * byte count while exercising the non-ACK reply handling path. + */ +static void dm_mst_test_aux_transfer_non_ack_reply(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 0 }; + struct drm_dp_aux_msg msg = { + .address = 4, + .request = DP_AUX_NATIVE_READ, + .buffer = buffer, + .size = sizeof(buffer), + }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + dm_mst_test_aux_transfer_raw_reply = DP_AUX_NATIVE_REPLY_NACK; + + ret = dm_dp_aux_transfer(&dm_aux->aux, &msg); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer)); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 4U); +} + /** * dm_mst_test_fill_payload_flags_native_write - native write request decode. * @test: KUnit test context. @@ -843,31 +1011,8 @@ static void dm_mst_test_select_esi_dpcd_esi(struct kunit *test) static void dm_mst_test_sideband_msg_ready_no_ready_bits(struct kunit *test) { struct amdgpu_dm_connector *aconnector; - struct link_service *link_srv; - struct dc_link *link; - struct dc *dc; - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); - link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); - link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, aconnector); - KUNIT_ASSERT_NOT_NULL(test, link_srv); - KUNIT_ASSERT_NOT_NULL(test, link); - KUNIT_ASSERT_NOT_NULL(test, dc); - - mutex_init(&aconnector->handle_mst_msg_ready); - link_srv->get_status = dm_mst_test_get_status; - dc->link_srv = link_srv; - link->dc = dc; - link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link->link_status.dpcd_caps = &link->dpcd_caps; - aconnector->dc_link = link; - aconnector->dm_dp_aux.aux.name = "dm_mst_test_sideband_aux"; - aconnector->dm_dp_aux.aux.transfer = dm_mst_test_aux_transfer; - drm_dp_aux_init(&aconnector->dm_dp_aux.aux); - drm_dp_dpcd_set_probe(&aconnector->dm_dp_aux.aux, false); - memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + aconnector = dm_mst_test_alloc_sideband_connector(test); dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_REP_MSG_RDY_EVENT); @@ -875,6 +1020,185 @@ static void dm_mst_test_sideband_msg_ready_no_ready_bits(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[1], (u8)0); } +/** + * dm_mst_test_sideband_msg_ready_read_error - Test ESI read failure path + * @test: KUnit test context + * + * Verify that dm_handle_mst_sideband_msg_ready_event() returns cleanly when + * the DPCD read fails before a ready bit can be handled. + */ +static void dm_mst_test_sideband_msg_ready_read_error(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_mst_test_alloc_sideband_connector(test); + dm_mst_test_aux_transfer_override = -EIO; + + dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, + DOWN_REP_MSG_RDY_EVENT); + + KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[1], (u8)0); + dm_mst_test_aux_transfer_override = 0; +} + +/** + * dm_mst_test_sideband_msg_ready_without_mst_state - Test ready bit no-op path + * @test: KUnit test context + * + * Verify that a DOWN_REP ready bit is filtered and then ignored when the MST + * topology manager is not enabled. + */ +static void dm_mst_test_sideband_msg_ready_without_mst_state(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_mst_test_alloc_sideband_connector(test); + dm_mst_test_dpcd[(DP_SINK_COUNT_ESI + 1) & 0xf] = DP_DOWN_REP_MSG_RDY; + + dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, + DOWN_REP_MSG_RDY_EVENT); + + KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[(DP_SINK_COUNT_ESI + 1) & 0xf], + DP_DOWN_REP_MSG_RDY); +} + +/** + * dm_mst_test_down_rep_msg_ready_wrapper - Test DOWN_REP wrapper + * @test: KUnit test context + * + * Verify that dm_handle_mst_down_rep_msg_ready() forwards to the generic MST + * sideband handler with the DOWN_REP event selection. + */ +static void dm_mst_test_down_rep_msg_ready_wrapper(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = dm_mst_test_alloc_sideband_connector(test); + + dm_handle_mst_down_rep_msg_ready(&aconnector->mst_mgr); + + KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[1], (u8)0); +} + +/** + * dm_mst_test_initialize_dp_connector_edp - Test eDP initialization path + * @test: KUnit test context + * + * Verify that amdgpu_dm_initialize_dp_connector() initializes the DP AUX state + * and exits before MST topology setup for eDP connectors. + */ +static void dm_mst_test_initialize_dp_connector_edp(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + link = dm_kunit_alloc_link(test); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc); + + adev->dm.adev = adev; + adev->dm.ddev = &adev->ddev; + link->ddc = ddc; + aconnector = dm_kunit_alloc_connector(test, adev, link); + aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + + amdgpu_dm_initialize_dp_connector(&adev->dm, aconnector, 5); + + KUNIT_EXPECT_TRUE(test, aconnector->dm_dp_aux.aux.transfer == dm_dp_aux_transfer); + KUNIT_EXPECT_PTR_EQ(test, aconnector->dm_dp_aux.aux.drm_dev, &adev->ddev); + KUNIT_EXPECT_PTR_EQ(test, aconnector->dm_dp_aux.ddc_service, ddc); + KUNIT_EXPECT_PTR_EQ(test, aconnector->mst_mgr.dev, NULL); + KUNIT_EXPECT_NOT_NULL(test, aconnector->dm_dp_aux.aux.name); + if (aconnector->dm_dp_aux.aux.name) + KUNIT_EXPECT_NOT_NULL(test, strstr(aconnector->dm_dp_aux.aux.name, "5")); + + drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux); + kfree(aconnector->dm_dp_aux.aux.name); +} + +static bool dm_mst_test_dp_get_max_link_enc_cap(const struct dc_link *link, + struct dc_link_settings *cap) +{ + return true; +} + +static void dm_mst_test_connector_destroy(struct drm_connector *connector) +{ +} + +static const struct drm_connector_funcs dm_mst_test_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .destroy = dm_mst_test_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +/** + * dm_mst_test_initialize_dp_connector_mst - Test MST root initialization path + * @test: KUnit test context + * + * Verify that amdgpu_dm_initialize_dp_connector() initializes the MST topology + * manager for a non-eDP DisplayPort connector. This exercises the path past the + * eDP early return, including dc_link_dp_get_max_link_enc_cap() and + * drm_dp_mst_topology_mgr_init(). A fully initialized DRM mode config and + * connector are required because the topology manager registers a private + * atomic object and the subconnector property is attached to the connector. + */ +static void dm_mst_test_initialize_dp_connector_mst(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct link_service *link_srv; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + int ret; + + adev = dm_kunit_alloc_adev(test); + + ret = drmm_mode_config_init(&adev->ddev); + KUNIT_ASSERT_EQ(test, ret, 0); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = dm_kunit_alloc_link(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + + link_srv->dp_get_max_link_enc_cap = dm_mst_test_dp_get_max_link_enc_cap; + dc->link_srv = link_srv; + link->dc = dc; + link->ddc = ddc; + + adev->dm.adev = adev; + adev->dm.ddev = &adev->ddev; + + aconnector = dm_kunit_alloc_connector(test, adev, link); + + ret = drm_connector_init(&adev->ddev, &aconnector->base, + &dm_mst_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + KUNIT_ASSERT_EQ(test, ret, 0); + + amdgpu_dm_initialize_dp_connector(&adev->dm, aconnector, 7); + + KUNIT_EXPECT_TRUE(test, aconnector->dm_dp_aux.aux.transfer == dm_dp_aux_transfer); + KUNIT_EXPECT_PTR_EQ(test, aconnector->mst_mgr.dev, &adev->ddev); + KUNIT_EXPECT_PTR_EQ(test, aconnector->mst_mgr.aux, &aconnector->dm_dp_aux.aux); + KUNIT_EXPECT_EQ(test, aconnector->mst_mgr.max_payloads, 4); + KUNIT_EXPECT_TRUE(test, aconnector->mst_mgr.cbs != NULL); + + drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr); + drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux); + kfree(aconnector->dm_dp_aux.aux.name); + drm_connector_cleanup(&aconnector->base); +} + /** * dm_mst_test_atomic_best_encoder - Test MST encoder selection * @test: KUnit test context @@ -1015,6 +1339,7 @@ static void dm_mst_test_detect_unregistered(struct kunit *test) (int)connector_status_disconnected); } +#if !defined(CONFIG_DRM_AMD_DC_FP) /** * dm_mst_test_fp_guarded_public_stubs - Test FP-off public fallbacks * @test: KUnit test context @@ -1027,6 +1352,7 @@ static void dm_mst_test_fp_guarded_public_stubs(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_dp_mst_is_port_support_mode(NULL, NULL), (enum dc_status)DC_OK); } +#endif static struct kunit_case dm_mst_types_test_cases[] = { /* needs_dsc_aux_workaround tests */ @@ -1038,11 +1364,13 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_zero_sink_count), /* dm_mst_get_pbn_divider tests */ KUNIT_CASE(dm_mst_test_pbn_divider_null_link), + KUNIT_CASE(dm_mst_test_pbn_divider_uses_link_bandwidth), /* amdgpu_dm_mst_reset_mst_connector_setting tests */ KUNIT_CASE(dm_mst_test_reset_connector_setting), /* retrieve_downstream_port_device tests */ KUNIT_CASE(dm_mst_test_retrieve_downstream_no_aux), KUNIT_CASE(dm_mst_test_retrieve_downstream_present), + KUNIT_CASE(dm_mst_test_retrieve_downstream_aux_error), /* retrieve_branch_specific_data tests */ KUNIT_CASE(dm_mst_test_retrieve_branch_no_parent), KUNIT_CASE(dm_mst_test_retrieve_branch_reads_oui), @@ -1056,6 +1384,7 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_aux_transfer_partial_write), KUNIT_CASE(dm_mst_test_aux_transfer_error_result), KUNIT_CASE(dm_mst_test_aux_transfer_hpd_discon_quirk), + KUNIT_CASE(dm_mst_test_aux_transfer_non_ack_reply), /* dm_dp_aux_fill_payload_flags tests */ KUNIT_CASE(dm_mst_test_fill_payload_flags_native_write), KUNIT_CASE(dm_mst_test_fill_payload_flags_native_read), @@ -1068,6 +1397,12 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_select_esi_dpcd_esi), /* dm_handle_mst_sideband_msg_ready_event tests */ KUNIT_CASE(dm_mst_test_sideband_msg_ready_no_ready_bits), + KUNIT_CASE(dm_mst_test_sideband_msg_ready_read_error), + KUNIT_CASE(dm_mst_test_sideband_msg_ready_without_mst_state), + KUNIT_CASE(dm_mst_test_down_rep_msg_ready_wrapper), + /* amdgpu_dm_initialize_dp_connector tests */ + KUNIT_CASE(dm_mst_test_initialize_dp_connector_edp), + KUNIT_CASE(dm_mst_test_initialize_dp_connector_mst), /* dm_mst_atomic_best_encoder tests */ KUNIT_CASE(dm_mst_test_atomic_best_encoder), /* dm_dp_create_fake_mst_encoders tests */ @@ -1077,7 +1412,9 @@ static struct kunit_case dm_mst_types_test_cases[] = { /* dm_dp_mst_detect tests */ KUNIT_CASE(dm_mst_test_detect_unregistered), /* CONFIG_DRM_AMD_DC_FP disabled public paths */ +#if !defined(CONFIG_DRM_AMD_DC_FP) KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs), +#endif {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index 46c9af432e37..ba97092c7bb8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -5,13 +5,16 @@ * Copyright 2026 Advanced Micro Devices, Inc. */ - #include - #include - #include "link_enc_cfg.h" - #include "amdgpu_dm_plane.h" - #include - #include - +#include +#include +#include +#include "link_enc_cfg.h" +#include "amdgpu_dm_plane.h" +#include "amdgpu_rlc.h" +#include "gc/gc_11_0_0_offset.h" +#include "gc/gc_11_0_0_sh_mask.h" +#include +#include struct dm_test_dcc_cap_ctx { bool callback_ret; @@ -23,6 +26,39 @@ struct dm_test_dcc_cap_ctx { static struct dm_test_dcc_cap_ctx *dm_test_dcc_ctx; +struct dm_test_gfx11_reg_ctx { + u32 gb_addr_config; + u32 gc_reg_offsets[1]; + u32 expected_reg; + u32 captured_reg; + u32 captured_acc_flags; + u32 captured_hwip; + u32 captured_xcc_id; + bool called; +}; + +static struct dm_test_gfx11_reg_ctx *dm_test_gfx11_reg_ctx; + +static u32 dm_test_gfx11_rreg32(struct amdgpu_device *adev, + u32 reg, u32 acc_flags, u32 hwip, + u32 xcc_id) +{ + if (!dm_test_gfx11_reg_ctx) + return 0; + + dm_test_gfx11_reg_ctx->called = true; + dm_test_gfx11_reg_ctx->captured_reg = reg; + dm_test_gfx11_reg_ctx->captured_acc_flags = acc_flags; + dm_test_gfx11_reg_ctx->captured_hwip = hwip; + dm_test_gfx11_reg_ctx->captured_xcc_id = xcc_id; + + return dm_test_gfx11_reg_ctx->gb_addr_config; +} + +static const struct amdgpu_rlc_reg_funcs dm_test_gfx11_reg_funcs = { + .rreg32 = dm_test_gfx11_rreg32, +}; + static bool dm_test_get_dcc_compression_cap(const struct dc *dc, const struct dc_dcc_surface_param *input, struct dc_surface_dcc_cap *output) @@ -292,12 +328,6 @@ static void dm_test_get_plane_modifiers(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, adev); - adev->family = AMDGPU_FAMILY_SI; - KUNIT_EXPECT_EQ(test, - amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_PRIMARY, &mods), - 0); - KUNIT_EXPECT_PTR_EQ(test, mods, NULL); - adev->family = AMDGPU_FAMILY_NV; KUNIT_ASSERT_EQ(test, amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_CURSOR, &mods), @@ -376,62 +406,6 @@ static void dm_test_get_min_max_dc_plane_scaling(struct kunit *test) KUNIT_EXPECT_EQ(test, max_upscale, 1600); } -/** - * dm_test_fill_plane_buffer_attributes_gfx8() - Verify graphics path and GFX8 tiling fill. - * @test: KUnit test context. - * - * Verify if GFX8 plane buffer attributes and tiling fields are filled correctly. - */ -static void dm_test_fill_plane_buffer_attributes_gfx8(struct kunit *test) -{ - struct amdgpu_device *adev; - struct amdgpu_framebuffer *afb; - struct dc_tiling_info *tiling_info; - struct plane_size *plane_size; - struct dc_plane_dcc_param *dcc; - struct dc_plane_address *address; - uint64_t tiling_flags = 0; - int ret; - - adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); - tiling_info = kunit_kzalloc(test, sizeof(*tiling_info), GFP_KERNEL); - plane_size = kunit_kzalloc(test, sizeof(*plane_size), GFP_KERNEL); - dcc = kunit_kzalloc(test, sizeof(*dcc), GFP_KERNEL); - address = kunit_kzalloc(test, sizeof(*address), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, adev); - KUNIT_ASSERT_NOT_NULL(test, afb); - KUNIT_ASSERT_NOT_NULL(test, tiling_info); - KUNIT_ASSERT_NOT_NULL(test, plane_size); - KUNIT_ASSERT_NOT_NULL(test, dcc); - KUNIT_ASSERT_NOT_NULL(test, address); - - adev->family = AMDGPU_FAMILY_SI; - afb->address = 0x12345000ULL; - afb->base.width = 1920; - afb->base.height = 1080; - afb->base.offsets[0] = 0x1000; - afb->base.pitches[0] = 7680; - afb->base.format = drm_format_info(DRM_FORMAT_XRGB8888); - KUNIT_ASSERT_NOT_NULL(test, afb->base.format); - - tiling_flags |= AMDGPU_TILING_SET(ARRAY_MODE, DC_ARRAY_1D_TILED_THIN1); - tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 5); - - ret = amdgpu_dm_plane_fill_plane_buffer_attributes(adev, afb, - SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, - tiling_flags, tiling_info, plane_size, dcc, address, true); - - KUNIT_EXPECT_EQ(test, ret, 0); - KUNIT_EXPECT_EQ(test, plane_size->surface_size.width, 1920); - KUNIT_EXPECT_EQ(test, plane_size->surface_size.height, 1080); - KUNIT_EXPECT_EQ(test, plane_size->surface_pitch, 1920); - KUNIT_EXPECT_EQ(test, address->type, (int)PLN_ADDR_TYPE_GRAPHICS); - KUNIT_EXPECT_TRUE(test, address->tmz_surface); - KUNIT_EXPECT_EQ(test, (int)tiling_info->gfx8.array_mode, (int)DC_ARRAY_1D_TILED_THIN1); - KUNIT_EXPECT_EQ(test, tiling_info->gfx8.pipe_config, 5U); -} - /** * dm_test_get_cursor_position() - Verify cursor clipping and off-screen handling. * @test: KUnit test context. @@ -579,7 +553,7 @@ static void dm_test_fill_gfx12_plane_attributes_from_modifiers(struct kunit *tes plane_size.surface_size.height = 1080; KUNIT_EXPECT_EQ(test, - amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers( + amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers( adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, &plane_size, &tiling_info, &dcc, &address), 0); @@ -623,7 +597,7 @@ static void dm_test_fill_gfx9_plane_attributes_from_modifiers(struct kunit *test afb->base.modifier = DRM_FORMAT_MOD_LINEAR; KUNIT_EXPECT_EQ(test, - amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers( + amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers( adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, &plane_size, &tiling_info, &dcc, &address), 0); @@ -749,6 +723,77 @@ static void dm_test_validate_dcc_missing_cap_func_fails(struct kunit *test) -EINVAL); } +/** + * dm_test_validate_dcc_cap_callback_fails() - Verify callback failure path. + * @test: KUnit test context. + * + * Verify if validation fails when the DCC capability callback returns false. + */ +static void dm_test_validate_dcc_cap_callback_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + enum surface_pixel_format format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; + enum dc_rotation_angle rotation = ROTATION_ANGLE_0; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = false, + .capable = true, + }; + int ret; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + ret = amdgpu_dm_plane_validate_dcc(adev, format, rotation, &tiling_info, + &dcc, &address, &plane_size); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_TRUE(test, ctx.called); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_validate_dcc_not_capable_fails() - Verify not-capable callback output. + * @test: KUnit test context. + * + * Verify if validation fails when the DCC capability callback reports that the + * surface is not DCC capable. + */ +static void dm_test_validate_dcc_not_capable_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + enum surface_pixel_format format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; + enum dc_rotation_angle rotation = ROTATION_ANGLE_0; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = false, + }; + int ret; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + ret = amdgpu_dm_plane_validate_dcc(adev, format, rotation, &tiling_info, + &dcc, &address, &plane_size); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_TRUE(test, ctx.called); + + dm_test_dcc_ctx = NULL; +} + /** * dm_test_validate_dcc_success_and_scan_mapping() - Verify success path and rotation-to-scan mapping. * @test: KUnit test context. @@ -891,86 +936,6 @@ static void dm_test_add_modifier_noop_when_mods_null(struct kunit *test) KUNIT_EXPECT_EQ(test, cap, 7ULL); } -/** - * dm_test_fill_gfx8_tiling_info_2d_tiled() - Verify GFX8 2D tiled flag parsing. - * @test: KUnit test context. - * - * Verify if 2D tiled GFX8 flags populate expected tiling fields. - */ -static void dm_test_fill_gfx8_tiling_info_2d_tiled(struct kunit *test) -{ - struct dc_tiling_info tiling_info = {0}; - uint64_t tiling_flags = 0; - - tiling_flags |= AMDGPU_TILING_SET(ARRAY_MODE, DC_ARRAY_2D_TILED_THIN1); - tiling_flags |= AMDGPU_TILING_SET(BANK_WIDTH, 2); - tiling_flags |= AMDGPU_TILING_SET(BANK_HEIGHT, 1); - tiling_flags |= AMDGPU_TILING_SET(MACRO_TILE_ASPECT, 3); - tiling_flags |= AMDGPU_TILING_SET(TILE_SPLIT, 4); - tiling_flags |= AMDGPU_TILING_SET(NUM_BANKS, 2); - tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 7); - - amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(&tiling_info, tiling_flags); - - KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion8); - KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.array_mode, (int)DC_ARRAY_2D_TILED_THIN1); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.bank_width, 2U); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.bank_height, 1U); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_aspect, 3U); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_split, 4U); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 2U); - KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.tile_mode, - (int)DC_ADDR_SURF_MICRO_TILING_DISPLAY); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 7U); -} - -/** - * dm_test_fill_gfx8_tiling_info_1d_tiled() - Verify GFX8 1D tiled flag parsing. - * @test: KUnit test context. - * - * Verify if 1D tiled GFX8 flags populate array mode and pipe config. - */ -static void dm_test_fill_gfx8_tiling_info_1d_tiled(struct kunit *test) -{ - struct dc_tiling_info tiling_info = {0}; - uint64_t tiling_flags = 0; - - tiling_flags |= AMDGPU_TILING_SET(ARRAY_MODE, DC_ARRAY_1D_TILED_THIN1); - tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 5); - - amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(&tiling_info, tiling_flags); - - KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.array_mode, (int)DC_ARRAY_1D_TILED_THIN1); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 5U); -} - -/** - * dm_test_fill_gfx8_tiling_info_other_mode() - Verify non-1D/non-2D mode handling. - * @test: KUnit test context. - * - * Verify if unsupported array mode keeps preset fields and updates pipe config. - */ -static void dm_test_fill_gfx8_tiling_info_other_mode(struct kunit *test) -{ - struct dc_tiling_info tiling_info = {0}; - uint64_t tiling_flags = 0; - - tiling_info.gfxversion = 0x7f; - tiling_info.gfx8.array_mode = 0x7f; - tiling_info.gfx8.tile_mode = 0x7f; - tiling_info.gfx8.num_banks = 0x7f; - - tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 6); - - amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(&tiling_info, tiling_flags); - - KUNIT_EXPECT_EQ(test, tiling_info.gfxversion, 0x7f); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.array_mode, 0x7f); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_mode, 0x7f); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 0x7f); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 6U); -} - /** * dm_test_fill_gfx9_tiling_info_from_device_pre_10_3() - Verify GFX9 field copy before 10.3. * @test: KUnit test context. @@ -1162,45 +1127,1987 @@ static void dm_test_fill_gfx9_tiling_info_from_modifier_nv(struct kunit *test) KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); } +/** + * dm_test_get_format_info() - Verify modifier-based format info lookup. + * @test: KUnit test context. + * + * Verify if non-AMD modifiers return NULL and AMD DCC modifiers resolve a + * dedicated format info structure. + */ +static void dm_test_get_format_info(struct kunit *test) +{ + u64 dcc_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(DCC, 1); + const struct drm_format_info *format_info; + + KUNIT_EXPECT_PTR_EQ(test, + (void *)amdgpu_dm_plane_get_format_info(DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_LINEAR), + NULL); + format_info = amdgpu_dm_plane_get_format_info(DRM_FORMAT_XRGB8888, dcc_mod); + KUNIT_EXPECT_NOT_NULL(test, format_info); +} + +/** + * dm_test_fill_blending_global_alpha_dcn42() - Verify DCN 4.2 alpha scaling. + * @test: KUnit test context. + * + * Verify if DCN 4.2 scales the 16-bit DRM alpha down by 4 bits instead of 8. + */ +static void dm_test_fill_blending_global_alpha_dcn42(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane *plane; + struct drm_plane_state *state; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 2, 0); + plane->dev = &adev->ddev; + state->plane = plane; + state->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + state->alpha = 0x8000; + + amdgpu_dm_plane_fill_blending_from_plane_state(state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_TRUE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0x800); +} + +static void dm_test_expect_mods_terminated(struct kunit *test, struct amdgpu_device *adev) +{ + u64 *mods = NULL; + int ret; + int i; + + ret = amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_PRIMARY, &mods); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_NOT_NULL(test, mods); + + for (i = 0; mods[i] != DRM_FORMAT_MOD_INVALID; i++) + ; + + KUNIT_EXPECT_GT(test, i, 0); + KUNIT_EXPECT_EQ(test, mods[i - 1], DRM_FORMAT_MOD_LINEAR); + kfree(mods); +} + +static bool dm_test_mods_contain(const u64 *mods, u64 expected) +{ + int i; + + for (i = 0; mods[i] != DRM_FORMAT_MOD_INVALID; i++) { + if (mods[i] == expected) + return true; + } + + return false; +} + +static u64 *dm_test_get_primary_mods(struct kunit *test, struct amdgpu_device *adev) +{ + u64 *mods = NULL; + int ret; + + ret = amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_PRIMARY, &mods); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_NOT_NULL(test, mods); + + return mods; +} + +static int dm_test_gfx9_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers(adev, + afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, + plane_size, tiling_info, dcc, address); +} + +static int dm_test_gfx12_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers(adev, + afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, + plane_size, tiling_info, dcc, address); +} + +static int dm_test_plane_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + enum surface_pixel_format format, + struct dc_tiling_info *tiling_info, + struct plane_size *plane_size, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return amdgpu_dm_plane_fill_plane_buffer_attributes(adev, afb, format, + ROTATION_ANGLE_0, tiling_info, plane_size, dcc, address, + false); +} + +static int dm_test_video_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + struct dc_tiling_info *tiling_info, + struct plane_size *plane_size, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return dm_test_plane_attrs(adev, afb, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + tiling_info, plane_size, dcc, address); +} + +static int dm_test_graphics_attrs(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + struct dc_tiling_info *tiling_info, + struct plane_size *plane_size, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) +{ + return dm_test_plane_attrs(adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + tiling_info, plane_size, dcc, address); +} + +static void dm_test_setup_gfx9_dcc_device(struct amdgpu_device *adev, + struct dc *dc, + struct dm_test_dcc_cap_ctx *ctx, + bool output_independent_64b_blks) +{ + adev->family = AMDGPU_FAMILY_NV; + adev->dm.dc = dc; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + ctx->callback_ret = true; + ctx->capable = true; + ctx->output_independent_64b_blks = output_independent_64b_blks; + dm_test_dcc_ctx = ctx; +} + +static u64 dm_test_gfx9_dcc_modifier(u64 tile_version, bool independent_64b_blks, + bool independent_128b_blks) +{ + return AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, tile_version) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, independent_64b_blks) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_128B, independent_128b_blks); +} + +static void dm_test_setup_gfx11_device(struct amdgpu_device *adev, + struct dm_test_gfx11_reg_ctx *ctx, + u32 num_pkrs_log2, u32 num_pipes_log2) +{ + ctx->gb_addr_config = + REG_SET_FIELD(0, GB_ADDR_CONFIG, NUM_PKRS, num_pkrs_log2) | + REG_SET_FIELD(0, GB_ADDR_CONFIG, NUM_PIPES, num_pipes_log2); + ctx->gc_reg_offsets[regGB_ADDR_CONFIG_BASE_IDX] = 0; + ctx->expected_reg = ctx->gc_reg_offsets[regGB_ADDR_CONFIG_BASE_IDX] + + regGB_ADDR_CONFIG; + dm_test_gfx11_reg_ctx = ctx; + + adev->family = AMDGPU_FAMILY_GC_11_0_0; + adev->reg_offset[GC_HWIP][0] = ctx->gc_reg_offsets; + adev->gfx.rlc.reg_funcs = &dm_test_gfx11_reg_funcs; +} + +static u64 dm_test_gfx11_dcc_best_modifier(u32 pipe_xor_bits, u32 pkrs, u32 tile) +{ + return AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX11) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(TILE, tile) | + AMD_FMT_MOD_SET(PACKERS, pkrs) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_128B, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, AMD_FMT_MOD_DCC_BLOCK_128B); +} + +static u64 dm_test_gfx11_dcc_4k_modifier(u32 pipe_xor_bits, u32 pkrs, u32 tile) +{ + return AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX11) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(TILE, tile) | + AMD_FMT_MOD_SET(PACKERS, pkrs) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_128B, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, AMD_FMT_MOD_DCC_BLOCK_64B); +} + +/** + * dm_test_get_plane_formats_overlay_universal_cap() - Verify universal overlay. + * @test: KUnit test context. + * + * Verify if an overlay plane with a DCN universal plane cap reports the RGB + * format list instead of the overlay-only list. + */ +static void dm_test_get_plane_formats_overlay_universal_cap(struct kunit *test) +{ + struct drm_plane *plane; + struct dc_plane_cap *cap; + u32 formats[32] = {0}; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + cap = kunit_kzalloc(test, sizeof(*cap), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, cap); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + cap->type = DC_PLANE_TYPE_DCN_UNIVERSAL; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_get_plane_formats(plane, cap, formats, 32), + 14); +} + +/** + * dm_test_get_plane_modifiers_gfx9() - Verify GFX9 modifier list generation. + * @test: KUnit test context. + * + * Verify if the GFX9 family produces a non-empty modifier list terminated by + * LINEAR and INVALID entries. + */ +static void dm_test_get_plane_modifiers_gfx9(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_AI; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 8; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + + dm_test_expect_mods_terminated(test, adev); +} + +/** + * dm_test_get_plane_modifiers_rv() - Verify RV modifier list generation. + * @test: KUnit test context. + * + * Verify if pre-Raven2 RV devices add RV-specific S-swizzle modifiers and + * non-constant-encode DCC modifiers. + */ +static void dm_test_get_plane_modifiers_rv(struct kunit *test) +{ + struct amdgpu_device *adev; + u64 *mods; + u64 dcc_mod; + u64 s_x_mod; + u64 s_mod; + int pipes = 2; + int pipe_xor_bits = 3; + int bank_xor_bits = 2; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_RV; + adev->asic_type = CHIP_RAVEN; + adev->external_rev_id = 0x80; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + + mods = dm_test_get_primary_mods(test, adev); + dcc_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, bank_xor_bits) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, AMD_FMT_MOD_DCC_BLOCK_64B) | + AMD_FMT_MOD_SET(DCC_CONSTANT_ENCODE, 0); + s_x_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, bank_xor_bits); + s_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9); + + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_mod)); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_mod | + AMD_FMT_MOD_SET(DCC_RETILE, 1) | + AMD_FMT_MOD_SET(RB, 2) | + AMD_FMT_MOD_SET(PIPE, pipes))); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, s_x_mod)); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, s_mod)); + + kfree(mods); +} + +/** + * dm_test_get_plane_modifiers_rv_constant_encode() - Verify Raven2+ modifiers. + * @test: KUnit test context. + * + * Verify if Raven2 and later RV devices add the constant-encode modifier + * variants. + */ +static void dm_test_get_plane_modifiers_rv_constant_encode(struct kunit *test) +{ + struct amdgpu_device *adev; + u64 *mods; + u64 dcc_mod; + int pipes = 2; + int pipe_xor_bits = 3; + int bank_xor_bits = 2; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_RV; + adev->asic_type = CHIP_RAVEN; + adev->external_rev_id = 0x81; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + + mods = dm_test_get_primary_mods(test, adev); + dcc_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, pipe_xor_bits) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, bank_xor_bits) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, AMD_FMT_MOD_DCC_BLOCK_64B) | + AMD_FMT_MOD_SET(DCC_CONSTANT_ENCODE, 1); + + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_mod)); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_mod | + AMD_FMT_MOD_SET(DCC_RETILE, 1) | + AMD_FMT_MOD_SET(RB, 2) | + AMD_FMT_MOD_SET(PIPE, pipes))); + + kfree(mods); +} + +/** + * dm_test_get_plane_modifiers_gfx10_1() - Verify GFX10.1 modifier list generation. + * @test: KUnit test context. + * + * Verify if a pre-10.3 NV family device dispatches to the GFX10.1 modifier + * builder and produces a terminated list. + */ +static void dm_test_get_plane_modifiers_gfx10_1(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 1, 0); + + dm_test_expect_mods_terminated(test, adev); +} + +/** + * dm_test_get_plane_modifiers_gfx10_3() - Verify GFX10.3 modifier list generation. + * @test: KUnit test context. + * + * Verify if a 10.3+ NV family device dispatches to the GFX10.3 modifier + * builder and produces a terminated list. + */ +static void dm_test_get_plane_modifiers_gfx10_3(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + dm_test_expect_mods_terminated(test, adev); +} + +/** + * dm_test_get_plane_modifiers_gfx11_64k_first() - Verify GFX11 small-pipe order. + * @test: KUnit test context. + * + * Verify if GFX11 modifier generation reads GB_ADDR_CONFIG through the RLC + * register callback and prefers 64K_R_X when the pipe count is 16 or lower. + */ +static void dm_test_get_plane_modifiers_gfx11_64k_first(struct kunit *test) +{ + struct dm_test_gfx11_reg_ctx ctx = {0}; + struct amdgpu_device *adev; + u64 *mods; + u32 pipe_xor_bits = 4; + u32 pkrs = 1; + u32 tile = AMD_FMT_MOD_TILE_GFX9_64K_R_X; + u64 dcc_best; + u64 dcc_4k; + u64 d_mod; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + dm_test_setup_gfx11_device(adev, &ctx, pkrs, pipe_xor_bits); + + mods = dm_test_get_primary_mods(test, adev); + dcc_best = dm_test_gfx11_dcc_best_modifier(pipe_xor_bits, pkrs, tile); + dcc_4k = dm_test_gfx11_dcc_4k_modifier(pipe_xor_bits, pkrs, tile); + d_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX11) | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_D); + + KUNIT_EXPECT_TRUE(test, ctx.called); + KUNIT_EXPECT_EQ(test, ctx.captured_reg, ctx.expected_reg); + KUNIT_EXPECT_EQ(test, ctx.captured_acc_flags, 0U); + KUNIT_EXPECT_EQ(test, ctx.captured_hwip, (u32)GC_HWIP); + KUNIT_EXPECT_EQ(test, ctx.captured_xcc_id, 0U); + KUNIT_EXPECT_EQ(test, mods[0], dcc_best); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, dcc_4k)); + KUNIT_EXPECT_TRUE(test, + dm_test_mods_contain(mods, + dcc_best | AMD_FMT_MOD_SET(DCC_RETILE, 1))); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, d_mod)); + + dm_test_gfx11_reg_ctx = NULL; + kfree(mods); +} + +/** + * dm_test_get_plane_modifiers_gfx11_256k_first() - Verify GFX11 large-pipe order. + * @test: KUnit test context. + * + * Verify if GFX11 modifier generation prefers 256K_R_X when more than 16 pipes + * are reported by GB_ADDR_CONFIG. + */ +static void dm_test_get_plane_modifiers_gfx11_256k_first(struct kunit *test) +{ + struct dm_test_gfx11_reg_ctx ctx = {0}; + struct amdgpu_device *adev; + u64 *mods; + u32 pipe_xor_bits = 5; + u32 pkrs = 2; + u32 tile = AMD_FMT_MOD_TILE_GFX11_256K_R_X; + u32 fallback_tile = AMD_FMT_MOD_TILE_GFX9_64K_R_X; + u64 dcc_best; + u64 fallback_dcc_best = dm_test_gfx11_dcc_best_modifier(pipe_xor_bits, pkrs, + fallback_tile); + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + dm_test_setup_gfx11_device(adev, &ctx, pkrs, pipe_xor_bits); + + mods = dm_test_get_primary_mods(test, adev); + dcc_best = dm_test_gfx11_dcc_best_modifier(pipe_xor_bits, pkrs, tile); + + KUNIT_EXPECT_TRUE(test, ctx.called); + KUNIT_EXPECT_EQ(test, ctx.captured_reg, ctx.expected_reg); + KUNIT_EXPECT_EQ(test, mods[0], dcc_best); + KUNIT_EXPECT_TRUE(test, dm_test_mods_contain(mods, fallback_dcc_best)); + + dm_test_gfx11_reg_ctx = NULL; + kfree(mods); +} + +/** + * dm_test_get_plane_modifiers_gfx12() - Verify GFX12 modifier list generation. + * @test: KUnit test context. + * + * Verify if the GFX12 family dispatches to the GFX12 modifier builder and + * produces a terminated list. + */ +static void dm_test_get_plane_modifiers_gfx12(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + + dm_test_expect_mods_terminated(test, adev); +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc() - Verify GFX9 DCC modifier path. + * @test: KUnit test context. + * + * Verify if a GFX10-RBPLUS DCC modifier enables DCC and selects the 64B + * independent block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = true, + }; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_NV; + adev->dm.dc = dc; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + afb->base.pitches[1] = 256; + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_INDEPENDENT_64B, 1); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion9); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_64b); + KUNIT_EXPECT_EQ(test, dcc.meta_pitch, 256U); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_validate_fails() - Verify GFX9 error path. + * @test: KUnit test context. + * + * Verify if GFX9 modifier parsing returns validation errors from the shared DCC + * validation helper. + */ +static void dm_test_fill_gfx9_plane_attributes_validate_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + u64 tile_version = AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_NV; + adev->dm.dc = dc; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + afb->base.modifier = dm_test_gfx9_dcc_modifier(tile_version, true, false); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_rbplus_64b_no_128bcl() - Verify block mode. + * @test: KUnit test context. + * + * Verify if a GFX10-RBPLUS modifier with both 64B and 128B independent block + * bits selects the 64B-no-128BCL block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_rbplus_64b_no_128bcl(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + u64 tile_version = AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, true); + afb->base.modifier = dm_test_gfx9_dcc_modifier(tile_version, true, true); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, + (int)hubp_ind_block_64b_no_128bcl); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_rbplus_128b() - Verify 128B block mode. + * @test: KUnit test context. + * + * Verify if a GFX10-RBPLUS modifier with only the 128B independent block bit + * selects the 128B block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_rbplus_128b(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + u64 tile_version = AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, false); + afb->base.modifier = dm_test_gfx9_dcc_modifier(tile_version, false, true); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_128b); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_rbplus_unconstrained() - Verify block mode. + * @test: KUnit test context. + * + * Verify if a GFX10-RBPLUS modifier without independent block bits selects the + * unconstrained block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_rbplus_unconstrained(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + u64 tile_version = AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, false); + afb->base.modifier = dm_test_gfx9_dcc_modifier(tile_version, false, false); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, + (int)hubp_ind_block_unconstrained); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_gfx9_64b() - Verify legacy 64B mode. + * @test: KUnit test context. + * + * Verify if a pre-RBPLUS GFX9 modifier with the 64B independent block bit + * selects the 64B block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_gfx9_64b(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, true); + afb->base.modifier = dm_test_gfx9_dcc_modifier(AMD_FMT_MOD_TILE_VER_GFX9, + true, false); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_64b); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_dcc_gfx9_unconstrained() - Verify legacy mode. + * @test: KUnit test context. + * + * Verify if a pre-RBPLUS GFX9 modifier without the 64B independent block bit + * selects the unconstrained block mode. + */ +static void dm_test_fill_gfx9_plane_attributes_dcc_gfx9_unconstrained(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = {0}; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + dm_test_setup_gfx9_dcc_device(adev, dc, &ctx, false); + afb->base.modifier = dm_test_gfx9_dcc_modifier(AMD_FMT_MOD_TILE_VER_GFX9, + false, false); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx9_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, + (int)hubp_ind_block_unconstrained); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx12_plane_attributes_block0() - Verify GFX12 64B max-compressed-block path. + * @test: KUnit test context. + * + * Verify if a zero max-compressed-block modifier selects the 64B independent + * block mode on GFX12. + */ +static void dm_test_fill_gfx12_plane_attributes_block0(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = false, + }; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_64K_2D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX12) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, 0); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx12_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_TRUE(test, dcc.independent_64b_blks); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_64b); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx12_plane_attributes_block_unconstrained() - Verify block path. + * @test: KUnit test context. + * + * Verify if a max-compressed-block value above one selects the unconstrained + * independent block mode on GFX12. + */ +static void dm_test_fill_gfx12_plane_attributes_block_unconstrained(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = false, + }; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_64K_2D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX12) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, 2); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx12_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_FALSE(test, dcc.independent_64b_blks); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, + (int)hubp_ind_block_unconstrained); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx12_plane_attributes_validate_fails() - Verify GFX12 error path. + * @test: KUnit test context. + * + * Verify if GFX12 modifier parsing returns validation errors from the shared + * DCC validation helper. + */ +static void dm_test_fill_gfx12_plane_attributes_validate_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_64K_2D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX12) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, 1); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + ret = dm_test_gfx12_attrs(adev, afb, &plane_size, &tiling_info, &dcc, + &address); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_fill_plane_buffer_attributes_video() - Verify NV12 attributes. + * @test: KUnit test context. + * + * Verify if a video pixel format fills chroma plane size and the progressive + * video address type on a GFX9 family device. + */ +static void dm_test_fill_plane_buffer_attributes_video(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_framebuffer *afb; + struct dc_tiling_info tiling_info; + struct plane_size plane_size; + struct dc_plane_dcc_param dcc; + struct dc_plane_address address; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + tiling_info = (struct dc_tiling_info){0}; + plane_size = (struct plane_size){0}; + dcc = (struct dc_plane_dcc_param){0}; + address = (struct dc_plane_address){0}; + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_NV; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + afb->address = 0x80000000ULL; + afb->base.width = 1920; + afb->base.height = 1080; + afb->base.offsets[0] = 0; + afb->base.offsets[1] = 0x200000; + afb->base.pitches[0] = 1920; + afb->base.pitches[1] = 1920; + afb->base.format = drm_format_info(DRM_FORMAT_NV12); + afb->base.modifier = DRM_FORMAT_MOD_LINEAR; + KUNIT_ASSERT_NOT_NULL(test, afb->base.format); + + ret = dm_test_video_attrs(adev, afb, &tiling_info, &plane_size, &dcc, + &address); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, plane_size.surface_size.width, 1920); + KUNIT_EXPECT_EQ(test, plane_size.chroma_size.width, 960U); + KUNIT_EXPECT_EQ(test, plane_size.chroma_size.height, 540U); + KUNIT_EXPECT_EQ(test, address.type, + (int)PLN_ADDR_TYPE_VIDEO_PROGRESSIVE); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion9); +} + +/** + * dm_test_fill_plane_buffer_attributes_gfx12() - Verify GFX12 dispatch path. + * @test: KUnit test context. + * + * Verify if a GFX12 family device fills graphics attributes via the GFX12 + * modifier path and reports the GFX addr3 version. + */ +static void dm_test_fill_plane_buffer_attributes_gfx12(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct dc_tiling_info tiling_info; + struct plane_size plane_size; + struct dc_plane_dcc_param dcc; + struct dc_plane_address address; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + tiling_info = (struct dc_tiling_info){0}; + plane_size = (struct plane_size){0}; + dcc = (struct dc_plane_dcc_param){0}; + address = (struct dc_plane_address){0}; + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + afb->address = 0x80000000ULL; + afb->base.width = 1920; + afb->base.height = 1080; + afb->base.pitches[0] = 7680; + afb->base.format = drm_format_info(DRM_FORMAT_XRGB8888); + afb->base.modifier = DRM_FORMAT_MOD_LINEAR; + KUNIT_ASSERT_NOT_NULL(test, afb->base.format); + + ret = dm_test_graphics_attrs(adev, afb, &tiling_info, &plane_size, &dcc, + &address); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, address.type, (int)PLN_ADDR_TYPE_GRAPHICS); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxAddr3); +} + +/** + * dm_test_get_min_max_dc_plane_scaling_fp16() - Verify fp16 cap selection. + * @test: KUnit test context. + * + * Verify if 64bpp fp16 formats use the fp16 scaling caps. + */ +static void dm_test_get_min_max_dc_plane_scaling_fp16(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct drm_framebuffer *fb; + int min_downscale = 0; + int max_upscale = 0; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.fp16 = 2000; + dc->caps.planes[0].max_downscale_factor.fp16 = 500; + + fb->format = drm_format_info(DRM_FORMAT_ARGB16161616F); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + amdgpu_dm_plane_get_min_max_dc_plane_scaling(&adev->ddev, fb, + &min_downscale, &max_upscale); + KUNIT_EXPECT_EQ(test, min_downscale, 500); + KUNIT_EXPECT_EQ(test, max_upscale, 2000); +} + +/** + * dm_test_helper_check_state_small_viewport_width() - Verify width rejection. + * @test: KUnit test context. + * + * Verify if a viewport width below the minimum pipe-split width is rejected. + */ +static void dm_test_helper_check_state_small_viewport_width(struct kunit *test) +{ + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->crtc_x = 0; + state->crtc_y = 0; + state->crtc_w = 10; + state->crtc_h = 100; + new_crtc_state->mode.crtc_hdisplay = 1920; + new_crtc_state->mode.crtc_vdisplay = 1080; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_helper_check_state(state, new_crtc_state), + -EINVAL); +} + +/** + * dm_test_helper_check_state_small_viewport_height() - Verify height rejection. + * @test: KUnit test context. + * + * Verify if a negative-offset viewport with a too-small height is rejected. + */ +static void dm_test_helper_check_state_small_viewport_height(struct kunit *test) +{ + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->crtc_x = -2; + state->crtc_y = -95; + state->crtc_w = 100; + state->crtc_h = 100; + new_crtc_state->mode.crtc_hdisplay = 1920; + new_crtc_state->mode.crtc_vdisplay = 1080; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_helper_check_state(state, new_crtc_state), + -EINVAL); +} + +/** + * dm_test_helper_check_state_bottom_clipped_height() - Verify bottom clipping. + * @test: KUnit test context. + * + * Verify if a viewport clipped by the bottom edge to below the minimum height + * is rejected. + */ +static void dm_test_helper_check_state_bottom_clipped_height(struct kunit *test) +{ + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->crtc_x = 0; + state->crtc_y = 95; + state->crtc_w = 100; + state->crtc_h = 100; + new_crtc_state->mode.crtc_hdisplay = 1920; + new_crtc_state->mode.crtc_vdisplay = 100; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_helper_check_state(state, new_crtc_state), + -EINVAL); +} + +/** + * dm_test_helper_check_state_scaling_caps() - Verify DC scaling caps are applied. + * @test: KUnit test context. + * + * Verify if helper_check_state converts DC plane scaling caps to DRM scale + * limits and rejects scaling outside those limits. + */ +static void dm_test_helper_check_state_scaling_caps(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.argb8888 = 1; + dc->caps.planes[0].max_downscale_factor.argb8888 = 1; + + plane->type = DRM_PLANE_TYPE_OVERLAY; + plane->dev = &adev->ddev; + crtc->dev = &adev->ddev; + fb->width = 100; + fb->height = 100; + fb->format = drm_format_info(DRM_FORMAT_XRGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->src_w = 100 << 16; + state->src_h = 100 << 16; + state->crtc_w = 200; + state->crtc_h = 200; + new_crtc_state->crtc = crtc; + new_crtc_state->mode.crtc_hdisplay = 1920; + new_crtc_state->mode.crtc_vdisplay = 1080; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_helper_check_state(state, new_crtc_state), + -ERANGE); +} + +/** + * dm_test_fill_dc_scaling_info_nv12_dcn1x() - Verify NV12 DCN1x rejection. + * @test: KUnit test context. + * + * Verify if a non-zero NV12 source origin is rejected on DCN 1.0 to avoid the + * known DCN1x hang. + */ +static void dm_test_fill_dc_scaling_info_nv12_dcn1x(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane_state state = {0}; + struct drm_framebuffer fb = {0}; + struct dc_scaling_info info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(1, 0, 0); + fb.format = drm_format_info(DRM_FORMAT_NV12); + KUNIT_ASSERT_NOT_NULL(test, fb.format); + + state.fb = &fb; + state.src_x = 10 << 16; + state.src_y = 0; + state.src_w = 100 << 16; + state.src_h = 100 << 16; + state.crtc_w = 100; + state.crtc_h = 100; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_dc_scaling_info(adev, &state, &info), + -EINVAL); + + state.src_x = 0; + state.src_y = 10 << 16; + memset(&info, 0, sizeof(info)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_dc_scaling_info(adev, &state, &info), + -EINVAL); +} + +/** + * dm_test_fill_dc_scaling_info_plane_caps() - Verify scaling caps path. + * @test: KUnit test context. + * + * Verify if scaling info uses plane caps when the state references a plane, + * device, and framebuffer. + */ +static void dm_test_fill_dc_scaling_info_plane_caps(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_framebuffer *fb; + struct dc_scaling_info info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.argb8888 = 16000; + dc->caps.planes[0].max_downscale_factor.argb8888 = 250; + + plane->dev = &adev->ddev; + fb->format = drm_format_info(DRM_FORMAT_XRGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + + state->plane = plane; + state->fb = fb; + state->src_w = 100 << 16; + state->src_h = 100 << 16; + state->crtc_w = 100; + state->crtc_h = 100; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_dc_scaling_info(adev, state, &info), + 0); +} + +/** + * dm_test_get_cursor_position_bad_size() - Verify oversized cursor rejection. + * @test: KUnit test context. + * + * Verify if a cursor larger than the CRTC maximum is rejected. + */ +static void dm_test_get_cursor_position_bad_size(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *amdgpu_crtc; + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_framebuffer *fb; + struct dc_cursor_position position = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + amdgpu_crtc = kunit_kzalloc(test, sizeof(*amdgpu_crtc), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, amdgpu_crtc); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + amdgpu_crtc->max_cursor_width = 64; + amdgpu_crtc->max_cursor_height = 64; + plane->dev = &adev->ddev; + plane->state = state; + state->fb = fb; + state->crtc_w = 128; + state->crtc_h = 32; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_get_cursor_position(plane, &amdgpu_crtc->base, &position), + -EINVAL); +} + +/** + * dm_test_format_mod_supported_d_swizzle_reject() - Verify D swizzle rejection. + * @test: KUnit test context. + * + * Verify if a D micro-swizzle modifier is rejected for formats narrower than + * 8 bytes per pixel. + */ +static void dm_test_format_mod_supported_d_swizzle_reject(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane *plane; + u64 listed_mod; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, plane); + + adev->family = AMDGPU_FAMILY_RV; + plane->dev = &adev->ddev; + + listed_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9); + plane->modifiers = &listed_mod; + plane->modifier_count = 1; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB8888, + listed_mod)); +} + +/** + * dm_test_atomic_async_check_rejects() - Verify async check rejections. + * @test: KUnit test context. + * + * Verify if async flip on non-overlay planes and async cursor update on + * non-cursor planes are rejected. + */ +static void dm_test_atomic_async_check_rejects(struct kunit *test) +{ + struct drm_plane *plane; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + + plane->type = DRM_PLANE_TYPE_PRIMARY; + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_atomic_async_check(plane, NULL, true), + -EINVAL); + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_atomic_async_check(plane, NULL, false), + -EINVAL); +} + +/** + * dm_test_atomic_async_check_overlay_cursor() - Verify overlay cursor rejection. + * @test: KUnit test context. + * + * Verify if async cursor updates are rejected while the CRTC is using an + * overlay cursor mode. + */ +static void dm_test_atomic_async_check_overlay_cursor(struct kunit *test) +{ + struct drm_atomic_commit *state; + struct __drm_planes_state *planes; + struct __drm_crtcs_state *crtcs; + struct drm_plane *plane; + struct drm_plane_state *plane_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + planes = kunit_kzalloc(test, sizeof(*planes), GFP_KERNEL); + crtcs = kunit_kzalloc(test, sizeof(*crtcs), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + plane_state = kunit_kzalloc(test, sizeof(*plane_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, planes); + KUNIT_ASSERT_NOT_NULL(test, crtcs); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, plane_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + plane->type = DRM_PLANE_TYPE_CURSOR; + plane->index = 0; + crtc->index = 0; + plane_state->crtc = crtc; + dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE; + state->planes = planes; + state->crtcs = crtcs; + state->planes[0].new_state = plane_state; + state->crtcs[0].new_state = &dm_crtc_state->base; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_atomic_async_check(plane, state, false), + -EINVAL); + + dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE; + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_atomic_async_check(plane, state, false), + 0); +} + +static struct amdgpu_device *dm_test_init_atomic_check_state(struct kunit *test, + struct drm_atomic_commit **state, + struct drm_plane **plane, + struct dm_plane_state **dm_plane_state, + struct drm_crtc_state **new_crtc_state, + struct drm_framebuffer **fb) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct __drm_planes_state *planes; + struct __drm_crtcs_state *crtcs; + struct dc_plane_state *dc_plane_state; + struct drm_crtc *crtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + *state = kunit_kzalloc(test, sizeof(**state), GFP_KERNEL); + planes = kunit_kzalloc(test, sizeof(*planes), GFP_KERNEL); + crtcs = kunit_kzalloc(test, sizeof(*crtcs), GFP_KERNEL); + *plane = kunit_kzalloc(test, sizeof(**plane), GFP_KERNEL); + *dm_plane_state = kunit_kzalloc(test, sizeof(**dm_plane_state), GFP_KERNEL); + dc_plane_state = kunit_kzalloc(test, sizeof(*dc_plane_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + *new_crtc_state = kunit_kzalloc(test, sizeof(**new_crtc_state), GFP_KERNEL); + *fb = kunit_kzalloc(test, sizeof(**fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, *state); + KUNIT_ASSERT_NOT_NULL(test, planes); + KUNIT_ASSERT_NOT_NULL(test, crtcs); + KUNIT_ASSERT_NOT_NULL(test, *plane); + KUNIT_ASSERT_NOT_NULL(test, *dm_plane_state); + KUNIT_ASSERT_NOT_NULL(test, dc_plane_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, *new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, *fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.argb8888 = 1000; + dc->caps.planes[0].max_downscale_factor.argb8888 = 1000; + dc->caps.planes[0].max_upscale_factor.nv12 = 1000; + dc->caps.planes[0].max_downscale_factor.nv12 = 1000; + + (*plane)->dev = &adev->ddev; + (*plane)->index = 0; + (*plane)->type = DRM_PLANE_TYPE_OVERLAY; + (*plane)->name = "kunit-plane"; + crtc->dev = &adev->ddev; + crtc->index = 0; + (*fb)->width = 100; + (*fb)->height = 100; + (*fb)->format = drm_format_info(DRM_FORMAT_XRGB8888); + KUNIT_ASSERT_NOT_NULL(test, (*fb)->format); + + (*dm_plane_state)->base.plane = *plane; + (*dm_plane_state)->base.state = *state; + (*dm_plane_state)->base.crtc = crtc; + (*dm_plane_state)->base.fb = *fb; + (*dm_plane_state)->base.src_w = 100 << 16; + (*dm_plane_state)->base.src_h = 100 << 16; + (*dm_plane_state)->base.crtc_w = 100; + (*dm_plane_state)->base.crtc_h = 100; + (*dm_plane_state)->dc_state = dc_plane_state; + + (*new_crtc_state)->crtc = crtc; + (*new_crtc_state)->enable = true; + (*new_crtc_state)->mode.crtc_hdisplay = 1920; + (*new_crtc_state)->mode.crtc_vdisplay = 1080; + + (*state)->planes = planes; + (*state)->crtcs = crtcs; + (*state)->planes[0].new_state = &(*dm_plane_state)->base; + (*state)->crtcs[0].new_state = *new_crtc_state; + + return adev; +} + +/** + * dm_test_atomic_check_no_dc_state() - Verify missing DC plane state succeeds. + * @test: KUnit test context. + * + * Verify if atomic_check exits before deeper validation when the DM plane state + * has no DC plane state attached. + */ +static void dm_test_atomic_check_no_dc_state(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct __drm_planes_state *planes; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + planes = kunit_kzalloc(test, sizeof(*planes), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, planes); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + + plane->dev = &adev->ddev; + plane->index = 0; + dm_plane_state->base.plane = plane; + state->planes = planes; + state->planes[0].new_state = &dm_plane_state->base; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), 0); +} + +/** + * dm_test_atomic_check_missing_crtc_state() - Verify missing CRTC state fails. + * @test: KUnit test context. + * + * Verify if atomic_check rejects a plane with DC state when the atomic CRTC + * state is absent. + */ +static void dm_test_atomic_check_missing_crtc_state(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct __drm_planes_state *planes; + struct __drm_crtcs_state *crtcs; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + struct dc_plane_state *dc_plane_state; + struct drm_crtc *crtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + planes = kunit_kzalloc(test, sizeof(*planes), GFP_KERNEL); + crtcs = kunit_kzalloc(test, sizeof(*crtcs), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + dc_plane_state = kunit_kzalloc(test, sizeof(*dc_plane_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, planes); + KUNIT_ASSERT_NOT_NULL(test, crtcs); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + KUNIT_ASSERT_NOT_NULL(test, dc_plane_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + plane->dev = &adev->ddev; + plane->index = 0; + crtc->index = 0; + dm_plane_state->base.plane = plane; + dm_plane_state->base.crtc = crtc; + dm_plane_state->dc_state = dc_plane_state; + state->planes = planes; + state->crtcs = crtcs; + state->planes[0].new_state = &dm_plane_state->base; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), -EINVAL); +} + +/** + * dm_test_atomic_check_helper_failure() - Verify helper-check failures return. + * @test: KUnit test context. + * + * Verify if atomic_check returns before DC validation when the DRM helper state + * validation rejects the plane. + */ +static void dm_test_atomic_check_helper_failure(struct kunit *test) +{ + struct drm_atomic_commit *state; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + dm_test_init_atomic_check_state(test, &state, &plane, &dm_plane_state, + &new_crtc_state, &fb); + dm_plane_state->base.crtc_w = 10; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), -EINVAL); +} + +/** + * dm_test_atomic_check_color_pipeline_conflict() - Verify color conflict rejection. + * @test: KUnit test context. + * + * Verify if atomic_check rejects use of both plane COLOR_PIPELINE and CRTC + * DEGAMMA_LUT before DC validation. + */ +static void dm_test_atomic_check_color_pipeline_conflict(struct kunit *test) +{ + struct drm_atomic_commit *state; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + void *color_pipeline; + void *degamma_lut; + + dm_test_init_atomic_check_state(test, &state, &plane, &dm_plane_state, + &new_crtc_state, &fb); + color_pipeline = kunit_kzalloc(test, 1, GFP_KERNEL); + degamma_lut = kunit_kzalloc(test, 1, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, color_pipeline); + KUNIT_ASSERT_NOT_NULL(test, degamma_lut); + + dm_plane_state->base.color_pipeline = color_pipeline; + new_crtc_state->degamma_lut = degamma_lut; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), -EINVAL); +} + +/** + * dm_test_atomic_check_scaling_failure() - Verify scaling-info failures return. + * @test: KUnit test context. + * + * Verify if atomic_check returns the scaling-info error before DC validation. + */ +static void dm_test_atomic_check_scaling_failure(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + adev = dm_test_init_atomic_check_state(test, &state, &plane, &dm_plane_state, + &new_crtc_state, &fb); + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(1, 0, 0); + fb->width = 200; + fb->format = drm_format_info(DRM_FORMAT_NV12); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + dm_plane_state->base.src_x = 1 << 16; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_atomic_check(plane, state), -EINVAL); +} + +/** + * dm_test_panic_flush_no_dc_state() - Verify panic flush exits without DC state. + * @test: KUnit test context. + * + * Verify if panic_flush returns without dereferencing DC state when the current + * plane state has no DC plane state attached. + */ +static void dm_test_panic_flush_no_dc_state(struct kunit *test) +{ + struct drm_plane *plane; + struct dm_plane_state *dm_plane_state; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + + plane->state = &dm_plane_state->base; + + amdgpu_dm_plane_panic_flush(plane); +} + +static const struct drm_plane_funcs dm_test_plane_reset_funcs = { + .atomic_destroy_state = amdgpu_dm_plane_drm_plane_destroy_state, +}; + +/** + * dm_test_plane_reset_initializes_state() - Verify reset installs default state. + * @test: KUnit test context. + * + * Verify amdgpu_dm_plane_drm_plane_reset() destroys the existing plane state, + * allocates a fresh dm_plane_state, and initializes the AMD-specific transfer + * function and HDR multiplier defaults. + */ +static void dm_test_plane_reset_initializes_state(struct kunit *test) +{ + struct dm_plane_state *old_state; + struct dm_plane_state *new_state; + struct drm_plane *plane; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + + /* + * Provide an existing state plus a funcs table so reset exercises the + * destroy-existing-state path. The destroy hook frees this state, so it + * must be a plain (non-KUnit-managed) allocation. + */ + old_state = kzalloc(sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_state); + plane->funcs = &dm_test_plane_reset_funcs; + plane->state = &old_state->base; + + amdgpu_dm_plane_drm_plane_reset(plane); + + KUNIT_ASSERT_NOT_NULL(test, plane->state); + new_state = to_dm_plane_state(plane->state); + KUNIT_EXPECT_EQ(test, new_state->degamma_tf, AMDGPU_TRANSFER_FUNCTION_DEFAULT); + KUNIT_EXPECT_EQ(test, new_state->hdr_mult, AMDGPU_HDR_MULT_DEFAULT); + KUNIT_EXPECT_EQ(test, new_state->shaper_tf, AMDGPU_TRANSFER_FUNCTION_DEFAULT); + KUNIT_EXPECT_EQ(test, new_state->blend_tf, AMDGPU_TRANSFER_FUNCTION_DEFAULT); + + kfree(new_state); +} + +/** + * dm_test_plane_duplicate_state_copies_fields() - Verify state duplication. + * @test: KUnit test context. + * + * Verify amdgpu_dm_plane_drm_plane_duplicate_state() allocates a new state and + * copies the transfer-function and HDR-multiplier fields from the current + * plane state when no DC state or color blob is attached. + */ +static void dm_test_plane_duplicate_state_copies_fields(struct kunit *test) +{ + struct dm_plane_state *old_state; + struct drm_plane_state *dup_base; + struct dm_plane_state *dup_state; + struct drm_plane *plane; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, old_state); + + old_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_PQ_EOTF; + old_state->hdr_mult = 0x123456789ULL; + old_state->shaper_tf = AMDGPU_TRANSFER_FUNCTION_IDENTITY; + old_state->blend_tf = AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; + plane->state = &old_state->base; + + dup_base = amdgpu_dm_plane_drm_plane_duplicate_state(plane); + KUNIT_ASSERT_NOT_NULL(test, dup_base); + + dup_state = to_dm_plane_state(dup_base); + KUNIT_EXPECT_EQ(test, dup_state->degamma_tf, AMDGPU_TRANSFER_FUNCTION_PQ_EOTF); + KUNIT_EXPECT_EQ(test, dup_state->hdr_mult, 0x123456789ULL); + KUNIT_EXPECT_EQ(test, dup_state->shaper_tf, AMDGPU_TRANSFER_FUNCTION_IDENTITY); + KUNIT_EXPECT_EQ(test, dup_state->blend_tf, AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF); + KUNIT_EXPECT_NULL(test, dup_state->dc_state); + + kfree(dup_state); +} + +/** + * dm_test_plane_destroy_state_minimal() - Verify destroy of a minimal state. + * @test: KUnit test context. + * + * Verify amdgpu_dm_plane_drm_plane_destroy_state() tears down a plane state + * that has no color blobs or DC plane state attached without dereferencing + * NULL resources. + */ +static void dm_test_plane_destroy_state_minimal(struct kunit *test) +{ + struct dm_plane_state *dm_plane_state; + struct drm_plane *plane; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + + /* destroy_state frees the state itself, so use a plain allocation. */ + dm_plane_state = kzalloc(sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + + amdgpu_dm_plane_drm_plane_destroy_state(plane, &dm_plane_state->base); +} + static struct kunit_case amdgpu_dm_plane_test_cases[] = { /* amdgpu_dm_plane_is_video_format() */ KUNIT_CASE(dm_test_plane_is_video_format_known_video), + /* amdgpu_dm_plane_get_format_info() */ + KUNIT_CASE(dm_test_get_format_info), /* amdgpu_dm_plane_fill_blending_from_plane_state() */ KUNIT_CASE(dm_test_fill_blending_defaults), KUNIT_CASE(dm_test_fill_blending_premulti_alpha_format), KUNIT_CASE(dm_test_fill_blending_coverage_alpha_format), KUNIT_CASE(dm_test_fill_blending_global_alpha), + KUNIT_CASE(dm_test_fill_blending_global_alpha_dcn42), /* amdgpu_dm_plane_modifier_* helpers() */ KUNIT_CASE(dm_test_modifier_has_dcc), KUNIT_CASE(dm_test_modifier_gfx9_swizzle_mode), /* amdgpu_dm_plane_get_plane_formats() */ KUNIT_CASE(dm_test_get_plane_formats), + KUNIT_CASE(dm_test_get_plane_formats_overlay_universal_cap), /* amdgpu_dm_plane_get_plane_modifiers() */ KUNIT_CASE(dm_test_get_plane_modifiers), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx9), + KUNIT_CASE(dm_test_get_plane_modifiers_rv), + KUNIT_CASE(dm_test_get_plane_modifiers_rv_constant_encode), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx10_1), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx10_3), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx11_64k_first), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx11_256k_first), + KUNIT_CASE(dm_test_get_plane_modifiers_gfx12), /* amdgpu_dm_plane_fill_dc_scaling_info() */ KUNIT_CASE(dm_test_fill_dc_scaling_info), + KUNIT_CASE(dm_test_fill_dc_scaling_info_nv12_dcn1x), + KUNIT_CASE(dm_test_fill_dc_scaling_info_plane_caps), /* amdgpu_dm_plane_get_min_max_dc_plane_scaling() */ KUNIT_CASE(dm_test_get_min_max_dc_plane_scaling), + KUNIT_CASE(dm_test_get_min_max_dc_plane_scaling_fp16), /* amdgpu_dm_plane_fill_plane_buffer_attributes() */ - KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx8), + KUNIT_CASE(dm_test_fill_plane_buffer_attributes_video), + KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx12), /* amdgpu_dm_plane_get_cursor_position() */ KUNIT_CASE(dm_test_get_cursor_position), + KUNIT_CASE(dm_test_get_cursor_position_bad_size), /* amdgpu_dm_plane_format_mod_supported() */ KUNIT_CASE(dm_test_format_mod_supported), - /* amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers() */ + KUNIT_CASE(dm_test_format_mod_supported_d_swizzle_reject), + /* amdgpu_dm_plane_fill_gfx12_attrs_from_modifiers() */ KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_from_modifiers), - /* amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers() */ + KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_block0), + KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_block_unconstrained), + KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_validate_fails), + /* amdgpu_dm_plane_fill_gfx9_attrs_from_modifiers() */ KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_from_modifiers), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_validate_fails), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_rbplus_64b_no_128bcl), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_rbplus_128b), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_rbplus_unconstrained), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_gfx9_64b), + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_dcc_gfx9_unconstrained), /* amdgpu_dm_plane_helper_check_state() */ KUNIT_CASE(dm_test_helper_check_state_viewport_reject), + KUNIT_CASE(dm_test_helper_check_state_small_viewport_width), + KUNIT_CASE(dm_test_helper_check_state_small_viewport_height), + KUNIT_CASE(dm_test_helper_check_state_bottom_clipped_height), + KUNIT_CASE(dm_test_helper_check_state_scaling_caps), + /* amdgpu_dm_plane_atomic_async_check() */ + KUNIT_CASE(dm_test_atomic_async_check_rejects), + KUNIT_CASE(dm_test_atomic_async_check_overlay_cursor), + /* amdgpu_dm_plane_atomic_check() */ + KUNIT_CASE(dm_test_atomic_check_no_dc_state), + KUNIT_CASE(dm_test_atomic_check_missing_crtc_state), + KUNIT_CASE(dm_test_atomic_check_helper_failure), + KUNIT_CASE(dm_test_atomic_check_color_pipeline_conflict), + KUNIT_CASE(dm_test_atomic_check_scaling_failure), + /* amdgpu_dm_plane_panic_flush() */ + KUNIT_CASE(dm_test_panic_flush_no_dc_state), + /* amdgpu_dm_plane_drm_plane_reset() */ + KUNIT_CASE(dm_test_plane_reset_initializes_state), + /* amdgpu_dm_plane_drm_plane_duplicate_state() */ + KUNIT_CASE(dm_test_plane_duplicate_state_copies_fields), + /* amdgpu_dm_plane_drm_plane_destroy_state() */ + KUNIT_CASE(dm_test_plane_destroy_state_minimal), /* amdgpu_dm_plane_add_modifier() */ KUNIT_CASE(dm_test_add_modifier_appends_value), KUNIT_CASE(dm_test_add_modifier_grows_capacity), KUNIT_CASE(dm_test_add_modifier_noop_when_mods_null), - /* amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags() */ - KUNIT_CASE(dm_test_fill_gfx8_tiling_info_2d_tiled), - KUNIT_CASE(dm_test_fill_gfx8_tiling_info_1d_tiled), - KUNIT_CASE(dm_test_fill_gfx8_tiling_info_other_mode), /* amdgpu_dm_plane_fill_gfx9_tiling_info_from_device() */ KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_pre_10_3), KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_10_3_plus), @@ -1212,6 +3119,8 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_validate_dcc_disabled_returns_success), KUNIT_CASE(dm_test_validate_dcc_video_non_gfx12_fails), KUNIT_CASE(dm_test_validate_dcc_missing_cap_func_fails), + KUNIT_CASE(dm_test_validate_dcc_cap_callback_fails), + KUNIT_CASE(dm_test_validate_dcc_not_capable_fails), KUNIT_CASE(dm_test_validate_dcc_success_and_scan_mapping), KUNIT_CASE(dm_test_validate_dcc_independent_64b_mismatch_fails), {} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c index 8d1d26bfcc16..fbd07af60230 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c @@ -16,6 +16,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_pp_smu.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* ---- Stub DPM layer ---- */ @@ -2321,6 +2322,23 @@ static void dm_test_nv_get_uclk_dpm_states_fail(struct kunit *test) (int)PP_SMU_RESULT_FAIL); } +/* Tests for amdgpu_dm_smu_write_watermarks_table() */ + +/** + * dm_test_smu_write_watermarks_table_default - Test watermarks table skips non-Navi1x IP versions + * @test: The KUnit test context + */ +static void dm_test_smu_write_watermarks_table_default(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + + /* + * A zeroed adev reports DCE IP version 0, which is not one of the + * Navi1x versions handled by the switch, so the function returns early. + */ + KUNIT_EXPECT_EQ(test, amdgpu_dm_smu_write_watermarks_table(adev), 0); +} + static struct kunit_case dm_pp_smu_test_cases[] = { /* get_default_clock_levels */ KUNIT_CASE(dm_test_default_clock_levels_display), @@ -2440,6 +2458,8 @@ static struct kunit_case dm_pp_smu_test_cases[] = { KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_ok), KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_unsupported), KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_fail), + /* amdgpu_dm_smu_write_watermarks_table */ + KUNIT_CASE(dm_test_smu_write_watermarks_table_default), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c index 09bd98e93047..0a484ea7c0e8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c @@ -46,19 +46,10 @@ static struct core_power *create_test_power_module(struct kunit *test, static struct dc_link *alloc_test_psrsu_link(struct kunit *test) { - struct dc_link *link = dm_kunit_alloc_link(test); - struct dc_context *ctx; - struct dc *dc; + struct dc_link *link = dm_kunit_alloc_link_with_ctx(test); + struct dc_context *ctx = link->ctx; + struct dc *dc = ctx->dc; - ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx); - - dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dc); - - link->ctx = ctx; - ctx->dc = dc; - dc->ctx = ctx; dc->caps.dmcub_support = true; ctx->dce_version = DCN_VERSION_3_1; link->dpcd_caps.edp_rev = DP_EDP_14; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c index a09f31ee0a2a..150c53a68bed 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c @@ -6,6 +6,7 @@ */ #include +#include #include "dc.h" #include "amdgpu_mode.h" @@ -83,11 +84,75 @@ static void dm_test_quirks_no_dmi_match_both_false(struct kunit *test) KUNIT_EXPECT_FALSE(test, dm->edp0_on_dp1_quirk); } +/* Tests for dm_should_disable_stutter() */ + +/** + * dm_test_should_disable_stutter_match - Test the quirk device matches + * @test: The KUnit test context + */ +static void dm_test_should_disable_stutter_match(struct kunit *test) +{ + struct pci_dev *pdev; + + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + pdev->vendor = 0x1002; + pdev->device = 0x15dd; + pdev->subsystem_vendor = 0x1002; + pdev->subsystem_device = 0x15dd; + pdev->revision = 0xc8; + + KUNIT_EXPECT_TRUE(test, dm_should_disable_stutter(pdev)); +} + +/** + * dm_test_should_disable_stutter_no_match - Test a non-quirk device does not match + * @test: The KUnit test context + */ +static void dm_test_should_disable_stutter_no_match(struct kunit *test) +{ + struct pci_dev *pdev; + + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + pdev->vendor = 0x1002; + pdev->device = 0x1234; + + KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev)); +} + +/** + * dm_test_should_disable_stutter_revision_differs - Test a partial match (revision) fails + * @test: The KUnit test context + */ +static void dm_test_should_disable_stutter_revision_differs(struct kunit *test) +{ + struct pci_dev *pdev; + + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + /* Everything matches the quirk except the revision */ + pdev->vendor = 0x1002; + pdev->device = 0x15dd; + pdev->subsystem_vendor = 0x1002; + pdev->subsystem_device = 0x15dd; + pdev->revision = 0x00; + + KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev)); +} + static struct kunit_case amdgpu_dm_quirks_tests[] = { /* retrieve_dmi_info */ KUNIT_CASE(dm_test_quirks_aux_hpd_discon_reset), KUNIT_CASE(dm_test_quirks_edp0_on_dp1_reset), KUNIT_CASE(dm_test_quirks_no_dmi_match_both_false), + /* dm_should_disable_stutter */ + KUNIT_CASE(dm_test_should_disable_stutter_match), + KUNIT_CASE(dm_test_should_disable_stutter_no_match), + KUNIT_CASE(dm_test_should_disable_stutter_revision_differs), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 0b29bf0a7d04..3c10eec9b1e0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -6,10 +6,245 @@ */ #include +#include +#include +#include +#include +#include +#include #include "dc.h" +#include "inc/core_types.h" +#include "amd_shared.h" +#include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +/* Tests for simple DM callbacks */ + +/** + * dm_test_is_idle - Test placeholder idle callback returns true + * @test: The KUnit test context + */ +static void dm_test_is_idle(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, dm_is_idle(NULL)); +} + +/** + * dm_test_wait_for_idle - Test placeholder wait-for-idle callback returns success + * @test: The KUnit test context + */ +static void dm_test_wait_for_idle(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_wait_for_idle(NULL), 0); +} + +/** + * dm_test_soft_reset - Test placeholder soft-reset callback returns success + * @test: The KUnit test context + */ +static void dm_test_soft_reset(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_soft_reset(NULL), 0); +} + +/** + * dm_test_set_clockgating_state - Test placeholder clockgating callback returns success + * @test: The KUnit test context + */ +static void dm_test_set_clockgating_state(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_set_clockgating_state(NULL, AMD_CG_STATE_GATE), 0); +} + +/** + * dm_test_set_powergating_state - Test placeholder powergating callback returns success + * @test: The KUnit test context + */ +static void dm_test_set_powergating_state(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_set_powergating_state(NULL, AMD_PG_STATE_GATE), 0); +} + +/** + * dm_test_bandwidth_update - Test placeholder bandwidth update is callable + * @test: The KUnit test context + */ +static void dm_test_bandwidth_update(struct kunit *test) +{ + dm_bandwidth_update(NULL); +} + +/** + * dm_test_crtc_complete_writeback_no_connector - Test no writeback connector returns false + * @test: The KUnit test context + */ +static void dm_test_crtc_complete_writeback_no_connector(struct kunit *test) +{ + struct amdgpu_crtc *acrtc; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_complete_writeback(acrtc)); +} + +/** + * dm_test_crtc_complete_writeback_not_pending - Test non-pending writeback returns false + * @test: The KUnit test context + */ +static void dm_test_crtc_complete_writeback_not_pending(struct kunit *test) +{ + struct amdgpu_crtc *acrtc; + struct drm_writeback_connector *wb_conn; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + wb_conn = kunit_kzalloc(test, sizeof(*wb_conn), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, wb_conn); + + spin_lock_init(&wb_conn->job_lock); + acrtc->wb_conn = wb_conn; + acrtc->wb_pending = false; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_complete_writeback(acrtc)); +} + +/** + * dm_test_vblank_get_counter_out_of_range - Test out-of-range CRTC returns zero + * @test: The KUnit test context + */ +static void dm_test_vblank_get_counter_out_of_range(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + + adev->mode_info.num_crtc = 1; + + KUNIT_EXPECT_EQ(test, dm_vblank_get_counter(adev, 1), 0U); +} + +/** + * dm_test_vblank_get_counter_no_stream - Test missing stream returns zero + * @test: The KUnit test context + */ +static void dm_test_vblank_get_counter_no_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + adev->mode_info.num_crtc = 1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, dm_vblank_get_counter(adev, 0), 0U); +} + +/** + * dm_test_crtc_get_scanoutpos_invalid_crtc - Test invalid CRTC returns -EINVAL + * @test: The KUnit test context + */ +static void dm_test_crtc_get_scanoutpos_invalid_crtc(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + u32 vbl = 0; + u32 position = 0; + + adev->mode_info.num_crtc = 1; + + KUNIT_EXPECT_EQ(test, dm_crtc_get_scanoutpos(adev, -1, &vbl, &position), + -EINVAL); + KUNIT_EXPECT_EQ(test, dm_crtc_get_scanoutpos(adev, 1, &vbl, &position), + -EINVAL); +} + +/** + * dm_test_crtc_get_scanoutpos_no_stream - Test missing stream returns zero + * @test: The KUnit test context + */ +static void dm_test_crtc_get_scanoutpos_no_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc; + u32 vbl = 0; + u32 position = 0; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + adev->mode_info.num_crtc = 1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, dm_crtc_get_scanoutpos(adev, 0, &vbl, &position), 0); + KUNIT_EXPECT_EQ(test, vbl, 0U); + KUNIT_EXPECT_EQ(test, position, 0U); +} + +/** + * dm_test_atomic_get_new_state_empty - Test empty atomic state has no DM state + * @test: The KUnit test context + */ +static void dm_test_atomic_get_new_state_empty(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + state->dev = &adev->ddev; + + KUNIT_EXPECT_NULL(test, dm_atomic_get_new_state(state)); +} + +/** + * dm_test_atomic_get_new_state_match - Test atomic state returns matching DM private state + * @test: The KUnit test context + */ +static void dm_test_atomic_get_new_state_match(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dm_atomic_state *dm_state; + struct drm_atomic_commit *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + state->private_objs = kunit_kzalloc(test, sizeof(*state->private_objs), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->private_objs); + + state->dev = &adev->ddev; + state->num_private_objs = 1; + state->private_objs[0].ptr = &adev->dm.atomic_obj; + state->private_objs[0].new_state = &dm_state->base; + + KUNIT_EXPECT_PTR_EQ(test, dm_atomic_get_new_state(state), dm_state); +} + +/** + * dm_test_atomic_destroy_state_no_context - Test destroying DM atomic state without a DC context + * @test: The KUnit test context + */ +static void dm_test_atomic_destroy_state_no_context(struct kunit *test) +{ + struct dm_atomic_state *dm_state; + + /* + * Use kzalloc(), not kunit_kzalloc(): dm_atomic_destroy_state() frees + * the state itself, so KUnit-managed memory would be double-freed. + */ + dm_state = kzalloc(sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + /* context == NULL: dc_state_release() is skipped and the state is freed. */ + dm_atomic_destroy_state(NULL, &dm_state->base); +} /* Tests for dm_plane_layer_index_cmp() */ @@ -294,156 +529,6 @@ static void dm_test_modereset_not_required_when_inactive_without_modeset(struct KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state)); } -/* Tests for dm_get_oriented_plane_size() */ - -/** - * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0 - * @test: The KUnit test context - */ -static void dm_test_oriented_plane_size_rotate_0(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int src_w = 0; - int src_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_0; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); - - KUNIT_EXPECT_EQ(test, src_w, 1920); - KUNIT_EXPECT_EQ(test, src_h, 1080); -} - -/** - * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate 90 - * @test: The KUnit test context - */ -static void dm_test_oriented_plane_size_rotate_90(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int src_w = 0; - int src_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_90; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); - - KUNIT_EXPECT_EQ(test, src_w, 1080); - KUNIT_EXPECT_EQ(test, src_h, 1920); -} - -/** - * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate 180 - * @test: The KUnit test context - */ -static void dm_test_oriented_plane_size_rotate_180(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int src_w = 0; - int src_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_180; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); - - KUNIT_EXPECT_EQ(test, src_w, 1920); - KUNIT_EXPECT_EQ(test, src_h, 1080); -} - -/** - * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate 270 - * @test: The KUnit test context - */ -static void dm_test_oriented_plane_size_rotate_270(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int src_w = 0; - int src_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_270; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); - - KUNIT_EXPECT_EQ(test, src_w, 1080); - KUNIT_EXPECT_EQ(test, src_h, 1920); -} - -/* Tests for dm_get_plane_scale() */ - -/** - * dm_test_get_plane_scale_identity - Test Get plane scale identity - * @test: The KUnit test context - */ -static void dm_test_get_plane_scale_identity(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int scale_w = 0; - int scale_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_0; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - plane_state.crtc_w = 1920; - plane_state.crtc_h = 1080; - - dm_get_plane_scale(&plane_state, &scale_w, &scale_h); - - KUNIT_EXPECT_EQ(test, scale_w, 1000); - KUNIT_EXPECT_EQ(test, scale_h, 1000); -} - -/** - * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate 90 identity - * @test: The KUnit test context - */ -static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int scale_w = 0; - int scale_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_90; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - plane_state.crtc_w = 1080; - plane_state.crtc_h = 1920; - - dm_get_plane_scale(&plane_state, &scale_w, &scale_h); - - KUNIT_EXPECT_EQ(test, scale_w, 1000); - KUNIT_EXPECT_EQ(test, scale_h, 1000); -} - -/** - * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src width - * @test: The KUnit test context - */ -static void dm_test_get_plane_scale_zero_src_width(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int scale_w = 0; - int scale_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_0; - plane_state.src_w = 0; - plane_state.src_h = 1080 << 16; - plane_state.crtc_w = 100; - plane_state.crtc_h = 200; - - dm_get_plane_scale(&plane_state, &scale_w, &scale_h); - - KUNIT_EXPECT_EQ(test, scale_w, 0); - KUNIT_EXPECT_EQ(test, scale_h, 185); -} - /* Tests for is_scaling_state_different() */ /** @@ -510,6 +595,28 @@ static void dm_test_scaling_state_underscan_enabled(struct kunit *test) KUNIT_EXPECT_TRUE(test, is_scaling_state_different(new_state, old_state)); } +/** + * dm_test_scaling_state_underscan_disabled - Test disabling underscan with borders differs + * @test: The KUnit test context + */ +static void dm_test_scaling_state_underscan_disabled(struct kunit *test) +{ + struct dm_connector_state *old_state; + struct dm_connector_state *new_state; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_state); + KUNIT_ASSERT_NOT_NULL(test, new_state); + + old_state->underscan_enable = true; + old_state->underscan_hborder = 16; + old_state->underscan_vborder = 16; + new_state->underscan_enable = false; + + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(new_state, old_state)); +} + /** * dm_test_scaling_state_underscan_border_changed - Test changed underscan borders differ * @test: The KUnit test context @@ -533,214 +640,6 @@ static void dm_test_scaling_state_underscan_border_changed(struct kunit *test) KUNIT_EXPECT_TRUE(test, is_scaling_state_different(a, b)); } -/* Tests for is_timing_unchanged_for_freesync() */ - -/** - * dm_test_timing_unchanged_null_args - Test NULL crtc states return false - * @test: The KUnit test context - */ -static void dm_test_timing_unchanged_null_args(struct kunit *test) -{ - struct drm_crtc_state crtc_state = { 0 }; - - KUNIT_EXPECT_FALSE(test, - is_timing_unchanged_for_freesync(NULL, &crtc_state)); - KUNIT_EXPECT_FALSE(test, - is_timing_unchanged_for_freesync(&crtc_state, NULL)); -} - -/** - * dm_test_timing_unchanged_identical_modes - Test identical modes are not "unchanged" - * @test: The KUnit test context - * - * The helper only returns true when vtotal/vsync shift (vrr) while the rest - * of the timing stays fixed, so identical modes must return false. - */ -static void dm_test_timing_unchanged_identical_modes(struct kunit *test) -{ - struct drm_crtc_state old_state = { 0 }; - struct drm_crtc_state new_state = { 0 }; - - old_state.mode.clock = 148500; - old_state.mode.hdisplay = 1920; - old_state.mode.vdisplay = 1080; - old_state.mode.htotal = 2200; - old_state.mode.vtotal = 1125; - new_state.mode = old_state.mode; - - KUNIT_EXPECT_FALSE(test, - is_timing_unchanged_for_freesync(&old_state, &new_state)); -} - -/** - * dm_test_timing_unchanged_vrr_shift - Test vrr-style vtotal/vsync shift is detected - * @test: The KUnit test context - */ -static void dm_test_timing_unchanged_vrr_shift(struct kunit *test) -{ - struct drm_crtc_state old_state = { 0 }; - struct drm_crtc_state new_state = { 0 }; - - old_state.mode.clock = 148500; - old_state.mode.hdisplay = 1920; - old_state.mode.vdisplay = 1080; - old_state.mode.htotal = 2200; - old_state.mode.vtotal = 1125; - old_state.mode.hsync_start = 2008; - old_state.mode.vsync_start = 1084; - old_state.mode.hsync_end = 2052; - old_state.mode.vsync_end = 1089; - - /* Same horizontal timing, vertical totals/sync shifted by 125 lines */ - new_state.mode = old_state.mode; - new_state.mode.vtotal = 1250; - new_state.mode.vsync_start = 1209; - new_state.mode.vsync_end = 1214; - - KUNIT_EXPECT_TRUE(test, - is_timing_unchanged_for_freesync(&old_state, &new_state)); -} - -/** - * dm_test_timing_unchanged_clock_changed - Test pixel clock change returns false - * @test: The KUnit test context - */ -static void dm_test_timing_unchanged_clock_changed(struct kunit *test) -{ - struct drm_crtc_state old_state = { 0 }; - struct drm_crtc_state new_state = { 0 }; - - old_state.mode.clock = 148500; - old_state.mode.htotal = 2200; - old_state.mode.vtotal = 1125; - old_state.mode.vsync_start = 1084; - old_state.mode.vsync_end = 1089; - - new_state.mode = old_state.mode; - new_state.mode.clock = 297000; - new_state.mode.vtotal = 1250; - new_state.mode.vsync_start = 1209; - new_state.mode.vsync_end = 1214; - - KUNIT_EXPECT_FALSE(test, - is_timing_unchanged_for_freesync(&old_state, &new_state)); -} - -/* Tests for set_freesync_fixed_config() */ - -/** - * dm_test_set_freesync_fixed_config_60hz - Test fixed refresh computed for 1080p60 - * @test: The KUnit test context - */ -static void dm_test_set_freesync_fixed_config_60hz(struct kunit *test) -{ - struct dm_crtc_state dm_crtc_state = { 0 }; - - dm_crtc_state.base.mode.clock = 148500; - dm_crtc_state.base.mode.htotal = 2200; - dm_crtc_state.base.mode.vtotal = 1125; - - set_freesync_fixed_config(&dm_crtc_state); - - KUNIT_EXPECT_EQ(test, (int)dm_crtc_state.freesync_config.state, - (int)VRR_STATE_ACTIVE_FIXED); - /* 148500 kHz / (2200 * 1125) = 60 Hz = 60000000 uHz */ - KUNIT_EXPECT_EQ(test, dm_crtc_state.freesync_config.fixed_refresh_in_uhz, - 60000000U); -} - -/* Tests for is_dc_timing_adjust_needed() */ - -/** - * dm_test_dc_timing_adjust_pending - Test a pending hw timing adjust forces true - * @test: The KUnit test context - */ -static void dm_test_dc_timing_adjust_pending(struct kunit *test) -{ - struct dm_crtc_state *old_state, *new_state; - struct dc_stream_state *stream; - - old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); - new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); - - new_state->stream = stream; - stream->adjust.timing_adjust_pending = 1; - - KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state)); -} - -/** - * dm_test_dc_timing_adjust_active_fixed - Test VRR active-fixed forces true - * @test: The KUnit test context - */ -static void dm_test_dc_timing_adjust_active_fixed(struct kunit *test) -{ - struct dm_crtc_state *old_state, *new_state; - struct dc_stream_state *stream; - - old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); - new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); - - new_state->stream = stream; - new_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; - - KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state)); -} - -/** - * dm_test_dc_timing_adjust_vrr_toggle - Test a change in vrr active state forces true - * @test: The KUnit test context - */ -static void dm_test_dc_timing_adjust_vrr_toggle(struct kunit *test) -{ - struct dm_crtc_state *old_state, *new_state; - struct dc_stream_state *stream; - - old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); - new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); - - new_state->stream = stream; - old_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; - new_state->freesync_config.state = VRR_STATE_INACTIVE; - - KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state)); -} - -/** - * dm_test_dc_timing_adjust_not_needed - Test steady-state timing needs no adjust - * @test: The KUnit test context - */ -static void dm_test_dc_timing_adjust_not_needed(struct kunit *test) -{ - struct dm_crtc_state *old_state, *new_state; - struct dc_stream_state *stream; - - old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); - new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); - - new_state->stream = stream; - old_state->freesync_config.state = VRR_STATE_INACTIVE; - new_state->freesync_config.state = VRR_STATE_INACTIVE; - - KUNIT_EXPECT_FALSE(test, is_dc_timing_adjust_needed(old_state, new_state)); -} - /* Tests for set_multisync_trigger_params() */ /** @@ -883,7 +782,391 @@ static void dm_test_master_stream_defaults_to_first(struct kunit *test) stream0); } +/* Tests for is_content_protection_different() */ + +struct dm_test_cp_ctx { + struct amdgpu_dm_connector *aconnector; + struct dm_connector_state *new_dm; /* also connector->state */ + struct dm_connector_state *old_dm; + struct drm_crtc_state *new_crtc; + struct drm_crtc_state *old_crtc; +}; + +static struct dm_test_cp_ctx *dm_test_cp_ctx_alloc(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->new_dm = kunit_kzalloc(test, sizeof(*ctx->new_dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->new_dm); + ctx->old_dm = kunit_kzalloc(test, sizeof(*ctx->old_dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->old_dm); + ctx->new_crtc = kunit_kzalloc(test, sizeof(*ctx->new_crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->new_crtc); + ctx->old_crtc = kunit_kzalloc(test, sizeof(*ctx->old_crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->old_crtc); + + /* connector->state must be the new dm connector state */ + ctx->aconnector->base.state = &ctx->new_dm->base; + ctx->aconnector->base.dpms = DRM_MODE_DPMS_ON; + + return ctx; +} + +static bool dm_test_cp_diff(struct dm_test_cp_ctx *ctx) +{ + return is_content_protection_different(ctx->new_crtc, ctx->old_crtc, + &ctx->new_dm->base, + &ctx->old_dm->base, + &ctx->aconnector->base, NULL); +} + +/** + * dm_test_cp_diff_hdcp_type_change - Test an HDCP content-type change forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_hdcp_type_change(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.hdcp_content_type = 0; + ctx->new_dm->base.hdcp_content_type = 1; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, + (int)DRM_MODE_CONTENT_PROTECTION_DESIRED); +} + +/** + * dm_test_cp_diff_reenable_mode_changed - Test ENABLED->DESIRED with modeset forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_reenable_mode_changed(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_crtc->mode_changed = true; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, + (int)DRM_MODE_CONTENT_PROTECTION_DESIRED); +} + +/** + * dm_test_cp_diff_reenable_no_change - Test ENABLED->DESIRED without modeset restores ENABLED + * @test: The KUnit test context + */ +static void dm_test_cp_diff_reenable_no_change(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_crtc->mode_changed = false; + + KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, + (int)DRM_MODE_CONTENT_PROTECTION_ENABLED); +} + +/** + * dm_test_cp_diff_undesired - Test UNDESIRED->UNDESIRED needs no update + * @test: The KUnit test context + */ +static void dm_test_cp_diff_undesired(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + + KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); +} + +/** + * dm_test_cp_diff_desired_mode_changed - Test DESIRED->DESIRED with modeset forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_desired_mode_changed(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_crtc->mode_changed = true; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); +} + +/** + * dm_test_cp_diff_desired_no_change - Test steady DESIRED->DESIRED needs no update + * @test: The KUnit test context + */ +static void dm_test_cp_diff_desired_no_change(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_crtc->mode_changed = false; + + KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); +} + +/** + * dm_test_cp_diff_update_hdcp_hotplug - Test the update_hdcp hot-plug path forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_update_hdcp_hotplug(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + struct dc_sink *sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, sink); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->update_hdcp = true; + ctx->aconnector->base.dpms = DRM_MODE_DPMS_ON; + ctx->aconnector->dc_sink = sink; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_FALSE(test, ctx->new_dm->update_hdcp); +} + +/** + * dm_test_cp_diff_stream_reenabled - Test the stream removed-and-re-enabled path forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_stream_reenabled(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + struct drm_crtc *crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, crtc); + crtc->enabled = true; + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->update_hdcp = true; + ctx->old_dm->base.crtc = NULL; + ctx->new_dm->base.crtc = crtc; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_FALSE(test, ctx->new_dm->update_hdcp); +} + +/** + * dm_test_cp_diff_s3_undesired_to_enabled - Test the S3 UNDESIRED->ENABLED path forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_s3_undesired_to_enabled(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, + (int)DRM_MODE_CONTENT_PROTECTION_DESIRED); +} + +/** + * dm_test_cp_diff_desired_to_enabled - Test DESIRED->ENABLED needs no update + * @test: The KUnit test context + */ +static void dm_test_cp_diff_desired_to_enabled(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + + KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); +} + +/** + * dm_test_cp_diff_desired_to_undesired - Test DESIRED->UNDESIRED forces update + * @test: The KUnit test context + */ +static void dm_test_cp_diff_desired_to_undesired(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); +} + +/* Tests for dm_enable_per_frame_crtc_master_sync() */ + +/** + * dm_test_per_frame_master_sync_single_stream - Test fewer than two streams is a no-op + * @test: The KUnit test context + */ +static void dm_test_per_frame_master_sync_single_stream(struct kunit *test) +{ + struct dc_state *context; + struct dc_stream_state *stream; + + context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, context); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->triggered_crtc_reset.enabled = true; + context->streams[0] = stream; + context->stream_count = 1; + + dm_enable_per_frame_crtc_master_sync(context); + + /* < 2 streams: early return, event_source stays NULL */ + KUNIT_EXPECT_NULL(test, stream->triggered_crtc_reset.event_source); +} + +/** + * dm_test_per_frame_master_sync_two_streams - Test the master is picked and applied + * @test: The KUnit test context + */ +static void dm_test_per_frame_master_sync_two_streams(struct kunit *test) +{ + struct dc_state *context; + struct dc_stream_state *stream0, *stream1; + + context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, context); + stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream0); + stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream1); + + /* stream0 60Hz, stream1 120Hz, both trigger-reset enabled */ + stream0->triggered_crtc_reset.enabled = true; + stream0->timing.pix_clk_100hz = 1485000; + stream0->timing.h_total = 2200; + stream0->timing.v_total = 1125; + stream1->triggered_crtc_reset.enabled = true; + stream1->timing.pix_clk_100hz = 2970000; + stream1->timing.h_total = 2200; + stream1->timing.v_total = 1125; + stream1->timing.flags.VSYNC_POSITIVE_POLARITY = 1; + + context->streams[0] = stream0; + context->streams[1] = stream1; + context->stream_count = 2; + + dm_enable_per_frame_crtc_master_sync(context); + + /* set_master_stream picks the highest refresh (stream1) as event source */ + KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source, + stream1); + KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source, + stream1); + /* set_multisync_trigger_params applied to enabled streams */ + KUNIT_EXPECT_EQ(test, (int)stream0->triggered_crtc_reset.event, + (int)CRTC_EVENT_VSYNC_RISING); + KUNIT_EXPECT_EQ(test, (int)stream0->triggered_crtc_reset.delay, + (int)TRIGGER_DELAY_NEXT_PIXEL); +} + +/** + * dm_test_per_frame_master_sync_skips_null_stream - Test NULL stream entries are skipped + * @test: The KUnit test context + */ +static void dm_test_per_frame_master_sync_skips_null_stream(struct kunit *test) +{ + struct dc_state *context; + struct dc_stream_state *stream; + + context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, context); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->triggered_crtc_reset.enabled = true; + stream->timing.pix_clk_100hz = 1485000; + stream->timing.h_total = 2200; + stream->timing.v_total = 1125; + context->streams[0] = stream; + context->streams[1] = NULL; + context->stream_count = 2; + + dm_enable_per_frame_crtc_master_sync(context); + + KUNIT_EXPECT_PTR_EQ(test, stream->triggered_crtc_reset.event_source, + stream); +} + +/* Tests for amdgpu_dm_apply_delay_after_dpcd_poweroff() */ + +/** + * dm_test_apply_delay_null_sink - Test a NULL sink returns without delay + * @test: The KUnit test context + */ +static void dm_test_apply_delay_null_sink(struct kunit *test) +{ + /* NULL sink: early return, no delay, no dereference */ + amdgpu_dm_apply_delay_after_dpcd_poweroff(NULL, NULL); +} + +/** + * dm_test_apply_delay_zero_wait - Test a zero wait interval skips the delay + * @test: The KUnit test context + */ +static void dm_test_apply_delay_zero_wait(struct kunit *test) +{ + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + /* wait == 0: no msleep, adev is unused so NULL is safe */ + sink->edid_caps.panel_patch.wait_after_dpcd_poweroff_ms = 0; + amdgpu_dm_apply_delay_after_dpcd_poweroff(NULL, sink); +} + +/** + * dm_test_apply_delay_nonzero_wait - Test a non-zero wait interval executes delay path + * @test: The KUnit test context + */ +static void dm_test_apply_delay_nonzero_wait(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->edid_caps.panel_patch.wait_after_dpcd_poweroff_ms = 1; + amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, sink); +} + static struct kunit_case amdgpu_dm_tests[] = { + /* Simple DM callbacks */ + KUNIT_CASE(dm_test_is_idle), + KUNIT_CASE(dm_test_wait_for_idle), + KUNIT_CASE(dm_test_soft_reset), + KUNIT_CASE(dm_test_set_clockgating_state), + KUNIT_CASE(dm_test_set_powergating_state), + KUNIT_CASE(dm_test_bandwidth_update), + KUNIT_CASE(dm_test_crtc_complete_writeback_no_connector), + KUNIT_CASE(dm_test_crtc_complete_writeback_not_pending), + KUNIT_CASE(dm_test_vblank_get_counter_out_of_range), + KUNIT_CASE(dm_test_vblank_get_counter_no_stream), + KUNIT_CASE(dm_test_crtc_get_scanoutpos_invalid_crtc), + KUNIT_CASE(dm_test_crtc_get_scanoutpos_no_stream), + KUNIT_CASE(dm_test_atomic_get_new_state_empty), + KUNIT_CASE(dm_test_atomic_get_new_state_match), + KUNIT_CASE(dm_test_atomic_destroy_state_no_context), /* dm_plane_layer_index_cmp */ KUNIT_CASE(dm_test_plane_layer_index_cmp_equal), KUNIT_CASE(dm_test_plane_layer_index_cmp_descending), @@ -901,32 +1184,12 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_modereset_required_when_inactive_and_modeset), KUNIT_CASE(dm_test_modereset_not_required_when_active_and_modeset), KUNIT_CASE(dm_test_modereset_not_required_when_inactive_without_modeset), - /* dm_get_oriented_plane_size */ - KUNIT_CASE(dm_test_oriented_plane_size_rotate_0), - KUNIT_CASE(dm_test_oriented_plane_size_rotate_90), - KUNIT_CASE(dm_test_oriented_plane_size_rotate_180), - KUNIT_CASE(dm_test_oriented_plane_size_rotate_270), - /* dm_get_plane_scale */ - KUNIT_CASE(dm_test_get_plane_scale_identity), - KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity), - KUNIT_CASE(dm_test_get_plane_scale_zero_src_width), /* is_scaling_state_different */ KUNIT_CASE(dm_test_scaling_state_same), KUNIT_CASE(dm_test_scaling_state_scaling_changed), KUNIT_CASE(dm_test_scaling_state_underscan_enabled), + KUNIT_CASE(dm_test_scaling_state_underscan_disabled), KUNIT_CASE(dm_test_scaling_state_underscan_border_changed), - /* is_timing_unchanged_for_freesync */ - KUNIT_CASE(dm_test_timing_unchanged_null_args), - KUNIT_CASE(dm_test_timing_unchanged_identical_modes), - KUNIT_CASE(dm_test_timing_unchanged_vrr_shift), - KUNIT_CASE(dm_test_timing_unchanged_clock_changed), - /* set_freesync_fixed_config */ - KUNIT_CASE(dm_test_set_freesync_fixed_config_60hz), - /* is_dc_timing_adjust_needed */ - KUNIT_CASE(dm_test_dc_timing_adjust_pending), - KUNIT_CASE(dm_test_dc_timing_adjust_active_fixed), - KUNIT_CASE(dm_test_dc_timing_adjust_vrr_toggle), - KUNIT_CASE(dm_test_dc_timing_adjust_not_needed), /* set_multisync_trigger_params */ KUNIT_CASE(dm_test_multisync_trigger_disabled), KUNIT_CASE(dm_test_multisync_trigger_rising), @@ -934,6 +1197,26 @@ static struct kunit_case amdgpu_dm_tests[] = { /* set_master_stream */ KUNIT_CASE(dm_test_master_stream_highest_refresh), KUNIT_CASE(dm_test_master_stream_defaults_to_first), + /* is_content_protection_different */ + KUNIT_CASE(dm_test_cp_diff_hdcp_type_change), + KUNIT_CASE(dm_test_cp_diff_reenable_mode_changed), + KUNIT_CASE(dm_test_cp_diff_reenable_no_change), + KUNIT_CASE(dm_test_cp_diff_undesired), + KUNIT_CASE(dm_test_cp_diff_desired_mode_changed), + KUNIT_CASE(dm_test_cp_diff_desired_no_change), + KUNIT_CASE(dm_test_cp_diff_update_hdcp_hotplug), + KUNIT_CASE(dm_test_cp_diff_stream_reenabled), + KUNIT_CASE(dm_test_cp_diff_s3_undesired_to_enabled), + KUNIT_CASE(dm_test_cp_diff_desired_to_enabled), + KUNIT_CASE(dm_test_cp_diff_desired_to_undesired), + /* dm_enable_per_frame_crtc_master_sync */ + KUNIT_CASE(dm_test_per_frame_master_sync_single_stream), + KUNIT_CASE(dm_test_per_frame_master_sync_two_streams), + KUNIT_CASE(dm_test_per_frame_master_sync_skips_null_stream), + /* amdgpu_dm_apply_delay_after_dpcd_poweroff */ + KUNIT_CASE(dm_test_apply_delay_null_sink), + KUNIT_CASE(dm_test_apply_delay_zero_wait), + KUNIT_CASE(dm_test_apply_delay_nonzero_wait), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c index c71f61a2438d..3454cf371414 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -266,7 +267,7 @@ static void dm_test_wb_get_modes_returns_modes(struct kunit *test) DRIVER_MODESET | DRIVER_ATOMIC); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_wb_test_connector_funcs, @@ -301,7 +302,7 @@ static void dm_test_wb_get_modes_bounded_by_max(struct kunit *test) DRIVER_MODESET | DRIVER_ATOMIC); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_wb_test_connector_funcs, @@ -364,6 +365,47 @@ static void dm_test_wb_connector_init_success(struct kunit *test) KUNIT_EXPECT_EQ(test, wbcon->base.encoder.possible_crtcs, 0x1); } +/* Tests for amdgpu_dm_wb_prepare_job / amdgpu_dm_wb_cleanup_job */ + +/** + * dm_test_wb_prepare_job_no_fb - Verify prepare_job early return without a framebuffer + * @test: KUnit test context + * + * When job->fb is NULL there is nothing to pin, so amdgpu_dm_wb_prepare_job() + * must return 0 without touching any buffer object. + */ +static void dm_test_wb_prepare_job_no_fb(struct kunit *test) +{ + struct drm_writeback_job *job; + int ret; + + job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, job); + + job->fb = NULL; + ret = amdgpu_dm_wb_prepare_job(NULL, job); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/** + * dm_test_wb_cleanup_job_no_fb - Verify cleanup_job early return without a framebuffer + * @test: KUnit test context + * + * When job->fb is NULL there is nothing to unpin, so amdgpu_dm_wb_cleanup_job() + * must return immediately. + */ +static void dm_test_wb_cleanup_job_no_fb(struct kunit *test) +{ + struct drm_writeback_job *job; + + job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, job); + + job->fb = NULL; + /* Should return without dereferencing any buffer object. */ + amdgpu_dm_wb_cleanup_job(NULL, job); +} + static struct kunit_case dm_wb_test_cases[] = { /* amdgpu_dm_wb_encoder_atomic_check */ KUNIT_CASE(dm_test_wb_atomic_check_no_job), @@ -378,6 +420,9 @@ static struct kunit_case dm_wb_test_cases[] = { KUNIT_CASE(dm_test_wb_get_modes_bounded_by_max), /* amdgpu_dm_wb_connector_init */ KUNIT_CASE(dm_test_wb_connector_init_success), + /* amdgpu_dm_wb_prepare_job / amdgpu_dm_wb_cleanup_job */ + KUNIT_CASE(dm_test_wb_prepare_job_no_fb), + KUNIT_CASE(dm_test_wb_cleanup_job_no_fb), {} }; diff --git a/drivers/gpu/drm/amd/display/dc/basics/conversion.c b/drivers/gpu/drm/amd/display/dc/basics/conversion.c index a1f2350388e2..1c8d4907245a 100644 --- a/drivers/gpu/drm/amd/display/dc/basics/conversion.c +++ b/drivers/gpu/drm/amd/display/dc/basics/conversion.c @@ -26,11 +26,7 @@ #include "dm_services.h" #include "basics/conversion.h" -#define DIVIDER 10000 -/* S2D13 value in [-3.00...0.9999] */ -#define S2D13_MIN (-3 * DIVIDER) -#define S2D13_MAX (3 * DIVIDER) uint16_t fixed_point_to_int_frac( struct fixed31_32 arg, @@ -74,28 +70,44 @@ uint16_t fixed_point_to_int_frac( return result; } /* - * convert_float_matrix - This converts a double into HW register spec defined format S2D13. + * convert_float_matrix - This converts a double into HW register spec defined format S2D13 / S3D12. */ void convert_float_matrix( uint16_t *matrix, - struct fixed31_32 *flt, + const struct fixed31_32 *flt, + enum cm_gamut_coef_format format, uint32_t buffer_size) { - const struct fixed31_32 min_2_13 = - dc_fixpt_from_fraction(S2D13_MIN, DIVIDER); - const struct fixed31_32 max_2_13 = - dc_fixpt_from_fraction(S2D13_MAX, DIVIDER); + struct fixed31_32 min; + struct fixed31_32 max; + uint8_t num_int_bits; + uint8_t num_dec_bits; uint32_t i; + if (format == CM_GAMUT_REMAP_COEF_FORMAT_S2_13) { + min = dc_fixpt_from_fraction(S2D13_MIN, DIVIDER); + max = dc_fixpt_from_fraction(S2D13_MAX, DIVIDER); + num_int_bits = 2; + num_dec_bits = 13; + } else if (format == CM_GAMUT_REMAP_COEF_FORMAT_S3_12) { + min = dc_fixpt_from_fraction(S3D12_MIN, DIVIDER); + max = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); + num_int_bits = 3; + num_dec_bits = 12; + } else { + ASSERT(false); + return; + } + for (i = 0; i < buffer_size; ++i) { uint32_t reg_value = fixed_point_to_int_frac( dc_fixpt_clamp( flt[i], - min_2_13, - max_2_13), - 2, - 13); + min, + max), + num_int_bits, + num_dec_bits); matrix[i] = (uint16_t)reg_value; } @@ -129,10 +141,27 @@ static struct fixed31_32 int_frac_to_fixed_point(uint16_t arg, */ void convert_hw_matrix(struct fixed31_32 *matrix, uint16_t *reg, + enum cm_gamut_coef_format format, uint32_t buffer_size) { - for (uint32_t i = 0; i < buffer_size; ++i) - matrix[i] = int_frac_to_fixed_point(reg[i], 2, 13); + uint8_t num_int_bits; + uint8_t num_dec_bits; + uint32_t i; + + if (format == CM_GAMUT_REMAP_COEF_FORMAT_S2_13) { + num_int_bits = 2; + num_dec_bits = 13; + } else if (format == CM_GAMUT_REMAP_COEF_FORMAT_S3_12) { + num_int_bits = 3; + num_dec_bits = 12; + } else { + ASSERT(false); + return; + } + + for (i = 0; i < buffer_size; ++i) + matrix[i] = int_frac_to_fixed_point(reg[i], + num_int_bits, num_dec_bits); } static uint32_t find_gcd(uint32_t a, uint32_t b) diff --git a/drivers/gpu/drm/amd/display/dc/basics/conversion.h b/drivers/gpu/drm/amd/display/dc/basics/conversion.h index a433cef78496..c7bed2539fa8 100644 --- a/drivers/gpu/drm/amd/display/dc/basics/conversion.h +++ b/drivers/gpu/drm/amd/display/dc/basics/conversion.h @@ -28,6 +28,14 @@ #include "include/fixed31_32.h" +#define DIVIDER 10000 + +#define S2D13_MIN (-4 * DIVIDER) +#define S2D13_MAX (39999) + +#define S3D12_MIN (-8 * DIVIDER) +#define S3D12_MAX (79998) + uint16_t fixed_point_to_int_frac( struct fixed31_32 arg, uint8_t integer_bits, @@ -35,7 +43,8 @@ uint16_t fixed_point_to_int_frac( void convert_float_matrix( uint16_t *matrix, - struct fixed31_32 *flt, + const struct fixed31_32 *flt, + enum cm_gamut_coef_format format, uint32_t buffer_size); void reduce_fraction(uint32_t num, uint32_t den, @@ -43,6 +52,7 @@ void reduce_fraction(uint32_t num, uint32_t den, void convert_hw_matrix(struct fixed31_32 *matrix, uint16_t *reg, + enum cm_gamut_coef_format format, uint32_t buffer_size); static inline unsigned int log_2(unsigned int num) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c index d01e5969a670..c7b9bad93a93 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c @@ -375,6 +375,24 @@ void dcn42_enable_pme_wa(struct clk_mgr *clk_mgr_base) dcn42_smu_enable_pme_wa(clk_mgr); } +void dcn42_notify_cstate_disable(struct clk_mgr *clk_mgr_base, bool disable) +{ + struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base); + bool target_allow = !disable; + + DC_LOGGER_INIT(clk_mgr_base->ctx->logger); + + /* Idempotent: only send when the cached vote actually changes. */ + if (clk_mgr_base->clks.cstate_allow == target_allow) + return; + + if (dcn42_smu_set_df_cstate_disable(clk_mgr, disable)) + clk_mgr_base->clks.cstate_allow = target_allow; + else + DC_LOG_WARNING("%s: PMFW did not ack DfCstateDisable(%s); leaving cstate_allow=%d to retry\n", + __func__, disable ? "Disable" : "Allow", clk_mgr_base->clks.cstate_allow); +} + bool dcn42_are_clock_states_equal(struct dc_clocks *a, struct dc_clocks *b) @@ -594,6 +612,15 @@ void dcn42_init_clocks(struct clk_mgr *clk_mgr_base) init_clk_states(clk_mgr_base); + /* + * DF C-state policy + * D0 entry must NOT send a PMFW message, but must unconditionally clear + * the cached vote so the next allow-side transition (prepare_bandwidth + * or dc_power_down_on_boot) is guaranteed to issue a fresh + * DfCstateDisable(Allow) and resync DAL with PMFW. + */ + clk_mgr_base->clks.cstate_allow = false; + // to adjust dp_dto reference clock if ssc is enable otherwise to apply dprefclk if (dcn42_is_spll_ssc_enabled(clk_mgr_base)) clk_mgr_base->dp_dto_source_clock_in_khz = @@ -999,15 +1026,21 @@ void dcn42_get_smu_clocks(struct clk_mgr_internal *clk_mgr_int) clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_fclk_levels = dpm_clks->NumFclkLevelsEnabled; clk_mgr_base->bw_params->clk_table.num_entries = dpm_clks->NumFclkLevelsEnabled; - /* Memory Pstate table is in reverse order*/ + /* Memory Pstate table is in reverse order for dcn42. Proper way to map this is for pmfw to provide an fclk indexed uclk key since we consume fclk matched pairs.*/ ASSERT(dpm_clks->NumMemPstatesEnabled <= NUM_MEM_PSTATE_LEVELS); if (dpm_clks->NumMemPstatesEnabled > NUM_MEM_PSTATE_LEVELS) dpm_clks->NumMemPstatesEnabled = NUM_MEM_PSTATE_LEVELS; - for (i = 0; i < dpm_clks->NumMemPstatesEnabled; i++) { - clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].memclk_mhz = dpm_clks->MemPstateTable[i].MemClk; - clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[i].WckRatio) ; + for (i = 0; i < dpm_clks->NumDcfClkLevelsEnabled; i++) { + if (i < dpm_clks->NumMemPstatesEnabled) { + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].memclk_mhz = dpm_clks->MemPstateTable[i].MemClk; + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[i].WckRatio); + } else { + clk_mgr_base->bw_params->clk_table.entries[i].memclk_mhz = dpm_clks->MemPstateTable[0].MemClk; + clk_mgr_base->bw_params->clk_table.entries[i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[0].WckRatio); + } } - clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_memclk_levels = dpm_clks->NumMemPstatesEnabled; + + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_memclk_levels = dpm_clks->NumDcfClkLevelsEnabled; /* DTBCLK*/ clk_mgr_base->bw_params->clk_table.entries[0].dtbclk_mhz = 600; /* Fixed on platform */ @@ -1032,6 +1065,7 @@ static struct clk_mgr_funcs dcn42_funcs = { .get_max_clock_khz = dcn42_get_max_clock_khz, .get_dispclk_from_dentist = dcn42_get_dispclk_from_dentist, .is_smu_present = dcn42_is_smu_present, + .notify_cstate_disable = dcn42_notify_cstate_disable, }; struct clk_mgr_funcs dcn42_fpga_funcs = { diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.h b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.h index 9568ca06f00f..330242747fff 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.h +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.h @@ -67,6 +67,7 @@ unsigned int dcn42_convert_wck_ratio(uint8_t wck_ratio); extern struct dcn42_ss_info_table dcn42_ss_info_table; void dcn42_build_watermark_ranges(struct clk_bw_params *bw_params, struct dcn42_watermarks *table); void dcn42_enable_pme_wa(struct clk_mgr *clk_mgr_base); +void dcn42_notify_cstate_disable(struct clk_mgr *clk_mgr_base, bool disable); void dcn42_notify_wm_ranges(struct clk_mgr *clk_mgr_base); void dcn42_set_low_power_state(struct clk_mgr *clk_mgr_base); void dcn42_exit_low_power_state(struct clk_mgr *clk_mgr_base); diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.c index 6d0012b7d6dc..5e8d979f25f0 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.c @@ -85,7 +85,8 @@ #define DALSMC_MSG_DispIPS2Exit 0x11 ///< Display IPS2 exit #define DALSMC_MSG_QueryIPS2Support 0x12 ///< Return 1: support; else not supported -#define DALSMC_Message_Count 0x13 ///< Total number of VBIS and DAL messages +#define DALSMC_MSG_DfCstateDisable 0x13 ///< DCN DF C-state vote (PMFW FWDEV-193711): param 0 = Allow, 1 = Disable +#define DALSMC_Message_Count 0x14 ///< Total number of VBIS and DAL messages /** @}*/ @@ -428,3 +429,29 @@ void dcn42_smu_set_dtbclk(struct clk_mgr_internal *clk_mgr, bool enable) enable); smu_print("%s: smu_set_dtbclk = %d\n", __func__, enable ? 1 : 0); } + +/* + * Vote DCN's DF C-state policy to PMFW. param: 0 = Allow, 1 = Disable. + * Returns true only when PMFW acknowledged the vote (or there is no SMU to + * talk to, in which case there is no DF arbiter to satisfy). On a non-OK + * response the caller must NOT update its cached cstate_allow so the next + * transition retries + */ +bool dcn42_smu_set_df_cstate_disable(struct clk_mgr_internal *clk_mgr, bool disable) +{ + int retv; + + if (!clk_mgr->smu_present) + return true; + + retv = dcn42_smu_send_msg_with_param( + clk_mgr, + DALSMC_MSG_DfCstateDisable, + disable ? 1 : 0); + + smu_print("%s: DfCstateDisable param = %d, return = %d\n", + __func__, disable ? 1 : 0, retv); + + /* dcn42_smu_send_msg_with_param() returns -1 on a non-OK PMFW response. */ + return retv != -1; +} diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.h b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.h index 8ba7ff04dc05..10c67326ff7e 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.h +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.h @@ -187,4 +187,6 @@ void dcn42_vbios_smu_enable_48mhz_tmdp_refclk_pwrdwn(struct clk_mgr_internal *cl int dcn42_smu_get_dtbclk(struct clk_mgr_internal *clk_mgr); int dcn42_smu_get_dprefclk(struct clk_mgr_internal *clk_mgr); +bool dcn42_smu_set_df_cstate_disable(struct clk_mgr_internal *clk_mgr, bool disable); + #endif /* DAL_DC_42_SMU_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42b/dcn42b_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42b/dcn42b_clk_mgr.c index 4be5abdc60ea..2c2ba09221e4 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42b/dcn42b_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42b/dcn42b_clk_mgr.c @@ -221,10 +221,6 @@ static void init_clk_states(struct clk_mgr *clk_mgr) clk_mgr->clks.zstate_support = DCN_ZSTATE_SUPPORT_UNKNOWN; } -/* dcn42b_get_dpm_table_from_smu removed: reuse dcn42_get_dpm_table_from_smu. - * Function is identical - only uses SMU calls, no hardware register differences. - */ - void dcn42b_init_clocks(struct clk_mgr *clk_mgr_base) { struct clk_mgr_internal *clk_mgr_int = TO_CLK_MGR_INTERNAL(clk_mgr_base); @@ -412,6 +408,176 @@ static struct clk_mgr_funcs dcn42b_funcs = { .is_smu_present = dcn42_is_smu_present, }; +typedef struct { + uint32_t DcfClocks[NUM_DCFCLK_DPM_LEVELS]; + uint32_t DispClocks[NUM_DISPCLK_DPM_LEVELS]; + uint32_t DppClocks[NUM_DPPCLK_DPM_LEVELS]; + uint32_t SocClocks[NUM_SOCCLK_DPM_LEVELS]; + uint32_t VPEClocks[NUM_VPE_DPM_LEVELS]; + uint32_t FclkClocks_Freq[NUM_FCLK_DPM_LEVELS]; + uint32_t FclkClocks_Voltage[NUM_FCLK_DPM_LEVELS]; + uint32_t SocVoltage[NUM_SOC_VOLTAGE_LEVELS]; + MemPstateTable_t MemPstateTable[NUM_MEM_PSTATE_LEVELS]; + + uint8_t NumDcfClkLevelsEnabled; + uint8_t NumDispClkLevelsEnabled; //Applies to both Dispclk and Dppclk + uint8_t NumSocClkLevelsEnabled; + uint8_t VpeClkLevelsEnabled; + + uint8_t NumMemPstatesEnabled; + uint8_t NumFclkLevelsEnabled; + uint8_t Spare1; + uint8_t Spare2; + + uint8_t Spare3; + uint8_t Spare4; + uint8_t Spare5[2]; + + uint32_t MinGfxClk; + uint32_t MaxGfxClk; + + uint32_t Spare6[8]; + uint32_t Spare7[8]; + uint32_t Spare8[8]; + uint32_t Spare9[8]; +} DpmClocks_t_dcn42b; +struct dcn42b_smu_dpm_clks { + DpmClocks_t_dcn42b *dpm_clks; + union large_integer mc_address; +}; + +static void dcn42b_get_dpm_table_from_smu(struct clk_mgr_internal *clk_mgr, + struct dcn42b_smu_dpm_clks *smu_dpm_clks) +{ + DpmClocks_t_dcn42b *table = smu_dpm_clks->dpm_clks; + + if (!clk_mgr->smu_ver) + return; + + if (!table || smu_dpm_clks->mc_address.quad_part == 0) + return; + + memset(table, 0, sizeof(*table)); + + dcn42_smu_set_dram_addr_high(clk_mgr, + smu_dpm_clks->mc_address.high_part); + dcn42_smu_set_dram_addr_low(clk_mgr, + smu_dpm_clks->mc_address.low_part); + dcn42_smu_transfer_dpm_table_smu_2_dram(clk_mgr); +} + +static void dcn42b_get_smu_clocks(struct clk_mgr_internal *clk_mgr_int) +{ + struct clk_mgr *clk_mgr_base = &clk_mgr_int->base; + struct dcn42b_smu_dpm_clks smu_dpm_clks = { 0 }; + + DC_LOGGER_INIT(clk_mgr_base->ctx->logger); + (void)dc_logger; + + smu_dpm_clks.dpm_clks = (DpmClocks_t_dcn42b *)dm_helpers_allocate_gpu_mem( + clk_mgr_base->ctx, + DC_MEM_ALLOC_TYPE_GART, + sizeof(DpmClocks_t_dcn42b), + &smu_dpm_clks.mc_address.quad_part); + + ASSERT(smu_dpm_clks.dpm_clks); + if (clk_mgr_base->ctx->dc->debug.pstate_enabled && smu_dpm_clks.mc_address.quad_part != 0) { + int i; + DpmClocks_t_dcn42b *dpm_clks = smu_dpm_clks.dpm_clks; + + dcn42b_get_dpm_table_from_smu(clk_mgr_int, &smu_dpm_clks); + DC_LOG_SMU("NumDcfClkLevelsEnabled: %d\n" + "NumDispClkLevelsEnabled: %d\n" + "NumSocClkLevelsEnabled: %d\n" + "VpeClkLevelsEnabled: %d\n" + "FClkLevelsEnabled: %d\n" + "NumMemPstatesEnabled: %d\n" + "MinGfxClk: %d\n" + "MaxGfxClk: %d\n", + dpm_clks->NumDcfClkLevelsEnabled, + dpm_clks->NumDispClkLevelsEnabled, + dpm_clks->NumSocClkLevelsEnabled, + dpm_clks->VpeClkLevelsEnabled, + dpm_clks->NumFclkLevelsEnabled, + dpm_clks->NumMemPstatesEnabled, + dpm_clks->MinGfxClk, + dpm_clks->MaxGfxClk); + + for (i = 0; i < NUM_DCFCLK_DPM_LEVELS; i++) + DC_LOG_SMU("dpm_clks->DcfClocks[%d] = %d\n", i, dpm_clks->DcfClocks[i]); + for (i = 0; i < NUM_DISPCLK_DPM_LEVELS; i++) + DC_LOG_SMU("dpm_clks->DispClocks[%d] = %d\n", i, dpm_clks->DispClocks[i]); + for (i = 0; i < NUM_SOCCLK_DPM_LEVELS; i++) + DC_LOG_SMU("dpm_clks->SocClocks[%d] = %d\n", i, dpm_clks->SocClocks[i]); + for (i = 0; i < NUM_FCLK_DPM_LEVELS; i++) { + DC_LOG_SMU("dpm_clks->FclkClocks_Freq[%d] = %d\n", i, dpm_clks->FclkClocks_Freq[i]); + DC_LOG_SMU("dpm_clks->FclkClocks_Voltage[%d] = %d\n", i, dpm_clks->FclkClocks_Voltage[i]); + } + for (i = 0; i < NUM_SOCCLK_DPM_LEVELS; i++) + DC_LOG_SMU("dpm_clks->SocVoltage[%d] = %d\n", i, dpm_clks->SocVoltage[i]); + + for (i = 0; i < NUM_MEM_PSTATE_LEVELS; i++) + DC_LOG_SMU("dpm_clks.MemPstateTable[%d].UClk = %d MemClk = %d Voltage = %d\n", + i, dpm_clks->MemPstateTable[i].UClk, dpm_clks->MemPstateTable[i].MemClk, dpm_clks->MemPstateTable[i].Voltage); + + if (clk_mgr_base->ctx->dc_bios->integrated_info && clk_mgr_base->ctx->dc->config.use_default_clock_table == false) { + /* DCFCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].dcfclk_mhz, + dpm_clks->DcfClocks, + dpm_clks->NumDcfClkLevelsEnabled); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dcfclk_levels = dpm_clks->NumDcfClkLevelsEnabled; + + /* SOCCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].socclk_mhz, + dpm_clks->SocClocks, + dpm_clks->NumSocClkLevelsEnabled); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_socclk_levels = dpm_clks->NumSocClkLevelsEnabled; + + /* DISPCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].dispclk_mhz, + dpm_clks->DispClocks, + dpm_clks->NumDispClkLevelsEnabled); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dispclk_levels = dpm_clks->NumDispClkLevelsEnabled; + + /* DPPCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].dppclk_mhz, + dpm_clks->DppClocks, + dpm_clks->NumDispClkLevelsEnabled); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dppclk_levels = dpm_clks->NumDispClkLevelsEnabled; + + /* FCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].fclk_mhz, + dpm_clks->FclkClocks_Freq, + NUM_FCLK_DPM_LEVELS); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_fclk_levels = dpm_clks->NumFclkLevelsEnabled; + clk_mgr_base->bw_params->clk_table.num_entries = dpm_clks->NumFclkLevelsEnabled; + + /* Memory Pstate table is in reverse order for dcn42. Proper way to map this is for pmfw to provide an fclk indexed uclk key since we consume fclk matched pairs.*/ + ASSERT(dpm_clks->NumMemPstatesEnabled <= NUM_MEM_PSTATE_LEVELS); + if (dpm_clks->NumMemPstatesEnabled > NUM_MEM_PSTATE_LEVELS) + dpm_clks->NumMemPstatesEnabled = NUM_MEM_PSTATE_LEVELS; + for (i = 0; i < dpm_clks->NumDcfClkLevelsEnabled; i++) { + if (i < dpm_clks->NumMemPstatesEnabled) { + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].memclk_mhz = dpm_clks->MemPstateTable[i].MemClk; + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[i].WckRatio); + } else { + clk_mgr_base->bw_params->clk_table.entries[i].memclk_mhz = dpm_clks->MemPstateTable[0].MemClk; + clk_mgr_base->bw_params->clk_table.entries[i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[0].WckRatio); + } + } + + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_memclk_levels = dpm_clks->NumDcfClkLevelsEnabled; + + /* DTBCLK tied off in DCN42B */ + clk_mgr_base->bw_params->clk_table.entries[0].dtbclk_mhz = 0; + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dtbclk_levels = 0; + } + } + if (smu_dpm_clks.dpm_clks && smu_dpm_clks.mc_address.quad_part != 0) + dm_helpers_free_gpu_mem(clk_mgr_base->ctx, DC_MEM_ALLOC_TYPE_GART, + smu_dpm_clks.dpm_clks); +} + void dcn42b_clk_mgr_construct( struct dc_context *ctx, struct clk_mgr_dcn42 *clk_mgr, @@ -473,11 +639,6 @@ void dcn42b_clk_mgr_construct( dcn42b_read_ss_info_from_lut(&clk_mgr->base); clk_mgr->base.base.bw_params = &dcn42b_bw_params; - if (clk_mgr->base.smu_present) { - dcn42_get_smu_clocks(&clk_mgr->base); - //overwrite values from dcn42_get_smu_clocks since dtbclk is tied off in DCN42B - clk_mgr->base.base.bw_params->clk_table.entries[0].dtbclk_mhz = 0; - clk_mgr->base.base.bw_params->clk_table.num_entries_per_clk.num_dtbclk_levels = 0; - clk_mgr->base.base.clks.ref_dtbclk_khz = 0; - } + if (clk_mgr->base.smu_present) + dcn42b_get_smu_clocks(&clk_mgr->base); } diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 28339e4b6d67..6c1a8ecc2cb5 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -151,8 +151,8 @@ static const char DC_BUILD_ID[] = "production-build"; /* Private functions */ static inline void elevate_update_type( - struct surface_update_descriptor *descriptor, - enum surface_update_type new_type, + struct dc_update_descriptor *descriptor, + enum dc_update_type new_type, enum dc_lock_descriptor new_locks ) { @@ -769,22 +769,23 @@ void dc_stream_set_dither_option(struct dc_stream_state *stream, { struct bit_depth_reduction_params params; struct dc_link *link = stream->link; - struct pipe_ctx *pipes = NULL; + struct resource_context *res_ctx = &link->dc->current_state->res_ctx; + struct pipe_ctx *otg_master; + struct pipe_ctx *opp_heads[MAX_PIPES]; + int opp_cnt; int i; - for (i = 0; i < MAX_PIPES; i++) { - if (link->dc->current_state->res_ctx.pipe_ctx[i].stream == - stream) { - pipes = &link->dc->current_state->res_ctx.pipe_ctx[i]; - break; - } - } - - if (!pipes) + otg_master = resource_get_otg_master_for_stream(res_ctx, stream); + if (!otg_master) return; if (option > DITHER_OPTION_MAX) return; + opp_cnt = resource_get_opp_heads_for_otg_master(otg_master, res_ctx, opp_heads); + + if (opp_cnt == 0) + return; + dc_exit_ips_for_hw_access(stream->ctx->dc); stream->dither_option = option; @@ -793,16 +794,30 @@ void dc_stream_set_dither_option(struct dc_stream_state *stream, resource_build_bit_depth_reduction_params(stream, ¶ms); stream->bit_depth_params = params; - if (pipes->plane_res.xfm && - pipes->plane_res.xfm->funcs->transform_set_pixel_storage_depth) { - pipes->plane_res.xfm->funcs->transform_set_pixel_storage_depth( - pipes->plane_res.xfm, - pipes->plane_res.scl_data.lb_params.depth, - &stream->bit_depth_params); - } + /* + * Program bit-depth reduction (dither) on every OPP head of the + * stream. Under ODM combine there is more than one OPP head and they + * must all be kept in sync, otherwise (e.g. when CRC capture requests + * dither off) a secondary ODM segment can keep dither enabled and + * produce a different CRC than the primary segment. + */ + for (i = 0; i < opp_cnt; i++) { + struct pipe_ctx *opp_head = opp_heads[i]; - pipes->stream_res.opp->funcs-> - opp_program_bit_depth_reduction(pipes->stream_res.opp, ¶ms); + if (opp_head->plane_res.xfm && + opp_head->plane_res.xfm->funcs->transform_set_pixel_storage_depth) { + opp_head->plane_res.xfm->funcs->transform_set_pixel_storage_depth( + opp_head->plane_res.xfm, + opp_head->plane_res.scl_data.lb_params.depth, + &stream->bit_depth_params); + } + + if (opp_head->stream_res.opp && + opp_head->stream_res.opp->funcs->opp_program_bit_depth_reduction) { + opp_head->stream_res.opp->funcs->opp_program_bit_depth_reduction( + opp_head->stream_res.opp, ¶ms); + } + } } bool dc_stream_set_gamut_remap(struct dc *dc, const struct dc_stream_state *stream) @@ -816,7 +831,15 @@ bool dc_stream_set_gamut_remap(struct dc *dc, const struct dc_stream_state *stre for (i = 0; i < MAX_PIPES; i++) { if (dc->current_state->res_ctx.pipe_ctx[i].stream == stream) { pipes = &dc->current_state->res_ctx.pipe_ctx[i]; - dc->hwss.program_gamut_remap(pipes); + dc->hwss.program_gamut_remap(&(struct program_gamut_remap_params) { + .xfm = pipes->plane_res.xfm, + .dpp = pipes->plane_res.dpp, + .mpc = dc->res_pool->mpc, + .mpcc_id = pipes->plane_res.mpcc_inst, + .stream = pipes->stream, + .plane = pipes->plane_state, + .is_top_pipe = pipes->top_pipe == NULL, + }); ret = true; } } @@ -875,6 +898,32 @@ void dc_stream_set_static_screen_params(struct dc *dc, dc->hwss.set_static_screen_control(pipes_affected, num_pipes_affected, params); } +static void dc_destruct_update_scratch_pool(struct dc *dc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dc->update_scratch_pool); i++) { + kfree(dc->update_scratch_pool[i]); + dc->update_scratch_pool[i] = NULL; + dc->update_scratch_in_use[i] = false; + } +} + +static bool dc_construct_update_scratch_pool(struct dc *dc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dc->update_scratch_pool); i++) { + dc->update_scratch_pool[i] = kzalloc( + sizeof(struct dc_update_scratch_space), GFP_KERNEL); + if (!dc->update_scratch_pool[i]) + return false; + dc->update_scratch_in_use[i] = false; + } + + return true; +} + static void dc_destruct(struct dc *dc) { // reset link encoder assignment table on destruct @@ -882,6 +931,8 @@ static void dc_destruct(struct dc *dc) !dc->config.unify_link_enc_assignment) link_enc_cfg_init(dc, dc->current_state); + dc_destruct_update_scratch_pool(dc); + if (dc->current_state) { dc_state_release(dc->current_state); dc->current_state = NULL; @@ -1125,6 +1176,11 @@ static bool dc_construct(struct dc *dc, goto fail; } + if (!dc_construct_update_scratch_pool(dc)) { + dm_error("%s: failed to create update scratch pool\n", __func__); + goto fail; + } + return true; fail: @@ -1930,6 +1986,13 @@ bool dc_validate_boot_timing(const struct dc *dc, struct display_stream_compressor *dsc = NULL; struct dcn_dsc_state dsc_state = {0}; + if (dc->ctx->dce_version < DCN_VERSION_4_2) { + /*vbios enabled eDP dsc for one of DCN315 only but it has known issue, + since there is no production bios update, block it there*/ + DC_LOG_DEBUG("boot timing validation failed due to unsupported DSC on this ASIC\n"); + return false; + } + /* Find DSC associated with this timing generator */ if (tg_inst < (unsigned int)dc->res_pool->res_cap->num_dsc) { dsc = dc->res_pool->dscs[tg_inst]; @@ -2449,7 +2512,7 @@ enum dc_status dc_commit_streams(struct dc *dc, struct dc_commit_streams_params unsigned int i, j; struct dc_state *context; enum dc_status res = DC_OK; - struct dc_validation_set set[MAX_STREAMS] = {0}; + struct dc_validation_set set = {0}; struct pipe_ctx *pipe; bool handle_exit_odm2to1 = false; @@ -2482,14 +2545,15 @@ enum dc_status dc_commit_streams(struct dc *dc, struct dc_commit_streams_params dc_stream_log(dc, stream); - set[i].stream = stream; + set.streams[i].stream = stream; if (status) { - set[i].plane_count = (uint8_t)status->plane_count; + set.streams[i].plane_count = (uint8_t)status->plane_count; for (j = 0; j < (unsigned int)status->plane_count; j++) - set[i].plane_states[j] = status->plane_states[j]; + set.streams[i].plane_states[j] = status->plane_states[j]; } } + set.stream_count = (uint8_t)params->stream_count; /* ODM Combine 2:1 power optimization is only applied for single stream * scenario, it uses extra pipes than needed to reduce power consumption @@ -2513,7 +2577,7 @@ enum dc_status dc_commit_streams(struct dc *dc, struct dc_commit_streams_params context->power_source = params->power_source; - res = dc_validate_with_context(dc, set, params->stream_count, context, DC_VALIDATE_MODE_AND_PROGRAMMING); + res = dc_validate_with_context(dc, &set, context, DC_VALIDATE_MODE_AND_PROGRAMMING); /* * Only update link encoder to stream assignment after bandwidth validation passed. @@ -2762,10 +2826,10 @@ static bool is_surface_in_context( return false; } -static struct surface_update_descriptor get_plane_info_update_type(const struct dc_surface_update *u) +static struct dc_update_descriptor get_plane_info_update_type(const struct dc_surface_update *u) { struct pipe_update_bits *update_bits = &u->surface->update_bits; - struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; + struct dc_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; if (!u->plane_info) return update_type; @@ -2852,12 +2916,12 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct return update_type; } -static struct surface_update_descriptor get_scaling_info_update_type( +static struct dc_update_descriptor get_scaling_info_update_type( const struct dc_check_config *check_config, const struct dc_surface_update *u) { struct pipe_update_bits *update_bits = &u->surface->update_bits; - struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; + struct dc_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; if (!u->scaling_info) return update_type; @@ -2908,11 +2972,11 @@ static struct surface_update_descriptor get_scaling_info_update_type( return update_type; } -static struct surface_update_descriptor det_surface_update( +static struct dc_update_descriptor det_surface_update( const struct dc_check_config *check_config, struct dc_surface_update *u) { - struct surface_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; + struct dc_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; struct pipe_update_bits *update_bits = &u->surface->update_bits; if (u->surface->force_full_update) { @@ -2923,8 +2987,7 @@ static struct surface_update_descriptor det_surface_update( dc_pipe_update_bits_clear(update_bits); - struct surface_update_descriptor inner_type = get_plane_info_update_type(u); - + struct dc_update_descriptor inner_type = get_plane_info_update_type(u); elevate_update_type(&overall_type, inner_type.update_type, inner_type.lock_descriptor); inner_type = get_scaling_info_update_type(check_config, u); @@ -3066,13 +3129,13 @@ static void force_immediate_gsl_plane_flip(struct dc *dc, struct dc_surface_upda } } -static struct surface_update_descriptor check_update_surfaces_for_stream( +static struct dc_update_descriptor check_update_surfaces_for_stream( const struct dc_check_config *check_config, struct dc_surface_update *updates, int surface_count, struct dc_stream_update *stream_update) { - struct surface_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; + struct dc_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; /* When countdown finishes, promote this flip to full to trigger deferred final transition */ if (check_config->deferred_transition_state && !check_config->transition_countdown_to_steady_state) { @@ -3195,7 +3258,7 @@ static struct surface_update_descriptor check_update_surfaces_for_stream( } for (int i = 0 ; i < surface_count; i++) { - struct surface_update_descriptor inner_type = + struct dc_update_descriptor inner_type = det_surface_update(check_config, &updates[i]); elevate_update_type(&overall_type, inner_type.update_type, inner_type.lock_descriptor); @@ -3204,23 +3267,65 @@ static struct surface_update_descriptor check_update_surfaces_for_stream( return overall_type; } -/* - * dc_check_update_surfaces_for_stream() - Determine update type (fast, med, or full) +/** + * dc_check_state_update - Classify a dc_state_update by locking / re-entrancy requirements. + * @check_config: ASIC capabilities and display configuration context + * @updates: root update object describing the full desired commit * - * See :c:type:`enum surface_update_type ` for explanation of update types + * Determines whether the update requires a fast, medium, or full lock + * by inspecting the stream, stream_update, and surface_updates carried on + * the root object. A probe update elevates the result to at least MED with + * the PROBE lock, so a probe-carrying commit takes the probe mutex. + * + * Return: dc_update_descriptor with update_type and lock_descriptor. */ -struct surface_update_descriptor dc_check_update_surfaces_for_stream( +struct dc_update_descriptor dc_check_state_update( + const struct dc_check_config *check_config, + const struct dc_state_update *updates) +{ + struct dc_update_descriptor desc = {0}; + + if (updates->stream_update) + stream_update_flags_clear(&updates->stream_update->stream->update_flags); + for (int i = 0; i < updates->surface_count; i++) + dc_pipe_update_bits_clear(&updates->surface_updates[i].surface->update_bits); + + desc = check_update_surfaces_for_stream(check_config, updates->surface_updates, + updates->surface_count, updates->stream_update); + + if (updates->probe_updates && updates->probe_updates->probe_count > 0) + elevate_update_type(&desc, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_PROBE); + + return desc; +} + +/** + * dc_check_update_surfaces_for_stream - Shim for dc_check_state_update. + * @check_config: ASIC capabilities and display configuration context + * @updates: array of surface update descriptors + * @surface_count: number of entries in @updates + * @stream_update: optional stream update + * + * Packs the individual arguments into a dc_state_update and forwards to + * dc_check_state_update(). Preserved for out-of-tree and incremental callers. + * + * Return: dc_update_descriptor with update_type and lock_descriptor. + */ +struct dc_update_descriptor dc_check_update_surfaces_for_stream( const struct dc_check_config *check_config, struct dc_surface_update *updates, int surface_count, struct dc_stream_update *stream_update) { - if (stream_update) - stream_update_flags_clear(&stream_update->stream->update_flags); - for (int i = 0; i < surface_count; i++) - dc_pipe_update_bits_clear(&updates[i].surface->update_bits); + struct dc_state_update root = { + .stream = stream_update ? stream_update->stream : NULL, + .stream_update = stream_update, + .surface_updates = updates, + .surface_count = surface_count, + .probe_updates = NULL + }; - return check_update_surfaces_for_stream(check_config, updates, surface_count, stream_update); + return dc_check_state_update(check_config, &root); } static struct dc_stream_status *stream_get_status( @@ -3238,7 +3343,7 @@ static struct dc_stream_status *stream_get_status( return NULL; } -static const enum surface_update_type update_surface_trace_level = UPDATE_TYPE_FULL; +static const enum dc_update_type update_surface_trace_level = UPDATE_TYPE_FULL; static void copy_surface_update_to_plane( struct dc_plane_state *surface, @@ -3636,13 +3741,6 @@ static bool full_update_required_weak( const struct dc_stream_update *stream_update, const struct dc_stream_state *stream); -struct pipe_split_policy_backup { - bool dynamic_odm_policy; - bool subvp_policy; - enum pipe_split_policy mpc_policy; - char force_odm[MAX_PIPES]; -}; - static void backup_and_set_minimal_pipe_split_policy(struct dc *dc, struct dc_state *context, struct pipe_split_policy_backup *policy) @@ -3706,13 +3804,13 @@ static bool update_planes_and_stream_state(struct dc *dc, struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type *new_update_type, + enum dc_update_type *new_update_type, struct dc_state **new_context) { struct dc_state *context; int i; unsigned int j; - enum surface_update_type update_type; + enum dc_update_type update_type; const struct dc_stream_status *stream_status; struct dc_context *dc_ctx = dc->ctx; @@ -4121,7 +4219,7 @@ static void add_link_update_dsc_config_sequence( static void commit_planes_do_stream_update_sequence(struct dc *dc, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *context, struct block_sequence block_sequence[MAX_HWSS_BLOCK_SEQUENCE_SIZE], unsigned int *num_steps) @@ -4292,7 +4390,7 @@ static void commit_planes_do_stream_update_sequence(struct dc *dc, static void commit_planes_do_stream_update(struct dc *dc, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *context) { unsigned int j; @@ -4695,7 +4793,7 @@ static void commit_planes_for_stream_fast(struct dc *dc, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *context) { int i; @@ -4815,7 +4913,7 @@ static void commit_planes_for_stream(struct dc *dc, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *context) { int i; @@ -5898,7 +5996,7 @@ static bool update_planes_and_stream_v2(struct dc *dc, struct dc_stream_update *stream_update) { struct dc_state *context; - enum surface_update_type update_type; + enum dc_update_type update_type; struct dc_fast_update fast_update[MAX_SURFACES] = {0}; /* In cases where MPO and split or ODM are used transitions can @@ -5983,7 +6081,7 @@ static void commit_planes_and_stream_update_on_current_context(struct dc *dc, struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type) + enum dc_update_type update_type) { struct dc_fast_update fast_update[MAX_SURFACES] = {0}; @@ -6015,7 +6113,7 @@ static void commit_planes_and_stream_update_with_new_context(struct dc *dc, struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *new_context) { bool skip_new_context = false; @@ -6083,7 +6181,7 @@ static bool update_planes_and_stream_v3(struct dc *dc, struct dc_stream_update *stream_update) { struct dc_state *new_context; - enum surface_update_type update_type; + enum dc_update_type update_type; /* * When this function returns true and new_context is not equal to @@ -6129,28 +6227,91 @@ static void clear_update_bits(struct dc_surface_update *srf_updates, dc_pipe_update_bits_clear(&srf_updates[i].surface->update_bits); } +static struct dc_update_scratch_space *dc_update_scratch_acquire(struct dc *dc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dc->update_scratch_pool); i++) { + if (dc->update_scratch_in_use[i]) + continue; + + dc->update_scratch_in_use[i] = true; + return dc->update_scratch_pool[i]; + } + + /* TODO: add recoverable scratch acquisition failure handling. */ + ASSERT(false); + return NULL; +} + +static void dc_update_scratch_release(struct dc *dc, + struct dc_update_scratch_space *scratch) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dc->update_scratch_pool); i++) { + if (dc->update_scratch_pool[i] == scratch) { + dc->update_scratch_in_use[i] = false; + return; + } + } +} + +/** + * dc_update_state - Commit an absolute dc_state_update. + * @dc: DC structure + * @updates: root update object carrying stream, plane, and probe updates + * Return: true on success, false on failure. + */ +bool dc_update_state(struct dc *dc, const struct dc_state_update *updates) +{ + struct dc_update_scratch_space *scratch; + bool more = true; + + if (!dc || !updates) + return false; + + scratch = dc_update_state_init(dc, updates); + if (!scratch) + return false; + + while (more) { + if (!dc_update_state_prepare(scratch)) + return false; + + dc_update_state_execute(scratch); + more = dc_update_state_cleanup(scratch); + } + + return true; +} + +/** + * dc_update_planes_and_stream - Shim for dc_update_state. + * @dc: DC structure + * @srf_updates: array of surface update descriptors + * @surface_count: number of entries in @srf_updates + * @stream: target stream + * @stream_update: optional stream update + * + * Packs the individual arguments into a dc_state_update and forwards to + * dc_update_state(). Preserved for out-of-tree and incremental callers. + * + * Return: true on success; false on failure. + */ bool dc_update_planes_and_stream(struct dc *dc, struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update) { - struct dc_update_scratch_space *scratch = dc_update_planes_and_stream_init( - dc, - srf_updates, - surface_count, - stream, - stream_update - ); - bool more = true; + struct dc_state_update updates = { + .stream = stream, + .stream_update = stream_update, + .surface_updates = srf_updates, + .surface_count = surface_count, + }; - while (more) { - if (!dc_update_planes_and_stream_prepare(scratch)) - return false; - - dc_update_planes_and_stream_execute(scratch); - more = dc_update_planes_and_stream_cleanup(scratch); - } - return true; + return dc_update_state(dc, &updates); } void dc_commit_updates_for_stream(struct dc *dc, @@ -6218,6 +6379,160 @@ void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src) dal_irq_service_ack(dc->res_pool->irqs, src); } +/* Preserve this tg if a physical link is still lighting a present display */ +static bool should_preserve_tg(struct dc *dc, struct timing_generator *tg) +{ + unsigned int i, j; + + /* Check if a physical link is lighting this tg */ + for (i = 0; i < dc->link_count; i++) { + struct dc_link *link = dc->links[i]; + int fe; + + if (!link || link->ep_type != DISPLAY_ENDPOINT_PHY || + !link->link_enc || + !link->link_enc->funcs->is_dig_enabled || + !link->link_enc->funcs->is_dig_enabled(link->link_enc) || + !link->link_enc->funcs->get_dig_frontend) + continue; + + /* Get the DIG front-end this link's encoder drives; skip if none */ + fe = link->link_enc->funcs->get_dig_frontend(link->link_enc); + if (fe == ENGINE_ID_UNKNOWN) + continue; + + /* Find the stream encoder bound to this link's front-end */ + for (j = 0; j < dc->res_pool->stream_enc_count; j++) { + struct stream_encoder *se = dc->res_pool->stream_enc[j]; + + /* Skip unless this stream encoder feeds our front-end and drives this tg */ + if (se->id != fe || !se->funcs->dig_source_otg || + (int)se->funcs->dig_source_otg(se) != tg->inst) + continue; + + /* This link drives the OTG: keep a seamless-boot eDP, or + * any external link whose sink is still connected. + */ + if (link->connector_signal == SIGNAL_TYPE_EDP) + return true; + if (link->link_enc->funcs->get_hpd_state && + dc->link_srv->get_hpd_state(link)) + return true; + } + } + + return false; +} + +/* + * GOP/vBIOS may leave an OPTC enabled for a display present at power-on but no + * longer driven (e.g. external DP unplugged at boot). Such a dangling pipe keeps + * DCN out of idle and blocks s0i3. If nothing needs to survive (no committed + * stream or seamless-boot eDP) and no sink is still connected, power down all hw + * blocks. + */ +void dc_disable_dangling_timing_generators(struct dc *dc) +{ + struct dce_hwseq *hws = dc->hwseq; + bool any_dangling = false; + bool any_preserved = false; + bool any_connected = false; + unsigned int i; + + /* No real hw to touch on a virtual/emulated environment */ + if (dc->ctx->dce_environment == DCE_ENV_VIRTUAL_HW) + return; + + /* Wake hw out of IPS before reading/touching tg state */ + dc_exit_ips_for_hw_access(dc); + + /* Classify every enabled tg as either to-preserve or dangling */ + for (i = 0; i < dc->res_pool->timing_generator_count; i++) { + struct timing_generator *tg = dc->res_pool->timing_generators[i]; + + if (!tg || !tg->funcs->is_tg_enabled || + !tg->funcs->is_tg_enabled(tg)) + continue; + + if (should_preserve_tg(dc, tg)) + any_preserved = true; + else + any_dangling = true; + } + + /* A physically connected sink (HPD asserted) will be re-lit by a + * subsequent atomic commit. For that case we don't call the global + * power_down(). + */ + for (i = 0; i < dc->link_count; i++) { + struct dc_link *link = dc->links[i]; + + if (link && link->ep_type == DISPLAY_ENDPOINT_PHY && + link->link_enc && link->link_enc->funcs && + link->link_enc->funcs->get_hpd_state && + dc->link_srv->get_hpd_state(link)) { + any_connected = true; + break; + } + } + + if (!any_dangling) + return; + + if (!any_preserved && !any_connected && hws && hws->funcs.power_down) { + /* Truly headless / all sinks unplugged: nothing to preserve */ + DC_LOG_DC("%s: powering down dangling hw blocks to allow idle\n", + __func__); + hws->funcs.power_down(dc); + return; + } +} + +/* + * dc_get_flip_pending_on_otg() - Check if a GRPH_FLIP is still pending on OTG + * + * @dc: display core context @otg_inst: OTG instance to query + * + * Reads the HUBP flip-pending status for the pipe(s) bound to @otg_inst, + * returning true if any of them has not yet latched its programmed surface + * address. + * + * Unlike dc_plane_get_status(), this does not take or mutate a dc_plane_state, + * so it is safe to call from interrupt context without racing a concurrent + * commit that may be updating plane state. + * + * Return: true if a flip is still pending on the OTG, false otherwise. + */ +bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst) +{ + bool flip_pending = false; + unsigned int i; + + if (!dc || !dc->current_state) + return false; + + dc_exit_ips_for_hw_access(dc); + + for (i = 0; i < dc->res_pool->pipe_count; i++) { + struct pipe_ctx *pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i]; + struct hubp *hubp = pipe_ctx->plane_res.hubp; + + if (!pipe_ctx->plane_state || !pipe_ctx->stream_res.tg) + continue; + + if (pipe_ctx->stream_res.tg->inst != otg_inst) + continue; + + if (hubp && hubp->funcs->hubp_is_flip_pending && + hubp->funcs->hubp_is_flip_pending(hubp)) { + flip_pending = true; + break; + } + } + + return flip_pending; +} + void dc_power_down_on_boot(struct dc *dc) { if (dc->ctx->dce_environment != DCE_ENV_VIRTUAL_HW && @@ -6228,6 +6543,9 @@ void dc_power_down_on_boot(struct dc *dc) if (dc->caps.ips_support) dc_exit_ips_for_hw_access(dc); dc->hwss.power_down_on_boot(dc); + + if (dc->clk_mgr && dc->clk_mgr->funcs && dc->clk_mgr->funcs->notify_cstate_disable) + dc->clk_mgr->funcs->notify_cstate_disable(dc->clk_mgr, false); } } @@ -7870,24 +8188,11 @@ void dc_log_preos_dmcub_info(const struct dc *dc) bool dc_get_qos_info(struct dc *dc, struct dc_qos_info *info) { const struct dc_clocks *clk = &dc->current_state->bw_ctx.bw.dcn.clk; - struct dc_measured_memory_qos measured = {}; struct dc_requested_memory_qos requested = {}; memset(info, 0, sizeof(*info)); - // Check if measurement function is available - if (!dc->hwss.measure_memory_qos) { - return false; - } - - dc->hwss.measure_memory_qos(dc, &measured); - - info->actual_peak_bw_in_mbps = measured.peak_bw_mbps; - info->actual_avg_bw_in_mbps = measured.avg_bw_mbps; - info->actual_min_latency_in_ns = measured.min_latency_ns; - info->actual_max_latency_in_ns = measured.max_latency_ns; - info->actual_avg_latency_in_ns = measured.avg_latency_ns; - info->dcn_bandwidth_ub_in_mbps = (uint32_t)(clk->fclk_khz / 1000 * 64); + info->dcn_bandwidth_ub_in_mbps = (uint32_t)(clk->fclk_khz / 1000 * 64); if (dc->clk_mgr && dc->clk_mgr->funcs->get_requested_memory_qos) { dc->clk_mgr->funcs->get_requested_memory_qos(dc->clk_mgr, &requested); @@ -7912,38 +8217,6 @@ unsigned int dc_override_memory_bandwidth_request( dc->clk_mgr, bw_mbps * 1000) / 1000; } -enum update_v3_flow { - UPDATE_V3_FLOW_INVALID, - UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FAST, - UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FULL, - UPDATE_V3_FLOW_NEW_CONTEXT_SEAMLESS, - UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_NEW, - UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_CURRENT, -}; - -struct dc_update_scratch_space { - struct dc *dc; - struct dc_surface_update *surface_updates; - int surface_count; - struct dc_stream_state *stream; - struct dc_stream_update *stream_update; - bool update_v3; - bool do_clear_update_bits; - enum surface_update_type update_type; - struct dc_state *new_context; - enum update_v3_flow flow; - struct dc_state *backup_context; - struct dc_state *intermediate_context; - struct pipe_split_policy_backup intermediate_policy; - struct dc_surface_update intermediate_updates[MAX_SURFACES]; - int intermediate_count; -}; - -size_t dc_update_scratch_space_size(void) -{ - return sizeof(struct dc_update_scratch_space); -} - static bool update_planes_and_stream_prepare_v2( struct dc_update_scratch_space *scratch ) @@ -8110,6 +8383,19 @@ static bool update_planes_and_stream_prepare_v3( return false; } +/** + * should_commit_intermediate_context - Does this flow commit a transient + * minimal-transition intermediate context + * @flow: the commit flow selected for this iteration + * + * Return: true if this iteration commits the intermediate context. + */ +static bool should_commit_intermediate_context(enum update_v3_flow flow) +{ + return flow == UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_NEW + || flow == UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_CURRENT; +} + static void update_planes_and_stream_execute_v3_commit( const struct dc_update_scratch_space *scratch, bool intermediate_update, @@ -8133,6 +8419,8 @@ static void update_planes_and_stream_execute_v3( const struct dc_update_scratch_space *scratch ) { + bool intermediate_context = should_commit_intermediate_context(scratch->flow); + switch (scratch->flow) { case UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FAST: commit_planes_for_stream_fast( @@ -8148,16 +8436,16 @@ static void update_planes_and_stream_execute_v3( case UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FULL: case UPDATE_V3_FLOW_NEW_CONTEXT_SEAMLESS: - update_planes_and_stream_execute_v3_commit(scratch, false, false, true); + update_planes_and_stream_execute_v3_commit(scratch, false, intermediate_context, true); break; case UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_NEW: - update_planes_and_stream_execute_v3_commit(scratch, false, true, + update_planes_and_stream_execute_v3_commit(scratch, false, intermediate_context, scratch->dc->check_config.deferred_transition_state); break; case UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_CURRENT: - update_planes_and_stream_execute_v3_commit(scratch, true, true, false); + update_planes_and_stream_execute_v3_commit(scratch, true, intermediate_context, false); break; case UPDATE_V3_FLOW_INVALID: @@ -8232,54 +8520,145 @@ static bool update_planes_and_stream_cleanup_v3( return false; } -struct dc_update_scratch_space *dc_update_planes_and_stream_init( +struct dc_update_scratch_space *dc_update_state_init( struct dc *dc, - struct dc_surface_update *surface_updates, - int surface_count, - struct dc_stream_state *stream, - struct dc_stream_update *stream_update + const struct dc_state_update *updates ) { const enum dce_version version = dc->ctx->dce_version; - struct dc_update_scratch_space *scratch = stream->update_scratch; + struct dc_update_scratch_space *scratch = dc_update_scratch_acquire(dc); + const bool has_stream_or_plane = updates->stream || updates->stream_update || updates->surface_updates; + const bool has_probe = updates->probe_updates; + const bool surface_without_stream = updates->surface_updates && !updates->stream; + const bool stream_update_without_stream = updates->stream_update && !updates->stream; + const bool bad_surface_count = updates->surface_count > 0 && !updates->surface_updates; + + if (!scratch) + return NULL; + + if (!has_stream_or_plane && !has_probe) { + dc_update_scratch_release(dc, scratch); + return NULL; + } + + if (surface_without_stream || stream_update_without_stream || bad_surface_count) { + dc_update_scratch_release(dc, scratch); + return NULL; + } *scratch = (struct dc_update_scratch_space){ .dc = dc, - .surface_updates = surface_updates, - .surface_count = surface_count, - .stream = stream, - .stream_update = stream_update, - .update_v3 = version >= DCN_VERSION_4_01 || version == DCN_VERSION_3_2 || version == DCN_VERSION_3_21, + .surface_updates = updates->surface_updates, + .surface_count = updates->surface_count, + .stream = updates->stream, + .stream_update = updates->stream_update, + .probe_updates = updates->probe_updates, + .update_v3 = version >= DCN_VERSION_4_01 + || version == DCN_VERSION_3_2 + || version == DCN_VERSION_3_21, .do_clear_update_bits = version >= DCN_VERSION_1_0, + .new_context = NULL, + .flow = UPDATE_V3_FLOW_INVALID, }; return scratch; } -bool dc_update_planes_and_stream_prepare( - struct dc_update_scratch_space *scratch -) +/** + * dc_update_probes_prepare - Commit the desired probe set into new_context. + * @scratch: commit scratch carrying the probe updates + * + * Return: true on success or when there is nothing to do; false when the + * desired set is unachievable. + */ +static bool dc_update_probes_prepare(struct dc_update_scratch_space *scratch) { - return scratch->update_v3 - ? update_planes_and_stream_prepare_v3(scratch) - : update_planes_and_stream_prepare_v2(scratch); + struct dc *dc = scratch->dc; + const struct dc_probe_updates *probe_updates = scratch->probe_updates; + uint8_t i; + + if (!probe_updates) + return true; + + if (resource_validate_probe_set(dc, probe_updates->probes, + (uint8_t)probe_updates->probe_count) != DC_OK) + return false; + + if (!scratch->new_context) + scratch->new_context = dc->current_state; + + for (i = 0; i < probe_updates->probe_count && i < MAX_PROBES; i++) + scratch->new_context->probes[i] = probe_updates->probes[i]; + scratch->new_context->probe_count = probe_updates->probe_count; + + return true; } -void dc_update_planes_and_stream_execute( +/** + * dc_update_probes_execute - Program the committed probes. + * @scratch: commit scratch carrying the probe updates + * + */ +static void dc_update_probes_execute(const struct dc_update_scratch_space *scratch) +{ + struct dc *dc = scratch->dc; + + if (should_commit_intermediate_context(scratch->flow)) + return; + + if (dc->hwss.program_perfmon) + dc->hwss.program_perfmon(dc, scratch->new_context); +} + +bool dc_update_state_prepare(struct dc_update_scratch_space *scratch) +{ + if (scratch->stream) { + bool ok = scratch->update_v3 + ? update_planes_and_stream_prepare_v3(scratch) + : update_planes_and_stream_prepare_v2(scratch); + + if (!ok) + goto release_scratch; + } + + if (!dc_update_probes_prepare(scratch)) + goto release_scratch; + + return true; + +release_scratch: + /* execute and cleanup never run on this path, so release here. */ + dc_update_scratch_release(scratch->dc, scratch); + return false; +} + +void dc_update_state_execute( const struct dc_update_scratch_space *scratch ) { - scratch->update_v3 - ? update_planes_and_stream_execute_v3(scratch) - : update_planes_and_stream_execute_v2(scratch); + if (scratch->stream) + scratch->update_v3 + ? update_planes_and_stream_execute_v3(scratch) + : update_planes_and_stream_execute_v2(scratch); + + if (scratch->probe_updates) + dc_update_probes_execute(scratch); } -bool dc_update_planes_and_stream_cleanup( +bool dc_update_state_cleanup( struct dc_update_scratch_space *scratch ) { - return scratch->update_v3 - ? update_planes_and_stream_cleanup_v3(scratch) - : update_planes_and_stream_cleanup_v2(scratch); + bool more = false; + + if (scratch->stream) + more = scratch->update_v3 + ? update_planes_and_stream_cleanup_v3(scratch) + : update_planes_and_stream_cleanup_v2(scratch); + + if (!more) + dc_update_scratch_release(scratch->dc, scratch); + + return more; } diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c index e47c8cf5d036..11411fa94665 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c @@ -1069,7 +1069,14 @@ void hwss_build_fast_sequence(struct dc *dc, if (dc->hwss.program_gamut_remap && (current_mpc_pipe->plane_state->update_bits.gamut_remap_change || current_mpc_pipe->stream->update_flags.bits.gamut_remap)) { - block_sequence[*num_steps].params.program_gamut_remap_params.pipe_ctx = current_mpc_pipe; + struct program_gamut_remap_params *params = &block_sequence[*num_steps].params.program_gamut_remap_params; + params->dpp = current_mpc_pipe->plane_res.dpp; + params->mpc = dc->res_pool->mpc; + params->xfm = current_mpc_pipe->plane_res.xfm; + params->mpcc_id = current_mpc_pipe->plane_res.hubp->inst; + params->plane = current_mpc_pipe->plane_state; + params->stream = current_mpc_pipe->stream; + params->is_top_pipe = current_mpc_pipe->top_pipe == NULL; block_sequence[*num_steps].func = DPP_PROGRAM_GAMUT_REMAP; (*num_steps)++; } @@ -1105,9 +1112,15 @@ void hwss_build_fast_sequence(struct dc *dc, } } if (hws->funcs.set_output_transfer_func && current_mpc_pipe->stream->update_flags.bits.out_tf) { - block_sequence[*num_steps].params.set_output_transfer_func_params.dc = dc; - block_sequence[*num_steps].params.set_output_transfer_func_params.pipe_ctx = current_mpc_pipe; - block_sequence[*num_steps].params.set_output_transfer_func_params.stream = current_mpc_pipe->stream; + struct set_output_transfer_func_params *otf_params = + &block_sequence[*num_steps].params.set_output_transfer_func_params; + + otf_params->dpp = current_mpc_pipe->plane_res.dpp; + otf_params->xfm = current_mpc_pipe->plane_res.xfm; + otf_params->mpc = dc->res_pool->mpc; + otf_params->mpcc_id = current_mpc_pipe->plane_res.hubp->inst; + otf_params->is_top_pipe = resource_is_pipe_type(current_mpc_pipe, OPP_HEAD); + otf_params->stream = current_mpc_pipe->stream; block_sequence[*num_steps].func = DPP_SET_OUTPUT_TRANSFER_FUNC; (*num_steps)++; } @@ -1236,7 +1249,8 @@ void hwss_execute_sequence(struct dc *dc, params->set_input_transfer_func_params.plane_state); break; case DPP_PROGRAM_GAMUT_REMAP: - hwss_program_gamut_remap(params); + if (dc->hwss.program_gamut_remap) + dc->hwss.program_gamut_remap(¶ms->program_gamut_remap_params); break; case HUBP_ENABLE_3DLUT_FL: hwss_hubp_enable_3dlut_fl(params); @@ -1263,9 +1277,7 @@ void hwss_execute_sequence(struct dc *dc, hwss_program_manual_trigger(params); break; case DPP_SET_OUTPUT_TRANSFER_FUNC: - hws->funcs.set_output_transfer_func(params->set_output_transfer_func_params.dc, - params->set_output_transfer_func_params.pipe_ctx, - params->set_output_transfer_func_params.stream); + hws->funcs.set_output_transfer_func(¶ms->set_output_transfer_func_params); break; case MPC_UPDATE_VISUAL_CONFIRM: dc->hwss.update_visual_confirm_color(params->update_visual_confirm_params.dc, @@ -1483,6 +1495,48 @@ void hwss_execute_sequence(struct dc *dc, case HUBBUB_SOFT_RESET: hwss_hubbub_soft_reset(params); break; + case HUBBUB_PERFMON_RESET: + hwss_hubbub_perfmon_reset(params); + break; + case HUBBUB_PERFMON_ARM_OUT_OF_ORDER_BW: + hwss_hubbub_perfmon_arm_out_of_order_bw(params); + break; + case HUBBUB_PERFMON_START_OUT_OF_ORDER_BW: + hwss_hubbub_perfmon_start_out_of_order_bw(params); + break; + case HUBBUB_PERFMON_START_IN_ORDER_BW: + hwss_hubbub_perfmon_start_in_order_bw(params); + break; + case HUBBUB_PERFMON_START_MEMORY_LATENCIES: + hwss_hubbub_perfmon_start_memory_latencies(params); + break; + case HUBBUB_PERFMON_START_URGENT_ASSERTION_COUNT: + hwss_hubbub_perfmon_start_urgent_assertion_count(params); + break; + case HUBBUB_PERFMON_START_URGENT_RAMP_LATENCY: + hwss_hubbub_perfmon_start_urgent_ramp_latency(params); + break; + case HUBBUB_PERFMON_START_PREFETCH_DATA_SIZE: + hwss_hubbub_perfmon_start_prefetch_data_size(params); + break; + case HUBBUB_PERFMON_GET_OUT_OF_ORDER_BW: + hwss_hubbub_perfmon_get_out_of_order_bw(params); + break; + case HUBBUB_PERFMON_GET_IN_ORDER_BW: + hwss_hubbub_perfmon_get_in_order_bw(params); + break; + case HUBBUB_PERFMON_GET_MEMORY_LATENCIES: + hwss_hubbub_perfmon_get_memory_latencies(params); + break; + case HUBBUB_PERFMON_GET_URGENT_ASSERTION_COUNT: + hwss_hubbub_perfmon_get_urgent_assertion_count(params); + break; + case HUBBUB_PERFMON_GET_PREFETCH_DATA_SIZE: + hwss_hubbub_perfmon_get_prefetch_data_size(params); + break; + case HUBBUB_PERFMON_GET_URGENT_RAMP_LATENCY: + hwss_hubbub_perfmon_get_urgent_ramp_latency(params); + break; case HUBP_CLK_CNTL: hwss_hubp_clk_cntl(params); break; @@ -1779,7 +1833,14 @@ void hwss_add_dpp_program_gamut_remap(struct block_sequence_state *seq_state, struct pipe_ctx *pipe_ctx) { if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { - seq_state->steps[*seq_state->num_steps].params.program_gamut_remap_params.pipe_ctx = pipe_ctx; + struct program_gamut_remap_params *params = &seq_state->steps[*seq_state->num_steps].params.program_gamut_remap_params; + params->xfm = pipe_ctx->plane_res.xfm; + params->dpp = pipe_ctx->plane_res.dpp; + params->mpc = pipe_ctx->stream->ctx->dc->res_pool->mpc; + params->mpcc_id = pipe_ctx->plane_res.hubp->inst; + params->plane = pipe_ctx->plane_state; + params->stream = pipe_ctx->stream; + params->is_top_pipe = pipe_ctx->top_pipe == NULL; seq_state->steps[*seq_state->num_steps].func = DPP_PROGRAM_GAMUT_REMAP; (*seq_state->num_steps)++; } @@ -1814,19 +1875,39 @@ void hwss_add_optc_program_manual_trigger(struct block_sequence_state *seq_state * Helper function to add DPP set output transfer function to block sequence */ void hwss_add_dpp_set_output_transfer_func(struct block_sequence_state *seq_state, - struct dc *dc, - struct pipe_ctx *pipe_ctx, - struct dc_stream_state *stream) + struct dc *dc, struct pipe_ctx *pipe_ctx) { if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { - seq_state->steps[*seq_state->num_steps].params.set_output_transfer_func_params.dc = dc; - seq_state->steps[*seq_state->num_steps].params.set_output_transfer_func_params.pipe_ctx = pipe_ctx; - seq_state->steps[*seq_state->num_steps].params.set_output_transfer_func_params.stream = stream; + seq_state->steps[*seq_state->num_steps].params.set_output_transfer_func_params = + (struct set_output_transfer_func_params) { + .xfm = pipe_ctx->plane_res.xfm, + .dpp = pipe_ctx->plane_res.dpp, + .mpc = dc->res_pool->mpc, + .mpcc_id = pipe_ctx->plane_res.hubp->inst, + .is_top_pipe = resource_is_pipe_type(pipe_ctx, OPP_HEAD), + .stream = pipe_ctx->stream, + }; seq_state->steps[*seq_state->num_steps].func = DPP_SET_OUTPUT_TRANSFER_FUNC; (*seq_state->num_steps)++; } } +void hwss_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx) +{ + if (dc->hwseq->funcs.set_output_transfer_func) { + dc->hwseq->funcs.set_output_transfer_func( + &(struct set_output_transfer_func_params) { + .xfm = pipe_ctx->plane_res.xfm, + .dpp = pipe_ctx->plane_res.dpp, + .mpc = dc->res_pool->mpc, + .mpcc_id = pipe_ctx->plane_res.hubp->inst, + .is_top_pipe = resource_is_pipe_type(pipe_ctx, OPP_HEAD), + .stream = pipe_ctx->stream, + } + ); + } +} + /* * Helper function to add MPC update visual confirm to block sequence */ @@ -3212,6 +3293,139 @@ void hwss_hubbub_soft_reset(union block_sequence_params *params) params->hubbub_soft_reset_params.hubbub_soft_reset(hubbub, reset); } +void hwss_hubbub_perfmon_reset(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_reset_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.reset) + hubbub->funcs->perfmon.reset(hubbub); +} + +void hwss_hubbub_perfmon_arm_out_of_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_arm_out_of_order_bw_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.arm_measuring_out_of_order_bandwidth) + hubbub->funcs->perfmon.arm_measuring_out_of_order_bandwidth(hubbub); +} + +void hwss_hubbub_perfmon_start_out_of_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_out_of_order_bw_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_out_of_order_bandwidth) + hubbub->funcs->perfmon.start_measuring_out_of_order_bandwidth(hubbub); +} + +void hwss_hubbub_perfmon_start_in_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_in_order_bw_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_in_order_bandwidth) + hubbub->funcs->perfmon.start_measuring_in_order_bandwidth(hubbub); +} + +void hwss_hubbub_perfmon_start_memory_latencies(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_memory_latencies_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_memory_latencies) + hubbub->funcs->perfmon.start_measuring_memory_latencies(hubbub); +} + +void hwss_hubbub_perfmon_start_urgent_assertion_count(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_urgent_assertion_count_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_urgent_assertion_count) + hubbub->funcs->perfmon.start_measuring_urgent_assertion_count(hubbub); +} + +void hwss_hubbub_perfmon_start_urgent_ramp_latency(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_urgent_ramp_latency_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_urgent_ramp_latency) + hubbub->funcs->perfmon.start_measuring_urgent_ramp_latency( + hubbub, + ¶ms->hubbub_perfmon_start_urgent_ramp_latency_params.latency_params); +} + +void hwss_hubbub_perfmon_start_prefetch_data_size(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_prefetch_data_size_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_prefetch_data_size) + hubbub->funcs->perfmon.start_measuring_prefetch_data_size(hubbub); +} + +void hwss_hubbub_perfmon_get_out_of_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_out_of_order_bw_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_out_of_order_bw_params.refclk_mhz; + uint32_t *mbps = params->hubbub_perfmon_get_out_of_order_bw_params.bandwidth_mbps; + uint32_t *duration = params->hubbub_perfmon_get_out_of_order_bw_params.duration_ns; + + if (hubbub && hubbub->funcs->perfmon.get_out_of_order_bandwidth_mbps && mbps) + *mbps = hubbub->funcs->perfmon.get_out_of_order_bandwidth_mbps( + hubbub, refclk_mhz, duration); +} + +void hwss_hubbub_perfmon_get_in_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_in_order_bw_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_in_order_bw_params.refclk_mhz; + uint32_t min_duration_ns = params->hubbub_perfmon_get_in_order_bw_params.min_duration_ns; + uint32_t *mbps = params->hubbub_perfmon_get_in_order_bw_params.bandwidth_mbps; + uint32_t *duration = params->hubbub_perfmon_get_in_order_bw_params.duration_ns; + + if (hubbub && hubbub->funcs->perfmon.get_in_order_bandwidth_mbps && mbps) + *mbps = hubbub->funcs->perfmon.get_in_order_bandwidth_mbps( + hubbub, refclk_mhz, min_duration_ns, duration); +} + +void hwss_hubbub_perfmon_get_memory_latencies(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_memory_latencies_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_memory_latencies_params.refclk_mhz; + struct dc_probe_latencies *result = params->hubbub_perfmon_get_memory_latencies_params.result; + + if (hubbub && hubbub->funcs->perfmon.get_memory_latencies_ns && result) + hubbub->funcs->perfmon.get_memory_latencies_ns( + hubbub, refclk_mhz, &result->min_latency_ns, &result->max_latency_ns, &result->avg_latency_ns); +} + +void hwss_hubbub_perfmon_get_urgent_assertion_count(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_urgent_assertion_count_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_urgent_assertion_count_params.refclk_mhz; + uint32_t *count = params->hubbub_perfmon_get_urgent_assertion_count_params.assertion_count; + + if (hubbub && hubbub->funcs->perfmon.get_urgent_assertion_count) + hubbub->funcs->perfmon.get_urgent_assertion_count( + hubbub, refclk_mhz, count, NULL, NULL); +} + +void hwss_hubbub_perfmon_get_prefetch_data_size(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_prefetch_data_size_params.hubbub; + uint32_t *prefetch_size = params->hubbub_perfmon_get_prefetch_data_size_params.prefetch_data_size; + + if (hubbub && hubbub->funcs->perfmon.get_prefetch_data_size && prefetch_size) + *prefetch_size = hubbub->funcs->perfmon.get_prefetch_data_size(hubbub); +} + +void hwss_hubbub_perfmon_get_urgent_ramp_latency(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_urgent_ramp_latency_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_urgent_ramp_latency_params.refclk_mhz; + uint32_t *latency_ns = params->hubbub_perfmon_get_urgent_ramp_latency_params.latency_ns; + + if (hubbub && hubbub->funcs->perfmon.get_urgent_ramp_latency_ns && latency_ns) + *latency_ns = hubbub->funcs->perfmon.get_urgent_ramp_latency_ns( + hubbub, refclk_mhz); +} + void hwss_hubp_clk_cntl(union block_sequence_params *params) { struct hubp *hubp = params->hubp_clk_cntl_params.hubp; @@ -3617,12 +3831,20 @@ void hwss_set_cursor_sdr_white_level(union block_sequence_params *params) dc->hwss.set_cursor_sdr_white_level(pipe_ctx); } -void hwss_program_gamut_remap(union block_sequence_params *params) +void hwss_program_gamut_remap(struct pipe_ctx *pipe_ctx) { - struct dc *dc = params->program_gamut_remap_params.pipe_ctx->stream->ctx->dc; + struct dc *dc = pipe_ctx->stream->ctx->dc; - if (dc && dc->hwss.program_gamut_remap) - dc->hwss.program_gamut_remap(params->program_gamut_remap_params.pipe_ctx); + if (dc->hwss.program_gamut_remap) + dc->hwss.program_gamut_remap(&(struct program_gamut_remap_params) { + .xfm = pipe_ctx->plane_res.xfm, + .dpp = pipe_ctx->plane_res.dpp, + .mpc = dc->res_pool->mpc, + .mpcc_id = pipe_ctx->plane_res.hubp->inst, + .stream = pipe_ctx->stream, + .plane = pipe_ctx->plane_state, + .is_top_pipe = pipe_ctx->top_pipe == NULL, + }); } void hwss_program_output_csc(union block_sequence_params *params) @@ -4375,6 +4597,168 @@ void hwss_add_hubbub_soft_reset(struct block_sequence_state *seq_state, } } +void hwss_add_hubbub_perfmon_reset(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_RESET; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_reset_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_arm_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_ARM_OUT_OF_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_arm_out_of_order_bw_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_OUT_OF_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_out_of_order_bw_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_in_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_IN_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_in_order_bw_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_memory_latencies(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_MEMORY_LATENCIES; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_memory_latencies_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_urgent_assertion_count(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_URGENT_ASSERTION_COUNT; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_urgent_assertion_count_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_urgent_ramp_latency(struct block_sequence_state *seq_state, + struct hubbub *hubbub, + const struct hubbub_urgent_latency_params *latency_params) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_URGENT_RAMP_LATENCY; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_urgent_ramp_latency_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_urgent_ramp_latency_params.latency_params = + *latency_params; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_prefetch_data_size(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_PREFETCH_DATA_SIZE; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_prefetch_data_size_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *bandwidth_mbps, uint32_t *duration_ns) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_OUT_OF_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_out_of_order_bw_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_out_of_order_bw_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_out_of_order_bw_params.bandwidth_mbps = bandwidth_mbps; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_out_of_order_bw_params.duration_ns = duration_ns; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_in_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, uint32_t min_duration_ns, + uint32_t *bandwidth_mbps, uint32_t *duration_ns) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_IN_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.min_duration_ns = min_duration_ns; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.bandwidth_mbps = bandwidth_mbps; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.duration_ns = duration_ns; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_memory_latencies(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + struct dc_probe_latencies *result) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_MEMORY_LATENCIES; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_memory_latencies_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_memory_latencies_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_memory_latencies_params.result = result; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_urgent_assertion_count(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *assertion_count) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_URGENT_ASSERTION_COUNT; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_assertion_count_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_assertion_count_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_assertion_count_params.assertion_count = assertion_count; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_prefetch_data_size(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t *prefetch_data_size) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_PREFETCH_DATA_SIZE; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_prefetch_data_size_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_prefetch_data_size_params.prefetch_data_size = prefetch_data_size; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_urgent_ramp_latency(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *latency_ns) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_URGENT_RAMP_LATENCY; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_ramp_latency_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_ramp_latency_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_ramp_latency_params.latency_ns = latency_ns; + (*seq_state->num_steps)++; + } +} + void hwss_add_hubp_clk_cntl(struct block_sequence_state *seq_state, struct hubp *hubp, bool enable) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c index f25d4b3e11ac..14fc4ef482f1 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c @@ -534,6 +534,13 @@ bool dc_link_set_pr_general_cmd(struct dc_link *link, return link->dc->link_srv->dp_pr_set_general_cmd(link, general_cmd_data); } +void dc_link_edp_replay_residency(const struct dc_link *link, + unsigned int *residency, bool is_start, enum pr_residency_mode mode) +{ + if (link && link->dc && link->dc->link_srv) + link->dc->link_srv->edp_replay_residency(link, residency, is_start, mode); +} + bool dc_link_get_pr_state(const struct dc_link *link, uint64_t *state) { return link->dc->link_srv->dp_pr_get_state(link, state); diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c index b21d41df0fab..b970f152d67f 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c @@ -4325,8 +4325,7 @@ bool dc_resource_is_dsc_encoding_supported(const struct dc *dc) static bool planes_changed_for_existing_stream(struct dc_state *context, struct dc_stream_state *stream, - const struct dc_validation_set set[], - unsigned int set_count) + const struct dc_validation_set *set) { unsigned int i, j; struct dc_stream_status *stream_status = NULL; @@ -4343,18 +4342,18 @@ static bool planes_changed_for_existing_stream(struct dc_state *context, return false; } - for (i = 0; i < set_count; i++) - if (set[i].stream == stream) + for (i = 0; i < set->stream_count; i++) + if (set->streams[i].stream == stream) break; - if (i == set_count) + if (i == set->stream_count) ASSERT(0); - if (set[i].plane_count != stream_status->plane_count) + if (set->streams[i].plane_count != stream_status->plane_count) return true; - for (j = 0; j < set[i].plane_count; j++) - if (set[i].plane_states[j] != stream_status->plane_states[j]) + for (j = 0; j < set->streams[i].plane_count; j++) + if (set->streams[i].plane_states[j] != stream_status->plane_states[j]) return true; return false; @@ -4363,34 +4362,66 @@ static bool planes_changed_for_existing_stream(struct dc_state *context, static bool add_all_planes_for_stream( const struct dc *dc, struct dc_stream_state *stream, - const struct dc_validation_set set[], - unsigned int set_count, + const struct dc_validation_set *set, struct dc_state *state) { unsigned int i, j; - for (i = 0; i < set_count; i++) - if (set[i].stream == stream) + for (i = 0; i < set->stream_count; i++) + if (set->streams[i].stream == stream) break; - if (i == set_count) { + if (i == set->stream_count) { dm_error("Stream %p not found in set!\n", stream); return false; } - for (j = 0; j < set[i].plane_count; j++) - if (!dc_state_add_plane(dc, stream, set[i].plane_states[j], state)) + for (j = 0; j < set->streams[i].plane_count; j++) + if (!dc_state_add_plane(dc, stream, set->streams[i].plane_states[j], state)) return false; return true; } +/** + * resource_validate_probe_set - Validate a probe descriptor set against the ASIC. + * @dc: DC instance providing the HWSS capability hooks + * @probes: desired probe descriptors + * @probe_count: number of valid entries in @probes + * + * Return: DC_OK if achievable, otherwise a DC error. + */ +enum dc_status resource_validate_probe_set(struct dc *dc, + const struct dc_probe_state *probes, + uint8_t probe_count) +{ + uint8_t i; + + if (probe_count == 0) + return DC_OK; + + if (!dc->hwss.program_perfmon) + return DC_NOT_SUPPORTED; + + if (probe_count > MAX_PROBES) + return DC_NOT_SUPPORTED; + + for (i = 0; i < probe_count; i++) { + if (probes[i].target_state == DC_PROBE_MEASURING) + return DC_NOT_SUPPORTED; + + if (probes[i].scope.type != DC_PROBE_SCOPE_GLOBAL) + return DC_NOT_SUPPORTED; + } + + return DC_OK; +} + /** * dc_validate_with_context - Validate and update the potential new stream in the context object * * @dc: Used to get the current state status - * @set: An array of dc_validation_set with all the current streams reference - * @set_count: Total of streams + * @set: Root validation object holding all streams and their planes * @context: New context * @validate_mode: identify the validation mode * @@ -4404,8 +4435,7 @@ static bool add_all_planes_for_stream( * In case of success, return DC_OK (1), otherwise, return a DC error. */ enum dc_status dc_validate_with_context(struct dc *dc, - const struct dc_validation_set set[], - unsigned int set_count, + const struct dc_validation_set *set, struct dc_state *context, enum dc_validate_mode validate_mode) { @@ -4426,8 +4456,8 @@ enum dc_status dc_validate_with_context(struct dc *dc, for (i = 0; i < old_stream_count; i++) { struct dc_stream_state *stream = context->streams[i]; - for (j = 0; j < set_count; j++) { - if (stream == set[j].stream) { + for (j = 0; j < set->stream_count; j++) { + if (stream == set->streams[j].stream) { found = true; break; } @@ -4440,8 +4470,8 @@ enum dc_status dc_validate_with_context(struct dc *dc, } /* Second, build a list of new streams */ - for (i = 0; i < set_count; i++) { - struct dc_stream_state *stream = set[i].stream; + for (i = 0; i < set->stream_count; i++) { + struct dc_stream_state *stream = set->streams[i].stream; for (j = 0; j < old_stream_count; j++) { if (stream == context->streams[j]) { @@ -4459,10 +4489,10 @@ enum dc_status dc_validate_with_context(struct dc *dc, /* Build a list of unchanged streams which is necessary for handling * planes change such as added, removed, and updated. */ - for (i = 0; i < set_count; i++) { + for (i = 0; i < set->stream_count; i++) { /* Check if stream is part of the delete list */ for (j = 0; j < del_streams_count; j++) { - if (set[i].stream == del_streams[j]) { + if (set->streams[i].stream == del_streams[j]) { found = true; break; } @@ -4471,7 +4501,7 @@ enum dc_status dc_validate_with_context(struct dc *dc, if (!found) { /* Check if stream is part of the add list */ for (j = 0; j < add_streams_count; j++) { - if (set[i].stream == add_streams[j]) { + if (set->streams[i].stream == add_streams[j]) { found = true; break; } @@ -4479,7 +4509,7 @@ enum dc_status dc_validate_with_context(struct dc *dc, } if (!found) - unchanged_streams[unchanged_streams_count++] = set[i].stream; + unchanged_streams[unchanged_streams_count++] = set->streams[i].stream; found = false; } @@ -4488,8 +4518,7 @@ enum dc_status dc_validate_with_context(struct dc *dc, for (i = 0; i < unchanged_streams_count; i++) { if (planes_changed_for_existing_stream(context, unchanged_streams[i], - set, - set_count)) { + set)) { if (!dc_state_rem_all_planes_for_stream(dc, unchanged_streams[i], @@ -4557,7 +4586,7 @@ enum dc_status dc_validate_with_context(struct dc *dc, if (res != DC_OK) goto fail; - if (!add_all_planes_for_stream(dc, add_streams[i], set, set_count, context)) { + if (!add_all_planes_for_stream(dc, add_streams[i], set, context)) { res = DC_FAIL_ATTACH_SURFACES; goto fail; } @@ -4567,9 +4596,8 @@ enum dc_status dc_validate_with_context(struct dc *dc, for (i = 0; i < unchanged_streams_count; i++) { if (planes_changed_for_existing_stream(context, unchanged_streams[i], - set, - set_count)) { - if (!add_all_planes_for_stream(dc, unchanged_streams[i], set, set_count, context)) { + set)) { + if (!add_all_planes_for_stream(dc, unchanged_streams[i], set, context)) { res = DC_FAIL_ATTACH_SURFACES; goto fail; } @@ -4760,6 +4788,10 @@ enum dc_status dc_validate_global_state( if (result == DC_OK) result = dc->res_pool->funcs->validate_bandwidth(dc, new_ctx, validate_mode); + if (result == DC_OK) + result = resource_validate_probe_set(dc, new_ctx->probes, + (uint8_t)new_ctx->probe_count); + return result; } diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_sink.c b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c index e5ac7056a187..38ef295e43d6 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_sink.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c @@ -61,6 +61,7 @@ void dc_sink_retain(struct dc_sink *sink) { kref_get(&sink->refcount); } +EXPORT_IF_KUNIT(dc_sink_retain); static void dc_sink_free(struct kref *kref) { @@ -73,6 +74,7 @@ void dc_sink_release(struct dc_sink *sink) { kref_put(&sink->refcount, dc_sink_free); } +EXPORT_IF_KUNIT(dc_sink_release); struct dc_sink *dc_sink_create(const struct dc_sink_init_data *init_params) { @@ -94,6 +96,7 @@ construct_fail: alloc_fail: return NULL; } +EXPORT_IF_KUNIT(dc_sink_create); /******************************************************************************* * Protected functions - visible only inside of DC (not visible in DM) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_state.c b/drivers/gpu/drm/amd/display/dc/core/dc_state.c index 7edeed52cc32..b534b1a641af 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_state.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_state.c @@ -25,6 +25,7 @@ #include "dc_types.h" #include "core_types.h" #include "core_status.h" +#include "dc.h" #include "dc_state.h" #include "dc_state_priv.h" #include "dc_stream_priv.h" @@ -359,6 +360,10 @@ void dc_state_destruct(struct dc_state *state) } state->phantom_plane_count = 0; + memset(state->probes, 0, sizeof(state->probes)); + memset(state->probe_status, 0, sizeof(state->probe_status)); + state->probe_count = 0; + state->stream_mask = 0; memset(&state->res_ctx, 0, sizeof(state->res_ctx)); memset(&state->pp_display_cfg, 0, sizeof(state->pp_display_cfg)); @@ -678,28 +683,67 @@ bool dc_state_add_all_planes_for_stream( /* Private dc_state functions */ /** - * dc_state_get_stream_status - Get stream status from given dc state - * @state: DC state to find the stream status in - * @stream: The stream to get the stream status for + * dc_state_get_status - Unified status readback for dc_state. + * @status: output object populated per options->types + * @options: selects source state, status classes to fill, and optional filters * - * The given stream is expected to exist in the given dc state. Otherwise, NULL - * will be returned. + * Return: DC_OK on success, DC_ERROR_UNEXPECTED if options or state is NULL. + */ +enum dc_status dc_state_get_status(struct dc_state_status *status, + const struct dc_get_status_options *options) +{ + uint8_t i; + + if (!status || !options || !options->state) + return DC_ERROR_UNEXPECTED; + + if (options->types & DC_GET_STATUS_STREAM) { + status->stream_count = 0; + for (i = 0; i < options->state->stream_count; i++) { + if (options->stream && + options->stream != options->state->streams[i]) + continue; + status->stream_status[status->stream_count++] = + &options->state->stream_status[i]; + } + } + + if (options->types & DC_GET_STATUS_PROBE) { + status->probe_count = 0; + for (i = 0; i < options->state->probe_count; i++) { + if (options->probe && + options->probe->type != options->state->probes[i].type) + continue; + status->probe_status[status->probe_count++] = + &options->state->probe_status[i]; + } + } + + return DC_OK; +} + +/** + * dc_state_get_stream_status - Shim for dc_state_get_status. + * @state: state to search + * @stream: stream to find status for + * + * Return: pointer to the matching dc_stream_status, or NULL if not found. */ struct dc_stream_status *dc_state_get_stream_status( struct dc_state *state, const struct dc_stream_state *stream) { - uint8_t i; + struct dc_state_status status = {}; + struct dc_get_status_options options = { + .state = state, + .types = DC_GET_STATUS_STREAM, + .stream = stream, + }; - if (state == NULL) + if (dc_state_get_status(&status, &options) != DC_OK) return NULL; - for (i = 0; i < state->stream_count; i++) { - if (stream == state->streams[i]) - return &state->stream_status[i]; - } - - return NULL; + return status.stream_count > 0 ? status.stream_status[0] : NULL; } enum mall_stream_type dc_state_get_pipe_subvp_type(const struct dc_state *state, diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c index 7666cdc78f4e..cdcf140bc1bb 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c @@ -24,6 +24,7 @@ */ #include "dm_services.h" +#include "dm_helpers.h" #include "basics/dc_common.h" #include "dc.h" #include "core_types.h" @@ -99,7 +100,9 @@ void update_stream_signal(struct dc_stream_state *stream, struct dc_sink *sink) if (stream->link->frl_flags.force_frl_always || stream->link->frl_flags.force_frl_max - || stream->link->frl_flags.force_frl_dsc) + || stream->link->frl_flags.force_frl_dsc + || (stream->link->frl_flags.force_frl_rate != 0 && + stream->link->frl_flags.force_frl_rate != 0xF)) stream->signal = SIGNAL_TYPE_HDMI_FRL; } } @@ -193,7 +196,6 @@ static void dc_stream_free(struct kref *kref) struct dc_stream_state *stream = container_of(kref, struct dc_stream_state, refcount); dc_stream_destruct(stream); - kfree(stream->update_scratch); kfree(stream); } @@ -203,6 +205,7 @@ void dc_stream_release(struct dc_stream_state *stream) kref_put(&stream->refcount, dc_stream_free); } } +EXPORT_IF_KUNIT(dc_stream_release); struct dc_stream_state *dc_create_stream_for_sink( struct dc_sink *sink) @@ -217,13 +220,6 @@ struct dc_stream_state *dc_create_stream_for_sink( if (stream == NULL) goto fail; - DC_RUN_WITH_PREEMPTION_ENABLED(stream->update_scratch = - kzalloc((int32_t) dc_update_scratch_space_size(), - GFP_ATOMIC)); - - if (stream->update_scratch == NULL) - goto fail; - if (dc_stream_construct(stream, sink) == false) goto fail; @@ -232,10 +228,8 @@ struct dc_stream_state *dc_create_stream_for_sink( return stream; fail: - if (stream) { - kfree(stream->update_scratch); + if (stream) kfree(stream); - } return NULL; } @@ -248,16 +242,6 @@ struct dc_stream_state *dc_copy_stream(const struct dc_stream_state *stream) if (!new_stream) return NULL; - // Scratch is not meant to be reused across copies, as might have self-referential pointers - new_stream->update_scratch = kzalloc( - (int32_t) dc_update_scratch_space_size(), - GFP_KERNEL - ); - if (!new_stream->update_scratch) { - kfree(new_stream); - return NULL; - } - if (new_stream->sink) dc_sink_retain(new_stream->sink); @@ -720,20 +704,17 @@ bool dc_stream_remove_writeback(struct dc *dc, return false; } - /* remove writeback info for disabled writeback pipes from stream */ + /* remove writeback info for the requested writeback pipe from stream */ for (i = 0, j = 0; i < stream->num_wb_info; i++) { - if (stream->writeback_info[i].wb_enabled) { + /* drop every entry that targets the pipe being removed */ + if (stream->writeback_info[i].dwb_pipe_inst == dwb_pipe_inst) + continue; - if (stream->writeback_info[i].dwb_pipe_inst == dwb_pipe_inst) - stream->writeback_info[i].wb_enabled = false; - - /* trim the array */ - if (j < i) { - memcpy(&stream->writeback_info[j], &stream->writeback_info[i], - sizeof(struct dc_writeback_info)); - j++; - } - } + /* keep this entry, compacting it down when earlier entries were removed */ + if (j != i) + memcpy(&stream->writeback_info[j], &stream->writeback_info[i], + sizeof(struct dc_writeback_info)); + j++; } stream->num_wb_info = j; diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 13c1f7cd9d7d..63c0dec85697 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -29,6 +29,7 @@ #include "dc_types.h" #include "dc_state.h" #include "dc_plane.h" +#include "dc_probe.h" #include "grph_object_defs.h" #include "logger_types.h" #include "hdcp_msg_types.h" @@ -65,7 +66,7 @@ struct dcn_dsc_reg_state; struct dcn_optc_reg_state; struct dcn_dccg_reg_state; -#define DC_VER "3.2.388" +#define DC_VER "3.2.391" /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC @@ -314,6 +315,7 @@ struct mpc_color_caps { struct lut3d_caps mcm_3d_lut_caps; struct lut3d_caps rmcm_3d_lut_caps; bool preblend; + struct fixed31_32 max_gamut_remap_coeff; }; /** @@ -518,7 +520,7 @@ struct dc_static_screen_params { * underscan we don't expect to see this call at all. */ -enum surface_update_type { +enum dc_update_type { UPDATE_TYPE_FAST, /* super fast, safe to execute in isr */ UPDATE_TYPE_MED, /* ISR safe, most of programming needed, no bw/clk change*/ UPDATE_TYPE_FULL, /* may need to shuffle resources */ @@ -529,10 +531,11 @@ enum dc_lock_descriptor { LOCK_DESCRIPTOR_STREAM = 0x1, LOCK_DESCRIPTOR_LINK = 0x2, LOCK_DESCRIPTOR_GLOBAL = 0x4, + LOCK_DESCRIPTOR_PROBE = 0x8, }; -struct surface_update_descriptor { - enum surface_update_type update_type; +struct dc_update_descriptor { + enum dc_update_type update_type; enum dc_lock_descriptor lock_descriptor; }; @@ -632,6 +635,7 @@ enum visual_confirm { VISUAL_CONFIRM_SMARTMUX_DGPU = 10, VISUAL_CONFIRM_REPLAY = 12, VISUAL_CONFIRM_SUBVP = 14, + VISUAL_CONFIRM_ABM = 15, VISUAL_CONFIRM_MCLK_SWITCH = 16, VISUAL_CONFIRM_FAMS2 = 19, VISUAL_CONFIRM_HW_CURSOR = 20, @@ -756,6 +760,15 @@ struct dc_clocks { * Elements below are not compared for the purposes of * optimization required */ + + /* + * @cstate_allow + * + * DCN's DF C-state vote as last successfully acknowledged by PMFW. + * false = DCN does NOT permit DF C-state; + * true = DCN permits DF C-state; + */ + bool cstate_allow; bool prev_p_state_change_support; bool fclk_prev_p_state_change_support; int num_ways; @@ -1016,14 +1029,9 @@ struct dc_bounding_box_overrides { }; struct dc_qos_info { - uint32_t actual_peak_bw_in_mbps; uint32_t qos_bandwidth_lb_in_mbps; - uint32_t actual_avg_bw_in_mbps; uint32_t calculated_avg_bw_in_mbps; - uint32_t actual_max_latency_in_ns; - uint32_t actual_min_latency_in_ns; uint32_t qos_max_latency_ub_in_ns; - uint32_t actual_avg_latency_in_ns; uint32_t qos_avg_latency_ub_in_ns; uint32_t dcn_bandwidth_ub_in_mbps; uint32_t qos_max_bw_budget_in_mbps; @@ -1288,6 +1296,8 @@ struct dc_debug_options { bool enable_replay_esd_recovery; uint8_t iommu_mismatch_temp_wka; bool disable_dynamic_expansion_for_test_pattern; + uint32_t dml21_custom_derate_num_dpms; + uint32_t dml21_custom_derate_at_dpm[DML2_MAX_NUM_DPM_LVL]; }; @@ -1505,13 +1515,9 @@ union dc_plane_cm_flags { unsigned int blend_enable : 1; /* whether legacy (lut3d_func) or DMA is valid */ unsigned int lut3d_dma_enable : 1; -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) /* RMCM lut to be used instead of MCM */ unsigned int rmcm_enable : 1; unsigned int reserved: 27; -#else - unsigned int reserved: 28; -#endif } bits; }; @@ -1933,6 +1939,8 @@ struct dc_scratch_space { bool dp_skip_DID2; bool dp_skip_reset_segment; bool dp_skip_fs_144hz; + /* Some DP bridges don't work with RBR and must use HBR. */ + bool dp_skip_rbr; bool dp_mot_reset_segment; /* Some USB4 docks do not handle turning off MST DSC once it has been enabled. */ bool dpia_mst_dsc_always_on; @@ -1985,6 +1993,12 @@ struct dc { struct dc_state *current_state; struct resource_pool *res_pool; + /** + * @update_scratch_pool: Per-commit scratch buffers for dc_update_state. + */ + struct dc_update_scratch_space *update_scratch_pool[MAX_STREAMS + 1]; + bool update_scratch_in_use[MAX_STREAMS + 1]; + struct clk_mgr *clk_mgr; /* Display Engine Clock levels */ @@ -2104,6 +2118,157 @@ struct dc_surface_update { struct cm_hist_control *cm_hist_control; }; +struct dc_state_update { + struct dc_stream_state *stream; + struct dc_stream_update *stream_update; + struct dc_surface_update *surface_updates; + int surface_count; + const struct dc_probe_updates *probe_updates; +}; + +/** + * dc_check_state_update() - Classify an update without committing it. + * @check_config: DC check configuration + * @updates: root update object to classify + * + * Return: descriptor indicating update type and required lock scope. + */ +struct dc_update_descriptor dc_check_state_update( + const struct dc_check_config *check_config, + const struct dc_state_update *updates); + +/** + * dc_update_state - Commit an absolute dc_state_update. + * @dc: DC structure + * @updates: root update object carrying stream, plane, and probe updates + * + * Return: true on success, false on failure. + */ +bool dc_update_state(struct dc *dc, const struct dc_state_update *updates); + +struct dc_update_scratch_space; + +/** + * dc_update_state_init - Acquire and initialise a commit scratch buffer. + * @dc: DC structure + * @updates: update descriptor; validated before the slot is acquired + * + * Return: a scratch slot on success, NULL if validation fails or the pool + * is exhausted. The slot must be released via dc_update_state_cleanup() on + * success, or automatically by dc_update_state_prepare() on failure. + */ +struct dc_update_scratch_space *dc_update_state_init( + struct dc *dc, + const struct dc_state_update *updates +); + +/** + * dc_update_state_prepare - Prepare the commit under the global lock. + * @scratch: commit scratch from dc_update_state_init() + * + * On failure the scratch slot is released and false is returned; the caller + * must not call execute or cleanup. + */ +bool dc_update_state_prepare(struct dc_update_scratch_space *scratch); + +/** + * dc_update_state_execute - Program hardware; called without the global lock. + * @scratch: commit scratch from dc_update_state_init() + */ +void dc_update_state_execute(const struct dc_update_scratch_space *scratch); + +/** + * dc_update_state_cleanup - Finalise the commit and release the scratch slot. + * @scratch: commit scratch from dc_update_state_init() + * + * Must be called with the global lock held. Returns true if the caller must + * loop back to prepare (SEAMLESS continuation). + */ +bool dc_update_state_cleanup(struct dc_update_scratch_space *scratch); + +/** + * struct dc_probe_latencies - min/max/avg memory latency in ns. + * @max_latency_ns: maximum latency in nanoseconds + * @avg_latency_ns: average latency in nanoseconds + * @min_latency_ns: minimum latency in nanoseconds + */ +struct dc_probe_latencies { + uint32_t max_latency_ns; + uint32_t avg_latency_ns; + uint32_t min_latency_ns; +}; + +/** + * struct dc_probe_status - results for a probe. + * @valid: true if a measurement was latched. + * @type: type of the probe that produced this result. + * @u.bandwidth_mbps: peak BW in Mbps (DC_PROBE_PEAK_MEM_BW). + * @u.latency: min/max/avg memory latency in ns (DC_PROBE_MEM_LATENCY), + * stored as struct dc_probe_latencies. + * @u.urgent_assertion_count: number of urgent assertion events (DC_PROBE_URGENT_ASSERTION_COUNT). + * @u.prefetch_data_size: total prefetch data in bytes (DC_PROBE_PREFETCH_DATA_SIZE). + */ +struct dc_probe_status { + bool valid; + enum dc_probe_type type; + union { + uint32_t bandwidth_mbps; + struct dc_probe_latencies latency; + uint32_t urgent_assertion_count; + uint32_t prefetch_data_size; + } u; +}; + +/** + * enum dc_get_status_type - Bitmask selecting which status classes to populate. + * @DC_GET_STATUS_STREAM: populate stream_status fields in dc_state_status + * @DC_GET_STATUS_PROBE: populate probe_status fields in dc_state_status + */ +enum dc_get_status_type { + DC_GET_STATUS_STREAM = (1u << 0), + DC_GET_STATUS_PROBE = (1u << 1), +}; + +/** + * struct dc_get_status_options - Input selector for dc_state_get_status. + * @state: source state to read status from + * @types: OR of dc_get_status_type values selecting classes to populate + * @stream: optional stream filter for DC_GET_STATUS_STREAM. NULL means + * populate status for all streams in the state + * @probe: optional probe filter for DC_GET_STATUS_PROBE. NULL means + * populate status for all probes in the state + */ +struct dc_get_status_options { + struct dc_state *state; + uint32_t types; + const struct dc_stream_state *stream; + const struct dc_probe_state *probe; +}; + +/** + * struct dc_state_status - Output-only status object from dc_state_get_status. + * @stream_count: number of valid entries in stream_status (DC_GET_STATUS_STREAM) + * @stream_status: pointers to live per-stream status entries + * @probe_count: number of valid entries in probe_status (DC_GET_STATUS_PROBE) + * @probe_status: pointers to live per-probe status entries + */ +struct dc_state_status { + int stream_count; + struct dc_stream_status *stream_status[MAX_STREAMS]; + int probe_count; + struct dc_probe_status *probe_status[MAX_PROBES]; +}; + +/** + * dc_state_get_status - Unified status readback for dc_state. + * @status: output object populated according to options->types + * @options: selects the source state, status classes to fill, and filters + * + * Return: DC_OK on success, DC_ERROR_UNEXPECTED if state is NULL. + */ +enum dc_status dc_state_get_status(struct dc_state_status *status, + const struct dc_get_status_options *options); + struct dc_underflow_debug_data { struct dcn_hubbub_reg_state *hubbub_reg_state; struct dcn_hubp_reg_state *hubp_reg_state[MAX_PIPES]; @@ -2159,9 +2324,9 @@ void dc_post_update_surfaces_to_stream( void dc_get_default_tiling_info(const struct dc *dc, struct dc_tiling_info *tiling_info); /** - * struct dc_validation_set - Struct to store surface/stream associations for validation + * struct dc_validation_stream - Per-stream surface/stream association for validation */ -struct dc_validation_set { +struct dc_validation_stream { /** * @stream: Stream state properties */ @@ -2178,6 +2343,31 @@ struct dc_validation_set { uint8_t plane_count; }; +/** + * struct dc_validation_set - Root validation input grouping all streams for a commit + */ +struct dc_validation_set { + /** + * @streams: Per-stream entries (stream + its planes) + */ + struct dc_validation_stream streams[MAX_STREAMS]; + + /** + * @stream_count: Number of active entries in @streams + */ + uint8_t stream_count; + + /** + * @probes: Global probe descriptors to validate alongside the streams + */ + struct dc_probe_state probes[MAX_PROBES]; + + /** + * @probe_count: Number of active entries in @probes + */ + uint8_t probe_count; +}; + bool dc_validate_boot_timing(const struct dc *dc, const struct dc_sink *sink, struct dc_crtc_timing *crtc_timing); @@ -2185,8 +2375,7 @@ bool dc_validate_boot_timing(const struct dc *dc, enum dc_status dc_validate_plane(struct dc *dc, const struct dc_plane_state *plane_state); enum dc_status dc_validate_with_context(struct dc *dc, - const struct dc_validation_set set[], - unsigned int set_count, + const struct dc_validation_set *set, struct dc_state *context, enum dc_validate_mode validate_mode); @@ -2795,6 +2984,14 @@ bool dc_link_update_pr_state(struct dc_link *link, bool dc_link_set_pr_general_cmd(struct dc_link *link, struct dmub_cmd_pr_general_cmd_data *general_cmd_data); +/* + * Measure Panel Replay residency for the given link. + * mode: PR_RESIDENCY_MODE_PHY, PR_RESIDENCY_MODE_ALPM, or + * PR_RESIDENCY_MODE_ENABLEMENT_PERIOD + */ +void dc_link_edp_replay_residency(const struct dc_link *link, + unsigned int *residency, bool is_start, enum pr_residency_mode mode); + /* * Get Panel Replay state: * @@ -2993,6 +3190,7 @@ enum dc_irq_source dc_interrupt_to_irq_source( uint32_t ext_id); bool dc_interrupt_set(struct dc *dc, enum dc_irq_source src, bool enable); void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src); +bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst); enum dc_irq_source dc_get_hpd_irq_source_at_index( struct dc *dc, uint32_t link_index); @@ -3007,6 +3205,8 @@ void dc_resume(struct dc *dc); void dc_power_down_on_boot(struct dc *dc); +void dc_disable_dangling_timing_generators(struct dc *dc); + /* * HDCP Interfaces */ diff --git a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c index 68ed0e16639d..80a21d7cae90 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c +++ b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c @@ -1908,6 +1908,8 @@ static void dc_dmub_srv_ib_based_fams2_update_config(struct dc *dc, config->global.features.bits.enable = enable && context->bw_ctx.bw.dcn.fams2_global_config.features.bits.enable; config->global.features.bits.enable_ppt_check = dc->debug.fams2_config.bits.enable_ppt_check; + dmub_srv_flush_buffer_mem(dc->ctx->dmub_srv->dmub, &dc->ctx->dmub_srv->dmub->ib_mem_gart); + dm_execute_dmub_cmd_list(dc->ctx, 1, &cmd, DM_DMUB_WAIT_TYPE_WAIT); } @@ -2073,7 +2075,7 @@ bool dc_dmub_srv_ips_query_residency_info(const struct dc_context *ctx, uint8_t union dmub_rb_cmd cmd; uint32_t bytes = sizeof(struct dmub_ips_residency_info); - dmub_flush_buffer_mem(&ctx->dmub_srv->dmub->scratch_mem_fb); + dmub_srv_flush_buffer_mem(ctx->dmub_srv->dmub, &ctx->dmub_srv->dmub->scratch_mem_fb); memset(&cmd, 0, sizeof(cmd)); cmd.ips_query_residency_info.header.type = DMUB_CMD__IPS; diff --git a/drivers/gpu/drm/amd/display/dc/dc_probe.h b/drivers/gpu/drm/amd/display/dc/dc_probe.h new file mode 100644 index 000000000000..ebf33b162b63 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/dc_probe.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +// +// Copyright 2025 Advanced Micro Devices, Inc. + +#ifndef _DC_PROBE_H_ +#define _DC_PROBE_H_ + +#include "os_types.h" + +/** + * enum dc_probe_type - What DM wants to probe. + * + * Each value names a measurable quantity as an abstraction. DC resolves it to + * whatever HW measurement block fulfills it. DM never selects the HW block. + */ +enum dc_probe_type { + DC_PROBE_PEAK_MEM_BW = 0, + DC_PROBE_AVG_MEM_BW, + DC_PROBE_MEM_LATENCY, + DC_PROBE_URGENT_RAMP_LATENCY, + DC_PROBE_URGENT_ASSERTION_COUNT, + DC_PROBE_PREFETCH_DATA_SIZE, +}; + +/** + * enum dc_probe_target_state - Target lifecycle state DM wants DC to reach. + * + * DM sets this to describe the final state DC must reach by the end of the + * commit. DC performs whatever HW transition sequence is needed. + * + * @DC_PROBE_NOT_MEASURING: probe inactive, no valid data available. + * @DC_PROBE_MEASURING: probe runs continuously. The latest value can be + * read back at any time and may differ on each read. + * @DC_PROBE_MEASURED: probe performed one shot. The result is latched and + * stays valid until DM transitions back to DC_PROBE_NOT_MEASURING. + */ +enum dc_probe_target_state { + DC_PROBE_NOT_MEASURING = 0, + DC_PROBE_MEASURING, + DC_PROBE_MEASURED, +}; + +/** + * enum dc_probe_scope_type - What the probe is scoped to. + * @DC_PROBE_SCOPE_GLOBAL: whole memory subsystem, no stream/plane selector. + * + * Only GLOBAL is implemented. Per-stream/plane scoping must select targets by + * stable id, not object pointer — dc_state copy semantics would dangle a raw + * pointer when the absolute-set commit removes or replaces the target. + */ +enum dc_probe_scope_type { + DC_PROBE_SCOPE_GLOBAL = 0, +}; + +/** + * struct dc_probe_scope - Selects what a probe measures against. + * @type: scope kind, only DC_PROBE_SCOPE_GLOBAL is implemented. + */ +struct dc_probe_scope { + enum dc_probe_scope_type type; +}; + +/** + * struct dc_probe_state - DM-authored descriptor of a single probe. + * + * A plain inline value with copy semantics: no allocation, no refcount. DC + * resolves each descriptor to a HW measurement instance and diffs the desired + * set against the committed set to plan the transition. + * + * @type: what to measure. + * @target_state: desired lifecycle state for this probe. + * @scope: what the probe is scoped to (GLOBAL only for now). + */ +struct dc_probe_state { + enum dc_probe_type type; + enum dc_probe_target_state target_state; + struct dc_probe_scope scope; +}; + +#define MAX_PROBES 1 + +/** + * struct dc_probe_updates - Absolute set of probes DM wants active. + * + * Mirrors the plane/stream absolute-set model: the array is the complete + * desired set. DC compares it against the committed set to add, remove, or + * transition probes. + * + * @probes: desired probe descriptors. + * @probe_count: number of valid entries in @probes. + */ +struct dc_probe_updates { + struct dc_probe_state probes[MAX_PROBES]; + int probe_count; +}; + +#endif /* _DC_PROBE_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/dc_state.h b/drivers/gpu/drm/amd/display/dc/dc_state.h index db1e63a7d460..acf461225e9d 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_state.h +++ b/drivers/gpu/drm/amd/display/dc/dc_state.h @@ -74,4 +74,11 @@ bool dc_state_add_all_planes_for_stream( struct dc_stream_status *dc_state_get_stream_status( struct dc_state *state, const struct dc_stream_state *stream); + +struct dc_state_status; +struct dc_get_status_options; + +enum dc_status dc_state_get_status(struct dc_state_status *status, + const struct dc_get_status_options *options); + #endif /* _DC_STATE_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/dc_stream.h b/drivers/gpu/drm/amd/display/dc/dc_stream.h index 8b164edc9c51..4530f294f1c2 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_stream.h +++ b/drivers/gpu/drm/amd/display/dc/dc_stream.h @@ -329,7 +329,6 @@ struct dc_stream_state { enum dc_drr_trigger_mode drr_trigger_mode; - struct dc_update_scratch_space *update_scratch; bool firmware_controlled_hdr_info_packet; }; @@ -408,32 +407,8 @@ bool dc_update_planes_and_stream(struct dc *dc, struct dc_stream_state *dc_stream, struct dc_stream_update *stream_update); -struct dc_update_scratch_space; +struct dc_state_update; -size_t dc_update_scratch_space_size(void); - -struct dc_update_scratch_space *dc_update_planes_and_stream_init( - struct dc *dc, - struct dc_surface_update *surface_updates, - int surface_count, - struct dc_stream_state *dc_stream, - struct dc_stream_update *stream_update -); - -// Locked, false is failed -bool dc_update_planes_and_stream_prepare( - struct dc_update_scratch_space *scratch -); - -// Unlocked -void dc_update_planes_and_stream_execute( - const struct dc_update_scratch_space *scratch -); - -// Locked, true if call again -bool dc_update_planes_and_stream_cleanup( - struct dc_update_scratch_space *scratch -); /* * Set up surface attributes and associate to a stream @@ -518,7 +493,8 @@ void dc_enable_stereo( /* Triggers multi-stream synchronization. */ void dc_trigger_sync(struct dc *dc, struct dc_state *context); -struct surface_update_descriptor dc_check_update_surfaces_for_stream( +/* Shim: packs args into dc_state_update and calls dc_check_state_update(). */ +struct dc_update_descriptor dc_check_update_surfaces_for_stream( const struct dc_check_config *check_config, struct dc_surface_update *updates, int surface_count, diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index 90dd1ae7e953..3edeb94fba23 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -188,6 +188,7 @@ struct dc_panel_patch { unsigned int skip_audio_sab_check; unsigned int mst_start_top_delay; unsigned int remove_sink_ext_caps; + bool disable_second_tile; unsigned int disable_colorimetry; uint8_t blankstream_before_otg_off; bool oled_optimize_display_on; @@ -1347,6 +1348,14 @@ struct dc_panel_config { struct ilr { bool optimize_edp_link_rate; /* eDP ILR */ } ilr; + /* CACP*/ + struct cacp { + unsigned int cacp_supported; + unsigned int cacp_control_mode; + unsigned int strscl_valid; + unsigned int strscl_sdr[4]; + unsigned int strscl_hdr[4]; + } cacp; /* Adaptive VariBright*/ struct adaptive_vb { bool disable_adaptive_vb; @@ -1406,28 +1415,20 @@ enum dc_cm_lut_swizzle { enum dc_cm_lut_pixel_format { CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB, -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) CM_LUT_PIXEL_FORMAT_BGRA16161616_UNORM_12MSB, -#endif CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB, -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) CM_LUT_PIXEL_FORMAT_BGRA16161616_UNORM_12LSB, -#endif CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10, -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) CM_LUT_PIXEL_FORMAT_BGRA16161616_FLOAT_FP1_5_10 -#endif }; enum dc_cm_lut_size { CM_LUT_SIZE_NONE, CM_LUT_SIZE_999, CM_LUT_SIZE_171717, -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) CM_LUT_SIZE_333333, CM_LUT_SIZE_454545, CM_LUT_SIZE_656565, -#endif }; #ifndef TRIM_CM2 diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c index 483cd9ab7eb7..71903a6c63a0 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c @@ -572,7 +572,7 @@ static void dccg35_set_hdmistreamclk_src_new( case 0: REG_UPDATE_2(HDMISTREAMCLK_CNTL, HDMISTREAMCLK0_EN, (src == HDMI_STREAM_REFCLK) ? 0 : 1, - DPSTREAMCLK0_SRC_SEL, + HDMISTREAMCLK0_SRC_SEL, (src == HDMI_STREAM_REFCLK) ? 0 : src); break; default: @@ -1654,7 +1654,7 @@ void dccg35_set_dpstreamclk_root_clock_gating(struct dccg *dccg, int dp_hpo_inst -static void dccg35_set_hdmistreamclk( +void dccg35_set_hdmistreamclk( struct dccg *dccg, enum streamclk_source src, uint32_t otg_inst) @@ -1680,8 +1680,11 @@ void dccg35_set_hdmistreamclk_root_clock_gating(struct dccg *dccg, bool enable) { struct dcn_dccg *dccg_dcn = TO_DCN_DCCG(dccg); - if (dccg->ctx->dc->debug.root_clock_optimization.bits.hdmistream) - REG_UPDATE(DCCG_GATE_DISABLE_CNTL6, HDMISTREAMCLK0_ROOT_GATE_DISABLE, enable ? 1 : 0); + if (!dccg->ctx->dc->debug.root_clock_optimization.bits.hdmistream && !enable) { + DC_LOG_DEBUG("%s: HDMISTREAMCLK0_ROOT_GATE DISABLE = 0 bypassed", __func__); + return; + } + REG_UPDATE(DCCG_GATE_DISABLE_CNTL6, HDMISTREAMCLK0_ROOT_GATE_DISABLE, enable ? 1 : 0); DC_LOG_DEBUG("%s: HDMISTREAMCLK0_ROOT_GATE_DISABLE = %d\n", __func__, enable ? 1 : 0); } diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h index 554700287c1a..58f4c1e998b3 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h @@ -269,5 +269,8 @@ void dccg35_disable_dscclk(struct dccg *dccg, int inst); void dccg35_enable_symclk_se(struct dccg *dccg, uint32_t stream_enc_inst, uint32_t link_enc_inst); void dccg35_disable_symclk_se(struct dccg *dccg, uint32_t stream_enc_inst, uint32_t link_enc_inst); - +void dccg35_set_hdmistreamclk( + struct dccg *dccg, + enum streamclk_source src, + uint32_t otg_inst); #endif //__DCN35_DCCG_H__ diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c index 616a896f0782..3b03b152da22 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c @@ -313,7 +313,7 @@ static void dccg42_init(struct dccg *dccg) static const struct dccg_funcs dccg42_funcs = { .enable_hdmicharclk = dccg401_enable_hdmicharclk, .disable_hdmicharclk = dccg42_disable_hdmicharclk, - .set_hdmistreamclk = dccg401_set_hdmistreamclk, + .set_hdmistreamclk = dccg35_set_hdmistreamclk, .set_hdmistreamclk_root_clock_gating = dccg35_set_hdmistreamclk_root_clock_gating, .update_dpp_dto = dccg35_update_dpp_dto, .dpp_root_clock_control = dccg35_dpp_root_clock_control, diff --git a/drivers/gpu/drm/amd/display/dc/dce/Makefile b/drivers/gpu/drm/amd/display/dc/dce/Makefile index 986e0e7abbc2..72bf70d89b74 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dce/Makefile @@ -23,14 +23,14 @@ # Makefile for common 'dce' logic # HW object file under this folder follow similar pattern for HW programming # - register offset and/or shift + mask stored in the dec_hw struct -# - register programming through common macros that look up register +# - register programming through common macros that look up register # offset/shift/mask stored in dce_hw struct DCE = dce_audio.o dce_stream_encoder.o dce_link_encoder.o \ dce_mem_input.o dce_clock_source.o dce_scl_filters.o dce_transform.o \ dce_opp.o dce_dmcu.o dce_abm.o dce_ipp.o dce_aux.o \ dce_i2c.o dce_i2c_hw.o dce_i2c_sw.o dmub_psr.o dmub_abm.o dmub_abm_lcd.o dce_panel_cntl.o \ -dmub_hw_lock_mgr.o dmub_outbox.o dmub_replay.o +dmub_hw_lock_mgr.o dmub_outbox.o dmub_replay.o dmub_abm_cacp.o AMD_DAL_DCE = $(addprefix $(AMDDALPATH)/dc/dce/,$(DCE)) diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c b/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c index 7c293917e6fd..8e3862f6dac2 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c @@ -45,9 +45,7 @@ clk_src->base.ctx #define DC_LOGGER \ - calc_pll_cs->ctx->logger -#define DC_LOGGER_INIT() \ - struct calc_pll_clock_source *calc_pll_cs = &clk_src->calc_pll + CTX->logger #undef FN #define FN(reg_name, field_name) \ @@ -287,6 +285,7 @@ static bool calc_pll_dividers_in_range( } static uint32_t calculate_pixel_clock_pll_dividers( + struct dce110_clk_src *clk_src, struct calc_pll_clock_source *calc_pll_cs, struct pll_settings *pll_settings) { @@ -474,7 +473,7 @@ static uint32_t dce110_get_pix_clk_dividers_helper ( { uint32_t field = 0; uint32_t pll_calc_error = MAX_PLL_CALC_ERROR; - DC_LOGGER_INIT(); + /* Check if reference clock is external (not pcie/xtalin) * HW Dce80 spec: * 00 - PCIE_REFCLK, 01 - XTALIN, 02 - GENERICA, 03 - GENERICB @@ -517,12 +516,14 @@ static uint32_t dce110_get_pix_clk_dividers_helper ( /*Calculate Dividers by HDMI object, no SS case or SS case */ pll_calc_error = calculate_pixel_clock_pll_dividers( + clk_src, &clk_src->calc_pll_hdmi, pll_settings); else /*Calculate Dividers by default object, no SS case or SS case */ pll_calc_error = calculate_pixel_clock_pll_dividers( + clk_src, &clk_src->calc_pll, pll_settings); @@ -568,7 +569,6 @@ static uint32_t dce110_get_pix_clk_dividers( { struct dce110_clk_src *clk_src = TO_DCE110_CLK_SRC(cs); uint32_t pll_calc_error = MAX_PLL_CALC_ERROR; - DC_LOGGER_INIT(); if (pix_clk_params == NULL || pll_settings == NULL || pix_clk_params->requested_pix_clk_100hz == 0) { @@ -600,7 +600,6 @@ static uint32_t dce112_get_pix_clk_dividers( struct pll_settings *pll_settings) { struct dce110_clk_src *clk_src = TO_DCE110_CLK_SRC(cs); - DC_LOGGER_INIT(); if (pix_clk_params == NULL || pll_settings == NULL || pix_clk_params->requested_pix_clk_100hz == 0) { @@ -1229,9 +1228,9 @@ static bool get_dp_dto_frequency_100hz( */ modulo_hz = REG_READ(MODULO[inst]); if (modulo_hz) { - temp = div_u64((uint64_t)clock_hz * dp_dto_ref_khz * 10, modulo_hz); - ASSERT(temp / 100 <= 0xFFFFFFFFUL); - *pixel_clk_100hz = (unsigned int)(temp / 100); + temp = clock_hz * dp_dto_ref_khz * 10; + ASSERT(temp <= UINT_MAX * modulo_hz * 100ULL); + *pixel_clk_100hz = div_u64(temp, modulo_hz * 100); } else *pixel_clk_100hz = 0; } else { @@ -1285,13 +1284,12 @@ static bool dcn401_get_dp_dto_frequency_100hz(const struct clock_source *clock_s * - target pix_clk_hz = (DPDTO INTEGER * DPDTO MODULO + DPDTO PHASE) */ temp = (unsigned long long)dp_dto_integer * modulo_hz + phase_hz; - - if (temp / 100 > 0xFFFFFFFFUL) { + if (temp > (UINT_MAX * 100ULL)) { /* pixel rate 100hz should never be this high, if it is, throw an assert and return 0 */ BREAK_TO_DEBUGGER(); *pixel_clk_100hz = 0; } else { - *pixel_clk_100hz = (unsigned int)(temp / 100); + *pixel_clk_100hz = div_u64(temp, 100); } return true; @@ -1443,8 +1441,6 @@ static uint32_t dcn3_get_pix_clk_dividers( unsigned long long actual_pix_clk_100Hz = pix_clk_params ? pix_clk_params->requested_pix_clk_100hz : 0; struct dce110_clk_src *clk_src = TO_DCE110_CLK_SRC(cs); - DC_LOGGER_INIT(); - if (pix_clk_params == NULL || pll_settings == NULL || pix_clk_params->requested_pix_clk_100hz == 0) { DC_LOG_ERROR( @@ -1514,7 +1510,6 @@ static const struct clock_source_funcs dce110_clk_src_funcs = { .get_dp_dto_frequency_100hz = get_dp_dto_frequency_100hz }; - static void get_ss_info_from_atombios( struct dce110_clk_src *clk_src, enum as_signal_type as_signal, @@ -1527,7 +1522,7 @@ static void get_ss_info_from_atombios( struct spread_spectrum_info *ss_info_cur; struct spread_spectrum_data *ss_data_cur; uint32_t i; - DC_LOGGER_INIT(); + if (ss_entries_num == NULL) { DC_LOG_SYNC( "Invalid entry !!!\n"); @@ -1658,6 +1653,7 @@ static void ss_info_from_atombios_create( } static bool calc_pll_max_vco_construct( + struct dce110_clk_src *clk_src, struct calc_pll_clock_source *calc_pll_cs, struct calc_pll_clock_source_init_data *init_data) { @@ -1809,6 +1805,7 @@ bool dce110_clk_src_construct( ss_info_from_atombios_create(clk_src); if (!calc_pll_max_vco_construct( + clk_src, &clk_src->calc_pll, &calc_pll_cs_init_data)) { ASSERT_CRITICAL(false); @@ -1823,7 +1820,7 @@ bool dce110_clk_src_construct( if (!calc_pll_max_vco_construct( - &clk_src->calc_pll_hdmi, &calc_pll_cs_init_data_hdmi)) { + clk_src, &clk_src->calc_pll_hdmi, &calc_pll_cs_init_data_hdmi)) { ASSERT_CRITICAL(false); goto unexpected_failure; } diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c b/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c index 12c85c3afd6a..864491bfd7f7 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c @@ -1145,8 +1145,8 @@ static void dce_transform_set_gamut_remap( for (i = 0; i < GAMUT_MATRIX_SIZE; i++) arr_matrix[i] = adjust->temperature_matrix[i]; - convert_float_matrix( - arr_reg_val, arr_matrix, GAMUT_MATRIX_SIZE); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, GAMUT_MATRIX_SIZE); program_gamut_remap(xfm_dce, arr_reg_val); } diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c index 93550c5e4d02..2df6e82b794f 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c @@ -25,6 +25,7 @@ #include "dmub_abm.h" #include "dmub_abm_lcd.h" +#include "dmub_abm_cacp.h" #include "dc.h" #include "core_types.h" #include "dmub_cmd.h" @@ -36,6 +37,7 @@ #define ABM_FEATURE_NO_SUPPORT 0 #define ABM_LCD_SUPPORT 1 +#define ABM_CACP_SUPPORT 2 static unsigned int abm_feature_support(struct abm *abm, unsigned int panel_inst) { @@ -52,15 +54,48 @@ static unsigned int abm_feature_support(struct abm *abm, unsigned int panel_inst } if (i < edp_num) { - ret = ABM_LCD_SUPPORT; + if (edp_links[panel_inst]->panel_config.cacp.cacp_supported) + ret = ABM_CACP_SUPPORT; + else if ((edp_links[panel_inst]->panel_type == PANEL_TYPE_LCD) || + (edp_links[panel_inst]->panel_type == PANEL_TYPE_MINILED)) + ret = ABM_LCD_SUPPORT; } return ret; } +static enum dc_panel_type abm_get_paneltype(struct abm *abm, unsigned int panel_inst) +{ + struct dc_context *dc = abm->ctx; + struct dc_link *edp_links[MAX_NUM_EDP]; + unsigned int i, edp_num; + enum dc_panel_type ret = PANEL_TYPE_NONE; + + dc_get_edp_links(dc->dc, edp_links, &edp_num); + + for (i = 0; i < edp_num; i++) { + if (edp_links[i]->link_status.link_active + && panel_inst == i) + break; + } + + if (i < edp_num) + ret = edp_links[panel_inst]->panel_type; + + return ret; +} + static void dmub_abm_init_ex(struct abm *abm, uint32_t backlight, uint32_t user_level) { + unsigned int i = 0; + uint8_t panel_mask = 0; + dmub_abm_init(abm, backlight, user_level); + for (i = 0; i < MAX_NUM_EDP; i++) + panel_mask |= (0x01 << i); + + if (panel_mask) + dmub_cacp_enable_fractional_pwm(abm, panel_mask); } static unsigned int dmub_abm_get_current_backlight_ex(struct abm *abm) @@ -82,17 +117,23 @@ static bool dmub_abm_set_level_ex(struct abm *abm, uint32_t level) bool ret = false; unsigned int feature_support, i; uint8_t panel_mask0 = 0; + uint8_t panel_mask1 = 0; for (i = 0; i < MAX_NUM_EDP; i++) { feature_support = abm_feature_support(abm, i); if (feature_support == ABM_LCD_SUPPORT) panel_mask0 |= (0x01 << i); + else if (feature_support == ABM_CACP_SUPPORT) + panel_mask1 |= (0x01 << i); } if (panel_mask0) ret = dmub_abm_set_level(abm, level, panel_mask0); + if (panel_mask1) + ret = dmub_cacp_set_level(abm, level, panel_mask1); + return ret; } @@ -107,6 +148,8 @@ static bool dmub_abm_init_config_ex(struct abm *abm, if (feature_support == ABM_LCD_SUPPORT) dmub_abm_init_config(abm, src, bytes, inst); + else if (feature_support == ABM_CACP_SUPPORT) + dmub_cacp_init(abm, src, bytes, inst); return true; } @@ -120,6 +163,8 @@ static bool dmub_abm_set_pause_ex(struct abm *abm, bool pause, unsigned int pane if (feature_support == ABM_LCD_SUPPORT) ret = dmub_abm_set_pause(abm, pause, panel_inst, stream_inst); + else if (feature_support == ABM_CACP_SUPPORT) + ret = dmub_cacp_set_pause(abm, pause, panel_inst, stream_inst); return ret; } @@ -163,6 +208,25 @@ static bool dmub_abm_set_pipe_ex(struct abm *abm, if (feature_support == ABM_LCD_SUPPORT) ret = dmub_abm_set_pipe(abm, otg_inst, option, panel_inst, pwrseq_inst); + else if (feature_support == ABM_CACP_SUPPORT) + ret = dmub_cacp_set_pipe(abm, otg_inst, option, panel_inst, pwrseq_inst); + + return ret; +} + +static bool dmub_abm_set_event_ex(struct abm *abm, unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int scaling_strength_map, + unsigned int panel_inst) +{ + bool ret = false; + unsigned int feature_support; + + feature_support = abm_feature_support(abm, panel_inst); + + if (feature_support == ABM_LCD_SUPPORT) + ret = dmub_abm_set_event(abm, scaling_enable, scaling_strength_map, panel_inst); + else if (feature_support == ABM_CACP_SUPPORT) + ret = dmub_cacp_set_event(abm, full_screen, trans_info, hdr_mode, scaling_enable, panel_inst); return ret; } @@ -176,11 +240,17 @@ static bool dmub_abm_set_backlight_level_pwm_ex(struct abm *abm, (void)controller_id; bool ret = false; unsigned int feature_support; + enum dc_panel_type panel_type = PANEL_TYPE_NONE; feature_support = abm_feature_support(abm, panel_inst); if (feature_support == ABM_LCD_SUPPORT) ret = dmub_abm_set_backlight_level(abm, backlight_pwm_u16_16, frame_ramp, panel_inst); + else if (feature_support == ABM_CACP_SUPPORT) { + panel_type = abm_get_paneltype(abm, panel_inst); + if (panel_type == PANEL_TYPE_MINILED) + ret = dmub_cacp_set_backlight_level(abm, backlight_pwm_u16_16, frame_ramp, panel_inst); + } return ret; } @@ -194,6 +264,7 @@ static const struct abm_funcs abm_funcs = { .set_abm_pause = dmub_abm_set_pause_ex, .save_restore = dmub_abm_save_restore_ex, .set_pipe_ex = dmub_abm_set_pipe_ex, + .set_abm_event = dmub_abm_set_event_ex, .set_backlight_level_pwm = dmub_abm_set_backlight_level_pwm_ex, }; diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c new file mode 100644 index 000000000000..9f58fa2e25fd --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c @@ -0,0 +1,213 @@ +/* Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved. */ + +#include "dmub_abm.h" +#include "dmub_abm_cacp.h" +#include "dce_abm.h" +#include "dc.h" +#include "dc_dmub_srv.h" +#include "dmub/dmub_srv.h" +#include "core_types.h" + +#define CACP_LEVEL_NUM 4 + +void dmub_cacp_init(struct abm *abm, const char *src, unsigned int bytes, unsigned int panel_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + uint8_t panel_mask = 0x01 << panel_inst; + struct dc_link *edp_links[MAX_NUM_EDP]; + unsigned int i, edp_num; + + // TODO: Optimize by only reading back final 4 bytes + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); + + // Copy iramtable into cw7 + memcpy(dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, (void *)src, bytes); + + memset(&cmd, 0, sizeof(cmd)); + // Fw will copy from cw7 to fw_state + cmd.cacp_init_config.header.type = DMUB_CMD__CACP; + cmd.cacp_init_config.header.sub_type = DMUB_CMD__CACP_INIT_CONFIG; + cmd.cacp_init_config.cacp_init_config_data.src.quad_part = dc->dmub_srv->dmub->scratch_mem_fb.gpu_addr; + cmd.cacp_init_config.cacp_init_config_data.bytes = (uint16_t)bytes; + cmd.cacp_init_config.cacp_init_config_data.panel_mask = panel_mask; + cmd.cacp_init_config.cacp_init_config_data.visual_confirm = + (dc->dc->debug.visual_confirm == VISUAL_CONFIRM_ABM) ? true : false; + + cmd.cacp_init_config.header.payload_bytes = sizeof(struct dmub_cmd_cacp_init_config_data); + + dc_get_edp_links(dc->dc, edp_links, &edp_num); + for (i = 0; i < edp_num; i++) { + if (panel_inst == i) + break; + } + + if (i < edp_num) { + cmd.cacp_init_config.cacp_init_config_data.strscl_valid = + (uint8_t)edp_links[panel_inst]->panel_config.cacp.strscl_valid; + cmd.cacp_init_config.cacp_init_config_data.mode = + edp_links[panel_inst]->panel_config.cacp.cacp_control_mode ? + DMUB_CMD_CACP_CONTROL_MODE_1 : DMUB_CMD_CACP_CONTROL_MODE_0; + for (int j = 0; j < CACP_LEVEL_NUM; j++) { + cmd.cacp_init_config.cacp_init_config_data.strscl_sdr[j] = + (uint8_t)edp_links[panel_inst]->panel_config.cacp.strscl_sdr[j]; + cmd.cacp_init_config.cacp_init_config_data.strscl_hdr[j] = + (uint8_t)edp_links[panel_inst]->panel_config.cacp.strscl_hdr[j]; + } + } + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); +} + +bool dmub_cacp_set_level(struct abm *abm, unsigned int abm_level, unsigned char panel_mask) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_level.header.type = DMUB_CMD__CACP; + cmd.cacp_set_level.header.sub_type = DMUB_CMD__CACP_SET_LEVEL; + + cmd.cacp_set_level.cacp_set_level_data.level = abm_level; + cmd.cacp_set_level.cacp_set_level_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_level.cacp_set_level_data.panel_mask = panel_mask; + + cmd.cacp_set_level.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_level_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + +bool dmub_cacp_set_pipe(struct abm *abm, unsigned int otg_inst, + unsigned int pipe_option, unsigned int panel_inst, unsigned int pwrseq_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_pipe.header.type = DMUB_CMD__CACP; + cmd.cacp_set_pipe.header.sub_type = DMUB_CMD__CACP_SET_PIPE; + + cmd.cacp_set_pipe.cacp_set_pipe_data.otg_inst = (uint8_t)otg_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.panel_inst = (uint8_t)panel_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.set_pipe_option = (uint8_t)pipe_option; + cmd.cacp_set_pipe.cacp_set_pipe_data.pwrseq_inst = (uint8_t)pwrseq_inst; + cmd.cacp_set_pipe.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_pipe_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + +bool dmub_cacp_set_event(struct abm *abm, unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int panel_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_event.header.type = DMUB_CMD__CACP; + cmd.cacp_set_event.header.sub_type = DMUB_CMD__CACP_SET_EVENT; + + //TODO: + cmd.cacp_set_event.cacp_set_event_data.full_screen_mode = (uint8_t)full_screen; + cmd.cacp_set_event.cacp_set_event_data.trans_info = trans_info; + cmd.cacp_set_event.cacp_set_event_data.hdr_mode = (uint8_t)hdr_mode; + cmd.cacp_set_event.cacp_set_event_data.vb_scaling_enable = (uint8_t)scaling_enable; + cmd.cacp_set_event.cacp_set_event_data.panel_mask = (1<ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_pause.header.type = DMUB_CMD__CACP; + cmd.cacp_pause.header.sub_type = DMUB_CMD__CACP_PAUSE; + + cmd.cacp_pause.cacp_pause_data.panel_mask = (1<ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_backlight.header.type = DMUB_CMD__CACP; + cmd.cacp_set_backlight.header.sub_type = DMUB_CMD__CACP_SET_BACKLIGHT; + cmd.cacp_set_backlight.cacp_set_backlight_data.frame_ramp = frame_ramp; + cmd.cacp_set_backlight.cacp_set_backlight_data.backlight_user_level = backlight_pwm_u16_16; + cmd.cacp_set_backlight.cacp_set_backlight_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_backlight.cacp_set_backlight_data.panel_mask = (0x01 << panel_inst); + cmd.cacp_set_backlight.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_backlight_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + +void dmub_cacp_enable_fractional_pwm(struct abm *abm, uint8_t panel_mask) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + uint32_t fractional_pwm = (dc->dc->config.disable_fractional_pwm == false) ? 1 : 0; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_pwm_frac.header.type = DMUB_CMD__CACP; + cmd.cacp_set_pwm_frac.header.sub_type = DMUB_CMD__CACP_SET_PWM_FRAC; + cmd.cacp_set_pwm_frac.cacp_set_pwm_frac_data.fractional_pwm = fractional_pwm; + cmd.cacp_set_pwm_frac.cacp_set_pwm_frac_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_pwm_frac.cacp_set_pwm_frac_data.panel_mask = panel_mask; + cmd.cacp_set_pwm_frac.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_pwm_frac_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); +} + +bool dmub_cacp_get_histogram( + struct dc_context *dc, + unsigned int panel_inst, + unsigned int *histogram, + enum dmub_abm_histogram_type histogram_type, + unsigned int size) +{ + union dmub_rb_cmd cmd; + + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_get_histogram.header.type = DMUB_CMD__CACP; + cmd.cacp_get_histogram.header.sub_type = DMUB_CMD__CACP_GET_HISTOGRAM; + + cmd.cacp_get_histogram.dest.quad_part = dc->dmub_srv->dmub->scratch_mem_fb.gpu_addr; + cmd.cacp_get_histogram.bytes = (uint16_t)size; + cmd.cacp_get_histogram.panel_inst = (uint8_t)panel_inst; + cmd.cacp_get_histogram.histogram_type = histogram_type; + cmd.cacp_get_histogram.header.payload_bytes = sizeof(struct dmub_rb_cmd_cacp_get_histogram); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + memcpy((void *)histogram, dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, size); + + return true; +} + diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h new file mode 100644 index 000000000000..3b3b712c1242 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h @@ -0,0 +1,20 @@ +/* Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved. */ + +#include "dmub_cmd.h" + +#ifndef __DMUB_ABM_CACP_H__ +#define __DMUB_ABM_CACP_H__ + +void dmub_cacp_init(struct abm *abm, const char *src, unsigned int bytes, unsigned int panel_inst); +bool dmub_cacp_set_level(struct abm *abm, unsigned int cacp_level, unsigned char panel_mask); +bool dmub_cacp_set_pipe(struct abm *abm, unsigned int otg_inst, unsigned int pipe_option, + unsigned int panel_inst, unsigned int pwrseq_inst); +bool dmub_cacp_set_event(struct abm *abm, unsigned int full_screen, unsigned int trans_info, unsigned int hdr_mode, + unsigned int scaling_enable, unsigned int panel_inst); +bool dmub_cacp_set_pause(struct abm *abm, bool pause, unsigned int panel_inst, unsigned int otg_inst); +bool dmub_cacp_set_backlight_level(struct abm *abm, unsigned int backlight_pwm_u16_16, unsigned int frame_ramp, + unsigned int panel_inst); +void dmub_cacp_enable_fractional_pwm(struct abm *abm, uint8_t panel_mask); +bool dmub_cacp_get_histogram(struct dc_context *dc, unsigned int panel_inst, unsigned int *histogram, + enum dmub_abm_histogram_type histogram_type, unsigned int size); +#endif diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c index 2f4d368bd3fd..f60e43ba6060 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c @@ -166,7 +166,7 @@ void dmub_abm_init_config(struct abm *abm, uint8_t panel_mask = 0x01 << inst; // TODO: Optimize by only reading back final 4 bytes - dmub_flush_buffer_mem(&dc->dmub_srv->dmub->scratch_mem_fb); + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); // Copy iramtable into cw7 memcpy(dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, (void *)src, bytes); @@ -227,7 +227,7 @@ bool dmub_abm_save_restore( unsigned int bytes = sizeof(struct abm_save_restore); // TODO: Optimize by only reading back final 4 bytes - dmub_flush_buffer_mem(&dc->dmub_srv->dmub->scratch_mem_fb); + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); // Copy iramtable into cw7 memcpy(dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, (void *)pData, bytes); diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_replay.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_replay.c index 9b50bf1d80fc..eda3a2f1402c 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dmub_replay.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_replay.c @@ -165,7 +165,7 @@ static bool dmub_replay_copy_settings(struct dmub_replay *dmub, // Misc copy_settings_data->line_time_in_ns = (uint16_t)replay_context->line_time_in_ns; copy_settings_data->panel_inst = (uint16_t)panel_inst; - copy_settings_data->debug.u32All = (uint16_t)link->replay_settings.config.debug_flags; + copy_settings_data->debug.u32All = link->replay_settings.config.debug_flags; copy_settings_data->pixel_deviation_per_line = link->dpcd_caps.pr_info.pixel_deviation_per_line; copy_settings_data->max_deviation_line = (uint16_t)link->dpcd_caps.pr_info.max_deviation_line; copy_settings_data->smu_optimizations_en = link->replay_settings.replay_smu_opt_enable; diff --git a/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c b/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c index 4b36c01a9e7a..00e82d627d91 100644 --- a/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c +++ b/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c @@ -1189,8 +1189,9 @@ void dcn10_link_encoder_dp_set_phy_pattern( set_dp_phy_pattern_prbs7(enc10); break; case DP_TEST_PATTERN_80BIT_CUSTOM: - set_dp_phy_pattern_80bit_custom( - enc10, param->custom_pattern); + if (param) + set_dp_phy_pattern_80bit_custom( + enc10, param->custom_pattern); break; case DP_TEST_PATTERN_CP2520_1: set_dp_phy_pattern_hbr2_compliance_cp2520_2(enc10, 1); @@ -1202,8 +1203,9 @@ void dcn10_link_encoder_dp_set_phy_pattern( set_dp_phy_pattern_hbr2_compliance_cp2520_2(enc10, 3); break; case DP_TEST_PATTERN_VIDEO_MODE: { - set_dp_phy_pattern_passthrough_mode( - enc10, param->dp_panel_mode); + if (param) + set_dp_phy_pattern_passthrough_mode( + enc10, param->dp_panel_mode); break; } diff --git a/drivers/gpu/drm/amd/display/dc/dm_helpers.h b/drivers/gpu/drm/amd/display/dc/dm_helpers.h index 63704d21a0b5..f5252931ce35 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_helpers.h +++ b/drivers/gpu/drm/amd/display/dc/dm_helpers.h @@ -192,6 +192,17 @@ void dm_helpers_mccs_vcp_set( struct dc_link *link, struct dc_sink *sink); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +#define STATIC_IFN_KUNIT +#define INLINE_IFN_KUNIT inline +#define EXPORT_IF_KUNIT(symbol) EXPORT_SYMBOL(symbol) + +#else +#define STATIC_IFN_KUNIT static +#define INLINE_IFN_KUNIT +#define EXPORT_IF_KUNIT(symbol) +#endif + bool dm_helpers_dp_handle_test_pattern_request( struct dc_context *ctx, const struct dc_link *link, diff --git a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c index 260f58a97fc7..da2c92e5c02f 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +++ b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c @@ -630,17 +630,17 @@ enum frl_cap_chk_result dml1_frl_cap_chk(struct frl_cap_chk_params *params) { struct frl_cap_chk_intermediates inter; -#if defined (CONFIG_DRM_AMD_DC_FP) - if (params->compressed) - return dml1_frl_cap_chk_compressed(params, &inter); -#endif - return dml1_frl_cap_chk_inter(params, &inter); } enum frl_cap_chk_result dml1_frl_cap_chk_inter(struct frl_cap_chk_params *params, struct frl_cap_chk_intermediates *inter) { +#if defined (CONFIG_DRM_AMD_DC_FP) + if (params->compressed) + return dml1_frl_cap_chk_compressed(params, inter); +#endif + return dml1_frl_cap_chk_uncompressed(params, inter); } diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile index 44e00c2b7ac7..e9ce7813b0f5 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile @@ -55,8 +55,11 @@ subdir-ccflags-y += -I$(FULL_AMD_DISPLAY_PATH)/dc/dml2_0/dml21/ # Add FPU flags to all dml2 files by default, remove NO_FPU flags. # FPU flags step 1: Find all .c files in dal/dc/dml2_0 and it's subfolders -DML2_ABS_PATH := $(FULL_AMD_DISPLAY_PATH)/dc/dml2_0 -DML2_C_FILES := $(shell find $(DML2_ABS_PATH) -name '*.c' -type f) + +THIS_MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +DML2_ABS_PATH := $(patsubst %/,%,$(dir $(THIS_MAKEFILE_PATH))) + +DML2_C_FILES := $(shell find $(DML2_ABS_PATH) -name '*.c') # FPU flags step 2: Convert to .o and make paths relative to $(AMDDALPATH)/dc/dml2_0/ DML2_RELATIVE_O_FILES := $(patsubst $(DML2_ABS_PATH)/%,dc/dml2_0/%,$(patsubst %.c,%.o,$(DML2_C_FILES))) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c index c1a3e2496983..47164fa150d9 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c @@ -90,7 +90,7 @@ static unsigned int calc_max_hardware_v_total(const struct dc_stream_state *stre static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cfg *timing, struct dc_stream_state *stream, - struct pipe_ctx *pipe_ctx, + struct pipe_ctx *otg_master_pipe, struct dml2_context *dml_ctx) { const unsigned int min_v_front_porch = (stream->timing.flags.INTERLACE != 0) ? 2 : 1; @@ -99,24 +99,24 @@ static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cf uint64_t min_hardware_refresh_in_uhz; uint32_t pix_clk_100hz; - timing->h_active = stream->timing.h_addressable + stream->timing.h_border_left + stream->timing.h_border_right + pipe_ctx->dsc_padding_params.dsc_hactive_padding; + timing->h_active = stream->timing.h_addressable + stream->timing.h_border_left + stream->timing.h_border_right + otg_master_pipe->dsc_padding_params.dsc_hactive_padding; timing->v_active = stream->timing.v_addressable + stream->timing.v_border_bottom + stream->timing.v_border_top; timing->h_front_porch = stream->timing.h_front_porch; timing->v_front_porch = stream->timing.v_front_porch > min_v_front_porch ? stream->timing.v_front_porch : min_v_front_porch; timing->pixel_clock_khz = stream->timing.pix_clk_100hz / 10; - if (pipe_ctx->dsc_padding_params.dsc_hactive_padding != 0) - timing->pixel_clock_khz = pipe_ctx->dsc_padding_params.dsc_pix_clk_100hz / 10; + if (otg_master_pipe->dsc_padding_params.dsc_hactive_padding != 0) + timing->pixel_clock_khz = otg_master_pipe->dsc_padding_params.dsc_pix_clk_100hz / 10; if (stream->timing.timing_3d_format == TIMING_3D_FORMAT_HW_FRAME_PACKING) timing->pixel_clock_khz *= 2; - timing->h_total = stream->timing.h_total + pipe_ctx->dsc_padding_params.dsc_htotal_padding; + timing->h_total = stream->timing.h_total + otg_master_pipe->dsc_padding_params.dsc_htotal_padding; timing->v_total = stream->timing.v_total; timing->h_sync_width = stream->timing.h_sync_width; timing->interlaced = (stream->timing.flags.INTERLACE != 0); hblank_start = stream->timing.h_total - stream->timing.h_front_porch; - timing->h_blank_end = hblank_start - stream->timing.h_addressable - pipe_ctx->dsc_padding_params.dsc_hactive_padding + timing->h_blank_end = hblank_start - stream->timing.h_addressable - otg_master_pipe->dsc_padding_params.dsc_hactive_padding - stream->timing.h_border_left - stream->timing.h_border_right; if (hblank_start < stream->timing.h_addressable) @@ -135,8 +135,8 @@ static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cf /* limit min refresh rate to DC cap */ min_hardware_refresh_in_uhz = stream->timing.min_refresh_in_uhz; if (stream->ctx->dc->caps.max_v_total != 0) { - if (pipe_ctx->dsc_padding_params.dsc_hactive_padding != 0) { - pix_clk_100hz = pipe_ctx->dsc_padding_params.dsc_pix_clk_100hz; + if (otg_master_pipe->dsc_padding_params.dsc_hactive_padding != 0) { + pix_clk_100hz = otg_master_pipe->dsc_padding_params.dsc_pix_clk_100hz; } else { pix_clk_100hz = stream->timing.pix_clk_100hz; } @@ -197,7 +197,7 @@ static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cf } static void populate_dml21_output_config_from_stream_state(struct dml2_link_output_cfg *output, - struct dc_stream_state *stream, const struct pipe_ctx *pipe) + struct dc_stream_state *stream, const struct pipe_ctx *otg_master_pipe) { output->output_dp_lane_count = 4; @@ -205,7 +205,7 @@ static void populate_dml21_output_config_from_stream_state(struct dml2_link_outp case SIGNAL_TYPE_DISPLAY_PORT_MST: case SIGNAL_TYPE_DISPLAY_PORT: output->output_encoder = dml2_dp; - if (check_dp2p0_output_encoder(pipe)) + if (check_dp2p0_output_encoder(otg_master_pipe)) output->output_encoder = dml2_dp2p0; break; case SIGNAL_TYPE_EDP: @@ -267,6 +267,61 @@ static void populate_dml21_output_config_from_stream_state(struct dml2_link_outp // output->validate_output } +static void populate_dml21_writeback_config_from_stream_state(struct dml2_writeback_cfg *writeback, + const struct dc_stream_state *stream) +{ + if (stream->num_wb_info > 0) { + writeback->active_writebacks_per_stream = stream->num_wb_info <= DML2_MAX_WRITEBACK ? + stream->num_wb_info : DML2_MAX_WRITEBACK; + + ASSERT(stream->num_wb_info <= DML2_MAX_WRITEBACK); + + for (unsigned int wb_index = 0; wb_index < stream->num_wb_info; wb_index++) { + const struct dc_writeback_info *dc_wb_info = &stream->writeback_info[wb_index]; + struct dml2_writeback_info *wb_info = &writeback->writeback_stream[wb_index]; + + switch (dc_wb_info->dwb_params.cnv_params.fc_out_format) { + case DWB_OUT_FORMAT_64BPP_ARGB: + case DWB_OUT_FORMAT_64BPP_RGBA: + wb_info->pixel_format = dml2_444_64; + break; + case DWB_OUT_FORMAT_32BPP_ARGB: + case DWB_OUT_FORMAT_32BPP_RGBA: + default: + wb_info->pixel_format = dml2_444_32; + break; + } + + wb_info->input_width = dc_wb_info->dwb_params.cnv_params.crop_en ? + dc_wb_info->dwb_params.cnv_params.crop_width : + dc_wb_info->dwb_params.cnv_params.src_width; + wb_info->input_height = dc_wb_info->dwb_params.cnv_params.crop_en ? + dc_wb_info->dwb_params.cnv_params.crop_height : + dc_wb_info->dwb_params.cnv_params.src_height; + wb_info->output_width = dc_wb_info->dwb_params.dest_width; + wb_info->output_height = dc_wb_info->dwb_params.dest_height; + wb_info->v_taps = dc_wb_info->dwb_params.scaler_taps.v_taps > 0 ? + dc_wb_info->dwb_params.scaler_taps.v_taps : 1; + wb_info->h_taps = dc_wb_info->dwb_params.scaler_taps.h_taps > 0 ? + dc_wb_info->dwb_params.scaler_taps.h_taps : 1; + wb_info->v_taps_chroma = dc_wb_info->dwb_params.scaler_taps.v_taps_c > 0 ? + dc_wb_info->dwb_params.scaler_taps.v_taps_c : 1; + wb_info->h_taps_chroma = dc_wb_info->dwb_params.scaler_taps.h_taps_c > 0 ? + dc_wb_info->dwb_params.scaler_taps.h_taps_c : 1; + wb_info->h_ratio = dc_wb_info->dwb_params.cnv_params.crop_en ? + (double)dc_wb_info->dwb_params.cnv_params.crop_width / + (double)dc_wb_info->dwb_params.dest_width : + (double)dc_wb_info->dwb_params.cnv_params.src_width / + (double)dc_wb_info->dwb_params.dest_width; + wb_info->v_ratio = dc_wb_info->dwb_params.cnv_params.crop_en ? + (double)dc_wb_info->dwb_params.cnv_params.crop_height / + (double)dc_wb_info->dwb_params.dest_height : + (double)dc_wb_info->dwb_params.cnv_params.src_height / + (double)dc_wb_info->dwb_params.dest_height; + } + } +} + static void populate_dml21_stream_overrides_from_stream_state( struct dml2_stream_parameters *stream_desc, struct dc_stream_state *stream, @@ -392,8 +447,8 @@ static void populate_dml21_dummy_surface_cfg(struct dml2_surface_cfg *surface, c surface->plane0.pitch = ((surface->plane0.width + 127) / 128) * 128; surface->plane1.pitch = 0; surface->dcc.enable = false; - surface->dcc.informative.dcc_rate_plane0 = 1.0; - surface->dcc.informative.dcc_rate_plane1 = 1.0; + surface->dcc.informative.dcc_rate_plane0 = 2.0; + surface->dcc.informative.dcc_rate_plane1 = 2.0; surface->dcc.informative.fraction_of_zero_size_request_plane0 = 0; surface->dcc.informative.fraction_of_zero_size_request_plane1 = 0; surface->tiling = dml2_sw_64kb_2d; @@ -462,8 +517,8 @@ static void populate_dml21_surface_config_from_plane_state( surface->plane1.height = plane_state->plane_size.chroma_size.height; surface->plane1.width = plane_state->plane_size.chroma_size.width; surface->dcc.enable = plane_state->dcc.enable; - surface->dcc.informative.dcc_rate_plane0 = 1.0; - surface->dcc.informative.dcc_rate_plane1 = 1.0; + surface->dcc.informative.dcc_rate_plane0 = 2.0; + surface->dcc.informative.dcc_rate_plane1 = 2.0; surface->dcc.informative.fraction_of_zero_size_request_plane0 = plane_state->dcc.independent_64b_blks; surface->dcc.informative.fraction_of_zero_size_request_plane1 = plane_state->dcc.independent_64b_blks_c; surface->dcc.plane0.pitch = plane_state->dcc.meta_pitch; @@ -795,6 +850,7 @@ bool dml21_map_dc_state_into_dml_display_cfg(const struct dc *in_dc, struct dc_s int disp_cfg_stream_location, disp_cfg_plane_location; struct dml2_display_cfg *dml_dispcfg = &dml_ctx->v21.display_config; unsigned int plane_count = 0; + struct pipe_ctx *otg_master_pipe; memset(&dml_ctx->v21.dml_to_dc_pipe_mapping, 0, sizeof(struct dml2_dml_to_dc_pipe_mapping)); @@ -819,9 +875,13 @@ bool dml21_map_dc_state_into_dml_display_cfg(const struct dc *in_dc, struct dc_s if (disp_cfg_stream_location < 0) disp_cfg_stream_location = dml_dispcfg->num_streams++; + otg_master_pipe = dml_ctx->config.callbacks.get_otg_master_for_stream(&context->res_ctx, context->streams[stream_index]); + ASSERT(otg_master_pipe); + ASSERT(disp_cfg_stream_location >= 0 && disp_cfg_stream_location < __DML2_WRAPPER_MAX_STREAMS_PLANES__); - populate_dml21_timing_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].timing, context->streams[stream_index], &context->res_ctx.pipe_ctx[stream_index], dml_ctx); - populate_dml21_output_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].output, context->streams[stream_index], &context->res_ctx.pipe_ctx[stream_index]); + populate_dml21_timing_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].timing, context->streams[stream_index], otg_master_pipe, dml_ctx); + populate_dml21_output_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].output, context->streams[stream_index], otg_master_pipe); + populate_dml21_writeback_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].writeback, context->streams[stream_index]); populate_dml21_stream_overrides_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location], context->streams[stream_index], &context->stream_status[stream_index]); dml_dispcfg->stream_descriptors[disp_cfg_stream_location].overrides.hw.twait_budgeting.fclk_pstate = dml2_twait_budgeting_setting_if_needed; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c index 835fece1d46a..012dff4cf3fa 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c @@ -359,14 +359,13 @@ void dml21_handle_phantom_streams_planes(const struct dc *dc, struct dc_state *c main_plane = main_stream_status->plane_states[dc_plane_index]; /* create phantom planes for subvp enabled plane */ - dml21_add_phantom_plane(dml_ctx, - dc, - context, - phantom_stream, - main_plane, - &dml_ctx->v21.mode_programming.programming->plane_programming[dml_plane_index]); - - phantoms_added = true; + if (dml21_add_phantom_plane(dml_ctx, + dc, + context, + phantom_stream, + main_plane, + &dml_ctx->v21.mode_programming.programming->plane_programming[dml_plane_index])) + phantoms_added = true; } } } @@ -532,3 +531,20 @@ bool dml21_is_plane1_enabled(enum dml2_source_format_class source_format) { return source_format >= dml2_420_8 && source_format <= dml2_rgbe_alpha; } + +void dml21_program_dc_mcif_arb_params(struct dml2_context *dml_ctx, + struct dc_state *context, + struct dml2_per_stream_programming *stream_prog, + unsigned int wb_index, + unsigned int dwb_inst) +{ + /* DC struct contains global reg for every WB instance */ + memcpy(&context->bw_ctx.bw.dcn.bw_writeback.mcif_wb_arb[dwb_inst].dcn4x.global_regs, + &dml_ctx->v21.mode_programming.programming->mcif_global_regs, + sizeof(struct dml2_mcif_global_register_set)); + + /* copy per-DWB pipe registers */ + memcpy(&context->bw_ctx.bw.dcn.bw_writeback.mcif_wb_arb[dwb_inst].dcn4x.inst_regs, + stream_prog->mcif_regs[wb_index], + sizeof(struct dml2_mcif_per_pipe_register_set)); +} diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.h index bff945a4ab3a..7312c33143c6 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.h @@ -48,4 +48,9 @@ void dml21_build_fams2_programming(const struct dc *dc, struct dc_state *context, struct dml2_context *dml_ctx); bool dml21_is_plane1_enabled(enum dml2_source_format_class source_format); +void dml21_program_dc_mcif_arb_params(struct dml2_context *dml_ctx, + struct dc_state *context, + struct dml2_per_stream_programming *stream_prog, + unsigned int wb_index, + unsigned int dwb_inst); #endif diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c index 11fc0b1cd152..aba7dd65c539 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c @@ -45,7 +45,22 @@ void dml21_init(const struct dc *in_dc, struct dml2_context *dml_ctx, const stru void dml21_reinit(const struct dc *in_dc, struct dml2_context *dml_ctx, const struct dml2_configuration_options *config) { - dml21_init(in_dc, dml_ctx, config); + struct dml2_instance *dml2 = dml_ctx->v21.dml_init.dml2_instance; + + dml21_populate_configuration_options(in_dc, dml_ctx, config); + + dml21_populate_dml_init_params(&dml_ctx->v21.dml_init, &dml_ctx->config, in_dc); + + // Skip full re-initialization if soc_bb, ip_caps and pmo_options are unchanged + if (memcmp(&dml2->soc_bbox, &dml_ctx->v21.dml_init.soc_bb, + sizeof(struct dml2_soc_bb)) == 0 && + memcmp(&dml2->ip_caps, &dml_ctx->v21.dml_init.ip_caps, + sizeof(struct dml2_ip_capabilities)) == 0 && + memcmp(&dml2->pmo_options, &dml_ctx->v21.dml_init.options.pmo_options, + sizeof(struct dml2_pmo_options)) == 0) + return; + + dml2_initialize_instance(&dml_ctx->v21.dml_init); } static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_state *context, struct resource_context *out_new_hw_state, @@ -60,6 +75,7 @@ static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_sta struct pipe_ctx *dc_phantom_pipes[__DML2_WRAPPER_MAX_STREAMS_PLANES__] = {0}; int num_pipes; unsigned int dml_phantom_prog_idx; + unsigned int stream_wb_idx; context->bw_ctx.bw.dcn.clk.dppclk_khz = 0; @@ -102,6 +118,15 @@ static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_sta } } + /* program WB */ + for (stream_wb_idx = 0; stream_wb_idx < stream_prog->stream_descriptor->writeback.active_writebacks_per_stream; stream_wb_idx++) { + dml21_program_dc_mcif_arb_params(in_ctx, + context, + stream_prog, + stream_wb_idx, + dc_main_pipes[0]->stream->writeback_info[stream_wb_idx].dwb_pipe_inst); + } + /* copy per plane mcache allocation */ memcpy(&context->bw_ctx.bw.dcn.mcache_allocations[dml_prog_idx], &pln_prog->mcache_allocation, sizeof(struct dml2_mcache_surface_allocation)); if (pln_prog->phantom_plane.valid) { @@ -130,6 +155,11 @@ static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_sta context->bw_ctx.bw.dcn.clk.max_supported_dppclk_khz = in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.clk_values_khz[0]; } + if (dc->config.forced_clocks || dc->debug.max_disp_clk) { + context->bw_ctx.bw.dcn.clk.bw_dispclk_khz = context->bw_ctx.bw.dcn.clk.max_supported_dispclk_khz; + context->bw_ctx.bw.dcn.clk.bw_dppclk_khz = context->bw_ctx.bw.dcn.clk.max_supported_dppclk_khz; + } + /* get global mall allocation */ if (dc->res_pool->funcs->calculate_mall_ways_from_bytes) { context->bw_ctx.bw.dcn.clk.num_ways = dc->res_pool->funcs->calculate_mall_ways_from_bytes(dc, context->bw_ctx.bw.dcn.mall_subvp_size_bytes); diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42_soc_bb.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42_soc_bb.h index 040d89f6de35..51cae3efe310 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42_soc_bb.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42_soc_bb.h @@ -205,7 +205,6 @@ static const struct dml2_soc_bb dml2_socbb_dcn42 = { .xtalclk_mhz = 24, .pcie_refclk_mhz = 100, .dchub_refclk_mhz = 50, - .mall_allocated_for_dcn_mbytes = 0, .max_outstanding_reqs = 256, .fabric_datapath_to_dcn_data_return_bytes = 32, .return_bus_width_bytes = 64, @@ -216,11 +215,6 @@ static const struct dml2_soc_bb dml2_socbb_dcn42 = { .phy_downspread_percent = 0.38, .dcn_downspread_percent = 0.38, .dispclk_dppclk_vco_speed_mhz = 3000, - .do_urgent_latency_adjustment = 0, - .mem_word_bytes = 32, - .num_dcc_mcaches = 8, - .mcache_size_bytes = 2048, - .mcache_line_size_bytes = 32, .max_fclk_for_uclk_dpm_khz = 2200 * 1000, }; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h index 60ef56419846..5d844ff4b671 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h @@ -210,7 +210,7 @@ static const struct dml2_soc_bb dml2_socbb_dcn42b = { .fabric_datapath_to_dcn_data_return_bytes = 32, .return_bus_width_bytes = 64, .hostvm_min_page_size_kbytes = 4, - .gpuvm_min_page_size_kbytes = 256, + .gpuvm_min_page_size_kbytes = 4, .gpuvm_max_page_table_levels = 1, .hostvm_max_non_cached_page_table_levels = 2, .phy_downspread_percent = 0.38, @@ -236,7 +236,7 @@ static const struct dml2_ip_capabilities dml2_dcn42b_max_ip_caps = { .config_return_buffer_segment_size_in_kbytes = 64, .meta_fifo_size_in_kentries = 32, .compressed_buffer_segment_size_in_kbytes = 64, - .cursor_buffer_size = 24, + .cursor_buffer_size = 42, .max_flip_time_us = 110, .max_flip_time_lines = 50, .hostvm_mode = 0, diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h index bf57df42d1d9..5a5f7b92c324 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h @@ -46,6 +46,9 @@ struct dml2_display_dlg_regs { uint32_t dst_y_delta_drq_limit; uint32_t refcyc_per_vm_dmdata; uint32_t dmdata_dl_delta; + uint32_t dst_y_svp_drq_limit; + uint32_t force_prefetch_to_vblank; + uint32_t force_cursor_to_disp_pref; // MRQ uint32_t refcyc_per_meta_chunk_vblank_l; @@ -153,6 +156,13 @@ struct dml2_dchub_per_pipe_register_set { uint32_t det_size; }; +struct dml2_mcif_per_pipe_register_set { + unsigned int time_per_pixel; // U6.6 format + unsigned int arbitration_slice; + unsigned int slice_lines; + unsigned int max_scaled_time_ns; +}; + struct dml2_dchub_watermark_regs { /* watermarks */ uint32_t urgent; @@ -164,8 +174,15 @@ struct dml2_dchub_watermark_regs { uint32_t sr_exit_low_power; uint32_t uclk_pstate; uint32_t fclk_pstate; + union { uint32_t temp_read_or_ppt; + uint32_t temp_read; + }; + uint32_t ppt; + union { uint32_t usr; + uint32_t buffer_fullness; + }; /* qos */ uint32_t refcyc_per_trip_to_mem; uint32_t refcyc_per_meta_trip_to_mem; @@ -188,4 +205,17 @@ struct dml2_dchub_global_register_set { unsigned int num_watermark_sets; }; +struct dml2_mcif_watermark_regs { + /* watermarks */ + uint32_t urgent; /* (CLI) */ + uint32_t uclk_pstate; + uint32_t fclk_pstate; + uint32_t temp_read_or_ppt; +}; + +struct dml2_mcif_global_register_set { + struct dml2_mcif_watermark_regs wm_regs[DML2_DCHUB_WATERMARK_SET_NUM]; + unsigned int num_watermark_sets; +}; + #endif diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h index 8d7960a340c2..f00ca2983ff5 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h @@ -279,6 +279,8 @@ struct dml2_per_stream_programming { enum dml2_pstate_method uclk_pstate_method; + struct dml2_mcif_per_pipe_register_set *mcif_regs[DML2_MAX_WRITEBACK]; + struct { bool enabled; struct dml2_stream_parameters descriptor; @@ -436,12 +438,14 @@ struct dml2_display_cfg_programming { } z8_stutter; struct dml2_dchub_global_register_set global_regs; + struct dml2_mcif_global_register_set mcif_global_regs; struct dml2_per_plane_programming plane_programming[DML2_MAX_PLANES]; struct dml2_per_stream_programming stream_programming[DML2_MAX_PLANES]; // Don't access this structure directly, access it through plane_programming.pipe_regs struct dml2_dchub_per_pipe_register_set pipe_regs[DML2_MAX_PLANES]; + struct dml2_mcif_per_pipe_register_set mcif_regs[DML2_MAX_WRITEBACK]; struct { struct { @@ -457,6 +461,7 @@ struct dml2_display_cfg_programming { double fclk_pstate_change_us; double usr_retraining_us; double temp_read_or_ppt_watermark_us; + double writeback_temp_read_or_ppt_watermark_us; } watermarks; struct { diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c index c983869e0fa3..6ea36aedcf20 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c @@ -368,9 +368,10 @@ static void expand_implict_subvp(const struct display_configuation_with_meta *di static void pack_mode_programming_params_with_implicit_subvp(struct dml2_core_instance *core, const struct display_configuation_with_meta *display_cfg, const struct dml2_display_cfg *svp_expanded_display_cfg, struct dml2_display_cfg_programming *programming, struct dml2_core_scratch *scratch) { - unsigned int stream_index, plane_index, pipe_offset, stream_already_populated_mask, main_plane_index, mcache_index; + unsigned int stream_index, plane_index, pipe_offset, stream_already_populated_mask, main_plane_index, mcache_index, dwb_index; unsigned int total_main_mcaches_required = 0; int total_pipe_regs_copied = 0; + int total_dwb_regs_copied = 0; int dml_internal_pipe_index = 0; const struct dml2_plane_parameters *main_plane; const struct dml2_plane_parameters *phantom_plane; @@ -386,6 +387,8 @@ static void pack_mode_programming_params_with_implicit_subvp(struct dml2_core_in // display config dml2_core_calcs_get_watermarks(&display_cfg->display_config, &core->clean_me_up.mode_lib, &programming->global_regs.wm_regs[0]); + dml2_core_calcs_get_mcif_arb_params(&core->clean_me_up.mode_lib, &programming->mcif_global_regs); + // Check if FAMS2 is required if (display_cfg->stage3.performed && display_cfg->stage3.success) { programming->fams2_required = display_cfg->stage3.fams2_required; @@ -470,6 +473,19 @@ static void pack_mode_programming_params_with_implicit_subvp(struct dml2_core_in programming->stream_programming[main_plane->stream_index].uclk_pstate_method, plane_index); + /* populate DWB */ + for (dwb_index = 0; dwb_index < svp_expanded_display_cfg->stream_descriptors[main_plane->stream_index].writeback.active_writebacks_per_stream; dwb_index++) { + programming->stream_programming[main_plane->stream_index].mcif_regs[dwb_index] = &programming->mcif_regs[total_dwb_regs_copied]; + memset(programming->stream_programming[main_plane->stream_index].mcif_regs[dwb_index], 0, sizeof(struct dml2_mcif_per_pipe_register_set)); + total_dwb_regs_copied++; + + dml2_core_calcs_get_per_dwb_params(svp_expanded_display_cfg, + &core->clean_me_up.mode_lib, + programming->stream_programming[main_plane->stream_index].mcif_regs[dwb_index], + main_plane->stream_index, + dwb_index); + } + stream_already_populated_mask |= (0x1 << main_plane->stream_index); } dml_internal_pipe_index++; @@ -538,6 +554,10 @@ bool core_dcn4_mode_support(struct dml2_core_mode_support_in_out *in_out) l->mode_support_ex_params.min_clk_index = in_out->min_clk_index; l->mode_support_ex_params.out_evaluation_info = &in_out->mode_support_result.cfg_support_info.clean_me_up.support_info; + l->mode_support_ex_params.uclk_pstate_switch_modes = + in_out->display_cfg->stage3.performed ? + in_out->display_cfg->stage3.pstate_switch_modes : NULL; + result = dml2_core_calcs_mode_support_ex(&l->mode_support_ex_params); in_out->mode_support_result.cfg_support_info.is_supported = result; @@ -661,10 +681,12 @@ bool core_dcn4_mode_programming(struct dml2_core_mode_programming_in_out *in_out unsigned int pipe_offset; int dml_internal_pipe_index; int total_pipe_regs_copied = 0; + int total_dwb_regs_copied = 0; int stream_already_populated_mask = 0; int main_stream_index; unsigned int plane_index; + unsigned int dwb_index; expand_implict_subvp(in_out->display_cfg, &l->svp_expanded_display_cfg, &core->scratch); @@ -688,6 +710,8 @@ bool core_dcn4_mode_programming(struct dml2_core_mode_programming_in_out *in_out dml2_core_calcs_get_arb_params(&l->svp_expanded_display_cfg, &core->clean_me_up.mode_lib, &in_out->programming->global_regs.arb_regs); dml2_core_calcs_get_watermarks(&l->svp_expanded_display_cfg, &core->clean_me_up.mode_lib, &in_out->programming->global_regs.wm_regs[0]); + dml2_core_calcs_get_mcif_arb_params(&core->clean_me_up.mode_lib, &in_out->programming->mcif_global_regs); + dml_internal_pipe_index = 0; for (plane_index = 0; plane_index < in_out->programming->display_config.num_planes; plane_index++) { @@ -733,6 +757,18 @@ bool core_dcn4_mode_programming(struct dml2_core_mode_programming_in_out *in_out in_out->programming->stream_programming[main_stream_index].num_odms_required = in_out->cfg_support_info->stream_support_info[main_stream_index].odms_used; dml2_core_calcs_get_stream_programming(&core->clean_me_up.mode_lib, &in_out->programming->stream_programming[main_stream_index], dml_internal_pipe_index); + for (dwb_index = 0; dwb_index < l->svp_expanded_display_cfg.stream_descriptors[main_stream_index].writeback.active_writebacks_per_stream; dwb_index++) { + in_out->programming->stream_programming[main_stream_index].mcif_regs[dwb_index] = &in_out->programming->mcif_regs[total_dwb_regs_copied]; + memset(in_out->programming->stream_programming[main_stream_index].mcif_regs[dwb_index], 0, sizeof(struct dml2_mcif_per_pipe_register_set)); + total_dwb_regs_copied++; + + dml2_core_calcs_get_per_dwb_params(&l->svp_expanded_display_cfg, + &core->clean_me_up.mode_lib, + in_out->programming->stream_programming[main_stream_index].mcif_regs[dwb_index], + main_stream_index, + dwb_index); + } + stream_already_populated_mask |= (0x1 << main_stream_index); } dml_internal_pipe_index++; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c index 51a66e1be7a1..09452f3e49db 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c @@ -317,6 +317,7 @@ dml_get_var_func(meta_trip_memory_us, double, mode_lib->mp.MetaTripToMemory); dml_get_var_func(wm_fclk_change, double, mode_lib->mp.Watermark.FCLKChangeWatermark); dml_get_var_func(wm_usr_retraining, double, mode_lib->mp.Watermark.USRRetrainingWatermark); dml_get_var_func(wm_temp_read_or_ppt, double, mode_lib->mp.Watermark.temp_read_or_ppt_watermark_us); +dml_get_var_func(wm_writeback_temp_read_or_ppt, double, mode_lib->mp.Watermark.writeback_temp_read_or_ppt_watermark_us); dml_get_var_func(wm_dram_clock_change, double, mode_lib->mp.Watermark.DRAMClockChangeWatermark); dml_get_var_func(fraction_of_urgent_bandwidth, double, mode_lib->mp.FractionOfUrgentBandwidth); dml_get_var_func(fraction_of_urgent_bandwidth_imm_flip, double, mode_lib->mp.FractionOfUrgentBandwidthImmediateFlip); @@ -2452,7 +2453,7 @@ static void calculate_mcache_row_bytes( DML_ASSERT(*p->num_mcaches > 0); } -static void calculate_mcache_setting( +static bool calculate_mcache_setting( struct dml2_core_internal_scratch *scratch, struct dml2_core_calcs_calculate_mcache_setting_params *p) { @@ -2478,7 +2479,7 @@ static void calculate_mcache_setting( *p->lc_comb_mcache = 0; if (!p->dcc_enable) - return; + return true; l->is_dual_plane = dml_is_420(p->source_format) || p->source_format == dml2_rgbe_alpha; @@ -2515,7 +2516,14 @@ static void calculate_mcache_setting( l->l_p.mvmpg_per_mcache_lb = &l->mvmpg_per_mcache_lb_l; calculate_mcache_row_bytes(scratch, &l->l_p); - DML_ASSERT(*p->num_mcaches_l > 0); + if (*p->num_mcaches_l == 0 || + (p->surf_vert ? l->mvmpg_height_l : l->mvmpg_width_l) == 0) { + DML_LOG_VERBOSE("DML::%s: degenerate luma viewport (num_mcaches_l=%u mvmpg_%s_l=%u) — mode not supported\n", + __func__, *p->num_mcaches_l, + p->surf_vert ? "height" : "width", + p->surf_vert ? l->mvmpg_height_l : l->mvmpg_width_l); + return false; + } if (l->is_dual_plane) { l->c_p.num_chans = p->num_chans; @@ -2551,7 +2559,14 @@ static void calculate_mcache_setting( l->c_p.mvmpg_per_mcache_lb = &l->mvmpg_per_mcache_lb_c; calculate_mcache_row_bytes(scratch, &l->c_p); - DML_ASSERT(*p->num_mcaches_c > 0); + if (*p->num_mcaches_c == 0 || + (p->surf_vert ? l->mvmpg_height_c : l->mvmpg_width_c) == 0) { + DML_LOG_VERBOSE("DML::%s: degenerate chroma viewport (num_mcaches_c=%u mvmpg_%s_c=%u) — mode not supported\n", + __func__, *p->num_mcaches_c, + p->surf_vert ? "height" : "width", + p->surf_vert ? l->mvmpg_height_c : l->mvmpg_width_c); + return false; + } } // Sharing for iMALL access @@ -2661,6 +2676,7 @@ static void calculate_mcache_setting( *p->mcache_shift_granularity_l = l->mvmpg_access_width_l; *p->mcache_shift_granularity_c = l->mvmpg_access_width_c; + return true; } static void calculate_mall_bw_overhead_factor( @@ -5606,7 +5622,8 @@ static bool CalculatePrefetchSchedule(struct dml2_core_internal_scratch *scratch + 2 * (p->PixelPTEBytesPerRow * p->HostVMInefficiencyFactor + p->meta_row_bytes + tdlut_row_bytes) + *p->prefetch_sw_bytes) / (*p->Tpre_rounded - *p->Tno_bw); - s->Tsw_est1 = *p->prefetch_sw_bytes / s->prefetch_bw1; + /* due to rounding, VM can clamp to +1/4, Row can clamp to +2/4, giving SW less time */ + s->Tsw_est1 = *p->prefetch_sw_bytes / s->prefetch_bw1 - 3.0 * s->LineTime / 4.0; } else s->prefetch_bw1 = 0; @@ -5630,7 +5647,8 @@ static bool CalculatePrefetchSchedule(struct dml2_core_internal_scratch *scratch if (*p->Tpre_rounded - *p->Tno_bw - 2.0 * s->Tr0_trips_rounded > 0) { s->prefetch_bw2 = (vm_bytes * p->HostVMInefficiencyFactor + *p->prefetch_sw_bytes) / (*p->Tpre_rounded - *p->Tno_bw - 2.0 * s->Tr0_trips_rounded); - s->Tsw_est2 = *p->prefetch_sw_bytes / s->prefetch_bw2; + /* due to rounding, VM can clamp to +1/4 */ + s->Tsw_est2 = *p->prefetch_sw_bytes / s->prefetch_bw2 - 1.0 * s->LineTime / 4.0; } else s->prefetch_bw2 = 0; @@ -5644,7 +5662,8 @@ static bool CalculatePrefetchSchedule(struct dml2_core_internal_scratch *scratch if (*p->Tpre_rounded - s->Tvm_trips_rounded > 0) { s->prefetch_bw3 = (2 * (p->PixelPTEBytesPerRow * p->HostVMInefficiencyFactor + p->meta_row_bytes + tdlut_row_bytes) + *p->prefetch_sw_bytes) / (*p->Tpre_rounded - s->Tvm_trips_rounded); - s->Tsw_est3 = *p->prefetch_sw_bytes / s->prefetch_bw3; + /* due to rounding, Row can clamp to +2/4 */ + s->Tsw_est3 = *p->prefetch_sw_bytes / s->prefetch_bw3 - 2.0 * s->LineTime / 4.0; } else s->prefetch_bw3 = 0; @@ -6740,6 +6759,25 @@ static void CalculateFlipSchedule( #endif } +static double calculate_writeback_latency_hiding_us( + const struct dml2_display_cfg *display_cfg, + unsigned int writeback_buffer_size_bytes, + unsigned int stream_index, + unsigned int dwb_index) +{ + double line_time_us = (double)display_cfg->stream_descriptors[stream_index].timing.h_total / + (double)display_cfg->stream_descriptors[stream_index].timing.pixel_clock_khz / 1000.0; + + double writeback_latency_hiding_us = (double)writeback_buffer_size_bytes / + ((double)display_cfg->stream_descriptors[stream_index].writeback.writeback_stream[dwb_index].output_height * + (double)display_cfg->stream_descriptors[stream_index].writeback.writeback_stream[dwb_index].output_width / + ((double)display_cfg->stream_descriptors[stream_index].writeback.writeback_stream[dwb_index].input_height * + line_time_us) * 4.0); + + return display_cfg->stream_descriptors[stream_index].writeback.writeback_stream[dwb_index].pixel_format == dml2_444_64 ? + writeback_latency_hiding_us / 2 : writeback_latency_hiding_us; +} + static void CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport( struct dml2_core_internal_scratch *scratch, struct dml2_core_calcs_CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport_params *p) @@ -6904,13 +6942,10 @@ static void CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport( p->VActiveLatencyHidingUs[k] = s->ActiveClockChangeLatencyHiding; if (p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.active_writebacks_per_stream > 0) { - s->WritebackLatencyHiding = (double)p->WritebackInterfaceBufferSize * 1024.0 - / ((double)p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.writeback_stream[0].output_height - * (double)p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.writeback_stream[0].output_width - / ((double)p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.writeback_stream[0].input_height * (double)h_total / pixel_clock_mhz) * 4.0); - if (p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.writeback_stream[0].pixel_format == dml2_444_64) { - s->WritebackLatencyHiding = s->WritebackLatencyHiding / 2; - } + s->WritebackLatencyHiding = calculate_writeback_latency_hiding_us(p->display_cfg, + p->WritebackInterfaceBufferSize * 1024, + p->display_cfg->plane_descriptors[k].stream_index, + 0); s->WritebackDRAMClockChangeLatencyMargin = s->WritebackLatencyHiding - p->Watermark->WritebackDRAMClockChangeWatermark; s->WritebackFCLKChangeLatencyMargin = s->WritebackLatencyHiding - p->Watermark->WritebackFCLKChangeWatermark; @@ -8022,6 +8057,16 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out memset(&mode_lib->ms, 0, sizeof(struct dml2_core_internal_mode_support)); mode_lib->ms.num_active_planes = display_cfg->num_planes; + + for (k = 0; k < mode_lib->ms.num_active_planes; k++) { + if (in_out_params->uclk_pstate_switch_modes && + !dml_is_phantom_pipe(&display_cfg->plane_descriptors[k])) + mode_lib->ms.uclk_pstate_switch_modes[k] = + in_out_params->uclk_pstate_switch_modes[k]; + else + mode_lib->ms.uclk_pstate_switch_modes[k] = dml2_pstate_method_na; + } + get_stream_output_bpp(s->OutputBpp, display_cfg); mode_lib->ms.state_idx = in_out_params->min_clk_index; @@ -8692,6 +8737,7 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out s->TotalNumberOfActiveWriteback = 0; memset(s->stream_visited, 0, DML2_MAX_PLANES * sizeof(bool)); + mode_lib->ms.support.EnoughWritebackUnits = true; for (k = 0; k < mode_lib->ms.num_active_planes; ++k) { if (!dml_is_phantom_pipe(&display_cfg->plane_descriptors[k])) { if (!s->stream_visited[display_cfg->plane_descriptors[k].stream_index]) { @@ -8700,6 +8746,10 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out if (display_cfg->stream_descriptors[display_cfg->plane_descriptors[k].stream_index].writeback.active_writebacks_per_stream > 0) s->TotalNumberOfActiveWriteback = s->TotalNumberOfActiveWriteback + 1; + /* >1 writeback per stream is currently not supported */ + if (display_cfg->stream_descriptors[display_cfg->plane_descriptors[k].stream_index].writeback.active_writebacks_per_stream > 1) + mode_lib->ms.support.EnoughWritebackUnits = false; + s->TotalNumberOfActiveOTG = s->TotalNumberOfActiveOTG + 1; if (display_cfg->stream_descriptors[display_cfg->plane_descriptors[k].stream_index].output.output_encoder == dml2_hdmifrl) s->TotalNumberOfActiveHDMIFRL = s->TotalNumberOfActiveHDMIFRL + 1; @@ -8715,10 +8765,10 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out } /* Writeback Mode Support Check */ - mode_lib->ms.support.EnoughWritebackUnits = 1; if (s->TotalNumberOfActiveWriteback > (unsigned int)mode_lib->ip.max_num_wb) { mode_lib->ms.support.EnoughWritebackUnits = false; } + mode_lib->ms.support.NumberOfOTGSupport = (s->TotalNumberOfActiveOTG <= (unsigned int)mode_lib->ip.max_num_otg); mode_lib->ms.support.NumberOfHDMIFRLSupport = (s->TotalNumberOfActiveHDMIFRL <= (unsigned int)mode_lib->ip.max_num_hdmi_frl_outputs); mode_lib->ms.support.NumberOfDP2p0Support = (s->TotalNumberOfActiveDP2p0 <= (unsigned int)mode_lib->ip.max_num_dp2p0_streams && s->TotalNumberOfActiveDP2p0Outputs <= (unsigned int)mode_lib->ip.max_num_dp2p0_outputs); @@ -9468,7 +9518,10 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out calculate_mcache_setting_params->mall_comb_mcache_c = &mode_lib->ms.mall_comb_mcache_c[k]; calculate_mcache_setting_params->lc_comb_mcache = &mode_lib->ms.lc_comb_mcache[k]; - calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params); + if (!calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params)) { + mode_lib->ms.support.ModeSupport = false; + return false; + } } calculate_mall_bw_overhead_factor( @@ -9597,6 +9650,21 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out DML_LOG_VERBOSE("DML::%s: ROBSupport = %u\n", __func__, mode_lib->ms.support.ROBSupport); #endif + mode_lib->ms.support.global_dram_clock_change_support_required = false; + + if (!display_cfg->overrides.all_streams_blanked) { + for (k = 0; k < mode_lib->ms.num_active_planes; k++) { + if (mode_lib->ms.uclk_pstate_switch_modes[k] != dml2_pstate_method_vactive && + mode_lib->ms.uclk_pstate_switch_modes[k] != dml2_pstate_method_fw_vactive_drr) + continue; + + mode_lib->ms.support.global_dram_clock_change_support_required = true; + + if (mode_lib->ms.VActiveLatencyHidingMargin[k] < 0) + mode_lib->ms.support.global_dram_clock_change_supported = false; + } + } + /*Mode Support, Voltage State and SOC Configuration*/ { if (mode_lib->ms.support.ScaleRatioAndTapsSupport @@ -9643,6 +9711,8 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out && mode_lib->ms.support.DCCMetaBufferSizeNotExceeded && !mode_lib->ms.support.ExceededMALLSize && mode_lib->ms.support.g6_temp_read_support + && (mode_lib->ms.support.global_dram_clock_change_supported + || !mode_lib->ms.support.global_dram_clock_change_support_required) && ((!display_cfg->hostvm_enable && !s->ImmediateFlipRequired) || mode_lib->ms.support.ImmediateFlipSupport)) { DML_LOG_VERBOSE("DML::%s: mode is supported\n", __func__); mode_lib->ms.support.ModeSupport = true; @@ -10947,7 +11017,8 @@ static bool dml_core_mode_programming(struct dml2_core_calcs_mode_programming_ex calculate_mcache_setting_params->mall_comb_mcache_l = &mode_lib->mp.mall_comb_mcache_l[k]; calculate_mcache_setting_params->mall_comb_mcache_c = &mode_lib->mp.mall_comb_mcache_c[k]; calculate_mcache_setting_params->lc_comb_mcache = &mode_lib->mp.lc_comb_mcache[k]; - calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params); + if (!calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params)) + return false; } calculate_mall_bw_overhead_factor( @@ -12830,6 +12901,14 @@ void dml2_core_calcs_get_arb_params(const struct dml2_display_cfg *display_cfg, rq_dlg_get_arb_params(display_cfg, mode_lib, out); } +void dml2_core_calcs_get_mcif_arb_params(const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_mcif_global_register_set *out) +{ + out->wm_regs[0].fclk_pstate = (unsigned int)(mode_lib->mp.Watermark.WritebackFCLKChangeWatermark * 1000.0); + out->wm_regs[0].uclk_pstate = (unsigned int)(mode_lib->mp.Watermark.WritebackDRAMClockChangeWatermark * 1000.0); + out->wm_regs[0].urgent = (unsigned int)(mode_lib->mp.Watermark.WritebackUrgentWatermark * 1000.0); + out->wm_regs[0].temp_read_or_ppt = (unsigned int)(mode_lib->mp.Watermark.writeback_temp_read_or_ppt_watermark_us * 1000.0); +} + void dml2_core_calcs_get_pipe_regs(const struct dml2_display_cfg *display_cfg, struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_dchub_per_pipe_register_set *out, int pipe_index) @@ -12853,6 +12932,29 @@ void dml2_core_calcs_get_stream_programming(const struct dml2_core_internal_disp dml2_core_calcs_get_global_sync_programming(mode_lib, &out->global_sync, pipe_index); } +void dml2_core_calcs_get_per_dwb_params(const struct dml2_display_cfg *display_cfg, + const struct dml2_core_internal_display_mode_lib *mode_lib, + struct dml2_mcif_per_pipe_register_set *out, + int stream_index, + int dwb_index) +{ + double writeback_latency_hiding_us = calculate_writeback_latency_hiding_us(display_cfg, + mode_lib->ip.writeback_interface_buffer_size_kbytes * 1024, + stream_index, + dwb_index); + + out->max_scaled_time_ns = (unsigned int)math_max2( + (writeback_latency_hiding_us - mode_lib->mp.Watermark.WritebackUrgentWatermark) * 1000.0, + 0.0); + + /* 1024ps units in U6.6 format */ + out->time_per_pixel = (unsigned int)((1000000.0 * math_pow(2, 6)) / + (double)display_cfg->stream_descriptors[stream_index].timing.pixel_clock_khz); + + out->slice_lines = 31; + out->arbitration_slice = 2; +} + void dml2_core_calcs_get_global_fams2_programming(const struct dml2_core_internal_display_mode_lib *mode_lib, const struct display_configuation_with_meta *display_cfg, struct dmub_cmd_fams2_global_config *fams2_global_config) @@ -13230,6 +13332,8 @@ void dml2_core_calcs_get_informative(const struct dml2_core_internal_display_mod out->informative.watermarks.fclk_pstate_change_us = dml_get_wm_fclk_change(mode_lib); out->informative.watermarks.usr_retraining_us = dml_get_wm_usr_retraining(mode_lib); out->informative.watermarks.temp_read_or_ppt_watermark_us = dml_get_wm_temp_read_or_ppt(mode_lib); + out->informative.watermarks.writeback_temp_read_or_ppt_watermark_us = dml_get_wm_writeback_temp_read_or_ppt(mode_lib); + out->informative.mall.total_surface_size_in_mall_bytes = 0; out->informative.dpp.total_num_dpps_required = 0; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.h index 27ef0e096b25..3249f6bcf7bc 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.h @@ -19,6 +19,7 @@ struct display_configuation_with_meta; unsigned int dml2_core_calcs_mode_support_ex(struct dml2_core_calcs_mode_support_ex *in_out_params); bool dml2_core_calcs_mode_programming_ex(struct dml2_core_calcs_mode_programming_ex *in_out_params); void dml2_core_calcs_get_watermarks(const struct dml2_display_cfg *display_cfg, const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_dchub_watermark_regs *out); +void dml2_core_calcs_get_mcif_arb_params(const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_mcif_global_register_set *out); void dml2_core_calcs_get_arb_params(const struct dml2_display_cfg *display_cfg, const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_display_arb_regs *out); void dml2_core_calcs_get_pipe_regs(const struct dml2_display_cfg *dml2_display_cfg, struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_dchub_per_pipe_register_set *out, int pipe_index); void dml2_core_calcs_get_stream_programming(const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_per_stream_programming *out, int pipe_index); @@ -30,6 +31,7 @@ void dml2_core_calcs_get_stream_support_info(const struct dml2_display_cfg *disp void dml2_core_calcs_get_mall_allocation(struct dml2_core_internal_display_mode_lib *mode_lib, unsigned int *out, int pipe_index); void dml2_core_calcs_get_stream_fams2_programming(const struct dml2_core_internal_display_mode_lib *mode_lib, const struct display_configuation_with_meta *display_cfg, union dmub_cmd_fams2_config *fams2_base_programming, union dmub_cmd_fams2_config *fams2_sub_programming, enum dml2_pstate_method pstate_method, int plane_index); void dml2_core_calcs_get_global_fams2_programming(const struct dml2_core_internal_display_mode_lib *mode_lib, const struct display_configuation_with_meta *display_cfg, struct dmub_cmd_fams2_global_config *fams2_global_config); +void dml2_core_calcs_get_per_dwb_params(const struct dml2_display_cfg *display_cfg, const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_mcif_per_pipe_register_set *out, int stream_index, int dwb_index); void dml2_core_calcs_get_dpte_row_height(unsigned int *dpte_row_height, struct dml2_core_internal_display_mode_lib *mode_lib, bool is_plane1, enum dml2_source_format_class SourcePixelFormat, enum dml2_swizzle_mode SurfaceTiling, enum dml2_rotation_angle ScanDirection, unsigned int pitch, unsigned int GPUVMMinPageSizeKBytes); void dml2_core_calcs_cursor_dlg_reg(struct dml2_cursor_dlg_regs *cursor_dlg_regs, const struct dml2_get_cursor_dlg_reg *p); diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h index e9f970794488..b8af72392176 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h @@ -212,6 +212,7 @@ struct dml2_core_internal_watermarks { double Z8StutterEnterPlusExitWatermark; double USRRetrainingWatermark; double temp_read_or_ppt_watermark_us; + double writeback_temp_read_or_ppt_watermark_us; }; struct dml2_core_internal_mode_support_info { @@ -1269,6 +1270,7 @@ struct dml2_core_calcs_CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport_local double FullDETBufferingTimeC; double WritebackDRAMClockChangeLatencyMargin; double WritebackFCLKChangeLatencyMargin; + double WritebackTempReadOrPptLatencyMargin; double WritebackLatencyHiding; unsigned int TotalActiveWriteback; @@ -2331,6 +2333,7 @@ struct dml2_core_calcs_mode_support_ex { int min_clk_index; //unsigned int in_state_index; struct dml2_core_internal_mode_support_info *out_evaluation_info; + const enum dml2_pstate_method *uclk_pstate_switch_modes; }; struct core_display_cfg_support_info; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_dpmm/dml2_dpmm_dcn4.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_dpmm/dml2_dpmm_dcn4.c index 5ffe211a6643..c60a1faf2d5d 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_dpmm/dml2_dpmm_dcn4.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_dpmm/dml2_dpmm_dcn4.c @@ -779,6 +779,25 @@ bool dpmm_dcn4_map_mode_to_soc_dpm(struct dml2_dpmm_map_mode_to_soc_dpm_params_i return result; } +static void dpmm_dcn4_map_mcif_watermarks(struct dml2_dpmm_map_watermarks_params_in_out *in_out) +{ + const struct dml2_core_internal_display_mode_lib *mode_lib = &in_out->core->clean_me_up.mode_lib; + struct dml2_mcif_global_register_set *mcif_regs = &in_out->programming->mcif_global_regs; + + /* MCIF */ + mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A].urgent = (int unsigned)(mode_lib->mp.Watermark.WritebackUrgentWatermark * 1000.0); + mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A].uclk_pstate = (int unsigned)(mode_lib->mp.Watermark.WritebackDRAMClockChangeWatermark * 1000.0); + mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A].fclk_pstate = (int unsigned)(mode_lib->mp.Watermark.WritebackFCLKChangeWatermark * 1000.0); + mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A].temp_read_or_ppt = (int unsigned)(mode_lib->mp.Watermark.writeback_temp_read_or_ppt_watermark_us * 1000.0); + + /* replicate sets A through D */ + memcpy(&mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_B], &mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A], sizeof(mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A])); + memcpy(&mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_C], &mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A], sizeof(mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A])); + memcpy(&mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_D], &mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A], sizeof(mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A])); + + mcif_regs->num_watermark_sets = 4; +} + bool dpmm_dcn4_map_watermarks(struct dml2_dpmm_map_watermarks_params_in_out *in_out) { const struct dml2_display_cfg *display_cfg = &in_out->display_cfg->display_config; @@ -821,6 +840,8 @@ bool dpmm_dcn4_map_watermarks(struct dml2_dpmm_map_watermarks_params_in_out *in_ dchubbub_regs->num_watermark_sets = 2; + dpmm_dcn4_map_mcif_watermarks(in_out); + return true; } bool dpmm_dcn42_map_watermarks(struct dml2_dpmm_map_watermarks_params_in_out *in_out) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c index 790aef95caa6..57a6a7d5b740 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c @@ -13,8 +13,6 @@ * configurations, ensuring p-state watermark support in the blank period only. */ -static const double MIN_VACTIVE_MARGIN_PCT = 0.25; // We need more than non-zero margin because DET buffer granularity can alter vactive latency hiding - static const struct dml2_pmo_pstate_strategy dcn42_strategy_list_1_display[] = { // VBlank only { @@ -179,7 +177,7 @@ bool pmo_dcn42_init_for_pstate_support(struct dml2_pmo_init_for_pstate_support_i // Figure out which streams can do vactive, and also build up implicit SVP and FAMS2 meta for (stream_index = 0; stream_index < display_config->display_config.num_streams; stream_index++) { - if (dcn4_get_vactive_pstate_margin(display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) >= (int)(MIN_VACTIVE_MARGIN_PCT * pmo->soc_bb->power_management_parameters.dram_clk_change_blackout_us)) + if (dcn4_get_vactive_pstate_margin(display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) >= 0) dcn42_set_bit_in_bitfield(&s->pmo_dcn4.stream_vactive_capability_mask, stream_index); } @@ -259,27 +257,17 @@ bool pmo_dcn42_fams2_optimize_for_pstate_support(struct dml2_pmo_optimize_for_ps bool pmo_dcn42_test_for_pstate_support(struct dml2_pmo_test_for_pstate_support_in_out *in_out) { - const struct dml2_pmo_scratch *s = &in_out->instance->scratch; - bool p_state_supported = true; - unsigned int stream_index; - - if (s->pmo_dcn4.cur_pstate_candidate < 0) + /* Return false on the initial candidate (cur_pstate_candidate == -1) so the + * optimization phase runs at least one optimize iteration; otherwise the + * FAMS2/stage-3 setup in the optimize callback is skipped. + */ + if (in_out->instance->scratch.pmo_dcn4.cur_pstate_candidate < 0) return false; - for (stream_index = 0; stream_index < in_out->base_display_config->display_config.num_streams; stream_index++) { - if (s->pmo_dcn4.pstate_strategy_candidates[s->pmo_dcn4.cur_pstate_candidate].per_stream_pstate_method[stream_index] == dml2_pstate_method_vactive) { - if (dcn4_get_minimum_reserved_time_us_for_planes(in_out->base_display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) < (int)in_out->instance->soc_bb->power_management_parameters.dram_clk_change_blackout_us || - dcn4_get_vactive_pstate_margin(in_out->base_display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) < (int)(MIN_VACTIVE_MARGIN_PCT * in_out->instance->soc_bb->power_management_parameters.dram_clk_change_blackout_us)) { - p_state_supported = false; - break; - } - } else { - p_state_supported = false; - break; - } - } - - return p_state_supported; + /* No-op: reserved time is guaranteed by the override and vactive p-state + * margin is now enforced in core mode support. + */ + return true; } bool pmo_dcn42_initialize(struct dml2_pmo_initialize_in_out *in_out) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c index d5f20c85006e..8f481ef9316f 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c @@ -1710,7 +1710,7 @@ static void build_pstate_meta_per_stream(struct dml2_pmo_instance *pmo, /* common */ stream_pstate_meta->valid = true; stream_pstate_meta->otg_vline_time_us = (double)timing->h_total / timing->pixel_clock_khz * 1000.0; - stream_pstate_meta->nom_vtotal = stream_descriptor->timing.vblank_nom + stream_descriptor->timing.v_active; + stream_pstate_meta->nom_vtotal = stream_descriptor->timing.v_total; stream_pstate_meta->nom_refresh_rate_hz = timing->pixel_clock_khz * 1000.0 / (stream_pstate_meta->nom_vtotal * timing->h_total); stream_pstate_meta->nom_frame_time_us = diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c index 9788628cf0ad..ff17a9c9eed0 100644 --- a/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c +++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c @@ -171,8 +171,8 @@ void dpp1_cm_set_gamut_remap( for (i = 0; i < 12; i++) arr_matrix[i] = adjust->temperature_matrix[i]; - convert_float_matrix( - arr_reg_val, arr_matrix, 12); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12); program_gamut_remap(dpp, arr_reg_val, GAMUT_REMAP_COEFF); } @@ -242,8 +242,8 @@ void dpp1_cm_get_gamut_remap(struct dpp *dpp_base, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, ARRAY_SIZE(arr_reg_val)); } static void dpp1_cm_program_color_matrix( diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c index 26f9485f165d..74cc176d9437 100644 --- a/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c +++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c @@ -227,8 +227,8 @@ void dpp2_cm_set_gamut_remap( for (i = 0; i < 12; i++) arr_matrix[i] = adjust->temperature_matrix[i]; - convert_float_matrix( - arr_reg_val, arr_matrix, 12); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12); program_gamut_remap(dpp, arr_reg_val, DCN2_GAMUT_REMAP_COEF_A); } @@ -285,8 +285,8 @@ void dpp2_cm_get_gamut_remap(struct dpp *dpp_base, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, ARRAY_SIZE(arr_reg_val)); } void dpp2_program_input_csc( diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c index e01d8e9672fc..8fe3474411b7 100644 --- a/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c +++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c @@ -392,8 +392,8 @@ void dpp3_cm_set_gamut_remap( for (i = 0; i < 12; i++) arr_matrix[i] = adjust->temperature_matrix[i]; - convert_float_matrix( - arr_reg_val, arr_matrix, 12); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12); //current coefficient set in use REG_GET(CM_GAMUT_REMAP_CONTROL, CM_GAMUT_REMAP_MODE_CURRENT, &gamut_mode); @@ -460,6 +460,6 @@ void dpp3_cm_get_gamut_remap(struct dpp *dpp_base, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, ARRAY_SIZE(arr_reg_val)); } diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn401/dcn401_dpp_dscl.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn401/dcn401_dpp_dscl.c index 62994aecf499..e0ee00c955bc 100644 --- a/drivers/gpu/drm/amd/display/dc/dpp/dcn401/dcn401_dpp_dscl.c +++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn401/dcn401_dpp_dscl.c @@ -963,14 +963,13 @@ static void dpp401_dscl_program_isharp(struct dpp *dpp_base, PERF_TRACE(); /*power on isharp_delta_mem first*/ - if (dpp_base->ctx->dc->caps.ips_v2_support) { - /*HW default is LS, need to wake up*/ - REG_UPDATE_2(ISHARP_DELTA_LUT_MEM_PWR_CTRL, - ISHARP_DELTA_LUT_MEM_PWR_FORCE, 0, - ISHARP_DELTA_LUT_MEM_PWR_DIS, 1); - REG_WAIT(ISHARP_DELTA_LUT_MEM_PWR_CTRL, - ISHARP_DELTA_LUT_MEM_PWR_STATE, 0, 1, 100); - } + REG_UPDATE_2(ISHARP_DELTA_LUT_MEM_PWR_CTRL, + ISHARP_DELTA_LUT_MEM_PWR_FORCE, 0, + ISHARP_DELTA_LUT_MEM_PWR_DIS, 1); + + REG_WAIT(ISHARP_DELTA_LUT_MEM_PWR_CTRL, + ISHARP_DELTA_LUT_MEM_PWR_STATE, 0, 1, 100); + /* ISHARP_MODE */ REG_SET_6(ISHARP_MODE, 0, ISHARP_EN, scl_data->dscl_prog_data.isharp_en, @@ -1049,12 +1048,10 @@ static void dpp401_dscl_program_isharp(struct dpp *dpp_base, } /*power on isharp_delta_mem first*/ - if (dpp_base->ctx->dc->caps.ips_v2_support) { - /*HW default is LS, need to wake up*/ - REG_UPDATE_SEQ_2(ISHARP_DELTA_LUT_MEM_PWR_CTRL, - ISHARP_DELTA_LUT_MEM_PWR_FORCE, 0, - ISHARP_DELTA_LUT_MEM_PWR_DIS, 0); - } + REG_UPDATE_SEQ_2(ISHARP_DELTA_LUT_MEM_PWR_CTRL, + ISHARP_DELTA_LUT_MEM_PWR_FORCE, 0, + ISHARP_DELTA_LUT_MEM_PWR_DIS, 0); + PERF_TRACE(); } // dpp401_dscl_program_isharp /** diff --git a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c index 37adf0e6a166..5a611dd14581 100644 --- a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c +++ b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c @@ -371,7 +371,8 @@ void dwb3_set_gamut_remap( for (i = 0; i < 12; i++) arr_matrix[i] = adjust.temperature_matrix[i]; - convert_float_matrix(arr_reg_val, arr_matrix, 12); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12); REG_GET(DWB_GAMUT_REMAP_MODE, DWB_GAMUT_REMAP_MODE_CURRENT, ¤t_mode); diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.h b/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.h index 5ae9fb76c675..ddc614c98234 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.h +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.h @@ -340,7 +340,12 @@ HUBBUB_SF(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_Z8_D, DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_Z8_D, mask_sh), \ HUBBUB_SF(DCHUBBUB_ARB_DRAM_STATE_CNTL, DCHUBBUB_ARB_ALLOW_CSTATE_DEEPSLEEP_LEGACY_MODE, mask_sh), \ HUBBUB_SF(DCHUBBUB_ARB_HOSTVM_CNTL, DCHUBBUB_ARB_MAX_QOS_COMMIT_THRESHOLD, mask_sh),\ - HUBBUB_SF(DCHUBBUB_ARB_DF_REQ_OUTSTAND, DCHUBBUB_ARB_MIN_REQ_OUTSTAND_COMMIT_THRESHOLD, mask_sh) + HUBBUB_SF(DCHUBBUB_ARB_DF_REQ_OUTSTAND, DCHUBBUB_ARB_MIN_REQ_OUTSTAND_COMMIT_THRESHOLD, mask_sh), \ + HUBBUB_SF(DCHUBBUB_CTRL_STATUS, DCHUBBUB_HW_DEBUG, mask_sh) + +#define DCHUBBUB_CTRL_STATUS__DCHUBBUB_HW_DEBUG__SHIFT 0x4 + +#define DCHUBBUB_CTRL_STATUS__DCHUBBUB_HW_DEBUG_MASK 0x3FFFFFF0L void hubbub35_construct(struct dcn20_hubbub *hubbub2, struct dc_context *ctx, diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c index bf30a1bb61b7..e9ff20c02b1e 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c @@ -1250,7 +1250,8 @@ static const struct hubbub_funcs hubbub4_01_funcs = { .program_compbuf_segments = dcn401_program_compbuf_segments, .wait_for_det_update = dcn401_wait_for_det_update, .program_arbiter = dcn401_program_arbiter, - .hubbub_read_reg_state = hubbub3_read_reg_state + .hubbub_read_reg_state = hubbub3_read_reg_state, + .get_mall_en = hubbub32_get_mall_en }; void hubbub401_construct(struct dcn20_hubbub *hubbub2, diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h index f48715544429..346bead81504 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h @@ -133,7 +133,9 @@ HUBBUB_SF(DCHUBBUB_CTRL_STATUS, ROB_OVERFLOW_STATUS, mask_sh),\ HUBBUB_SF(DCHUBBUB_CTRL_STATUS, ROB_OVERFLOW_CLEAR, mask_sh),\ HUBBUB_SF(DCHUBBUB_CTRL_STATUS, DCHUBBUB_HW_DEBUG, mask_sh),\ - HUBBUB_SF(DCHUBBUB_CTRL_STATUS, CSTATE_SWATH_CHK_GOOD_MODE, mask_sh) + HUBBUB_SF(DCHUBBUB_CTRL_STATUS, CSTATE_SWATH_CHK_GOOD_MODE, mask_sh),\ + HUBBUB_SF(DCHUBBUB_ARB_MALL_CNTL, MALL_PREFETCH_COMPLETE, mask_sh),\ + HUBBUB_SF(DCHUBBUB_ARB_MALL_CNTL, MALL_IN_USE, mask_sh) bool hubbub401_program_urgent_watermarks( struct hubbub *hubbub, diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn10/dcn10_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn10/dcn10_hubp.c index 7c97a774141f..d8eb5996b577 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn10/dcn10_hubp.c +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn10/dcn10_hubp.c @@ -772,8 +772,7 @@ bool hubp1_is_flip_pending(struct hubp *hubp) if (flip_pending) return true; - if (hubp && - earliest_inuse_address.grph.addr.quad_part != hubp->request_address.grph.addr.quad_part) + if (earliest_inuse_address.grph.addr.quad_part != hubp->request_address.grph.addr.quad_part) return true; return false; diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn20/dcn20_hubp.h b/drivers/gpu/drm/amd/display/dc/hubp/dcn20/dcn20_hubp.h index 80be394a8852..2595c6441f00 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn20/dcn20_hubp.h +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn20/dcn20_hubp.h @@ -286,7 +286,8 @@ type MCACHEID_MALL_PREF_2H_P0;\ type MCACHEID_MALL_PREF_1H_P1;\ type MCACHEID_MALL_PREF_2H_P1;\ - type HUBP_FGCG_REP_DIS + type HUBP_FGCG_REP_DIS;\ + type DST_Y_DELTA_DRQ_LIMIT #define DCN42_HUBP_REG_FIELD_VARIABLE_LIST(type) \ type HUBP_3DLUT_CROSSBAR_SEL_G;\ diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c index 57de98444f6c..94730f3cff42 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c @@ -580,7 +580,56 @@ static bool hubp42_program_surface_flip_and_addr( return true; } +void hubp42_setup_interdependent( + struct hubp *hubp, + struct dml2_dchub_per_pipe_register_set *pipe_regs) +{ + struct dcn20_hubp *hubp2 = TO_DCN20_HUBP(hubp); + REG_SET_2(PREFETCH_SETTINGS, 0, + DST_Y_PREFETCH, pipe_regs->dlg_regs.dst_y_prefetch, + VRATIO_PREFETCH, pipe_regs->dlg_regs.vratio_prefetch); + + REG_SET(PREFETCH_SETTINGS_C, 0, + VRATIO_PREFETCH_C, pipe_regs->dlg_regs.vratio_prefetch_c); + + REG_SET_2(VBLANK_PARAMETERS_0, 0, + DST_Y_PER_VM_VBLANK, pipe_regs->dlg_regs.dst_y_per_vm_vblank, + DST_Y_PER_ROW_VBLANK, pipe_regs->dlg_regs.dst_y_per_row_vblank); + + REG_SET_2(FLIP_PARAMETERS_0, 0, + DST_Y_PER_VM_FLIP, pipe_regs->dlg_regs.dst_y_per_vm_flip, + DST_Y_PER_ROW_FLIP, pipe_regs->dlg_regs.dst_y_per_row_flip); + + REG_SET(VBLANK_PARAMETERS_3, 0, + REFCYC_PER_META_CHUNK_VBLANK_L, pipe_regs->dlg_regs.refcyc_per_meta_chunk_vblank_l); + + REG_SET(VBLANK_PARAMETERS_4, 0, + REFCYC_PER_META_CHUNK_VBLANK_C, pipe_regs->dlg_regs.refcyc_per_meta_chunk_vblank_c); + + REG_SET(FLIP_PARAMETERS_2, 0, + REFCYC_PER_META_CHUNK_FLIP_L, pipe_regs->dlg_regs.refcyc_per_meta_chunk_flip_l); + + REG_SET_2(PER_LINE_DELIVERY_PRE, 0, + REFCYC_PER_LINE_DELIVERY_PRE_L, pipe_regs->dlg_regs.refcyc_per_line_delivery_pre_l, + REFCYC_PER_LINE_DELIVERY_PRE_C, pipe_regs->dlg_regs.refcyc_per_line_delivery_pre_c); + + REG_SET(DCN_SURF0_TTU_CNTL1, 0, + REFCYC_PER_REQ_DELIVERY_PRE, + pipe_regs->ttu_regs.refcyc_per_req_delivery_pre_l); + REG_SET(DCN_SURF1_TTU_CNTL1, 0, + REFCYC_PER_REQ_DELIVERY_PRE, + pipe_regs->ttu_regs.refcyc_per_req_delivery_pre_c); + REG_SET(DCN_CUR0_TTU_CNTL1, 0, + REFCYC_PER_REQ_DELIVERY_PRE, pipe_regs->ttu_regs.refcyc_per_req_delivery_pre_cur0); + + REG_SET_2(DCN_GLOBAL_TTU_CNTL, 0, + MIN_TTU_VBLANK, pipe_regs->ttu_regs.min_ttu_vblank, + QoS_LEVEL_FLIP, pipe_regs->ttu_regs.qos_level_flip); + + REG_SET(DST_Y_DELTA_DRQ_LIMIT, 0, + DST_Y_DELTA_DRQ_LIMIT, pipe_regs->dlg_regs.dst_y_delta_drq_limit); +} struct hubp_funcs dcn42_hubp_funcs = { .hubp_enable_tripleBuffer = hubp2_enable_triplebuffer, .hubp_is_triplebuffer_enabled = hubp2_is_triplebuffer_enabled, @@ -588,7 +637,7 @@ struct hubp_funcs dcn42_hubp_funcs = { .hubp_program_surface_config = hubp42_program_surface_config, .hubp_is_flip_pending = hubp2_is_flip_pending, .hubp_setup2 = hubp42_setup, - .hubp_setup_interdependent2 = hubp401_setup_interdependent, + .hubp_setup_interdependent2 = hubp42_setup_interdependent, .hubp_set_vm_system_aperture_settings = hubp3_set_vm_system_aperture_settings, .set_blank = hubp2_set_blank, .set_blank_regs = hubp2_set_blank_regs, diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h index fc30afd4c912..ba4a58c353dd 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.h @@ -35,6 +35,7 @@ HUBP_SF(HUBP0_3DLUT_FL_CONFIG, HUBP0_3DLUT_FL_FORMAT, mask_sh),\ HUBP_SF(HUBP0_3DLUT_FL_BIAS_SCALE, HUBP0_3DLUT_FL_BIAS, mask_sh),\ HUBP_SF(HUBP0_3DLUT_FL_BIAS_SCALE, HUBP0_3DLUT_FL_SCALE, mask_sh),\ + HUBP_SF(HUBPREQ0_DST_Y_DELTA_DRQ_LIMIT, DST_Y_DELTA_DRQ_LIMIT, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_ENABLE, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_DONE, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_ADDRESSING_MODE, mask_sh),\ @@ -54,6 +55,7 @@ HUBP_SF(HUBP0_3DLUT_FL_CONFIG, HUBP0_3DLUT_FL_FORMAT, mask_sh),\ HUBP_SF(HUBP0_3DLUT_FL_BIAS_SCALE, HUBP0_3DLUT_FL_BIAS, mask_sh),\ HUBP_SF(HUBP0_3DLUT_FL_BIAS_SCALE, HUBP0_3DLUT_FL_SCALE, mask_sh),\ + HUBP_SF(HUBPREQ0_DST_Y_DELTA_DRQ_LIMIT, DST_Y_DELTA_DRQ_LIMIT, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_ENABLE, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_DONE, mask_sh),\ HUBP_SF(CURSOR0_0_HUBP_3DLUT_CONTROL, HUBP_3DLUT_ADDRESSING_MODE, mask_sh),\ @@ -95,4 +97,7 @@ void hubp42_setup( union dml2_global_sync_programming *pipe_global_sync, struct dc_crtc_timing *timing); +void hubp42_setup_interdependent( + struct hubp *hubp, + struct dml2_dchub_per_pipe_register_set *pipe_regs); #endif /* __DC_HUBP_DCN42_H__ */ diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index c9691974bf72..f91ce2b4b1bf 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -607,11 +607,10 @@ dce110_translate_regamma_to_hw_format(const struct dc_transfer_func *output_tf, } static bool -dce110_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +dce110_set_output_transfer_func(struct set_output_transfer_func_params *params) { - (void)dc; - struct transform *xfm = pipe_ctx->plane_res.xfm; + struct transform *xfm = params->xfm; + const struct dc_stream_state *stream = params->stream; xfm->funcs->opp_power_on_regamma_lut(xfm, true); xfm->regamma_params.hw_points_num = GAMMA_HW_POINTS_NUM; @@ -1337,8 +1336,27 @@ void dce110_blank_stream(struct pipe_ctx *pipe_ctx) void dce110_set_avmute(struct pipe_ctx *pipe_ctx, bool enable) { - if (pipe_ctx != NULL && pipe_ctx->stream_res.stream_enc != NULL) + if (pipe_ctx == NULL || pipe_ctx->stream_res.stream_enc == NULL) + return; + + if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { pipe_ctx->stream_res.stream_enc->funcs->set_avmute(pipe_ctx->stream_res.stream_enc, enable); + + /* Wait for three frames to make sure AV mute is sent out. + * Some HDMI sinks need additional GCP packets to properly + * process the mute state, especially after link re-establishment + * with HDMI 2.0 scrambling enabled. + */ + if (enable && pipe_ctx->stream_res.tg->funcs->is_tg_enabled(pipe_ctx->stream_res.tg)) { + int i; + + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); + for (i = 0; i < 3; i++) { + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK); + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); + } + } + } } enum audio_dto_source translate_to_dto_source(enum controller_id crtc_id) @@ -2804,23 +2822,26 @@ static void program_surface_visibility(const struct dc *dc, } -static void program_gamut_remap(struct pipe_ctx *pipe_ctx) +static void program_gamut_remap(struct program_gamut_remap_params *params) { + struct transform *xfm = params->xfm; + const struct dc_stream_state *stream = params->stream; int i = 0; struct xfm_grph_csc_adjustment adjust; + memset(&adjust, 0, sizeof(adjust)); adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; - if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { + if (stream->gamut_remap_matrix.enable_remap == true) { adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) adjust.temperature_matrix[i] = - pipe_ctx->stream->gamut_remap_matrix.matrix[i]; + stream->gamut_remap_matrix.matrix[i]; } - pipe_ctx->plane_res.xfm->funcs->transform_set_gamut_remap(pipe_ctx->plane_res.xfm, &adjust); + xfm->funcs->transform_set_gamut_remap(xfm, &adjust); } static void update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx) @@ -3172,7 +3193,7 @@ static void dce110_program_front_end_for_pipe( hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); if (pipe_ctx->plane_state->update_bits.full_update) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); DC_LOG_SURFACE( "Pipe:%d %p: addr hi:0x%x, " diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c index 26aa303b8237..221996b348ab 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c @@ -338,7 +338,7 @@ dce60_program_front_end_for_pipe( hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); if (pipe_ctx->plane_state->update_bits.full_update) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); DC_LOG_SURFACE( "Pipe:%d %p: addr hi:0x%x, " diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c index 541cd908b341..22c7535fd9c4 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c @@ -2154,10 +2154,11 @@ static void log_tf(struct dc_context *ctx, } } -bool dcn10_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn10_set_output_transfer_func(struct set_output_transfer_func_params *params) { - struct dpp *dpp = pipe_ctx->plane_res.dpp; + struct dpp *dpp = params->dpp; + const struct dc_stream_state *stream = params->stream; + struct dc *dc; if (!stream) return false; @@ -2165,6 +2166,8 @@ bool dcn10_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, if (dpp == NULL) return false; + dc = dpp->ctx->dc; + dpp->regamma_params.hw_points_num = GAMMA_HW_POINTS_NUM; if (stream->out_transfer_func.type == TF_TYPE_PREDEFINED && @@ -2814,28 +2817,32 @@ static void dcn10_enable_plane( } -void dcn10_program_gamut_remap(struct pipe_ctx *pipe_ctx) +void dcn10_program_gamut_remap(struct program_gamut_remap_params *params) { + struct dpp *dpp = params->dpp; + const struct dc_stream_state *stream = params->stream; + const struct dc_plane_state *plane = params->plane; int i = 0; struct dpp_grph_csc_adjustment adjust; + memset(&adjust, 0, sizeof(adjust)); adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; - if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { + if (stream->gamut_remap_matrix.enable_remap == true) { adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) adjust.temperature_matrix[i] = - pipe_ctx->stream->gamut_remap_matrix.matrix[i]; - } else if (pipe_ctx->plane_state && - pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) { + stream->gamut_remap_matrix.matrix[i]; + } else if (plane && + plane->gamut_remap_matrix.enable_remap == true) { adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) adjust.temperature_matrix[i] = - pipe_ctx->plane_state->gamut_remap_matrix.matrix[i]; + plane->gamut_remap_matrix.matrix[i]; } - pipe_ctx->plane_res.dpp->funcs->dpp_set_gamut_remap(pipe_ctx->plane_res.dpp, &adjust); + dpp->funcs->dpp_set_gamut_remap(dpp, &adjust); } @@ -3152,7 +3159,7 @@ static void dcn10_update_dchubp_dpp( if (plane_state->update_bits.full_update) { /*gamut remap*/ - dc->hwss.program_gamut_remap(pipe_ctx); + hwss_program_gamut_remap(pipe_ctx); dc->hwss.program_output_csc(dc, pipe_ctx, @@ -3297,7 +3304,7 @@ void dcn10_program_pipe( * doing heavy calculation and programming */ if (pipe_ctx->plane_state->update_bits.full_update) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); } void dcn10_wait_for_pending_cleared(struct dc *dc, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h index 476095c5dd0c..2cb674ba54e1 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h @@ -73,8 +73,7 @@ void dcn10_program_output_csc(struct dc *dc, enum dc_color_space colorspace, uint16_t *matrix, int opp_id); -bool dcn10_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn10_set_output_transfer_func(struct set_output_transfer_func_params *params); bool dcn10_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); void dcn10_update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx); @@ -108,7 +107,7 @@ void dcn10_program_pipe( struct dc *dc, struct pipe_ctx *pipe_ctx, struct dc_state *context); -void dcn10_program_gamut_remap(struct pipe_ctx *pipe_ctx); +void dcn10_program_gamut_remap(struct program_gamut_remap_params *params); void dcn10_init_hw(struct dc *dc); void dcn10_init_pipes(struct dc *dc, struct dc_state *context); void dcn10_power_down_on_boot(struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c index 95e5b6a6ba0f..98778d5e114e 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c @@ -1019,26 +1019,30 @@ void dcn20_program_output_csc(struct dc *dc, } } -bool dcn20_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn20_set_output_transfer_func(struct set_output_transfer_func_params *otf_params) { - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dpp *dpp = otf_params->dpp; + struct mpc *mpc = otf_params->mpc; + int mpcc_id = otf_params->mpcc_id; + bool is_top_pipe = otf_params->is_top_pipe; + const struct dc_stream_state *stream = otf_params->stream; + struct dc *dc = dpp->ctx->dc; const struct pwl_params *params = NULL; + /* * program OGAM only for the top pipe * if there is a pipe split then fix diagnostic is required: * how to pass OGAM parameter for stream. * if programming for all pipes is required then remove condition - * pipe_ctx->top_pipe == NULL ,but then fix the diagnostic. + * is_top_pipe ,but then fix the diagnostic. */ if (mpc->funcs->power_on_mpc_mem_pwr) mpc->funcs->power_on_mpc_mem_pwr(mpc, mpcc_id, true); - if (pipe_ctx->top_pipe == NULL + if (is_top_pipe && mpc->funcs->set_output_gamma) { if (stream->out_transfer_func.type == TF_TYPE_HWPWL) params = &stream->out_transfer_func.pwl; - else if (pipe_ctx->stream->out_transfer_func.type == + else if (stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && cm_helper_translate_curve_to_hw_format(dc->ctx, &stream->out_transfer_func, @@ -1815,7 +1819,7 @@ void dcn20_update_dchubp_dpp( || plane_state->update_bits.gamut_remap_change || pipe_ctx->stream->update_flags.bits.out_csc) { /* dpp/cm gamut remap*/ - dc->hwss.program_gamut_remap(pipe_ctx); + hwss_program_gamut_remap(pipe_ctx); /*call the dcn2 method which uses mpc csc*/ dc->hwss.program_output_csc(dc, @@ -1991,7 +1995,7 @@ static void dcn20_program_pipe( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.out_tf) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); /* If the pipe has been enabled or has a different opp, we * should reprogram the fmt. This deals with cases where diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h index 9d1ad3b29ca5..749348d3c793 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h @@ -44,8 +44,7 @@ void dcn20_update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx); void dcn20_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx); bool dcn20_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); -bool dcn20_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn20_set_output_transfer_func(struct set_output_transfer_func_params *params); void dcn20_program_output_csc(struct dc *dc, struct pipe_ctx *pipe_ctx, enum dc_color_space colorspace, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c index 476c2112afec..2be68f8dd0fc 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c @@ -136,6 +136,26 @@ void dcn21_PLAT_58856_wa(struct dc_state *context, struct pipe_ctx *pipe_ctx) pipe_ctx->stream->dpms_off = true; } +bool dcn21_dmub_cacp_set_pipe(struct abm *abm, uint32_t otg_inst, + uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_pipe.header.type = DMUB_CMD__CACP; + cmd.cacp_set_pipe.header.sub_type = DMUB_CMD__CACP_SET_PIPE; + cmd.cacp_set_pipe.cacp_set_pipe_data.otg_inst = (uint8_t)otg_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.pwrseq_inst = (uint8_t)pwrseq_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.set_pipe_option = (uint8_t)option; + cmd.cacp_set_pipe.cacp_set_pipe_data.panel_inst = (uint8_t)panel_inst; + cmd.cacp_set_pipe.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_pipe_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + bool dcn21_dmub_abm_set_pipe(struct abm *abm, uint32_t otg_inst, uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst) { @@ -181,10 +201,12 @@ void dcn21_set_abm_immediate_disable(struct pipe_ctx *pipe_ctx) uint32_t otg_inst = pipe_ctx->stream_res.tg->inst; struct panel_cntl *panel_cntl = pipe_ctx->stream->link->panel_cntl; struct dmcu *dmcu = pipe_ctx->stream->ctx->dc->res_pool->dmcu; + struct dc_link *link = pipe_ctx->stream->link; // make a short term w/a for an issue that backlight ramping unexpectedly paused in the middle, // will decouple backlight from ABM and redefine DMUB interface, then this w/a could be removed - if (pipe_ctx->stream->abm_level == 0 || pipe_ctx->stream->abm_level == ABM_LEVEL_IMMEDIATE_DISABLE) { + if ((pipe_ctx->stream->abm_level == 0 || pipe_ctx->stream->abm_level == ABM_LEVEL_IMMEDIATE_DISABLE) + && (link && !link->panel_config.cacp.cacp_supported)) { return; } @@ -197,6 +219,9 @@ void dcn21_set_abm_immediate_disable(struct pipe_ctx *pipe_ctx) if (abm->funcs && abm->funcs->set_pipe_ex) { abm->funcs->set_pipe_ex(abm, otg_inst, SET_ABM_PIPE_IMMEDIATELY_DISABLE, panel_cntl->inst, panel_cntl->pwrseq_inst); + } else if (link && link->panel_config.cacp.cacp_supported) { + dcn21_dmub_cacp_set_pipe(abm, otg_inst, SET_CACP_PIPE_IMMEDIATELY_DISABLE, + panel_cntl->inst, panel_cntl->pwrseq_inst); } else { dcn21_dmub_abm_set_pipe(abm, otg_inst, @@ -214,6 +239,7 @@ void dcn21_set_pipe(struct pipe_ctx *pipe_ctx) struct timing_generator *tg = pipe_ctx->stream_res.tg; struct panel_cntl *panel_cntl = pipe_ctx->stream->link->panel_cntl; struct dmcu *dmcu = pipe_ctx->stream->ctx->dc->res_pool->dmcu; + struct dc_link *link = pipe_ctx->stream->link; uint32_t otg_inst; if (!abm || !tg || !panel_cntl) @@ -233,6 +259,11 @@ void dcn21_set_pipe(struct pipe_ctx *pipe_ctx) panel_cntl->inst, panel_cntl->pwrseq_inst); } else { + + if (link && link->panel_config.cacp.cacp_supported) + dcn21_dmub_cacp_set_pipe(abm, otg_inst, SET_CACP_PIPE_NORMAL, + panel_cntl->inst, panel_cntl->pwrseq_inst); + else dcn21_dmub_abm_set_pipe(abm, otg_inst, SET_ABM_PIPE_NORMAL, panel_cntl->inst, @@ -250,6 +281,7 @@ bool dcn21_set_backlight_level(struct pipe_ctx *pipe_ctx, uint32_t otg_inst; uint32_t backlight_pwm_u16_16 = backlight_level_params->backlight_pwm_u16_16; uint32_t frame_ramp = backlight_level_params->frame_ramp; + struct dc_link *link = pipe_ctx->stream->link; if (!abm || !tg || !panel_cntl) return false; @@ -268,6 +300,12 @@ bool dcn21_set_backlight_level(struct pipe_ctx *pipe_ctx, panel_cntl->inst, panel_cntl->pwrseq_inst); } else { + if (link && link->panel_config.cacp.cacp_supported) + dcn21_dmub_cacp_set_pipe(abm, otg_inst, + SET_CACP_PIPE_NORMAL, + panel_cntl->inst, + panel_cntl->pwrseq_inst); + else dcn21_dmub_abm_set_pipe(abm, otg_inst, SET_ABM_PIPE_NORMAL, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h index f72a27ac1bf1..aae13447cfa8 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h @@ -47,6 +47,8 @@ void dcn21_optimize_pwr_state( void dcn21_PLAT_58856_wa(struct dc_state *context, struct pipe_ctx *pipe_ctx); +bool dcn21_dmub_cacp_set_pipe(struct abm *abm, uint32_t otg_inst, + uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst); bool dcn21_dmub_abm_set_pipe(struct abm *abm, uint32_t otg_inst, uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst); void dcn21_set_pipe(struct pipe_ctx *pipe_ctx); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index aed9d06ec538..59184e146fc9 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c @@ -256,13 +256,10 @@ bool dcn30_set_blend_lut( return result; } -static bool dcn30_set_mpc_shaper_3dlut(struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +static bool dcn30_set_mpc_shaper_3dlut(struct dpp *dpp, struct mpc *mpc, + int mpcc_id, const struct dc_stream_state *stream) { - struct dpp *dpp_base = pipe_ctx->plane_res.dpp; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct dc *dc = pipe_ctx->stream->ctx->dc; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dc *dc = dpp->ctx->dc; bool result = false; uint32_t acquired_rmu = 0; int mpcc_id_projected = 0; @@ -274,8 +271,8 @@ static bool dcn30_set_mpc_shaper_3dlut(struct pipe_ctx *pipe_ctx, shaper_lut = &stream->func_shaper->pwl; } else if (stream->func_shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { cm_helper_translate_curve_to_hw_format(stream->ctx, stream->func_shaper, - &dpp_base->shaper_params, true); - shaper_lut = &dpp_base->shaper_params; + &dpp->shaper_params, true); + shaper_lut = &dpp->shaper_params; } } @@ -357,60 +354,66 @@ bool dcn30_set_input_transfer_func(struct dc *dc, return result; } -void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx) +void dcn30_program_gamut_remap(struct program_gamut_remap_params *params) { + struct dpp *dpp = params->dpp; + struct mpc *mpc = params->mpc; + int mpcc_id = params->mpcc_id; + const struct dc_stream_state *stream = params->stream; + const struct dc_plane_state *plane = params->plane; + bool is_top_pipe = params->is_top_pipe; int i = 0; struct dpp_grph_csc_adjustment dpp_adjust; struct mpc_grph_gamut_adjustment mpc_adjust; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; memset(&dpp_adjust, 0, sizeof(dpp_adjust)); dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; - if (pipe_ctx->plane_state && - pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) { + if (plane && + plane->gamut_remap_matrix.enable_remap == true) { dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) dpp_adjust.temperature_matrix[i] = - pipe_ctx->plane_state->gamut_remap_matrix.matrix[i]; + plane->gamut_remap_matrix.matrix[i]; } - pipe_ctx->plane_res.dpp->funcs->dpp_set_gamut_remap(pipe_ctx->plane_res.dpp, + dpp->funcs->dpp_set_gamut_remap(dpp, &dpp_adjust); memset(&mpc_adjust, 0, sizeof(mpc_adjust)); mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; - if (pipe_ctx->top_pipe == NULL) { - if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { + if (is_top_pipe) { + if (stream->gamut_remap_matrix.enable_remap == true) { mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) mpc_adjust.temperature_matrix[i] = - pipe_ctx->stream->gamut_remap_matrix.matrix[i]; + stream->gamut_remap_matrix.matrix[i]; } } mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust); } -bool dcn30_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn30_set_output_transfer_func(struct set_output_transfer_func_params *otf_params) { - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dpp *dpp = otf_params->dpp; + struct mpc *mpc = otf_params->mpc; + int mpcc_id = otf_params->mpcc_id; + bool is_top_pipe = otf_params->is_top_pipe; + const struct dc_stream_state *stream = otf_params->stream; + struct dc *dc = dpp->ctx->dc; const struct pwl_params *params = NULL; bool ret = false; /* program OGAM or 3DLUT only for the top pipe*/ - if (pipe_ctx->top_pipe == NULL) { + if (is_top_pipe) { /*program rmu shaper and 3dlut in MPC*/ - ret = dcn30_set_mpc_shaper_3dlut(pipe_ctx, stream); + ret = dcn30_set_mpc_shaper_3dlut(dpp, mpc, mpcc_id, stream); if (ret == false && mpc->funcs->set_output_gamma) { if (stream->out_transfer_func.type == TF_TYPE_HWPWL) params = &stream->out_transfer_func.pwl; - else if (pipe_ctx->stream->out_transfer_func.type == + else if (stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && cm3_helper_translate_curve_to_hw_format(stream->ctx, &stream->out_transfer_func, @@ -849,13 +852,19 @@ void dcn30_set_avmute(struct pipe_ctx *pipe_ctx, bool enable) pipe_ctx->stream_res.stream_enc, enable); - /* Wait for two frame to make sure AV mute is sent out */ + /* Wait for three frames to make sure AV mute is sent out. + * Some HDMI sinks need additional GCP packets to properly + * process the mute state, especially after link re-establishment + * with HDMI 2.0 scrambling enabled. + */ if (enable && pipe_ctx->stream_res.tg->funcs->is_tg_enabled(pipe_ctx->stream_res.tg)) { + int i; + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); - pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK); - pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); - pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK); - pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); + for (i = 0; i < 3; i++) { + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK); + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); + } } } } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h index a963d360a368..0399642076eb 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h @@ -63,11 +63,9 @@ bool dcn30_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); -void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx); +void dcn30_program_gamut_remap(struct program_gamut_remap_params *params); -bool dcn30_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn30_set_output_transfer_func(struct set_output_transfer_func_params *params); void dcn30_set_avmute(struct pipe_ctx *pipe_ctx, bool enable); void dcn30_update_info_frame(struct pipe_ctx *pipe_ctx); void dcn30_program_dmdata_engine(struct pipe_ctx *pipe_ctx); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c index 43e4edfe9182..b6f3c0480ab6 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c @@ -54,6 +54,7 @@ #include "dce/dce_i2c_hw.h" #include "dce/dmub_abm_lcd.h" #include "dio/dcn10/dcn10_dio.h" +#include "dce/dmub_abm_cacp.h" #define DC_LOGGER_INIT(logger) @@ -718,6 +719,34 @@ static void dmub_abm_set_backlight(struct dc_context *dc, dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); } +static bool dmub_cacp_set_backlight(struct dc_context *dc, + struct set_backlight_level_params *backlight_level_params, + unsigned int panel_inst) +{ + union dmub_rb_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_backlight.header.type = DMUB_CMD__CACP; + cmd.cacp_set_backlight.header.sub_type = DMUB_CMD__CACP_SET_BACKLIGHT; + cmd.cacp_set_backlight.cacp_set_backlight_data.aux_inst = backlight_level_params->aux_inst; + cmd.cacp_set_backlight.cacp_set_backlight_data.frame_ramp = backlight_level_params->frame_ramp; + cmd.cacp_set_backlight.cacp_set_backlight_data.backlight_user_level = + backlight_level_params->backlight_pwm_u16_16; + cmd.cacp_set_backlight.cacp_set_backlight_data.backlight_control_type = + (enum dmub_backlight_control_type)backlight_level_params->control_type; + cmd.cacp_set_backlight.cacp_set_backlight_data.min_luminance = backlight_level_params->min_luminance; + cmd.cacp_set_backlight.cacp_set_backlight_data.max_luminance = backlight_level_params->max_luminance; + cmd.cacp_set_backlight.cacp_set_backlight_data.min_backlight_pwm = backlight_level_params->min_backlight_pwm; + cmd.cacp_set_backlight.cacp_set_backlight_data.max_backlight_pwm = backlight_level_params->max_backlight_pwm; + cmd.cacp_set_backlight.cacp_set_backlight_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_backlight.cacp_set_backlight_data.panel_mask = (0x01 << panel_inst); + cmd.cacp_set_backlight.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_backlight_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + bool dcn31_set_backlight_level(struct pipe_ctx *pipe_ctx, struct set_backlight_level_params *backlight_level_params) { @@ -726,20 +755,38 @@ bool dcn31_set_backlight_level(struct pipe_ctx *pipe_ctx, struct timing_generator *tg = pipe_ctx->stream_res.tg; struct panel_cntl *panel_cntl = pipe_ctx->stream->link->panel_cntl; uint32_t otg_inst; + struct dc_link *link = pipe_ctx->stream->link; if (!abm || !tg || !panel_cntl) return false; otg_inst = tg->inst; + if (link && link->panel_config.cacp.cacp_supported) + dcn21_dmub_cacp_set_pipe(abm, otg_inst, + SET_CACP_PIPE_NORMAL, + panel_cntl->inst, + panel_cntl->pwrseq_inst); + else dcn21_dmub_abm_set_pipe(abm, otg_inst, SET_ABM_PIPE_NORMAL, panel_cntl->inst, panel_cntl->pwrseq_inst); - if (backlight_level_params->control_type != BACKLIGHT_CONTROL_AMD_AUX) + if (link && link->panel_type == PANEL_TYPE_OLED) { + /* For OLED panel with AMD AUX, skip set backlight call */ + if (backlight_level_params->control_type == BACKLIGHT_CONTROL_VESA_AUX) + dmub_cacp_set_backlight(dc, backlight_level_params, panel_cntl->inst); + } else if (link && link->panel_type == PANEL_TYPE_MINILED) { + /* For MiniLED panel we need to check if CACP or ABM is being used */ + if (link->panel_config.cacp.cacp_supported) + dmub_cacp_set_backlight(dc, backlight_level_params, panel_cntl->inst); + else + dmub_abm_set_backlight(dc, backlight_level_params, panel_cntl->inst); + } else { dmub_abm_set_backlight(dc, backlight_level_params, panel_cntl->inst); + } return true; } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c index c2ea25927765..c5f6eb482f07 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c @@ -443,13 +443,10 @@ void dcn32_subvp_pipe_control_lock_fast(union block_sequence_params *params) } } -bool dcn32_set_mpc_shaper_3dlut( - struct pipe_ctx *pipe_ctx, const struct dc_stream_state *stream) +bool dcn32_set_mpc_shaper_3dlut(struct dpp *dpp, struct mpc *mpc, + int mpcc_id, const struct dc_stream_state *stream) { - struct dpp *dpp_base = pipe_ctx->plane_res.dpp; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct dc *dc = pipe_ctx->stream->ctx->dc; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dc *dc = dpp->ctx->dc; bool result = false; const struct pwl_params *shaper_lut = NULL; @@ -460,8 +457,8 @@ bool dcn32_set_mpc_shaper_3dlut( else if (stream->func_shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { cm_helper_translate_curve_to_hw_format(stream->ctx, stream->func_shaper, - &dpp_base->shaper_params, true); - shaper_lut = &dpp_base->shaper_params; + &dpp->shaper_params, true); + shaper_lut = &dpp->shaper_params; } } @@ -566,24 +563,24 @@ bool dcn32_set_input_transfer_func(struct dc *dc, return result; } -bool dcn32_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn32_set_output_transfer_func(struct set_output_transfer_func_params *otf_params) { - (void)dc; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dpp *dpp = otf_params->dpp; + struct mpc *mpc = otf_params->mpc; + int mpcc_id = otf_params->mpcc_id; + bool is_top_pipe = otf_params->is_top_pipe; + const struct dc_stream_state *stream = otf_params->stream; const struct pwl_params *params = NULL; bool ret = false; /* program OGAM or 3DLUT only for the top pipe*/ - if (resource_is_pipe_type(pipe_ctx, OPP_HEAD)) { + if (is_top_pipe) { /*program shaper and 3dlut in MPC*/ - ret = dcn32_set_mpc_shaper_3dlut(pipe_ctx, stream); + ret = dcn32_set_mpc_shaper_3dlut(dpp, mpc, mpcc_id, stream); if (ret == false && mpc->funcs->set_output_gamma) { if (stream->out_transfer_func.type == TF_TYPE_HWPWL) params = &stream->out_transfer_func.pwl; - else if (pipe_ctx->stream->out_transfer_func.type == + else if (stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && cm3_helper_translate_curve_to_hw_format(stream->ctx, &stream->out_transfer_func, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h index 0303a5953673..090d94d38343 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h @@ -54,12 +54,10 @@ bool dcn32_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); -bool dcn32_set_mpc_shaper_3dlut( - struct pipe_ctx *pipe_ctx, const struct dc_stream_state *stream); +bool dcn32_set_mpc_shaper_3dlut(struct dpp *dpp_base, struct mpc *mpc, + int mpcc_id, const struct dc_stream_state *stream); -bool dcn32_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn32_set_output_transfer_func(struct set_output_transfer_func_params *params); void dcn32_init_hw(struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c index 01027d120cb0..f14c39a643da 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c @@ -487,7 +487,7 @@ void dcn35_update_odm(struct dc *dc, struct dc_state *context, struct pipe_ctx * void dcn35_dpp_root_clock_control(struct dce_hwseq *hws, unsigned int dpp_inst, bool clock_on) { - if (!hws->ctx->dc->debug.root_clock_optimization.bits.dpp) + if (!hws->ctx->dc->debug.root_clock_optimization.bits.dpp && !clock_on) return; if (hws->ctx->dc->res_pool->dccg->funcs->dpp_root_clock_control) { @@ -498,7 +498,7 @@ void dcn35_dpp_root_clock_control(struct dce_hwseq *hws, unsigned int dpp_inst, void dcn35_dpstream_root_clock_control(struct dce_hwseq *hws, unsigned int dp_hpo_inst, bool clock_on) { - if (!hws->ctx->dc->debug.root_clock_optimization.bits.dpstream) + if (!hws->ctx->dc->debug.root_clock_optimization.bits.dpstream && !clock_on) return; if (hws->ctx->dc->res_pool->dccg->funcs->set_dpstreamclk_root_clock_gating) { @@ -509,7 +509,7 @@ void dcn35_dpstream_root_clock_control(struct dce_hwseq *hws, unsigned int dp_hp void dcn35_hdmistream_root_clock_control(struct dce_hwseq *hws, bool clock_on) { - if (!hws->ctx->dc->debug.root_clock_optimization.bits.hdmistream) + if (!hws->ctx->dc->debug.root_clock_optimization.bits.hdmistream && !clock_on) return; if (hws->ctx->dc->res_pool->dccg->funcs->set_hdmistreamclk_root_clock_gating) { @@ -520,7 +520,7 @@ void dcn35_hdmistream_root_clock_control(struct dce_hwseq *hws, bool clock_on) void dcn35_physymclk_root_clock_control(struct dce_hwseq *hws, unsigned int phy_inst, bool clock_on) { - if (!hws->ctx->dc->debug.root_clock_optimization.bits.physymclk) + if (!hws->ctx->dc->debug.root_clock_optimization.bits.physymclk && !clock_on) return; if (hws->ctx->dc->res_pool->dccg->funcs->set_physymclk_root_clock_gating) { diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c index 0336d118e77e..fa9a5d8065d3 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c @@ -89,28 +89,31 @@ void dcn401_initialize_min_clocks(struct dc *dc) true); } -void dcn401_program_gamut_remap(struct pipe_ctx *pipe_ctx) +void dcn401_program_gamut_remap(struct program_gamut_remap_params *params) { + struct mpc *mpc = params->mpc; + int mpcc_id = params->mpcc_id; + const struct dc_stream_state *stream = params->stream; + const struct dc_plane_state *plane = params->plane; + bool is_top_pipe = params->is_top_pipe; unsigned int i = 0; struct mpc_grph_gamut_adjustment mpc_adjust; - unsigned int mpcc_id = pipe_ctx->plane_res.mpcc_inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; //For now assert if location is not pre-blend - if (pipe_ctx->plane_state) - ASSERT(pipe_ctx->plane_state->mcm_location == MPCC_MOVABLE_CM_LOCATION_BEFORE); + if (plane) + ASSERT(plane->mcm_location == MPCC_MOVABLE_CM_LOCATION_BEFORE); // program MPCC_MCM_FIRST_GAMUT_REMAP memset(&mpc_adjust, 0, sizeof(mpc_adjust)); mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; mpc_adjust.mpcc_gamut_remap_block_id = MPCC_MCM_FIRST_GAMUT_REMAP; - if (pipe_ctx->plane_state && - pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) { + if (plane && + plane->gamut_remap_matrix.enable_remap == true) { mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) mpc_adjust.temperature_matrix[i] = - pipe_ctx->plane_state->gamut_remap_matrix.matrix[i]; + plane->gamut_remap_matrix.matrix[i]; } mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust); @@ -126,12 +129,12 @@ void dcn401_program_gamut_remap(struct pipe_ctx *pipe_ctx) mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; mpc_adjust.mpcc_gamut_remap_block_id = MPCC_OGAM_GAMUT_REMAP; - if (pipe_ctx->top_pipe == NULL) { - if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { + if (is_top_pipe) { + if (stream->gamut_remap_matrix.enable_remap == true) { mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) mpc_adjust.temperature_matrix[i] = - pipe_ctx->stream->gamut_remap_matrix.matrix[i]; + stream->gamut_remap_matrix.matrix[i]; } } @@ -515,12 +518,10 @@ void dcn401_populate_mcm_luts(struct dc *dc, /* Select width based on the requested LUT size */ switch (cm->lut3d_dma.size) { -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) case CM_LUT_SIZE_333333: if (dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_33) width = hubp_3dlut_fl_width_33; break; -#endif // CONFIG_DRM_AMD_DC_DCN4_2 case CM_LUT_SIZE_171717: width = hubp_3dlut_fl_width_17; break; @@ -695,24 +696,24 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, return result; } -bool dcn401_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn401_set_output_transfer_func(struct set_output_transfer_func_params *otf_params) { - (void)dc; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dpp *dpp = otf_params->dpp; + struct mpc *mpc = otf_params->mpc; + int mpcc_id = otf_params->mpcc_id; + bool is_top_pipe = otf_params->is_top_pipe; + const struct dc_stream_state *stream = otf_params->stream; const struct pwl_params *params = NULL; bool ret = false; /* program OGAM or 3DLUT only for the top pipe*/ - if (resource_is_pipe_type(pipe_ctx, OPP_HEAD)) { + if (is_top_pipe) { /*program shaper and 3dlut in MPC*/ - ret = dcn32_set_mpc_shaper_3dlut(pipe_ctx, stream); + ret = dcn32_set_mpc_shaper_3dlut(dpp, mpc, mpcc_id, stream); if (ret == false && mpc->funcs->set_output_gamma) { if (stream->out_transfer_func.type == TF_TYPE_HWPWL) params = &stream->out_transfer_func.pwl; - else if (pipe_ctx->stream->out_transfer_func.type == + else if (stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && cm3_helper_translate_curve_to_hw_format(stream->ctx, &stream->out_transfer_func, @@ -2385,7 +2386,7 @@ void dcn401_program_pipe( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.out_tf) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); /* If the pipe has been enabled or has a different opp, we * should reprogram the fmt. This deals with cases where @@ -2543,7 +2544,7 @@ void dcn401_program_pipe_sequence( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.out_tf) { - hwss_add_dpp_set_output_transfer_func(seq_state, dc, pipe_ctx, pipe_ctx->stream); + hwss_add_dpp_set_output_transfer_func(seq_state, dc, pipe_ctx); } /* If the pipe has been enabled or has a different opp, we diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h index a760050eea8c..f90e25243ead 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h @@ -32,15 +32,13 @@ struct ips_ono_region_state { uint32_t current_pwr_state; }; -void dcn401_program_gamut_remap(struct pipe_ctx *pipe_ctx); +void dcn401_program_gamut_remap(struct program_gamut_remap_params *params); void dcn401_init_hw(struct dc *dc); bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); -bool dcn401_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn401_set_output_transfer_func(struct set_output_transfer_func_params *params); void dcn401_trigger_3dlut_dma_load(struct dc *dc, struct pipe_ctx *pipe_ctx); void dcn401_calculate_dccg_tmds_div_value(struct pipe_ctx *pipe_ctx, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c index f415473517d4..151a29bf0e9d 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c @@ -281,6 +281,8 @@ void dcn42_init_hw(struct dc *dc) dc->caps.dmub_caps.psr = dc->ctx->dmub_srv->dmub->feature_caps.psr; dc->caps.dmub_caps.mclk_sw = dc->ctx->dmub_srv->dmub->feature_caps.fw_assisted_mclk_switch_ver > 0; dc->caps.dmub_caps.fams_ver = dc->ctx->dmub_srv->dmub->feature_caps.fw_assisted_mclk_switch_ver; + dc->caps.dmub_caps.aux_backlight_support = + dc->ctx->dmub_srv->dmub->feature_caps.abm_aux_backlight_support; /* sw and fw FAMS versions must match for support */ dc->debug.fams2_config.bits.enable &= @@ -468,7 +470,6 @@ static bool dc_is_rmcm_3dlut_supported(struct hubp *hubp, struct mpc *mpc) return false; } -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) static bool is_rmcm_3dlut_fl_supported(struct dc *dc) { /* size was previously hard-coded to TRANSFORMED in local_mcm, @@ -478,7 +479,6 @@ static bool is_rmcm_3dlut_fl_supported(struct dc *dc) return false; return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_17 != 0u; } -#endif static void dcn42_set_mcm_location_post_blend(struct dc *dc, struct pipe_ctx *pipe_ctx, bool bPostBlend) { @@ -722,9 +722,8 @@ void dcn42_populate_mcm_luts(struct dc *dc, false); //RMCM - 3dLUT+Shaper -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) if (cm->flags.bits.rmcm_enable && - is_rmcm_3dlut_fl_supported(dc)) { + is_rmcm_3dlut_fl_supported(dc)) dcn42_program_rmcm_luts( hubp, pipe_ctx, @@ -732,8 +731,6 @@ void dcn42_populate_mcm_luts(struct dc *dc, mpc, lut_bank_a, mpcc_id); - } -#endif /* CONFIG_DRM_AMD_DC_DCN4_2 */ /* 1D LUT */ { @@ -949,7 +946,11 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, } void dcn42_hardware_release(struct dc *dc) { + if (dc->clk_mgr && dc->clk_mgr->funcs && dc->clk_mgr->funcs->notify_cstate_disable) + dc->clk_mgr->funcs->notify_cstate_disable(dc->clk_mgr, true); + dcn35_hardware_release(dc); + dc_dmub_srv_release_hw(dc); } @@ -1085,6 +1086,12 @@ void dcn42_prepare_bandwidth( } dcn401_prepare_bandwidth(dc, context); + + /* valid C-state watermarks have now been committed to HW, so it + * is safe to vote "allow" to PMFW. + */ + if (dc->clk_mgr && dc->clk_mgr->funcs && dc->clk_mgr->funcs->notify_cstate_disable) + dc->clk_mgr->funcs->notify_cstate_disable(dc->clk_mgr, false); } void dcn42_optimize_bandwidth(struct dc *dc, struct dc_state *context) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h index 65df8002d3d7..4549c435501a 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h @@ -32,6 +32,7 @@ #include "inc/hw/link_encoder.h" #include "inc/core_status.h" #include "inc/hw/hw_shared.h" +#include "inc/hw/dchubbub.h" #include "dsc/dsc.h" #include "link_service_types.h" @@ -53,7 +54,6 @@ struct drr_params; struct dc_underflow_debug_data; struct dsc_optc_config; struct vm_system_aperture_param; -struct dc_measured_memory_qos; struct stream_encoder; struct hpo_dp_stream_encoder; struct hpo_frl_stream_encoder; @@ -95,7 +95,13 @@ struct set_input_transfer_func_params { }; struct program_gamut_remap_params { - struct pipe_ctx *pipe_ctx; + struct transform *xfm; + struct dpp *dpp; + struct mpc *mpc; + int mpcc_id; + const struct dc_stream_state *stream; + const struct dc_plane_state *plane; + bool is_top_pipe; }; struct hubp_enable_3dlut_fl_params { @@ -132,8 +138,11 @@ struct program_bias_and_scale_params { }; struct set_output_transfer_func_params { - struct dc *dc; - struct pipe_ctx *pipe_ctx; + struct transform *xfm; + struct dpp *dpp; + struct mpc *mpc; + int mpcc_id; + bool is_top_pipe; const struct dc_stream_state *stream; }; @@ -571,6 +580,77 @@ struct hubbub_soft_reset_params { bool reset; }; +struct hubbub_perfmon_reset_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_arm_out_of_order_bw_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_out_of_order_bw_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_in_order_bw_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_memory_latencies_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_urgent_assertion_count_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_urgent_ramp_latency_params { + struct hubbub *hubbub; + struct hubbub_urgent_latency_params latency_params; +}; + +struct hubbub_perfmon_start_prefetch_data_size_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_get_out_of_order_bw_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + uint32_t *bandwidth_mbps; + uint32_t *duration_ns; +}; + +struct hubbub_perfmon_get_in_order_bw_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + uint32_t min_duration_ns; + uint32_t *bandwidth_mbps; + uint32_t *duration_ns; +}; + +struct hubbub_perfmon_get_memory_latencies_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + struct dc_probe_latencies *result; +}; + +struct hubbub_perfmon_get_urgent_assertion_count_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + uint32_t *assertion_count; +}; + +struct hubbub_perfmon_get_prefetch_data_size_params { + struct hubbub *hubbub; + uint32_t *prefetch_data_size; +}; + +struct hubbub_perfmon_get_urgent_ramp_latency_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + uint32_t *latency_ns; +}; + struct hubp_clk_cntl_params { struct hubp *hubp; bool enable; @@ -1022,6 +1102,20 @@ union block_sequence_params { struct hubp_set_blank_en_params hubp_set_blank_en_params; struct hubp_disable_control_params hubp_disable_control_params; struct hubbub_soft_reset_params hubbub_soft_reset_params; + struct hubbub_perfmon_reset_params hubbub_perfmon_reset_params; + struct hubbub_perfmon_arm_out_of_order_bw_params hubbub_perfmon_arm_out_of_order_bw_params; + struct hubbub_perfmon_start_out_of_order_bw_params hubbub_perfmon_start_out_of_order_bw_params; + struct hubbub_perfmon_start_in_order_bw_params hubbub_perfmon_start_in_order_bw_params; + struct hubbub_perfmon_start_memory_latencies_params hubbub_perfmon_start_memory_latencies_params; + struct hubbub_perfmon_start_urgent_assertion_count_params hubbub_perfmon_start_urgent_assertion_count_params; + struct hubbub_perfmon_start_urgent_ramp_latency_params hubbub_perfmon_start_urgent_ramp_latency_params; + struct hubbub_perfmon_start_prefetch_data_size_params hubbub_perfmon_start_prefetch_data_size_params; + struct hubbub_perfmon_get_out_of_order_bw_params hubbub_perfmon_get_out_of_order_bw_params; + struct hubbub_perfmon_get_in_order_bw_params hubbub_perfmon_get_in_order_bw_params; + struct hubbub_perfmon_get_memory_latencies_params hubbub_perfmon_get_memory_latencies_params; + struct hubbub_perfmon_get_urgent_assertion_count_params hubbub_perfmon_get_urgent_assertion_count_params; + struct hubbub_perfmon_get_prefetch_data_size_params hubbub_perfmon_get_prefetch_data_size_params; + struct hubbub_perfmon_get_urgent_ramp_latency_params hubbub_perfmon_get_urgent_ramp_latency_params; struct hubp_clk_cntl_params hubp_clk_cntl_params; struct hubp_init_params hubp_init_params; struct hubp_set_vm_system_aperture_settings_params hubp_set_vm_system_aperture_settings_params; @@ -1249,6 +1343,20 @@ enum block_sequence_func { HUBBUB_PROGRAM_WATERMARKS, HUBBUB_PROGRAM_ARBITER, HUBBUB_PROGRAM_COMPBUF_SEGMENTS, + HUBBUB_PERFMON_RESET, + HUBBUB_PERFMON_ARM_OUT_OF_ORDER_BW, + HUBBUB_PERFMON_START_OUT_OF_ORDER_BW, + HUBBUB_PERFMON_START_IN_ORDER_BW, + HUBBUB_PERFMON_START_MEMORY_LATENCIES, + HUBBUB_PERFMON_START_URGENT_ASSERTION_COUNT, + HUBBUB_PERFMON_START_URGENT_RAMP_LATENCY, + HUBBUB_PERFMON_START_PREFETCH_DATA_SIZE, + HUBBUB_PERFMON_GET_OUT_OF_ORDER_BW, + HUBBUB_PERFMON_GET_IN_ORDER_BW, + HUBBUB_PERFMON_GET_MEMORY_LATENCIES, + HUBBUB_PERFMON_GET_URGENT_ASSERTION_COUNT, + HUBBUB_PERFMON_GET_PREFETCH_DATA_SIZE, + HUBBUB_PERFMON_GET_URGENT_RAMP_LATENCY, /* This must be the last value in this enum, add new ones above */ HWSS_BLOCK_SEQUENCE_FUNC_COUNT }; @@ -1389,7 +1497,7 @@ struct hw_sequencer_funcs { void (*program_cursor_offload_now)(struct dc *dc, const struct pipe_ctx *pipe); /* Colour Related */ - void (*program_gamut_remap)(struct pipe_ctx *pipe_ctx); + void (*program_gamut_remap)(struct program_gamut_remap_params *params); void (*program_output_csc)(struct dc *dc, struct pipe_ctx *pipe_ctx, enum dc_color_space colorspace, uint16_t *matrix, int opp_id); @@ -1582,14 +1690,16 @@ struct hw_sequencer_funcs { struct dc_underflow_debug_data *out_data); /** - * measure_memory_qos - Measure memory QoS metrics - * @dc: DC structure - * @qos: Pointer to dc_measured_memory_qos struct to populate with measured values + * program_perfmon - Program/transition perfmon probes for a commit. + * @dc: DC structure + * @context: target state; probes, probe_count, and probe_status are + * read from and written to this object * - * Populates the provided dc_measured_memory_qos struct with peak bandwidth, average bandwidth, - * max latency, min latency, and average latency from hardware performance counters. + * Invoked during the execute phase of dc_update_state. The hook resolves + * each probe's transition by diffing @context against dc->current_state + * and latches MEASURED results into @context->probe_status. */ - void (*measure_memory_qos)(struct dc *dc, struct dc_measured_memory_qos *qos); + void (*program_perfmon)(struct dc *dc, struct dc_state *context); }; @@ -1866,6 +1976,34 @@ void hwss_hubp_disable_control(union block_sequence_params *params); void hwss_hubbub_soft_reset(union block_sequence_params *params); +void hwss_hubbub_perfmon_reset(union block_sequence_params *params); + +void hwss_hubbub_perfmon_arm_out_of_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_out_of_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_in_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_memory_latencies(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_urgent_assertion_count(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_urgent_ramp_latency(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_prefetch_data_size(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_out_of_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_in_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_memory_latencies(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_urgent_assertion_count(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_prefetch_data_size(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_urgent_ramp_latency(union block_sequence_params *params); + void hwss_hubp_clk_cntl(union block_sequence_params *params); void hwss_hubp_init(union block_sequence_params *params); @@ -1940,7 +2078,7 @@ void hwss_set_cursor_position(union block_sequence_params *params); void hwss_set_cursor_sdr_white_level(union block_sequence_params *params); -void hwss_program_gamut_remap(union block_sequence_params *params); +void hwss_program_gamut_remap(struct pipe_ctx *pipe_ctx); void hwss_program_output_csc(union block_sequence_params *params); @@ -1987,7 +2125,9 @@ void hwss_add_optc_program_manual_trigger(struct block_sequence_state *seq_state struct pipe_ctx *pipe_ctx); void hwss_add_dpp_set_output_transfer_func(struct block_sequence_state *seq_state, - struct dc *dc, struct pipe_ctx *pipe_ctx, struct dc_stream_state *stream); + struct dc *dc, struct pipe_ctx *pipe_ctx); + +void hwss_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx); void hwss_add_mpc_update_visual_confirm(struct block_sequence_state *seq_state, struct dc *dc, struct pipe_ctx *pipe_ctx, int mpcc_id); @@ -2202,6 +2342,54 @@ void hwss_add_hubbub_soft_reset(struct block_sequence_state *seq_state, void (*hubbub_soft_reset)(struct hubbub *hubbub, bool reset), bool reset); +void hwss_add_hubbub_perfmon_reset(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_arm_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_in_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_memory_latencies(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_urgent_assertion_count(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_urgent_ramp_latency(struct block_sequence_state *seq_state, + struct hubbub *hubbub, + const struct hubbub_urgent_latency_params *latency_params); + +void hwss_add_hubbub_perfmon_start_prefetch_data_size(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_get_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *bandwidth_mbps, uint32_t *duration_ns); + +void hwss_add_hubbub_perfmon_get_in_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, uint32_t min_duration_ns, + uint32_t *bandwidth_mbps, uint32_t *duration_ns); + +void hwss_add_hubbub_perfmon_get_memory_latencies(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + struct dc_probe_latencies *result); + +void hwss_add_hubbub_perfmon_get_urgent_assertion_count(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *assertion_count); + +void hwss_add_hubbub_perfmon_get_prefetch_data_size(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t *prefetch_data_size); + +void hwss_add_hubbub_perfmon_get_urgent_ramp_latency(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *latency_ns); + void hwss_add_hubp_clk_cntl(struct block_sequence_state *seq_state, struct hubp *hubp, bool enable); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h index b4956893ae9a..31ace62a37d9 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h @@ -67,6 +67,8 @@ struct dc_phy_addr_space_config; struct dc_virtual_addr_space_config; struct hubp; struct dpp; +struct transform; +struct mpc; struct dce_hwseq; struct timing_generator; struct tg_color; @@ -92,9 +94,7 @@ struct hwseq_private_funcs { bool (*set_input_transfer_func)(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); - bool (*set_output_transfer_func)(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); + bool (*set_output_transfer_func)(struct set_output_transfer_func_params *params); void (*power_down)(struct dc *dc); void (*enable_display_pipe_clock_gating)(struct dc_context *ctx, bool clock_gating); diff --git a/drivers/gpu/drm/amd/display/dc/inc/core_status.h b/drivers/gpu/drm/amd/display/dc/inc/core_status.h index 388f801f4582..1a17e727ed04 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/core_status.h +++ b/drivers/gpu/drm/amd/display/dc/inc/core_status.h @@ -62,6 +62,16 @@ enum dc_status { DC_FAIL_DP_LINK_BANDWIDTH = 28, DC_FAIL_HW_CURSOR_SUPPORT = 29, DC_FAIL_DP_TUNNEL_BW_VALIDATE = 30, + + /// Link protocol handshake and DPMS hardware programming successful. + DC_DPMS_SUCCESS = DC_OK, + /// Handshake skipped by optimized path, programming successfully completed. + DC_DPMS_SKIPPED_HANDSHAKE = 31, + /// Handshake failed, programming successful, DCN in consistent state. + DC_DPMS_FAILED_HANDSHAKE = 32, + /// Handshake failed, programming aborted, DCN may be in inconsistent state. + DC_DPMS_FAILED_INCOMPLETE = 33, + DC_ERROR_UNEXPECTED = -1 }; diff --git a/drivers/gpu/drm/amd/display/dc/inc/core_types.h b/drivers/gpu/drm/amd/display/dc/inc/core_types.h index cbbc1fb4b3dd..e87f9fd60bcb 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/core_types.h +++ b/drivers/gpu/drm/amd/display/dc/inc/core_types.h @@ -33,6 +33,7 @@ #include "dc_bios_types.h" #include "mem_input.h" #include "hubp.h" +#include "hw/dchubbub.h" #include "mpc.h" #include "dwb.h" #include "hw/dio.h" @@ -622,6 +623,7 @@ struct dc_state { * @stream_status: Planes status on a given stream */ struct dc_stream_status stream_status[MAX_PIPES]; + /** * @phantom_streams: Stream state properties for phantoms */ @@ -645,6 +647,22 @@ struct dc_state { * @stream_count: Total phantom planes in use */ uint8_t phantom_plane_count; + + /** + * @probes: Committed absolute set of probe descriptors. + */ + struct dc_probe_state probes[MAX_PROBES]; + + /** + * @probe_status: Committed absolute set of probe results. + */ + struct dc_probe_status probe_status[MAX_PROBES]; + + /** + * @probe_count: Number of valid entries in @probes. + */ + int probe_count; + /** * @res_ctx: Persistent state of resources */ @@ -735,4 +753,39 @@ struct dc_requested_memory_qos { uint32_t max_bw_budget_in_mbps; }; +enum update_v3_flow { + UPDATE_V3_FLOW_INVALID, + UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FAST, + UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FULL, + UPDATE_V3_FLOW_NEW_CONTEXT_SEAMLESS, + UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_NEW, + UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_CURRENT, +}; + +struct pipe_split_policy_backup { + bool dynamic_odm_policy; + bool subvp_policy; + enum pipe_split_policy mpc_policy; + char force_odm[MAX_PIPES]; +}; + +struct dc_update_scratch_space { + struct dc *dc; + struct dc_surface_update *surface_updates; + int surface_count; + struct dc_stream_state *stream; + struct dc_stream_update *stream_update; + const struct dc_probe_updates *probe_updates; + bool update_v3; + bool do_clear_update_bits; + enum dc_update_type update_type; + struct dc_state *new_context; + enum update_v3_flow flow; + struct dc_state *backup_context; + struct dc_state *intermediate_context; + struct pipe_split_policy_backup intermediate_policy; + struct dc_surface_update intermediate_updates[MAX_SURFACES]; + int intermediate_count; +}; + #endif /* _CORE_TYPES_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h b/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h index 3f0161d64675..6ae7cf109565 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h @@ -66,6 +66,9 @@ struct abm_funcs { unsigned int option, unsigned int panel_inst, unsigned int pwrseq_inst); + bool (*set_abm_event)(struct abm *abm, unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int scaling_strength_map, + unsigned int panel_inst); }; #endif diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h index 68dc2d4ba7ca..ec678bd249ef 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h @@ -412,6 +412,16 @@ struct clk_mgr_funcs { struct clk_mgr *clk_mgr, struct dc_requested_memory_qos *qos); + /** + * notify_cstate_disable - Vote DCN's DF C-state policy to PMFW. + * @disable: true -> vote "disable" + * false -> vote "allow" + * Sends the message only when the cached dc_clocks.cstate_allow would + * change, then updates the cache on an OK response (idempotent no-op + * otherwise). + */ + void (*notify_cstate_disable)(struct clk_mgr *clk_mgr, bool disable); + void (*build_clock_update_for_bls)(struct clk_mgr *clk_mgr, struct dc_state *context, bool safe_to_lower, struct block_sequence_state *seq_state); diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h b/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h index 4307362749f0..02bc0010e565 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h @@ -145,12 +145,6 @@ struct dcn_hubbub_reg_state { uint32_t compbuf_ctrl; }; -struct hubbub_system_latencies { - uint32_t max_latency_ns; - uint32_t avg_latency_ns; - uint32_t min_latency_ns; -}; - struct hubbub_urgent_latency_params { uint32_t refclk_mhz; uint32_t t_win_ns; @@ -158,6 +152,12 @@ struct hubbub_urgent_latency_params { uint32_t bw_factor_x1000; }; +struct hubbub; +struct dchub_init_data; +struct dc_dcc_surface_param; +struct dc_surface_dcc_cap; +union dcn_watermark_set; +struct dml2_display_arb_regs; struct hubbub_funcs { void (*update_dchub)( struct hubbub *hubbub, @@ -274,9 +274,11 @@ struct hubbub_funcs { const struct hubbub_urgent_latency_params *params); uint32_t (*get_urgent_ramp_latency_ns)(struct hubbub *hubbub, uint32_t refclk_mhz); - void (*start_measuring_unbounded_bandwidth)( + void (*arm_measuring_out_of_order_bandwidth)( struct hubbub *hubbub); - uint32_t (*get_unbounded_bandwidth_mbps)(struct hubbub *hubbub, + void (*start_measuring_out_of_order_bandwidth)( + struct hubbub *hubbub); + uint32_t (*get_out_of_order_bandwidth_mbps)(struct hubbub *hubbub, uint32_t refclk_mhz, uint32_t *duration_ns); void (*start_measuring_in_order_bandwidth)( struct hubbub *hubbub); diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/mcif_wb.h b/drivers/gpu/drm/amd/display/dc/inc/hw/mcif_wb.h index 15cb782f129b..aae0ad7ae835 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/mcif_wb.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/mcif_wb.h @@ -27,6 +27,7 @@ #include "dc_hw_types.h" +#include "dml2_0/dml21/inc/dml_top_dchub_registers.h" enum mmhubbub_wbif_mode { PACKED_444 = 0, @@ -36,14 +37,21 @@ enum mmhubbub_wbif_mode { }; struct mcif_arb_params { - - unsigned int time_per_pixel; - unsigned int cli_watermark[4]; - unsigned int pstate_watermark[4]; - unsigned int arbitration_slice; - unsigned int slice_lines; - unsigned int max_scaled_time; - unsigned int dram_speed_change_duration; + union { + struct { + unsigned int time_per_pixel; + unsigned int cli_watermark[4]; + unsigned int pstate_watermark[4]; + unsigned int arbitration_slice; + unsigned int slice_lines; + unsigned int max_scaled_time; + unsigned int dram_speed_change_duration; + }; + struct { + struct dml2_mcif_global_register_set global_regs; + struct dml2_mcif_per_pipe_register_set inst_regs; + } dcn4x; //dcn4+ + }; }; struct mcif_irq_params { diff --git a/drivers/gpu/drm/amd/display/dc/inc/link_service.h b/drivers/gpu/drm/amd/display/dc/inc/link_service.h index addeb3e3b25a..026d28046eea 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/link_service.h +++ b/drivers/gpu/drm/amd/display/dc/inc/link_service.h @@ -155,8 +155,8 @@ struct link_service { /*************************** DPMS *************************************/ - void (*set_dpms_on)(struct dc_state *state, struct pipe_ctx *pipe_ctx); - void (*set_dpms_off)(struct pipe_ctx *pipe_ctx); + enum dc_status (*set_dpms_on)(struct dc_state *state, struct pipe_ctx *pipe_ctx); + enum dc_status (*set_dpms_off)(struct pipe_ctx *pipe_ctx); void (*resume)(struct dc_link *link); void (*blank_all_dp_displays)(struct dc *dc); void (*blank_all_edp_displays)(struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/inc/resource.h b/drivers/gpu/drm/amd/display/dc/inc/resource.h index 945cdcefb7d4..d3171e70e07c 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/resource.h +++ b/drivers/gpu/drm/amd/display/dc/inc/resource.h @@ -577,12 +577,15 @@ struct pipe_ctx *resource_find_free_secondary_pipe_legacy( const struct pipe_ctx *primary_pipe); bool resource_validate_attach_surfaces( - const struct dc_validation_set set[], - int set_count, + const struct dc_validation_set *set, const struct dc_state *old_context, struct dc_state *context, const struct resource_pool *pool); +enum dc_status resource_validate_probe_set(struct dc *dc, + const struct dc_probe_state *probes, + uint8_t probe_count); + enum dc_status resource_map_clock_resources( const struct dc *dc, struct dc_state *context, diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index 281a7c5acaca..4f18b668340c 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -497,6 +497,12 @@ static void query_hdcp_capability(enum signal_type signal, struct dc_link *link) msg22.data = link->hdcp_caps.rx_caps.raw; msg22.length = sizeof(link->hdcp_caps.rx_caps.raw); msg22.msg_id = HDCP_MESSAGE_ID_RX_CAPS; + if (link->force_to_use_aux && (signal == SIGNAL_TYPE_HDMI_TYPE_A)) { + // case with passive dongle with i2c over aux + msg22.data = &link->hdcp_caps.rx_caps.fields.version; + msg22.length = sizeof(link->hdcp_caps.rx_caps.fields.version); + msg22.msg_id = HDCP_MESSAGE_ID_HDCP2VERSION; + } } else { msg22.data = &link->hdcp_caps.rx_caps.fields.version; msg22.length = sizeof(link->hdcp_caps.rx_caps.fields.version); @@ -597,7 +603,24 @@ static bool detect_dp(struct dc_link *link, if (sink_caps->transaction_type == DDC_TRANSACTION_TYPE_I2C_OVER_AUX) { sink_caps->signal = SIGNAL_TYPE_DISPLAY_PORT; if (!detect_dp_sink_caps(link)) { - return false; + if (link->force_to_use_aux) { + sink_caps->signal = dp_passive_dongle_detection(link->ddc, sink_caps, audio_support); + link->dpcd_caps.dongle_type = sink_caps->dongle_type; + link->dpcd_caps.is_dongle_type_one = sink_caps->is_dongle_type_one; + link->dpcd_caps.dpcd_rev.raw = 0; + /* Type 1 dongles do not work with I2C over Aux and also some of + * Type 2 dongles do not support I2C over Aux well, so for these + * cases when native Aux transactons fails and I2C over Aux fails + * report that nothing is connected, as we can't tell is it bad + * DP sink or bad passive dongle. + */ + if (sink_caps->dongle_type == DISPLAY_DONGLE_NONE) + return false; + else + return true; + } else { + return false; + } } if (is_dp_branch_device(link)) @@ -623,7 +646,7 @@ static bool detect_dp(struct dc_link *link, link->dpcd_caps.sink_count.bits.SINK_COUNT = 1; /* NUTMEG requires that we use HBR, doesn't work with RBR. */ if (link->dpcd_caps.branch_dev_id == DP_BRANCH_DEVICE_ID_00001A) - link->preferred_link_setting.link_rate = LINK_RATE_HIGH; + link->wa_flags.dp_skip_rbr = true; } return true; diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c index 42e7f0e9b3ce..48b086d15ab0 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c @@ -384,7 +384,7 @@ static bool write_i2c_retimer_vga( for (size_t i = 0; i < ARRAY_SIZE(vga_data); i++) { if (!write_i2c_retimer_offset_value(link, address, vga_data[i][0], vga_data[i][1])) { - DC_LOG_ERROR("Set retimer failed, vga index: %zu\n", i); + DC_LOG_DEBUG("Set retimer failed, vga index: %zu\n", i); return false; } } @@ -405,7 +405,7 @@ static bool write_i2c_retimer_byte( return true; if (!write_i2c_retimer_offset_value(link, address, index, value)) { - DC_LOG_ERROR("Set retimer failed, 3g index: 0x%x, value: 0x%x\n", index, value); + DC_LOG_DEBUG("Set retimer failed, 3g index: 0x%x, value: 0x%x\n", index, value); return false; } @@ -421,14 +421,14 @@ static bool write_i2c_retimer_byte( if (!link_query_ddc_data( link->ddc, address, &offset, 1, &value, 1 )) { - DC_LOG_ERROR("Set retimer failed, link_query_ddc_data\n"); + DC_LOG_DEBUG("Set retimer failed, link_query_ddc_data\n"); return false; } } value |= apply_rx_tx_change; if (!write_i2c_retimer_offset_value(link, address, offset, value)) { - DC_LOG_ERROR("Set retimer failed, 3g offset: 0x%x, value: 0x%x\n", offset, value); + DC_LOG_DEBUG("Set retimer failed, 3g offset: 0x%x, value: 0x%x\n", offset, value); return false; } } @@ -449,7 +449,7 @@ static bool write_i2c_retimer_setting( uint8_t value = settings->reg_settings[i].i2c_reg_val; if (!write_i2c_retimer_byte(link, address, index, value)) { - DC_LOG_ERROR("Set retimer failed, index: %zu\n", i); + DC_LOG_DEBUG("Set retimer failed, index: %zu\n", i); return false; } } @@ -460,7 +460,7 @@ static bool write_i2c_retimer_setting( uint8_t value = settings->reg_settings_6g[i].i2c_reg_val; if (!write_i2c_retimer_byte(link, address, index, value)) { - DC_LOG_ERROR("Set retimer failed, 6g index: %zu\n", i); + DC_LOG_DEBUG("Set retimer failed, 6g index: %zu\n", i); return false; } } @@ -492,7 +492,7 @@ static bool write_i2c_default_retimer_setting( for (size_t i = 0; i < ARRAY_SIZE(data); i++) { if (!write_i2c_retimer_offset_value(link, address, data[i][0], data[i][1])) { - DC_LOG_ERROR("Set default retimer failed, index: %zu\n", i); + DC_LOG_DEBUG("Set default retimer failed, index: %zu\n", i); return false; } } @@ -524,23 +524,31 @@ static bool write_i2c_redriver_setting( ); if (!success) - DC_LOG_ERROR("Set redriver failed"); + DC_LOG_DEBUG("Set redriver failed"); return success; } +static struct link_encoder *get_link_encoder(struct pipe_ctx *pipe_ctx) +{ + struct link_encoder *link_enc + = pipe_ctx->stream->ctx->dc->config.unify_link_enc_assignment + ? pipe_ctx->link_res.dio_link_enc + : link_enc_cfg_get_link_enc(pipe_ctx->stream->link); + + ASSERT(link_enc); + return link_enc; +} + static void update_psp_stream_config(struct pipe_ctx *pipe_ctx, bool dpms_off) { struct cp_psp *cp_psp = &pipe_ctx->stream->ctx->cp_psp; - struct link_encoder *link_enc = pipe_ctx->link_res.dio_link_enc; + struct link_encoder *link_enc = get_link_encoder(pipe_ctx); struct cp_psp_stream_config config = {0}; enum dp_panel_mode panel_mode = dp_get_panel_mode(pipe_ctx->stream->link); if (cp_psp == NULL || cp_psp->funcs.update_stream_config == NULL) return; - if (!pipe_ctx->stream->ctx->dc->config.unify_link_enc_assignment) - link_enc = link_enc_cfg_get_link_enc(pipe_ctx->stream->link); - ASSERT(link_enc); if (link_enc == NULL) return; @@ -2296,6 +2304,8 @@ static enum dc_status enable_link( static bool allocate_usb4_bandwidth_for_stream(struct dc_stream_state *stream, int stream_bw) { + ASSERT(stream->sink); + struct dc_link *link = stream->sink->link; int req_bw = stream_bw; @@ -2342,6 +2352,8 @@ static bool allocate_usb4_bandwidth_for_stream(struct dc_stream_state *stream, i static bool allocate_usb4_bandwidth(struct dc_stream_state *stream) { + ASSERT(stream->sink); + bool ret; int bw = dc_bandwidth_in_kbps_from_timing(&stream->timing, @@ -2361,38 +2373,44 @@ static bool deallocate_usb4_bandwidth(struct dc_stream_state *stream) return ret; } -void link_set_dpms_off(struct pipe_ctx *pipe_ctx) +static struct vpg *get_vpg(struct pipe_ctx *pipe_ctx) { - struct dc *dc = pipe_ctx->stream->ctx->dc; - struct dc_stream_state *stream = pipe_ctx->stream; - struct dc_link *link = stream->sink->link; - struct vpg *vpg = pipe_ctx->stream_res.stream_enc->vpg; - enum dp_panel_mode panel_mode_dp = dp_get_panel_mode(link); + if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) + return pipe_ctx->stream_res.hpo_frl_stream_enc->vpg; + else if (dp_is_128b_132b_signal(pipe_ctx)) + return pipe_ctx->stream_res.hpo_dp_stream_enc->vpg; + else + return pipe_ctx->stream_res.stream_enc->vpg; +} +enum dc_status link_set_dpms_off(struct pipe_ctx *pipe_ctx) +{ DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc *dc = stream->ctx->dc; + struct dc_link *link = stream->link; + struct vpg *vpg = get_vpg(pipe_ctx); + enum dp_panel_mode panel_mode_dp = dp_get_panel_mode(link); ASSERT(is_master_pipe_for_link(link, pipe_ctx)); - if (dp_is_128b_132b_signal(pipe_ctx)) - vpg = pipe_ctx->stream_res.hpo_dp_stream_enc->vpg; - if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - vpg = pipe_ctx->stream_res.hpo_frl_stream_enc->vpg; - if (dc_is_virtual_signal(pipe_ctx->stream->signal)) - return; + if (dc_is_virtual_signal(stream->signal)) + /* No real hardware to program for virtual signals. */ + return DC_DPMS_SKIPPED_HANDSHAKE; - if (pipe_ctx->stream->sink) { - if (pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && - pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_NONE) { + if (stream->sink) { + if (stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && + stream->sink->sink_signal != SIGNAL_TYPE_NONE) { DC_LOG_DC("%s pipe_ctx dispname=%s signal=%x link=%d sink_count=%d\n", __func__, - pipe_ctx->stream->sink->edid_caps.display_name, - pipe_ctx->stream->signal, link->link_index, link->sink_count); + stream->sink->edid_caps.display_name, + stream->signal, link->link_index, link->sink_count); } } link_wait_for_unlocked(link); - if (!pipe_ctx->stream->sink->edid_caps.panel_patch.skip_avmute) { - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) + if (stream->sink && !stream->sink->edid_caps.panel_patch.skip_avmute) { + if (dc_is_hdmi_signal(stream->signal)) set_avmute(pipe_ctx, true); } @@ -2402,15 +2420,15 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) dc->hwss.blank_stream(pipe_ctx); if (pipe_ctx->link_config.dp_tunnel_settings.should_use_dp_bw_allocation) - deallocate_usb4_bandwidth(pipe_ctx->stream); + deallocate_usb4_bandwidth(stream); - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) deallocate_mst_payload(pipe_ctx); - else if (dc_is_dp_sst_signal(pipe_ctx->stream->signal) && + else if (dc_is_dp_sst_signal(stream->signal) && dp_is_128b_132b_signal(pipe_ctx)) update_sst_payload(pipe_ctx, false); - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { + if (dc_is_hdmi_signal(stream->signal)) { struct ext_hdmi_settings settings = {0}; enum engine_id eng_id = pipe_ctx->stream_res.stream_enc->id; @@ -2433,7 +2451,7 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) } } - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT && + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT && !dp_is_128b_132b_signal(pipe_ctx)) { /* In DP1.x SST mode, our encoder will go to TPS1 @@ -2443,18 +2461,18 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) * state machine. * In DP2 or MST mode, our encoder will stay video active */ - disable_link(pipe_ctx->stream->link, &pipe_ctx->link_res, pipe_ctx->stream->signal); + disable_link(link, &pipe_ctx->link_res, stream->signal); dc->hwss.disable_stream(pipe_ctx); } else { dc->hwss.disable_stream(pipe_ctx); - disable_link(pipe_ctx->stream->link, &pipe_ctx->link_res, pipe_ctx->stream->signal); + disable_link(link, &pipe_ctx->link_res, stream->signal); } edp_set_panel_assr(link, pipe_ctx, &panel_mode_dp, false); - if (pipe_ctx->stream->timing.flags.DSC) { - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (stream->timing.flags.DSC) { + if (dc_is_dp_signal(stream->signal)) link_set_dsc_enable(pipe_ctx, false); - else if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) + else if (dc_is_hdmi_frl_signal(stream->signal)) link_set_dsc_on_stream(pipe_ctx, false); } if (dp_is_128b_132b_signal(pipe_ctx)) { @@ -2468,72 +2486,72 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) /* for psp not exist case */ if (link->connector_signal == SIGNAL_TYPE_EDP && dc->debug.psp_disabled_wa) { /* reset internal save state to default since eDP is off */ - enum dp_panel_mode panel_mode = dp_get_panel_mode(pipe_ctx->stream->link); + enum dp_panel_mode panel_mode = dp_get_panel_mode(link); /* since current psp not loaded, we need to reset it to default */ link->panel_mode = panel_mode; } + + return DC_DPMS_SUCCESS; } -void link_set_dpms_on( +static enum dc_status link_set_dpms_on_pre_enable_link( struct dc_state *state, - struct pipe_ctx *pipe_ctx) + struct pipe_ctx *pipe_ctx +) { - struct dc *dc = pipe_ctx->stream->ctx->dc; - struct dc_stream_state *stream = pipe_ctx->stream; - struct dc_link *link = stream->sink->link; - enum dc_status status; - struct link_encoder *link_enc = pipe_ctx->link_res.dio_link_enc; - enum otg_out_mux_dest otg_out_dest = OUT_MUX_DIO; - struct vpg *vpg = pipe_ctx->stream_res.stream_enc->vpg; - const struct link_hwss *link_hwss = get_link_hwss(link, &pipe_ctx->link_res); - bool apply_edp_fast_boot_optimization = - pipe_ctx->stream->apply_edp_fast_boot_optimization; + // Used conditionally in ifdef'ed diagnostic builds + (void) state; DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc *dc = stream->ctx->dc; + struct dc_link *link = stream->link; + + enum otg_out_mux_dest otg_out_dest = OUT_MUX_DIO; + struct vpg *vpg = get_vpg(pipe_ctx); + const struct link_hwss *link_hwss = get_link_hwss(link, &pipe_ctx->link_res); + bool apply_edp_fast_boot_optimization = + stream->apply_edp_fast_boot_optimization; ASSERT(is_master_pipe_for_link(link, pipe_ctx)); - if (dp_is_128b_132b_signal(pipe_ctx)) - vpg = pipe_ctx->stream_res.hpo_dp_stream_enc->vpg; - if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - vpg = pipe_ctx->stream_res.hpo_frl_stream_enc->vpg; - if (dc_is_virtual_signal(pipe_ctx->stream->signal)) - return; + if (dc_is_virtual_signal(stream->signal)) + /* No real hardware to program for virtual signals. */ + return DC_DPMS_SKIPPED_HANDSHAKE; - if (pipe_ctx->stream->sink) { - if (pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && - pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_NONE) { + if (stream->sink) { + if (stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && + stream->sink->sink_signal != SIGNAL_TYPE_NONE) { DC_LOG_DC("%s pipe_ctx dispname=%s signal=%x link=%d sink_count=%d\n", __func__, - pipe_ctx->stream->sink->edid_caps.display_name, - pipe_ctx->stream->signal, + stream->sink->edid_caps.display_name, + stream->signal, link->link_index, link->sink_count); } } - link_wait_for_unlocked(stream->link); - if (!dc->config.unify_link_enc_assignment) - link_enc = link_enc_cfg_get_link_enc(link); - ASSERT(link_enc); + link_wait_for_unlocked(link); - if (!dc_is_virtual_signal(pipe_ctx->stream->signal) - && !dc_is_hdmi_frl_signal(pipe_ctx->stream->signal) + if (!dc_is_virtual_signal(stream->signal) + && !dc_is_hdmi_frl_signal(stream->signal) && !dp_is_128b_132b_signal(pipe_ctx)) { + struct link_encoder *link_enc = get_link_encoder(pipe_ctx); + if (link_enc) link_enc->funcs->setup( link_enc, - pipe_ctx->stream->signal); + stream->signal); } - pipe_ctx->stream->link->link_state_valid = true; + link->link_state_valid = true; - if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - hdmi_frl_decide_link_settings(stream, &stream->link->frl_link_settings, &pipe_ctx->dsc_padding_params); + if (dc_is_hdmi_frl_signal(stream->signal)) + hdmi_frl_decide_link_settings(stream, &link->frl_link_settings, &pipe_ctx->dsc_padding_params); if (pipe_ctx->stream_res.tg->funcs->set_out_mux) { if (dp_is_128b_132b_signal(pipe_ctx)) otg_out_dest = OUT_MUX_HPO_DP; - else if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) + else if (dc_is_hdmi_frl_signal(stream->signal)) otg_out_dest = OUT_MUX_HPO_FRL; else otg_out_dest = OUT_MUX_DIO; @@ -2542,7 +2560,7 @@ void link_set_dpms_on( link_hwss->setup_stream_attribute(pipe_ctx); - pipe_ctx->stream->apply_edp_fast_boot_optimization = false; + stream->apply_edp_fast_boot_optimization = false; // Enable VPG before building infoframe if (vpg && vpg->funcs->vpg_poweron) @@ -2551,29 +2569,31 @@ void link_set_dpms_on( resource_build_info_frame(pipe_ctx); dc->hwss.update_info_frame(pipe_ctx); - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (dc_is_dp_signal(stream->signal)) dp_trace_source_sequence(link, DPCD_SOURCE_SEQ_AFTER_UPDATE_INFO_FRAME); /* Do not touch link on seamless boot optimization. */ - if (pipe_ctx->stream->apply_seamless_boot_optimization) { - pipe_ctx->stream->dpms_off = false; + if (stream->apply_seamless_boot_optimization) { + stream->dpms_off = false; /* Still enable stream features & audio on seamless boot for DP external displays */ - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT) { + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT) { enable_stream_features(pipe_ctx); dc->hwss.enable_audio_stream(pipe_ctx); } update_psp_stream_config(pipe_ctx, false); - return; + + /* Seamless boot: hardware already enabled by BIOS; skip link training. */ + return DC_DPMS_SKIPPED_HANDSHAKE; } /* eDP lit up by bios already, no need to enable again. */ - if (pipe_ctx->stream->signal == SIGNAL_TYPE_EDP && + if (stream->signal == SIGNAL_TYPE_EDP && apply_edp_fast_boot_optimization && - !pipe_ctx->stream->timing.flags.DSC && + !stream->timing.flags.DSC && !pipe_ctx->next_odm_pipe) { - pipe_ctx->stream->dpms_off = false; + stream->dpms_off = false; update_psp_stream_config(pipe_ctx, false); if (link->is_dds) { @@ -2583,11 +2603,16 @@ void link_set_dpms_on( msleep(post_oui_delay); } - return; + /* eDP already lit by BIOS; skip standard enable steps. */ + return DC_DPMS_SKIPPED_HANDSHAKE; } - if (pipe_ctx->stream->dpms_off) - return; + if (stream->dpms_off) + /* + * Stream is configured as DPMS-off; skip link enable. + * Hardware will NOT be in a fully enabled state after this early exit. + */ + return DC_DPMS_FAILED_INCOMPLETE; /* For Dp tunneling link, a pending HPD means that we have a race condition between processing * current link and processing the pending HPD. If we enable the link now, we may end up with a @@ -2595,7 +2620,11 @@ void link_set_dpms_on( */ if (link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA && link->is_hpd_pending) { DC_LOG_DEBUG("%s, Link%d HPD is pending, not enable it.\n", __func__, link->link_index); - return; + /* + * Pending HPD on USB4 DPIA link: skip enable to avoid race condition. + * Hardware will NOT be in a fully enabled state after this early exit. + */ + return DC_DPMS_FAILED_INCOMPLETE; } /* Have to setup DSC before DIG FE and BE are connected (which happens before the @@ -2604,42 +2633,74 @@ void link_set_dpms_on( * will be automatically set at a later time when the video is enabled * (DP_VID_STREAM_EN = 1). */ - if (pipe_ctx->stream->timing.flags.DSC) { - if (dc_is_dp_signal(pipe_ctx->stream->signal) || - dc_is_virtual_signal(pipe_ctx->stream->signal)) + if (stream->timing.flags.DSC) { + if (dc_is_dp_signal(stream->signal) || + dc_is_virtual_signal(stream->signal)) link_set_dsc_enable(pipe_ctx, true); } if (link->replay_settings.config.replay_supported && !dc_is_embedded_signal(link->connector_signal)) dp_setup_replay(link, stream); - status = enable_link(state, pipe_ctx); + return DC_DPMS_SUCCESS; +} - if (status != DC_OK) { - DC_LOG_WARNING("enabling link %u failed: %d\n", - pipe_ctx->stream->link->link_index, - status); +static enum dc_status link_set_dpms_on_enable_link( + struct dc_state *state, + struct pipe_ctx *pipe_ctx +) +{ + DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc_link *link = stream->link; + const enum dc_status status = enable_link(state, pipe_ctx); - /* Abort stream enable *unless* the failure was due to - * DP link training - some DP monitors will recover and - * show the stream anyway. But MST displays can't proceed - * without link training. - */ - if ((status != DC_FAIL_DP_LINK_TRAINING && - status != DC_FAIL_HDMI_FRL_LINK_TRAINING) || - pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { - if (false == stream->link->link_status.link_active) - disable_link(stream->link, &pipe_ctx->link_res, - pipe_ctx->stream->signal); - BREAK_TO_DEBUGGER(); - return; - } + if (status == DC_OK) + return DC_DPMS_SUCCESS; + + DC_LOG_WARNING("enabling link %u failed: %d\n", link->link_index, status); + + /* Abort stream enable *unless* the failure was due to + * DP link training - some DP monitors will recover and + * show the stream anyway. But MST displays can't proceed + * without link training. + */ + switch (status) { + case DC_FAIL_DP_LINK_TRAINING: + case DC_FAIL_HDMI_FRL_LINK_TRAINING: + if (stream->signal != SIGNAL_TYPE_DISPLAY_PORT_MST) + return DC_DPMS_SUCCESS; + break; + + default: + break; } - if (pipe_ctx->stream->timing.flags.DSC && - dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - //TODO: bring HDMI FRL in line with DP - link_set_dsc_on_stream(pipe_ctx, true); + if (!link->link_status.link_active) + disable_link(link, &pipe_ctx->link_res, stream->signal); + + /* + * Link enable failed; do NOT set skip_remaining so that post_enable_link + * still runs and leaves hardware in a consistent state. + */ + return DC_DPMS_FAILED_HANDSHAKE; +} + +static enum dc_status link_set_dpms_on_post_enable_link( + struct dc_state *state, + struct pipe_ctx *pipe_ctx +) +{ + // Used conditionally in ifdef'ed diagnostic builds + (void) state; + + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc_link *link = stream->link; + struct hw_sequencer_funcs *hwss = &stream->ctx->dc->hwss; + + if (stream->timing.flags.DSC && dc_is_hdmi_frl_signal(stream->signal)) + //TODO: bring HDMI FRL in line with DP + link_set_dsc_on_stream(pipe_ctx, true); /* turn off otg test pattern if enable */ if (pipe_ctx->stream_res.tg->funcs->set_test_pattern) @@ -2651,37 +2712,37 @@ void link_set_dpms_on( * as a workaround for the incorrect value being applied * from transmitter control. */ - if (!(dc_is_virtual_signal(pipe_ctx->stream->signal) || - dc_is_hdmi_frl_signal(pipe_ctx->stream->signal) || + if (!(dc_is_virtual_signal(stream->signal) || + dc_is_hdmi_frl_signal(stream->signal) || dp_is_128b_132b_signal(pipe_ctx))) { + struct link_encoder *link_enc = get_link_encoder(pipe_ctx); - if (link_enc) - link_enc->funcs->setup( + if (link_enc) + link_enc->funcs->setup( link_enc, - pipe_ctx->stream->signal); + stream->signal); + } - } - - dc->hwss.enable_stream(pipe_ctx); + hwss->enable_stream(pipe_ctx); /* Set DPS PPS SDP (AKA "info frames") */ - if (pipe_ctx->stream->timing.flags.DSC) { - if (dc_is_dp_signal(pipe_ctx->stream->signal) || - dc_is_virtual_signal(pipe_ctx->stream->signal)) { + if (stream->timing.flags.DSC) { + if (dc_is_dp_signal(stream->signal) || + dc_is_virtual_signal(stream->signal)) { dp_set_dsc_on_rx(pipe_ctx, true); link_set_dsc_pps_packet(pipe_ctx, true, true); } } - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (dc_is_dp_signal(stream->signal)) dp_set_hblank_reduction_on_rx(pipe_ctx); if (pipe_ctx->link_config.dp_tunnel_settings.should_use_dp_bw_allocation) - allocate_usb4_bandwidth(pipe_ctx->stream); + allocate_usb4_bandwidth(stream); - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) allocate_mst_payload(pipe_ctx); - else if (dc_is_dp_sst_signal(pipe_ctx->stream->signal) && + else if (dc_is_dp_sst_signal(stream->signal) && dp_is_128b_132b_signal(pipe_ctx)) update_sst_payload(pipe_ctx, true); @@ -2690,23 +2751,64 @@ void link_set_dpms_on( * training and stream unblank resolves the corruption issue. * This is workaround. */ - if (pipe_ctx->stream->signal == SIGNAL_TYPE_EDP && + if (stream->signal == SIGNAL_TYPE_EDP && link->is_display_mux_present) msleep(20); - dc->hwss.unblank_stream(pipe_ctx, - &pipe_ctx->stream->link->cur_link_settings); + hwss->unblank_stream(pipe_ctx, + &link->cur_link_settings); if (stream->sink_patches.delay_ignore_msa > 0) msleep(stream->sink_patches.delay_ignore_msa); - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (dc_is_dp_signal(stream->signal)) enable_stream_features(pipe_ctx); update_psp_stream_config(pipe_ctx, false); - dc->hwss.enable_audio_stream(pipe_ctx); + hwss->enable_audio_stream(pipe_ctx); - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { + if (dc_is_hdmi_signal(stream->signal)) set_avmute(pipe_ctx, false); + + return DC_DPMS_SUCCESS; +} + +enum dc_status link_set_dpms_on( + struct dc_state *state, + struct pipe_ctx *pipe_ctx +) +{ + enum dc_status result = DC_DPMS_SUCCESS; + + typedef enum dc_status (*step)(struct dc_state *, struct pipe_ctx *); + const step steps[] = { + link_set_dpms_on_pre_enable_link, + link_set_dpms_on_enable_link, + link_set_dpms_on_post_enable_link, + }; + + for (size_t i = 0; i < ARRAY_SIZE(steps); i++) { + const enum dc_status step_result = steps[i](state, pipe_ctx); + + switch (step_result) { + case DC_DPMS_SUCCESS: + case DC_DPMS_FAILED_HANDSHAKE: + // Enum is ordered from "best" to "worst" results + result = max(result, step_result); + break; + + case DC_DPMS_SKIPPED_HANDSHAKE: + return step_result; + + case DC_DPMS_FAILED_INCOMPLETE: + ASSERT(false); + return step_result; + + default: + ASSERT(false); + return step_result; + } } + + return result; } diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.h b/drivers/gpu/drm/amd/display/dc/link/link_dpms.h index e8662147dd8e..6559bc04d90e 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.h +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.h @@ -27,10 +27,10 @@ #define __DC_LINK_DPMS_H__ #include "link_service.h" -void link_set_dpms_on( +enum dc_status link_set_dpms_on( struct dc_state *state, struct pipe_ctx *pipe_ctx); -void link_set_dpms_off(struct pipe_ctx *pipe_ctx); +enum dc_status link_set_dpms_off(struct pipe_ctx *pipe_ctx); void link_resume(struct dc_link *link); void link_blank_all_dp_displays(struct dc *dc); void link_blank_all_edp_displays(struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c index d2329714408a..24e3bbec15ff 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c @@ -756,8 +756,10 @@ static bool decide_dp_link_settings(struct dc_link *link, struct dc_link_setting if (req_bw > dp_link_bandwidth_kbps(link, &link->verified_link_cap)) return false; - if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) - initial_link_setting.link_rate = link->preferred_link_setting.link_rate; + if (link->wa_flags.dp_skip_rbr) { + initial_link_setting.link_rate = LINK_RATE_HIGH; + current_link_setting.link_rate = LINK_RATE_HIGH; + } /* search for the minimum link setting that: * 1. is supported according to the link training result diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_irq_handler.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_irq_handler.c index 54ce768ae6ad..da679fb7d89c 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_irq_handler.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_irq_handler.c @@ -39,7 +39,6 @@ #include "link/link_dpms.h" #include "dm_helpers.h" #include "link_dp_dpia_bw.h" -#include "link_dp_panel_replay.h" #define DC_LOGGER \ link->ctx->logger diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c index 605bf19dc4f2..04eedec8a230 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c @@ -1721,6 +1721,20 @@ bool perform_link_training_with_retries( } } + if (j == (attempts - 1)) { + DC_LOG_WARNING( + "%s: Link(%d) training attempt %u of %d failed @ rate(%d) x lane(%d) @ spread = %x : fail reason:(%d)\n", + __func__, link->link_index, (unsigned int)j + 1, attempts, + cur_link_settings.link_rate, cur_link_settings.lane_count, + cur_link_settings.link_spread, status); + } else { + DC_LOG_HW_LINK_TRAINING( + "%s: Link(%d) training attempt %u of %d failed @ rate(%d) x lane(%d) @ spread = %x : fail reason:(%d)\n", + __func__, link->link_index, (unsigned int)j + 1, attempts, + cur_link_settings.link_rate, cur_link_settings.lane_count, + cur_link_settings.link_spread, status); + } + fail_count++; dp_trace_lt_fail_count_update(link, fail_count, false); if (link->ep_type == DISPLAY_ENDPOINT_PHY) { @@ -1740,20 +1754,6 @@ bool perform_link_training_with_retries( do_fallback = false; } - if (j == (attempts - 1)) { - DC_LOG_WARNING( - "%s: Link(%d) training attempt %u of %d failed @ rate(%d) x lane(%d) @ spread = %x : fail reason:(%d)\n", - __func__, link->link_index, (unsigned int)j + 1, attempts, - cur_link_settings.link_rate, cur_link_settings.lane_count, - cur_link_settings.link_spread, status); - } else { - DC_LOG_HW_LINK_TRAINING( - "%s: Link(%d) training attempt %u of %d failed @ rate(%d) x lane(%d) @ spread = %x : fail reason:(%d)\n", - __func__, link->link_index, (unsigned int)j + 1, attempts, - cur_link_settings.link_rate, cur_link_settings.lane_count, - cur_link_settings.link_spread, status); - } - dp_disable_link_phy(link, &pipe_ctx->link_res, signal); /* Abort link training if failure due to sink being unplugged. */ diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c index baf57692bbb5..16951a9550f2 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c @@ -161,9 +161,7 @@ bool edp_set_backlight_level_nits(struct dc_link *link, if (link->is_dds && !link->dpcd_caps.panel_luminance_control) return true; - // use internal backlight control if dmub capabilities are not present - if (link->backlight_control_type == BACKLIGHT_CONTROL_VESA_AUX && - !link->dc->caps.dmub_caps.aux_backlight_support) { + if (link->backlight_control_type == BACKLIGHT_CONTROL_VESA_AUX) { uint8_t backlight_enable = 0; struct target_luminance_value *target_luminance = NULL; @@ -273,10 +271,11 @@ bool edp_backlight_enable_aux(struct dc_link *link, bool enable) if (link->is_dds) return true; - if (core_link_write_dpcd(link, DP_SOURCE_BACKLIGHT_ENABLE, - &backlight_enable, 1) != DC_OK) - return false; - + if (!link->dpcd_caps.panel_luminance_control) { + if (core_link_write_dpcd(link, DP_SOURCE_BACKLIGHT_ENABLE, + &backlight_enable, 1) != DC_OK) + return false; + } return true; } diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c index c35074177a36..2afdfc113675 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c @@ -1090,7 +1090,7 @@ void hdmi_frl_decide_link_settings(struct dc_stream_state *stream, &stream->link->frl_verified_link_cap); if (stream->link->frl_flags.force_frl_rate != 0 && - stream->link->frl_flags.force_frl_rate < stream->link->frl_verified_link_cap.frl_link_rate) { + stream->link->frl_flags.force_frl_rate <= stream->link->frl_verified_link_cap.frl_link_rate) { switch (stream->link->frl_flags.force_frl_rate) { case HDMI_FRL_LINK_RATE_3GBPS: temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_3GBPS; diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile b/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile index afc376defa0b..dddf5a9eb0b8 100644 --- a/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile @@ -50,6 +50,16 @@ MMHUBBUB_DCN35 = dcn35_mmhubbub.o AMD_DAL_MMHUBBUB_DCN35 = $(addprefix $(AMDDALPATH)/dc/mmhubbub/dcn35/,$(MMHUBBUB_DCN35)) AMD_DISPLAY_FILES += $(AMD_DAL_MMHUBBUB_DCN35) + + +############################################################################### +# DCN401 +############################################################################### +MMHUBBUB_DCN401 = dcn401_mmhubbub.o + +AMD_DAL_MMHUBBUB_DCN401 = $(addprefix $(AMDDALPATH)/dc/mmhubbub/dcn401/,$(MMHUBBUB_DCN401)) + +AMD_DISPLAY_FILES += $(AMD_DAL_MMHUBBUB_DCN401) endif ############################################################################### diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c index 6b6f80a3bfd5..5ce787632fe9 100644 --- a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c @@ -73,7 +73,7 @@ * the bufmgr status can show the progress of write back, can be used for debug purpose */ -static void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb, +void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb, struct mcif_warmup_params *params) { struct dcn30_mmhubbub *mcif_wb30 = TO_DCN30_MMHUBBUB(mcif_wb); @@ -100,7 +100,7 @@ static void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb, REG_UPDATE(MMHUBBUB_WARMUP_CONTROL_STATUS, MMHUBBUB_WARMUP_EN, false); } -static void mmhubbub32_config_mcif_buf(struct mcif_wb *mcif_wb, +void mmhubbub32_config_mcif_buf(struct mcif_wb *mcif_wb, struct mcif_buf_params *params, unsigned int dest_height) { diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h index ef15b4f1f6b9..682f3bba6d50 100644 --- a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h @@ -200,6 +200,11 @@ SF(MMHUBBUB_WARMUP_CONTROL_STATUS, MMHUBBUB_WARMUP_SW_INT_ACK, mask_sh),\ SF(MMHUBBUB_WARMUP_CONTROL_STATUS, MMHUBBUB_WARMUP_INC_ADDR, mask_sh) +void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb, + struct mcif_warmup_params *params); +void mmhubbub32_config_mcif_buf(struct mcif_wb *mcif_wb, + struct mcif_buf_params *params, + unsigned int dest_height); void dcn32_mmhubbub_construct(struct dcn30_mmhubbub *mcif_wb30, struct dc_context *ctx, diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c new file mode 100644 index 000000000000..d2845acb74c8 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +// +// Copyright 2026 Advanced Micro Devices, Inc. + +#include "dcn401_mmhubbub.h" +#include "reg_helper.h" + +#define REG(reg) \ + ((const struct dcn35_mmhubbub_registers *)(mcif_wb30->mcif_wb_regs)) \ + ->reg + +#define CTX mcif_wb30->base.ctx + +#undef FN +#define FN(reg_name, field_name) \ + ((const struct dcn401_mmhubbub_shift *)(mcif_wb30->mcif_wb_shift)) \ + ->field_name, \ + ((const struct dcn401_mmhubbub_mask *)(mcif_wb30->mcif_wb_mask)) \ + ->field_name + +static void mmhubbub401_config_mcif_arb(struct mcif_wb *mcif_wb, + struct mcif_arb_params *params) +{ + struct dcn30_mmhubbub *mcif_wb30 = TO_DCN30_MMHUBBUB(mcif_wb); + + /* Programmed by the video driver based on the CRTC timing (for DWB) */ + REG_UPDATE(MCIF_WB_ARBITRATION_CONTROL, + MCIF_WB_TIME_PER_PIXEL, params->dcn4x.inst_regs.time_per_pixel); + + /* + * Programming DWB watermark. + * Watermark to generate urgent in MCIF_WB_CLI, value is determined by MCIF_WB_CLI_WATERMARK_MASK. + * Program in ns. A formula will be provided in the pseudo code to calculate the value. + */ + /* Program urgent_watermarkA */ + REG_UPDATE_2(MCIF_WB_WATERMARK, + MCIF_WB_CLI_WATERMARK_MASK, 0x0, + MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent); + /* Program urgent_watermarkB */ + REG_UPDATE_2(MCIF_WB_WATERMARK, + MCIF_WB_CLI_WATERMARK_MASK, 0x1, + MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent); + /* Program urgent_watermarkC */ + REG_UPDATE_2(MCIF_WB_WATERMARK, + MCIF_WB_CLI_WATERMARK_MASK, 0x2, + MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent); + /* Program urgent_watermarkD */ + REG_UPDATE_2(MCIF_WB_WATERMARK, + MCIF_WB_CLI_WATERMARK_MASK, 0x3, + MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent); + + /* Programming UCLK P-State watermark */ + /* Program nbp_state_change_watermarkA */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x0, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate); + /* Program nbp_state_change_watermarkB */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x1, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate); + /* Program nbp_state_change_watermarkC */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x2, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate); + /* Program nbp_state_change_watermarkD */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x3, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate); + + /* Programming FCLK P-State watermark */ + /* Program nbp_state_change_watermarkA */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x0, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].temp_read_or_ppt); + /* Program nbp_state_change_watermarkB */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x1, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].temp_read_or_ppt); + /* Program nbp_state_change_watermarkC */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x2, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].fclk_pstate); + /* Program nbp_state_change_watermarkD */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x3, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].fclk_pstate); + + /* Program max_scaled_time */ + REG_UPDATE(MULTI_LEVEL_QOS_CTRL, + MAX_SCALED_TIME_TO_URGENT, params->dcn4x.inst_regs.max_scaled_time_ns); + + /* Program slice_lines */ + REG_UPDATE(MCIF_WB_BUFMGR_VCE_CONTROL, + MCIF_WB_BUFMGR_SLICE_SIZE, params->dcn4x.inst_regs.slice_lines); + + /* Set arbitration unit for Luma/Chroma */ + /* arb_unit=2 should be chosen for more efficiency */ + /* Arbitration size, 0: 2048 bytes 1: 4096 bytes 2: 8192 Bytes */ + REG_UPDATE(MCIF_WB_ARBITRATION_CONTROL, + MCIF_WB_CLIENT_ARBITRATION_SLICE, params->dcn4x.inst_regs.arbitration_slice); +} + +static const struct mcif_wb_funcs dcn401_mmhubbub_funcs = { + .warmup_mcif = mmhubbub32_warmup_mcif, + .enable_mcif = mmhubbub2_enable_mcif, + .disable_mcif = mmhubbub2_disable_mcif, + .config_mcif_buf = mmhubbub32_config_mcif_buf, + .config_mcif_arb = mmhubbub401_config_mcif_arb, + .config_mcif_irq = mmhubbub2_config_mcif_irq, + .dump_frame = mcifwb2_dump_frame, +}; + +void dcn401_mmhubbub_construct(struct dcn30_mmhubbub *mcif_wb30, + struct dc_context *ctx, + const struct dcn35_mmhubbub_registers *mcif_wb_regs, + const struct dcn401_mmhubbub_shift *mcif_wb_shift, + const struct dcn401_mmhubbub_mask *mcif_wb_mask, + int inst) +{ + mcif_wb30->base.ctx = ctx; + + mcif_wb30->base.inst = inst; + mcif_wb30->base.funcs = &dcn401_mmhubbub_funcs; + mcif_wb30->mcif_wb_regs = (const struct dcn30_mmhubbub_registers *)mcif_wb_regs; + mcif_wb30->mcif_wb_shift = (const struct dcn30_mmhubbub_shift *)mcif_wb_shift; + mcif_wb30->mcif_wb_mask = (const struct dcn30_mmhubbub_mask *)mcif_wb_mask; +} diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h new file mode 100644 index 000000000000..fbc69a210fa7 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright 2026 Advanced Micro Devices, Inc. */ + + +#ifndef __DCN401_MMHUBBUB_H +#define __DCN401_MMHUBBUB_H + +#include "mcif_wb.h" +#include "dcn32/dcn32_mmhubbub.h" +#include "dcn35/dcn35_mmhubbub.h" + +#define MCIF_WB_REG_VARIABLE_LIST_DCN4_01 \ + MCIF_WB_REG_VARIABLE_LIST_DCN3_5; + +#define MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(mask_sh) \ + MCIF_WB_COMMON_MASK_SH_LIST_DCN3_5(mask_sh), \ + SF(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, NB_PSTATE_CHANGE_WATERMARK_TYPE, mask_sh) + +#define MCIF_WB_REG_FIELD_LIST_DCN4_01(type) \ + struct { \ + MCIF_WB_REG_FIELD_LIST_DCN3_5(type); \ + type NB_PSTATE_CHANGE_WATERMARK_TYPE; \ + } + +struct dcn401_mmhubbub_mask { + MCIF_WB_REG_FIELD_LIST_DCN4_01(uint32_t); +}; + +struct dcn401_mmhubbub_shift { + MCIF_WB_REG_FIELD_LIST_DCN4_01(uint8_t); +}; + +void dcn401_mmhubbub_construct(struct dcn30_mmhubbub *mcif_wb30, + struct dc_context *ctx, + const struct dcn35_mmhubbub_registers *mcif_wb_regs, + const struct dcn401_mmhubbub_shift *mcif_wb_shift, + const struct dcn401_mmhubbub_mask *mcif_wb_mask, + int inst); + +#endif // __DCN401_MMHUBBUB_H diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h index 0dd1f0d2eb88..0001903a04ec 100644 --- a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h @@ -8,6 +8,7 @@ #include "mcif_wb.h" #include "dcn32/dcn32_mmhubbub.h" #include "dcn35/dcn35_mmhubbub.h" +#include "dcn401/dcn401_mmhubbub.h" void dcn42_mmhubbub_set_fgcg(struct dcn30_mmhubbub *mcif_wb30, bool enabled); #endif // __DCN42_MMHUBBUB_H diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c b/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c index d7a07e29d23a..9cc6fd38da19 100644 --- a/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c +++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c @@ -1067,7 +1067,8 @@ static void program_gamut_remap( struct dcn30_mpc *mpc30, int mpcc_id, const uint16_t *regval, - uint32_t select) + uint32_t select, + enum cm_gamut_coef_format coef_format) { uint16_t selection = 0; struct color_matrices_reg gam_regs; @@ -1117,9 +1118,11 @@ static void program_gamut_remap( &gam_regs); } - //select coefficient set to use + // select coefficient set + format to use REG_SET(MPCC_GAMUT_REMAP_MODE[mpcc_id], 0, MPCC_GAMUT_REMAP_MODE, selection); + REG_SET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0, + MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format); } void mpc3_set_gamut_remap( @@ -1130,18 +1133,26 @@ void mpc3_set_gamut_remap( struct dcn30_mpc *mpc30 = TO_DCN30_MPC(mpc); int i = 0; uint32_t gamut_mode; + uint16_t hw_matrix[12]; + enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; + struct fixed31_32 abs_max_coef = {0}; if (adjust->gamut_adjust_type != GRAPHICS_GAMUT_ADJUST_TYPE_SW) - program_gamut_remap(mpc30, mpcc_id, NULL, GAMUT_REMAP_BYPASS); + program_gamut_remap(mpc30, mpcc_id, NULL, GAMUT_REMAP_BYPASS, coef_format); else { - struct fixed31_32 arr_matrix[12]; - uint16_t arr_reg_val[12]; + // take largest absolute value of coefficient in temperature matrix + // if S2D13 cannot fit value, use S3D12 + for (i = 0; i < 12; i++) { + if (dc_fixpt_le(abs_max_coef, dc_fixpt_abs(adjust->temperature_matrix[i]))) + abs_max_coef = dc_fixpt_abs(adjust->temperature_matrix[i]); + } - for (i = 0; i < 12; i++) - arr_matrix[i] = adjust->temperature_matrix[i]; + if (dc_fixpt_le(abs_max_coef, dc_fixpt_from_fraction(S2D13_MAX, DIVIDER))) + coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; + else + coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S3_12; - convert_float_matrix( - arr_reg_val, arr_matrix, 12); + convert_float_matrix(hw_matrix, adjust->temperature_matrix, coef_format, 12); //current coefficient set in use REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], MPCC_GAMUT_REMAP_MODE_CURRENT, &gamut_mode); @@ -1153,19 +1164,21 @@ void mpc3_set_gamut_remap( else gamut_mode = 1; - program_gamut_remap(mpc30, mpcc_id, arr_reg_val, gamut_mode); + program_gamut_remap(mpc30, mpcc_id, hw_matrix, gamut_mode, coef_format); } } static void read_gamut_remap(struct dcn30_mpc *mpc30, int mpcc_id, uint16_t *regval, - uint32_t *select) + uint32_t *select, + enum cm_gamut_coef_format *coef_format) { struct color_matrices_reg gam_regs; - //current coefficient set in use + //current coefficient set in use + format REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], MPCC_GAMUT_REMAP_MODE_CURRENT, select); + REG_GET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format); gam_regs.shifts.csc_c11 = mpc30->mpc_shift->MPCC_GAMUT_REMAP_C11_A; gam_regs.masks.csc_c11 = mpc30->mpc_mask->MPCC_GAMUT_REMAP_C11_A; @@ -1202,8 +1215,9 @@ void mpc3_get_gamut_remap(struct mpc *mpc, struct dcn30_mpc *mpc30 = TO_DCN30_MPC(mpc); uint16_t arr_reg_val[12] = {0}; uint32_t select; + enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; - read_gamut_remap(mpc30, mpcc_id, arr_reg_val, &select); + read_gamut_remap(mpc30, mpcc_id, arr_reg_val, &select, &coef_format); if (select == GAMUT_REMAP_BYPASS) { adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; @@ -1211,8 +1225,8 @@ void mpc3_get_gamut_remap(struct mpc *mpc, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + coef_format, ARRAY_SIZE(arr_reg_val)); } bool mpc3_program_3dlut( diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c index ce1ee2062e41..d8a4e604a34e 100644 --- a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c +++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c @@ -302,7 +302,8 @@ void mpc_program_gamut_remap( unsigned int mpcc_id, const uint16_t *regval, enum mpcc_gamut_remap_id gamut_remap_block_id, - enum mpcc_gamut_remap_mode_select mode_select) + enum mpcc_gamut_remap_mode_select mode_select, + enum cm_gamut_coef_format coef_format) { struct color_matrices_reg gamut_regs; struct dcn401_mpc *mpc401 = TO_DCN401_MPC(mpc); @@ -339,8 +340,11 @@ void mpc_program_gamut_remap( regval, &gamut_regs); - //select coefficient set to use, set A (MODE_1) or set B (MODE_2) - REG_SET(MPCC_GAMUT_REMAP_MODE[mpcc_id], 0, MPCC_GAMUT_REMAP_MODE, mode_select); + //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + format + REG_SET(MPCC_GAMUT_REMAP_MODE[mpcc_id], 0, + MPCC_GAMUT_REMAP_MODE, mode_select); + REG_SET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0, + MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format); break; case MPCC_MCM_FIRST_GAMUT_REMAP: @@ -373,9 +377,11 @@ void mpc_program_gamut_remap( regval, &gamut_regs); - //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + format REG_SET(MPCC_MCM_FIRST_GAMUT_REMAP_MODE[mpcc_id], 0, MPCC_MCM_FIRST_GAMUT_REMAP_MODE, mode_select); + REG_SET(MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0, + MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT, coef_format); break; case MPCC_MCM_SECOND_GAMUT_REMAP: @@ -408,9 +414,11 @@ void mpc_program_gamut_remap( regval, &gamut_regs); - //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + format REG_SET(MPCC_MCM_SECOND_GAMUT_REMAP_MODE[mpcc_id], 0, MPCC_MCM_SECOND_GAMUT_REMAP_MODE, mode_select); + REG_SET(MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0, + MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT, coef_format); break; default: @@ -426,19 +434,28 @@ void mpc401_set_gamut_remap( struct dcn401_mpc *mpc401 = TO_DCN401_MPC(mpc); unsigned int i = 0; uint32_t mode_select = 0; + uint16_t hw_matrix[12]; + enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; + struct fixed31_32 abs_max_coef = {0}; if (adjust->gamut_adjust_type != GRAPHICS_GAMUT_ADJUST_TYPE_SW) { /* Bypass / Disable if type is bypass or hw */ - mpc_program_gamut_remap(mpc, mpcc_id, NULL, - adjust->mpcc_gamut_remap_block_id, MPCC_GAMUT_REMAP_MODE_SELECT_0); + mpc_program_gamut_remap(mpc, mpcc_id, NULL, adjust->mpcc_gamut_remap_block_id, + MPCC_GAMUT_REMAP_MODE_SELECT_0, coef_format); } else { - struct fixed31_32 arr_matrix[12]; - uint16_t arr_reg_val[12]; + // take largest absolute value of coefficient in temperature matrix + // if S2D13 cannot fit value, use S3D12 + for (i = 0; i < 12; i++) { + if (dc_fixpt_le(abs_max_coef, dc_fixpt_abs(adjust->temperature_matrix[i]))) + abs_max_coef = dc_fixpt_abs(adjust->temperature_matrix[i]); + } - for (i = 0; i < 12; i++) - arr_matrix[i] = adjust->temperature_matrix[i]; + if (dc_fixpt_le(abs_max_coef, dc_fixpt_from_fraction(S2D13_MAX, DIVIDER))) + coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; + else + coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S3_12; - convert_float_matrix(arr_reg_val, arr_matrix, 12); + convert_float_matrix(hw_matrix, adjust->temperature_matrix, coef_format, 12); switch (adjust->mpcc_gamut_remap_block_id) { case MPCC_OGAM_GAMUT_REMAP: @@ -463,8 +480,8 @@ void mpc401_set_gamut_remap( else mode_select = MPCC_GAMUT_REMAP_MODE_SELECT_2; - mpc_program_gamut_remap(mpc, mpcc_id, arr_reg_val, - adjust->mpcc_gamut_remap_block_id, mode_select); + mpc_program_gamut_remap(mpc, mpcc_id, hw_matrix, + adjust->mpcc_gamut_remap_block_id, mode_select, coef_format); } } @@ -472,7 +489,8 @@ void mpc_read_gamut_remap(struct mpc *mpc, int mpcc_id, uint16_t *regval, enum mpcc_gamut_remap_id gamut_remap_block_id, - uint32_t *mode_select) + uint32_t *mode_select, + enum cm_gamut_coef_format *coef_format) { struct color_matrices_reg gamut_regs = {0}; struct dcn401_mpc *mpc401 = TO_DCN401_MPC(mpc); @@ -480,7 +498,10 @@ void mpc_read_gamut_remap(struct mpc *mpc, switch (gamut_remap_block_id) { case MPCC_OGAM_GAMUT_REMAP: //current coefficient set in use - REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], MPCC_GAMUT_REMAP_MODE_CURRENT, mode_select); + REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], + MPCC_GAMUT_REMAP_MODE_CURRENT, mode_select); + REG_GET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], + MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format); gamut_regs.shifts.csc_c11 = mpc401->mpc_shift->MPCC_GAMUT_REMAP_C11_A; gamut_regs.masks.csc_c11 = mpc401->mpc_mask->MPCC_GAMUT_REMAP_C11_A; @@ -503,7 +524,9 @@ void mpc_read_gamut_remap(struct mpc *mpc, case MPCC_MCM_FIRST_GAMUT_REMAP: REG_GET(MPCC_MCM_FIRST_GAMUT_REMAP_MODE[mpcc_id], - MPCC_MCM_FIRST_GAMUT_REMAP_MODE_CURRENT, mode_select); + MPCC_MCM_FIRST_GAMUT_REMAP_MODE_CURRENT, mode_select); + REG_GET(MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT[mpcc_id], + MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT, coef_format); gamut_regs.shifts.csc_c11 = mpc401->mpc_shift->MPCC_MCM_FIRST_GAMUT_REMAP_C11_A; gamut_regs.masks.csc_c11 = mpc401->mpc_mask->MPCC_MCM_FIRST_GAMUT_REMAP_C11_A; @@ -526,7 +549,9 @@ void mpc_read_gamut_remap(struct mpc *mpc, case MPCC_MCM_SECOND_GAMUT_REMAP: REG_GET(MPCC_MCM_SECOND_GAMUT_REMAP_MODE[mpcc_id], - MPCC_MCM_SECOND_GAMUT_REMAP_MODE_CURRENT, mode_select); + MPCC_MCM_SECOND_GAMUT_REMAP_MODE_CURRENT, mode_select); + REG_GET(MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT[mpcc_id], + MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT, coef_format); gamut_regs.shifts.csc_c11 = mpc401->mpc_shift->MPCC_MCM_SECOND_GAMUT_REMAP_C11_A; gamut_regs.masks.csc_c11 = mpc401->mpc_mask->MPCC_MCM_SECOND_GAMUT_REMAP_C11_A; @@ -565,8 +590,10 @@ void mpc401_get_gamut_remap(struct mpc *mpc, { uint16_t arr_reg_val[12] = {0}; uint32_t mode_select = MPCC_GAMUT_REMAP_MODE_SELECT_0; + enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; - mpc_read_gamut_remap(mpc, mpcc_id, arr_reg_val, adjust->mpcc_gamut_remap_block_id, &mode_select); + mpc_read_gamut_remap(mpc, mpcc_id, arr_reg_val, + adjust->mpcc_gamut_remap_block_id, &mode_select, &coef_format); if (mode_select == MPCC_GAMUT_REMAP_MODE_SELECT_0) { adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; @@ -574,8 +601,8 @@ void mpc401_get_gamut_remap(struct mpc *mpc, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + coef_format, ARRAY_SIZE(arr_reg_val)); } static const struct mpc_funcs dcn401_mpc_funcs = { diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h index 6d842d7b95c7..3d00d9497ed2 100644 --- a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h +++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h @@ -242,13 +242,15 @@ void mpc_program_gamut_remap( unsigned int mpcc_id, const uint16_t *regval, enum mpcc_gamut_remap_id gamut_remap_block_id, - enum mpcc_gamut_remap_mode_select mode_select); + enum mpcc_gamut_remap_mode_select mode_select, + enum cm_gamut_coef_format coef_format); void mpc_read_gamut_remap(struct mpc *mpc, int mpcc_id, uint16_t *regval, enum mpcc_gamut_remap_id gamut_remap_block_id, - uint32_t *mode_select); + uint32_t *mode_select, + enum cm_gamut_coef_format *coef_format); void mpc401_get_3dlut_fast_load_status( struct mpc *mpc, diff --git a/drivers/gpu/drm/amd/display/dc/os_types.h b/drivers/gpu/drm/amd/display/dc/os_types.h index 6af831710489..538d00105738 100644 --- a/drivers/gpu/drm/amd/display/dc/os_types.h +++ b/drivers/gpu/drm/amd/display/dc/os_types.h @@ -57,6 +57,16 @@ #include "amdgpu_dm/dc_fpu.h" #endif /* CONFIG_DRM_AMD_DC_FP */ + +/* + * On Linux this is provided by and evaluates Kconfig + * options for both built-in (=y) and module (=m) cases. Windows has no + * Kconfig, so config options are never set here and this always yields 0. + */ +#ifndef IS_ENABLED +#define IS_ENABLED(option) 0 +#endif + /* * * general debug capabilities diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c index b92d4f378d60..97ea22af5d2b 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c @@ -992,6 +992,11 @@ struct stream_encoder *dce100_find_first_free_match_stream_enc_for_link( for (i = 0; i < pool->stream_enc_count; i++) { if (!res_ctx->is_stream_enc_acquired[i] && pool->stream_enc[i]) { + /* DP/MST needs a digital encoder; skip analog/no-DP encoders */ + if (dc_is_dp_signal(stream->signal) && + (!pool->stream_enc[i]->funcs || + !pool->stream_enc[i]->funcs->dp_set_stream_attribute)) + continue; /* Store first available for MST second display * in daisy chain use case */ @@ -1014,7 +1019,7 @@ struct stream_encoder *dce100_find_first_free_match_stream_enc_for_link( * required for non DP connectors. */ - if (j >= 0 && link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) + if (j >= 0 && dc_is_dp_signal(stream->signal)) return pool->stream_enc[j]; return NULL; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.h index dd150a4b4610..bc130793348a 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.h @@ -33,7 +33,6 @@ struct dc; struct resource_pool; -struct dc_validation_set; struct resource_pool *dce100_create_resource_pool( uint8_t num_virtual_links, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce112/dce112_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dce112/dce112_resource.h index 3efc4c55d2d2..f2493945b8f4 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce112/dce112_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dce112/dce112_resource.h @@ -35,13 +35,6 @@ struct resource_pool *dce112_create_resource_pool( uint8_t num_virtual_links, struct dc *dc); -enum dc_status dce112_validate_with_context( - struct dc *dc, - const struct dc_validation_set set[], - int set_count, - struct dc_state *context, - struct dc_state *old_context); - enum dc_status dce112_validate_bandwidth( struct dc *dc, struct dc_state *context, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c index d11ab57afcdd..40fc66e64c1b 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn20/dcn20_resource.h" #include "dcn30_resource.h" @@ -2515,6 +2516,7 @@ static bool dcn30_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.dp_hdmi21_pcon_support = true; dc->caps.max_v_total = (1 << 15) - 1; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c index 4ecf448d15b6..551565ae9ef2 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn30/dcn30_resource.h" #include "dcn301_resource.h" @@ -1516,6 +1517,7 @@ static bool dcn301_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.dp_hdmi21_pcon_support = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c index ae8918a4ad3e..2574e706631d 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c @@ -27,6 +27,7 @@ #include "dcn302_resource.h" #include "dcn302/dcn302_dccg.h" #include "irq/dcn302/irq_service_dcn302.h" +#include "basics/conversion.h" #include "dcn30/dcn30_dio_link_encoder.h" #include "dcn30/dcn30_dio_stream_encoder.h" @@ -1416,6 +1417,7 @@ static bool dcn302_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.dp_hdmi21_pcon_support = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c index 75e6f4e46f60..8b24628e3e14 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c @@ -27,6 +27,7 @@ #include "dcn303_resource.h" #include "dcn303/dcn303_dccg.h" #include "irq/dcn303/irq_service_dcn303.h" +#include "basics/conversion.h" #include "dcn30/dcn30_dio_link_encoder.h" #include "dcn30/dcn30_dio_stream_encoder.h" @@ -1360,6 +1361,7 @@ static bool dcn303_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.dp_hdmi21_pcon_support = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c index e29efa452c87..8bb2b8b29cbf 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn31_resource.h" #include "dcn20/dcn20_resource.h" @@ -2123,6 +2124,7 @@ static bool dcn31_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 2; dc->caps.num_of_dpias_per_host_router = 2; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c index f50b3250dcba..372e05487847 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c @@ -33,6 +33,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn314_resource.h" #include "dcn20/dcn20_resource.h" @@ -2051,6 +2052,7 @@ static bool dcn314_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.max_disp_clock_khz_at_vmin = 650000; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c index 8297f2f04c16..6e112ef0a49a 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn315_resource.h" #include "dcn20/dcn20_resource.h" @@ -2095,6 +2096,7 @@ static bool dcn315_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->config.no_native422_support = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c index 046566ad1afe..b9d2567a4180 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn316_resource.h" #include "dcn20/dcn20_resource.h" @@ -1969,6 +1970,7 @@ static bool dcn316_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); /* read VBIOS LTTPR caps */ { diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c index 004c5690f876..cd9d2087d14e 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c @@ -32,6 +32,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn32_resource.h" #include "dcn20/dcn20_resource.h" @@ -2448,6 +2449,7 @@ static bool dcn32_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); /* Use pipe context based otg sync logic */ dc->config.use_pipe_ctx_sync_logic = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c index 53fd32249310..ff9c1379896e 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn32/dcn32_resource.h" #include "dcn321_resource.h" @@ -1939,6 +1940,7 @@ static bool dcn321_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); /* HACK: Force FRL support until BIOS is ready. */ dc->config.force_hdmi21_frl_enc_enable = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c index 5541b89b1350..1b9b6eb8b322 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c @@ -32,6 +32,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn35_resource.h" #include "dml2_0/dml2_wrapper.h" @@ -2078,6 +2079,7 @@ static bool dcn35_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 2; dc->caps.num_of_dpias_per_host_router = 2; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c index 053b4380f57e..2268ced16969 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c @@ -10,6 +10,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn351_resource.h" #include "dcn20/dcn20_resource.h" @@ -2051,6 +2052,7 @@ static bool dcn351_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 2; dc->caps.num_of_dpias_per_host_router = 2; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c index 592000cf9250..b1323e76801e 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c @@ -10,6 +10,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn36_resource.h" #include "dml2_0/dml2_wrapper.h" @@ -2048,6 +2049,7 @@ static bool dcn36_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 2; dc->caps.num_of_dpias_per_host_router = 2; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c index e3ee792fb2e8..181cb7135dfb 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c @@ -10,12 +10,14 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn401_resource.h" #include "dcn20/dcn20_resource.h" #include "dcn30/dcn30_resource.h" #include "dcn32/dcn32_resource.h" #include "dcn321/dcn321_resource.h" +#include "dcn35/dcn35_resource.h" #include "dcn10/dcn10_ipp.h" #include "dcn401/dcn401_hubbub.h" @@ -58,7 +60,7 @@ #include "dcn31/dcn31_panel_cntl.h" #include "dcn30/dcn30_dwb.h" -#include "dcn32/dcn32_mmhubbub.h" +#include "dcn401/dcn401_mmhubbub.h" #include "dcn/dcn_4_1_0_offset.h" #include "dcn/dcn_4_1_0_sh_mask.h" @@ -435,17 +437,17 @@ static const struct dcn30_dwbc_mask dwbc401_mask = { }; -#define mcif_wb_regs_dcn3_init(id)\ - MCIF_WB_COMMON_REG_LIST_DCN32_RI(id) +#define mcif_wb_regs_dcn401_init(id) \ + MCIF_WB_COMMON_REG_LIST_DCN4_01_RI(id) -static struct dcn30_mmhubbub_registers mcif_wb30_regs[1]; +static struct dcn35_mmhubbub_registers mcif_wb401_regs[1]; -static const struct dcn30_mmhubbub_shift mcif_wb30_shift = { - MCIF_WB_COMMON_MASK_SH_LIST_DCN32(__SHIFT) +static const struct dcn401_mmhubbub_shift mcif_wb401_shift = { + MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(__SHIFT) }; -static const struct dcn30_mmhubbub_mask mcif_wb30_mask = { - MCIF_WB_COMMON_MASK_SH_LIST_DCN32(_MASK) +static const struct dcn401_mmhubbub_mask mcif_wb401_mask = { + MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(_MASK) }; #define dsc_regs_init(id)\ @@ -1704,13 +1706,13 @@ static bool dcn401_mmhubbub_create(struct dc_context *ctx, struct resource_pool } #undef REG_STRUCT -#define REG_STRUCT mcif_wb30_regs - mcif_wb_regs_dcn3_init(0); +#define REG_STRUCT mcif_wb401_regs + mcif_wb_regs_dcn401_init(0); - dcn32_mmhubbub_construct(mcif_wb30, ctx, - &mcif_wb30_regs[i], - &mcif_wb30_shift, - &mcif_wb30_mask, + dcn401_mmhubbub_construct(mcif_wb30, ctx, + &mcif_wb401_regs[i], + &mcif_wb401_shift, + &mcif_wb401_mask, i); pool->mcif_wb[i] = &mcif_wb30->base; @@ -2130,6 +2132,7 @@ static bool dcn401_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); /* HACK: Force FRL support until BIOS is ready. */ dc->config.force_hdmi21_frl_enc_enable = true; dc->config.use_spl = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h index 47f82b818262..42a0986d6fe2 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h @@ -7,6 +7,7 @@ #include "core_types.h" #include "dcn32/dcn32_resource.h" +#include "dcn35/dcn35_resource.h" #include "dcn401/dcn401_hubp.h" #define TO_DCN401_RES_POOL(pool)\ @@ -622,7 +623,8 @@ int dcn401_get_power_profile(const struct dc_state *context); SR(DCHUBBUB_MEM_PWR_MODE_CTRL), \ SR(DCHUBBUB_TIMEOUT_DETECTION_CTRL1), \ SR(DCHUBBUB_TIMEOUT_DETECTION_CTRL2), \ - SR(DCHUBBUB_CTRL_STATUS) + SR(DCHUBBUB_CTRL_STATUS), \ + SR(DCHUBBUB_ARB_MALL_CNTL) /* DCCG */ @@ -659,4 +661,7 @@ int dcn401_get_power_profile(const struct dc_state *context); SR(SYMCLKC_CLOCK_ENABLE),\ SR(SYMCLKD_CLOCK_ENABLE) +#define MCIF_WB_COMMON_REG_LIST_DCN4_01_RI(inst) \ + MCIF_WB_COMMON_REG_LIST_DCN3_5_RI(inst) + #endif /* _DCN401_RESOURCE_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c index 7620da96ffc1..dba1b69137f9 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c @@ -9,6 +9,7 @@ #include "dcn42/dcn42_init.h" #include "resource.h" +#include "basics/conversion.h" #include "include/irq_service_interface.h" #include "dcn42_resource.h" @@ -400,16 +401,18 @@ static const struct dcn30_dwbc_shift dwbc401_shift = { static const struct dcn30_dwbc_mask dwbc401_mask = { DWBC_COMMON_MASK_SH_LIST_DCN30(_MASK)}; -#define mcif_wb_regs_dcn3_init(id) \ - MCIF_WB_COMMON_REG_LIST_DCN3_5_RI(id) +#define mcif_wb_regs_dcn401_init(id) \ + MCIF_WB_COMMON_REG_LIST_DCN4_01_RI(id) -static struct dcn35_mmhubbub_registers mcif_wb35_regs[1]; +static struct dcn35_mmhubbub_registers mcif_wb401_regs[1]; -static const struct dcn35_mmhubbub_shift mcif_wb35_shift = { - MCIF_WB_COMMON_MASK_SH_LIST_DCN3_5(__SHIFT)}; +static const struct dcn401_mmhubbub_shift mcif_wb401_shift = { + MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(__SHIFT) +}; -static const struct dcn35_mmhubbub_mask mcif_wb35_mask = { - MCIF_WB_COMMON_MASK_SH_LIST_DCN3_5(_MASK)}; +static const struct dcn401_mmhubbub_mask mcif_wb401_mask = { + MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(_MASK) +}; #define dsc_regs_init(id) \ DSC_REG_LIST_DCN401_RI(id) @@ -729,8 +732,8 @@ static const struct dc_debug_options debug_defaults_drv = { .clock_trace = true, .disable_pplib_clock_request = false, .ignore_pg = false, - .disable_dpp_power_gate = true, - .disable_hubp_power_gate = true, + .disable_dpp_power_gate = false, + .disable_hubp_power_gate = false, .disable_optc_power_gate = true, .disable_dsc_power_gate = false, .disable_dio_power_gate = true, @@ -762,7 +765,7 @@ static const struct dc_debug_options debug_defaults_drv = { .bits = { .dpp = true, .dsc = true,/*dscclk and dsc pg*/ - .hdmistream = false, + .hdmistream = true, .hdmichar = true, .dpstream = true, .symclk32_se = true, @@ -1768,13 +1771,13 @@ static bool dcn42_mmhubbub_create(struct dc_context *ctx, struct resource_pool * } #undef REG_STRUCT -#define REG_STRUCT mcif_wb35_regs - mcif_wb_regs_dcn3_init(0); +#define REG_STRUCT mcif_wb401_regs + mcif_wb_regs_dcn401_init(0); - dcn35_mmhubbub_construct(mcif_wb30, ctx, - &mcif_wb35_regs[i], - &mcif_wb35_shift, - &mcif_wb35_mask, + dcn401_mmhubbub_construct(mcif_wb30, ctx, + &mcif_wb401_regs[i], + &mcif_wb401_shift, + &mcif_wb401_mask, i); dcn42_mmhubbub_init(mcif_wb30, ctx); @@ -2108,6 +2111,7 @@ static bool dcn42_resource_construct( dc->caps.color.mpc.rmcm_3d_lut_caps.mem_format_support.float_fp1_5_10 = 1; dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_rgba = 1; dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_bgra = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 3; dc->caps.num_of_dpias_per_host_router = 2; @@ -2145,6 +2149,7 @@ static bool dcn42_resource_construct( dc->config.use_pipe_ctx_sync_logic = true; dc->config.dc_mode_clk_limit_support = false; dc->config.enable_windowed_mpo_odm = true; + dc->config.set_pipe_unlock_order = true; /* Need to ensure DET gets freed before allocating */ /* Use psp mailbox to enable assr */ dc->config.use_assr_psp_message = true; /* dcn42 and afterward always support external panel replay */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.h index 3e4d9b188b26..f2b98f1574ed 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.h @@ -581,6 +581,7 @@ SRI_ARR(DCHUBP_VMPG_CONFIG, HUBP, id), \ SRI_ARR(UCLK_PSTATE_FORCE, HUBPREQ, id), \ SRI_ARR(HUBP_3DLUT_DLG_PARAM, CURSOR0_, id), \ + SRI_ARR(DST_Y_DELTA_DRQ_LIMIT, HUBPREQ, id), \ HUBP_3DLUT_FL_REG_LIST_DCN401(id) struct dcn42_resource_pool { struct resource_pool base; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 60cbaf4f6fdf..dbd6267340ac 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -13,6 +13,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn42b_resource.h" #include "dcn20/dcn20_resource.h" @@ -22,6 +23,7 @@ #include "dcn35/dcn35_resource.h" #include "dcn321/dcn321_resource.h" #include "dcn401/dcn401_resource.h" +#include "dcn42/dcn42_resource.h" #include "dcn42/dcn42_resource_fpu.h" #include "dcn10/dcn10_ipp.h" @@ -116,6 +118,36 @@ #define regAPG9_APG_DBG_GEN_CONTROL 0x38ae #define regAPG9_APG_DBG_GEN_CONTROL_BASE_IDX 2 +#define regHUBP0_HUBPREQ_DEBUG_DB 0x05f8 +#define regHUBP0_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP0_HUBPREQ_DEBUG 0x05f9 +#define regHUBP0_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP1_HUBPREQ_DEBUG_DB 0x06d4 +#define regHUBP1_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP1_HUBPREQ_DEBUG 0x06d5 +#define regHUBP1_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP2_HUBPREQ_DEBUG_DB 0x07b0 +#define regHUBP2_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP2_HUBPREQ_DEBUG 0x07b1 +#define regHUBP2_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP3_HUBPREQ_DEBUG_DB 0x088c +#define regHUBP3_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP3_HUBPREQ_DEBUG 0x088d +#define regHUBP3_HUBPREQ_DEBUG_BASE_IDX 2 + +#define regDP_SYM32_ENC0_DP_SYM32_ENC_VID_CRC_CONTROL 0x3687 +#define regDP_SYM32_ENC0_DP_SYM32_ENC_VID_CRC_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC1_DP_SYM32_ENC_VID_CRC_CONTROL 0x375b +#define regDP_SYM32_ENC1_DP_SYM32_ENC_VID_CRC_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC2_DP_SYM32_ENC_VID_CRC_CONTROL 0x382f +#define regDP_SYM32_ENC2_DP_SYM32_ENC_VID_CRC_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC0_DP_SYM32_ENC_HBLANK_CONTROL 0x366b +#define regDP_SYM32_ENC0_DP_SYM32_ENC_HBLANK_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC1_DP_SYM32_ENC_HBLANK_CONTROL 0x373f +#define regDP_SYM32_ENC1_DP_SYM32_ENC_HBLANK_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC2_DP_SYM32_ENC_HBLANK_CONTROL 0x3813 +#define regDP_SYM32_ENC2_DP_SYM32_ENC_HBLANK_CONTROL_BASE_IDX 2 + enum dcn401_clk_src_array_id { DCN401_CLK_SRC_PLL0, DCN401_CLK_SRC_PLL1, @@ -235,10 +267,10 @@ static struct bios_registers bios_regs; static struct dce110_clk_src_regs clk_src_regs[5]; static const struct dce110_clk_src_shift cs_shift = { - CS_COMMON_MASK_SH_LIST_DCN3_2(__SHIFT) + CS_COMMON_MASK_SH_LIST_DCN4_0_1(__SHIFT) }; static const struct dce110_clk_src_mask cs_mask = { - CS_COMMON_MASK_SH_LIST_DCN3_2(_MASK) + CS_COMMON_MASK_SH_LIST_DCN4_0_1(_MASK) }; #define abm_regs_init(id) \ ABM_DCN42B_REG_LIST_RI(id) @@ -333,7 +365,7 @@ static const struct dcn10_link_enc_mask le_mask = { LINK_ENCODER_MASK_SH_LIST_DCN42B(_MASK)}; #define hpo_dp_stream_encoder_reg_init(id) \ - DCN42B_HPO_DP_STREAM_ENC_REG_LIST_RI(id) + DCN42_HPO_DP_STREAM_ENC_REG_LIST_RI(id) static struct dcn31_hpo_dp_stream_encoder_registers hpo_dp_stream_enc_regs[4]; @@ -461,7 +493,7 @@ static const struct dcn_optc_mask optc_mask = { OPTC_COMMON_MASK_SH_LIST_DCN42B(_MASK)}; #define hubp_regs_init(id) \ - HUBP_REG_LIST_DCN42B_RI(id) + HUBP_REG_LIST_DCN42_RI(id) static struct dcn_hubp2_registers hubp_regs[4]; @@ -783,7 +815,7 @@ static const struct dc_debug_options debug_defaults_drv = { } }, .seamless_boot_odm_combine = DML_FAIL_SOURCE_PIXEL_FORMAT, - .enable_z9_disable_interface = false, /* Allow support for the PMFW interface for disable Z9*/ + .enable_z9_disable_interface = true, /* Allow support for the PMFW interface for disable Z9*/ .minimum_z8_residency_time = 1, /* Always allow when other conditions are met */ .support_eDP1_5 = true, .use_max_lb = true, @@ -805,7 +837,7 @@ static const struct dc_debug_options debug_defaults_drv = { .disable_timeout = true, .min_disp_clk_khz = 50000, .static_screen_wait_frames = 2, - .disable_z10 = true, + .disable_z10 = false, .ignore_pg = true, .disable_stutter_for_wm_program = true, .min_deep_sleep_dcfclk_khz = 8000, @@ -1016,9 +1048,9 @@ static struct hubp *dcn42b_hubp_create( } static const struct dc_panel_config dcn42b_panel_config_defaults = { .psr = { - .disable_psr = true, + .disable_psr = false, .disallow_psrsu = true, - .disallow_replay = true, + .disallow_replay = false, }, .ilr = { .optimize_edp_link_rate = true, @@ -1884,9 +1916,7 @@ static struct resource_funcs dcn42b_res_pool_funcs = { .update_soc_for_wm_a = dcn30_update_soc_for_wm_a, .add_phantom_pipes = dcn32_add_phantom_pipes, .calculate_mall_ways_from_bytes = dcn32_calculate_mall_ways_from_bytes, -#ifdef CONFIG_DRM_AMD_DC_DML21 .prepare_mcache_programming = dcn42b_prepare_mcache_programming, -#endif .build_pipe_pix_clk_params = dcn42b_build_pipe_pix_clk_params, .get_power_profile = dcn401_get_power_profile, .get_vstartup_for_pipe = dcn401_get_vstartup_for_pipe, @@ -1988,11 +2018,10 @@ static bool dcn42b_resource_construct( dc->caps.dmcub_support = true; dc->caps.is_apu = true; dc->caps.seamless_odm = true; - dc->caps.zstate_support = false; - dc->caps.ips_support = false; + dc->caps.zstate_support = true; + dc->caps.ips_support = true; dc->caps.max_v_total = (1 << 15) - 1; dc->caps.vtotal_limited_by_fp2 = true; - dc->config.disable_ips = DMUB_IPS_DISABLE_ALL; /* Color pipeline capabilities */ dc->caps.color.dpp.dcn_arch = 1; @@ -2052,6 +2081,7 @@ static bool dcn42b_resource_construct( dc->caps.color.mpc.rmcm_3d_lut_caps.mem_format_support.float_fp1_5_10 = 1; dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_rgba = 1; dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_bgra = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 0; dc->caps.num_of_dpias_per_host_router = 0; @@ -2089,6 +2119,7 @@ static bool dcn42b_resource_construct( dc->config.use_pipe_ctx_sync_logic = true; dc->config.dc_mode_clk_limit_support = false; dc->config.enable_windowed_mpo_odm = true; + dc->config.set_pipe_unlock_order = true; /* Need to ensure DET gets freed before allocating */ /* Use psp mailbox to enable assr */ dc->config.use_assr_psp_message = true; /* dcn42 and afterward always support external panel replay */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h index 2da3e3c8304a..484ba1455468 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h @@ -254,46 +254,6 @@ SRI_ARR(STREAM_MAPPER_CONTROL, DIG, id),\ SRI_ARR(DIG_FE_AUDIO_CNTL, DIG, id) -/* HPO DP stream encoder */ -/* Not in DCN42B: - * SRI_ARR(DP_SYM32_ENC_VID_CRC_CONTROL, DP_SYM32_ENC, id), - * SRI_ARR(DP_SYM32_ENC_HBLANK_CONTROL, DP_SYM32_ENC, id), - */ -#define DCN42B_HPO_DP_STREAM_ENC_REG_LIST_RI(id) \ - SR_ARR(DP_STREAM_MAPPER_CONTROL0, id), \ - SR_ARR(DP_STREAM_MAPPER_CONTROL1, id), \ - SR_ARR(DP_STREAM_MAPPER_CONTROL2, id), \ - SR_ARR(DP_STREAM_MAPPER_CONTROL3, id), \ - SRI_ARR(DP_STREAM_ENC_CLOCK_CONTROL, DP_STREAM_ENC, id), \ - SRI_ARR(DP_STREAM_ENC_INPUT_MUX_CONTROL, DP_STREAM_ENC, id), \ - SRI_ARR(DP_STREAM_ENC_AUDIO_CONTROL, DP_STREAM_ENC, id), \ - SRI_ARR(DP_STREAM_ENC_CLOCK_RAMP_ADJUSTER_FIFO_STATUS_CONTROL0, DP_STREAM_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_PIXEL_FORMAT, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_PIXEL_FORMAT_DOUBLE_BUFFER_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA0, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA1, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA2, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA3, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA4, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA5, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA6, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA7, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA8, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA_DOUBLE_BUFFER_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_FIFO_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_STREAM_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_VBID_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL0, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL2, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL3, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL5, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL11, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_METADATA_PACKET_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_AUDIO_CONTROL0, DP_SYM32_ENC, id) - /*HPO DP link encoder regs */ #define DCN42B_HPO_DP_LINK_ENC_REG_LIST_RI(id) \ SRI_ARR(DP_LINK_ENC_CLOCK_CONTROL, DP_LINK_ENC, id), \ @@ -344,7 +304,6 @@ * DCCG_SRII(PHASE, DP_DTO, 3), * DCCG_SRII(MODULO, DP_DTO, 3), * SR(DSCCLK3_DTO_PARAM), - * SR(HDMISTREAMCLK_CNTL), * SR(SYMCLKD_CLOCK_ENABLE), * SR(SYMCLKE_CLOCK_ENABLE) */ @@ -360,6 +319,7 @@ SR(PHYBSYMCLK_CLOCK_CNTL), \ SR(PHYCSYMCLK_CLOCK_CNTL), \ SR(DPSTREAMCLK_CNTL), \ + SR(HDMISTREAMCLK_CNTL), \ SR(SYMCLK32_SE_CNTL), \ SR(SYMCLK32_LE_CNTL), \ DCCG_SRII(PIXEL_RATE_CNTL, OTG, 0), \ @@ -518,7 +478,8 @@ SRII_ARR_2(MODULO, DP_DTO, 2, index), \ SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 0, index), \ SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 1, index), \ - SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 2, index) + SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 2, index), \ + SR_ARR(OTG_PIXEL_RATE_DIV, index) /* ABM */ #define ABM_DCN42B_REG_LIST_RI(id) \ @@ -542,120 +503,6 @@ SRI_ARR(DC_ABM1_ACE_OFFSET_SLOPE_DATA, ABM, id), \ SRI_ARR(DC_ABM1_ACE_PWL_CNTL, ABM, id) -/* HUBP */ -/* Not in DCN42B: HUBPREQ_DEBUG_DB and HUBPREQ_DEBUG */ -#define HUBP_REG_LIST_DCN42B_RI(id) \ - SRI_ARR(DCN_DMDATA_VM_CNTL, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_3, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_6, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_6, HUBPREQ, id), \ - HUBP_REG_LIST_DCN_VM_RI(id), \ - SRI_ARR(PREFETCH_SETTINGS, HUBPREQ, id), \ - SRI_ARR(PREFETCH_SETTINGS_C, HUBPREQ, id), \ - SRI_ARR(DCN_VM_SYSTEM_APERTURE_LOW_ADDR, HUBPREQ, id), \ - SRI_ARR(DCN_VM_SYSTEM_APERTURE_HIGH_ADDR, HUBPREQ, id), \ - SRI_ARR(CURSOR_SETTINGS, HUBPREQ, id), \ - SRI_ARR(CURSOR_SURFACE_ADDRESS_HIGH, CURSOR0_, id), \ - SRI_ARR(CURSOR_SURFACE_ADDRESS, CURSOR0_, id), \ - SRI_ARR(CURSOR_SIZE, CURSOR0_, id), \ - SRI_ARR(CURSOR_CONTROL, CURSOR0_, id), \ - SRI_ARR(CURSOR_POSITION, CURSOR0_, id), \ - SRI_ARR(CURSOR_HOT_SPOT, CURSOR0_, id), \ - SRI_ARR(CURSOR_DST_OFFSET, CURSOR0_, id), \ - SRI_ARR(DMDATA_ADDRESS_HIGH, CURSOR0_, id), \ - SRI_ARR(DMDATA_ADDRESS_LOW, CURSOR0_, id), \ - SRI_ARR(DMDATA_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_SW_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_QOS_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_SW_DATA, CURSOR0_, id), \ - SRI_ARR(DMDATA_STATUS, CURSOR0_, id), \ - SRI_ARR(FLIP_PARAMETERS_0, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_1, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_2, HUBPREQ, id), \ - SRI_ARR(DCN_CUR1_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_CUR1_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCSURF_FLIP_CONTROL2, HUBPREQ, id), \ - SRI_ARR(VMID_SETTINGS_0, HUBPREQ, id), \ - SRI_ARR(DCHUBP_CNTL, HUBP, id), \ - SRI_ARR(DCSURF_ADDR_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_TILING_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_SURFACE_PITCH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_PITCH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_FLIP_CONTROL, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_DIMENSION, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_START, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_DIMENSION, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_START, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_DIMENSION_C, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_START_C, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_DIMENSION_C, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_START_C, HUBP, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_CONTROL, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_FLIP_INTERRUPT, HUBPREQ, id), \ - SRI_ARR(HUBPRET_CONTROL, HUBPRET, id), \ - SRI_ARR(HUBPRET_READ_LINE_STATUS, HUBPRET, id), \ - SRI_ARR(DCN_EXPANSION_MODE, HUBPREQ, id), \ - SRI_ARR(DCHUBP_REQ_SIZE_CONFIG, HUBP, id), \ - SRI_ARR(DCHUBP_REQ_SIZE_CONFIG_C, HUBP, id), \ - SRI_ARR(BLANK_OFFSET_0, HUBPREQ, id), \ - SRI_ARR(BLANK_OFFSET_1, HUBPREQ, id), \ - SRI_ARR(DST_DIMENSIONS, HUBPREQ, id), \ - SRI_ARR(DST_AFTER_SCALER, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_0, HUBPREQ, id), \ - SRI_ARR(REF_FREQ_TO_PIX_FREQ, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_1, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_3, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(PER_LINE_DELIVERY_PRE, HUBPREQ, id), \ - SRI_ARR(PER_LINE_DELIVERY, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_2, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_6, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_7, HUBPREQ, id), \ - SRI_ARR(DCN_TTU_QOS_WM, HUBPREQ, id), \ - SRI_ARR(DCN_GLOBAL_TTU_CNTL, HUBPREQ, id), \ - SRI_ARR(DCN_SURF0_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_SURF0_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCN_SURF1_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_SURF1_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCN_CUR0_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_CUR0_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(HUBP_CLK_CNTL, HUBP, id), \ - SRI_ARR(HUBPRET_READ_LINE_VALUE, HUBPRET, id), \ - SRI_ARR(DCHUBP_MALL_CONFIG, HUBP, id), \ - SRI_ARR(DCHUBP_VMPG_CONFIG, HUBP, id), \ - SRI_ARR(UCLK_PSTATE_FORCE, HUBPREQ, id), \ - SRI_ARR(HUBP_3DLUT_DLG_PARAM, CURSOR0_, id), \ - HUBP_3DLUT_FL_REG_LIST_DCN401(id) struct dcn42b_resource_pool { struct resource_pool base; }; diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c index 89f7ccd7f81f..0c8e652c3532 100644 --- a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c @@ -269,6 +269,22 @@ void dcn401_update_soc_bb_with_values_from_software_policy(struct dml2_soc_bb *s if (dc->bb_overrides.sr_enter_plus_exit_z8_time_ns) soc_bb->power_management_parameters.z8_stutter_enter_plus_exit_latency_us = dc->bb_overrides.sr_enter_plus_exit_z8_time_ns / 1000.0; + + /* Override per-dpm derates based on a custom derate table. + * Global derate value will be used for derates that aren't populated + * 3 derates for a single DPM level: + * bits 0-7: dram_derate_percent_pixel + * bits 8-15: fclk_derate_percent + * bits 16-23: dcfclk_derate_percent + */ + for (unsigned int i = 0; i < dc->debug.dml21_custom_derate_num_dpms; i++) { + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dram_derate_percent_pixel[i] + = dc->debug.dml21_custom_derate_at_dpm[i] & 0xFF; + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.fclk_derate_percent[i] + = (dc->debug.dml21_custom_derate_at_dpm[i] >> 8) & 0xFF; + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dcfclk_derate_percent[i] + = (dc->debug.dml21_custom_derate_at_dpm[i] >> 16) & 0xFF; + } } static void apply_soc_bb_updates(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config) diff --git a/drivers/gpu/drm/amd/display/dc/sspl/dc_spl.c b/drivers/gpu/drm/amd/display/dc/sspl/dc_spl.c index d8aebaff7c3f..1f1318769d5b 100644 --- a/drivers/gpu/drm/amd/display/dc/sspl/dc_spl.c +++ b/drivers/gpu/drm/amd/display/dc/sspl/dc_spl.c @@ -9,7 +9,6 @@ #define IDENTITY_RATIO(ratio) (SPL_NAMESPACE(spl_fixpt_u3d19(ratio)) == (1 << 19)) #define MIN_VIEWPORT_SIZE 12 - static bool spl_is_yuv420(enum spl_pixel_format format) { if ((format >= SPL_PIXEL_FORMAT_420BPP8) && @@ -19,6 +18,15 @@ static bool spl_is_yuv420(enum spl_pixel_format format) return false; } +static bool spl_is_yuv422(enum spl_pixel_format format) +{ + if ((format >= SPL_PIXEL_FORMAT_422BPP8) && + (format <= SPL_PIXEL_FORMAT_422BPP12)) + return true; + + return false; +} + static bool spl_is_rgb8(enum spl_pixel_format format) { if (format == SPL_PIXEL_FORMAT_ARGB8888) @@ -468,6 +476,12 @@ static void spl_calculate_scaling_ratios(struct spl_in *spl_in, if (spl_is_yuv420(spl_in->basic_in.format)) { spl_scratch->scl_data.ratios.horz_c.value /= 2; spl_scratch->scl_data.ratios.vert_c.value /= 2; + } else if (spl_is_yuv422(spl_in->basic_in.format)) { + if (spl_in->basic_in.rotation == SPL_ROTATION_ANGLE_90 || + spl_in->basic_in.rotation == SPL_ROTATION_ANGLE_270) + spl_scratch->scl_data.ratios.vert_c.value /= 2; + else + spl_scratch->scl_data.ratios.horz_c.value /= 2; } spl_scratch->scl_data.ratios.horz = spl_fixpt_truncate( spl_scratch->scl_data.ratios.horz, 19); @@ -612,7 +626,8 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, struct spl_rect recout_clip_in_recout_dst; struct spl_rect overlap_in_active_timing; struct spl_rect odm_slice = calculate_odm_slice_in_timing_active(spl_in); - int vpc_div = spl_is_subsampled_format(spl_in->basic_in.format) ? 2 : 1; + int vp_hc_div = spl_is_subsampled_format(spl_in->basic_in.format) ? 2 : 1; + int vp_vc_div = spl_is_yuv420(spl_in->basic_in.format) ? 2 : 1; bool orthogonal_rotation, flip_vert_scan_dir, flip_horz_scan_dir; struct spl_fixed31_32 init_adj_h = spl_fixpt_zero; struct spl_fixed31_32 init_adj_v = spl_fixpt_zero; @@ -667,6 +682,7 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, if (orthogonal_rotation) { spl_swap(src.width, src.height); spl_swap(flip_vert_scan_dir, flip_horz_scan_dir); + spl_swap(vp_hc_div, vp_vc_div); spl_swap(init_adj_h, init_adj_v); } @@ -685,7 +701,7 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, flip_horz_scan_dir, recout_clip_in_recout_dst.x, spl_scratch->scl_data.recout.width, - src.width / vpc_div, + src.width / vp_hc_div, spl_scratch->scl_data.taps.h_taps_c, spl_scratch->scl_data.ratios.horz_c, init_adj_h, @@ -707,7 +723,7 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, flip_vert_scan_dir, recout_clip_in_recout_dst.y, spl_scratch->scl_data.recout.height, - src.height / vpc_div, + src.height / vp_vc_div, spl_scratch->scl_data.taps.v_taps_c, spl_scratch->scl_data.ratios.vert_c, init_adj_v, @@ -719,12 +735,13 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, spl_swap(spl_scratch->scl_data.viewport.width, spl_scratch->scl_data.viewport.height); spl_swap(spl_scratch->scl_data.viewport_c.x, spl_scratch->scl_data.viewport_c.y); spl_swap(spl_scratch->scl_data.viewport_c.width, spl_scratch->scl_data.viewport_c.height); + spl_swap(vp_hc_div, vp_vc_div); } spl_scratch->scl_data.viewport.x += src.x; spl_scratch->scl_data.viewport.y += src.y; - SPL_ASSERT(src.x % vpc_div == 0 && src.y % vpc_div == 0); - spl_scratch->scl_data.viewport_c.x += src.x / vpc_div; - spl_scratch->scl_data.viewport_c.y += src.y / vpc_div; + SPL_ASSERT(src.x % vp_hc_div == 0 && src.y % vp_vc_div == 0); + spl_scratch->scl_data.viewport_c.x += src.x / vp_hc_div; + spl_scratch->scl_data.viewport_c.y += src.y / vp_vc_div; } static void spl_handle_3d_recout(struct spl_in *spl_in, struct spl_rect *recout) @@ -760,6 +777,7 @@ static enum scl_mode spl_get_dscl_mode(const struct spl_in *spl_in, const struct spl_scaler_data *data, bool enable_isharp, bool enable_easf) { + (void)enable_easf; const long long one = spl_fixpt_one.value; enum spl_pixel_format pixel_format = spl_in->basic_in.format; @@ -894,7 +912,8 @@ static bool spl_get_isharp_en(struct spl_in *spl_in, static void spl_get_taps_non_adaptive_scaler( struct spl_scratch *spl_scratch, const struct spl_taps *in_taps, - bool is_subsampled) + bool is_horz_subsampled, + bool is_vert_subsampled) { bool check_max_downscale = false; @@ -959,11 +978,10 @@ static void spl_get_taps_non_adaptive_scaler( spl_scratch->scl_data.taps.h_taps = 1; if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert)) spl_scratch->scl_data.taps.v_taps = 1; - if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c) && !is_subsampled) + if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c) && !is_horz_subsampled) spl_scratch->scl_data.taps.h_taps_c = 1; - if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c) && !is_subsampled) + if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c) && !is_vert_subsampled) spl_scratch->scl_data.taps.v_taps_c = 1; - } /* Calculate optimal number of taps */ @@ -977,12 +995,13 @@ static bool spl_get_optimal_number_of_taps( unsigned int min_taps_y, min_taps_c; enum lb_memory_config lb_config; bool skip_easf = false; - bool is_subsampled = spl_is_subsampled_format(spl_in->basic_in.format); + bool is_horz_subsampled = spl_is_subsampled_format(spl_in->basic_in.format); + bool is_vert_subsampled = spl_is_yuv420(spl_in->basic_in.format); if (spl_scratch->scl_data.viewport.width > spl_scratch->scl_data.h_active && max_downscale_src_width != 0 && spl_scratch->scl_data.viewport.width > max_downscale_src_width) { - spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_subsampled); + spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_horz_subsampled, is_vert_subsampled); *enable_easf_v = false; *enable_easf_h = false; *enable_isharp = false; @@ -991,7 +1010,7 @@ static bool spl_get_optimal_number_of_taps( /* Disable adaptive scaler and sharpener when integer scaling is enabled */ if (spl_in->scaling_quality.integer_scaling) { - spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_subsampled); + spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_horz_subsampled, is_vert_subsampled); *enable_easf_v = false; *enable_easf_h = false; *enable_isharp = false; @@ -1007,15 +1026,15 @@ static bool spl_get_optimal_number_of_taps( * taps = 4 for upscaling */ if (skip_easf) { - spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_subsampled); + spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_horz_subsampled, is_vert_subsampled); } else { - if (spl_is_video_format(spl_in->basic_in.format)) { + if (spl_is_subsampled_format(spl_in->basic_in.format)) { spl_scratch->scl_data.taps.h_taps = 6; spl_scratch->scl_data.taps.v_taps = 6; spl_scratch->scl_data.taps.h_taps_c = 4; spl_scratch->scl_data.taps.v_taps_c = 4; - } else { /* RGB */ + } else { /* RGB / YUV444 */ spl_scratch->scl_data.taps.h_taps = 6; spl_scratch->scl_data.taps.v_taps = 6; spl_scratch->scl_data.taps.h_taps_c = 6; @@ -1149,10 +1168,10 @@ static bool spl_get_optimal_number_of_taps( (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert))) { spl_scratch->scl_data.taps.h_taps = 1; spl_scratch->scl_data.taps.v_taps = 1; - if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c) && !is_subsampled) + if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c) && !is_horz_subsampled) spl_scratch->scl_data.taps.h_taps_c = 1; - if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c) && !is_subsampled) + if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c) && !is_vert_subsampled) spl_scratch->scl_data.taps.v_taps_c = 1; *enable_easf_v = false; @@ -1166,11 +1185,11 @@ static bool spl_get_optimal_number_of_taps( (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert))) spl_scratch->scl_data.taps.v_taps = 1; - if ((!*enable_easf_h) && !is_subsampled && + if ((!*enable_easf_h) && !is_horz_subsampled && (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c))) spl_scratch->scl_data.taps.h_taps_c = 1; - if ((!*enable_easf_v) && !is_subsampled && + if ((!*enable_easf_v) && !is_vert_subsampled && (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c))) spl_scratch->scl_data.taps.v_taps_c = 1; @@ -1693,6 +1712,7 @@ static void spl_set_isharp_data(struct dscl_prog_data *dscl_prog_data, const struct spl_scaler_data *data, struct spl_fixed31_32 ratio, enum system_setup setup, enum scale_to_sharpness_policy scale_to_sharpness_policy) { + (void)format; /* Turn off sharpener if not required */ if (!enable_isharp) { dscl_prog_data->isharp_en = 0; @@ -1823,6 +1843,144 @@ static void spl_set_isharp_data(struct dscl_prog_data *dscl_prog_data, SPL_NAMESPACE(spl_set_blur_scale_data(dscl_prog_data, data)); } +static void determine_upsp_values(struct spl_in *spl_in, struct dscl_prog_data *dscl_prog_data) +{ + dscl_prog_data->upsp_mode = spl_in->upsp_mode; + + if (dscl_prog_data->upsp_mode == UPSP_BYPASS) { //Set all UPSP register fields to 0 if bypass + dscl_prog_data->upsp_v_num_taps = UPSP_2_TAPS; + dscl_prog_data->upsp_h_num_taps = UPSP_2_TAPS; + dscl_prog_data->upsp_boundary_mode = UPSP_BOUNDARY_EDGE; + dscl_prog_data->upsp_v_init_int = 0x0; + dscl_prog_data->upsp_v_init_frac = 0x0; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0x0; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x0; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x0; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0x0; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0x0; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x0; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x0; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0x0; + dscl_prog_data->upsp_h_init_int = 0x0; + dscl_prog_data->upsp_h_init_frac = 0x0; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0x0; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x0; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x0; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0x0; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0x0; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x0; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x0; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0x0; + dscl_prog_data->upsp_clamp_max = 0x0; + dscl_prog_data->upsp_clamp_min = 0x0; + } else { + dscl_prog_data->upsp_v_num_taps = UPSP_4_TAPS; + dscl_prog_data->upsp_h_num_taps = UPSP_4_TAPS; + dscl_prog_data->upsp_boundary_mode = UPSP_BOUNDARY_EDGE; + dscl_prog_data->upsp_clamp_max = 0xFFF;//4095 + dscl_prog_data->upsp_clamp_min = 0x0; + + if (spl_in->basic_in.cositing == CHROMA_COSITING_TOPLEFT) { //Vertical Subsampling: Co-sited + if (dscl_prog_data->upsp_v_num_taps == UPSP_4_TAPS) { + dscl_prog_data->upsp_v_init_int = 0x3; + dscl_prog_data->upsp_v_init_frac = 0x0; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x40; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0xFC; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x24; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x24; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0xFC; + } else { //2 taps + dscl_prog_data->upsp_v_init_int = 0x2; + dscl_prog_data->upsp_v_init_frac = 0x0; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0x40; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0x20; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x20; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0x00; + } + } else { //Vertical Subsampling: Interstitial + if (dscl_prog_data->upsp_v_num_taps == UPSP_4_TAPS) { + dscl_prog_data->upsp_v_init_int = 0x2; + dscl_prog_data->upsp_v_init_frac = 0x1; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0xFB; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x2F; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x19; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0xFD; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0xFD; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x19; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x2F; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0xFB; + } else { //2 taps + dscl_prog_data->upsp_v_init_int = 0x1; + dscl_prog_data->upsp_v_init_frac = 0x1; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0x28; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x18; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0x18; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x28; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0x00; + } + } + if (spl_in->basic_in.cositing == CHROMA_COSITING_LEFT || spl_in->basic_in.cositing == CHROMA_COSITING_TOPLEFT) { //Horizontal Subsampling: Co-sited + if (dscl_prog_data->upsp_h_num_taps == UPSP_4_TAPS) { + dscl_prog_data->upsp_h_init_int = 0x3; + dscl_prog_data->upsp_h_init_frac = 0x0; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x40; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0xFC; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x24; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x24; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0xFC; + } else { //2 taps + dscl_prog_data->upsp_h_init_int = 0x2; + dscl_prog_data->upsp_h_init_frac = 0x0; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0x40; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0x20; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x20; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0x00; + } + } else { //Horizontal Subsampling: Interstitial + if (dscl_prog_data->upsp_h_num_taps == UPSP_4_TAPS) { + dscl_prog_data->upsp_h_init_int = 0x2; + dscl_prog_data->upsp_h_init_frac = 0x1; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0xFB; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x2F; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x19; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0xFD; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0xFD; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x19; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x2F; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0xFB; + } else { //2 taps + dscl_prog_data->upsp_h_init_int = 0x1; + dscl_prog_data->upsp_h_init_frac = 0x1; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0x28; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x18; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0x18; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x28; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0x00; + } + } + } +} + /* Calculate recout, scaling ratio, and viewport, then get optimal number of taps */ static bool spl_calculate_number_of_taps(struct spl_in *spl_in, struct spl_scratch *spl_scratch, struct spl_out *spl_out, bool *enable_easf_v, bool *enable_easf_h, bool *enable_isharp) @@ -1865,6 +2023,8 @@ bool SPL_NAMESPACE(spl_calculate_scaler_params(struct spl_in *spl_in, struct spl bool enable_isharp = false; const struct spl_scaler_data *data = &spl_scratch.scl_data; + determine_upsp_values(spl_in, spl_out->dscl_prog_data); + res = spl_calculate_number_of_taps(spl_in, &spl_scratch, spl_out, &enable_easf_v, &enable_easf_h, &enable_isharp); diff --git a/drivers/gpu/drm/amd/display/dc/sspl/dc_spl_types.h b/drivers/gpu/drm/amd/display/dc/sspl/dc_spl_types.h index 20e4e52a77ac..b24f1ef3fe17 100644 --- a/drivers/gpu/drm/amd/display/dc/sspl/dc_spl_types.h +++ b/drivers/gpu/drm/amd/display/dc/sspl/dc_spl_types.h @@ -62,17 +62,21 @@ enum spl_pixel_format { /*video*/ SPL_PIXEL_FORMAT_420BPP8, SPL_PIXEL_FORMAT_420BPP10, + SPL_PIXEL_FORMAT_422BPP8, + SPL_PIXEL_FORMAT_422BPP10, + SPL_PIXEL_FORMAT_422BPP12, + SPL_PIXEL_FORMAT_444BPP8, + SPL_PIXEL_FORMAT_444BPP10, /*end of pixel format definition*/ SPL_PIXEL_FORMAT_GRPH_BEGIN = SPL_PIXEL_FORMAT_INDEX8, SPL_PIXEL_FORMAT_GRPH_END = SPL_PIXEL_FORMAT_FP16, SPL_PIXEL_FORMAT_SUBSAMPLED_BEGIN = SPL_PIXEL_FORMAT_420BPP8, - SPL_PIXEL_FORMAT_SUBSAMPLED_END = SPL_PIXEL_FORMAT_420BPP10, + SPL_PIXEL_FORMAT_SUBSAMPLED_END = SPL_PIXEL_FORMAT_422BPP12, SPL_PIXEL_FORMAT_VIDEO_BEGIN = SPL_PIXEL_FORMAT_420BPP8, - SPL_PIXEL_FORMAT_VIDEO_END = SPL_PIXEL_FORMAT_420BPP10, + SPL_PIXEL_FORMAT_VIDEO_END = SPL_PIXEL_FORMAT_444BPP10, SPL_PIXEL_FORMAT_INVALID, SPL_PIXEL_FORMAT_UNKNOWN }; - enum lb_memory_config { /* Enable all 3 pieces of memory */ LB_MEMORY_CONFIG_0 = 0, @@ -88,7 +92,6 @@ enum lb_memory_config { */ LB_MEMORY_CONFIG_3 = 3 }; - /* Rotation angle */ enum spl_rotation_angle { SPL_ROTATION_ANGLE_0 = 0, @@ -128,6 +131,23 @@ enum chroma_cositing { CHROMA_COSITING_COUNT }; +enum upsp_mode { + UPSP_BYPASS = 0, + UPSP_HORIZONTAL_UPSAMPLING_ONLY, + UPSP_VERTICAL_UPSAMPLING_ONLY, + UPSP_HORIZONTAL_VERTICAL_UPSAMPLING +}; + +enum upsp_num_taps { + UPSP_2_TAPS, + UPSP_4_TAPS +}; + +enum upsp_boundary_mode { + UPSP_BOUNDARY_EDGE, //Replace out of bound samples with the edge samples + UPSP_BOUNDARY_BLACK //Replace out of bound samples with black as 12bpc(0x800) +}; + // Scratch space for calculating scaler params struct spl_scaler_data { int h_active; @@ -253,7 +273,7 @@ enum isharp_en { #define ISHARP_LUT_TABLE_SIZE 32 // Below struct holds values that can be directly used to program // hardware registers. No conversion/clamping is required -struct dscl_prog_data { +struct dscl_prog_data { struct spl_rect recout; // RECOUT - set based on scl_data.recout struct mpc_size mpc_size; uint32_t dscl_mode; @@ -395,6 +415,33 @@ struct dscl_prog_data { uint32_t easf_matrix_c1; uint32_t easf_matrix_c2; uint32_t easf_matrix_c3; + // UPSP registers + uint32_t upsp_mode;//UPSP_MODE + uint32_t upsp_v_num_taps; + uint32_t upsp_v_init_int; + uint32_t upsp_v_init_frac; + uint32_t upsp_h_num_taps; + uint32_t upsp_h_init_int; + uint32_t upsp_h_init_frac; + uint32_t upsp_boundary_mode; + uint32_t upsp_v_coef_tap0_p0;//UPSP_V_COEF_P0 + uint32_t upsp_v_coef_tap1_p0; + uint32_t upsp_v_coef_tap2_p0; + uint32_t upsp_v_coef_tap3_p0; + uint32_t upsp_v_coef_tap0_p1;//UPSP_V_COEF_P1 + uint32_t upsp_v_coef_tap1_p1; + uint32_t upsp_v_coef_tap2_p1; + uint32_t upsp_v_coef_tap3_p1; + uint32_t upsp_h_coef_tap0_p0;//UPSP_H_COEF_P0 + uint32_t upsp_h_coef_tap1_p0; + uint32_t upsp_h_coef_tap2_p0; + uint32_t upsp_h_coef_tap3_p0; + uint32_t upsp_h_coef_tap0_p1;//UPSP_H_COEF_P1 + uint32_t upsp_h_coef_tap1_p1; + uint32_t upsp_h_coef_tap2_p1; + uint32_t upsp_h_coef_tap3_p1; + uint32_t upsp_clamp_max;//UPSP_CLAMP + uint32_t upsp_clamp_min; // iSharp uint32_t isharp_en; // ISHARP_EN struct isharp_noise_det isharp_noise_det; // ISHARP_NOISEDET @@ -554,6 +601,7 @@ struct spl_in { int min_viewport_size; int sdr_white_level_nits; enum sharpen_policy sharpen_policy; + enum upsp_mode upsp_mode; }; // end of SPL inputs diff --git a/drivers/gpu/drm/amd/display/dmub/dmub_srv.h b/drivers/gpu/drm/amd/display/dmub/dmub_srv.h index f4d05dcfef29..94d4b40ee16a 100644 --- a/drivers/gpu/drm/amd/display/dmub/dmub_srv.h +++ b/drivers/gpu/drm/amd/display/dmub/dmub_srv.h @@ -329,7 +329,7 @@ struct dmub_soc_fb_info { * @load_inst_const: true if DMUB should load inst const fw */ struct dmub_srv_hw_params { - struct dmub_fb *fb[DMUB_WINDOW_TOTAL]; + struct dmub_srv_fb_info *fb_info; struct dmub_soc_fb_info soc_fb_info; uint32_t psp_version; bool load_inst_const; @@ -592,9 +592,11 @@ struct dmub_srv { void *user_ctx; uint32_t fw_version; bool is_virtual; + bool no_ext_reg_access; struct dmub_fb scratch_mem_fb; struct dmub_fb ib_mem_gart; struct dmub_fb cursor_offload_fb; + const struct dmub_srv_fb_info *fb_info; volatile struct dmub_shared_state_feature_block *shared_state; volatile struct dmub_cursor_offload_v1 *cursor_offload_v1; volatile const struct dmub_fw_state *fw_state; @@ -954,14 +956,15 @@ enum dmub_status dmub_srv_get_gpint_dataout(struct dmub_srv *dmub, uint32_t *dataout); /** - * dmub_flush_buffer_mem() - Read back entire frame buffer region. + * dmub_srv_flush_buffer_mem() - Read back entire frame buffer region. * This ensures that the write from x86 has been flushed and will not * hang the DMCUB. + * @dmub: the dmub service * @fb: frame buffer to flush * * Can be called after software initialization. */ -void dmub_flush_buffer_mem(const struct dmub_fb *fb); +void dmub_srv_flush_buffer_mem(struct dmub_srv *dmub, const struct dmub_fb *fb); /** * dmub_srv_get_fw_boot_status() - Returns the DMUB boot status bits. diff --git a/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c b/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c index 7463d2ae5055..5a1a2276c432 100644 --- a/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c +++ b/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c @@ -96,7 +96,7 @@ static inline uint32_t dmub_align(uint32_t val, uint32_t factor) return (val + factor - 1) / factor * factor; } -void dmub_flush_buffer_mem(const struct dmub_fb *fb) +void dmub_srv_flush_buffer_mem(struct dmub_srv *dmub, const struct dmub_fb *fb) { const uint8_t *base = (const uint8_t *)fb->cpu_addr; uint8_t buf[64]; @@ -114,6 +114,8 @@ void dmub_flush_buffer_mem(const struct dmub_fb *fb) /* Read anything leftover into the buffer. */ if (end < fb->size) dmub_memcpy(buf, base + pos, fb->size - end); + + (void)dmub; } static const struct dmub_fw_meta_info * @@ -748,31 +750,38 @@ enum dmub_status dmub_srv_is_hw_init(struct dmub_srv *dmub, bool *is_hw_init) enum dmub_status dmub_srv_hw_init(struct dmub_srv *dmub, const struct dmub_srv_hw_params *params) { - struct dmub_fb *inst_fb = params->fb[DMUB_WINDOW_0_INST_CONST]; - struct dmub_fb *stack_fb = params->fb[DMUB_WINDOW_1_STACK]; - struct dmub_fb *data_fb = params->fb[DMUB_WINDOW_2_BSS_DATA]; - struct dmub_fb *bios_fb = params->fb[DMUB_WINDOW_3_VBIOS]; - struct dmub_fb *mail_fb = params->fb[DMUB_WINDOW_4_MAILBOX]; - struct dmub_fb *tracebuff_fb = params->fb[DMUB_WINDOW_5_TRACEBUFF]; - struct dmub_fb *fw_state_fb = params->fb[DMUB_WINDOW_6_FW_STATE]; - struct dmub_fb *shared_state_fb = params->fb[DMUB_WINDOW_SHARED_STATE]; + struct dmub_fb *inst_fb; + struct dmub_fb *stack_fb; + struct dmub_fb *data_fb; + struct dmub_fb *bios_fb; + struct dmub_fb *mail_fb; + struct dmub_fb *tracebuff_fb; + struct dmub_fb *fw_state_fb; + struct dmub_fb *shared_state_fb; struct dmub_rb_init_params rb_params, outbox0_rb_params; struct dmub_window cw0, cw1, cw2, cw3, cw4, cw5, cw6, region6; struct dmub_region inbox1, outbox1, outbox0; - uint32_t i; - if (!dmub->sw_init) return DMUB_STATUS_INVALID; - for (i = 0; i < DMUB_WINDOW_TOTAL; ++i) { - if (!params->fb[i]) { - ASSERT(0); - return DMUB_STATUS_INVALID; - } + if (!params->fb_info || params->fb_info->num_fb < DMUB_WINDOW_TOTAL) { + ASSERT(0); + return DMUB_STATUS_INVALID; } + inst_fb = ¶ms->fb_info->fb[DMUB_WINDOW_0_INST_CONST]; + stack_fb = ¶ms->fb_info->fb[DMUB_WINDOW_1_STACK]; + data_fb = ¶ms->fb_info->fb[DMUB_WINDOW_2_BSS_DATA]; + bios_fb = ¶ms->fb_info->fb[DMUB_WINDOW_3_VBIOS]; + mail_fb = ¶ms->fb_info->fb[DMUB_WINDOW_4_MAILBOX]; + tracebuff_fb = ¶ms->fb_info->fb[DMUB_WINDOW_5_TRACEBUFF]; + fw_state_fb = ¶ms->fb_info->fb[DMUB_WINDOW_6_FW_STATE]; + shared_state_fb = ¶ms->fb_info->fb[DMUB_WINDOW_SHARED_STATE]; + + dmub->fb_info = params->fb_info; + memcpy(&dmub->soc_fb_info, ¶ms->soc_fb_info, sizeof(params->soc_fb_info)); dmub->psp_version = params->psp_version; @@ -800,7 +809,7 @@ enum dmub_status dmub_srv_hw_init(struct dmub_srv *dmub, * flushed yet. This only occurs in backdoor loading. */ if (params->mem_access_type == DMUB_MEMORY_ACCESS_CPU) - dmub_flush_buffer_mem(inst_fb); + dmub_srv_flush_buffer_mem(dmub, inst_fb); if (params->fw_in_system_memory && dmub->hw_funcs.backdoor_load_zfb_mode) dmub->hw_funcs.backdoor_load_zfb_mode(dmub, &cw0, &cw1); @@ -851,10 +860,10 @@ enum dmub_status dmub_srv_hw_init(struct dmub_srv *dmub, dmub->shared_state = shared_state_fb->cpu_addr; - dmub->scratch_mem_fb = *params->fb[DMUB_WINDOW_7_SCRATCH_MEM]; - dmub->ib_mem_gart = *params->fb[DMUB_WINDOW_IB_MEM]; + dmub->scratch_mem_fb = params->fb_info->fb[DMUB_WINDOW_7_SCRATCH_MEM]; + dmub->ib_mem_gart = params->fb_info->fb[DMUB_WINDOW_IB_MEM]; - dmub->cursor_offload_fb = *params->fb[DMUB_WINDOW_CURSOR_OFFLOAD]; + dmub->cursor_offload_fb = params->fb_info->fb[DMUB_WINDOW_CURSOR_OFFLOAD]; dmub->cursor_offload_v1 = (struct dmub_cursor_offload_v1 *)dmub->cursor_offload_fb.cpu_addr; if (dmub->hw_funcs.setup_windows) @@ -1014,13 +1023,14 @@ enum dmub_status dmub_srv_wait_for_hw_pwr_up(struct dmub_srv *dmub, enum dmub_status dmub_srv_wait_for_auto_load(struct dmub_srv *dmub, uint32_t timeout_us) { + const uint32_t delay_us = 100; uint32_t i; bool hw_on = true; if (!dmub->hw_init) return DMUB_STATUS_INVALID; - for (i = 0; i <= timeout_us; i += 100) { + for (i = 0; i <= timeout_us; i += delay_us) { union dmub_fw_boot_status status = dmub->hw_funcs.get_fw_status(dmub); if (dmub->hw_funcs.is_hw_powered_up) @@ -1029,7 +1039,7 @@ enum dmub_status dmub_srv_wait_for_auto_load(struct dmub_srv *dmub, if (status.bits.dal_fw && status.bits.mailbox_rdy && hw_on) return DMUB_STATUS_OK; - udelay(100); + udelay(delay_us); } return DMUB_STATUS_TIMEOUT; @@ -1258,6 +1268,7 @@ bool dmub_srv_get_diagnostic_data(struct dmub_srv *dmub) if (!dmub || !dmub->hw_funcs.get_diagnostic_data) return false; dmub->hw_funcs.get_diagnostic_data(dmub); + return true; } diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h index f9814cf7bbdb..02bee3b1956d 100644 --- a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h @@ -328,6 +328,17 @@ bool mod_power_is_abm_active(struct mod_power *mod_power, const struct dc_link *link, unsigned int inst); +bool mod_power_is_abm_supported(struct mod_power *mod_power, + unsigned int inst); + +bool mod_power_abm_set_event(struct mod_power *mod_power, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, + unsigned int scaling_strength_map, unsigned int inst); + +bool mod_power_abm_set_strength(struct mod_power *mod_power, + unsigned int strength, + unsigned int inst); bool mod_power_set_psr_event(struct mod_power *mod_power, struct dc_stream_state *stream, bool set_event, diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c index db101fdb11f0..ee15c14a899e 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power.c +++ b/drivers/gpu/drm/amd/display/modules/power/power.c @@ -270,13 +270,11 @@ struct mod_power *mod_power_create(struct dc *dc, fail_bad_brightness_range: fail_alloc_backlight_array: for (inst = 0; inst < edp_num; inst++) - if (core_power->bl_prop[inst].backlight_lut) - kfree(core_power->bl_prop[inst].backlight_lut); + kfree(core_power->bl_prop[inst].backlight_lut); fail_construct: - for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) { - if (core_power->map[i].psr_context) - kfree(core_power->map[i].psr_context); - } + for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) + kfree(core_power->map[i].psr_context); + kfree(core_power->map); fail_alloc_map: @@ -295,8 +293,7 @@ void mod_power_destroy(struct mod_power *mod_power) MOD_POWER_TO_CORE(mod_power); for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) - if (core_power->map[i].psr_context) - kfree(core_power->map[i].psr_context); + kfree(core_power->map[i].psr_context); for (i = 0; i < core_power->num_entities; i++) if (core_power->map[i].stream) @@ -305,8 +302,7 @@ void mod_power_destroy(struct mod_power *mod_power) kfree(core_power->map); for (i = 0; i < MAX_NUM_EDP; i++) - if (core_power->bl_prop[i].backlight_lut) - kfree(core_power->bl_prop[i].backlight_lut); + kfree(core_power->bl_prop[i].backlight_lut); kfree(core_power); } diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c index b9447cb7485b..b26ceaba940d 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c @@ -716,8 +716,9 @@ void mod_power_update_backlight_on_mode_change( { struct set_backlight_level_params backlight_level_params = { 0 }; - if (link->dpcd_sink_ext_caps.bits.hdr_aux_backlight_control == 1 || - link->dpcd_sink_ext_caps.bits.sdr_aux_backlight_control == 1) + if ((link->dpcd_sink_ext_caps.bits.hdr_aux_backlight_control == 1 || + link->dpcd_sink_ext_caps.bits.sdr_aux_backlight_control == 1) && + link->backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX) dc_link_set_backlight_level_nits(link, core_power->bl_state[panel_inst].isHDR, core_power->bl_state[panel_inst].backlight_millinit, 0); @@ -746,6 +747,11 @@ static bool set_backlight_millinits_aux(struct core_power *core_power, link = dc_stream_get_link(stream); + // only use internal backlight control if dmub capabilities are not present + if (link->backlight_control_type == BACKLIGHT_CONTROL_VESA_AUX && + link->dc->caps.dmub_caps.aux_backlight_support) + return true; + return dc_link_set_backlight_level_nits(link, core_power->bl_state[inst].isHDR, backlight_millinits, transition_time_millisec); } @@ -1453,6 +1459,76 @@ bool mod_power_is_abm_active(struct mod_power *mod_power, return is_active; } +bool mod_power_is_abm_supported(struct mod_power *mod_power, + unsigned int inst) +{ + struct core_power *core_power = NULL; + struct dc *dc = NULL; + + if (mod_power == NULL) + return false; + + core_power = MOD_POWER_TO_CORE(mod_power); + dc = core_power->dc; + + // It's only implemented on dmcub. + if (dc->ctx->dmub_srv) { + if (!dmub_is_abm_supported(dc->res_pool, inst)) + return false; + } else + return false; + + return true; +} + +bool mod_power_abm_set_event(struct mod_power *mod_power, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, + unsigned int scaling_strength_map, unsigned int inst) +{ + struct core_power *core_power = NULL; + struct dc *dc = NULL; + + if (mod_power == NULL) + return false; + + core_power = MOD_POWER_TO_CORE(mod_power); + dc = core_power->dc; + + // It's only implemented on dmcub. + if (dc->ctx->dmub_srv) { + if (!dmub_set_abm_event(dc->res_pool, full_screen, trans_info, + hdr_mode, scaling_enable, scaling_strength_map, inst)) + return false; + } else + return false; + + return true; +} + +bool mod_power_abm_set_strength(struct mod_power *mod_power, + unsigned int strength, + unsigned int inst) +{ + struct core_power *core_power = NULL; + struct dc *dc = NULL; + + if (mod_power == NULL) + return false; + + core_power = MOD_POWER_TO_CORE(mod_power); + dc = core_power->dc; + + // It's only implemented on dmcub. + if (dc->ctx->dmub_srv) { + if (!dmub_set_abm_strength(dc->res_pool, strength, inst)) + return false; + } else + return false; + + return true; +} + static void fill_backlight_transform_table(struct dmcu_iram_parameters params, struct iram_table_v_2 *table) { @@ -1990,6 +2066,62 @@ bool dmub_init_abm_config(struct resource_pool *res_pool, return result; } +bool dmub_is_abm_supported(struct resource_pool *res_pool, unsigned int inst) +{ + + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; + + return true; +} + +bool dmub_set_abm_event(struct resource_pool *res_pool, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int scaling_strength_map, + unsigned int inst) +{ + bool result = false; + + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; + + if (res_pool->multiple_abms[inst]) { + if (res_pool->multiple_abms[inst]->funcs->set_abm_event) + result = res_pool->multiple_abms[inst]->funcs->set_abm_event( + res_pool->multiple_abms[inst], full_screen, trans_info, + hdr_mode, scaling_enable, scaling_strength_map, inst); + } else { + if (res_pool->abm->funcs->set_abm_event) + result = res_pool->abm->funcs->set_abm_event( + res_pool->abm, full_screen, trans_info, + hdr_mode, scaling_enable, scaling_strength_map, inst); + } + + return result; +} + +bool dmub_set_abm_strength(struct resource_pool *res_pool, + unsigned int strength, + unsigned int inst) +{ + bool result = false; + + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; + + if (res_pool->multiple_abms[inst]) { + if (res_pool->multiple_abms[inst]->funcs->set_abm_level) + result = res_pool->multiple_abms[inst]->funcs->set_abm_level( + res_pool->multiple_abms[inst], strength); + } else { + if (res_pool->abm->funcs->set_abm_level) + result = res_pool->abm->funcs->set_abm_level( + res_pool->abm, strength); + } + + return result; +} + bool dmcu_load_iram(struct dmcu *dmcu, struct dmcu_iram_parameters params) { diff --git a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h index 600da3e33126..548c8ff6ddb4 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h +++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h @@ -146,6 +146,16 @@ bool dmub_init_abm_config(struct resource_pool *res_pool, struct dmcu_iram_parameters params, unsigned int inst); +bool dmub_is_abm_supported(struct resource_pool *res_pool, + unsigned int inst); +bool dmub_set_abm_event(struct resource_pool *res_pool, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, + unsigned int scaling_strength_map, unsigned int inst); +bool dmub_set_abm_strength(struct resource_pool *res_pool, + unsigned int strength, + unsigned int inst); + void init_replay_config(struct dc_link *link, struct replay_config *pr_config); void set_replay_coasting_vtotal(struct dc_link *link, enum replay_coasting_vtotal_type type, diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h index 10396018afb3..6d0c74ebdb58 100644 --- a/drivers/gpu/drm/amd/include/amd_shared.h +++ b/drivers/gpu/drm/amd/include/amd_shared.h @@ -439,10 +439,7 @@ enum amd_dpm_forced_level; * @complete: handles IP specific changes after resume * @is_idle: returns current IP block idle status * @wait_for_idle: poll for idle - * @check_soft_reset: check soft reset the IP block - * @pre_soft_reset: pre soft reset the IP block * @soft_reset: soft reset the IP block - * @post_soft_reset: post soft reset the IP block * @set_clockgating_state: enable/disable cg for the IP block * @set_powergating_state: enable/disable pg for the IP block * @get_clockgating_state: get current clockgating status diff --git a/drivers/gpu/drm/amd/include/amdgpu_ptl.h b/drivers/gpu/drm/amd/include/amdgpu_ptl.h index 154b8da3bfa9..eead62b4b327 100644 --- a/drivers/gpu/drm/amd/include/amdgpu_ptl.h +++ b/drivers/gpu/drm/amd/include/amdgpu_ptl.h @@ -39,11 +39,18 @@ enum amdgpu_ptl_disable_source { AMDGPU_PTL_DISABLE_PROFILER, AMDGPU_PTL_DISABLE_MAX, }; + +enum amdgpu_ptl_hw_supported_state { + AMDGPU_PTL_HW_UNINIT = 0, /* Not yet initialized */ + AMDGPU_PTL_HW_SUPPORTED, /* Initialized and supported */ + AMDGPU_PTL_HW_NOT_SUPPORTED, /* Initialized and not supported */ +}; + struct amdgpu_ptl { enum amdgpu_ptl_fmt fmt1; enum amdgpu_ptl_fmt fmt2; bool enabled; - bool hw_supported; + enum amdgpu_ptl_hw_supported_state hw_supported_state; bool permanently_disabled; /* PTL disable reference counting */ atomic_t disable_ref; diff --git a/drivers/gpu/drm/amd/include/kgd_pp_interface.h b/drivers/gpu/drm/amd/include/kgd_pp_interface.h index 6371f292f2d8..606d48115301 100644 --- a/drivers/gpu/drm/amd/include/kgd_pp_interface.h +++ b/drivers/gpu/drm/amd/include/kgd_pp_interface.h @@ -24,6 +24,8 @@ #ifndef __KGD_PP_INTERFACE_H__ #define __KGD_PP_INTERFACE_H__ +#include + extern const struct amdgpu_ip_block_version pp_smu_ip_block; extern const struct amdgpu_ip_block_version smu_v11_0_ip_block; extern const struct amdgpu_ip_block_version smu_v12_0_ip_block; @@ -150,8 +152,8 @@ enum amd_pp_sensors { AMDGPU_PP_SENSOR_MEM_TEMP, AMDGPU_PP_SENSOR_VCE_POWER, AMDGPU_PP_SENSOR_UVD_POWER, - AMDGPU_PP_SENSOR_GPU_AVG_POWER, - AMDGPU_PP_SENSOR_GPU_INPUT_POWER, + AMDGPU_PP_SENSOR_GPU_AVG_POWER, /* milliwatts */ + AMDGPU_PP_SENSOR_GPU_INPUT_POWER, /* milliwatts */ AMDGPU_PP_SENSOR_SS_APU_SHARE, AMDGPU_PP_SENSOR_SS_DGPU_SHARE, AMDGPU_PP_SENSOR_STABLE_PSTATE_SCLK, diff --git a/drivers/gpu/drm/amd/include/mes_v11_api_def.h b/drivers/gpu/drm/amd/include/mes_v11_api_def.h index b06412ac8583..a8e6ccc7817c 100644 --- a/drivers/gpu/drm/amd/include/mes_v11_api_def.h +++ b/drivers/gpu/drm/amd/include/mes_v11_api_def.h @@ -329,6 +329,7 @@ union MESAPI__ADD_QUEUE { uint32_t pipe_id; uint32_t queue_id; uint32_t alignment_mode_setting; + uint32_t full_sh_mem_config_data; uint64_t unmap_flag_addr; }; @@ -358,6 +359,8 @@ union MESAPI__REMOVE_QUEUE { uint32_t tf_data; enum MES_QUEUE_TYPE queue_type; + uint64_t timestamp; + uint32_t gang_context_array_index; }; uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; @@ -516,14 +519,50 @@ union MESAPI__SET_LOGGING_BUFFER { uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; }; +enum MES_API_QUERY_MES_OPCODE { + MES_API_QUERY_MES__GET_CTX_ARRAY_SIZE, + MES_API_QUERY_MES__GET_CAPS = MES_API_QUERY_MES__GET_CTX_ARRAY_SIZE, + MES_API_QUERY_MES__CHECK_HEALTHY, + MES_API_QUERY_MES__MAX, +}; + +enum { QUERY_MES_MAX_SIZE_IN_DWORDS = 20 }; + +/** + * Obsolete + * to be removed once KMD stopped refering to it. + * use MES_API_QUERY_MES__CAPS instead +*/ +struct MES_API_QUERY_MES__CTX_ARRAY_SIZE { + uint64_t proc_ctx_array_size_addr; + uint64_t gang_ctx_array_size_addr; +}; + +struct MES_API_QUERY_MES__HEALTHY_CHECK { + uint64_t healthy_addr; +}; + +struct MES_API_QUERY_MES__CAPS { + uint64_t proc_ctx_array_size_addr; + uint64_t gang_ctx_array_size_addr; + uint64_t features_enablement_addr; +}; + union MESAPI__QUERY_MES_STATUS { struct { - union MES_API_HEADER header; - bool mes_healthy; /* 0 - not healthy, 1 - healthy */ - struct MES_API_STATUS api_status; + union MES_API_HEADER header; + enum MES_API_QUERY_MES_OPCODE subopcode; + struct MES_API_STATUS api_status; + uint64_t timestamp; + union { + struct MES_API_QUERY_MES__CTX_ARRAY_SIZE ctx_array_size; + struct MES_API_QUERY_MES__CAPS caps; + struct MES_API_QUERY_MES__HEALTHY_CHECK healthy_check; + uint32_t data[QUERY_MES_MAX_SIZE_IN_DWORDS]; + }; }; - uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; + uint32_t max_dwords_in_api[API_FRAME_SIZE_IN_DWORDS]; }; union MESAPI__PROGRAM_GDS { diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c index c09c60ad3f38..c3688b3b12cc 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c @@ -1183,20 +1183,36 @@ int amdgpu_dpm_dispatch_task(struct amdgpu_device *adev, return ret; } -int amdgpu_dpm_get_pp_table(struct amdgpu_device *adev, char **table) +static bool amdgpu_dpm_is_pp_table_allowed(struct amdgpu_device *adev) +{ + return !amdgpu_sriov_vf(adev) && + !(adev->flags & AMD_IS_APU) && + !adev->scpm_enabled; +} + +int amdgpu_dpm_get_pp_table(struct amdgpu_device *adev, char *table, + size_t size) { const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; + char *pptable = NULL; int ret = 0; - if (!table) + if ((!table && size) || (table && !size)) return -EINVAL; - if (amdgpu_sriov_vf(adev) || !pp_funcs->get_pp_table || adev->scpm_enabled) + if (!amdgpu_dpm_is_pp_table_allowed(adev) || + !pp_funcs->get_pp_table) return -EOPNOTSUPP; mutex_lock(&adev->pm.mutex); ret = pp_funcs->get_pp_table(adev->powerplay.pp_handle, - table); + &pptable); + if (ret > 0 && !pptable) { + ret = -EINVAL; + } else if (ret > 0 && table) { + ret = min_t(size_t, ret, size); + memcpy(table, pptable, ret); + } mutex_unlock(&adev->pm.mutex); return ret; @@ -1426,17 +1442,23 @@ int amdgpu_dpm_set_power_profile_mode(struct amdgpu_device *adev, return ret; } -int amdgpu_dpm_get_gpu_metrics(struct amdgpu_device *adev, void **table) +ssize_t amdgpu_dpm_get_gpu_metrics(struct amdgpu_device *adev, void *buf, + size_t size) { const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; - int ret = 0; + void *table; + ssize_t ret; if (!pp_funcs->get_gpu_metrics) return 0; mutex_lock(&adev->pm.mutex); ret = pp_funcs->get_gpu_metrics(adev->powerplay.pp_handle, - table); + &table); + if (ret > 0) { + ret = min_t(ssize_t, ret, size); + memcpy(buf, table, ret); + } mutex_unlock(&adev->pm.mutex); return ret; @@ -1703,7 +1725,8 @@ int amdgpu_dpm_set_pp_table(struct amdgpu_device *adev, if (!buf || !size) return -EINVAL; - if (amdgpu_sriov_vf(adev) || !pp_funcs->set_pp_table || adev->scpm_enabled) + if (!amdgpu_dpm_is_pp_table_allowed(adev) || + !pp_funcs->set_pp_table) return -EOPNOTSUPP; mutex_lock(&adev->pm.mutex); diff --git a/drivers/gpu/drm/amd/pm/amdgpu_pm.c b/drivers/gpu/drm/amd/pm/amdgpu_pm.c index f5a5d72b4108..4c6cdaeac5a9 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_pm.c @@ -41,8 +41,6 @@ #define DEVICE_ATTR_IS(_name) (attr_id == device_attr_id__##_name) -#define power_2_mwatt(power) (((power) >> 8) * 1000 + ((power) & 0xff)) - struct od_attribute { struct kobj_attribute attribute; struct list_head entry; @@ -595,25 +593,19 @@ static ssize_t amdgpu_get_pp_table(struct device *dev, { struct drm_device *ddev = dev_get_drvdata(dev); struct amdgpu_device *adev = drm_to_adev(ddev); - char *table = NULL; int size, ret; ret = amdgpu_pm_get_access_if_active(adev); if (ret) return ret; - size = amdgpu_dpm_get_pp_table(adev, &table); + size = amdgpu_dpm_get_pp_table(adev, buf, PAGE_SIZE - 1); amdgpu_pm_put_access(adev); if (size <= 0) return size; - if (size >= PAGE_SIZE) - size = PAGE_SIZE - 1; - - memcpy(buf, table, size); - return size; } @@ -1787,7 +1779,6 @@ static ssize_t amdgpu_get_gpu_metrics(struct device *dev, { struct drm_device *ddev = dev_get_drvdata(dev); struct amdgpu_device *adev = drm_to_adev(ddev); - void *gpu_metrics; ssize_t size = 0; int ret; @@ -1795,15 +1786,10 @@ static ssize_t amdgpu_get_gpu_metrics(struct device *dev, if (ret) return ret; - size = amdgpu_dpm_get_gpu_metrics(adev, &gpu_metrics); + size = amdgpu_dpm_get_gpu_metrics(adev, buf, PAGE_SIZE - 1); if (size <= 0) goto out; - if (size >= PAGE_SIZE) - size = PAGE_SIZE - 1; - - memcpy(buf, gpu_metrics, size); - out: amdgpu_pm_put_access(adev); @@ -2733,10 +2719,9 @@ static int default_attr_update(struct amdgpu_device *adev, struct amdgpu_device_ *states = ATTR_STATE_UNSUPPORTED; } else if (DEVICE_ATTR_IS(pp_table)) { int ret; - char *tmp = NULL; - ret = amdgpu_dpm_get_pp_table(adev, &tmp); - if (ret == -EOPNOTSUPP || !tmp) + ret = amdgpu_dpm_get_pp_table(adev, NULL, 0); + if (ret <= 0) *states = ATTR_STATE_UNSUPPORTED; else *states = ATTR_STATE_SUPPORTED; @@ -3371,7 +3356,7 @@ static int amdgpu_hwmon_get_power(struct device *dev, return r; /* convert to microwatts */ - return power_2_mwatt(query) * 1000; + return query * 1000; } static ssize_t amdgpu_hwmon_show_power_avg(struct device *dev, @@ -4912,7 +4897,7 @@ static int amdgpu_debugfs_pm_info_pp(struct seq_file *m, struct amdgpu_device *a seq_printf(m, "\t%u mV (VDDNB)\n", value); size = sizeof(uint32_t); if (!amdgpu_dpm_read_sensor(adev, AMDGPU_PP_SENSOR_GPU_AVG_POWER, (void *)&query, &size)) { - mwatt = power_2_mwatt(query); + mwatt = query; centiwatt = DIV_ROUND_CLOSEST(mwatt, 10); if (adev->flags & AMD_IS_APU) seq_printf(m, "\t%u.%02u W (average SoC including CPU)\n", centiwatt / 100, centiwatt % 100); @@ -4921,7 +4906,7 @@ static int amdgpu_debugfs_pm_info_pp(struct seq_file *m, struct amdgpu_device *a } size = sizeof(uint32_t); if (!amdgpu_dpm_read_sensor(adev, AMDGPU_PP_SENSOR_GPU_INPUT_POWER, (void *)&query, &size)) { - mwatt = power_2_mwatt(query); + mwatt = query; centiwatt = DIV_ROUND_CLOSEST(mwatt, 10); if (adev->flags & AMD_IS_APU) seq_printf(m, "\t%u.%02u W (current SoC including CPU)\n", centiwatt / 100, centiwatt % 100); diff --git a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h index c7ea29385682..8e8a052e1d53 100644 --- a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h +++ b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h @@ -487,7 +487,8 @@ int amdgpu_dpm_get_pp_num_states(struct amdgpu_device *adev, int amdgpu_dpm_dispatch_task(struct amdgpu_device *adev, enum amd_pp_task task_id, enum amd_pm_state_type *user_state); -int amdgpu_dpm_get_pp_table(struct amdgpu_device *adev, char **table); +int amdgpu_dpm_get_pp_table(struct amdgpu_device *adev, char *table, + size_t size); int amdgpu_dpm_set_fine_grain_clk_vol(struct amdgpu_device *adev, uint32_t type, long *input, @@ -517,7 +518,8 @@ int amdgpu_dpm_get_power_profile_mode(struct amdgpu_device *adev, char *buf); int amdgpu_dpm_set_power_profile_mode(struct amdgpu_device *adev, long *input, uint32_t size); -int amdgpu_dpm_get_gpu_metrics(struct amdgpu_device *adev, void **table); +ssize_t amdgpu_dpm_get_gpu_metrics(struct amdgpu_device *adev, void *buf, + size_t size); ssize_t amdgpu_dpm_get_xcp_metrics(struct amdgpu_device *adev, int xcp_id, void *table); ssize_t amdgpu_dpm_get_temp_metrics(struct amdgpu_device *adev, diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 8079da7c5335..012227c70600 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -3892,13 +3892,16 @@ static void si_notify_hw_of_powersource(void *handle) { struct amdgpu_device *adev = (struct amdgpu_device *)handle; - /* Check if the platform already manages the AC/DC switch via dedicated GPIO. */ - if (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC) - return; - - /* The SMU automatically notices DC, but needs to be notified when switching to AC. */ - if (adev->pm.ac_power) + /* + * Check if the platform already manages the AC/DC switch via dedicated GPIO. + * Otherwise SMU automatically notices DC, but needs to be notified of AC. + */ + if (adev->pm.ac_power && + (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC)) amdgpu_si_send_msg_to_smc(adev, PPSMC_MSG_RunningOnAC); + + /* Recompute clocks with updated max_limits. */ + amdgpu_legacy_dpm_compute_clocks(adev); } static PPSMC_Result si_send_msg_to_smc_with_parameter(struct amdgpu_device *adev, @@ -7689,7 +7692,7 @@ static int si_dpm_process_interrupt(struct amdgpu_device *adev, break; } - if (queue_thermal) + if (queue_thermal && amdgpu_dpm) schedule_work(&adev->pm.dpm.thermal.work); return 0; diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c index 1d6e30269d56..4d553be56396 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c @@ -106,11 +106,8 @@ int hwmgr_early_init(struct pp_hwmgr *hwmgr) hwmgr->od_enabled = false; switch (hwmgr->chip_id) { case CHIP_BONAIRE: - /* R9 M380 in iMac 2015: SMU hangs when enabling MCLK DPM - * R7 260X cards with old MC ucode: MCLK DPM is unstable - */ - if (adev->pdev->subsystem_vendor == 0x106B || - adev->pdev->device == 0x6658) { + /* R9 M380 in iMac 2015: SMU hangs when enabling MCLK DPM */ + if (adev->pdev->subsystem_vendor == 0x106B) { dev_info(adev->dev, "disabling MCLK DPM on quirky ASIC"); adev->pm.pp_feature &= ~PP_MCLK_DPM_MASK; hwmgr->feature_mask &= ~PP_MCLK_DPM_MASK; diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c index 1f1bb274685a..2393fa8d4e4b 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c @@ -4086,7 +4086,7 @@ static int smu7_get_gpu_power(struct pp_hwmgr *hwmgr, u32 *query) (adev->asic_type != CHIP_FIJI) && (adev->asic_type != CHIP_TONGA)) { smum_send_msg_to_smc_with_parameter(hwmgr, PPSMC_MSG_GetCurrPkgPwr, 0, &tmp); - *query = tmp; + *query = PP_PWR_Q24_8_TO_MW(tmp); if (tmp != 0) return 0; @@ -4105,7 +4105,7 @@ static int smu7_get_gpu_power(struct pp_hwmgr *hwmgr, u32 *query) if (tmp != 0) break; } - *query = tmp; + *query = PP_PWR_Q24_8_TO_MW(tmp); return 0; } @@ -5887,15 +5887,19 @@ static int smu7_power_off_asic(struct pp_hwmgr *hwmgr) static void smu7_notify_ac_dc(struct pp_hwmgr *hwmgr) { struct amdgpu_device *adev = (struct amdgpu_device *)(hwmgr->adev); + const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; - /* Check if the platform already manages the AC/DC switch via dedicated GPIO. */ - if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, + /* + * Check if the platform already manages the AC/DC switch via dedicated GPIO. + * Otherwise SMU automatically notices DC, but needs to be notified of AC. + */ + if (adev->pm.ac_power && + phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, PHM_PlatformCaps_AutomaticDCTransition)) - return; - - /* The SMU automatically notices DC, but needs to be notified when switching to AC. */ - if (adev->pm.ac_power) smum_send_msg_to_smc(hwmgr, PPSMC_MSG_RunningOnAC, NULL); + + /* Recompute clocks with updated max_limits. */ + pp_funcs->pm_compute_clocks(adev->powerplay.pp_handle); } static const struct pp_hwmgr_func smu7_hwmgr_funcs = { diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c index 0e237feb1629..05b1e69116fd 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c @@ -3967,8 +3967,8 @@ static int vega10_get_gpu_power(struct pp_hwmgr *hwmgr, if (ret) return ret; - /* SMC returning actual watts, keep consistent with legacy asics, low 8 bit as 8 fractional bits */ - *query = value << 8; + /* SMC returns whole Watts, while power sensors use milliwatts. */ + *query = value * MILLIWATT_PER_WATT; return 0; } diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c index 69a9074058ce..b466b08e4a67 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c @@ -1419,7 +1419,7 @@ static int vega12_get_gpu_power(struct pp_hwmgr *hwmgr, uint32_t *query) if (ret) return ret; - *query = metrics_table.CurrSocketPower << 8; + *query = metrics_table.CurrSocketPower * MILLIWATT_PER_WATT; return ret; } diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c index 7b8f4c1b80eb..e9c194056e32 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c @@ -2154,12 +2154,13 @@ static int vega20_get_gpu_power(struct pp_hwmgr *hwmgr, int idx, switch (idx) { case AMDGPU_PP_SENSOR_GPU_AVG_POWER: if (hwmgr->smu_version == 0x282e00) - *query = metrics_table.AverageSocketPower << 8; + *query = metrics_table.AverageSocketPower * + MILLIWATT_PER_WATT; else ret = -EOPNOTSUPP; break; case AMDGPU_PP_SENSOR_GPU_INPUT_POWER: - *query = metrics_table.CurrSocketPower << 8; + *query = metrics_table.CurrSocketPower * MILLIWATT_PER_WATT; break; } diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h index 7ebc1344023f..448246cc52cc 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h +++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h @@ -35,6 +35,11 @@ struct pp_hwmgr; struct phm_fan_speed_info; struct pp_atomctrl_voltage_table; +/* Decode legacy unsigned Q24.8 watts to internal milliwatts. */ +#define PP_PWR_Q24_8_TO_MW(power) \ + DIV_ROUND_CLOSEST_ULL((u64)(power) * MILLIWATT_PER_WATT, \ + BIT(8)) + #define VOLTAGE_SCALE 4 #define VOLTAGE_VID_OFFSET_SCALE1 625 #define VOLTAGE_VID_OFFSET_SCALE2 100 @@ -833,14 +838,17 @@ static inline uint32_t pp_entries_max(const struct pp_hwmgr *hwmgr, const void *sub_table, size_t hdr_size, size_t rec_size) { - struct amdgpu_device *adev = (struct amdgpu_device *)hwmgr->adev; - const char *bios_end = (const char *)adev->bios + adev->bios_size; - const char *pp_end = (const char *)hwmgr->soft_pp_table - + hwmgr->soft_pp_table_size; + const char *pp_start = hwmgr->soft_pp_table; + const char *pp_end = pp_start + hwmgr->soft_pp_table_size; const char *entries = (const char *)sub_table + hdr_size; - if (pp_end > bios_end) - return 0; + if (!hwmgr->hardcode_pp_table) { + struct amdgpu_device *adev = hwmgr->adev; + const char *bios_end = (const char *)adev->bios + adev->bios_size; + + if (pp_end > bios_end) + return 0; + } if (!rec_size || entries >= pp_end) return 0; return (uint32_t)((pp_end - entries) / rec_size); diff --git a/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c b/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c index 3e0068a6aeb2..45504ac3a31d 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/smumgr/smu7_smumgr.c @@ -134,14 +134,6 @@ int smu7_send_msg_to_smc(struct pp_hwmgr *hwmgr, uint16_t msg) PHM_WAIT_FIELD_UNEQUAL(hwmgr, SMC_RESP_0, SMC_RESP, 0); - ret = PHM_READ_FIELD(hwmgr->device, SMC_RESP_0, SMC_RESP); - - if (ret == 0xFE) - dev_dbg(adev->dev, "last message was not supported\n"); - else if (ret != 1) - dev_info(adev->dev, - "\nlast message was failed ret is %d\n", ret); - cgs_write_register(hwmgr->device, mmSMC_RESP_0, 0); cgs_write_register(hwmgr->device, mmSMC_MESSAGE_0, msg); @@ -149,13 +141,16 @@ int smu7_send_msg_to_smc(struct pp_hwmgr *hwmgr, uint16_t msg) ret = PHM_READ_FIELD(hwmgr->device, SMC_RESP_0, SMC_RESP); - if (ret == 0xFE) - dev_dbg(adev->dev, "message %x was not supported\n", msg); - else if (ret != 1) - dev_dbg(adev->dev, - "failed to send message %x ret is %d \n", msg, ret); - - return 0; + switch (ret) { + case 1: + return 0; + case 0xFE: + dev_dbg(adev->dev, "SMU message %#x was not supported\n", msg); + return -EOPNOTSUPP; + default: + dev_info(adev->dev, "SMU message %#x failed: response is %d\n", msg, ret); + return ret != 0xFFFF ? -EIO : -ENXIO; + } } int smu7_send_msg_to_smc_with_parameter(struct pp_hwmgr *hwmgr, uint16_t msg, uint32_t parameter) diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c index 504fd9762a8e..f4e817885e54 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c @@ -1366,6 +1366,14 @@ static void smu_feature_cap_init(struct smu_context *smu) bitmap_zero(fea_cap->cap_map, SMU_FEATURE_CAP_ID__COUNT); } +static int smu_set_power_dep(struct smu_context *smu, bool enable) +{ + if (!smu->ppt_funcs->set_power_dep) + return 0; + + return smu->ppt_funcs->set_power_dep(smu, enable); +} + static int smu_sw_init(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -1427,6 +1435,8 @@ static int smu_sw_init(struct amdgpu_ip_block *ip_block) if (!smu->ppt_funcs->get_fan_control_mode) smu->adev->pm.no_fan = true; + smu_set_power_dep(smu, true); + return 0; } @@ -1449,6 +1459,8 @@ static int smu_sw_fini(struct amdgpu_ip_block *ip_block) smu_fini_microcode(smu); + smu_set_power_dep(smu, false); + return 0; } diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h index f8fd93999617..eb945f1805ed 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h @@ -749,6 +749,9 @@ struct smu_context { bool pm_enabled; bool is_apu; + /* Power dependency link from an integrated xHCI controller to the GPU */ + struct device_link *usb_power_link; + uint32_t smc_driver_if_version; uint32_t smc_fw_if_version; uint32_t smc_fw_version; @@ -1333,7 +1336,7 @@ struct pptable_funcs { u32 (*set_gfx_off_residency)(struct smu_context *smu, bool start); /** - * @get_gfx_off_residency: Average GFXOFF residency % during the logging interval + * @get_gfx_off_residency: Live GFXOFF residency percentage */ u32 (*get_gfx_off_residency)(struct smu_context *smu, uint32_t *residency); @@ -1618,6 +1621,14 @@ struct pptable_funcs { */ int (*ras_send_msg)(struct smu_context *smu, enum smu_message_type msg, uint32_t param, uint32_t *read_arg); + + /** + * @set_power_dep: Create or destroy a power dependency link + * from an integrated xHCI controller to the GPU so that the GPU is + * resumed before the USB controller during PM resume. @enable is true + * to create the link and false to tear it down. + */ + int (*set_power_dep)(struct smu_context *smu, bool enable); }; typedef enum { diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu15_driver_if_v15_0_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu15_driver_if_v15_0_0.h index 47ddf533b08f..a5d3e8d88860 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu15_driver_if_v15_0_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu15_driver_if_v15_0_0.h @@ -144,6 +144,44 @@ typedef struct { uint32_t MaxGfxClk; } DpmClocks_t; +//Freq in MHz +//Voltage in milli volts with 2 fractional bits +typedef struct { + uint32_t DcfClocks[NUM_DCFCLK_DPM_LEVELS]; + uint32_t DispClocks[NUM_DISPCLK_DPM_LEVELS]; + uint32_t DppClocks[NUM_DPPCLK_DPM_LEVELS]; + uint32_t SocClocks[NUM_SOCCLK_DPM_LEVELS]; + + uint32_t VPEClocks[NUM_VPE_DPM_LEVELS]; + uint32_t FclkClocks_Freq[NUM_FCLK_DPM_LEVELS]; + uint32_t FclkClocks_Voltage[NUM_FCLK_DPM_LEVELS]; + uint32_t SocVoltage[NUM_SOC_VOLTAGE_LEVELS]; + MemPstateTable_t MemPstateTable[NUM_MEM_PSTATE_LEVELS]; + + uint8_t NumDcfClkLevelsEnabled; + uint8_t NumDispClkLevelsEnabled; //Applies to both Dispclk and Dppclk + uint8_t NumSocClkLevelsEnabled; + + uint8_t VpeClkLevelsEnabled; + + uint8_t NumMemPstatesEnabled; + uint8_t NumFclkLevelsEnabled; + uint8_t Spare1; + uint8_t Spare2; + + uint8_t Spare3; + uint8_t Spare4; + uint8_t Spare5[2]; + + uint32_t MinGfxClk; + uint32_t MaxGfxClk; + + uint32_t Spare6[8]; + uint32_t Spare7[8]; + uint32_t Spare8[8]; + uint32_t Spare9[8]; +} DpmClocks_t_v15_0_5; + typedef struct { uint16_t CoreFrequency[16]; //Target core frequency [MHz] uint16_t CorePower[16]; //CAC calculated core power [mW] diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v11_5_ppsmc.h b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v11_5_ppsmc.h index 7471e2df2828..4206514765cd 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v11_5_ppsmc.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v11_5_ppsmc.h @@ -110,8 +110,10 @@ #define PPSMC_MSG_GetSlowPPTLimit 0x4C #define PPSMC_MSG_GetGfxOffStatus 0x50 #define PPSMC_MSG_GetGfxOffEntryCount 0x51 -#define PPSMC_MSG_LogGfxOffResidency 0x52 -#define PPSMC_Message_Count 0x53 +#define PPSMC_MSG_GfxOffResidencyLogReadSample 0x52 +#define PPSMC_MSG_StopGfxOffResidencyLogging 0x53 +#define PPSMC_MSG_StartGfxOffResidencyLogging 0x56 +#define PPSMC_Message_Count 0x57 //Argument for PPSMC_MSG_GfxDeviceDriverReset enum { diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h index 636ff90923d9..acf03838d49d 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h @@ -252,7 +252,9 @@ __SMU_DUMMY_MAP(DriverMode2Reset), \ __SMU_DUMMY_MAP(GetGfxOffStatus), \ __SMU_DUMMY_MAP(GetGfxOffEntryCount), \ - __SMU_DUMMY_MAP(LogGfxOffResidency), \ + __SMU_DUMMY_MAP(StartGfxOffResidencyLogging), \ + __SMU_DUMMY_MAP(GfxOffResidencyLogReadSample), \ + __SMU_DUMMY_MAP(StopGfxOffResidencyLogging), \ __SMU_DUMMY_MAP(SetNumBadMemoryPagesRetired), \ __SMU_DUMMY_MAP(SetBadMemoryPagesRetiredFlagsPerChannel), \ __SMU_DUMMY_MAP(AllowGpo), \ diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h index 13723d45a7de..38e625cda97d 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h @@ -26,7 +26,7 @@ #include "amdgpu_smu.h" #define SMU15_DRIVER_IF_VERSION_INV 0xFFFFFFFF -#define SMU15_DRIVER_IF_VERSION_SMU_V15_0 0x7 +#define SMU15_DRIVER_IF_VERSION_SMU_V15_0 0x9 #define FEATURE_MASK(feature) (1ULL << feature) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c index db5db2c9c8e8..e7628f4f1de4 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c @@ -658,7 +658,7 @@ static int arcturus_get_smu_metrics_data(struct smu_context *smu, *value = metrics->VcnActivityPercentage; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/cyan_skillfish_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/cyan_skillfish_ppt.c index e6e009df9840..bb9620666dae 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/cyan_skillfish_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/cyan_skillfish_ppt.c @@ -158,12 +158,10 @@ cyan_skillfish_get_smu_metrics_data(struct smu_context *smu, *value = metrics->Current.MemclkFrequency; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->Current.CurrentSocketPower << 8) / - 1000; + *value = metrics->Current.CurrentSocketPower; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = (metrics->Average.CurrentSocketPower << 8) / - 1000; + *value = metrics->Average.CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->Current.GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c index 8feea44f3ca0..9f8ab4e097dd 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c @@ -602,7 +602,7 @@ static int navi10_get_legacy_smu_metrics_data(struct smu_context *smu, *value = metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * @@ -691,7 +691,7 @@ static int navi10_get_smu_metrics_data(struct smu_context *smu, *value = metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * @@ -777,7 +777,7 @@ static int navi12_get_legacy_smu_metrics_data(struct smu_context *smu, *value = metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * @@ -866,7 +866,7 @@ static int navi12_get_smu_metrics_data(struct smu_context *smu, *value = metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c index c0de73b85353..cef6287524d4 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c @@ -855,9 +855,10 @@ static int sienna_cichlid_get_smu_metrics_data(struct smu_context *smu, metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = use_metrics_v3 ? metrics_v3->AverageSocketPower << 8 : - use_metrics_v2 ? metrics_v2->AverageSocketPower << 8 : - metrics->AverageSocketPower << 8; + *value = use_metrics_v3 ? metrics_v3->AverageSocketPower : + use_metrics_v2 ? metrics_v2->AverageSocketPower : + metrics->AverageSocketPower; + *value *= MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = (use_metrics_v3 ? metrics_v3->TemperatureEdge : diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c index 016a5c893fee..3e3c68448ba9 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c @@ -72,6 +72,12 @@ static const struct smu_feature_bits vangogh_dpm_features = { } }; +/* + * SMU support new GFXOFF residency log interface since version 4.63.62.00, + * use this to get live readings of GFXOFF residency + */ +#define SUPPORT_LIVE_RESIDENCY_MSG_VERSION 0x043f3e00 + static struct cmn2asic_msg_mapping vangogh_message_map[SMU_MSG_MAX_COUNT] = { MSG_MAP(TestMessage, PPSMC_MSG_TestMessage, 0), MSG_MAP(GetSmuVersion, PPSMC_MSG_GetSmuVersion, 0), @@ -142,7 +148,9 @@ static struct cmn2asic_msg_mapping vangogh_message_map[SMU_MSG_MAX_COUNT] = { MSG_MAP(GetSlowPPTLimit, PPSMC_MSG_GetSlowPPTLimit, 0), MSG_MAP(GetGfxOffStatus, PPSMC_MSG_GetGfxOffStatus, 0), MSG_MAP(GetGfxOffEntryCount, PPSMC_MSG_GetGfxOffEntryCount, 0), - MSG_MAP(LogGfxOffResidency, PPSMC_MSG_LogGfxOffResidency, 0), + MSG_MAP(StartGfxOffResidencyLogging, PPSMC_MSG_StartGfxOffResidencyLogging, 0), + MSG_MAP(GfxOffResidencyLogReadSample, PPSMC_MSG_GfxOffResidencyLogReadSample, 0), + MSG_MAP(StopGfxOffResidencyLogging, PPSMC_MSG_StopGfxOffResidencyLogging, 0), }; static struct cmn2asic_mapping vangogh_feature_mask_map[SMU_FEATURE_COUNT] = { @@ -310,8 +318,7 @@ static int vangogh_get_legacy_smu_metrics_data(struct smu_context *smu, *value = metrics->UvdActivity / 100; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = (metrics->CurrentSocketPower << 8) / - 1000 ; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * @@ -379,12 +386,10 @@ static int vangogh_get_smu_metrics_data(struct smu_context *smu, *value = metrics->Current.UvdActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = (metrics->Average.CurrentSocketPower << 8) / - 1000; + *value = metrics->Average.CurrentSocketPower; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->Current.CurrentSocketPower << 8) / - 1000; + *value = metrics->Current.CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->Current.GfxTemperature / 100 * @@ -2449,19 +2454,32 @@ static int vangogh_set_power_limit(struct smu_context *smu, static u32 vangogh_set_gfxoff_residency(struct smu_context *smu, bool start) { int ret = 0; - u32 residency; struct amdgpu_device *adev = smu->adev; if (!(adev->pm.pp_feature & PP_GFXOFF_MASK)) return 0; - ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_LogGfxOffResidency, - start, &residency); - if (ret) - return ret; + if (smu->smc_fw_version < SUPPORT_LIVE_RESIDENCY_MSG_VERSION) { + u32 residency; - if (!start) - adev->gfx.gfx_off_residency = residency; + ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_GfxOffResidencyLogReadSample, + start, &residency); + if (ret) + return ret; + + if (!start) + adev->gfx.gfx_off_residency = residency; + } else { + if (start) { + ret = smu_cmn_send_smc_msg(smu, SMU_MSG_StartGfxOffResidencyLogging, NULL); + if (ret) + return ret; + } else { + ret = smu_cmn_send_smc_msg(smu, SMU_MSG_StopGfxOffResidencyLogging, NULL); + if (ret) + return ret; + } + } return ret; } @@ -2478,11 +2496,20 @@ static u32 vangogh_set_gfxoff_residency(struct smu_context *smu, bool start) */ static u32 vangogh_get_gfxoff_residency(struct smu_context *smu, uint32_t *residency) { + int ret = 0; struct amdgpu_device *adev = smu->adev; - *residency = adev->gfx.gfx_off_residency; + if (!(adev->pm.pp_feature & PP_GFXOFF_MASK)) + return 0; - return 0; + if (smu->smc_fw_version < SUPPORT_LIVE_RESIDENCY_MSG_VERSION) { + *residency = adev->gfx.gfx_off_residency; + } else { + ret = smu_cmn_send_smc_msg(smu, SMU_MSG_GfxOffResidencyLogReadSample, + residency); + } + + return ret; } /** diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c index 2b011610e3c7..0ec17c1054c0 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c @@ -1215,9 +1215,10 @@ static int renoir_get_smu_metrics_data(struct smu_context *smu, ((amdgpu_ip_version(adev, MP1_HWIP, 0) == IP_VERSION(12, 0, 0)) && (adev->pm.fw_version >= 0x373200))) - *value = metrics->CurrentSocketPower << 8; + *value = metrics->CurrentSocketPower * + MILLIWATT_PER_WATT; else - *value = (metrics->CurrentSocketPower << 8) / 1000; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = (metrics->GfxTemperature / 100) * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c index cd7bf36673cb..cbdfde79cb9e 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c @@ -677,7 +677,8 @@ static int aldebaran_get_smu_metrics_data(struct smu_context *smu, case METRICS_AVERAGE_SOCKETPOWER: /* Valid power data is available only from primary die */ if (aldebaran_is_primary(smu)) - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * + MILLIWATT_PER_WATT; else ret = -EOPNOTSUPP; break; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c index 4ce1429cf57b..48e54881a5d0 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c @@ -807,7 +807,7 @@ static int smu_v13_0_0_get_smu_metrics_data(struct smu_context *smu, metrics->Vcn1ActivityPercentage); break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->AvgTemperature[TEMP_EDGE] * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c index ecdbf9bb8eb7..8c719fff754a 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c @@ -434,7 +434,8 @@ int smu_v13_0_12_get_smu_metrics_data(struct smu_context *smu, *value = SMUQ10_ROUND(metrics->DramBandwidthUtilization); break; case METRICS_CURR_SOCKETPOWER: - *value = SMUQ10_ROUND(metrics->SocketPower) << 8; + *value = SMUQ10_ROUND(metrics->SocketPower) * + MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_HOTSPOT: *value = SMUQ10_ROUND(metrics->MaxSocketTemperature) * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_4_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_4_ppt.c index ba91bf590eed..a9d558f5b739 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_4_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_4_ppt.c @@ -338,10 +338,10 @@ static int smu_v13_0_4_get_smu_metrics_data(struct smu_context *smu, *value = metrics->UvdActivity / 100; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = (metrics->AverageSocketPower << 8) / 1000; + *value = metrics->AverageSocketPower; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->CurrentSocketPower << 8) / 1000; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_5_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_5_ppt.c index 27372a60e83d..f95b15af436d 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_5_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_5_ppt.c @@ -295,7 +295,7 @@ static int smu_v13_0_5_get_smu_metrics_data(struct smu_context *smu, *value = metrics->UvdActivity / 100; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->CurrentSocketPower << 8) / 1000; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c index 64db62a8d642..db94de88add2 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c @@ -1300,7 +1300,8 @@ static int smu_v13_0_6_get_smu_metrics_data(struct smu_context *smu, *value = SMUQ10_ROUND(GET_METRIC_FIELD(DramBandwidthUtilization, version)); break; case METRICS_CURR_SOCKETPOWER: - *value = SMUQ10_ROUND(GET_METRIC_FIELD(SocketPower, version)) << 8; + *value = SMUQ10_ROUND(GET_METRIC_FIELD(SocketPower, version)) * + MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_HOTSPOT: *value = SMUQ10_ROUND(GET_METRIC_FIELD(MaxSocketTemperature, version)) * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.h b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.h index a66bf33dbb58..6554780afff7 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.h +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.h @@ -144,15 +144,15 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; SMU_SCALAR(SMU_MATTR(ACCUMULATION_COUNTER), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), accumulation_counter); \ SMU_SCALAR(SMU_MATTR(PROCHOT_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), prochot_residency_acc); \ + SMU_MTYPE(U64), prochot_residency_acc); \ SMU_SCALAR(SMU_MATTR(PPT_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), ppt_residency_acc); \ + SMU_MTYPE(U64), ppt_residency_acc); \ SMU_SCALAR(SMU_MATTR(SOCKET_THM_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), socket_thm_residency_acc); \ + SMU_MTYPE(U64), socket_thm_residency_acc); \ SMU_SCALAR(SMU_MATTR(VR_THM_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), vr_thm_residency_acc); \ + SMU_MTYPE(U64), vr_thm_residency_acc); \ SMU_SCALAR(SMU_MATTR(HBM_THM_RESIDENCY_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), hbm_thm_residency_acc); \ + SMU_MTYPE(U64), hbm_thm_residency_acc); \ SMU_SCALAR(SMU_MATTR(GFXCLK_LOCK_STATUS), SMU_MUNIT(NONE), \ SMU_MTYPE(U32), gfxclk_lock_status); \ SMU_SCALAR(SMU_MATTR(PCIE_LINK_WIDTH), SMU_MUNIT(NONE), \ @@ -163,11 +163,11 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; SMU_MTYPE(U16), xgmi_link_width); \ SMU_SCALAR(SMU_MATTR(XGMI_LINK_SPEED), SMU_MUNIT(SPEED_1), \ SMU_MTYPE(U16), xgmi_link_speed); \ - SMU_SCALAR(SMU_MATTR(GFX_ACTIVITY_ACC), SMU_MUNIT(PERCENT), \ - SMU_MTYPE(U32), gfx_activity_acc); \ - SMU_SCALAR(SMU_MATTR(MEM_ACTIVITY_ACC), SMU_MUNIT(PERCENT), \ - SMU_MTYPE(U32), mem_activity_acc); \ - SMU_SCALAR(SMU_MATTR(PCIE_BANDWIDTH_ACC), SMU_MUNIT(PERCENT), \ + SMU_SCALAR(SMU_MATTR(GFX_ACTIVITY_ACC), SMU_MUNIT(NONE), \ + SMU_MTYPE(U64), gfx_activity_acc); \ + SMU_SCALAR(SMU_MATTR(MEM_ACTIVITY_ACC), SMU_MUNIT(NONE), \ + SMU_MTYPE(U64), mem_activity_acc); \ + SMU_SCALAR(SMU_MATTR(PCIE_BANDWIDTH_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), pcie_bandwidth_acc); \ SMU_SCALAR(SMU_MATTR(PCIE_BANDWIDTH_INST), SMU_MUNIT(BW_1), \ SMU_MTYPE(U64), pcie_bandwidth_inst); \ @@ -178,9 +178,9 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; SMU_SCALAR(SMU_MATTR(PCIE_REPLAY_ROVER_COUNT_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), pcie_replay_rover_count_acc); \ SMU_SCALAR(SMU_MATTR(PCIE_NAK_SENT_COUNT_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), pcie_nak_sent_count_acc); \ + SMU_MTYPE(U64), pcie_nak_sent_count_acc); \ SMU_SCALAR(SMU_MATTR(PCIE_NAK_RCVD_COUNT_ACC), SMU_MUNIT(NONE), \ - SMU_MTYPE(U32), pcie_nak_rcvd_count_acc); \ + SMU_MTYPE(U64), pcie_nak_rcvd_count_acc); \ SMU_ARRAY(SMU_MATTR(XGMI_READ_DATA_ACC), SMU_MUNIT(DATA_1), \ SMU_MTYPE(U64), xgmi_read_data_acc, \ SMU_13_0_6_NUM_XGMI_LINKS); \ @@ -203,7 +203,7 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; SMU_SCALAR(SMU_MATTR(CURRENT_UCLK), SMU_MUNIT(CLOCK_1), \ SMU_MTYPE(U16), current_uclk); \ SMU_SCALAR(SMU_MATTR(PCIE_LC_PERF_OTHER_END_RECOVERY), \ - SMU_MUNIT(NONE), SMU_MTYPE(U32), \ + SMU_MUNIT(NONE), SMU_MTYPE(U64), \ pcie_lc_perf_other_end_recovery); \ SMU_ARRAY(SMU_MATTR(GFX_BUSY_INST), SMU_MUNIT(PERCENT), \ SMU_MTYPE(U32), gfx_busy_inst, SMU_13_0_6_MAX_XCC); \ @@ -211,7 +211,7 @@ extern const struct smu_temp_funcs smu_v13_0_12_temp_funcs; jpeg_busy, SMU_13_0_6_MAX_JPEG); \ SMU_ARRAY(SMU_MATTR(VCN_BUSY), SMU_MUNIT(PERCENT), SMU_MTYPE(U16), \ vcn_busy, SMU_13_0_6_MAX_VCN); \ - SMU_ARRAY(SMU_MATTR(GFX_BUSY_ACC), SMU_MUNIT(PERCENT), SMU_MTYPE(U64), \ + SMU_ARRAY(SMU_MATTR(GFX_BUSY_ACC), SMU_MUNIT(NONE), SMU_MTYPE(U64), \ gfx_busy_acc, SMU_13_0_6_MAX_XCC); \ SMU_ARRAY(SMU_MATTR(GFX_BELOW_HOST_LIMIT_PPT_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), gfx_below_host_limit_ppt_acc, \ @@ -256,7 +256,7 @@ void smu_v13_0_12_get_gpu_metrics(struct smu_context *smu, void **table, jpeg_busy, SMU_13_0_6_MAX_JPEG); \ SMU_ARRAY(SMU_MATTR(VCN_BUSY), SMU_MUNIT(PERCENT), SMU_MTYPE(U16), \ vcn_busy, SMU_13_0_6_MAX_VCN); \ - SMU_ARRAY(SMU_MATTR(GFX_BUSY_ACC), SMU_MUNIT(PERCENT), SMU_MTYPE(U64), \ + SMU_ARRAY(SMU_MATTR(GFX_BUSY_ACC), SMU_MUNIT(NONE), SMU_MTYPE(U64), \ gfx_busy_acc, SMU_13_0_6_MAX_XCC); \ SMU_ARRAY(SMU_MATTR(GFX_BELOW_HOST_LIMIT_PPT_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), gfx_below_host_limit_ppt_acc, \ diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c index 5f23f2e7f401..4deb987e43e0 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c @@ -817,7 +817,7 @@ static int smu_v13_0_7_get_smu_metrics_data(struct smu_context *smu, *value = smu_safe_u16_nn(metrics->AverageUclkActivity); break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->AvgTemperature[TEMP_EDGE] * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c index 7bf88ffd311b..edc3fedca9f8 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c @@ -372,7 +372,7 @@ static int yellow_carp_get_smu_metrics_data(struct smu_context *smu, *value = metrics->UvdActivity / 100; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->CurrentSocketPower << 8) / 1000; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c index 75719c47a41e..568665265a13 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c @@ -312,8 +312,7 @@ static int smu_v14_0_0_get_smu_metrics_data(struct smu_context *smu, break; case METRICS_AVERAGE_SOCKETPOWER: case METRICS_CURR_SOCKETPOWER: - *value = (metrics->SocketPower / 1000 << 8) + - (metrics->SocketPower % 1000 / 10); + *value = metrics->SocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * @@ -1701,6 +1700,50 @@ static int smu_v14_0_0_restore_user_od_settings(struct smu_context *smu) return 0; } +/* + * Link any xHCI controller sharing the GPU's PCIe root port as a consumer + * of the GPU so the GPU resumes first, avoiding an xHCI resume race. + */ +static int smu_v14_0_0_set_power_dep(struct smu_context *smu, bool enable) +{ + struct amdgpu_device *adev = smu->adev; + struct pci_dev *gpu_pdev = adev->pdev; + struct pci_dev *root_port, *usb_pdev = NULL; + struct device_link *link; + + if (!enable) { + if (smu->usb_power_link) { + device_link_del(smu->usb_power_link); + smu->usb_power_link = NULL; + } + return 0; + } + + root_port = pcie_find_root_port(gpu_pdev); + while ((usb_pdev = pci_get_class(PCI_CLASS_SERIAL_USB_XHCI, usb_pdev))) { + struct pci_dev *usb_root; + + usb_root = pcie_find_root_port(usb_pdev); + if (usb_root != root_port) + continue; + + /* Create device link: USB (consumer) depends on GPU (supplier) */ + link = device_link_add(&usb_pdev->dev, &gpu_pdev->dev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME); + if (link) { + smu->usb_power_link = link; + drm_info(adev_to_drm(adev), "USB controller %s D0 power state depends on %s\n", + pci_name(usb_pdev), pci_name(gpu_pdev)); + /* Only create one link for the first USB controller found */ + break; + } + } + + pci_dev_put(usb_pdev); + + return 0; +} + static const struct pptable_funcs smu_v14_0_0_ppt_funcs = { .check_fw_status = smu_v14_0_check_fw_status, .check_fw_version = smu_cmn_check_fw_version, @@ -1734,6 +1777,7 @@ static const struct pptable_funcs smu_v14_0_0_ppt_funcs = { .dpm_set_umsch_mm_enable = smu_v14_0_0_set_umsch_mm_enable, .get_dpm_clock_table = smu_v14_0_common_get_dpm_table, .set_mall_enable = smu_v14_0_common_set_mall_enable, + .set_power_dep = smu_v14_0_0_set_power_dep, }; static void smu_v14_0_0_init_msg_ctl(struct smu_context *smu) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c index 0fbcaf884419..2fa4ce26e950 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c @@ -695,7 +695,7 @@ static int smu_v14_0_2_get_smu_metrics_data(struct smu_context *smu, metrics->Vcn1ActivityPercentage); break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->AvgTemperature[TEMP_EDGE] * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c index bb8d09e73c7d..0da1ffb3a567 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c @@ -168,7 +168,7 @@ static int smu_v15_0_0_init_smc_tables(struct smu_context *smu) SMU_TABLE_INIT(tables, SMU_TABLE_WATERMARKS, sizeof(Watermarks_t), PAGE_SIZE, AMDGPU_GEM_DOMAIN_VRAM); - SMU_TABLE_INIT(tables, SMU_TABLE_DPMCLOCKS, sizeof(DpmClocks_t), + SMU_TABLE_INIT(tables, SMU_TABLE_DPMCLOCKS, sizeof(DpmClocks_t_v15_0_5), PAGE_SIZE, AMDGPU_GEM_DOMAIN_VRAM); SMU_TABLE_INIT(tables, SMU_TABLE_SMU_METRICS, sizeof(SmuMetrics_t), PAGE_SIZE, AMDGPU_GEM_DOMAIN_VRAM); @@ -178,7 +178,7 @@ static int smu_v15_0_0_init_smc_tables(struct smu_context *smu) goto err0_out; smu_table->metrics_time = 0; - smu_table->clocks_table = kzalloc_obj(DpmClocks_t); + smu_table->clocks_table = kzalloc_obj(DpmClocks_t_v15_0_5); if (!smu_table->clocks_table) goto err1_out; @@ -378,8 +378,7 @@ static int smu_v15_0_0_get_smu_metrics_data(struct smu_context *smu, break; case METRICS_AVERAGE_SOCKETPOWER: case METRICS_CURR_SOCKETPOWER: - *value = (metrics->SocketPower / 1000 << 8) + - (metrics->SocketPower % 1000 / 10); + *value = metrics->SocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * @@ -752,11 +751,48 @@ static int smu_v15_0_0_get_dpm_freq_by_index(struct smu_context *smu, return 0; } +static int smu_v15_0_5_get_dpm_freq_by_index(struct smu_context *smu, + enum smu_clk_type clk_type, + uint32_t dpm_level, + uint32_t *freq) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + + if (!clk_table || clk_type >= SMU_CLK_COUNT) + return -EINVAL; + + switch (clk_type) { + case SMU_SOCCLK: + if (dpm_level >= clk_table->NumSocClkLevelsEnabled) + return -EINVAL; + *freq = clk_table->SocClocks[dpm_level]; + break; + case SMU_UCLK: + case SMU_MCLK: + if (dpm_level >= clk_table->NumMemPstatesEnabled) + return -EINVAL; + *freq = clk_table->MemPstateTable[dpm_level].MemClk; + break; + case SMU_FCLK: + if (dpm_level >= clk_table->NumFclkLevelsEnabled) + return -EINVAL; + *freq = clk_table->FclkClocks_Freq[dpm_level]; + break; + default: + return -EINVAL; + } + + return 0; +} + static int smu_v15_0_common_get_dpm_freq_by_index(struct smu_context *smu, enum smu_clk_type clk_type, uint32_t dpm_level, uint32_t *freq) { + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_get_dpm_freq_by_index(smu, clk_type, dpm_level, freq); + else smu_v15_0_0_get_dpm_freq_by_index(smu, clk_type, dpm_level, freq); return 0; @@ -907,12 +943,116 @@ failed: return ret; } +static int smu_v15_0_5_get_dpm_ultimate_freq(struct smu_context *smu, + enum smu_clk_type clk_type, + uint32_t *min, + uint32_t *max) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + uint32_t clock_limit; + uint32_t max_dpm_level, min_dpm_level; + int ret = 0; + + if (!smu_v15_0_0_clk_dpm_is_enabled(smu, clk_type)) { + switch (clk_type) { + case SMU_MCLK: + case SMU_UCLK: + clock_limit = smu->smu_table.boot_values.uclk; + break; + case SMU_FCLK: + clock_limit = smu->smu_table.boot_values.fclk; + break; + case SMU_GFXCLK: + case SMU_SCLK: + clock_limit = smu->smu_table.boot_values.gfxclk; + break; + case SMU_SOCCLK: + clock_limit = smu->smu_table.boot_values.socclk; + break; + default: + clock_limit = 0; + break; + } + + /* clock in Mhz unit */ + if (min) + *min = clock_limit / 100; + if (max) + *max = clock_limit / 100; + + return 0; + } + + if (max) { + switch (clk_type) { + case SMU_GFXCLK: + case SMU_SCLK: + *max = clk_table->MaxGfxClk; + break; + case SMU_MCLK: + case SMU_UCLK: + max_dpm_level = 0; + break; + case SMU_FCLK: + max_dpm_level = clk_table->NumFclkLevelsEnabled - 1; + break; + case SMU_SOCCLK: + max_dpm_level = clk_table->NumSocClkLevelsEnabled - 1; + break; + default: + ret = -EINVAL; + goto failed; + } + + if (clk_type != SMU_GFXCLK && clk_type != SMU_SCLK) { + ret = smu_v15_0_common_get_dpm_freq_by_index(smu, clk_type, + max_dpm_level, max); + if (ret) + goto failed; + } + } + + if (min) { + switch (clk_type) { + case SMU_GFXCLK: + case SMU_SCLK: + *min = clk_table->MinGfxClk; + break; + case SMU_MCLK: + case SMU_UCLK: + min_dpm_level = clk_table->NumMemPstatesEnabled - 1; + break; + case SMU_FCLK: + min_dpm_level = 0; + break; + case SMU_SOCCLK: + min_dpm_level = 0; + break; + default: + ret = -EINVAL; + goto failed; + } + + if (clk_type != SMU_GFXCLK && clk_type != SMU_SCLK) { + ret = smu_v15_0_common_get_dpm_freq_by_index(smu, clk_type, + min_dpm_level, min); + if (ret) + goto failed; + } + } + +failed: + return ret; +} + static int smu_v15_0_common_get_dpm_ultimate_freq(struct smu_context *smu, enum smu_clk_type clk_type, uint32_t *min, uint32_t *max) { - if (clk_type != SMU_VCLK1 && clk_type != SMU_DCLK1) + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_get_dpm_ultimate_freq(smu, clk_type, min, max); + else if (clk_type != SMU_VCLK1 && clk_type != SMU_DCLK1) smu_v15_0_0_get_dpm_ultimate_freq(smu, clk_type, min, max); return 0; @@ -986,11 +1126,36 @@ static int smu_v15_0_0_get_dpm_level_count(struct smu_context *smu, return 0; } +static int smu_v15_0_5_get_dpm_level_count(struct smu_context *smu, + enum smu_clk_type clk_type, + uint32_t *count) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + + switch (clk_type) { + case SMU_SOCCLK: + *count = clk_table->NumSocClkLevelsEnabled; + break; + case SMU_MCLK: + *count = clk_table->NumMemPstatesEnabled; + break; + case SMU_FCLK: + *count = clk_table->NumFclkLevelsEnabled; + break; + default: + break; + } + + return 0; +} + static int smu_v15_0_common_get_dpm_level_count(struct smu_context *smu, enum smu_clk_type clk_type, uint32_t *count) { - if (clk_type != SMU_VCLK1 && clk_type != SMU_DCLK1) + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_get_dpm_level_count(smu, clk_type, count); + else if (clk_type != SMU_VCLK1 && clk_type != SMU_DCLK1) smu_v15_0_0_get_dpm_level_count(smu, clk_type, count); return 0; @@ -1363,9 +1528,24 @@ static int smu_v15_0_0_set_fine_grain_gfx_freq_parameters(struct smu_context *sm return 0; } +static int smu_v15_0_5_set_fine_grain_gfx_freq_parameters(struct smu_context *smu) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + + smu->gfx_default_hard_min_freq = clk_table->MinGfxClk; + smu->gfx_default_soft_max_freq = clk_table->MaxGfxClk; + smu->gfx_actual_hard_min_freq = 0; + smu->gfx_actual_soft_max_freq = 0; + + return 0; +} + static int smu_v15_0_common_set_fine_grain_gfx_freq_parameters(struct smu_context *smu) { - smu_v15_0_0_set_fine_grain_gfx_freq_parameters(smu); + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_set_fine_grain_gfx_freq_parameters(smu); + else + smu_v15_0_0_set_fine_grain_gfx_freq_parameters(smu); return 0; } @@ -1405,9 +1585,36 @@ static int smu_v15_0_0_get_dpm_table(struct smu_context *smu, struct dpm_clocks return 0; } +static int smu_v15_0_5_get_dpm_table(struct smu_context *smu, struct dpm_clocks *clock_table) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + uint8_t idx; + + /* + * Only the Clock information of SOC and + * VPE is copied to provide VPE DPM settings for use. + */ + for (idx = 0; idx < NUM_SOCCLK_DPM_LEVELS; idx++) { + clock_table->SocClocks[idx].Freq = + (idx < clk_table->NumSocClkLevelsEnabled) ? clk_table->SocClocks[idx]:0; + clock_table->SocClocks[idx].Vol = 0; + } + + for (idx = 0; idx < NUM_VPE_DPM_LEVELS; idx++) { + clock_table->VPEClocks[idx].Freq = + (idx < clk_table->VpeClkLevelsEnabled) ? clk_table->VPEClocks[idx]:0; + clock_table->VPEClocks[idx].Vol = 0; + } + + return 0; +} + static int smu_v15_0_common_get_dpm_table(struct smu_context *smu, struct dpm_clocks *clock_table) { - smu_v15_0_0_get_dpm_table(smu, clock_table); + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_get_dpm_table(smu, clock_table); + else + smu_v15_0_0_get_dpm_table(smu, clock_table); return 0; } diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c index 2105a1d7bb34..59fdd2319b66 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c @@ -411,7 +411,8 @@ static int smu_v15_0_8_get_smu_metrics_data(struct smu_context *smu, *value = SMUQ10_ROUND(metrics->DramBandwidthUtilization); break; case METRICS_CURR_SOCKETPOWER: - *value = SMUQ10_ROUND(metrics->SocketPower) << 8; + *value = SMUQ10_ROUND(metrics->SocketPower) * + MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_HOTSPOT: *value = SMUQ10_ROUND(metrics->MaxSocketTemperature) * @@ -1766,12 +1767,17 @@ static ssize_t smu_v15_0_8_get_gpu_metrics(struct smu_context *smu, void **table idx++; } - /* Per-VCN clocks */ + /* Per-VCN clocks and busy */ for (i = 0; i < adev->vcn.num_vcn_inst; ++i) { inst = GET_INST(VCN, i); if (inst >= 0) { gpu_metrics->current_vclk0[i] = SMUQ10_ROUND(metrics->VclkFrequency[inst]); gpu_metrics->current_dclk0[i] = SMUQ10_ROUND(metrics->DclkFrequency[inst]); + gpu_metrics->vcn_busy[i] = SMUQ10_ROUND(metrics->VcnBusy[inst]); + for (j = 0; j < NUM_JPEG_RINGS_FW; ++j) + gpu_metrics->jpeg_busy[(i * NUM_JPEG_RINGS_FW) + j] = + SMUQ10_ROUND(metrics->JpegBusy[(inst * NUM_JPEG_RINGS_FW) + + j]); } } diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h index 4dfc40aaffcb..46fce0a79c21 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.h @@ -125,7 +125,7 @@ typedef struct { SMU_ARRAY(SMU_MATTR(PCIE_BANDWIDTH_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), pcie_bandwidth_acc, SMU_15_0_8_MAX_MID); \ SMU_ARRAY(SMU_MATTR(PCIE_BANDWIDTH_INST), SMU_MUNIT(BW_1), \ - SMU_MTYPE(U32), pcie_bandwidth_inst, SMU_15_0_8_MAX_MID); \ + SMU_MTYPE(U64), pcie_bandwidth_inst, SMU_15_0_8_MAX_MID); \ SMU_SCALAR(SMU_MATTR(PCIE_L0_TO_RECOV_COUNT_ACC), SMU_MUNIT(NONE), \ SMU_MTYPE(U64), pcie_l0_to_recov_count_acc); \ SMU_SCALAR(SMU_MATTR(PCIE_REPLAY_COUNT_ACC), SMU_MUNIT(NONE), \ diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c index bfbfdffbfbe6..f9ee297a6745 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c @@ -73,13 +73,41 @@ static int amdgpu_ras_trigger_error_end(struct ras_core_context *ras_core, return 0; } -static uint64_t local_addr_to_xgmi_global_addr(struct ras_core_context *ras_core, - uint64_t addr) -{ - struct amdgpu_device *adev = (struct amdgpu_device *)ras_core->dev; - struct amdgpu_xgmi *xgmi = &adev->gmc.xgmi; +/* + * UMC error injection is dispatched by the RAS TA using the injection method + * carried in struct ras_cmd_inject_error_req. Only the "coherent" methods + * program an explicit injection address and are therefore address-based; the + * single-shot, persistent and ac-parity methods ignore the address. + * + * Keep these values in sync with the RAS TA. + */ +enum umc_inject_method { + UMC_METHOD_COHERENT = 0, + UMC_METHOD_SINGLE_SHOT = 1, + UMC_METHOD_PERSISTENT = 2, + UMC_METHOD_PERSISTENT_DISABLE = 3, + UMC_METHOD_COHERENT_NO_DETECTION = 4, + UMC_METHOD_COHERENT_WR = 5, + UMC_METHOD_SINGLE_SHOT_WR = 6, + UMC_METHOD_PERSISTENT_WR = 7, + UMC_METHOD_SINGLE_SHOT_CLEAN = 8, +}; - return (addr + xgmi->physical_node_id * xgmi->node_segment_size); +/* + * Return true when @method does not program an explicit injection address. + * Only the coherent methods are address-based; every other method ignores the + * address, so userspace signals them by setting the address to U64_MAX. + */ +static bool amdgpu_ras_mgr_is_non_address_injection(u64 method) +{ + switch (method) { + case UMC_METHOD_COHERENT: + case UMC_METHOD_COHERENT_NO_DETECTION: + case UMC_METHOD_COHERENT_WR: + return false; + default: + return true; + } } static int amdgpu_ras_inject_error(struct ras_core_context *ras_core, @@ -91,25 +119,37 @@ static int amdgpu_ras_inject_error(struct ras_core_context *ras_core, int ret = RAS_CMD__ERROR_GENERIC; if (req->block_id == RAS_BLOCK_ID__UMC) { - if (amdgpu_ras_mgr_check_retired_addr(adev, req->address)) { - RAS_DEV_WARN(ras_core->dev, - "RAS WARN: inject: 0x%llx has already been marked as bad!\n", - req->address); - return RAS_CMD__ERROR_ACCESS_DENIED; - } - - if ((req->address >= adev->gmc.mc_vram_size && - adev->gmc.mc_vram_size) || - (req->address >= RAS_UMC_INJECT_ADDR_LIMIT)) { - RAS_DEV_WARN(adev, "RAS WARN: input address 0x%llx is invalid.", + /* + * Only address-based UMC injections carry an explicit injection + * address that has to be validated. A non address-based method + * ignores the address, and userspace flags such an injection by + * setting the address to U64_MAX. When both the sentinel and the + * method agree, clear the address so the RAS TA ignores it and + * skip the validation; otherwise validate the injection address. + */ + if (req->address == U64_MAX && amdgpu_ras_mgr_is_non_address_injection(req->method)) { + req->address = 0x0; + } else { + if (amdgpu_ras_mgr_check_retired_addr(adev, req->address)) { + RAS_DEV_WARN(ras_core->dev, + "RAS WARN: inject: 0x%llx has already been marked as bad!\n", req->address); - return RAS_CMD__ERROR_INVALID_INPUT_DATA; - } + return RAS_CMD__ERROR_ACCESS_DENIED; + } - /* Calculate XGMI relative offset */ - if (adev->gmc.xgmi.num_physical_nodes > 1 && - req->block_id != RAS_BLOCK_ID__GFX) { - req->address = local_addr_to_xgmi_global_addr(ras_core, req->address); + if ((req->address >= adev->gmc.mc_vram_size && + adev->gmc.mc_vram_size) || + (req->address >= RAS_UMC_INJECT_ADDR_LIMIT)) { + RAS_DEV_WARN(adev, "RAS WARN: input address 0x%llx is invalid.", + req->address); + return RAS_CMD__ERROR_INVALID_INPUT_DATA; + } + + /* + * Calculate XGMI relative offset if in XGMI hive, + * adapt to A+A platform with mc fb offset calculated. + */ + req->address += adev->vm_manager.vram_base_offset; } } diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c index b62bbb5ea292..c6c2e2cdaf18 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c @@ -284,6 +284,7 @@ static int amdgpu_ras_mgr_init_umc_config(struct amdgpu_device *adev, struct ras_umc_config *umc_cfg = &config->umc_cfg; umc_cfg->umc_vram_type = adev->gmc.vram_type; + umc_cfg->num_umc = adev->gmc.num_umc; return 0; } @@ -309,6 +310,7 @@ static struct ras_core_context *amdgpu_ras_mgr_create_ras_core(struct amdgpu_dev amdgpu_ras_mgr_eeprom_is_supported(adev); init_config.poison_supported = amdgpu_ras_is_poison_mode_supported(adev); + init_config.ras_debug_mask = amdgpu_debug_mask; amdgpu_ras_mgr_init_aca_config(adev, &init_config); amdgpu_ras_mgr_init_eeprom_config(adev, &init_config); @@ -332,8 +334,7 @@ static int amdgpu_ras_mgr_sw_init(struct amdgpu_ip_block *ip_block) if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 12) || - amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6) || - adev->debug_enable_ras_aca) + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6)) con->uniras_enabled = true; else return 0; diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c index e4444798bc73..0b36b689fcfc 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c @@ -171,6 +171,9 @@ static int amdgpu_ras_sys_check_gpu_status(struct ras_core_context *ras_core, if (amdgpu_in_reset(adev) || amdgpu_ras_in_recovery(adev)) gpu_status |= RAS_GPU_STATUS__IN_RESET; + if (amdgpu_device_bus_status_check(adev)) + gpu_status |= RAS_GPU_STATUS__DEVICE_LOST; + if (amdgpu_sriov_vf(adev)) gpu_status |= RAS_GPU_STATUS__IS_VF; diff --git a/drivers/gpu/drm/amd/ras/rascore/ras.h b/drivers/gpu/drm/amd/ras/rascore/ras.h index 878dfdfcb18a..8c3d603ba801 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras.h @@ -137,6 +137,7 @@ enum ras_gpu_status { RAS_GPU_STATUS__IN_RESET = 0x2, RAS_GPU_STATUS__IS_RMA = 0x4, RAS_GPU_STATUS__IS_VF = 0x8, + RAS_GPU_STATUS__DEVICE_LOST = 0x10, }; enum ras_fw_eeprom_cmd { @@ -278,6 +279,7 @@ struct ras_psp_config { struct ras_umc_config { uint32_t umc_vram_type; + uint32_t num_umc; }; struct ras_eeprom_config { @@ -301,6 +303,7 @@ struct ras_core_config { bool poison_supported; bool ras_eeprom_supported; + uint ras_debug_mask; const struct ras_sys_func *sys_fn; struct ras_aca_config aca_cfg; @@ -372,6 +375,7 @@ int ras_core_query_block_ecc_data(struct ras_core_context *ras_core, bool ras_core_gpu_in_reset(struct ras_core_context *ras_core); bool ras_core_gpu_is_rma(struct ras_core_context *ras_core); bool ras_core_gpu_is_vf(struct ras_core_context *ras_core); +bool ras_core_gpu_device_lost(struct ras_core_context *ras_core); bool ras_core_handle_nbio_irq(struct ras_core_context *ras_core, void *data); int ras_core_handle_fatal_error(struct ras_core_context *ras_core); @@ -403,4 +407,5 @@ int ras_core_convert_soc_pa_to_cur_nps_pages(struct ras_core_context *ras_core, int ras_core_check_address_sanity(struct ras_core_context *ras_core, uint64_t addr); int ras_core_set_debug_mode(struct ras_core_context *ras_core, bool enable); +bool ras_core_is_ce_log_disabled(struct ras_core_context *ras_core); #endif diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_aca.c b/drivers/gpu/drm/amd/ras/rascore/ras_aca.c index 67a35409ff0e..6f57b5d09cd3 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_aca.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_aca.c @@ -83,22 +83,20 @@ static void aca_report_ecc_info(struct ras_core_context *ras_core, seq_no, skt, aid, ecc_count.total_ue_count, blk_name(blk)); } - if (ecc_count.new_de_count) { + if (ecc_count.new_de_count && blk == RAS_BLOCK_ID__UMC) { RAS_DEV_INFO(ras_core->dev, - "{%llu} socket: %d, die: %d, %u new %s detected in %s block\n", + "{%llu} socket: %d, die: %d, %u new deferred hardware errors detected in %s block\n", seq_no, skt, aid, ecc_count.new_de_count, - (blk == RAS_BLOCK_ID__UMC) ? - "deferred hardware errors" : "poison consumption", blk_name(blk)); RAS_DEV_INFO(ras_core->dev, - "{%llu} socket: %d, die: %d, %u %s detected in total in %s block\n", + "{%llu} socket: %d, die: %d, %u deferred hardware errors detected in total in %s block\n", seq_no, skt, aid, ecc_count.total_de_count, - (blk == RAS_BLOCK_ID__UMC) ? - "deferred hardware errors" : "poison consumption", blk_name(blk)); } if (ecc_count.new_ce_count) { + if (ras_core_is_ce_log_disabled(ras_core)) + return; RAS_DEV_INFO(ras_core->dev, "{%llu} socket: %d, die: %d, %u new correctable hardware errors detected in %s block\n", seq_no, skt, aid, ecc_count.new_ce_count, blk_name(blk)); @@ -114,6 +112,11 @@ static void aca_bank_log(struct ras_core_context *ras_core, { int i; + if (ras_core_is_ce_log_disabled(ras_core) && + bank->ecc_type == RAS_ERR_TYPE__CE && + !bank_ecc->de_count) + return; + RAS_DEV_INFO(ras_core->dev, "{%llu}" RAS_HW_ERR "Accelerator Check Architecture events logged\n", bank->seq_no); diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c b/drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c index 840610538c1f..288fad0862a2 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c @@ -265,7 +265,7 @@ static int aca_parse_bank_default(struct ras_core_context *ras_core, ecc->bank_info.addr = bank->regs[ACA_REG_IDX__ADDR]; if (aca_check_bank_is_de(ras_core, status)) { - ecc->de_count = 0; + ecc->de_count = 1; } else { if (bank->ecc_type == RAS_ERR_TYPE__UE) ecc->ue_count = 1; diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_core.c b/drivers/gpu/drm/amd/ras/rascore/ras_core.c index c63a358b7e57..75e8216fae8a 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_core.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_core.c @@ -28,6 +28,10 @@ #define IS_LEAP_YEAR(x) ((x % 4 == 0 && x % 100 != 0) || x % 400 == 0) +enum RAS_DEBUG_MASK { + RAS_DEBUG_DISABLE_RAS_CE_LOG = 9, +}; + static const char * const ras_block_name[] = { "umc", "sdma", @@ -143,6 +147,20 @@ bool ras_core_gpu_is_vf(struct ras_core_context *ras_core) return (status & RAS_GPU_STATUS__IS_VF) ? true : false; } +bool ras_core_gpu_device_lost(struct ras_core_context *ras_core) +{ + uint32_t status = 0; + + if (!ras_core) + return false; + + if (ras_core->sys_fn && + ras_core->sys_fn->check_gpu_status) + ras_core->sys_fn->check_gpu_status(ras_core, &status); + + return (status & RAS_GPU_STATUS__DEVICE_LOST) ? true : false; +} + bool ras_core_gpu_is_rma(struct ras_core_context *ras_core) { if (!ras_core) @@ -545,6 +563,11 @@ bool ras_core_is_enabled(struct ras_core_context *ras_core) return ras_core->ras_core_enabled; } +bool ras_core_is_ce_log_disabled(struct ras_core_context *ras_core) +{ + return !!(ras_core->config->ras_debug_mask & BIT(RAS_DEBUG_DISABLE_RAS_CE_LOG)); +} + uint64_t ras_core_get_utc_second_timestamp(struct ras_core_context *ras_core) { if (!ras_core) diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c index 62d1a319c08c..3b1883a04179 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c @@ -1261,6 +1261,11 @@ int ras_eeprom_check_storage_status(struct ras_core_context *ras_core) } else { RAS_DEV_ERR(ras_core->dev, "RAS records:%d exceed threshold:%d", bad_page_count, control->record_threshold_count); + /* send the event when threshold is exceeded, and ignore the + * return value here + */ + ras_core_event_notify(ras_core, RAS_EVENT_ID__DEVICE_RMA, NULL); + if ((control->record_threshold_config == WARN_NONSTOP_OVER_THRESHOLD) || (control->record_threshold_config == NONSTOP_OVER_THRESHOLD)) { RAS_DEV_WARN(ras_core->dev, diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.h b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.h index f2c001ef64e1..5c3e92f710b4 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.h @@ -36,10 +36,10 @@ /* * Bad address pfn : eeprom_umc_record.retired_row_pfn[39:0], - * nps mode: eeprom_umc_record.retired_row_pfn[47:40] + * nps mode: eeprom_umc_record.retired_row_pfn[46:40] */ #define EEPROM_RECORD_UMC_ADDR_MASK 0xFFFFFFFFFFULL -#define EEPROM_RECORD_UMC_NPS_MASK 0xFF0000000000ULL +#define EEPROM_RECORD_UMC_NPS_MASK 0x7F0000000000ULL #define EEPROM_RECORD_UMC_NPS_SHIFT 40 #define EEPROM_RECORD_UMC_NPS_MODE(RECORD) \ diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_psp.c b/drivers/gpu/drm/amd/ras/rascore/ras_psp.c index 358f602b167d..35264c119bbb 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_psp.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_psp.c @@ -294,6 +294,9 @@ static int send_psp_cmd(struct ras_core_context *ras_core, psp_ctx->in_fence_value) { if (--timeout == 0) break; + + if (ras_core_gpu_device_lost(ras_core)) + break; /* * Shouldn't wait for timeout when err_event_athub occurs, * because gpu reset thread triggered and lock resource should diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c index e366fb97293e..f59f0b6cad06 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c @@ -346,7 +346,7 @@ static bool ras_umc_check_retired_record(struct ras_core_context *ras_core, if (ras_umc->ip_func && ras_umc->ip_func->eeprom_record_to_nps_record) { ret = ras_umc->ip_func->eeprom_record_to_nps_record(ras_core, record, nps); if (ret) - RAS_DEV_WARN(ras_core->dev, + RAS_DEV_WARN_RATELIMITED(ras_core->dev, "Failed to adjust eeprom record, ret:%d", ret); } return false; @@ -691,6 +691,7 @@ int ras_umc_hw_init(struct ras_core_context *ras_core) ras_umc->umc_err_data.umc_nps_mode = nps; ras_umc->umc_vram_type = ras_core->config->umc_cfg.umc_vram_type; + ras_umc->num_umc = ras_core->config->umc_cfg.num_umc; if (!ras_umc->umc_vram_type) { RAS_DEV_ERR(ras_core->dev, "Invalid UMC VRAM Type: %u!\n", ras_umc->umc_vram_type); diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h index ee7100f25f51..3d902b0d663a 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h @@ -46,6 +46,26 @@ #define UMC_ECC_NEW_DETECTED_TAG 0x1 #define UMC_INV_MEM_PFN (0xFFFFFFFFFFFFFFFF) +/* + * a flag to indicate v2 format channel index stored in eeprom + * + * v1: store channel index within a umc instance in eeprom + * range in UMC v12: 0 ~ 7 + * v2: store global channel index in eeprom + * range in UMC v12: 0 ~ 127 + * + * NOTE: it's better to store it in eeprom_table_record.mem_channel, + * but there is only 8 bits in mem_channel, and the channel number may + * increase in the future, we decide to save it in + * eeprom_table_record.retired_page. retired_page is useless in v2, + * we depend on eeprom_table_record.address instead of retired_page in v2. + * Only 48 bits are saved on eeprom, use bit 47 here. + * + * UMC_CHANNEL_IDX_V2 is replaced by nps value in cur_nps_retired_row_pfn + * in eeprom v3 format, so they have no conflict. + */ +#define UMC_CHANNEL_IDX_V2 BIT_ULL(47) + /* three column bits and one row bit in MCA address flip * in bad page retirement */ @@ -137,6 +157,7 @@ struct ras_umc_err_data { struct ras_umc { u32 umc_ip_version; u32 umc_vram_type; + u32 num_umc; const struct ras_umc_ip_func *ip_func; struct radix_tree_root root; struct mutex tree_lock; diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c b/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c index b809a2f21d73..6bf30dee8d17 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c @@ -64,6 +64,7 @@ static void __get_nps_pa_flip_bits(struct ras_core_context *ras_core, struct umc_flip_bits *flip_bits) { uint32_t vram_type = ras_core->ras_umc.umc_vram_type; + u32 num_umc = ras_core->ras_umc.num_umc; /* default setting */ flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_C2_BIT; @@ -74,41 +75,87 @@ static void __get_nps_pa_flip_bits(struct ras_core_context *ras_core, flip_bits->bit_num = 4; flip_bits->r13_in_pa = UMC_V12_0_PA_R13_BIT; - if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) { + if (num_umc == 16) { + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) { + flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH5_BIT; + flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_C2_BIT; + flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B1_BIT; + flip_bits->r13_in_pa = UMC_V12_0_PA_R12_BIT; + } else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) { + flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH4_BIT; + flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_CH5_BIT; + flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B0_BIT; + flip_bits->r13_in_pa = UMC_V12_0_PA_R11_BIT; + } + + switch (vram_type) { + case UMC_VRAM_TYPE_HBM: + /* other nps modes are taken as nps1 */ + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; + else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; + + break; + case UMC_VRAM_TYPE_HBM3E: + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; + flip_bits->flip_row_bit = 12; + + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; + else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R10_BIT; + + break; + default: + RAS_DEV_WARN(ras_core->dev, + "Unknown HBM type, set RAS retire flip bits to the value in NPS1 mode.\n"); + break; + } + } else if (num_umc == 8) { + /* num_umc == 8 base setting */ flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH5_BIT; flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_C2_BIT; flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B1_BIT; - flip_bits->r13_in_pa = UMC_V12_0_PA_R12_BIT; - } else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) { - flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH4_BIT; - flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_CH5_BIT; - flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B0_BIT; - flip_bits->r13_in_pa = UMC_V12_0_PA_R11_BIT; - } - - switch (vram_type) { - case UMC_VRAM_TYPE_HBM: - /* other nps modes are taken as nps1 */ - if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; - else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; - - break; - case UMC_VRAM_TYPE_HBM3E: - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; flip_bits->flip_row_bit = 12; + flip_bits->bit_num = 4; + flip_bits->r13_in_pa = UMC_V12_0_PA_R12_BIT; - if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; - else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R10_BIT; + /* only NPS1 and NPS2 are supported in this mode */ + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) { + flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH4_BIT; + flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_CH5_BIT; + flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B0_BIT; + flip_bits->r13_in_pa = UMC_V12_0_PA_R11_BIT; + } - break; - default: + switch (vram_type) { + case UMC_VRAM_TYPE_HBM: + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; + + /* other nps modes are taken as nps1 */ + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; + + break; + case UMC_VRAM_TYPE_HBM3E: + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R10_BIT; + + break; + default: + RAS_DEV_WARN(ras_core->dev, + "Unknown HBM type, set RAS retire flip bits to the value in NPS1 mode.\n"); + break; + } + } else { + /* num_umc is always 8 or 16 on umc v12, this path should not be + * entered. + */ RAS_DEV_WARN(ras_core->dev, - "Unknown HBM type, set RAS retire flip bits to the value in NPS1 mode.\n"); - break; + "Unsupported UMC number(%u), use default RAS flip bits setting.\n", + num_umc); } } @@ -413,16 +460,30 @@ static int convert_eeprom_record_to_nps_addr(struct ras_core_context *ras_core, static int umc_v12_0_eeprom_record_to_nps_record(struct ras_core_context *ras_core, struct eeprom_umc_record *record, uint32_t nps) { - uint64_t pa = 0; + uint64_t ch_idx_v2, pa = 0; + uint32_t save_nps; int ret = 0; - if (nps == EEPROM_RECORD_UMC_NPS_MODE(record) && !ras_fw_eeprom_supported(ras_core)) { - record->cur_nps_retired_row_pfn = EEPROM_RECORD_UMC_ADDR_PFN(record); - } else { - ret = convert_eeprom_record_to_nps_addr(ras_core, + save_nps = EEPROM_RECORD_UMC_NPS_MODE(record); + /* eeprom v2 has no stored nps, always convert if the flag is set */ + ch_idx_v2 = record->retired_row_pfn & UMC_CHANNEL_IDX_V2; + + if (save_nps || ch_idx_v2) { + if ((nps == save_nps) && !ras_fw_eeprom_supported(ras_core)) { + record->cur_nps_retired_row_pfn = + EEPROM_RECORD_UMC_ADDR_PFN(record); + } else { + ret = convert_eeprom_record_to_nps_addr(ras_core, record, &pa, nps); - if (!ret) - record->cur_nps_retired_row_pfn = RAS_ADDR_TO_PFN(pa); + if (!ret) + record->cur_nps_retired_row_pfn = RAS_ADDR_TO_PFN(pa); + } + } else { + /* old eeprom data format, the scope of channel index is + * limited to umc instance + */ + /* TODO */ + ret = -EOPNOTSUPP; } record->cur_nps = nps; diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c index f3fe47b12edc..d152c02419f5 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -566,7 +566,8 @@ anx6345_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void anx6345_bridge_disable(struct drm_bridge *bridge) +static void anx6345_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct anx6345 *anx6345 = bridge_to_anx6345(bridge); @@ -580,7 +581,8 @@ static void anx6345_bridge_disable(struct drm_bridge *bridge) anx6345_poweroff(anx6345); } -static void anx6345_bridge_enable(struct drm_bridge *bridge) +static void anx6345_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct anx6345 *anx6345 = bridge_to_anx6345(bridge); int err; @@ -600,11 +602,14 @@ static void anx6345_bridge_enable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs anx6345_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = anx6345_bridge_attach, .detach = anx6345_bridge_detach, .mode_valid = anx6345_bridge_mode_valid, - .disable = anx6345_bridge_disable, - .enable = anx6345_bridge_enable, + .atomic_disable = anx6345_bridge_disable, + .atomic_enable = anx6345_bridge_enable, }; static void unregister_i2c_dummy_clients(struct anx6345 *anx6345) @@ -658,6 +663,11 @@ static bool anx6345_get_chip_id(struct anx6345 *anx6345) return false; } +static void anx6345_panel_put_action(void *data) +{ + drm_panel_put(data); +} + static int anx6345_i2c_probe(struct i2c_client *client) { struct anx6345 *anx6345; @@ -686,6 +696,13 @@ static int anx6345_i2c_probe(struct i2c_client *client) if (err) DRM_DEBUG("No panel found\n"); + if (anx6345->panel) { + err = devm_add_action_or_reset(dev, anx6345_panel_put_action, + anx6345->panel); + if (err) + return err; + } + /* 1.2V digital core power regulator */ anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12"); if (IS_ERR(anx6345->dvdd12)) { diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c index ba0fc149a9e7..32186efa6f50 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c @@ -937,7 +937,8 @@ anx78xx_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void anx78xx_bridge_disable(struct drm_bridge *bridge) +static void anx78xx_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); @@ -975,7 +976,8 @@ unlock: mutex_unlock(&anx78xx->lock); } -static void anx78xx_bridge_enable(struct drm_bridge *bridge) +static void anx78xx_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); int err; @@ -992,12 +994,15 @@ static void anx78xx_bridge_enable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs anx78xx_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = anx78xx_bridge_attach, .detach = anx78xx_bridge_detach, .mode_valid = anx78xx_bridge_mode_valid, - .disable = anx78xx_bridge_disable, + .atomic_disable = anx78xx_bridge_disable, .mode_set = anx78xx_bridge_mode_set, - .enable = anx78xx_bridge_enable, + .atomic_enable = anx78xx_bridge_enable, }; static irqreturn_t anx78xx_hpd_threaded_handler(int irq, void *data) diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c index 1ed21a8713bf..169939d23310 100644 --- a/drivers/gpu/drm/bridge/aux-bridge.c +++ b/drivers/gpu/drm/bridge/aux-bridge.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -104,6 +105,9 @@ static int drm_aux_bridge_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs drm_aux_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = drm_aux_bridge_attach, }; diff --git a/drivers/gpu/drm/bridge/aux-hpd-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-bridge.c index f02a38a2638a..a2e525aa5788 100644 --- a/drivers/gpu/drm/bridge/aux-hpd-bridge.c +++ b/drivers/gpu/drm/bridge/aux-hpd-bridge.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -165,6 +166,9 @@ static int drm_aux_hpd_bridge_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs drm_aux_hpd_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = drm_aux_hpd_bridge_attach, }; diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c index a237c65ebd69..92d464727e41 100644 --- a/drivers/gpu/drm/bridge/chrontel-ch7033.c +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c @@ -330,7 +330,8 @@ static enum drm_mode_status ch7033_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void ch7033_bridge_disable(struct drm_bridge *bridge) +static void ch7033_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); @@ -338,7 +339,8 @@ static void ch7033_bridge_disable(struct drm_bridge *bridge) regmap_update_bits(priv->regmap, 0x52, RESETDB, 0x00); } -static void ch7033_bridge_enable(struct drm_bridge *bridge) +static void ch7033_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); @@ -514,11 +516,14 @@ static void ch7033_bridge_mode_set(struct drm_bridge *bridge, } static const struct drm_bridge_funcs ch7033_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = ch7033_bridge_attach, .detach = ch7033_bridge_detach, .mode_valid = ch7033_bridge_mode_valid, - .disable = ch7033_bridge_disable, - .enable = ch7033_bridge_enable, + .atomic_disable = ch7033_bridge_disable, + .atomic_enable = ch7033_bridge_enable, .mode_set = ch7033_bridge_mode_set, }; diff --git a/drivers/gpu/drm/bridge/cros-ec-anx7688.c b/drivers/gpu/drm/bridge/cros-ec-anx7688.c index a35dae9b56e2..da16c793e3ba 100644 --- a/drivers/gpu/drm/bridge/cros-ec-anx7688.c +++ b/drivers/gpu/drm/bridge/cros-ec-anx7688.c @@ -5,6 +5,7 @@ * Copyright 2020 Google LLC */ +#include #include #include #include @@ -92,6 +93,9 @@ static bool cros_ec_anx7688_bridge_mode_fixup(struct drm_bridge *bridge, } static const struct drm_bridge_funcs cros_ec_anx7688_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .mode_fixup = cros_ec_anx7688_bridge_mode_fixup, }; diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c index cc1f88e7873e..26cc72948f31 100644 --- a/drivers/gpu/drm/bridge/fsl-ldb.c +++ b/drivers/gpu/drm/bridge/fsl-ldb.c @@ -348,6 +348,7 @@ static int fsl_ldb_probe(struct platform_device *pdev) fsl_ldb->use_termination_resistor = true; fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(fsl_ldb->panel_bridge)) return PTR_ERR(fsl_ldb->panel_bridge); diff --git a/drivers/gpu/drm/bridge/lontium-lt8713sx.c b/drivers/gpu/drm/bridge/lontium-lt8713sx.c index cee485adf5e5..4a6420b876da 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8713sx.c +++ b/drivers/gpu/drm/bridge/lontium-lt8713sx.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -508,6 +509,9 @@ static const struct attribute_group *lt8713sx_attr_groups[] = { }; static const struct drm_bridge_funcs lt8713sx_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = lt8713sx_bridge_attach, }; diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c index 729b12b67470..df5d07e7644c 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8912b.c +++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c @@ -457,7 +457,8 @@ static void lt8912_bridge_mode_set(struct drm_bridge *bridge, drm_display_mode_to_videomode(adj, <->mode); } -static void lt8912_bridge_enable(struct drm_bridge *bridge) +static void lt8912_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct lt8912 *lt = bridge_to_lt8912(bridge); @@ -634,11 +635,14 @@ static const struct drm_edid *lt8912_bridge_edid_read(struct drm_bridge *bridge, } static const struct drm_bridge_funcs lt8912_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = lt8912_bridge_attach, .detach = lt8912_bridge_detach, .mode_valid = lt8912_bridge_mode_valid, .mode_set = lt8912_bridge_mode_set, - .enable = lt8912_bridge_enable, + .atomic_enable = lt8912_bridge_enable, .detect = lt8912_bridge_detect, .edid_read = lt8912_bridge_edid_read, }; diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c index f39d83a5ae37..6d97ae5640c4 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9211.c +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c @@ -660,6 +660,7 @@ static int lt9211_parse_dt(struct lt9211 *ctx) return ret; if (panel) { panel_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(panel_bridge)) return PTR_ERR(panel_bridge); } diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 9427cc2358ae..6625ba23761c 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -465,6 +465,9 @@ static void lt9611uxc_hdmi_audio_shutdown(struct drm_bridge *bridge, } static const struct drm_bridge_funcs lt9611uxc_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = lt9611uxc_bridge_attach, .mode_valid = lt9611uxc_bridge_mode_valid, .mode_set = lt9611uxc_bridge_mode_set, diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c index d1135dc3b99f..f97c8586273d 100644 --- a/drivers/gpu/drm/bridge/lvds-codec.c +++ b/drivers/gpu/drm/bridge/lvds-codec.c @@ -43,7 +43,8 @@ static int lvds_codec_attach(struct drm_bridge *bridge, bridge, flags); } -static void lvds_codec_enable(struct drm_bridge *bridge) +static void lvds_codec_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct lvds_codec *lvds_codec = to_lvds_codec(bridge); int ret; @@ -59,7 +60,8 @@ static void lvds_codec_enable(struct drm_bridge *bridge) gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 0); } -static void lvds_codec_disable(struct drm_bridge *bridge) +static void lvds_codec_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct lvds_codec *lvds_codec = to_lvds_codec(bridge); int ret; @@ -100,8 +102,8 @@ lvds_codec_atomic_get_input_bus_fmts(struct drm_bridge *bridge, static const struct drm_bridge_funcs funcs = { .attach = lvds_codec_attach, - .enable = lvds_codec_enable, - .disable = lvds_codec_disable, + .atomic_enable = lvds_codec_enable, + .atomic_disable = lvds_codec_disable, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_create_state = drm_atomic_helper_bridge_create_state, @@ -154,6 +156,7 @@ static int lvds_codec_probe(struct platform_device *pdev) lvds_codec->panel_bridge = devm_drm_panel_bridge_add_typed(dev, panel, lvds_codec->connector_type); + drm_panel_put(panel); if (IS_ERR(lvds_codec->panel_bridge)) return PTR_ERR(lvds_codec->panel_bridge); diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c index 2d02cc69f237..25ff0a0acc93 100644 --- a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c +++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c @@ -214,6 +214,9 @@ static int ge_b850v3_lvds_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs ge_b850v3_lvds_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = ge_b850v3_lvds_attach, .detect = ge_b850v3_lvds_bridge_detect, .edid_read = ge_b850v3_lvds_edid_read, diff --git a/drivers/gpu/drm/bridge/microchip-lvds.c b/drivers/gpu/drm/bridge/microchip-lvds.c index 5fb8633f43c5..dd4ffc9f4df3 100644 --- a/drivers/gpu/drm/bridge/microchip-lvds.c +++ b/drivers/gpu/drm/bridge/microchip-lvds.c @@ -167,6 +167,9 @@ static void mchp_lvds_atomic_disable(struct drm_bridge *bridge, } static const struct drm_bridge_funcs mchp_lvds_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = mchp_lvds_attach, .atomic_enable = mchp_lvds_atomic_enable, .atomic_disable = mchp_lvds_atomic_disable, diff --git a/drivers/gpu/drm/bridge/nxp-ptn3460.c b/drivers/gpu/drm/bridge/nxp-ptn3460.c index 7acb11f16dc1..a9b4ac2defc9 100644 --- a/drivers/gpu/drm/bridge/nxp-ptn3460.c +++ b/drivers/gpu/drm/bridge/nxp-ptn3460.c @@ -111,7 +111,8 @@ static int ptn3460_select_edid(struct ptn3460_bridge *ptn_bridge) return 0; } -static void ptn3460_pre_enable(struct drm_bridge *bridge) +static void ptn3460_pre_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge); int ret; @@ -139,7 +140,8 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge) ptn_bridge->enabled = true; } -static void ptn3460_disable(struct drm_bridge *bridge) +static void ptn3460_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge); @@ -163,7 +165,7 @@ static const struct drm_edid *ptn3460_edid_read(struct drm_bridge *bridge, int ret; power_off = !ptn_bridge->enabled; - ptn3460_pre_enable(&ptn_bridge->bridge); + ptn3460_pre_enable(&ptn_bridge->bridge, NULL); edid = kmalloc(EDID_LENGTH, GFP_KERNEL); if (!edid) { @@ -182,7 +184,7 @@ static const struct drm_edid *ptn3460_edid_read(struct drm_bridge *bridge, out: if (power_off) - ptn3460_disable(&ptn_bridge->bridge); + ptn3460_disable(&ptn_bridge->bridge, NULL); return drm_edid; } @@ -248,8 +250,11 @@ static int ptn3460_bridge_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs ptn3460_bridge_funcs = { - .pre_enable = ptn3460_pre_enable, - .disable = ptn3460_disable, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_pre_enable = ptn3460_pre_enable, + .atomic_disable = ptn3460_disable, .attach = ptn3460_bridge_attach, .edid_read = ptn3460_edid_read, }; diff --git a/drivers/gpu/drm/bridge/of-display-mode-bridge.c b/drivers/gpu/drm/bridge/of-display-mode-bridge.c index e66dae168fd0..af3e727e91c9 100644 --- a/drivers/gpu/drm/bridge/of-display-mode-bridge.c +++ b/drivers/gpu/drm/bridge/of-display-mode-bridge.c @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -52,6 +53,9 @@ static int of_display_mode_bridge_get_modes(struct drm_bridge *bridge, } static const struct drm_bridge_funcs of_display_mode_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = of_display_mode_bridge_attach, .get_modes = of_display_mode_bridge_get_modes, }; diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 62af499f1f5c..02388a3de626 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -294,7 +294,7 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel, return (void *)panel_bridge; panel_bridge->connector_type = connector_type; - panel_bridge->panel = panel; + panel_bridge->panel = drm_panel_get(panel); panel_bridge->bridge.of_node = panel->dev->of_node; panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES; @@ -316,6 +316,7 @@ EXPORT_SYMBOL(drm_panel_bridge_add_typed); void drm_panel_bridge_remove(struct drm_bridge *bridge) { struct panel_bridge *panel_bridge; + struct drm_panel *panel; if (!bridge) return; @@ -326,10 +327,12 @@ void drm_panel_bridge_remove(struct drm_bridge *bridge) } panel_bridge = drm_bridge_to_panel_bridge(bridge); + panel = panel_bridge->panel; drm_bridge_remove(bridge); /* TODO remove this after reworking panel_bridge lifetime */ - devm_drm_put_bridge(panel_bridge->panel->dev, bridge); + devm_drm_put_bridge(panel->dev, bridge); + drm_panel_put(panel); } EXPORT_SYMBOL(drm_panel_bridge_remove); @@ -357,11 +360,16 @@ EXPORT_SYMBOL(drm_panel_bridge_set_orientation); static void devm_drm_panel_bridge_release(struct device *dev, void *res) { struct drm_bridge *bridge = *(struct drm_bridge **)res; + struct panel_bridge *panel_bridge; + struct drm_panel *panel; if (!bridge) return; + panel_bridge = drm_bridge_to_panel_bridge(bridge); + panel = panel_bridge->panel; drm_bridge_remove(bridge); + drm_panel_put(panel); } /** @@ -507,8 +515,10 @@ struct drm_bridge *devm_drm_of_get_bridge(struct device *dev, if (ret) return ERR_PTR(ret); - if (panel) + if (panel) { bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); + } return bridge; } @@ -541,8 +551,10 @@ struct drm_bridge *drmm_of_get_bridge(struct drm_device *drm, if (ret) return ERR_PTR(ret); - if (panel) + if (panel) { bridge = drmm_panel_bridge_add(drm, panel); + drm_panel_put(panel); + } return bridge; } diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c index f879a1df077d..33844db753aa 100644 --- a/drivers/gpu/drm/bridge/parade-ps8622.c +++ b/drivers/gpu/drm/bridge/parade-ps8622.c @@ -336,7 +336,8 @@ static const struct backlight_ops ps8622_backlight_ops = { .update_status = ps8622_backlight_update, }; -static void ps8622_pre_enable(struct drm_bridge *bridge) +static void ps8622_pre_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge); int ret; @@ -381,13 +382,15 @@ static void ps8622_pre_enable(struct drm_bridge *bridge) ps8622->enabled = true; } -static void ps8622_disable(struct drm_bridge *bridge) +static void ps8622_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { /* Delay after panel is disabled */ msleep(PS8622_PWMO_END_T12_MS); } -static void ps8622_post_disable(struct drm_bridge *bridge) +static void ps8622_post_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge); @@ -428,9 +431,12 @@ static int ps8622_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs ps8622_bridge_funcs = { - .pre_enable = ps8622_pre_enable, - .disable = ps8622_disable, - .post_disable = ps8622_post_disable, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_pre_enable = ps8622_pre_enable, + .atomic_disable = ps8622_disable, + .atomic_post_disable = ps8622_post_disable, .attach = ps8622_attach, }; diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index 5788066d479e..e2fc69fc51b6 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -1919,7 +1919,7 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host, /* * of_graph_get_remote_node() produces a noisy error message if port * node isn't found and the absence of the port is a legit case here, - * so at first we silently check whether graph presents in the + * so at first we silently check whether a graph is present in the * device-tree node. */ if (!of_graph_is_present(np)) @@ -1934,6 +1934,7 @@ of_find_panel_or_bridge: panel = of_drm_find_panel(remote); if (!IS_ERR(panel)) { next_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(next_bridge)) { ret = PTR_ERR(next_bridge); next_bridge = NULL; // Inhibit the cleanup action on an ERR_PTR diff --git a/drivers/gpu/drm/bridge/sii9234.c b/drivers/gpu/drm/bridge/sii9234.c index bb1bed03eb5b..be67642ab7d3 100644 --- a/drivers/gpu/drm/bridge/sii9234.c +++ b/drivers/gpu/drm/bridge/sii9234.c @@ -13,6 +13,7 @@ * Dharam Kumar */ #include +#include #include #include #include @@ -339,7 +340,7 @@ static int sii9234_cbus_reset(struct sii9234 *ctx) return sii9234_clear_error(ctx); } -/* Require to chek mhl imformation of samsung in cbus_init_register */ +/* Require to check mhl information of samsung in cbus_init_register */ static int sii9234_cbus_init(struct sii9234 *ctx) { cbus_writeb(ctx, 0x07, 0xF2); @@ -614,7 +615,7 @@ static void sii9234_cable_out(struct sii9234 *ctx) disable_irq(to_i2c_client(ctx->dev)->irq); tpi_writeb(ctx, TPI_DPD_REG, 0); - /* Turn on&off hpd festure for only QCT HDMI */ + /* Turn on&off hpd feature for only QCT HDMI */ sii9234_hw_off(ctx); ctx->state = ST_OFF; @@ -708,7 +709,7 @@ static enum sii9234_state sii9234_rsen_change(struct sii9234 *ctx) { int value; - /* Work_around code to handle wrong interrupt */ + /* Workaround code to handle wrong interrupt */ if (ctx->state != ST_RGND_1K) { dev_err(ctx->dev, "RSEN_HIGH without RGND_1K\n"); return ST_FAILURE; @@ -723,9 +724,9 @@ static enum sii9234_state sii9234_rsen_change(struct sii9234 *ctx) } dev_dbg(ctx->dev, "RSEN lost\n"); /* - * Once RSEN loss is confirmed,we need to check - * based on cable status and chip power status,whether - * it is SINK Loss(HDMI cable not connected, TV Off) + * Once RSEN loss is confirmed, we need to check + * based on cable status and chip power status, whether + * it is SINK Loss (HDMI cable not connected, TV Off) * or MHL cable disconnection * TODO: Define the below mhl_disconnection() */ @@ -820,26 +821,22 @@ static int sii9234_init_resources(struct sii9234 *ctx, int ret; if (!ctx->dev->of_node) { - dev_err(ctx->dev, "not DT device\n"); + dev_err(ctx->dev, "no DT device\n"); return -ENODEV; } ctx->gpio_reset = devm_gpiod_get(ctx->dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(ctx->gpio_reset)) { - dev_err(ctx->dev, "failed to get reset gpio from DT\n"); - return PTR_ERR(ctx->gpio_reset); - } + if (IS_ERR(ctx->gpio_reset)) + return dev_err_probe(ctx->dev, PTR_ERR(ctx->gpio_reset), + "failed to get reset gpio from DT\n"); ctx->supplies[0].supply = "avcc12"; ctx->supplies[1].supply = "avcc33"; ctx->supplies[2].supply = "iovcc18"; ctx->supplies[3].supply = "cvcc12"; ret = devm_regulator_bulk_get(ctx->dev, 4, ctx->supplies); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(ctx->dev, "regulator_bulk failed\n"); - return ret; - } + if (ret) + return dev_err_probe(ctx->dev, ret, "regulator_bulk failed\n"); ctx->client[I2C_MHL] = client; @@ -878,6 +875,9 @@ static enum drm_mode_status sii9234_mode_valid(struct drm_bridge *bridge, } static const struct drm_bridge_funcs sii9234_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .mode_valid = sii9234_mode_valid, }; @@ -911,10 +911,9 @@ static int sii9234_probe(struct i2c_client *client) sii9234_irq_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "sii9234", ctx); - if (ret < 0) { - dev_err(dev, "failed to install IRQ handler\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to install IRQ handler\n"); ret = sii9234_init_resources(ctx, client); if (ret < 0) diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c index 982306eb4f0a..22c81b38b41e 100644 --- a/drivers/gpu/drm/bridge/sil-sii8620.c +++ b/drivers/gpu/drm/bridge/sil-sii8620.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -2280,6 +2281,9 @@ static bool sii8620_mode_fixup(struct drm_bridge *bridge, } static const struct drm_bridge_funcs sii8620_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = sii8620_attach, .detach = sii8620_detach, .mode_fixup = sii8620_mode_fixup, diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c index cc13c98f9be6..3fba7002322e 100644 --- a/drivers/gpu/drm/bridge/simple-bridge.c +++ b/drivers/gpu/drm/bridge/simple-bridge.c @@ -132,7 +132,8 @@ static int simple_bridge_attach(struct drm_bridge *bridge, return 0; } -static void simple_bridge_enable(struct drm_bridge *bridge) +static void simple_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge); int ret; @@ -146,7 +147,8 @@ static void simple_bridge_enable(struct drm_bridge *bridge) gpiod_set_value_cansleep(sbridge->enable, 1); } -static void simple_bridge_disable(struct drm_bridge *bridge) +static void simple_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge); @@ -157,9 +159,12 @@ static void simple_bridge_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs simple_bridge_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = simple_bridge_attach, - .enable = simple_bridge_enable, - .disable = simple_bridge_disable, + .atomic_enable = simple_bridge_enable, + .atomic_disable = simple_bridge_disable, }; static int simple_bridge_probe(struct platform_device *pdev) diff --git a/drivers/gpu/drm/bridge/ssd2825.c b/drivers/gpu/drm/bridge/ssd2825.c index 00a4ed4a0700..b0142d6d40dc 100644 --- a/drivers/gpu/drm/bridge/ssd2825.c +++ b/drivers/gpu/drm/bridge/ssd2825.c @@ -301,6 +301,7 @@ static int ssd2825_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_d if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); } diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c index 12b43245bb8f..f55a56cbd88f 100644 --- a/drivers/gpu/drm/bridge/tc358764.c +++ b/drivers/gpu/drm/bridge/tc358764.c @@ -266,7 +266,8 @@ static void tc358764_reset(struct tc358764 *ctx) usleep_range(1000, 2000); } -static void tc358764_post_disable(struct drm_bridge *bridge) +static void tc358764_post_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tc358764 *ctx = bridge_to_tc358764(bridge); int ret; @@ -278,7 +279,8 @@ static void tc358764_post_disable(struct drm_bridge *bridge) dev_err(ctx->dev, "error disabling regulators (%d)\n", ret); } -static void tc358764_pre_enable(struct drm_bridge *bridge) +static void tc358764_pre_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tc358764 *ctx = bridge_to_tc358764(bridge); int ret; @@ -303,8 +305,11 @@ static int tc358764_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs tc358764_bridge_funcs = { - .post_disable = tc358764_post_disable, - .pre_enable = tc358764_pre_enable, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_post_disable = tc358764_post_disable, + .atomic_pre_enable = tc358764_pre_enable, .attach = tc358764_attach, }; diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index 966c3eae437e..8f1648f799a7 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -527,7 +527,7 @@ static ssize_t tc_aux_transfer(struct drm_dp_aux *aux, * address-only transfer */ if (size) - size = FIELD_GET(AUX_BYTES, auxstatus); + size = min_t(size_t, size, FIELD_GET(AUX_BYTES, auxstatus)); msg->reply = FIELD_GET(AUX_STATUS, auxstatus); switch (request) { @@ -2329,6 +2329,7 @@ static int tc_probe_dpi_bridge_endpoint(struct tc_data *tc) if (panel) { bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); } @@ -2359,6 +2360,7 @@ static int tc_probe_edp_bridge_endpoint(struct tc_data *tc) struct drm_bridge *panel_bridge; panel_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(panel_bridge)) return PTR_ERR(panel_bridge); diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c index 0d85120fcc7a..112a421f99b5 100644 --- a/drivers/gpu/drm/bridge/tc358768.c +++ b/drivers/gpu/drm/bridge/tc358768.c @@ -479,6 +479,7 @@ static int tc358768_dsi_host_attach(struct mipi_dsi_host *host, if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); diff --git a/drivers/gpu/drm/bridge/tda998x_drv.c b/drivers/gpu/drm/bridge/tda998x_drv.c index 6c427bc75896..16c745591175 100644 --- a/drivers/gpu/drm/bridge/tda998x_drv.c +++ b/drivers/gpu/drm/bridge/tda998x_drv.c @@ -1410,7 +1410,8 @@ static enum drm_mode_status tda998x_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void tda998x_bridge_enable(struct drm_bridge *bridge) +static void tda998x_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tda998x_priv *priv = bridge_to_tda998x_priv(bridge); @@ -1428,7 +1429,8 @@ static void tda998x_bridge_enable(struct drm_bridge *bridge) } } -static void tda998x_bridge_disable(struct drm_bridge *bridge) +static void tda998x_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tda998x_priv *priv = bridge_to_tda998x_priv(bridge); @@ -1745,12 +1747,15 @@ static void tda998x_bridge_hpd_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs tda998x_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = tda998x_bridge_attach, .detach = tda998x_bridge_detach, .mode_valid = tda998x_bridge_mode_valid, - .disable = tda998x_bridge_disable, + .atomic_disable = tda998x_bridge_disable, .mode_set = tda998x_bridge_mode_set, - .enable = tda998x_bridge_enable, + .atomic_enable = tda998x_bridge_enable, .edid_read = tda998x_bridge_edid_read, .detect = tda998x_bridge_detect, .hpd_enable = tda998x_bridge_hpd_enable, diff --git a/drivers/gpu/drm/bridge/thc63lvd1024.c b/drivers/gpu/drm/bridge/thc63lvd1024.c index c804222846c3..679b424ba0c3 100644 --- a/drivers/gpu/drm/bridge/thc63lvd1024.c +++ b/drivers/gpu/drm/bridge/thc63lvd1024.c @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -81,7 +82,8 @@ static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void thc63_enable(struct drm_bridge *bridge) +static void thc63_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct thc63_dev *thc63 = to_thc63(bridge); int ret; @@ -97,7 +99,8 @@ static void thc63_enable(struct drm_bridge *bridge) gpiod_set_value(thc63->oe, 1); } -static void thc63_disable(struct drm_bridge *bridge) +static void thc63_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct thc63_dev *thc63 = to_thc63(bridge); int ret; @@ -112,10 +115,13 @@ static void thc63_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs thc63_bridge_func = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = thc63_attach, .mode_valid = thc63_mode_valid, - .enable = thc63_enable, - .disable = thc63_disable, + .atomic_enable = thc63_enable, + .atomic_disable = thc63_disable, }; static int thc63_parse_dt(struct thc63_dev *thc63) diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c index 6f06f7b4e992..f0a14f52aaac 100644 --- a/drivers/gpu/drm/bridge/ti-tfp410.c +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -174,14 +174,16 @@ static void tfp410_detach(struct drm_bridge *bridge) } } -static void tfp410_enable(struct drm_bridge *bridge) +static void tfp410_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); gpiod_set_value_cansleep(dvi->powerdown, 0); } -static void tfp410_disable(struct drm_bridge *bridge) +static void tfp410_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); @@ -242,8 +244,8 @@ static int tfp410_atomic_check(struct drm_bridge *bridge, static const struct drm_bridge_funcs tfp410_bridge_funcs = { .attach = tfp410_attach, .detach = tfp410_detach, - .enable = tfp410_enable, - .disable = tfp410_disable, + .atomic_enable = tfp410_enable, + .atomic_disable = tfp410_disable, .mode_valid = tfp410_mode_valid, .atomic_create_state = drm_atomic_helper_bridge_create_state, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, diff --git a/drivers/gpu/drm/bridge/ti-tpd12s015.c b/drivers/gpu/drm/bridge/ti-tpd12s015.c index 136e47ad1a10..e7b172ab7f55 100644 --- a/drivers/gpu/drm/bridge/ti-tpd12s015.c +++ b/drivers/gpu/drm/bridge/ti-tpd12s015.c @@ -19,6 +19,7 @@ #include #include +#include #include struct tpd12s015_device { @@ -96,6 +97,9 @@ static void tpd12s015_hpd_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs tpd12s015_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = tpd12s015_attach, .detach = tpd12s015_detach, .detect = tpd12s015_bridge_detect, diff --git a/drivers/gpu/drm/bridge/waveshare-dsi.c b/drivers/gpu/drm/bridge/waveshare-dsi.c index ded57f298d64..420f3b870a74 100644 --- a/drivers/gpu/drm/bridge/waveshare-dsi.c +++ b/drivers/gpu/drm/bridge/waveshare-dsi.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -92,7 +93,8 @@ static int ws_bridge_bridge_attach(struct drm_bridge *bridge, &ws->bridge, flags); } -static void ws_bridge_bridge_enable(struct drm_bridge *bridge) +static void ws_bridge_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ws_bridge *ws = bridge_to_ws_bridge(bridge); @@ -100,7 +102,8 @@ static void ws_bridge_bridge_enable(struct drm_bridge *bridge) backlight_enable(ws->backlight); } -static void ws_bridge_bridge_disable(struct drm_bridge *bridge) +static void ws_bridge_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ws_bridge *ws = bridge_to_ws_bridge(bridge); @@ -109,8 +112,11 @@ static void ws_bridge_bridge_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs ws_bridge_bridge_funcs = { - .enable = ws_bridge_bridge_enable, - .disable = ws_bridge_bridge_disable, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_enable = ws_bridge_bridge_enable, + .atomic_disable = ws_bridge_bridge_disable, .attach = ws_bridge_bridge_attach, }; @@ -163,6 +169,7 @@ static int ws_bridge_probe(struct i2c_client *i2c) return dev_err_probe(dev, ret, "Failed to find remote panel\n"); ws->next_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(ws->next_bridge)) return PTR_ERR(ws->next_bridge); diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 92f8a2d7aab4..8b54069fa53a 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -147,7 +147,7 @@ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, to_drm_bridge_connector(connector); /* Notify all bridges in the pipeline of hotplug events. */ - drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) { + drm_for_each_bridge_in_chain(bridge_connector->encoder, bridge) { if (bridge->funcs->hpd_notify) bridge->funcs->hpd_notify(bridge, connector, status); } @@ -854,7 +854,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, * detection are available, we don't support hotplug detection at all. */ connector_type = DRM_MODE_CONNECTOR_Unknown; - drm_for_each_bridge_in_chain_scoped(encoder, bridge) { + drm_for_each_bridge_in_chain(encoder, bridge) { if (!bridge->interlace_allowed) connector->interlace_allowed = false; if (!bridge->ycbcr_420_allowed) diff --git a/drivers/gpu/drm/display/drm_dp_tunnel.c b/drivers/gpu/drm/display/drm_dp_tunnel.c index 76c6bc84a806..d29ad4116e64 100644 --- a/drivers/gpu/drm/display/drm_dp_tunnel.c +++ b/drivers/gpu/drm/display/drm_dp_tunnel.c @@ -111,6 +111,8 @@ DPTUN_REG(DP_ALLOCATED_BW) | \ DPTUN_REG(DP_TUNNELING_MAX_LINK_RATE) | \ DPTUN_REG(DP_TUNNELING_MAX_LANE_COUNT) | \ + DPTUN_REG(DP_TUNNELING_MAIN_LINK_CHANNEL_CODING) | \ + DPTUN_REG(DP_TUNNELING_128B132B_LINK_RATE) | \ DPTUN_REG(DP_DPTX_BW_ALLOCATION_MODE_CONTROL)) static const DECLARE_BITMAP(dptun_info_regs, 64) = { @@ -140,11 +142,14 @@ struct drm_dp_tunnel { int estimated_bw; int allocated_bw; + u8 dprx_128b132b_rates; int max_dprx_rate; u8 max_dprx_lane_count; u8 adapter_id; + bool dprx_128b132b_support:1; + bool dprx_128b132b_lane0_mapping_support:1; bool bw_alloc_supported:1; bool bw_alloc_enabled:1; bool has_io_error:1; @@ -260,9 +265,46 @@ static int tunnel_reg_bw_granularity(const struct drm_dp_tunnel_regs *regs) return (250000 << gr) / 8; } +static bool tunnel_reg_dprx_128b132b_support(const struct drm_dp_tunnel_regs *regs) +{ + return tunnel_reg(regs, DP_TUNNELING_MAIN_LINK_CHANNEL_CODING) & DP_128B132B_DP_SUPPORTED; +} + +static bool tunnel_reg_dprx_128b132b_lane0_mapping_support(const struct drm_dp_tunnel_regs *regs) +{ + return tunnel_reg(regs, DP_TUNNELING_128B132B_LINK_RATE) & + DP_TUNNELING_128B132B_LL_LANE0_MAPPING_SUPPORT; +} + +static u8 tunnel_reg_dprx_128b132b_rates(const struct drm_dp_tunnel_regs *regs) +{ + if (!tunnel_reg_dprx_128b132b_support(regs)) + return 0; + + return tunnel_reg(regs, DP_TUNNELING_128B132B_LINK_RATE) & + DP_TUNNELING_128B132B_LINK_RATE_MASK; +} + +static u8 max_128b132b_rate(u8 rates) +{ + if (rates & DP_TUNNELING_20GBPS_PER_LANE_SUPPORT) + return DP_TUNNELING_20GBPS_PER_LANE_SUPPORT; + else if (rates & DP_TUNNELING_13_5GBPS_PER_LANE_SUPPORT) + return DP_TUNNELING_13_5GBPS_PER_LANE_SUPPORT; + else if (rates & DP_TUNNELING_10GBPS_PER_LANE_SUPPORT) + return DP_TUNNELING_10GBPS_PER_LANE_SUPPORT; + + WARN_ON(rates); + + return 0; +} + static int tunnel_reg_max_dprx_rate(const struct drm_dp_tunnel_regs *regs) { - u8 bw_code = tunnel_reg(regs, DP_TUNNELING_MAX_LINK_RATE); + u8 bw_code = max_128b132b_rate(tunnel_reg_dprx_128b132b_rates(regs)); + + if (!bw_code) + bw_code = tunnel_reg(regs, DP_TUNNELING_MAX_LINK_RATE); return drm_dp_bw_code_to_link_rate(bw_code); } @@ -706,6 +748,23 @@ static bool update_dprx_caps(struct drm_dp_tunnel *tunnel, const struct drm_dp_t { bool changed = false; + if (tunnel_reg_dprx_128b132b_support(regs) != tunnel->dprx_128b132b_support) { + tunnel->dprx_128b132b_support = tunnel_reg_dprx_128b132b_support(regs); + changed = true; + } + + if (tunnel_reg_dprx_128b132b_lane0_mapping_support(regs) != + tunnel->dprx_128b132b_lane0_mapping_support) { + tunnel->dprx_128b132b_lane0_mapping_support = + tunnel_reg_dprx_128b132b_lane0_mapping_support(regs); + changed = true; + } + + if (tunnel_reg_dprx_128b132b_rates(regs) != tunnel->dprx_128b132b_rates) { + tunnel->dprx_128b132b_rates = tunnel_reg_dprx_128b132b_rates(regs); + changed = true; + } + if (tunnel_reg_max_dprx_rate(regs) != tunnel->max_dprx_rate) { tunnel->max_dprx_rate = tunnel_reg_max_dprx_rate(regs); changed = true; @@ -1331,6 +1390,61 @@ int drm_dp_tunnel_handle_irq(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_aux *a } EXPORT_SYMBOL(drm_dp_tunnel_handle_irq); +/** + * drm_dp_tunnel_128b132b_supported - Query if 128b132b is supported by the tunnel's DPRX + * @tunnel: Tunnel object + * + * The function is used to query if 128b132b is supported by the DPRX connected + * to @tunnel. + * + * Returns %true if 128b132b is supported by the DPRX. + */ +bool drm_dp_tunnel_128b132b_supported(const struct drm_dp_tunnel *tunnel) +{ + return tunnel->dprx_128b132b_support; +} +EXPORT_SYMBOL(drm_dp_tunnel_128b132b_supported); + +/** + * drm_dp_tunnel_128b132b_lane0_mapping_supported - Check 128b/132b lane 0 mapping support + * @tunnel: Tunnel object + * + * Check whether the DP-out adapter always maps lane 0 as expected by the + * DPRX on a tunneled 128b/132b link. If the function returns %true, one- and + * two-lane configurations with UHBR link rates can always be used. If it + * returns %false, using one or two lanes with UHBR link rates may cause a + * lane-count conversion failure in the DPRX, requiring corrective action by + * the source during link training. See DP Standard v2.1b, section + * 3.5.2.16.3, 128b/132b DPRX Lane Count Conversion Failure Indication and + * Corrective Action. + * + * A four-lane configuration can always be used, provided that the DPRX + * supports it, regardless of the function's return value. + * + * Returns %true if the DP-out adapter supports the 128b/132b lane 0 mapping. + */ +bool drm_dp_tunnel_128b132b_lane0_mapping_supported(const struct drm_dp_tunnel *tunnel) +{ + return tunnel->dprx_128b132b_lane0_mapping_support; +} +EXPORT_SYMBOL(drm_dp_tunnel_128b132b_lane0_mapping_supported); + +/** + * drm_dp_tunnel_128b132b_dprx_rates - Query the supported 128b132b rates of the tunnel's DPRX + * @tunnel: Tunnel object + * + * The function is used to query the supported 128b132b rates of the DPRX connected + * to @tunnel. Note that the related DP_128B132B_SUPPROTED_LINK_RATES DPCD + * register will indicate no supported 128B132B rates for a tunneled DPRX. + * + * Returns the mask of supported 128b132b rates. + */ +u8 drm_dp_tunnel_128b132b_dprx_rates(const struct drm_dp_tunnel *tunnel) +{ + return tunnel->dprx_128b132b_rates; +} +EXPORT_SYMBOL(drm_dp_tunnel_128b132b_dprx_rates); + /** * drm_dp_tunnel_max_dprx_rate - Query the maximum rate of the tunnel's DPRX * @tunnel: Tunnel object diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 735ab7badc2e..e5c8ef06caed 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -1554,7 +1554,7 @@ drm_atomic_add_encoder_bridges(struct drm_atomic_commit *state, "Adding all bridges for [encoder:%d:%s] to %p\n", encoder->base.id, encoder->name, state); - drm_for_each_bridge_in_chain_scoped(encoder, bridge) { + drm_for_each_bridge_in_chain(encoder, bridge) { /* Skip bridges that don't implement the atomic state hooks. */ if (!bridge->funcs->atomic_duplicate_state) continue; diff --git a/drivers/gpu/drm/drm_blend.c b/drivers/gpu/drm/drm_blend.c index 2f0d1ba285be..d17ae964fb21 100644 --- a/drivers/gpu/drm/drm_blend.c +++ b/drivers/gpu/drm/drm_blend.c @@ -563,10 +563,10 @@ EXPORT_SYMBOL(drm_atomic_normalize_zpos); /** * drm_plane_create_blend_mode_property - create a new blend mode property * @plane: drm plane - * @supported_modes: bitmask of supported modes, must include - * BIT(DRM_MODE_BLEND_PREMULTI). Current DRM assumption is - * that alpha is premultiplied, and old userspace can break if - * the property defaults to anything else. + * @supported_modes: bitmask of supported modes. When + * BIT(DRM_MODE_BLEND_PREMULTI) is included, it will be used + * as the default. Otherwise, the default will fallback to one + * of the supported modes. * * This creates a new property describing the blend mode. * @@ -599,13 +599,14 @@ int drm_plane_create_blend_mode_property(struct drm_plane *plane, { DRM_MODE_BLEND_PREMULTI, "Pre-multiplied" }, { DRM_MODE_BLEND_COVERAGE, "Coverage" }, }; + unsigned int default_mode; unsigned int valid_mode_mask = BIT(DRM_MODE_BLEND_PIXEL_NONE) | BIT(DRM_MODE_BLEND_PREMULTI) | BIT(DRM_MODE_BLEND_COVERAGE); int i; if (WARN_ON((supported_modes & ~valid_mode_mask) || - ((supported_modes & BIT(DRM_MODE_BLEND_PREMULTI)) == 0))) + (supported_modes == 0))) return -EINVAL; prop = drm_property_create(dev, DRM_MODE_PROP_ENUM, @@ -630,7 +631,14 @@ int drm_plane_create_blend_mode_property(struct drm_plane *plane, } } - drm_object_attach_property(&plane->base, prop, DRM_MODE_BLEND_PREMULTI); + if (supported_modes & BIT(DRM_MODE_BLEND_PREMULTI)) + default_mode = DRM_MODE_BLEND_PREMULTI; + else if (supported_modes & BIT(DRM_MODE_BLEND_COVERAGE)) + default_mode = DRM_MODE_BLEND_COVERAGE; + else + default_mode = DRM_MODE_BLEND_PIXEL_NONE; + + drm_object_attach_property(&plane->base, prop, default_mode); plane->blend_mode_property = prop; return 0; diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 6ead9b75ae3d..83f1809a5d37 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -555,11 +555,6 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = { .atomic_destroy_state = drm_bridge_atomic_destroy_priv_state, }; -static bool drm_bridge_is_atomic(struct drm_bridge *bridge) -{ - return bridge->funcs->atomic_create_state != NULL; -} - /** * drm_bridge_attach - attach the bridge to an encoder's chain * @@ -629,9 +624,8 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, goto err_reset_bridge; } - if (drm_bridge_is_atomic(bridge)) - drm_atomic_private_obj_init(bridge->dev, &bridge->base, - &drm_bridge_priv_state_funcs); + drm_atomic_private_obj_init(bridge->dev, &bridge->base, + &drm_bridge_priv_state_funcs); return 0; @@ -664,8 +658,7 @@ void drm_bridge_detach(struct drm_bridge *bridge) if (WARN_ON(!bridge->dev)) return; - if (drm_bridge_is_atomic(bridge)) - drm_atomic_private_obj_fini(&bridge->base); + drm_atomic_private_obj_fini(&bridge->base); if (bridge->funcs->detach) bridge->funcs->detach(bridge); @@ -690,9 +683,9 @@ void drm_bridge_detach(struct drm_bridge *bridge) * disable the bridge automatically. * * The enable and disable operations are split in - * &drm_bridge_funcs.pre_enable, &drm_bridge_funcs.enable, - * &drm_bridge_funcs.disable and &drm_bridge_funcs.post_disable to provide - * finer-grained control. + * &drm_bridge_funcs.atomic_pre_enable, &drm_bridge_funcs.atomic_enable, + * &drm_bridge_funcs.atomic_disable and &drm_bridge_funcs.atomic_post_disable + * to provide finer-grained control. * * Bridge drivers may implement the legacy version of those operations, or * the atomic version (prefixed with atomic\_), in which case they shall also @@ -833,10 +826,9 @@ EXPORT_SYMBOL(drm_bridge_chain_mode_set); * @bridge: bridge control structure * @state: atomic state being committed * - * Calls &drm_bridge_funcs.atomic_disable (falls back on - * &drm_bridge_funcs.disable) op for all the bridges in the encoder chain, - * starting from the last bridge to the first. These are called before calling - * &drm_encoder_helper_funcs.atomic_disable + * Calls &drm_bridge_funcs.atomic_disable op for all the bridges in the encoder + * chain, starting from the last bridge to the first. These are called before + * calling &drm_encoder_helper_funcs.atomic_disable * * Note: the bridge passed should be the one closest to the encoder */ @@ -852,11 +844,8 @@ void drm_atomic_bridge_chain_disable(struct drm_bridge *bridge, encoder = bridge->encoder; mutex_lock(&encoder->bridge_chain_mutex); list_for_each_entry_reverse(iter, &encoder->bridge_chain, chain_node) { - if (iter->funcs->atomic_disable) { + if (iter->funcs->atomic_disable) iter->funcs->atomic_disable(iter, state); - } else if (iter->funcs->disable) { - iter->funcs->disable(iter); - } if (iter == bridge) break; @@ -870,8 +859,6 @@ static void drm_atomic_bridge_call_post_disable(struct drm_bridge *bridge, { if (state && bridge->funcs->atomic_post_disable) bridge->funcs->atomic_post_disable(bridge, state); - else if (bridge->funcs->post_disable) - bridge->funcs->post_disable(bridge); } /** @@ -880,10 +867,9 @@ static void drm_atomic_bridge_call_post_disable(struct drm_bridge *bridge, * @bridge: bridge control structure * @state: atomic state being committed * - * Calls &drm_bridge_funcs.atomic_post_disable (falls back on - * &drm_bridge_funcs.post_disable) op for all the bridges in the encoder chain, - * starting from the first bridge to the last. These are called after completing - * &drm_encoder_helper_funcs.atomic_disable + * Calls &drm_bridge_funcs.atomic_post_disable op for all the bridges in the + * encoder chain, starting from the first bridge to the last. These are called + * after completing &drm_encoder_helper_funcs.atomic_disable * * If a bridge sets @pre_enable_prev_first, then the @post_disable for that * bridge will be called before the previous one to reverse the @pre_enable @@ -967,8 +953,6 @@ static void drm_atomic_bridge_call_pre_enable(struct drm_bridge *bridge, { if (state && bridge->funcs->atomic_pre_enable) bridge->funcs->atomic_pre_enable(bridge, state); - else if (bridge->funcs->pre_enable) - bridge->funcs->pre_enable(bridge); } /** @@ -977,10 +961,9 @@ static void drm_atomic_bridge_call_pre_enable(struct drm_bridge *bridge, * @bridge: bridge control structure * @state: atomic state being committed * - * Calls &drm_bridge_funcs.atomic_pre_enable (falls back on - * &drm_bridge_funcs.pre_enable) op for all the bridges in the encoder chain, - * starting from the last bridge to the first. These are called before calling - * &drm_encoder_helper_funcs.atomic_enable + * Calls &drm_bridge_funcs.atomic_pre_enable op for all the bridges in the + * encoder chain, starting from the last bridge to the first. These are called + * before calling &drm_encoder_helper_funcs.atomic_enable * * If a bridge sets @pre_enable_prev_first, then the pre_enable for the * prev bridge will be called before pre_enable of this bridge. @@ -1057,10 +1040,9 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_pre_enable); * @first_bridge: bridge control structure * @state: atomic state being committed * - * Calls &drm_bridge_funcs.atomic_enable (falls back on - * &drm_bridge_funcs.enable) op for all the bridges in the encoder chain, - * starting from the first bridge to the last. These are called after completing - * &drm_encoder_helper_funcs.atomic_enable + * Calls &drm_bridge_funcs.atomic_enable op for all the bridges in the encoder + * chain, starting from the first bridge to the last. These are called after + * completing &drm_encoder_helper_funcs.atomic_enable * * Note: the bridge passed should be the one closest to the encoder */ @@ -1071,11 +1053,8 @@ void drm_atomic_bridge_chain_enable(struct drm_bridge *first_bridge, return; drm_for_each_bridge_in_chain_from(first_bridge, bridge) - if (bridge->funcs->atomic_enable) { + if (bridge->funcs->atomic_enable) bridge->funcs->atomic_enable(bridge, state); - } else if (bridge->funcs->enable) { - bridge->funcs->enable(bridge); - } } EXPORT_SYMBOL(drm_atomic_bridge_chain_enable); @@ -1717,7 +1696,7 @@ struct drm_bridge *of_drm_get_bridge_by_endpoint(const struct device_node *np, /* * of_graph_get_remote_node() produces a noisy error message if port * node isn't found and the absence of the port is a legit case here, - * so at first we silently check whether graph is present in the + * so at first we silently check whether a graph is present in the * device-tree node. */ if (!of_graph_is_present(np)) @@ -1817,7 +1796,7 @@ static int encoder_bridges_show(struct seq_file *m, void *data) struct drm_printer p = drm_seq_file_printer(m); unsigned int idx = 0; - drm_for_each_bridge_in_chain_scoped(encoder, bridge) + drm_for_each_bridge_in_chain(encoder, bridge) drm_bridge_debugfs_show_bridge(&p, bridge, idx++, false, true); return 0; diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 958cb605aedd..fcfe635bc195 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -48,6 +48,47 @@ * event. As mentioned above, ranges are tracked in a notifier's Red-Black * tree. * + * - Pages: + * struct drm_gpusvm_pages holds the DMA mapping state for a range of + * CPU virtual addresses: the DMA mapped device addresses, + * the device private pagemap, the IOVA state, the per mapping + * notifier sequence number, and the drm_device that owns the DMA + * mappings. + * A driver embeds one or more struct drm_gpusvm_pages alongside its + * struct drm_gpusvm_range, choosing one of two layouts: + * + * 1:1 - one drm_gpusvm_pages per range (one drm_device). Simplest + * layout; to mirror a VA range on several devices a driver uses a + * separate range (and notifier) per device, so the HMM fault is taken + * once per device. + * + * N:1 - one drm_gpusvm_pages per drm_device, all sharing one range and + * notifier; only the per-device DMA mapping differs. The instances must + * sit in contiguous memory so a single drm_gpusvm_range_set_unmapped() + * can mark them all. A driver can keep one instance inline for the single + * device case and switch to a heap array only when more devices join, + * e.g.: + * + * .. code-block:: c + * + * struct driver_range { + * struct drm_gpusvm_range base; + * unsigned int num_pages; // 1: inline_pages, >1: pages[] + * union { + * struct drm_gpusvm_pages inline_pages; + * struct drm_gpusvm_pages *pages; + * }; + * }; + * + * In the N:1 case the driver allocates the pages array with a zeroing + * allocator (e.g. kcalloc(num_pages, ...)), initialises each entry with + * drm_gpusvm_init_pages(), and frees each entry with + * drm_gpusvm_free_pages() plus the array itself, from its range free + * callback. Each drm_gpusvm_pages is mapped independently by their own + * drm_device. + * Each drm_gpusvm_pages must be zero-initialised and initialised with + * drm_gpusvm_init_pages(), called once per entry. + * * - Operations: * Define the interface for driver-specific GPU SVM operations such as * range allocation, notifier allocation, and invalidations. @@ -92,7 +133,7 @@ * range RB tree and list, as well as the range's DMA mappings and sequence * number. GPU SVM manages all necessary locking and unlocking operations, * except for the recheck range's pages being valid - * (drm_gpusvm_range_pages_valid) when the driver is committing GPU bindings. + * (drm_gpusvm_pages_valid) when the driver is committing GPU bindings. * This lock corresponds to the ``driver->update`` lock mentioned in * Documentation/mm/hmm.rst. Future revisions may transition from a GPU SVM * global lock to a per-notifier lock if finer-grained locking is deemed @@ -140,15 +181,20 @@ * * .. code-block:: c * - * int driver_bind_range(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range) + * struct driver_range { + * struct drm_gpusvm_range base; + * struct drm_gpusvm_pages pages; + * }; + * + * int driver_bind_range(struct drm_gpusvm *gpusvm, struct driver_range *drange) * { * int err = 0; * - * driver_alloc_and_setup_memory_for_bind(gpusvm, range); + * driver_alloc_and_setup_memory_for_bind(gpusvm, drange); * * drm_gpusvm_notifier_lock(gpusvm); - * if (drm_gpusvm_range_pages_valid(range)) - * driver_commit_bind(gpusvm, range); + * if (drm_gpusvm_pages_valid(gpusvm, &drange->pages)) + * driver_commit_bind(gpusvm, drange); * else * err = -EAGAIN; * drm_gpusvm_notifier_unlock(gpusvm); @@ -160,6 +206,8 @@ * unsigned long gpuva_start, unsigned long gpuva_end) * { * struct drm_gpusvm_ctx ctx = {}; + * struct driver_range *drange; + * struct drm_gpusvm_range *range; * int err; * * driver_svm_lock(); @@ -174,6 +222,7 @@ * err = PTR_ERR(range); * goto unlock; * } + * drange = container_of(range, struct driver_range, base); * * if (driver_migration_policy(range)) { * err = drm_pagemap_populate_mm(driver_choose_drm_pagemap(), @@ -183,7 +232,10 @@ * goto retry; * } * - * err = drm_gpusvm_range_get_pages(gpusvm, range, &ctx); + * err = drm_gpusvm_get_pages(gpusvm, &drange->pages, + * gpusvm->mm, &range->notifier->notifier, + * drm_gpusvm_range_start(range), + * drm_gpusvm_range_end(range), &ctx); * if (err == -EOPNOTSUPP || err == -EFAULT || err == -EPERM) { // CPU mappings changed * if (err == -EOPNOTSUPP) * drm_gpusvm_range_evict(gpusvm, range); @@ -192,7 +244,7 @@ * goto unlock; * } * - * err = driver_bind_range(gpusvm, range); + * err = driver_bind_range(gpusvm, drange); * if (err == -EAGAIN) // CPU mappings changed * goto retry * @@ -205,6 +257,21 @@ * * .. code-block:: c * + * // The driver owns the drm_gpusvm_pages lifecycle. ops->range_free is + * // the final fallback: drm_gpusvm_free_pages() unmaps any + * // lingering DMA mapping and a no-op if already unmapped and frees the + * // dma_addr array. The normal flow is to DMA unmap before + * // drm_gpusvm_range_remove() (before the range leaves the tree). + * void driver_range_free(struct drm_gpusvm_range *range) + * { + * struct driver_range *drange = + * container_of(range, struct driver_range, base); + * + * drm_gpusvm_free_pages(range->gpusvm, &drange->pages, + * drm_gpusvm_range_size(range) >> PAGE_SHIFT); + * kfree(drange); + * } + * * void __driver_garbage_collector(struct drm_gpusvm *gpusvm, * struct drm_gpusvm_range *range) * { @@ -215,6 +282,14 @@ * drm_gpusvm_range_evict(gpusvm, range); * * driver_unbind_range(range); + * // The pages must be DMA unmapped before drm_gpusvm_range_remove() + * // , so a range is never off the MMU interval tree while still DMA + * // mapped as the original drmsvm design flow. Otherwise a concurrent CPU + * // munmap's notifier could miss this range and free pages still mapped + * // for device DMA. This is the normal unmap point. + * drm_gpusvm_unmap_pages(gpusvm, &drange->pages, + * drm_gpusvm_range_size(range) >> PAGE_SHIFT, + * &(struct drm_gpusvm_ctx){ .in_notifier = false }); * drm_gpusvm_range_remove(gpusvm, range); * } * @@ -236,17 +311,22 @@ * { * struct drm_gpusvm_ctx ctx = { .in_notifier = true, }; * struct drm_gpusvm_range *range = NULL; + * struct driver_range *drange; * * driver_invalidate_device_pages(gpusvm, mmu_range->start, mmu_range->end); * * drm_gpusvm_for_each_range(range, notifier, mmu_range->start, * mmu_range->end) { - * drm_gpusvm_range_unmap_pages(gpusvm, range, &ctx); + * drange = container_of(range, struct driver_range, base); + * + * drm_gpusvm_unmap_pages(gpusvm, &drange->pages, + * drm_gpusvm_range_size(range) >> PAGE_SHIFT, + * &ctx); * * if (mmu_range->event != MMU_NOTIFY_UNMAP) * continue; * - * drm_gpusvm_range_set_unmapped(range, mmu_range); + * drm_gpusvm_range_set_unmapped(range, &drange->pages, 1, mmu_range); * driver_garbage_collector_add(gpusvm, range); * } * } @@ -359,7 +439,6 @@ static const struct mmu_interval_notifier_ops drm_gpusvm_notifier_ops = { * drm_gpusvm_init() - Initialize the GPU SVM. * @gpusvm: Pointer to the GPU SVM structure. * @name: Name of the GPU SVM. - * @drm: Pointer to the DRM device structure. * @mm: Pointer to the mm_struct for the address space. * @mm_start: Start address of GPU SVM. * @mm_range: Range of the GPU SVM. @@ -373,7 +452,9 @@ static const struct mmu_interval_notifier_ops drm_gpusvm_notifier_ops = { * This function initializes the GPU SVM. * * Note: If only using the simple drm_gpusvm_pages API (get/unmap/free), - * then only @gpusvm, @name, and @drm are expected. However, the same base + * then only @gpusvm and @name are expected. The @drm drm_device for dma + * mappings is bound per-pages via drm_gpusvm_init_pages() before the first + * drm_gpusvm_get_pages() call. However, the same base * @gpusvm can also be used with both modes together in which case the full * setup is needed, where the core drm_gpusvm_pages API will simply never use * the other fields. @@ -381,7 +462,7 @@ static const struct mmu_interval_notifier_ops drm_gpusvm_notifier_ops = { * Return: 0 on success, a negative error code on failure. */ int drm_gpusvm_init(struct drm_gpusvm *gpusvm, - const char *name, struct drm_device *drm, + const char *name, struct mm_struct *mm, unsigned long mm_start, unsigned long mm_range, unsigned long notifier_size, @@ -399,7 +480,6 @@ int drm_gpusvm_init(struct drm_gpusvm *gpusvm, } gpusvm->name = name; - gpusvm->drm = drm; gpusvm->mm = mm; gpusvm->mm_start = mm_start; gpusvm->mm_range = mm_range; @@ -640,8 +720,7 @@ drm_gpusvm_range_alloc(struct drm_gpusvm *gpusvm, range->itree.start = ALIGN_DOWN(fault_addr, chunk_size); range->itree.last = ALIGN(fault_addr + 1, chunk_size) - 1; INIT_LIST_HEAD(&range->entry); - range->pages.notifier_seq = LONG_MAX; - range->pages.flags.migrate_devmem = migrate_devmem ? 1 : 0; + range->flags.migrate_devmem = migrate_devmem ? 1 : 0; return range; } @@ -929,7 +1008,7 @@ retry: * mallocs 16k but the CPU VMA is ~128k which results in 64k SVM * ranges. When migrating the SVM ranges, some processes fail in * drm_pagemap_migrate_to_devmem with 'migrate.cpages != npages' - * and then upon drm_gpusvm_range_get_pages device pages from + * and then upon drm_gpusvm_get_pages device pages from * other processes are collected + faulted in which creates all * sorts of problems. Unsure exactly how this happening, also * problem goes away if 'xe_exec_system_allocator --r @@ -1135,11 +1214,16 @@ static void __drm_gpusvm_unmap_pages(struct drm_gpusvm *gpusvm, unsigned long npages) { struct drm_pagemap *dpagemap = svm_pages->dpagemap; - struct device *dev = gpusvm->drm->dev; + struct device *dev; unsigned long i, j; lockdep_assert_held(&gpusvm->notifier_lock); + if (!svm_pages->drm) + return; + + dev = svm_pages->drm->dev; + if (svm_pages->flags.has_dma_mapping) { struct drm_gpusvm_pages_flags flags = { .__flags = svm_pages->flags.__flags, @@ -1222,12 +1306,17 @@ EXPORT_SYMBOL_GPL(drm_gpusvm_free_pages); * This function removes the specified GPU SVM range and also removes the parent * GPU SVM notifier if no more ranges remain in the notifier. The caller must * hold a lock to protect range and notifier removal. + * + * This function does not unmap or free the drm_gpusvm_pages, the driver owns + * that lifecycle. The caller must DMA unmap the range's pages before calling + * this function, so a range is never removed from the MMU interval tree while + * still DMA mapped. Typically the driver calls drm_gpusvm_unmap_pages() first. + * And the range_free callback's drm_gpusvm_free_pages() is a final fallback safe + * net. */ void drm_gpusvm_range_remove(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range) { - unsigned long npages = npages_in_range(drm_gpusvm_range_start(range), - drm_gpusvm_range_end(range)); struct drm_gpusvm_notifier *notifier; drm_gpusvm_driver_lock_held(gpusvm); @@ -1239,8 +1328,6 @@ void drm_gpusvm_range_remove(struct drm_gpusvm *gpusvm, return; drm_gpusvm_notifier_lock(gpusvm); - __drm_gpusvm_unmap_pages(gpusvm, &range->pages, npages); - __drm_gpusvm_free_pages(gpusvm, &range->pages); __drm_gpusvm_range_remove(notifier, range); drm_gpusvm_notifier_unlock(gpusvm); @@ -1319,34 +1406,14 @@ EXPORT_SYMBOL_GPL(drm_gpusvm_range_put); * * Return: True if GPU SVM range has valid pages, False otherwise */ -static bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_pages *svm_pages) +bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm, + struct drm_gpusvm_pages *svm_pages) { lockdep_assert_held(&gpusvm->notifier_lock); return svm_pages->flags.has_devmem_pages || svm_pages->flags.has_dma_mapping; } - -/** - * drm_gpusvm_range_pages_valid() - GPU SVM range pages valid - * @gpusvm: Pointer to the GPU SVM structure - * @range: Pointer to the GPU SVM range structure - * - * This function determines if a GPU SVM range pages are valid. Expected be - * called holding gpusvm->notifier_lock and as the last step before committing a - * GPU binding. This is akin to a notifier seqno check in the HMM documentation - * but due to wider notifiers (i.e., notifiers which span multiple ranges) this - * function is required for finer grained checking (i.e., per range) if pages - * are valid. - * - * Return: True if GPU SVM range has valid pages, False otherwise - */ -bool drm_gpusvm_range_pages_valid(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_range *range) -{ - return drm_gpusvm_pages_valid(gpusvm, &range->pages); -} -EXPORT_SYMBOL_GPL(drm_gpusvm_range_pages_valid); +EXPORT_SYMBOL_GPL(drm_gpusvm_pages_valid); /** * drm_gpusvm_pages_valid_unlocked() - GPU SVM pages valid unlocked @@ -1421,6 +1488,9 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm, DMA_BIDIRECTIONAL; struct dma_iova_state *state = &svm_pages->state; + if (!svm_pages->drm) + return -EINVAL; + retry: if (time_after(jiffies, timeout)) return -EBUSY; @@ -1520,7 +1590,7 @@ map_pages: pagemap = page_pgmap(page); dpagemap = drm_pagemap_page_to_dpagemap(page); - if (drm_WARN_ON(gpusvm->drm, !dpagemap)) { + if (drm_WARN_ON(svm_pages->drm, !dpagemap)) { /* * Raced. This is not supposed to happen * since hmm_range_fault() should've migrated @@ -1532,10 +1602,10 @@ map_pages: } svm_pages->dma_addr[j] = dpagemap->ops->device_map(dpagemap, - gpusvm->drm->dev, + svm_pages->drm->dev, page, order, dma_dir); - if (dma_mapping_error(gpusvm->drm->dev, + if (dma_mapping_error(svm_pages->drm->dev, svm_pages->dma_addr[j].addr)) { err = -EFAULT; goto err_unmap; @@ -1555,11 +1625,11 @@ map_pages: } if (!i) - dma_iova_try_alloc(gpusvm->drm->dev, state, + dma_iova_try_alloc(svm_pages->drm->dev, state, 0, npages * PAGE_SIZE); if (dma_use_iova(state)) { - err = dma_iova_link(gpusvm->drm->dev, state, + err = dma_iova_link(svm_pages->drm->dev, state, hmm_pfn_to_phys(pfns[i]), svm_pages->state_offset, PAGE_SIZE << order, @@ -1570,11 +1640,11 @@ map_pages: addr = state->addr + svm_pages->state_offset; svm_pages->state_offset += PAGE_SIZE << order; } else { - addr = dma_map_page(gpusvm->drm->dev, + addr = dma_map_page(svm_pages->drm->dev, page, 0, PAGE_SIZE << order, dma_dir); - if (dma_mapping_error(gpusvm->drm->dev, addr)) { + if (dma_mapping_error(svm_pages->drm->dev, addr)) { err = -EFAULT; goto err_unmap; } @@ -1590,7 +1660,7 @@ map_pages: } if (dma_use_iova(state)) { - err = dma_iova_sync(gpusvm->drm->dev, state, 0, + err = dma_iova_sync(svm_pages->drm->dev, state, 0, svm_pages->state_offset); if (err) goto err_unmap; @@ -1625,28 +1695,6 @@ err_free: } EXPORT_SYMBOL_GPL(drm_gpusvm_get_pages); -/** - * drm_gpusvm_range_get_pages() - Get pages for a GPU SVM range - * @gpusvm: Pointer to the GPU SVM structure - * @range: Pointer to the GPU SVM range structure - * @ctx: GPU SVM context - * - * This function gets pages for a GPU SVM range and ensures they are mapped for - * DMA access. - * - * Return: 0 on success, negative error code on failure. - */ -int drm_gpusvm_range_get_pages(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_range *range, - const struct drm_gpusvm_ctx *ctx) -{ - return drm_gpusvm_get_pages(gpusvm, &range->pages, gpusvm->mm, - &range->notifier->notifier, - drm_gpusvm_range_start(range), - drm_gpusvm_range_end(range), ctx); -} -EXPORT_SYMBOL_GPL(drm_gpusvm_range_get_pages); - /** * drm_gpusvm_unmap_pages() - Unmap GPU svm pages * @gpusvm: Pointer to the GPU SVM structure @@ -1677,29 +1725,6 @@ void drm_gpusvm_unmap_pages(struct drm_gpusvm *gpusvm, } EXPORT_SYMBOL_GPL(drm_gpusvm_unmap_pages); -/** - * drm_gpusvm_range_unmap_pages() - Unmap pages associated with a GPU SVM range - * @gpusvm: Pointer to the GPU SVM structure - * @range: Pointer to the GPU SVM range structure - * @ctx: GPU SVM context - * - * This function unmaps pages associated with a GPU SVM range. If @in_notifier - * is set, it is assumed that gpusvm->notifier_lock is held in write mode; if it - * is clear, it acquires gpusvm->notifier_lock in read mode. Must be called on - * each GPU SVM range attached to notifier in gpusvm->ops->invalidate for IOMMU - * security model. - */ -void drm_gpusvm_range_unmap_pages(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_range *range, - const struct drm_gpusvm_ctx *ctx) -{ - unsigned long npages = npages_in_range(drm_gpusvm_range_start(range), - drm_gpusvm_range_end(range)); - - return drm_gpusvm_unmap_pages(gpusvm, &range->pages, npages, ctx); -} -EXPORT_SYMBOL_GPL(drm_gpusvm_range_unmap_pages); - /** * drm_gpusvm_range_evict() - Evict GPU SVM range * @gpusvm: Pointer to the GPU SVM structure @@ -1784,20 +1809,40 @@ EXPORT_SYMBOL_GPL(drm_gpusvm_has_mapping); /** * drm_gpusvm_range_set_unmapped() - Mark a GPU SVM range as unmapped * @range: Pointer to the GPU SVM range structure. + * @pages: Pointer to the GPU SVM pages structure(s). + * @pages_count: Number of GPU SVM pages structure(s) passed in. * @mmu_range: Pointer to the MMU notifier range structure. * * This function marks a GPU SVM range as unmapped and sets the partial_unmap flag * if the range partially falls within the provided MMU notifier range. */ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range, + struct drm_gpusvm_pages *pages, + unsigned int pages_count, const struct mmu_notifier_range *mmu_range) { + struct drm_gpusvm_range_flags range_flags = { + .__flags = range->flags.__flags, + }; + unsigned int i; + lockdep_assert_held_write(&range->gpusvm->notifier_lock); - range->pages.flags.unmapped = true; + range_flags.unmapped = true; + for (i = 0; i < pages_count; ++i) { + struct drm_gpusvm_pages_flags flags = { + .__flags = pages[i].flags.__flags, + }; + + flags.unmapped = true; + /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */ + WRITE_ONCE(pages[i].flags.__flags, flags.__flags); + } if (drm_gpusvm_range_start(range) < mmu_range->start || drm_gpusvm_range_end(range) > mmu_range->end) - range->pages.flags.partial_unmap = true; + range_flags.partial_unmap = true; + /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */ + WRITE_ONCE(range->flags.__flags, range_flags.__flags); } EXPORT_SYMBOL_GPL(drm_gpusvm_range_set_unmapped); diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index f432f485a914..3bcc7bf0900c 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -857,6 +857,25 @@ static void validate_encoder_possible_crtcs(struct drm_encoder *encoder) encoder->possible_crtcs, crtc_mask); } +static void validate_blend_mode_for_alpha_formats(struct drm_plane *plane) +{ + const struct drm_format_info *fmt; + u32 i; + + /* blend mode property supported, no need to check anything */ + if (plane->blend_mode_property) + return; + + for (i = 0; i < plane->format_count; i++) { + fmt = drm_format_info(plane->format_types[i]); + if (fmt->has_alpha) { + WARN(1, "[PLANE:%d:%s] pixel format with alpha exposed but blend mode not setup", + plane->base.id, plane->name); + break; + } + } +} + void drm_mode_config_validate(struct drm_device *dev) { struct drm_encoder *encoder; @@ -915,6 +934,8 @@ void drm_mode_config_validate(struct drm_device *dev) drm_for_each_plane(plane, dev) { if (plane->type == DRM_PLANE_TYPE_PRIMARY) num_primary++; + + validate_blend_mode_for_alpha_formats(plane); } WARN(num_primary != dev->mode_config.num_crtc, diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c index 96eef327bf7e..e495117735e1 100644 --- a/drivers/gpu/drm/drm_of.c +++ b/drivers/gpu/drm/drm_of.c @@ -269,7 +269,8 @@ EXPORT_SYMBOL_GPL(drm_of_get_panel_orientation); * @np: device tree node containing encoder output ports * @port: port in the device tree node * @endpoint: endpoint in the device tree node - * @panel: pointer to hold returned drm_panel, must not be NULL + * @panel: pointer to hold returned drm_panel, must not be NULL. On success + * the caller must call drm_panel_put() when done with the panel * @bridge: pointer to hold returned drm_bridge * * Given a DT node's port and endpoint number, find the connected node and @@ -297,7 +298,7 @@ int drm_of_find_panel_or_bridge(const struct device_node *np, /* * of_graph_get_remote_node() produces a noisy error message if port * node isn't found and the absence of the port is a legit case here, - * so at first we silently check whether graph presents in the + * so at first we silently check whether a graph is present in the * device-tree node. */ if (!of_graph_is_present(np)) diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c index d7c6f4824b2d..f8f6082e637f 100644 --- a/drivers/gpu/drm/drm_panel.c +++ b/drivers/gpu/drm/drm_panel.c @@ -82,6 +82,7 @@ static void drm_panel_init(struct drm_panel *panel, struct device *dev, */ void drm_panel_add(struct drm_panel *panel) { + drm_panel_get(panel); mutex_lock(&panel_lock); list_add_tail(&panel->list, &panel_list); mutex_unlock(&panel_lock); @@ -99,6 +100,7 @@ void drm_panel_remove(struct drm_panel *panel) mutex_lock(&panel_lock); list_del_init(&panel->list); mutex_unlock(&panel_lock); + drm_panel_put(panel); } EXPORT_SYMBOL(drm_panel_remove); @@ -457,14 +459,17 @@ EXPORT_SYMBOL(__devm_drm_panel_alloc); #ifdef CONFIG_OF /** - * of_drm_find_panel - look up a panel using a device tree node + * of_drm_find_panel - look up and reference a panel by device tree node * @np: device tree node of the panel * * Searches the set of registered panels for one that matches the given device - * tree node. If a matching panel is found, return a pointer to it. + * tree node. If a matching panel is found, the panel's reference count is + * incremented before returning a pointer to it. The caller must call + * drm_panel_put() when it no longer needs the panel pointer. * - * Return: A pointer to the panel registered for the specified device tree - * node or an ERR_PTR() if no panel matching the device tree node can be found. + * Return: A reference-counted pointer to the panel registered for the specified + * device tree node or an ERR_PTR() if no panel matching the device tree node + * can be found. * * Possible error codes returned by this function: * @@ -483,6 +488,7 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np) list_for_each_entry(panel, &panel_list, list) { if (panel->dev->of_node == np) { + drm_panel_get(panel); mutex_unlock(&panel_lock); return panel; } @@ -494,7 +500,13 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np) EXPORT_SYMBOL(of_drm_find_panel); #endif -/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */ +/* + * Find panel by fwnode, returning a counted reference. + * + * Behaves identically to of_drm_find_panel(). On success the returned + * pointer has been passed through drm_panel_get(); the caller must call + * drm_panel_put() when done with it. + */ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode) { struct drm_panel *panel; @@ -506,6 +518,7 @@ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode list_for_each_entry(panel, &panel_list, list) { if (dev_fwnode(panel->dev) == fwnode) { + drm_panel_get(panel); mutex_unlock(&panel_lock); return panel; } @@ -642,6 +655,7 @@ void drm_panel_remove_follower(struct drm_panel_follower *follower) mutex_unlock(&panel->follower_lock); put_device(panel->dev); + drm_panel_put(panel); } EXPORT_SYMBOL(drm_panel_remove_follower); diff --git a/drivers/gpu/drm/drm_panel.c.orig b/drivers/gpu/drm/drm_panel.c.orig new file mode 100644 index 000000000000..32fba1a562fb --- /dev/null +++ b/drivers/gpu/drm/drm_panel.c.orig @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2013, NVIDIA Corporation. All rights reserved. + * + * 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, sub license, + * 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 (including the + * next paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static DEFINE_MUTEX(panel_lock); +static LIST_HEAD(panel_list); + +/** + * DOC: drm panel + * + * The DRM panel helpers allow drivers to register panel objects with a + * central registry and provide functions to retrieve those panels in display + * drivers. + * + * For easy integration into drivers using the &drm_bridge infrastructure please + * take look at drm_panel_bridge_add() and devm_drm_panel_bridge_add(). + */ + +/** + * drm_panel_init - initialize a panel + * @panel: DRM panel + * @dev: parent device of the panel + * @funcs: panel operations + * @connector_type: the connector type (DRM_MODE_CONNECTOR_*) corresponding to + * the panel interface (must NOT be DRM_MODE_CONNECTOR_Unknown) + * + * Initialize the panel structure for subsequent registration with + * drm_panel_add(). + */ +static void drm_panel_init(struct drm_panel *panel, struct device *dev, + const struct drm_panel_funcs *funcs, + int connector_type) +{ + if (connector_type == DRM_MODE_CONNECTOR_Unknown) + DRM_WARN("%s: %s: a valid connector type is required!\n", __func__, dev_name(dev)); + + INIT_LIST_HEAD(&panel->list); + INIT_LIST_HEAD(&panel->followers); + mutex_init(&panel->follower_lock); + panel->dev = dev; + panel->funcs = funcs; + panel->connector_type = connector_type; +} + +/** + * drm_panel_add - add a panel to the global registry + * @panel: panel to add + * + * Add a panel to the global registry so that it can be looked + * up by display drivers. The panel to be added must have been + * allocated by devm_drm_panel_alloc(). + */ +void drm_panel_add(struct drm_panel *panel) +{ + drm_panel_get(panel); + mutex_lock(&panel_lock); + list_add_tail(&panel->list, &panel_list); + mutex_unlock(&panel_lock); +} +EXPORT_SYMBOL(drm_panel_add); + +/** + * drm_panel_remove - remove a panel from the global registry + * @panel: DRM panel + * + * Removes a panel from the global registry. + */ +void drm_panel_remove(struct drm_panel *panel) +{ + mutex_lock(&panel_lock); + list_del_init(&panel->list); + mutex_unlock(&panel_lock); + drm_panel_put(panel); +} +EXPORT_SYMBOL(drm_panel_remove); + +static void drm_panel_add_release(void *data) +{ + drm_panel_remove(data); +} + +/** + * devm_drm_panel_add - add a panel to the global registry using devres + * @dev: device to which the panel is attached + * @panel: panel to add + * + * Add a panel to the global registry so that it can be looked + * up by display drivers. The panel to be added must have been + * allocated by devm_drm_panel_alloc(). Unlike drm_panel_add() with this + * function there is no need to call drm_panel_remove(), it will be called + * automatically. + */ +int devm_drm_panel_add(struct device *dev, struct drm_panel *panel) +{ + drm_panel_add(panel); + + return devm_add_action_or_reset(dev, drm_panel_add_release, panel); +} +EXPORT_SYMBOL(devm_drm_panel_add); + +/** + * drm_panel_prepare - power on a panel + * @panel: DRM panel + * + * Calling this function will enable power and deassert any reset signals to + * the panel. After this has completed it is possible to communicate with any + * integrated circuitry via a command bus. This function cannot fail (as it is + * called from the pre_enable call chain). There will always be a call to + * drm_panel_disable() afterwards. + */ +void drm_panel_prepare(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + if (panel->prepared) { + dev_warn(panel->dev, "Skipping prepare of already prepared panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + if (panel->funcs && panel->funcs->prepare) { + ret = panel->funcs->prepare(panel); + if (ret < 0) + goto exit; + } + panel->prepared = true; + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_prepared) + continue; + + ret = follower->funcs->panel_prepared(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_prepared, ret); + } + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_prepare); + +/** + * drm_panel_unprepare - power off a panel + * @panel: DRM panel + * + * Calling this function will completely power off a panel (assert the panel's + * reset, turn off power supplies, ...). After this function has completed, it + * is usually no longer possible to communicate with the panel until another + * call to drm_panel_prepare(). + */ +void drm_panel_unprepare(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + /* + * If you are seeing the warning below it likely means one of two things: + * - Your panel driver incorrectly calls drm_panel_unprepare() in its + * shutdown routine. You should delete this. + * - You are using panel-edp or panel-simple and your DRM modeset + * driver's shutdown() callback happened after the panel's shutdown(). + * In this case the warning is harmless though ideally you should + * figure out how to reverse the order of the shutdown() callbacks. + */ + if (!panel->prepared) { + dev_warn(panel->dev, "Skipping unprepare of already unprepared panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_unpreparing) + continue; + + ret = follower->funcs->panel_unpreparing(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_unpreparing, ret); + } + + if (panel->funcs && panel->funcs->unprepare) { + ret = panel->funcs->unprepare(panel); + if (ret < 0) + goto exit; + } + panel->prepared = false; + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_unprepare); + +/** + * drm_panel_enable - enable a panel + * @panel: DRM panel + * + * Calling this function will cause the panel display drivers to be turned on + * and the backlight to be enabled. Content will be visible on screen after + * this call completes. This function cannot fail (as it is called from the + * enable call chain). There will always be a call to drm_panel_disable() + * afterwards. + */ +void drm_panel_enable(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + if (panel->enabled) { + dev_warn(panel->dev, "Skipping enable of already enabled panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + if (panel->funcs && panel->funcs->enable) { + ret = panel->funcs->enable(panel); + if (ret < 0) + goto exit; + } + panel->enabled = true; + + ret = backlight_enable(panel->backlight); + if (ret < 0) + DRM_DEV_INFO(panel->dev, "failed to enable backlight: %d\n", + ret); + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_enabled) + continue; + + ret = follower->funcs->panel_enabled(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_enabled, ret); + } + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_enable); + +/** + * drm_panel_disable - disable a panel + * @panel: DRM panel + * + * This will typically turn off the panel's backlight or disable the display + * drivers. For smart panels it should still be possible to communicate with + * the integrated circuitry via any command bus after this call. + */ +void drm_panel_disable(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + /* + * If you are seeing the warning below it likely means one of two things: + * - Your panel driver incorrectly calls drm_panel_disable() in its + * shutdown routine. You should delete this. + * - You are using panel-edp or panel-simple and your DRM modeset + * driver's shutdown() callback happened after the panel's shutdown(). + * In this case the warning is harmless though ideally you should + * figure out how to reverse the order of the shutdown() callbacks. + */ + if (!panel->enabled) { + dev_warn(panel->dev, "Skipping disable of already disabled panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_disabling) + continue; + + ret = follower->funcs->panel_disabling(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_disabling, ret); + } + + ret = backlight_disable(panel->backlight); + if (ret < 0) + DRM_DEV_INFO(panel->dev, "failed to disable backlight: %d\n", + ret); + + if (panel->funcs && panel->funcs->disable) { + ret = panel->funcs->disable(panel); + if (ret < 0) + goto exit; + } + panel->enabled = false; + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_disable); + +/** + * drm_panel_get_modes - probe the available display modes of a panel + * @panel: DRM panel + * @connector: DRM connector + * + * The modes probed from the panel are automatically added to the connector + * that the panel is attached to. + * + * Return: The number of modes available from the panel on success, or 0 on + * failure (no modes). + */ +int drm_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + if (!panel) + return 0; + + if (panel->funcs && panel->funcs->get_modes) { + int num; + + num = panel->funcs->get_modes(panel, connector); + if (num > 0) + return num; + } + + return 0; +} +EXPORT_SYMBOL(drm_panel_get_modes); + +static void __drm_panel_free(struct kref *kref) +{ + struct drm_panel *panel = container_of(kref, struct drm_panel, refcount); + + kfree(panel->container); +} + +/** + * drm_panel_get - Acquire a panel reference + * @panel: DRM panel + * + * This function increments the panel's refcount. + * Returns: + * Pointer to @panel + */ +struct drm_panel *drm_panel_get(struct drm_panel *panel) +{ + if (!panel) + return panel; + + kref_get(&panel->refcount); + + return panel; +} +EXPORT_SYMBOL(drm_panel_get); + +/** + * drm_panel_put - Release a panel reference + * @panel: DRM panel + * + * This function decrements the panel's reference count and frees the + * object if the reference count drops to zero. + */ +void drm_panel_put(struct drm_panel *panel) +{ + if (panel) + kref_put(&panel->refcount, __drm_panel_free); +} +EXPORT_SYMBOL(drm_panel_put); + +/** + * drm_panel_put_void - wrapper to drm_panel_put() taking a void pointer + * + * @data: pointer to @struct drm_panel, cast to a void pointer + * + * Wrapper of drm_panel_put() to be used when a function taking a void + * pointer is needed, for example as a devm action. + */ +static void drm_panel_put_void(void *data) +{ + struct drm_panel *panel = (struct drm_panel *)data; + + drm_panel_put(panel); +} + +void *__devm_drm_panel_alloc(struct device *dev, size_t size, size_t offset, + const struct drm_panel_funcs *funcs, + int connector_type) +{ + void *container; + struct drm_panel *panel; + int err; + + if (!funcs) { + dev_warn(dev, "Missing funcs pointer\n"); + return ERR_PTR(-EINVAL); + } + + container = kzalloc(size, GFP_KERNEL); + if (!container) + return ERR_PTR(-ENOMEM); + + panel = container + offset; + panel->container = container; + panel->funcs = funcs; + kref_init(&panel->refcount); + + err = devm_add_action_or_reset(dev, drm_panel_put_void, panel); + if (err) + return ERR_PTR(err); + + drm_panel_init(panel, dev, funcs, connector_type); + + return container; +} +EXPORT_SYMBOL(__devm_drm_panel_alloc); + +#ifdef CONFIG_OF +/** + * of_drm_find_panel - look up and reference a panel by device tree node + * @np: device tree node of the panel + * + * Searches the set of registered panels for one that matches the given device + * tree node. If a matching panel is found, the panel's reference count is + * incremented before returning a pointer to it. The caller must call + * drm_panel_put() when it no longer needs the panel pointer. + * + * Return: A reference-counted pointer to the panel registered for the specified + * device tree node or an ERR_PTR() if no panel matching the device tree node + * can be found. + * + * Possible error codes returned by this function: + * + * - EPROBE_DEFER: the panel device has not been probed yet, and the caller + * should retry later + * - ENODEV: the device is not available (status != "okay" or "ok") + */ +struct drm_panel *of_drm_find_panel(const struct device_node *np) +{ + struct drm_panel *panel; + + if (!of_device_is_available(np)) + return ERR_PTR(-ENODEV); + + mutex_lock(&panel_lock); + + list_for_each_entry(panel, &panel_list, list) { + if (panel->dev->of_node == np) { + drm_panel_get(panel); + mutex_unlock(&panel_lock); + return panel; + } + } + + mutex_unlock(&panel_lock); + return ERR_PTR(-EPROBE_DEFER); +} +EXPORT_SYMBOL(of_drm_find_panel); +#endif + +/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */ +static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode) +{ + struct drm_panel *panel; + + if (!fwnode_device_is_available(fwnode)) + return ERR_PTR(-ENODEV); + + mutex_lock(&panel_lock); + + list_for_each_entry(panel, &panel_list, list) { + if (dev_fwnode(panel->dev) == fwnode) { + mutex_unlock(&panel_lock); + return panel; + } + } + + mutex_unlock(&panel_lock); + + return ERR_PTR(-EPROBE_DEFER); +} + +/* Find panel by follower device */ +static struct drm_panel *find_panel_by_dev(struct device *follower_dev) +{ + struct fwnode_handle *fwnode; + struct drm_panel *panel; + + fwnode = fwnode_find_reference(dev_fwnode(follower_dev), "panel", 0); + if (IS_ERR(fwnode)) + return ERR_PTR(-ENODEV); + + panel = find_panel_by_fwnode(fwnode); + fwnode_handle_put(fwnode); + + return panel; +} + +/** + * drm_is_panel_follower() - Check if the device is a panel follower + * @dev: The 'struct device' to check + * + * This checks to see if a device needs to be power sequenced together with + * a panel using the panel follower API. + * + * The "panel" property of the follower points to the panel to be followed. + * + * Return: true if we should be power sequenced with a panel; false otherwise. + */ +bool drm_is_panel_follower(struct device *dev) +{ + /* + * The "panel" property is actually a phandle, but for simplicity we + * don't bother trying to parse it here. We just need to know if the + * property is there. + */ + return device_property_present(dev, "panel"); +} +EXPORT_SYMBOL(drm_is_panel_follower); + +/** + * drm_panel_add_follower() - Register something to follow panel state. + * @follower_dev: The 'struct device' for the follower. + * @follower: The panel follower descriptor for the follower. + * + * A panel follower is called right after preparing/enabling the panel and right + * before unpreparing/disabling the panel. It's primary intention is to power on + * an associated touchscreen, though it could be used for any similar devices. + * Multiple devices are allowed the follow the same panel. + * + * If a follower is added to a panel that's already been prepared/enabled, the + * follower's prepared/enabled callback is called right away. + * + * The "panel" property of the follower points to the panel to be followed. + * + * Return: 0 or an error code. Note that -ENODEV means that we detected that + * follower_dev is not actually following a panel. The caller may + * choose to ignore this return value if following a panel is optional. + */ +int drm_panel_add_follower(struct device *follower_dev, + struct drm_panel_follower *follower) +{ + struct drm_panel *panel; + int ret; + + panel = find_panel_by_dev(follower_dev); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + get_device(panel->dev); + follower->panel = panel; + + mutex_lock(&panel->follower_lock); + + list_add_tail(&follower->list, &panel->followers); + if (panel->prepared && follower->funcs->panel_prepared) { + ret = follower->funcs->panel_prepared(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_prepared, ret); + } + if (panel->enabled && follower->funcs->panel_enabled) { + ret = follower->funcs->panel_enabled(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_enabled, ret); + } + + mutex_unlock(&panel->follower_lock); + + return 0; +} +EXPORT_SYMBOL(drm_panel_add_follower); + +/** + * drm_panel_remove_follower() - Reverse drm_panel_add_follower(). + * @follower: The panel follower descriptor for the follower. + * + * Undo drm_panel_add_follower(). This includes calling the follower's + * unpreparing/disabling function if we're removed from a panel that's currently + * prepared/enabled. + * + * Return: 0 or an error code. + */ +void drm_panel_remove_follower(struct drm_panel_follower *follower) +{ + struct drm_panel *panel = follower->panel; + int ret; + + mutex_lock(&panel->follower_lock); + + if (panel->enabled && follower->funcs->panel_disabling) { + ret = follower->funcs->panel_disabling(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_disabling, ret); + } + if (panel->prepared && follower->funcs->panel_unpreparing) { + ret = follower->funcs->panel_unpreparing(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_unpreparing, ret); + } + list_del_init(&follower->list); + + mutex_unlock(&panel->follower_lock); + + put_device(panel->dev); +} +EXPORT_SYMBOL(drm_panel_remove_follower); + +static void drm_panel_remove_follower_void(void *follower) +{ + drm_panel_remove_follower(follower); +} + +/** + * devm_drm_panel_add_follower() - devm version of drm_panel_add_follower() + * @follower_dev: The 'struct device' for the follower. + * @follower: The panel follower descriptor for the follower. + * + * Handles calling drm_panel_remove_follower() using devm on the follower_dev. + * + * Return: 0 or an error code. + */ +int devm_drm_panel_add_follower(struct device *follower_dev, + struct drm_panel_follower *follower) +{ + int ret; + + ret = drm_panel_add_follower(follower_dev, follower); + if (ret) + return ret; + + return devm_add_action_or_reset(follower_dev, + drm_panel_remove_follower_void, follower); +} +EXPORT_SYMBOL(devm_drm_panel_add_follower); + +#if IS_REACHABLE(CONFIG_BACKLIGHT_CLASS_DEVICE) +/** + * drm_panel_of_backlight - use backlight device node for backlight + * @panel: DRM panel + * + * Use this function to enable backlight handling if your panel + * uses device tree and has a backlight phandle. + * + * When the panel is enabled backlight will be enabled after a + * successful call to &drm_panel_funcs.enable() + * + * When the panel is disabled backlight will be disabled before the + * call to &drm_panel_funcs.disable(). + * + * A typical implementation for a panel driver supporting device tree + * will call this function at probe time. Backlight will then be handled + * transparently without requiring any intervention from the driver. + * + * Return: 0 on success or a negative error code on failure. + */ +int drm_panel_of_backlight(struct drm_panel *panel) +{ + struct backlight_device *backlight; + + if (!panel || !panel->dev) + return -EINVAL; + + backlight = devm_of_find_backlight(panel->dev); + + if (IS_ERR(backlight)) + return PTR_ERR(backlight); + + panel->backlight = backlight; + return 0; +} +EXPORT_SYMBOL(drm_panel_of_backlight); +#endif + +MODULE_AUTHOR("Thierry Reding "); +MODULE_DESCRIPTION("DRM panel infrastructure"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/gpu/drm/drm_panel_backlight_quirks.c b/drivers/gpu/drm/drm_panel_backlight_quirks.c index f85cb293a3db..e417c7533053 100644 --- a/drivers/gpu/drm/drm_panel_backlight_quirks.c +++ b/drivers/gpu/drm/drm_panel_backlight_quirks.c @@ -20,6 +20,15 @@ struct drm_get_panel_backlight_quirk { }; static const struct drm_get_panel_backlight_quirk drm_panel_min_backlight_quirks[] = { + /* Lenovo Legion 5 15ARH05, AUX backlight non-functional, force PWM */ + { + .dmi_match.field = DMI_SYS_VENDOR, + .dmi_match.value = "LENOVO", + .dmi_match_other.field = DMI_PRODUCT_VERSION, + .dmi_match_other.value = "Lenovo Legion 5 15ARH05", + .ident.panel_id = drm_edid_encode_panel_id('B', 'O', 'E', 0x08df), + .quirk = { .force_pwm = true, }, + }, /* 13 inch matte panel */ { .dmi_match.field = DMI_BOARD_VENDOR, diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index d4dc8cb45bce..e26ead5b5e58 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -760,27 +760,32 @@ static void output_poll_execute(struct work_struct *work) struct drm_connector *connector; struct drm_connector_list_iter conn_iter; enum drm_connector_status old_status; - bool repoll = false, changed; + bool repoll = false, changed = false; u64 old_epoch_counter; if (!dev->mode_config.poll_enabled) return; - /* Pick up any changes detected by the probe functions. */ - changed = dev->mode_config.delayed_event; - dev->mode_config.delayed_event = false; - if (!drm_kms_helper_poll) { if (dev->mode_config.poll_running) { drm_kms_helper_disable_hpd(dev); dev->mode_config.poll_running = false; } - goto out; + + scoped_guard(mutex, &dev->mode_config.mutex) { + changed = dev->mode_config.delayed_event; + dev->mode_config.delayed_event = false; + } + + if (changed) + drm_kms_helper_hotplug_event(dev); + + return; } if (!mutex_trylock(&dev->mode_config.mutex)) { - repoll = true; - goto out; + schedule_delayed_work(delayed_work, DRM_OUTPUT_POLL_PERIOD); + return; } drm_connector_list_iter_begin(dev, &conn_iter); @@ -836,16 +841,23 @@ static void output_poll_execute(struct work_struct *work) connector->base.id, connector->name, old_epoch_counter, connector->epoch_counter); + drm_sysfs_connector_hotplug_event(connector); changed = true; } } drm_connector_list_iter_end(&conn_iter); + /* Pick up any changes detected by the probe functions. */ + if (dev->mode_config.delayed_event) { + dev->mode_config.delayed_event = false; + changed = true; + drm_sysfs_hotplug_event(dev); + } + mutex_unlock(&dev->mode_config.mutex); -out: if (changed) - drm_kms_helper_hotplug_event(dev); + drm_client_dev_hotplug(dev); if (repoll) schedule_delayed_work(delayed_work, DRM_OUTPUT_POLL_PERIOD); @@ -1081,9 +1093,9 @@ EXPORT_SYMBOL(drm_connector_helper_hpd_irq_event); */ bool drm_helper_hpd_irq_event(struct drm_device *dev) { - struct drm_connector *connector, *first_changed_connector = NULL; struct drm_connector_list_iter conn_iter; - int changed = 0; + struct drm_connector *connector; + bool changed = false; if (!dev->mode_config.poll_enabled) return false; @@ -1096,24 +1108,15 @@ bool drm_helper_hpd_irq_event(struct drm_device *dev) continue; if (check_connector_changed(connector)) { - if (!first_changed_connector) { - drm_connector_get(connector); - first_changed_connector = connector; - } - - changed++; + changed = true; + drm_sysfs_connector_hotplug_event(connector); } } drm_connector_list_iter_end(&conn_iter); mutex_unlock(&dev->mode_config.mutex); - if (changed == 1) - drm_kms_helper_connector_hotplug_event(first_changed_connector); - else if (changed > 0) - drm_kms_helper_hotplug_event(dev); - - if (first_changed_connector) - drm_connector_put(first_changed_connector); + if (changed) + drm_client_dev_hotplug(dev); return changed; } diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c index c314b3cb5e70..5c23182f4d33 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c @@ -545,13 +545,13 @@ static int etnaviv_hw_reset(struct etnaviv_gpu *gpu) u32 pulse_eater = 0x01590880; /* disable clock gating */ - gpu_write_power(gpu, VIVS_PM_POWER_CONTROLS, 0x0); + gpu_write_power_sync(gpu, VIVS_PM_POWER_CONTROLS, 0x0); /* disable pulse eater */ pulse_eater |= BIT(17); gpu_write_power(gpu, VIVS_PM_PULSE_EATER, pulse_eater); pulse_eater |= BIT(0); - gpu_write_power(gpu, VIVS_PM_PULSE_EATER, pulse_eater); + gpu_write_power_sync(gpu, VIVS_PM_PULSE_EATER, pulse_eater); /* enable clock */ control = VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(fscale); @@ -584,9 +584,9 @@ static int etnaviv_hw_reset(struct etnaviv_gpu *gpu) /* read idle register. */ idle = gpu_read(gpu, VIVS_HI_IDLE_STATE); - /* try resetting again if FE is not idle */ - if ((idle & VIVS_HI_IDLE_STATE_FE) == 0) { - dev_dbg(gpu->dev, "FE is not idle\n"); + /* try resetting again if any module is not idle */ + if ((idle & gpu->idle_mask) != gpu->idle_mask) { + dev_dbg(gpu->dev, "GPU modules not idle\n"); continue; } @@ -600,6 +600,23 @@ static int etnaviv_hw_reset(struct etnaviv_gpu *gpu) continue; } + /* try resetting again if MMUv2 is not disabled */ + if (gpu->identity.minor_features1 & chipMinorFeatures1_MMU_VERSION) { + if (gpu->sec_mode == ETNA_SEC_KERNEL) { + if (gpu_read(gpu, VIVS_MMUv2_SEC_CONTROL) & + VIVS_MMUv2_SEC_CONTROL_ENABLE) { + dev_dbg(gpu->dev, "MMU is not disabled\n"); + continue; + } + } else { + if (gpu_read(gpu, VIVS_MMUv2_CONTROL) & + VIVS_MMUv2_CONTROL_ENABLE) { + dev_dbg(gpu->dev, "MMU is not disabled\n"); + continue; + } + } + } + /* enable debug register access */ control &= ~VIVS_HI_CLOCK_CONTROL_DISABLE_DEBUG_REGISTERS; gpu_write(gpu, VIVS_HI_CLOCK_CONTROL, control); @@ -645,7 +662,7 @@ static void etnaviv_gpu_enable_mlcg(struct etnaviv_gpu *gpu) gpu->identity.revision == 0x4302) ppc |= VIVS_PM_POWER_CONTROLS_DISABLE_STALL_MODULE_CLOCK_GATING; - gpu_write_power(gpu, VIVS_PM_POWER_CONTROLS, ppc); + gpu_write_power_sync(gpu, VIVS_PM_POWER_CONTROLS, ppc); pmc = gpu_read_power(gpu, VIVS_PM_MODULE_CONTROLS); @@ -689,7 +706,7 @@ static void etnaviv_gpu_enable_mlcg(struct etnaviv_gpu *gpu) pmc |= VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_RA_HZ; pmc |= VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_RA_EZ; - gpu_write_power(gpu, VIVS_PM_MODULE_CONTROLS, pmc); + gpu_write_power_sync(gpu, VIVS_PM_MODULE_CONTROLS, pmc); } void etnaviv_gpu_start_fe(struct etnaviv_gpu *gpu, u32 address, u16 prefetch) @@ -755,7 +772,7 @@ static void etnaviv_gpu_setup_pulse_eater(struct etnaviv_gpu *gpu) pulse_eater |= BIT(18); } - gpu_write_power(gpu, VIVS_PM_PULSE_EATER, pulse_eater); + gpu_write_power_sync(gpu, VIVS_PM_PULSE_EATER, pulse_eater); } static void etnaviv_gpu_hw_init(struct etnaviv_gpu *gpu) diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h index 5cb46c84e03a..e89d591d7404 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.h +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h @@ -204,6 +204,12 @@ static inline u32 gpu_read_power(struct etnaviv_gpu *gpu, u32 reg) return readl(gpu->mmio + gpu_fix_power_address(gpu, reg)); } +static inline void gpu_write_power_sync(struct etnaviv_gpu *gpu, u32 reg, u32 data) +{ + gpu_write_power(gpu, reg, data); + gpu_read_power(gpu, reg); +} + int etnaviv_gpu_get_param(struct etnaviv_gpu *gpu, u32 param, u64 *value); int etnaviv_gpu_init(struct etnaviv_gpu *gpu); diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c index b80540328150..e1c9e2946b8a 100644 --- a/drivers/gpu/drm/exynos/exynos_dp.c +++ b/drivers/gpu/drm/exynos/exynos_dp.c @@ -24,11 +24,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include "exynos_drm_crtc.h" @@ -79,6 +79,10 @@ static void exynos_dp_nop(struct drm_encoder *encoder) /* do nothing */ } +static const struct drm_encoder_funcs exynos_dp_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = { .mode_set = exynos_dp_mode_set, .enable = exynos_dp_nop, @@ -95,7 +99,12 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data) dp->drm_dev = drm_dev; - drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); + ret = drm_encoder_init(drm_dev, encoder, &exynos_dp_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) { + dev_err(dp->dev, "Failed to initialize encoder\n"); + return ret; + } drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs); @@ -182,8 +191,15 @@ static int exynos_dp_probe(struct platform_device *pdev) out: dp->adp = analogix_dp_probe(dev, &dp->plat_data); - if (IS_ERR(dp->adp)) + if (IS_ERR(dp->adp)) { + /* + * The driver core does not invoke remove() for failed probes, + * so release the probe-time panel reference here. + */ + if (dp->plat_data.panel) + drm_panel_put(dp->plat_data.panel); return PTR_ERR(dp->adp); + } if (dp->plat_data.panel || dp->plat_data.next_bridge) return component_add(&pdev->dev, &exynos_dp_ops); @@ -193,6 +209,16 @@ out: static void exynos_dp_remove(struct platform_device *pdev) { + struct exynos_dp_device *dp = platform_get_drvdata(pdev); + + /* + * Release the probe-time reference from of_drm_find_panel(). If bind + * ran, the panel_bridge holds a second reference that devm cleanup + * will release when the bridge is destroyed after remove() returns. + */ + if (dp->plat_data.panel) + drm_panel_put(dp->plat_data.panel); + component_del(&pdev->dev, &exynos_dp_ops); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_dpi.c b/drivers/gpu/drm/exynos/exynos_drm_dpi.c index 0dc36df6ada3..0683236aa5df 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dpi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dpi.c @@ -12,10 +12,10 @@ #include #include +#include #include #include #include -#include #include