summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBhawanpreet Lakha <bhawanpreet.lakha@amd.com>2026-06-25 10:55:46 -0400
committerAlex Deucher <alexander.deucher@amd.com>2026-07-28 18:34:52 -0400
commit6be28c11dcb739cd3b2782ec36d035879805fedc (patch)
tree5280fb9f1f09335ddab15ab48fe82e54631235c9
parent46011ff577cfbcdceef53bc6796660a5fcac5e5f (diff)
downloadlinux-stable-6be28c11dcb739cd3b2782ec36d035879805fedc.tar.gz
linux-stable-6be28c11dcb739cd3b2782ec36d035879805fedc.zip
drm/amd/display: Add i2c and EDID parsing tests for connector
Add KUnit coverage for the i2c and EDID parsing helpers: amdgpu_dm_i2c_func(), amdgpu_dm_i2c_xfer(), get_amd_vsdb(), parse_hdmi_amd_vsdb() and parse_edid_displayid_vrr(). Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung <alex.hung@amd.com> Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com> Signed-off-by: Wayne Lin <wayne.lin@amd.com> Tested-by: Dan Wheeler <daniel.wheeler@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c15
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h10
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c249
3 files changed, 269 insertions, 5 deletions
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 7feb3105f8ba..01352fc9445e 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
@@ -3087,7 +3087,7 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
}
}
-static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap,
+STATIC_IFN_KUNIT int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap,
struct i2c_msg *msgs, int num)
{
struct amdgpu_i2c_adapter *i2c = i2c_get_adapdata(i2c_adap);
@@ -3131,11 +3131,13 @@ static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap,
kfree(cmd.payloads);
return result;
}
+EXPORT_IF_KUNIT(amdgpu_dm_i2c_xfer);
-static u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap)
+STATIC_IFN_KUNIT u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
+EXPORT_IF_KUNIT(amdgpu_dm_i2c_func);
static const struct i2c_algorithm amdgpu_dm_i2c_algo = {
.master_xfer = amdgpu_dm_i2c_xfer,
@@ -3496,7 +3498,7 @@ static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector,
return ret;
}
-static void parse_edid_displayid_vrr(struct drm_connector *connector,
+STATIC_IFN_KUNIT void parse_edid_displayid_vrr(struct drm_connector *connector,
const struct edid *edid)
{
u8 *edid_ext = NULL;
@@ -3538,8 +3540,9 @@ static void parse_edid_displayid_vrr(struct drm_connector *connector,
j++;
}
}
+EXPORT_IF_KUNIT(parse_edid_displayid_vrr);
-static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector,
+STATIC_IFN_KUNIT int get_amd_vsdb(struct amdgpu_dm_connector *aconnector,
struct amdgpu_hdmi_vsdb_info *vsdb_info)
{
struct drm_connector *connector = &aconnector->base;
@@ -3549,8 +3552,9 @@ static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector,
return connector->display_info.amd_vsdb.version != 0;
}
+EXPORT_IF_KUNIT(get_amd_vsdb);
-static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector,
+STATIC_IFN_KUNIT int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector,
const struct edid *edid,
struct amdgpu_hdmi_vsdb_info *vsdb_info)
{
@@ -3581,6 +3585,7 @@ static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector,
return valid_vsdb_found ? i : -ENODEV;
}
+EXPORT_IF_KUNIT(parse_hdmi_amd_vsdb);
/**
* amdgpu_dm_update_freesync_caps - Update Freesync capabilities
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index 082e613de3f1..13c54229d72c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -146,6 +146,16 @@ int amdgpu_dm_encoder_init(struct drm_device *dev,
uint32_t link_index);
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap,
+ struct i2c_msg *msgs, int num);
+u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap);
+void parse_edid_displayid_vrr(struct drm_connector *connector,
+ const struct edid *edid);
+int get_amd_vsdb(struct amdgpu_dm_connector *aconnector,
+ struct amdgpu_hdmi_vsdb_info *vsdb_info);
+int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector,
+ const struct edid *edid,
+ struct amdgpu_hdmi_vsdb_info *vsdb_info);
void amdgpu_dm_connector_funcs_force(struct drm_connector *connector);
enum dc_status dm_validate_stream_and_context(struct dc *dc,
struct dc_stream_state *stream);
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 d9aaf03dd470..c56efbc1f26b 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
@@ -17,6 +17,7 @@
#include <drm/drm_modes.h>
#include <drm/drm_property.h>
#include <linux/hdmi.h>
+#include <linux/i2c.h>
#include "dc.h"
#include "amdgpu.h"
@@ -4666,6 +4667,239 @@ static void dm_test_add_freesync_modes_null_edid_noop(struct kunit *test)
KUNIT_EXPECT_EQ(test, aconnector->num_modes, 7);
}
+/* EDID extension block tag values (avoids pulling in private drm headers). */
+#define DM_TEST_CEA_EXT 0x02
+#define DM_TEST_DISPLAYID_EXT 0x70
+
+/**
+ * dm_test_i2c_func_returns_flags - Test the i2c functionality flags
+ * @test: The KUnit test context
+ *
+ * The algorithm advertises plain I2C plus emulated SMBUS regardless of the
+ * adapter argument, which it never dereferences.
+ */
+static void dm_test_i2c_func_returns_flags(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_func(NULL),
+ I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL);
+}
+
+/**
+ * dm_test_i2c_xfer_no_ddc_pin - Test transfers without a DDC pin are rejected
+ * @test: The KUnit test context
+ *
+ * When the backing ddc_service has no ddc_pin the transfer bails out early
+ * with -EIO before touching the message buffers or the dc handle.
+ */
+static void dm_test_i2c_xfer_no_ddc_pin(struct kunit *test)
+{
+ struct amdgpu_i2c_adapter *i2c;
+ struct ddc_service *ddc;
+
+ i2c = kunit_kzalloc(test, sizeof(*i2c), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, i2c);
+ ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ddc);
+
+ i2c->ddc_service = ddc;
+ i2c_set_adapdata(&i2c->base, i2c);
+
+ /* ddc->ddc_pin is NULL -> transfer is rejected with -EIO. */
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_xfer(&i2c->base, NULL, 0), -EIO);
+}
+
+/**
+ * dm_test_get_amd_vsdb_unsupported - Test a zero VSDB version reports no support
+ * @test: The KUnit test context
+ */
+static void dm_test_get_amd_vsdb_unsupported(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_hdmi_vsdb_info vsdb_info = {0};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->base.display_info.amd_vsdb.version = 0;
+ aconnector->base.display_info.amd_vsdb.replay_mode = false;
+
+ KUNIT_EXPECT_EQ(test, get_amd_vsdb(aconnector, &vsdb_info), 0);
+ KUNIT_EXPECT_EQ(test, vsdb_info.amd_vsdb_version, 0);
+}
+
+/**
+ * dm_test_get_amd_vsdb_supported - Test a non-zero VSDB version is reported
+ * @test: The KUnit test context
+ *
+ * The display info's VSDB version and replay mode are copied out and a
+ * non-zero version reports support.
+ */
+static void dm_test_get_amd_vsdb_supported(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_hdmi_vsdb_info vsdb_info = {0};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->base.display_info.amd_vsdb.version = 2;
+ aconnector->base.display_info.amd_vsdb.replay_mode = true;
+
+ KUNIT_EXPECT_EQ(test, get_amd_vsdb(aconnector, &vsdb_info), 1);
+ KUNIT_EXPECT_EQ(test, vsdb_info.amd_vsdb_version, 2);
+ KUNIT_EXPECT_TRUE(test, vsdb_info.replay_mode);
+}
+
+/**
+ * dm_test_parse_hdmi_amd_vsdb_null_edid - Test NULL EDID returns -ENODEV
+ * @test: The KUnit test context
+ */
+static void dm_test_parse_hdmi_amd_vsdb_null_edid(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_hdmi_vsdb_info vsdb_info = {0};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ KUNIT_EXPECT_EQ(test,
+ parse_hdmi_amd_vsdb(aconnector, NULL, &vsdb_info),
+ -ENODEV);
+}
+
+/**
+ * dm_test_parse_hdmi_amd_vsdb_no_extensions - Test EDID without extensions
+ * @test: The KUnit test context
+ *
+ * An EDID that declares no extension blocks has no CEA block to parse.
+ */
+static void dm_test_parse_hdmi_amd_vsdb_no_extensions(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_hdmi_vsdb_info vsdb_info = {0};
+ struct edid *edid;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, edid);
+
+ edid->extensions = 0;
+
+ KUNIT_EXPECT_EQ(test,
+ parse_hdmi_amd_vsdb(aconnector, edid, &vsdb_info),
+ -ENODEV);
+}
+
+/**
+ * dm_test_parse_hdmi_amd_vsdb_no_cea_ext - Test EDID with no CEA extension
+ * @test: The KUnit test context
+ *
+ * An extension block that is not a CEA block leaves no VSDB to parse.
+ */
+static void dm_test_parse_hdmi_amd_vsdb_no_cea_ext(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_hdmi_vsdb_info vsdb_info = {0};
+ struct edid *edid;
+ u8 *raw;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ /* Base block + one extension block that is NOT a CEA extension. */
+ raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, raw);
+ edid = (struct edid *)raw;
+ edid->extensions = 1;
+ raw[EDID_LENGTH] = DM_TEST_DISPLAYID_EXT;
+
+ KUNIT_EXPECT_EQ(test,
+ parse_hdmi_amd_vsdb(aconnector, edid, &vsdb_info),
+ -ENODEV);
+}
+
+/**
+ * dm_test_parse_displayid_vrr_null_edid - Test NULL EDID leaves range untouched
+ * @test: The KUnit test context
+ */
+static void dm_test_parse_displayid_vrr_null_edid(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, connector);
+
+ parse_edid_displayid_vrr(connector, NULL);
+
+ KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 0);
+ KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.min_vfreq, 0);
+}
+
+/**
+ * dm_test_parse_displayid_vrr_no_displayid - Test EDID without a DisplayID ext
+ * @test: The KUnit test context
+ *
+ * Without a DisplayID extension block there is no dynamic range to extract.
+ */
+static void dm_test_parse_displayid_vrr_no_displayid(struct kunit *test)
+{
+ struct drm_connector *connector;
+ struct edid *edid;
+ u8 *raw;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, connector);
+ raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, raw);
+ edid = (struct edid *)raw;
+ edid->extensions = 1;
+ raw[EDID_LENGTH] = DM_TEST_CEA_EXT;
+
+ parse_edid_displayid_vrr(connector, edid);
+
+ KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 0);
+}
+
+/**
+ * dm_test_parse_displayid_vrr_sets_range - Test a DisplayID VRR block is parsed
+ * @test: The KUnit test context
+ *
+ * A DisplayID dynamic video timing range descriptor populates the connector's
+ * monitor refresh range.
+ */
+static void dm_test_parse_displayid_vrr_sets_range(struct kunit *test)
+{
+ struct drm_connector *connector;
+ struct edid *edid;
+ u8 *raw, *ext;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, connector);
+ raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, raw);
+ edid = (struct edid *)raw;
+ edid->extensions = 1;
+
+ ext = raw + EDID_LENGTH;
+ ext[0] = DM_TEST_DISPLAYID_EXT;
+ /*
+ * DisplayID dynamic video timing range descriptor, parsed from offset
+ * 1: tag 0x25, flags 0 (single-byte max), payload length 9, then the
+ * min/max vfreq bytes.
+ */
+ ext[1] = 0x25;
+ ext[2] = 0x00;
+ ext[3] = 9;
+ ext[10] = 40;
+ ext[11] = 144;
+
+ parse_edid_displayid_vrr(connector, edid);
+
+ KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.min_vfreq, 40);
+ KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 144);
+}
+
static struct kunit_case amdgpu_dm_connector_tests[] = {
/* get_subconnector_type */
KUNIT_CASE(dm_test_subconnector_type_none),
@@ -4917,6 +5151,21 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_add_fs_modes_no_preferred_mode),
/* amdgpu_dm_connector_add_freesync_modes */
KUNIT_CASE(dm_test_add_freesync_modes_null_edid_noop),
+ /* amdgpu_dm_i2c_func */
+ KUNIT_CASE(dm_test_i2c_func_returns_flags),
+ /* amdgpu_dm_i2c_xfer */
+ KUNIT_CASE(dm_test_i2c_xfer_no_ddc_pin),
+ /* get_amd_vsdb */
+ KUNIT_CASE(dm_test_get_amd_vsdb_unsupported),
+ KUNIT_CASE(dm_test_get_amd_vsdb_supported),
+ /* parse_hdmi_amd_vsdb */
+ KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_null_edid),
+ KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_no_extensions),
+ KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_no_cea_ext),
+ /* parse_edid_displayid_vrr */
+ KUNIT_CASE(dm_test_parse_displayid_vrr_null_edid),
+ KUNIT_CASE(dm_test_parse_displayid_vrr_no_displayid),
+ KUNIT_CASE(dm_test_parse_displayid_vrr_sets_range),
{}
};