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/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 8339ab6f5d32..f5d65bd0ac25 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -926,7 +926,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_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..0af8b7be326e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c @@ -498,6 +498,25 @@ 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)) 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_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index 0455c2cd043f..deab64765388 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -2178,6 +2178,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..472e96ae884e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1203,28 +1203,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) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index d9edac5f321d..44e7d2e3e6df 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -3367,9 +3367,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_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index ad631ad31899..5362705143bc 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), @@ -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; 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_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index c66e69b62361..1e3a23c81552 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -921,6 +921,206 @@ 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 + */ +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 + * + * Returns 0 on success, -ENOSPC if all slots are used (caller should + * fall back to system memory path). + */ +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 + */ +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 + */ +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 + */ +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_object.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c index f98bfba59a2c..d4a9d5e8fb42 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c @@ -276,10 +276,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 +304,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 +1699,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_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index fd2c0cc1b958..24fd24e8d874 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.", @@ -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_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index b10b0878df37..7920675af1d1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2684,12 +2684,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..babaabe2d891 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1422,18 +1422,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_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..157d4168907c 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); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index d2c5bb50d94a..c0f581e416f9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -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/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index 55342962422b..a998ce77da40 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -4988,12 +4988,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; } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 2ef6bc818aba..0cbbdc3694f4 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -1920,12 +1920,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; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 6fedb6df8db9..b3887c5262a0 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -1597,12 +1597,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; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c index b19a4956c222..1f7e243d2ad9 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); @@ -1149,6 +1270,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 +1557,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 +1598,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; } @@ -3912,6 +4187,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_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index 556e5cb3f4cd..1cad6dc9476e 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c @@ -2442,12 +2442,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; } 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/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index defa050fd871..e099304b4ac4 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -121,6 +121,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 +147,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 +171,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 +183,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; diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 72ca7302bbfb..b72da75dd851 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -335,8 +335,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 +389,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 +772,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 +839,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) { @@ -1904,10 +1964,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 +2028,10 @@ static int mes_v11_0_hw_init(struct amdgpu_ip_block *ip_block) 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/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/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..5446d89a84b3 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -3071,6 +3071,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 +3200,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_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index 28354a4e5dd5..98a5512b701b 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); @@ -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 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope 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/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 67b825cbb88f..e7080880b221 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -779,6 +779,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 +1018,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); @@ -1496,17 +1501,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 +1535,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 +1557,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 +1586,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"); } } @@ -3522,21 +3546,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 +3567,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 +3600,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 +3614,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. @@ -3927,9 +3951,13 @@ static 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 @@ -3939,7 +3967,8 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, * 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); + 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); @@ -3955,7 +3984,8 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, /* 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); + 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); @@ -4104,6 +4134,28 @@ 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) { + drm_crtc_vblank_get(&acrtc->base); + WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE); + /* Arm flip completion handling and event delivery after programming. */ + prepare_flip_isr(acrtc); + } else if (cursor_update) { + drm_crtc_vblank_get(&acrtc->base); + acrtc->event = acrtc->base.state->event; + acrtc->base.state->event = NULL; + } +} + static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, struct drm_device *dev, struct amdgpu_display_manager *dm, @@ -4126,6 +4178,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 { @@ -4288,6 +4342,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]; @@ -4356,39 +4412,24 @@ 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; + } + + /* + * 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) { + scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { + 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. */ @@ -4480,6 +4521,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); } @@ -5896,6 +6046,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, /* 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 @@ -6610,10 +6761,15 @@ static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, /* 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 + * + * Always set native cursor mode when the CRTC is disabled, + * to make sure it doesn't cause atomic commits to fail when + * they are trying to disable the CRTC. */ 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)) { + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) || + !dm_crtc_state->base.enable) { *cursor_mode = DM_CURSOR_NATIVE_MODE; return 0; } @@ -6649,6 +6805,31 @@ static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, 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; @@ -6804,7 +6985,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 +7000,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 +7020,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 +7037,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 +7046,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 +7091,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 +7105,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; } @@ -6938,7 +7119,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, ret = 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 +7151,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 +7164,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 +7177,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 +7191,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 +7207,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; } @@ -7164,13 +7345,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 +7359,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 +7368,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 +7380,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 +7469,7 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, 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..f396803d1485 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: * 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..e97ce5c2c0e0 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 @@ -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; 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..4eaf5e303850 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 @@ -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_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 88f938a8f24f..5c5f9c3e6d77 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 @@ -235,7 +235,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 +251,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 +293,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,6 +331,7 @@ 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) { @@ -391,44 +394,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 +440,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 +463,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 +500,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 +619,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 +640,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); @@ -798,7 +844,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 +984,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 +1005,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 +1275,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 +1339,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); @@ -2333,7 +2384,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 +2400,7 @@ amdgpu_dm_connector_atomic_check(struct drm_connector *conn, return ret; } + crtc = new_con_state->crtc; if (!crtc) return 0; 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..60f7ab63b782 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 @@ -147,6 +147,17 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) 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); 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 +166,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_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 0ad7704800d9..d63688b9d93d 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 @@ -281,7 +281,14 @@ 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) { + rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, enable); + } else if (dc_supports_vrr(dm->dc->ctx->dce_version)) { if (enable) { /* vblank irq on -> Only need vupdate irq in vrr mode */ if (amdgpu_dm_crtc_vrr_active(acrtc_state)) @@ -292,39 +299,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_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 2692d1890ba2..c17488e57eae 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 @@ -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 && @@ -507,6 +506,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 +640,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); 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..5f43f6a74ffd 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 @@ -258,6 +258,30 @@ static void link_lock(struct hdcp_workqueue *work, bool 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, struct amdgpu_dm_connector *aconnector, @@ -281,22 +305,8 @@ 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); @@ -363,6 +373,7 @@ 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) { @@ -381,7 +392,8 @@ static void event_callback(struct work_struct *work) process_output(hdcp_work); } -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,6 +452,7 @@ 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) { @@ -576,15 +589,27 @@ static bool enable_assr(void *handle, struct dc_link *link) static 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 +647,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; @@ -872,4 +897,5 @@ struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, 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..a82a20b80518 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 @@ -108,6 +108,11 @@ 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); #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..30ac1f32fad5 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 @@ -98,7 +98,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; @@ -141,6 +141,7 @@ static void apply_edid_quirks(struct dc_link *link, struct edid *edid, return; } } +EXPORT_IF_KUNIT(apply_edid_quirks); /** * dm_helpers_parse_edid_caps() - Parse edid caps @@ -237,8 +238,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 +290,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 +329,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 +382,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 +391,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 +400,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 +431,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 +466,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 +495,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 +715,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 +729,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 +803,9 @@ static bool execute_synaptics_rc_command(struct drm_dp_aux *aux, 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 +869,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 +910,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 +994,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 +1032,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 +1072,9 @@ dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len) 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 +1095,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 +1135,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 +1160,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 +1183,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 +1256,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 +1336,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 +1351,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 +1384,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 +1402,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 +1438,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 +1464,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 +1603,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 +1621,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 +1629,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 +1816,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 +1891,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..ae3f81f53074 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 @@ -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 @@ static void dm_handle_hpd_rx_offload_work(struct work_struct *work) 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 @@ struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(stru 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 @@ static void handle_hpd_rx_irq(void *param) mutex_unlock(&aconnector->hpd_lock); } +EXPORT_IF_KUNIT(handle_hpd_rx_irq); /** * dmub_hpd_callback - DMUB HPD interrupt processing callback. @@ -1559,7 +1605,7 @@ static void handle_hpd_rx_irq(void *param) * 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,91 +1938,26 @@ 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; @@ -2000,12 +1986,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 +2029,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 +2067,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 +2190,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 +2210,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 +2245,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 +2309,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 +2448,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 +2484,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 +2555,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 +2567,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 +2605,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..a9575bf25fc2 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 @@ -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_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index 0546efea5de1..a45d8d79f3b2 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 @@ -862,10 +862,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 +901,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..26f35434a92f 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 @@ -328,7 +328,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 +374,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 +415,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, @@ -927,14 +927,14 @@ 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); @@ -975,13 +975,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 +994,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; } @@ -1058,7 +1060,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 +1256,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 +1302,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 +1327,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 +1487,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 +1500,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 +1520,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 +1537,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 +1579,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, @@ -1636,8 +1644,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 +1665,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 +1870,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..365e306a52e7 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 @@ -92,7 +92,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 +100,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 +115,14 @@ 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); #endif #endif 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..9e7bad4d6ed0 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 @@ -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 @@ static int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector 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/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c index 79ff5d9b3fa5..3f0108e9a951 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,10 +6,13 @@ */ #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" @@ -38,6 +41,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() */ /** @@ -455,9 +509,73 @@ 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() */ + 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 */ 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..f55f93747df7 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,587 @@ 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) +{ + struct drm_device *ddev; + + ddev = kunit_kzalloc(test, sizeof(*ddev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddev); + + INIT_LIST_HEAD(&ddev->mode_config.connector_list); + spin_lock_init(&ddev->mode_config.connector_list_lock); + dm->ddev = ddev; +} + +/* 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 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]); +} + +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 +1394,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 +1420,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 +1621,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 +1629,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 +1643,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 +1652,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 +1666,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 +1770,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 +1791,90 @@ 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), + /* 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 +1926,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_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index aa451064b30c..0a5d439f66c3 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,10 +6,14 @@ */ #include +#include #include #include +#include #include #include +#include +#include #include #include "dc.h" @@ -290,6 +294,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 +578,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 +934,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 +1235,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() */ /** @@ -2015,6 +2196,1414 @@ 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 = kunit_kzalloc(test, 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 = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + link = kunit_kzalloc(test, 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 = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + link = kunit_kzalloc(test, 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 = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + link = kunit_kzalloc(test, 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 = kunit_kzalloc(test, 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 = 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, 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 = 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_eDP, NULL), 0); + drm_object_attach_property(&ctx->aconnector->base.base, prop, + DRM_MODE_PANEL_TYPE_UNKNOWN); + + ctx->link = kunit_kzalloc(test, 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 = 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->conn_state = kunit_kzalloc(test, 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); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -2037,6 +3626,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 +3643,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 +3671,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 +3693,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 +3741,63 @@ 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), {} }; 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..dbcff92672e6 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 @@ -402,8 +402,236 @@ 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. + */ +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); + + INIT_WORK(&work->property_update_work, dummy_work_fn); + + return work; +} + +/** + * 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); +} + +/* End of tests for event_property_update() */ + +/* 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); +} + +/* End of tests for hdcp_create_workqueue() */ + 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 +651,18 @@ 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), + /* 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), {} }; 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..ed20e278742d 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,321 @@ 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 struct dc *dm_test_alloc_dc_with_ctx(struct kunit *test) +{ + struct dc_context *ctx; + struct dc *dc; + + 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); + + dc->ctx = ctx; + ctx->dc = dc; + + return dc; +} + +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_test_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 +356,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 +1148,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 +1175,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 +1193,2734 @@ 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 system_highpri_wq. + * amdgpu_dm_irq_fini() flushes each pending work item before freeing + * the handlers, so the handler is guaranteed to have run afterwards. + */ + amdgpu_dm_irq_fini(adev); + KUNIT_EXPECT_EQ(test, count, 1); +} + +/** + * 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 + * amdgpu_dm_irq_fini() flushes the queue, 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); + + amdgpu_dm_irq_fini(adev); + KUNIT_EXPECT_EQ(test, count, 2); +} + +/* 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_test_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_test_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_test_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_test_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_test_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_test_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; amdgpu_dm_irq_fini() flushes + * each pending work item before freeing, so it has run afterwards. + */ + amdgpu_dm_irq_fini(adev); + KUNIT_EXPECT_EQ(test, low_count, 1); +} + +/* 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_test_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 +3973,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_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..a751e67cadf1 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) @@ -579,7 +615,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 +659,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 +785,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. @@ -1162,37 +1269,1984 @@ 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, 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), @@ -1212,6 +3266,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_wb_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c index c71f61a2438d..b43bc244487e 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 @@ -364,6 +364,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 +419,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/clk_mgr/dcn42/dcn42_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c index d01e5969a670..3baac7fa313a 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 @@ -999,15 +999,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 */ diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 28339e4b6d67..8d77158229f1 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -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) @@ -1930,6 +1945,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]; @@ -6218,6 +6240,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 && 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..dbc12640b01c 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c @@ -720,20 +720,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..e1b7d359d917 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -65,7 +65,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.389" /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC @@ -632,6 +632,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, @@ -1288,6 +1289,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]; }; @@ -1933,6 +1936,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; @@ -2993,6 +2998,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 +3013,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_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index 90dd1ae7e953..5ba7a2fffcf0 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -1347,6 +1347,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; 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..ecb8493ec523 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 @@ -1229,9 +1229,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 +1285,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; 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/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/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/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/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..cce4f3065575 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 @@ -1287,7 +1287,9 @@ void dce110_blank_stream(struct pipe_ctx *pipe_ctx) return; if (link->local_sink && link->local_sink->sink_signal == SIGNAL_TYPE_EDP) { - if (!link->skip_implict_edp_power_control && hws) + if (link->skip_implict_edp_power_control) + return; + if (hws) hws->funcs.edp_backlight_control(link, false); link->dc->hwss.set_abm_immediate_disable(pipe_ctx); } @@ -1793,7 +1795,9 @@ enum dc_status dce110_apply_single_controller_ctx_to_hw( dc->link_srv->set_dsc_enable(pipe_ctx, true); } - if (!stream->dpms_off) + if (!stream->dpms_off && + !(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) dc->link_srv->set_dpms_on(context, pipe_ctx); /* DCN3.1 FPGA Workaround @@ -1812,7 +1816,9 @@ enum dc_status dce110_apply_single_controller_ctx_to_hw( * is constructed with the same sink). Make sure not to override * and link programming on the main. */ - if (dc_state_get_pipe_subvp_type(context, pipe_ctx) != SUBVP_PHANTOM) { + if (dc_state_get_pipe_subvp_type(context, pipe_ctx) != SUBVP_PHANTOM && + !(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { pipe_ctx->stream->link->psr_settings.psr_feature_enabled = false; pipe_ctx->stream->link->replay_settings.replay_feature_enabled = false; } 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/dcn31/dcn31_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c index 43e4edfe9182..6f1ad651ed2c 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) @@ -536,10 +537,12 @@ static void dcn31_reset_back_end_for_pipe( } ASSERT(!pipe_ctx->top_pipe); - dc->hwss.set_abm_immediate_disable(pipe_ctx); - link = pipe_ctx->stream->link; + if (!(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) + dc->hwss.set_abm_immediate_disable(pipe_ctx); + if (dc->hwseq) dc->hwseq->wa_state.skip_blank_stream = false; @@ -554,9 +557,11 @@ static void dcn31_reset_back_end_for_pipe( pipe_ctx->stream_res.tg, OPTC_DSC_DISABLED, 0, 0); - pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); - - pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); + if (!(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { + pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); + pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); + } if (pipe_ctx->stream_res.tg->funcs->set_odm_bypass) pipe_ctx->stream_res.tg->funcs->set_odm_bypass( pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing); @@ -585,7 +590,12 @@ static void dcn31_reset_back_end_for_pipe( * screen only, the dpms_off would be true but * VBIOS lit up eDP, so check link status too. */ - if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) + if (link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control) { + /* DMSS is holding the panel across the commit; skip dpms-off. */ + if (pipe_ctx->stream_res.audio) + dc->hwss.disable_audio_stream(pipe_ctx); + } else if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) dc->link_srv->set_dpms_off(pipe_ctx); else if (pipe_ctx->stream_res.audio) dc->hwss.disable_audio_stream(pipe_ctx); @@ -718,6 +728,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 +764,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/dcn35/dcn35_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c index 01027d120cb0..4ed7480d1efa 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) { @@ -1818,8 +1818,11 @@ void dcn35_disable_link_output(struct dc_link *link, disable_link_output_symclk_on_tx_off(link, DP_UNKNOWN_ENCODING); link->phy_state.symclk_state = SYMCLK_ON_TX_OFF; } else { - link_hwss->disable_link_output(link, link_res, signal); - link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; + if (!(signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { + link_hwss->disable_link_output(link, link_res, signal); + link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; + } } /* * Add the logic to extract BOTH power up and power down sequences 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..d8d3208fc1af 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 @@ -1101,8 +1101,11 @@ void dcn401_disable_link_output(struct dc_link *link, disable_link_output_symclk_on_tx_off(link, DP_UNKNOWN_ENCODING); link->phy_state.symclk_state = SYMCLK_ON_TX_OFF; } else { - link_hwss->disable_link_output(link, link_res, signal); - link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; + if (!(signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { + link_hwss->disable_link_output(link, link_res, signal); + link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; + } } if (signal == SIGNAL_TYPE_EDP && @@ -2150,7 +2153,12 @@ void dcn401_reset_back_end_for_pipe( * screen only, the dpms_off would be true but * VBIOS lit up eDP, so check link status too. */ - if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) + if (link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control) { + /* DMSS is holding the panel across the commit; skip dpms-off. */ + if (pipe_ctx->stream_res.audio) + dc->hwss.disable_audio_stream(pipe_ctx); + } else if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) dc->link_srv->set_dpms_off(pipe_ctx); else if (pipe_ctx->stream_res.audio) dc->hwss.disable_audio_stream(pipe_ctx); @@ -2175,12 +2183,15 @@ void dcn401_reset_back_end_for_pipe( * parent pipe. */ if (pipe_ctx->top_pipe == NULL) { + if (!(link->connector_signal == SIGNAL_TYPE_EDP && + link->skip_implict_edp_power_control)) { - dc->hwss.set_abm_immediate_disable(pipe_ctx); + dc->hwss.set_abm_immediate_disable(pipe_ctx); - pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); + pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); - pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); + pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); + } if (pipe_ctx->stream_res.tg->funcs->set_odm_bypass) pipe_ctx->stream_res.tg->funcs->set_odm_bypass( pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing); 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..cc8e96ffe7d1 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 &= 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/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..335ae952ef60 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c @@ -2296,6 +2296,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 +2344,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 +2365,43 @@ static bool deallocate_usb4_bandwidth(struct dc_stream_state *stream) return ret; } +static struct vpg *get_vpg(struct pipe_ctx *pipe_ctx) +{ + 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; +} + void link_set_dpms_off(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); - 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)) + if (dc_is_virtual_signal(stream->signal)) return; - 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 +2411,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 +2442,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 +2452,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,7 +2477,7 @@ 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; } @@ -2478,62 +2487,57 @@ void link_set_dpms_on( struct dc_state *state, struct pipe_ctx *pipe_ctx) { - struct dc *dc = pipe_ctx->stream->ctx->dc; + DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); struct dc_stream_state *stream = pipe_ctx->stream; - struct dc_link *link = stream->sink->link; + struct dc *dc = stream->ctx->dc; + struct dc_link *link = stream->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; + 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 = - pipe_ctx->stream->apply_edp_fast_boot_optimization; - - DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); + 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)) + if (dc_is_virtual_signal(stream->signal)) return; - 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); + link_wait_for_unlocked(link); if (!dc->config.unify_link_enc_assignment) link_enc = link_enc_cfg_get_link_enc(link); ASSERT(link_enc); - 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)) { 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 +2546,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,15 +2555,15 @@ 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); } @@ -2569,11 +2573,11 @@ void link_set_dpms_on( } /* 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) { @@ -2586,7 +2590,7 @@ void link_set_dpms_on( return; } - if (pipe_ctx->stream->dpms_off) + if (stream->dpms_off) return; /* For Dp tunneling link, a pending HPD means that we have a race condition between processing @@ -2604,20 +2608,21 @@ 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); + // TODO: Split DPMS-on into 3 functions at this point status = enable_link(state, pipe_ctx); if (status != DC_OK) { DC_LOG_WARNING("enabling link %u failed: %d\n", - pipe_ctx->stream->link->link_index, + link->link_index, status); /* Abort stream enable *unless* the failure was due to @@ -2625,21 +2630,21 @@ void link_set_dpms_on( * 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); + if ((status != DC_FAIL_DP_LINK_TRAINING && + status != DC_FAIL_HDMI_FRL_LINK_TRAINING) || + stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { + if (false == link->link_status.link_active) + disable_link(link, &pipe_ctx->link_res, + stream->signal); BREAK_TO_DEBUGGER(); return; } } + // TODO: Split DPMS-on into 3 functions at this point - 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 (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 +2656,35 @@ 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))) { - - 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); /* 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 +2693,22 @@ 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); + &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); - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { + if (dc_is_hdmi_signal(stream->signal)) set_avmute(pipe_ctx, false); - } } 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_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/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/dcn401/dcn401_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h index 47f82b818262..ce96fe1ddb7c 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 @@ -622,7 +622,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 */ 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..547a0b816539 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 @@ -729,8 +729,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 +762,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, @@ -2145,6 +2145,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..2334bc5b75b8 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 @@ -22,6 +22,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 +117,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 +266,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 +364,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 +492,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 +814,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 +836,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 +1047,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 +1915,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 +2017,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; @@ -2089,6 +2117,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/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/display/modules/power/power_psr.c b/drivers/gpu/drm/amd/display/modules/power/power_psr.c index 5ecb570c204e..0ad4c4924696 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_psr.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_psr.c @@ -58,6 +58,13 @@ bool mod_power_psr_notify_mode_change(struct mod_power *mod_power, // stream_index is passed as validated parameter active_psr_events = core_power->map[stream_index].psr_events; + /* DMSS holds the panel in a forced PSR freeze (e.g. during HDR/SDR toggle). + * Re-running edp_setup_psr would reprogram DPCD 0x170 and disturb the freeze, + * so skip the PSR re-setup until DMSS releases the override. + */ + if (active_psr_events & psr_event_os_override_hold) + return false; + /* Calculate PSR configurations */ mod_power_calc_psr_configs(&psr_config, link, stream); diff --git a/drivers/gpu/drm/amd/display/modules/power/power_replay.c b/drivers/gpu/drm/amd/display/modules/power/power_replay.c index e782501442c4..1ad2ee01d560 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_replay.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_replay.c @@ -805,6 +805,13 @@ void mod_power_replay_notify_mode_change(struct mod_power *mod_power, core_power = MOD_POWER_TO_CORE(mod_power); active_replay_events = core_power->map[stream_index].replay_events; + /* DMSS holds the panel in a forced freeze (e.g. during HDR/SDR toggle). + * Re-running dp_setup_replay would reprogram DPCD 0x37B and disturb the + * freeze, so skip the replay re-setup until DMSS releases the override. + */ + if (active_replay_events & replay_event_os_override_hold) + return; + link->replay_settings.replay_smu_opt_enable = (link->replay_settings.config.replay_smu_opt_supported && mod_power_only_edp(dc->current_state, stream)); 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/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/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..fbb59e5a4453 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c @@ -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/inc/hwmgr.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h index 7ebc1344023f..e01afbc39d67 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h +++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h @@ -833,14 +833,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..7ea7c4a5279b 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; @@ -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/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/smu14/smu_v14_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c index 75719c47a41e..3d73f2050bbe 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 @@ -1701,6 +1701,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 +1778,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/smu15/smu_v15_0_8_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c index 2105a1d7bb34..227cd86e13e8 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 @@ -1766,12 +1766,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..c2285fde8b3c 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 @@ -82,6 +82,43 @@ static uint64_t local_addr_to_xgmi_global_addr(struct ras_core_context *ras_core return (addr + xgmi->physical_node_id * xgmi->node_segment_size); } +/* + * 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 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, struct ras_cmd_ctx *cmd, void *data) { @@ -91,25 +128,35 @@ 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 (adev->gmc.xgmi.num_physical_nodes > 1) + req->address = local_addr_to_xgmi_global_addr(ras_core, req->address); } } 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..3b5da8515a20 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 @@ -332,8 +332,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/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/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index 3a8c5199a0fe..215e47c94d29 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c @@ -596,20 +596,59 @@ static bool radeon_read_disabled_bios(struct radeon_device *rdev) return legacy_read_disabled_bios(rdev); } +/** + * radeon_acpi_vfct_match() - Check if a VFCT entry matches the device + * @rdev: Radeon 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 radeon_acpi_vfct_match(struct radeon_device *rdev, + VFCT_IMAGE_HEADER *vhdr) +{ + /* Vendor and device IDs must always match */ + if (vhdr->VendorID != rdev->pdev->vendor || + vhdr->DeviceID != rdev->pdev->device) + return -ENODEV; + + if (vhdr->PCIDevice != PCI_SLOT(rdev->pdev->devfn) || + vhdr->PCIFunction != PCI_FUNC(rdev->pdev->devfn)) + return -ENODEV; + + /* Exact bus number match - preferred */ + if (vhdr->PCIBus == rdev->pdev->bus->number) + return 0; + + /* Bus mismatch but device identity matches (PCI renumbering case) */ + dev_notice(&rdev->pdev->dev, + "VFCT bus number mismatch: table %u != runtime %u, matching by device identity (vendor 0x%04x device 0x%04x)\n", + vhdr->PCIBus, rdev->pdev->bus->number, + rdev->pdev->vendor, rdev->pdev->device); + return 0; +} + #ifdef CONFIG_ACPI static bool radeon_acpi_vfct_bios(struct radeon_device *rdev) { struct acpi_table_header *hdr; acpi_size tbl_size; UEFI_ACPI_VFCT *vfct; - unsigned offset; + 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)) { - DRM_ERROR("ACPI VFCT table present but broken (too short #1)\n"); + dev_warn(&rdev->pdev->dev, "ACPI VFCT table present but broken (too short #1),skipping\n"); goto out; } @@ -622,33 +661,34 @@ static bool radeon_acpi_vfct_bios(struct radeon_device *rdev) offset += sizeof(VFCT_IMAGE_HEADER); if (offset > tbl_size) { - DRM_ERROR("ACPI VFCT image header truncated\n"); + dev_warn(&rdev->pdev->dev, "ACPI VFCT image header truncated,skipping\n"); goto out; } offset += vhdr->ImageLength; if (offset > tbl_size) { - DRM_ERROR("ACPI VFCT image truncated\n"); + dev_warn(&rdev->pdev->dev, "ACPI VFCT image truncated,skipping\n"); goto out; } if (vhdr->ImageLength && - vhdr->PCIBus == rdev->pdev->bus->number && - vhdr->PCIDevice == PCI_SLOT(rdev->pdev->devfn) && - vhdr->PCIFunction == PCI_FUNC(rdev->pdev->devfn) && - vhdr->VendorID == rdev->pdev->vendor && - vhdr->DeviceID == rdev->pdev->device) { + !radeon_acpi_vfct_match(rdev, vhdr)) { rdev->bios = kmemdup(&vbios->VbiosContent, vhdr->ImageLength, GFP_KERNEL); - if (rdev->bios) - r = true; + if (!rdev->bios || + rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) { + kfree(rdev->bios); + rdev->bios = NULL; + goto out; + } + r = true; goto out; } } - DRM_ERROR("ACPI VFCT table present but broken (too short #2)\n"); + dev_warn(&rdev->pdev->dev, "ACPI VFCT table present but broken (too short #2),skipping\n"); out: acpi_put_table(hdr); diff --git a/include/drm/drm_utils.h b/include/drm/drm_utils.h index 6a46f755daba..7e077484c5bb 100644 --- a/include/drm/drm_utils.h +++ b/include/drm/drm_utils.h @@ -19,6 +19,7 @@ int drm_get_panel_orientation_quirk(int width, int height); struct drm_panel_backlight_quirk { u16 min_brightness; u32 brightness_mask; + bool force_pwm; }; const struct drm_panel_backlight_quirk *