From: Alex Hung <[email protected]>

[WHAT]
Add KUnit coverage for the EDID parsing helpers edid_extract_panel_id,
apply_edid_quirks and dm_helpers_parse_edid_caps, together with the
ACPI/VBIOS EDID readers dm_helpers_probe_acpi_edid,
dm_helpers_read_acpi_edid and dm_helpers_read_vbios_hardcoded_edid.

Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <[email protected]>
Signed-off-by: Alex Hung <[email protected]>
Signed-off-by: George Zhang <[email protected]>
---
 .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c |  27 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_helpers.h |  19 +
 .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c  | 727 ++++++++++++++++++
 3 files changed, 769 insertions(+), 4 deletions(-)

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 9c4e0a4e251c..881518a861d5 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,6 +238,7 @@ enum dc_edid_status dm_helpers_parse_edid_caps(

        return result;
 }
+EXPORT_IF_KUNIT(dm_helpers_parse_edid_caps);

 static void
 fill_dc_mst_payload_table_from_drm(struct dc_link *link,
@@ -979,6 +981,20 @@ bool dm_helpers_dp_write_dsc_enable(
        return ret;
 }

+#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)
 {
        // TODO
@@ -1002,7 +1018,7 @@ bool dm_helpers_is_dp_sink_present(struct dc_link *link)
        return 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 +1056,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 +1079,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 +1119,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)
 {
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..e5cbcd425847 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,31 @@
 #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
 #include <drm/drm_edid.h>

+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);
 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);
+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);
 #endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */

 #endif /* __AMDGPU_DM_HELPERS_H__ */
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..3766d8d211e4 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 <kunit/test.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_kunit_helpers.h>
+#include <drm/display/drm_dp_mst_helper.h>

 #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() */

 /**
@@ -581,6 +1281,33 @@ 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),
--
2.55.0


Reply via email to