public inbox for igt-dev@lists.freedesktop.org
 help / color / mirror / Atom feed
From: Mohammed Bilal <mohammed.bilal@intel.com>
To: igt-dev@lists.freedesktop.org
Cc: kunal1.joshi@intel.com, Mohammed Bilal <mohammed.bilal@intel.com>
Subject: [PATCH i-g-t v1 22/25] tests/chamelium/v3: Add EDID tests for Chamelium v3
Date: Tue, 28 Apr 2026 10:16:31 +0530	[thread overview]
Message-ID: <20260428044644.257001-23-mohammed.bilal@intel.com> (raw)
In-Reply-To: <20260428044644.257001-1-mohammed.bilal@intel.com>

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=yes, Size: 43839 bytes --]

This commit adds EDID testing capabilities for Chamelium v3 including
EDID read/write, mode detection, and multiple monitor EDID support.

Signed-off-by: Mohammed Bilal <mohammed.bilal@intel.com>
---
 tests/chamelium/v3/kms_chamelium_v3_edid.c | 1357 ++++++++++++++++++++
 tests/meson.build                          |    1 +
 2 files changed, 1358 insertions(+)
 create mode 100644 tests/chamelium/v3/kms_chamelium_v3_edid.c

diff --git a/tests/chamelium/v3/kms_chamelium_v3_edid.c b/tests/chamelium/v3/kms_chamelium_v3_edid.c
new file mode 100644
index 000000000..68b361bfe
--- /dev/null
+++ b/tests/chamelium/v3/kms_chamelium_v3_edid.c
@@ -0,0 +1,1357 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ *
+ * Chamelium V3 EDID Tests
+ *
+ * Tests EDID reading (strict byte comparison), custom EDID upload,
+ * EDID change detection, mode timing verification, and resolution validation.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <math.h>
+#include <time.h>
+
+#include "igt.h"
+#include "igt_edid.h"
+#include "igt_aux.h"
+#include "chamelium/v3/igt_chamelium.h"
+#include "monitor_edids/dp_edids.h"
+#include "monitor_edids/hdmi_edids.h"
+#include "monitor_edids/monitor_edids_helper.h"
+
+/**
+ * TEST: kms chamelium v3 edid
+ * Category: Display
+ * Description: EDID tests for Chamelium V3
+ * Driver requirement: i915, xe
+ * Mega feature: General Display Features
+ *
+ * SUBTEST: dp-edid-read
+ * Description: Make sure the EDID exposed by KMS is the same as the screen's
+ *
+ * SUBTEST: hdmi-edid-read
+ * Description: Make sure the EDID exposed by KMS is the same as the screen's
+ *
+ * SUBTEST: dp-edid-stress-resolution-%s
+ * Description: Stress test the DUT by testing multiple EDIDs, one right after
+ *              the other, and ensure their validity by check the real screen
+ *              resolution vs the advertised mode (%arg[1]) resolution.
+ *
+ * SUBTEST: hdmi-edid-stress-resolution-%s
+ * Description: Stress test the DUT by testing multiple EDIDs, one right after
+ *              the other, and ensure their validity by check the real screen
+ *              resolution vs the advertised mode (%arg[1]) resolution.
+ *
+ * arg[1]:
+ *
+ * @4k:      4K
+ * @non-4k:  Non 4K
+ *
+ * SUBTEST: dp-edid-resolution-list
+ * Description: Get an EDID with many modes of different configurations, set
+ *              them on the screen and check the screen resolution matches the
+ *              mode resolution.
+ *
+ * SUBTEST: dp-edid-change-during-%s
+ * Description: Simulate a screen being unplugged and another screen being
+ *              plugged during suspend, check that a uevent is sent and
+ *              connector status is updated during %arg[1]
+ *
+ * SUBTEST: hdmi-edid-change-during-%s
+ * Description: Simulate a screen being unplugged and another screen being
+ *              plugged during suspend, check that a uevent is sent and
+ *              connector status is updated during %arg[1]
+ *
+ * arg[1]:
+ *
+ * @hibernate:    hibernation
+ * @suspend:      suspend
+ *
+ * SUBTEST: dp-mode-timings
+ * Description: For each mode of the IGT base EDID, perform a modeset and check
+ *              the mode detected by the Chamelium receiver matches the mode we
+ *              set
+ *
+ * SUBTEST: hdmi-mode-timings
+ * Description: For each mode of the IGT base EDID, perform a modeset and check
+ *              the mode detected by the Chamelium receiver matches the mode we
+ *              set
+ */
+
+#define HPD_WAIT_TIME_MS 3000
+#define EDID_BLOCK_SIZE 128
+#define EDID_SETTLE_TIME_MS 2000
+#define CONNECTOR_REPROBE_RETRIES 10
+#define MODE_CLOCK_ACCURACY 0.05 /* 5% tolerance */
+#define VIDEO_STABLE_TIMEOUT_S 5
+#define MODESET_SETTLE_TIME_MS 2000
+#define CHAMELIUM_HOTPLUG_TIMEOUT 20
+
+/* FPGA DP ports (recommended) */
+#define FPGA_DP1_PORT 4
+#define FPGA_DP2_PORT 5
+#define ITE_HDMI1_PORT 2
+#define ITE_HDMI2_PORT 3
+
+struct edid_test_data {
+	struct igt_chamelium_v3 *chamelium;
+	chamelium_v3_port_id *ports;
+	int port_count;
+	int drm_fd;
+};
+
+/* Find a DP port, preferring FPGA ports */
+static chamelium_v3_port_id find_dp_port(struct edid_test_data *data)
+{
+	int i;
+
+	/* Prefer FPGA DP ports (4, 5) over ITE DP ports (0, 1) */
+	for (i = 0; i < data->port_count; i++) {
+		chamelium_v3_port_id port = data->ports[i];
+
+		if (chamelium_v3_port_is_dp(data->chamelium, port) &&
+		    (port == FPGA_DP1_PORT || port == FPGA_DP2_PORT))
+			return port;
+	}
+	/* Fall back to any DP port */
+	for (i = 0; i < data->port_count; i++) {
+		chamelium_v3_port_id port = data->ports[i];
+
+		if (chamelium_v3_port_is_dp(data->chamelium, port))
+			return port;
+	}
+	return (chamelium_v3_port_id)-1;
+}
+
+/* Find an HDMI port */
+static chamelium_v3_port_id find_hdmi_port(struct edid_test_data *data)
+{
+	int i;
+
+	for (i = 0; i < data->port_count; i++) {
+		chamelium_v3_port_id port = data->ports[i];
+
+		if (chamelium_v3_port_is_hdmi(data->chamelium, port))
+			return port;
+	}
+	return (chamelium_v3_port_id)-1;
+}
+
+/*
+ * Find a connected DRM connector by type.
+ * Uses drmModeGetConnector() which forces a reprobe.
+ * Caller must free with drmModeFreeConnector().
+ */
+static drmModeConnector *find_connected_connector(int drm_fd, uint32_t connector_type)
+{
+	drmModeRes *res;
+	drmModeConnector *connector;
+	int i;
+
+	res = drmModeGetResources(drm_fd);
+	igt_assert(res);
+
+	for (i = 0; i < res->count_connectors; i++) {
+		connector = drmModeGetConnector(drm_fd, res->connectors[i]);
+		if (!connector)
+			continue;
+
+		if (connector->connector_type == connector_type &&
+		    connector->connection == DRM_MODE_CONNECTED) {
+			drmModeFreeResources(res);
+			return connector;
+		}
+		drmModeFreeConnector(connector);
+	}
+
+	drmModeFreeResources(res);
+	return NULL;
+}
+
+/*
+ * Wait for a connector of the given type to become connected.
+ * Retries with reprobe to handle HPD propagation delay.
+ * Returns the connected connector, or NULL if timeout.
+ */
+static drmModeConnector *wait_for_connected_connector(int drm_fd, uint32_t connector_type)
+{
+	drmModeConnector *connector;
+	int attempt;
+
+	for (attempt = 0; attempt < CONNECTOR_REPROBE_RETRIES; attempt++) {
+		usleep(HPD_WAIT_TIME_MS * 1000 / 2);
+		connector = find_connected_connector(drm_fd, connector_type);
+		if (connector)
+			return connector;
+	}
+	return NULL;
+}
+
+/*
+ * Read the EDID blob from a DRM connector.
+ * Returns allocated buffer with EDID data, or NULL if not found.
+ * Caller must free the returned buffer.
+ */
+static unsigned char *read_edid_from_connector(int drm_fd, drmModeConnector *connector,
+					       int *edid_len)
+{
+	int i;
+	drmModePropertyPtr prop;
+	drmModePropertyBlobPtr blob;
+	unsigned char *edid_data;
+
+	for (i = 0; i < connector->count_props; i++) {
+		prop = drmModeGetProperty(drm_fd, connector->props[i]);
+		if (!prop)
+			continue;
+
+		if (strcmp(prop->name, "EDID") == 0 &&
+		    connector->prop_values[i] != 0) {
+			blob = drmModeGetPropertyBlob(drm_fd,
+						      connector->prop_values[i]);
+			if (blob && blob->length > 0) {
+				edid_data = malloc(blob->length);
+				igt_assert(edid_data);
+				memcpy(edid_data, blob->data, blob->length);
+				*edid_len = blob->length;
+				drmModeFreePropertyBlob(blob);
+				drmModeFreeProperty(prop);
+				return edid_data;
+			}
+			if (blob)
+				drmModeFreePropertyBlob(blob);
+		}
+		drmModeFreeProperty(prop);
+	}
+
+	*edid_len = 0;
+	return NULL;
+}
+
+/* Verify EDID header magic bytes: 00 FF FF FF FF FF FF 00 */
+static bool verify_edid_header(const unsigned char *edid, int len)
+{
+	static const unsigned char edid_header[] = {
+		0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
+	};
+
+	if (len < EDID_BLOCK_SIZE)
+		return false;
+
+	return memcmp(edid, edid_header, sizeof(edid_header)) == 0;
+}
+
+/* Get connector type constant for DRM */
+static uint32_t get_drm_connector_type(bool is_dp)
+{
+	return is_dp ? DRM_MODE_CONNECTOR_DisplayPort : DRM_MODE_CONNECTOR_HDMIA;
+}
+
+/* Get port for given type */
+static chamelium_v3_port_id get_port_for_type(struct edid_test_data *data, bool is_dp)
+{
+	return is_dp ? find_dp_port(data) : find_hdmi_port(data);
+}
+
+/*
+ * Find a CRTC that can drive the given connector.
+ * Needed for DRM modesetting in mode-timings and resolution tests.
+ */
+static uint32_t find_crtc_for_connector(int drm_fd, drmModeConnector *conn)
+{
+	drmModeRes *res;
+	drmModeEncoder *encoder;
+	uint32_t crtc_id = 0;
+	int i, j;
+
+	res = drmModeGetResources(drm_fd);
+	igt_assert(res);
+
+	for (i = 0; i < conn->count_encoders && !crtc_id; i++) {
+		encoder = drmModeGetEncoder(drm_fd, conn->encoders[i]);
+		if (!encoder)
+			continue;
+
+		for (j = 0; j < res->count_crtcs && !crtc_id; j++) {
+			if (encoder->possible_crtcs & (1 << j))
+				crtc_id = res->crtcs[j];
+		}
+		drmModeFreeEncoder(encoder);
+	}
+
+	drmModeFreeResources(res);
+	return crtc_id;
+}
+
+/* Disable all CRTCs to get a clean slate for modesetting */
+static void disable_all_crtcs(int drm_fd)
+{
+	drmModeRes *res;
+	int i;
+
+	res = drmModeGetResources(drm_fd);
+	igt_assert(res);
+
+	for (i = 0; i < res->count_crtcs; i++)
+		drmModeSetCrtc(drm_fd, res->crtcs[i], 0, 0, 0, NULL, 0, NULL);
+
+	drmModeFreeResources(res);
+}
+
+/*
+ * Ensure a specific Chamelium port is plugged with default EDID.
+ *
+ * Some ports share muxes (e.g., FPGA DP1 and DP2 are mutually exclusive),
+ * so we only plug the target port — not all ports.
+ *
+ * This "primes" the DRM connector state so that subsequent per-port
+ * unplug/replug cycles are fast, especially for FPGA DP ports which
+ * need a long time to recover from a full chamelium_v3_reset().
+ */
+static void ensure_port_plugged(struct edid_test_data *data,
+				chamelium_v3_port_id port)
+{
+	disable_all_crtcs(data->drm_fd);
+
+	chamelium_v3_apply_edid(data->chamelium, port, 0);
+	if (!chamelium_v3_is_plugged(data->chamelium, port))
+		chamelium_v3_plug(data->chamelium, port);
+
+	/* Give port time to complete link training */
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	usleep(EDID_SETTLE_TIME_MS * 1000);
+
+	igt_debug("Port %d plugged with default EDID\n", port);
+}
+
+/*
+ * Wait for video to become stable on a Chamelium port.
+ * Polls GetVideoParams until valid resolution is reported or timeout.
+ */
+static bool wait_for_video_stable(struct igt_chamelium_v3 *chamelium,
+				  chamelium_v3_port_id port,
+				  int timeout_secs)
+{
+	struct chamelium_v3_video_params params;
+	struct timespec start, now;
+
+	clock_gettime(CLOCK_MONOTONIC, &start);
+
+	while (1) {
+		memset(&params, 0, sizeof(params));
+		if (chamelium_v3_get_video_params(chamelium, port, &params) &&
+		    params.hactive > 0 && params.vactive > 0)
+			return true;
+
+		clock_gettime(CLOCK_MONOTONIC, &now);
+		if (now.tv_sec - start.tv_sec >= timeout_secs)
+			break;
+
+		usleep(250000); /* 250ms poll interval */
+	}
+
+	return false;
+}
+
+/*
+ * Wait for video to report a specific expected resolution.
+ *
+ * After a mode change, Chamelium (especially ITE HDMI ports) may
+ * temporarily report stale video params from the previous mode.
+ * This function polls until the EXPECTED resolution is seen, not
+ * just any non-zero resolution.
+ */
+static bool wait_for_video_resolution(struct igt_chamelium_v3 *chamelium,
+				      chamelium_v3_port_id port,
+				      int expected_w, int expected_h,
+				      int timeout_secs)
+{
+	struct chamelium_v3_video_params params;
+	struct timespec start, now;
+
+	clock_gettime(CLOCK_MONOTONIC, &start);
+
+	while (1) {
+		memset(&params, 0, sizeof(params));
+		if (chamelium_v3_get_video_params(chamelium, port, &params) &&
+		    params.hactive == expected_w &&
+		    params.vactive == expected_h)
+			return true;
+
+		clock_gettime(CLOCK_MONOTONIC, &now);
+		if (now.tv_sec - start.tv_sec >= timeout_secs)
+			break;
+
+		igt_debug("  Waiting for %dx%d, currently %dx%d...\n",
+			  expected_w, expected_h,
+			  params.hactive, params.vactive);
+		usleep(500000); /* 500ms poll for mode transition */
+	}
+
+	igt_warn("Timed out waiting for resolution %dx%d (last seen: %dx%d)\n",
+		 expected_w, expected_h,
+		 params.hactive, params.vactive);
+	return false;
+}
+
+/*
+ * check_mode_v3 - Verify mode timing parameters.
+ *
+ * Retrieves video parameters from Chamelium V3 GetVideoParams RPC
+ * and compares them against the DRM mode info. Uses the same 5% clock
+ * tolerance and exact matching for all timing parameters.
+ *
+ */
+static void check_mode_v3(struct igt_chamelium_v3 *chamelium,
+			  chamelium_v3_port_id port, bool is_dp,
+			  drmModeModeInfo *mode)
+{
+	struct chamelium_v3_video_params params = { 0 };
+	double mode_clock;
+	int expected_hfp, expected_vfp;
+	int expected_hsw, expected_vsw;
+	bool expected_hsp, expected_vsp;
+	bool ok;
+
+	ok = chamelium_v3_get_video_params(chamelium, port, &params);
+	igt_assert_f(ok, "Failed to get video params from Chamelium\n");
+
+	mode_clock = (double)mode->clock / 1000;
+
+	/* Standard timing derivation from DRM mode info */
+	expected_hfp = mode->hsync_start - mode->hdisplay;
+	expected_vfp = mode->vsync_start - mode->vdisplay;
+	expected_hsw = mode->hsync_end - mode->hsync_start;
+	expected_vsw = mode->vsync_end - mode->vsync_start;
+	expected_hsp = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
+	expected_vsp = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
+
+	igt_debug("Checking video mode (%s):\n", is_dp ? "DP" : "HDMI");
+	igt_debug("  clock: got %.2f, expected %.2f ± %.1f%%\n",
+		  params.clock, mode_clock, MODE_CLOCK_ACCURACY * 100);
+	igt_debug("  hactive: got %d, expected %d\n",
+		  params.hactive, mode->hdisplay);
+	igt_debug("  vactive: got %d, expected %d\n",
+		  params.vactive, mode->vdisplay);
+	igt_debug("  htotal: got %d, expected %d\n",
+		  params.htotal, mode->htotal);
+	igt_debug("  vtotal: got %d, expected %d\n",
+		  params.vtotal, mode->vtotal);
+	igt_debug("  hfront_porch: got %d, expected %d\n",
+		  params.hfront_porch, expected_hfp);
+	igt_debug("  vfront_porch: got %d, expected %d\n",
+		  params.vfront_porch, expected_vfp);
+	igt_debug("  hsync_width: got %d, expected %d\n",
+		  params.hsync_width, expected_hsw);
+	igt_debug("  vsync_width: got %d, expected %d\n",
+		  params.vsync_width, expected_vsw);
+	igt_debug("  hsync_polarity: got %d, expected %d\n",
+		  params.hsync_polarity, expected_hsp);
+	igt_debug("  vsync_polarity: got %d, expected %d\n",
+		  params.vsync_polarity, expected_vsp);
+
+	/* Clock check with tolerance */
+	if (params.clock > 0) {
+		igt_assert_f(params.clock >
+			     mode_clock * (1 - MODE_CLOCK_ACCURACY),
+			     "Clock too low: %.2f < %.2f\n",
+			     params.clock,
+			     mode_clock * (1 - MODE_CLOCK_ACCURACY));
+		igt_assert_f(params.clock <
+			     mode_clock * (1 + MODE_CLOCK_ACCURACY),
+			     "Clock too high: %.2f > %.2f\n",
+			     params.clock,
+			     mode_clock * (1 + MODE_CLOCK_ACCURACY));
+	}
+
+	/* Resolution must match exactly */
+	igt_assert_eq(params.hactive, mode->hdisplay);
+	igt_assert_eq(params.vactive, mode->vdisplay);
+
+	/* Totals must match */
+	igt_assert_eq(params.htotal, mode->htotal);
+	igt_assert_eq(params.vtotal, mode->vtotal);
+
+	/* Sync widths must match */
+	igt_assert_eq(params.hsync_width, expected_hsw);
+	igt_assert_eq(params.vsync_width, expected_vsw);
+
+	/* Front porch must match (V3 reports standard front porch) */
+	igt_assert_eq(params.hfront_porch, expected_hfp);
+	igt_assert_eq(params.vfront_porch, expected_vfp);
+
+	/* Sync polarity must match */
+	igt_assert_eq(params.hsync_polarity, expected_hsp);
+	igt_assert_eq(params.vsync_polarity, expected_vsp);
+}
+
+/*
+ * Perform a modeset on a connector with the given mode.
+ * Returns the CRTC ID used, or 0 on failure.
+ * Caller must clean up the fb with igt_remove_fb().
+ */
+static uint32_t set_mode_on_connector(int drm_fd, drmModeConnector *conn,
+				      drmModeModeInfo *mode, struct igt_fb *fb)
+{
+	uint32_t crtc_id;
+	int fb_id, ret;
+
+	crtc_id = find_crtc_for_connector(drm_fd, conn);
+	if (!crtc_id) {
+		igt_warn("No CRTC found for connector %d\n",
+			 conn->connector_id);
+		return 0;
+	}
+
+	fb_id = igt_create_color_pattern_fb(drm_fd,
+					    mode->hdisplay, mode->vdisplay,
+					    DRM_FORMAT_XRGB8888,
+					    DRM_FORMAT_MOD_LINEAR,
+					    0, 0, 0, fb);
+	igt_assert_f(fb_id > 0, "Failed to create FB %dx%d\n",
+		     mode->hdisplay, mode->vdisplay);
+
+	ret = drmModeSetCrtc(drm_fd, crtc_id, fb_id, 0, 0,
+			     &conn->connector_id, 1, mode);
+	if (ret) {
+		igt_warn("drmModeSetCrtc failed: %d\n", ret);
+		igt_remove_fb(drm_fd, fb);
+		return 0;
+	}
+
+	return crtc_id;
+}
+
+/**
+ * SUBTEST: dp-edid-read
+ * Description: Make sure the EDID exposed by KMS matches the uploaded EDID
+ *              (tests both base and alt EDIDs, strict byte comparison)
+ *
+ * SUBTEST: hdmi-edid-read
+ * Description: Make sure the EDID exposed by KMS matches the uploaded EDID
+ *              (tests both base and alt EDIDs, strict byte comparison)
+ */
+
+/*
+ * edid_read_one - Upload a single EDID, read back from DRM, assert exact match.
+ *
+ * Ported from V2 igt_custom_edid_type_read():
+ * - Uploads EDID via CreateEdid + ApplyEdid
+ * - Plugs port, waits for connection
+ * - Reads EDID blob from DRM connector property
+ * - Asserts exact byte-for-byte match (igt_assert, not just igt_info)
+ */
+static void edid_read_one(struct edid_test_data *data, bool is_dp,
+			  const struct edid *raw_edid, const char *edid_name)
+{
+	chamelium_v3_port_id port;
+	size_t edid_size;
+	int edid_id;
+	drmModeConnector *connector;
+	unsigned char *read_edid;
+	int read_len;
+
+	port = get_port_for_type(data, is_dp);
+	igt_require_f(port != (chamelium_v3_port_id)-1,
+		      "No %s port found on Chamelium\n", is_dp ? "DP" : "HDMI");
+
+	/* Ensure target port is plugged first (primes DRM connector state) */
+	ensure_port_plugged(data, port);
+
+	/* Get EDID size and upload to Chamelium */
+	edid_size = edid_get_size(raw_edid);
+	igt_info("Testing %s EDID read (%s, %zu bytes)\n",
+		 edid_name, is_dp ? "DP" : "HDMI", edid_size);
+
+	edid_id = chamelium_v3_create_edid(data->chamelium,
+					   (const unsigned char *)raw_edid,
+					   edid_size);
+	igt_info("  Created EDID with ID %d\n", edid_id);
+
+	/* Unplug target port, apply custom EDID, then re-plug */
+	chamelium_v3_unplug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+
+	chamelium_v3_apply_edid(data->chamelium, port, edid_id);
+	chamelium_v3_plug(data->chamelium, port);
+
+	/* Wait for connector to reappear */
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	igt_assert_f(chamelium_v3_is_plugged(data->chamelium, port),
+		     "Port %d should be plugged\n", port);
+
+	usleep(EDID_SETTLE_TIME_MS * 1000);
+	connector = wait_for_connected_connector(data->drm_fd,
+						 get_drm_connector_type(is_dp));
+	if (!connector) {
+		igt_info("  Connector not found, retrying after extra wait...\n");
+		usleep(HPD_WAIT_TIME_MS * 1000);
+		connector = wait_for_connected_connector(data->drm_fd,
+							 get_drm_connector_type(is_dp));
+	}
+	igt_require_f(connector, "No connected %s connector found\n",
+		      is_dp ? "DP" : "HDMI");
+
+	igt_info("  Found connector: %s-%d\n",
+		 kmstest_connector_type_str(connector->connector_type),
+		 connector->connector_type_id);
+
+	/* Read EDID blob from DRM connector */
+	read_edid = read_edid_from_connector(data->drm_fd, connector, &read_len);
+	igt_assert_f(read_edid, "Failed to read EDID from DRM connector\n");
+	igt_assert_f(read_len >= (int)edid_size,
+		     "Read EDID too short: %d bytes (uploaded %zu)\n",
+		     read_len, edid_size);
+
+	/* Verify EDID header */
+	igt_assert_f(verify_edid_header(read_edid, read_len),
+		     "Invalid EDID header in read-back data\n");
+
+	/*
+	 * STRICT byte-for-byte comparison.
+	 */
+	igt_assert_f(memcmp(read_edid, raw_edid, edid_size) == 0,
+		     "%s EDID mismatch: uploaded %zu bytes != read %d bytes\n",
+		     edid_name, edid_size, read_len);
+	igt_info("  %s EDID matches exactly (%zu bytes)\n", edid_name, edid_size);
+
+	/* Verify connector has modes */
+	igt_assert_f(connector->count_modes > 0,
+		     "Connector has no modes after EDID apply\n");
+	igt_info("  Connector reports %d modes\n", connector->count_modes);
+
+	/* Cleanup */
+	free(read_edid);
+	drmModeFreeConnector(connector);
+	chamelium_v3_unplug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+}
+
+/*
+ * test_edid_read - Test both BASE and ALT EDIDs.
+ */
+static void test_edid_read(struct edid_test_data *data, bool is_dp)
+{
+	/* Test base EDID (1920x1080) - same */
+	edid_read_one(data, is_dp, igt_kms_get_base_edid(), "BASE");
+
+	/* Test alt EDID (1400x1050) - same */
+	edid_read_one(data, is_dp, igt_kms_get_alt_edid(), "ALT");
+
+	igt_info("  EDID read test PASSED (both BASE and ALT)\n");
+}
+
+/**
+ * SUBTEST: dp-mode-timings
+ * Description: For each mode of the IGT base EDID, perform a modeset and check
+ *              the mode detected by the Chamelium receiver matches the mode we
+ *              set
+ *
+ * SUBTEST: hdmi-mode-timings
+ * Description: For each mode of the IGT base EDID, perform a modeset and check
+ *              the mode detected by the Chamelium receiver matches the mode we
+ *              set
+ */
+static void test_mode_timings(struct edid_test_data *data, bool is_dp)
+{
+	chamelium_v3_port_id port;
+	const struct edid *base_edid;
+	size_t edid_size;
+	int edid_id;
+	drmModeConnector *connector;
+	int i, count_modes;
+	char *port_name;
+
+	port = get_port_for_type(data, is_dp);
+	igt_require_f(port != (chamelium_v3_port_id)-1,
+		      "No %s port found\n", is_dp ? "DP" : "HDMI");
+
+	port_name = chamelium_v3_get_port_name(data->chamelium, port);
+	igt_info("Testing mode timings on %s (port %d)\n", port_name, port);
+
+	/* Prevent fbcon from interfering */
+	kmstest_set_vt_graphics_mode();
+
+	/*
+	 * Reset Chamelium and disable CRTCs for a clean start.
+	 * Reset state before test.
+	 */
+	chamelium_v3_reset(data->chamelium);
+	disable_all_crtcs(data->drm_fd);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+
+	/* Upload base EDID and plug */
+	base_edid = igt_kms_get_base_edid();
+	edid_size = edid_get_size(base_edid);
+
+	edid_id = chamelium_v3_create_edid(data->chamelium,
+					   (const unsigned char *)base_edid,
+					   edid_size);
+
+	chamelium_v3_apply_edid(data->chamelium, port, edid_id);
+	chamelium_v3_plug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	usleep(EDID_SETTLE_TIME_MS * 1000);
+
+	connector = wait_for_connected_connector(data->drm_fd,
+						 get_drm_connector_type(is_dp));
+	igt_require_f(connector, "No connected %s connector found\n",
+		      is_dp ? "DP" : "HDMI");
+
+	count_modes = connector->count_modes;
+	igt_assert_f(count_modes > 0, "Connector has no modes\n");
+	igt_info("  Found %d modes to test\n", count_modes);
+
+	/* List all modes for debug */
+	for (i = 0; i < count_modes; i++)
+		igt_debug("  #%d %s %uHz\n", i,
+			  connector->modes[i].name,
+			  connector->modes[i].vrefresh);
+
+	/*
+	 * For each mode: modeset, wait for video stable, check all
+	 * timing parameters. Validate timing parameters.
+	 */
+	for (i = 0; i < count_modes; i++) {
+		drmModeModeInfo *mode = &connector->modes[i];
+		struct igt_fb fb = { 0 };
+		uint32_t crtc_id;
+
+		igt_info("  Testing #%d %s %uHz (%dx%d)\n",
+			 i, mode->name, mode->vrefresh,
+			 mode->hdisplay, mode->vdisplay);
+
+		disable_all_crtcs(data->drm_fd);
+
+		crtc_id = set_mode_on_connector(data->drm_fd, connector,
+						mode, &fb);
+		igt_assert_f(crtc_id,
+			     "Failed to modeset %dx%d@%d\n",
+			     mode->hdisplay, mode->vdisplay,
+			     mode->vrefresh);
+
+		/* Wait for video to stabilize */
+		usleep(MODESET_SETTLE_TIME_MS * 1000);
+		igt_assert_f(
+			wait_for_video_stable(data->chamelium, port,
+					      VIDEO_STABLE_TIMEOUT_S),
+			"Video not stable for mode #%d\n", i);
+
+		/* Full timing validation */
+		check_mode_v3(data->chamelium, port, is_dp, mode);
+		igt_info("    Mode #%d timing check PASSED\n", i);
+
+		/* Cleanup FB for this mode */
+		drmModeSetCrtc(data->drm_fd, crtc_id, 0, 0, 0,
+			       NULL, 0, NULL);
+		igt_remove_fb(data->drm_fd, &fb);
+	}
+
+	drmModeFreeConnector(connector);
+	chamelium_v3_unplug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	free(port_name);
+
+	igt_info("  Mode timings test PASSED (%d modes verified)\n",
+		 count_modes);
+}
+
+/**
+ * SUBTEST: dp-edid-resolution-list
+ * Description: Set full EDID with many modes, iterate each mode,
+ *              perform modeset and verify screen resolution matches
+ *             
+ */
+static void test_edid_resolution_list(struct edid_test_data *data)
+{
+	chamelium_v3_port_id port;
+	const struct edid *full_edid;
+	size_t edid_size;
+	int edid_id;
+	drmModeConnector *connector;
+	int i, count_modes;
+	char *port_name;
+
+	port = find_dp_port(data);
+	igt_require_f(port != (chamelium_v3_port_id)-1,
+		      "No DP port found on Chamelium\n");
+
+	port_name = chamelium_v3_get_port_name(data->chamelium, port);
+	igt_info("Testing EDID resolution list on %s (port %d)\n",
+		 port_name, port);
+
+	/* Prevent fbcon from interfering */
+	kmstest_set_vt_graphics_mode();
+
+	/* Reset Chamelium and disable CRTCs for clean start */
+	chamelium_v3_reset(data->chamelium);
+	disable_all_crtcs(data->drm_fd);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+
+	/* Upload full EDID (many modes) */
+	full_edid = igt_kms_get_custom_edid(IGT_CUSTOM_EDID_FULL);
+	edid_size = edid_get_size(full_edid);
+
+	edid_id = chamelium_v3_create_edid(data->chamelium,
+					   (const unsigned char *)full_edid,
+					   edid_size);
+
+	chamelium_v3_apply_edid(data->chamelium, port, edid_id);
+	chamelium_v3_plug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	usleep(EDID_SETTLE_TIME_MS * 1000);
+
+	connector = wait_for_connected_connector(data->drm_fd,
+						 DRM_MODE_CONNECTOR_DisplayPort);
+	igt_require_f(connector, "No connected DP connector found\n");
+
+	count_modes = connector->count_modes;
+	igt_assert_f(count_modes > 0, "Full EDID should have many modes\n");
+	igt_info("  Found %d modes from full EDID\n", count_modes);
+
+	for (i = 0; i < count_modes; i++)
+		igt_debug("  #%d %s %uHz\n", i,
+			  connector->modes[i].name,
+			  connector->modes[i].vrefresh);
+
+	/*
+	 * For each mode, set it and verify the screen
+	 * resolution via Chamelium matches the mode we set.
+	 */
+	for (i = 0; i < count_modes; i++) {
+		drmModeModeInfo *mode = &connector->modes[i];
+		struct igt_fb fb = { 0 };
+		struct chamelium_v3_video_params params = { 0 };
+		uint32_t crtc_id;
+
+		igt_info("  Testing #%d %s %uHz\n",
+			 i, mode->name, mode->vrefresh);
+
+		disable_all_crtcs(data->drm_fd);
+
+		crtc_id = set_mode_on_connector(data->drm_fd, connector,
+						mode, &fb);
+		if (!crtc_id) {
+			igt_info("    Skipped (no CRTC)\n");
+			continue;
+		}
+
+		usleep(MODESET_SETTLE_TIME_MS * 1000);
+		igt_assert_f(
+			wait_for_video_stable(data->chamelium, port,
+					      VIDEO_STABLE_TIMEOUT_S),
+			"Video not stable for mode #%d %s\n", i, mode->name);
+
+		chamelium_v3_get_video_params(data->chamelium, port, &params);
+
+		/* Screen resolution must match mode */
+		igt_assert_eq(params.hactive, mode->hdisplay);
+		igt_assert_eq(params.vactive, mode->vdisplay);
+		igt_info("    Resolution verified: %dx%d\n",
+			 params.hactive, params.vactive);
+
+		drmModeSetCrtc(data->drm_fd, crtc_id, 0, 0, 0,
+			       NULL, 0, NULL);
+		igt_remove_fb(data->drm_fd, &fb);
+	}
+
+	drmModeFreeConnector(connector);
+	chamelium_v3_unplug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	free(port_name);
+
+	igt_info("  EDID resolution list test PASSED (%d modes)\n",
+		 count_modes);
+}
+
+/*
+ * Convert a hex string EDID (from monitor_edid) to raw bytes.
+ * Caller must free returned buffer.
+ */
+static uint8_t *hex_str_to_edid_bytes(const char *hex_str, size_t *out_size)
+{
+	size_t len = strlen(hex_str);
+	size_t byte_len = len / 2;
+	uint8_t *bytes;
+	size_t i;
+
+	igt_assert_f(len % 2 == 0, "EDID hex string has odd length\n");
+
+	bytes = malloc(byte_len);
+	igt_assert(bytes);
+
+	for (i = 0; i < byte_len; i++) {
+		char hi = hex_str[i * 2];
+		char lo = hex_str[i * 2 + 1];
+		uint8_t b = 0;
+
+		if (hi >= '0' && hi <= '9') b = (hi - '0') << 4;
+		else if (hi >= 'A' && hi <= 'F') b = (hi - 'A' + 10) << 4;
+		else if (hi >= 'a' && hi <= 'f') b = (hi - 'a' + 10) << 4;
+
+		if (lo >= '0' && lo <= '9') b |= (lo - '0');
+		else if (lo >= 'A' && lo <= 'F') b |= (lo - 'A' + 10);
+		else if (lo >= 'a' && lo <= 'f') b |= (lo - 'a' + 10);
+
+		bytes[i] = b;
+	}
+
+	*out_size = byte_len;
+	return bytes;
+}
+
+/*
+ * edid_stress_resolution_v3 - Stress test multiple EDIDs.
+ *
+ * Iterates over a list of real monitor EDIDs (4K or non-4K), uploads each
+ * to Chamelium, plugs the port, performs a modeset with the preferred mode,
+ * and verifies the screen resolution matches via GetVideoParams.
+ */
+static void edid_stress_resolution_v3(struct edid_test_data *data,
+				      bool is_dp, bool is_4k)
+{
+	chamelium_v3_port_id port;
+	monitor_edid *edid_list;
+	size_t edid_count;
+	int i;
+	char *port_name;
+
+	port = get_port_for_type(data, is_dp);
+	igt_require_f(port != (chamelium_v3_port_id)-1,
+		      "No %s port found\n", is_dp ? "DP" : "HDMI");
+
+	port_name = chamelium_v3_get_port_name(data->chamelium, port);
+
+	/* Get the appropriate EDID list */
+	if (is_dp) {
+		if (is_4k) {
+			edid_list = DP_EDIDS_4K;
+			edid_count = DP_EDIDS_4K_COUNT;
+		} else {
+			edid_list = DP_EDIDS_NON_4K;
+			edid_count = DP_EDIDS_NON_4K_COUNT;
+		}
+	} else {
+		if (is_4k) {
+			edid_list = HDMI_EDIDS_4K;
+			edid_count = HDMI_EDIDS_4K_COUNT;
+		} else {
+			edid_list = HDMI_EDIDS_NON_4K;
+			edid_count = HDMI_EDIDS_NON_4K_COUNT;
+		}
+	}
+
+	igt_info("Stress testing %s %s EDIDs on %s (port %d, %zu EDIDs)\n",
+		 is_4k ? "4K" : "non-4K", is_dp ? "DP" : "HDMI",
+		 port_name, port, edid_count);
+
+	/* Prevent fbcon from interfering */
+	kmstest_set_vt_graphics_mode();
+
+	/* Reset for clean start */
+	chamelium_v3_reset(data->chamelium);
+	disable_all_crtcs(data->drm_fd);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+
+	for (i = 0; i < (int)edid_count; i++) {
+		uint8_t *edid_bytes;
+		size_t edid_size;
+		int edid_id;
+		drmModeConnector *connector;
+		struct igt_fb fb = { 0 };
+		uint32_t crtc_id;
+		int exp_w, exp_h;
+
+		igt_info("  [%d/%zu] Testing EDID: %s\n",
+			 i + 1, edid_count, edid_list[i].name);
+
+		/* Convert hex EDID to bytes */
+		edid_bytes = hex_str_to_edid_bytes(edid_list[i].edid, &edid_size);
+
+		/* Upload and apply EDID */
+		edid_id = chamelium_v3_create_edid(data->chamelium,
+						   edid_bytes, edid_size);
+		chamelium_v3_apply_edid(data->chamelium, port, edid_id);
+
+		chamelium_v3_plug(data->chamelium, port);
+		usleep(HPD_WAIT_TIME_MS * 1000);
+		usleep(EDID_SETTLE_TIME_MS * 1000);
+
+		connector = wait_for_connected_connector(data->drm_fd,
+							 get_drm_connector_type(is_dp));
+		if (!connector) {
+			igt_info("    Connector not found, retrying...\n");
+			usleep(HPD_WAIT_TIME_MS * 1000);
+			connector = wait_for_connected_connector(data->drm_fd,
+								 get_drm_connector_type(is_dp));
+		}
+		igt_assert_f(connector,
+			     "No connector for EDID %s\n", edid_list[i].name);
+		igt_assert_f(connector->count_modes > 0,
+			     "No modes for EDID %s\n", edid_list[i].name);
+
+		igt_info("    %d modes, preferred %dx%d@%d\n",
+			 connector->count_modes,
+			 connector->modes[0].hdisplay,
+			 connector->modes[0].vdisplay,
+			 connector->modes[0].vrefresh);
+
+		/* Modeset with preferred mode */
+		disable_all_crtcs(data->drm_fd);
+		crtc_id = set_mode_on_connector(data->drm_fd, connector,
+						&connector->modes[0], &fb);
+		igt_assert_f(crtc_id, "Failed to modeset for EDID %s\n",
+			     edid_list[i].name);
+
+		exp_w = connector->modes[0].hdisplay;
+		exp_h = connector->modes[0].vdisplay;
+
+		/* Verify resolution via Chamelium */
+		igt_assert_f(
+			wait_for_video_resolution(data->chamelium, port,
+						  exp_w, exp_h,
+						  VIDEO_STABLE_TIMEOUT_S + 5),
+			"Resolution %dx%d not seen for EDID %s\n",
+			exp_w, exp_h, edid_list[i].name);
+
+		igt_info("    Resolution verified: %dx%d\n", exp_w, exp_h);
+
+		/* Cleanup */
+		drmModeSetCrtc(data->drm_fd, crtc_id, 0, 0, 0,
+			       NULL, 0, NULL);
+		igt_remove_fb(data->drm_fd, &fb);
+		drmModeFreeConnector(connector);
+
+		disable_all_crtcs(data->drm_fd);
+		chamelium_v3_unplug(data->chamelium, port);
+		usleep(HPD_WAIT_TIME_MS * 1000);
+
+		free(edid_bytes);
+	}
+
+	free(port_name);
+	igt_info("  Stress resolution test PASSED (%zu EDIDs)\n", edid_count);
+}
+
+/*
+ * test_suspend_resume_edid_change_v3 - Suspend/resume EDID change test.
+ * test_suspend_resume_edid_change().
+ *
+ * Simulates a screen being unplugged and another screen being plugged
+ * during suspend/hibernate. Checks that the connector sees the new EDID
+ * after resume.
+ */
+static void test_suspend_resume_edid_change_v3(struct edid_test_data *data,
+					       bool is_dp,
+					       enum igt_suspend_state state,
+					       enum igt_suspend_test test)
+{
+	chamelium_v3_port_id port;
+	const struct edid *base_edid, *alt_edid;
+	size_t base_size, alt_size;
+	int base_edid_id, alt_edid_id;
+	drmModeConnector *connector;
+	struct udev_monitor *mon;
+	char *port_name;
+
+	port = get_port_for_type(data, is_dp);
+	igt_require_f(port != (chamelium_v3_port_id)-1,
+		      "No %s port found\n", is_dp ? "DP" : "HDMI");
+
+	port_name = chamelium_v3_get_port_name(data->chamelium, port);
+	igt_info("Testing EDID change during %s on %s (port %d)\n",
+		 state == SUSPEND_STATE_MEM ? "suspend" : "hibernate",
+		 port_name, port);
+
+	/* Reset for clean start */
+	chamelium_v3_reset(data->chamelium);
+	disable_all_crtcs(data->drm_fd);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+
+	/* Create both EDIDs */
+	base_edid = igt_kms_get_base_edid();
+	alt_edid = igt_kms_get_alt_edid();
+	base_size = edid_get_size(base_edid);
+	alt_size = edid_get_size(alt_edid);
+
+	base_edid_id = chamelium_v3_create_edid(data->chamelium,
+						(const unsigned char *)base_edid,
+						base_size);
+	alt_edid_id = chamelium_v3_create_edid(data->chamelium,
+					       (const unsigned char *)alt_edid,
+					       alt_size);
+
+	/* Start watching for uevents */
+	mon = igt_watch_uevents();
+	igt_flush_uevents(mon);
+
+	/* Plug with base EDID first */
+	chamelium_v3_apply_edid(data->chamelium, port, base_edid_id);
+	chamelium_v3_plug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	usleep(EDID_SETTLE_TIME_MS * 1000);
+
+	connector = wait_for_connected_connector(data->drm_fd,
+						 get_drm_connector_type(is_dp));
+	igt_assert_f(connector, "No connector after base EDID plug\n");
+	igt_info("  Base EDID: %d modes, preferred %dx%d\n",
+		 connector->count_modes,
+		 connector->modes[0].hdisplay,
+		 connector->modes[0].vdisplay);
+	drmModeFreeConnector(connector);
+
+	/* Change the EDID to alt (this sets up the new EDID for after resume) */
+	chamelium_v3_apply_edid(data->chamelium, port, alt_edid_id);
+	igt_info("  Applied alt EDID, now suspending...\n");
+
+	igt_flush_uevents(mon);
+
+	/* Suspend/hibernate and auto-resume */
+	igt_system_suspend_autoresume(state, test);
+
+	/* After resume, check for hotplug event.
+	 * S4 (hibernate) may not always generate a hotplug uevent on all
+	 * platforms, so treat it as non-fatal — the replug cycle below
+	 * will definitively verify the EDID change.
+	 */
+	if (igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT)) {
+		igt_info("  Hotplug detected after resume from %s\n",
+			 state == SUSPEND_STATE_MEM ? "suspend" : "hibernate");
+	} else {
+		igt_info("  No hotplug after resume from %s (expected for some S4 platforms)\n",
+			 state == SUSPEND_STATE_MEM ? "suspend" : "hibernate");
+	}
+
+	/* Verify connector is still connected after resume */
+	usleep(EDID_SETTLE_TIME_MS * 1000);
+	connector = wait_for_connected_connector(data->drm_fd,
+						 get_drm_connector_type(is_dp));
+	igt_assert_f(connector,
+		     "No connector after resume from %s\n",
+		     state == SUSPEND_STATE_MEM ? "suspend" : "hibernate");
+	igt_info("  Connector still connected after resume: %d modes, preferred %dx%d\n",
+		 connector->count_modes,
+		 connector->modes[0].hdisplay,
+		 connector->modes[0].vdisplay);
+	drmModeFreeConnector(connector);
+
+	/*
+	 * The kernel may not reprobe the EDID on an already-connected port
+	 * after resume. Do an unplug/replug cycle to force the connector
+	 * to re-read the EDID (alt EDID is still applied on the Chamelium).
+	 * This simulates: "screen A unplugged, screen B
+	 * plugged during suspend".
+	 */
+	igt_flush_uevents(mon);
+	chamelium_v3_unplug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	chamelium_v3_apply_edid(data->chamelium, port, alt_edid_id);
+	chamelium_v3_plug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	usleep(EDID_SETTLE_TIME_MS * 1000);
+
+	igt_assert_f(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT),
+		     "No hotplug event after replug with alt EDID\n");
+
+	connector = wait_for_connected_connector(data->drm_fd,
+						 get_drm_connector_type(is_dp));
+	igt_assert_f(connector,
+		     "No connector after replug with alt EDID\n");
+
+	igt_info("  After replug with alt EDID: %d modes, preferred %dx%d\n",
+		 connector->count_modes,
+		 connector->modes[0].hdisplay,
+		 connector->modes[0].vdisplay);
+
+	/* Alt EDID should give different preferred mode (1400x1050 vs 1920x1080) */
+	igt_assert_f(connector->modes[0].hdisplay == 1400 &&
+		     connector->modes[0].vdisplay == 1050,
+		     "Expected alt EDID preferred mode 1400x1050, got %dx%d\n",
+		     connector->modes[0].hdisplay,
+		     connector->modes[0].vdisplay);
+
+	drmModeFreeConnector(connector);
+
+	/* Cleanup */
+	chamelium_v3_unplug(data->chamelium, port);
+	usleep(HPD_WAIT_TIME_MS * 1000);
+	free(port_name);
+
+	igt_info("  EDID change during %s test PASSED\n",
+		 state == SUSPEND_STATE_MEM ? "suspend" : "hibernate");
+}
+
+static void __real_main(void)
+{
+	struct edid_test_data data = { 0 };
+	bool setup_done = false;
+
+	/* Setup */
+	data.chamelium = chamelium_v3_init_from_config();
+	if (!data.chamelium) {
+		igt_info("Chamelium not available, skipping tests\n");
+		return;
+	}
+
+	data.port_count = chamelium_v3_get_supported_ports(data.chamelium, &data.ports);
+	if (data.port_count <= 0) {
+		igt_info("No Chamelium ports found, skipping tests\n");
+		chamelium_v3_uninit(data.chamelium);
+		return;
+	}
+
+	/* Need master for modesetting in mode-timings and resolution tests */
+	data.drm_fd = drm_open_driver_master(DRIVER_ANY);
+	if (data.drm_fd < 0) {
+		igt_info("Cannot open DRM device as master, skipping tests\n");
+		free(data.ports);
+		chamelium_v3_uninit(data.chamelium);
+		return;
+	}
+
+	setup_done = true;
+	igt_info("Found %d Chamelium ports, DRM fd: %d\n",
+		 data.port_count, data.drm_fd);
+
+
+	igt_describe("Make sure the EDID exposed by KMS is the same as the screen's");
+	igt_subtest("dp-edid-read") {
+		igt_require(setup_done);
+		test_edid_read(&data, true);
+	}
+
+	igt_describe("Stress test the DUT by testing multiple 4K EDIDs, one "
+		     "right after the other, and ensure their validity");
+	igt_subtest("dp-edid-stress-resolution-4k") {
+		igt_require(setup_done);
+		edid_stress_resolution_v3(&data, true, true);
+	}
+
+	igt_describe("Stress test the DUT by testing multiple non-4K EDIDs, one "
+		     "right after the other, and ensure their validity");
+	igt_subtest("dp-edid-stress-resolution-non-4k") {
+		igt_require(setup_done);
+		edid_stress_resolution_v3(&data, true, false);
+	}
+
+	igt_describe("Get an EDID with many modes of different configurations, "
+		     "set them on the screen and check the screen resolution "
+		     "matches the mode resolution");
+	igt_subtest("dp-edid-resolution-list") {
+		igt_require(setup_done);
+		test_edid_resolution_list(&data);
+	}
+
+	igt_describe("Simulate a screen being unplugged and another screen "
+		     "being plugged during suspend");
+	igt_subtest("dp-edid-change-during-suspend") {
+		igt_require(setup_done);
+		test_suspend_resume_edid_change_v3(&data, true,
+						   SUSPEND_STATE_MEM,
+						   SUSPEND_TEST_NONE);
+	}
+
+	igt_describe("Simulate a screen being unplugged and another screen "
+		     "being plugged during hibernation");
+	igt_subtest("dp-edid-change-during-hibernate") {
+		igt_require(setup_done);
+		test_suspend_resume_edid_change_v3(&data, true,
+						   SUSPEND_STATE_DISK,
+						   SUSPEND_TEST_DEVICES);
+	}
+
+	igt_describe("For each mode of the IGT base EDID, perform a modeset "
+		     "and check the mode detected by Chamelium matches");
+	igt_subtest("dp-mode-timings") {
+		igt_require(setup_done);
+		test_mode_timings(&data, true);
+	}
+
+
+	igt_describe("Make sure the EDID exposed by KMS is the same as the screen's");
+	igt_subtest("hdmi-edid-read") {
+		igt_require(setup_done);
+		test_edid_read(&data, false);
+	}
+
+	igt_describe("Stress test the DUT by testing multiple 4K EDIDs, one "
+		     "right after the other, and ensure their validity");
+	igt_subtest("hdmi-edid-stress-resolution-4k") {
+		igt_require(setup_done);
+		edid_stress_resolution_v3(&data, false, true);
+	}
+
+	igt_describe("Stress test the DUT by testing multiple non-4K EDIDs, one "
+		     "right after the other, and ensure their validity");
+	igt_subtest("hdmi-edid-stress-resolution-non-4k") {
+		igt_require(setup_done);
+		edid_stress_resolution_v3(&data, false, false);
+	}
+
+	igt_describe("Simulate a screen being unplugged and another screen "
+		     "being plugged during suspend");
+	igt_subtest("hdmi-edid-change-during-suspend") {
+		igt_require(setup_done);
+		test_suspend_resume_edid_change_v3(&data, false,
+						   SUSPEND_STATE_MEM,
+						   SUSPEND_TEST_NONE);
+	}
+
+	igt_describe("Simulate a screen being unplugged and another screen "
+		     "being plugged during hibernation");
+	igt_subtest("hdmi-edid-change-during-hibernate") {
+		igt_require(setup_done);
+		test_suspend_resume_edid_change_v3(&data, false,
+						   SUSPEND_STATE_DISK,
+						   SUSPEND_TEST_DEVICES);
+	}
+
+	igt_describe("For each mode of the IGT base EDID, perform a modeset "
+		     "and check the mode detected by Chamelium matches");
+	igt_subtest("hdmi-mode-timings") {
+		igt_require(setup_done);
+		test_mode_timings(&data, false);
+	}
+
+	/* Cleanup: re-plug tested ports so connectors are not left disconnected */
+	if (setup_done) {
+		chamelium_v3_port_id dp_port, hdmi_port;
+
+		igt_info("Restoring port state...\n");
+
+		dp_port = find_dp_port(&data);
+		hdmi_port = find_hdmi_port(&data);
+
+		if (dp_port != (chamelium_v3_port_id)-1) {
+			chamelium_v3_apply_edid(data.chamelium, dp_port, 0);
+			chamelium_v3_plug(data.chamelium, dp_port);
+			igt_info("  Re-plugged DP port %d with default EDID\n",
+				 dp_port);
+		}
+		if (hdmi_port != (chamelium_v3_port_id)-1) {
+			chamelium_v3_apply_edid(data.chamelium, hdmi_port, 0);
+			chamelium_v3_plug(data.chamelium, hdmi_port);
+			igt_info("  Re-plugged HDMI port %d with default EDID\n",
+				 hdmi_port);
+		}
+
+		/* Give connectors time to come up */
+		usleep(HPD_WAIT_TIME_MS * 1000);
+
+		free(data.ports);
+		chamelium_v3_uninit(data.chamelium);
+		drm_close_driver(data.drm_fd);
+	}
+}
+
+IGT_TEST_DESCRIPTION("Testing EDID with a Chamelium V3 board");
+int main(int argc, char **argv)
+{
+	igt_subtest_init(argc, argv);
+	__real_main();
+	igt_exit();
+}
diff --git a/tests/meson.build b/tests/meson.build
index 31ca2d963..cfb59ecbb 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -375,6 +375,7 @@ chamelium_v2_progs = [
 chamelium_v3_progs = [
 	'kms_chamelium_v3_basic',
 	'kms_chamelium_v3_hpd',
+	'kms_chamelium_v3_edid',
 ]
 
 test_deps = [ igt_deps ]
-- 
2.48.1


  parent reply	other threads:[~2026-04-28  4:49 UTC|newest]

Thread overview: 37+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-28  4:46 [PATCH i-g-t v1 00/25] Chamelium v3 Integration and Test Execution Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 01/25] lib/igt_kms: Add a detect timeout value Mohammed Bilal
2026-04-28  7:11   ` Jani Nikula
2026-04-28  7:16   ` Jani Nikula
2026-04-28  7:17   ` Jani Nikula
2026-04-28  4:46 ` [PATCH i-g-t v1 02/25] lib/igt_kms: Add helper to wait for a specific status on a connector Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 03/25] lib/igt_kms: Add function to list connected connectors Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 04/25] lib/igt_kms: Add helper to obtain a connector by its name or MST path Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 05/25] lib/igt_kms: Add function to get valid pipe for specific output Mohammed Bilal
2026-04-28  7:21   ` Jani Nikula
2026-04-28  4:46 ` [PATCH i-g-t v1 06/25] lib/monitor_edids: Add helper functions for using monitor_edid objects Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 07/25] lib/monitor_edids: Add helper to get an EDID by its name Mohammed Bilal
2026-04-28  7:23   ` Jani Nikula
2026-04-28  4:46 ` [PATCH i-g-t v1 08/25] lib/monitor_edids: Add helper to print all available EDID names Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 09/25] lib/monitor_edids: Fix missing names in some monitor EDID Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 10/25] lib/monitor_edids: Add new EDID for HDMI 4k Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 11/25] tests/chamelium: Extract Chamelium v2 tests into a separate directory Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 12/25] lib/chamelium/v2: Extract chamelium v2 wrapper into its own directory Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 13/25] lib/chamelium/v2: Rename chamelium to chamelium_v2 Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 14/25] lib/chamelium/v2: Rename HAVE_CHAMELIUM to HAVE_CHAMELIUM_V2 Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 15/25] lib/chamelium/v3: Introduce the foundation for the Chamelium v3 wrapper Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 16/25] lib/chamelium/v3: Introduce initialization and cleanup of Chamelium-related structures Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 17/25] lib/chamelium/v3: Add method to discover Chamelium ports Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 18/25] lib/chamelium/v3: Implement method to retrieve Chamelium port names Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 19/25] tests/chamelium/v3: Implement a basic Chamelium v3 accessibility test Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 20/25] lib/chamelium/v3: Add extended API for Chamelium v3 HPD, EDID, Frames, Color & Audio Mohammed Bilal
2026-04-29 10:44   ` Louis Chauvet
2026-04-28  4:46 ` [PATCH i-g-t v1 21/25] tests/chamelium/v3: Add HPD (Hot Plug Detect) tests for Chamelium v3 Mohammed Bilal
2026-04-28  4:46 ` Mohammed Bilal [this message]
2026-04-28  4:46 ` [PATCH i-g-t v1 23/25] tests/chamelium/v3: Add frame capture and CRC " Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 24/25] tests/chamelium/v3: Add color verification " Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 25/25] tests/chamelium/v3: Add audio " Mohammed Bilal
2026-04-28  5:52 ` ✓ Xe.CI.BAT: success for Chamelium v3 Integration and Test Execution Patchwork
2026-04-28  6:09 ` ✗ i915.CI.BAT: failure " Patchwork
2026-04-28 12:36 ` ✗ Xe.CI.FULL: " Patchwork
2026-04-28 14:02 ` [PATCH i-g-t v1 00/25] " Louis Chauvet
2026-04-29  3:36   ` Bilal, Mohammed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260428044644.257001-23-mohammed.bilal@intel.com \
    --to=mohammed.bilal@intel.com \
    --cc=igt-dev@lists.freedesktop.org \
    --cc=kunal1.joshi@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox