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 23/25] tests/chamelium/v3: Add frame capture and CRC tests for Chamelium v3
Date: Tue, 28 Apr 2026 10:16:32 +0530	[thread overview]
Message-ID: <20260428044644.257001-24-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: 47325 bytes --]

This commit adds frame capture and CRC verification tests for Chamelium v3
including frame dump, pixel comparison, and display output verification.

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

diff --git a/tests/chamelium/v3/kms_chamelium_v3_frames.c b/tests/chamelium/v3/kms_chamelium_v3_frames.c
new file mode 100644
index 000000000..2132cf944
--- /dev/null
+++ b/tests/chamelium/v3/kms_chamelium_v3_frames.c
@@ -0,0 +1,1551 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ *
+ * Chamelium V3 Frame Capture / CRC Tests
+ *
+ * Tests frame capture, pixel-by-pixel comparison, format validation,
+ * and plane composition using the Chamelium DumpPixels API.
+ * Category: Display
+ * Description: Frame capture and comparison tests for Chamelium V3
+ * Driver requirement: i915, xe
+ * Mega feature: General Display Features
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "igt.h"
+#include "igt_kms.h"
+#include "igt_infoframe.h"
+#include "chamelium/v3/igt_chamelium.h"
+
+IGT_TEST_DESCRIPTION("Frame capture and comparison tests using Chamelium V3 DumpPixels API");
+
+#define HPD_WAIT_TIME_MS 3000
+
+/* Chamelium V3 port IDs */
+#define FPGA_DP1_PORT 4
+#define FPGA_DP2_PORT 5
+#define ITE_HDMI1_PORT 2
+#define ITE_HDMI2_PORT 3
+
+/*
+ * Tolerance for frame comparison.
+ * Digital connections (DP/HDMI) should be near-exact, but allow
+ * small differences for color quantization and rounding.
+ */
+#define FRAME_CRC_TOLERANCE 2
+#define FRAME_DUMP_TOLERANCE 0
+#define FRAME_CHECKERBOARD_TOLERANCE 10
+#define FRAME_FORMAT_TOLERANCE 10
+
+/* Maps Video Identification Codes to a mode */
+struct vic_mode {
+	int hactive, vactive;
+	int vrefresh; /* Hz */
+	uint32_t picture_ar;
+};
+
+static const struct vic_mode vic_modes[] = {
+	[16] = {
+		.hactive = 1920,
+		.vactive = 1080,
+		.vrefresh = 60,
+		.picture_ar = DRM_MODE_PICTURE_ASPECT_16_9,
+	},
+};
+
+struct frames_test_data {
+	/* Chamelium V3 */
+	struct igt_chamelium_v3 *chamelium;
+	chamelium_v3_port_id *ports;
+	int port_count;
+
+	/* DRM display */
+	int drm_fd;
+	igt_display_t display;
+};
+
+
+/* Find the first DP port, preferring FPGA ports */
+static chamelium_v3_port_id find_dp_port(struct frames_test_data *data)
+{
+	int i;
+
+	/* Prefer FPGA DP ports first */
+	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) &&
+		    chamelium_v3_is_plugged(data->chamelium, port) &&
+		    (port == FPGA_DP1_PORT || port == FPGA_DP2_PORT))
+			return port;
+	}
+	/* Fallback 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) &&
+		    chamelium_v3_is_plugged(data->chamelium, port))
+			return port;
+	}
+	return (chamelium_v3_port_id)-1;
+}
+
+/* Find the first HDMI port */
+static chamelium_v3_port_id find_hdmi_port(struct frames_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) &&
+		    chamelium_v3_is_plugged(data->chamelium, port))
+			return port;
+	}
+	return (chamelium_v3_port_id)-1;
+}
+
+
+/*
+ * Find a DRM output matching the Chamelium port type.
+ * For HDMI ports, look for HDMI-A connectors; for DP ports, look for DP connectors.
+ */
+static igt_output_t *find_output_for_port(struct frames_test_data *data,
+					  chamelium_v3_port_id port_id,
+					  bool is_hdmi)
+{
+	igt_output_t *output;
+	igt_crtc_t *crtc;
+
+	for_each_crtc(&data->display, crtc) {
+		for_each_valid_output_on_crtc(&data->display, crtc, output) {
+			uint32_t conn_type = output->config.connector->connector_type;
+			bool is_match = false;
+
+			if (is_hdmi)
+				is_match = (conn_type == DRM_MODE_CONNECTOR_HDMIA ||
+					    conn_type == DRM_MODE_CONNECTOR_HDMIB);
+			else
+				is_match = (conn_type == DRM_MODE_CONNECTOR_DisplayPort);
+
+			if (is_match)
+				return output;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Set up display: attach output to a pipe, get primary plane, set mode.
+ * Returns the primary plane, or NULL on failure.
+ */
+static igt_plane_t *setup_output(struct frames_test_data *data,
+				 igt_output_t *output,
+				 drmModeModeInfo *mode)
+{
+	igt_crtc_t *pipe_obj;
+	igt_plane_t *primary;
+	igt_crtc_t *crtc;
+
+	for_each_crtc(&data->display, crtc) {
+		if (igt_crtc_connector_valid(crtc, output)) {
+			pipe_obj = crtc;
+			primary = igt_crtc_get_plane_type(pipe_obj, DRM_PLANE_TYPE_PRIMARY);
+			if (primary) {
+				igt_output_set_crtc(output, pipe_obj);
+				igt_output_override_mode(output, mode);
+				return primary;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Enable output with the given framebuffer.
+ * Commits the modeset atomically and waits for signal stabilization.
+ */
+static void enable_output(struct frames_test_data *data,
+			  igt_output_t *output,
+			  igt_plane_t *primary,
+			  struct igt_fb *fb)
+{
+	igt_plane_set_fb(primary, fb);
+	igt_display_commit2(&data->display, COMMIT_ATOMIC);
+
+	/* Wait for the display signal to stabilize on the Chamelium */
+	sleep(5);
+}
+
+/*
+ * Disable output and clean up.
+ */
+static void disable_output(struct frames_test_data *data,
+			   igt_output_t *output,
+			   igt_plane_t *primary)
+{
+	igt_plane_set_fb(primary, NULL);
+	igt_output_set_crtc(output, NULL);
+	igt_display_commit2(&data->display, COMMIT_ATOMIC);
+}
+
+
+/*
+ * Compare a captured V3 frame (RGB, 3 bytes per pixel) against a local
+ * framebuffer (XRGB8888, 4 bytes per pixel in BGRA memory order).
+ *
+ * Returns true if frames match within tolerance.
+ */
+static bool compare_frame_to_fb(struct frames_test_data *data,
+				struct chamelium_v3_frame *frame,
+				struct igt_fb *fb,
+				int tolerance)
+{
+	cairo_t *cr;
+	cairo_surface_t *surface;
+	unsigned char *fb_data;
+	int stride;
+	int x, y;
+	int mismatches = 0;
+	int total_pixels = frame->width * frame->height;
+	/* Allow up to 2% mismatch for edge effects and format conversion */
+	int max_mismatches = total_pixels * 2 / 100;
+
+	cr = igt_get_cairo_ctx(data->drm_fd, fb);
+	surface = cairo_get_target(cr);
+	cairo_surface_flush(surface);
+	fb_data = cairo_image_surface_get_data(surface);
+	stride = cairo_image_surface_get_stride(surface);
+
+	igt_debug("Comparing frame %dx%d against framebuffer (stride=%d, tolerance=%d)...\n",
+		  frame->width, frame->height, stride, tolerance);
+
+	for (y = 0; y < frame->height && mismatches <= max_mismatches; y++) {
+		for (x = 0; x < frame->width && mismatches <= max_mismatches; x++) {
+			int frame_idx = (y * frame->width + x) * 3;
+			uint8_t cap_r = frame->data[frame_idx + 0];
+			uint8_t cap_g = frame->data[frame_idx + 1];
+			uint8_t cap_b = frame->data[frame_idx + 2];
+
+			int fb_idx = y * stride + x * 4;
+			uint8_t fb_b = fb_data[fb_idx + 0];
+			uint8_t fb_g = fb_data[fb_idx + 1];
+			uint8_t fb_r = fb_data[fb_idx + 2];
+
+			int diff_r = abs((int)cap_r - (int)fb_r);
+			int diff_g = abs((int)cap_g - (int)fb_g);
+			int diff_b = abs((int)cap_b - (int)fb_b);
+
+			if (diff_r > tolerance || diff_g > tolerance || diff_b > tolerance) {
+				mismatches++;
+				if (mismatches <= 10)
+					igt_debug("Mismatch at (%d,%d): captured RGB(%d,%d,%d) vs expected RGB(%d,%d,%d)\n",
+						  x, y, cap_r, cap_g, cap_b, fb_r, fb_g, fb_b);
+			}
+		}
+	}
+
+	igt_put_cairo_ctx(cr);
+
+	igt_info("Frame comparison: %d mismatches out of %d pixels (%.2f%%)\n",
+		 mismatches, total_pixels, 100.0 * mismatches / total_pixels);
+
+	return mismatches <= max_mismatches;
+}
+
+/*
+ * Strict pixel-exact comparison for frame dump tests.
+ * Returns true only if every pixel matches exactly.
+ */
+static bool compare_frame_to_fb_exact(struct frames_test_data *data,
+				      struct chamelium_v3_frame *frame,
+				      struct igt_fb *fb)
+{
+	cairo_t *cr;
+	cairo_surface_t *surface;
+	unsigned char *fb_data;
+	int stride;
+	int x, y;
+	int mismatches = 0;
+
+	cr = igt_get_cairo_ctx(data->drm_fd, fb);
+	surface = cairo_get_target(cr);
+	cairo_surface_flush(surface);
+	fb_data = cairo_image_surface_get_data(surface);
+	stride = cairo_image_surface_get_stride(surface);
+
+	igt_debug("Exact frame comparison %dx%d...\n", frame->width, frame->height);
+
+	for (y = 0; y < frame->height; y++) {
+		for (x = 0; x < frame->width; x++) {
+			int frame_idx = (y * frame->width + x) * 3;
+			uint8_t cap_r = frame->data[frame_idx + 0];
+			uint8_t cap_g = frame->data[frame_idx + 1];
+			uint8_t cap_b = frame->data[frame_idx + 2];
+
+			int fb_idx = y * stride + x * 4;
+			uint8_t fb_b = fb_data[fb_idx + 0];
+			uint8_t fb_g = fb_data[fb_idx + 1];
+			uint8_t fb_r = fb_data[fb_idx + 2];
+
+			if (cap_r != fb_r || cap_g != fb_g || cap_b != fb_b) {
+				mismatches++;
+				if (mismatches <= 10)
+					igt_debug("Mismatch at (%d,%d): captured RGB(%d,%d,%d) vs expected RGB(%d,%d,%d)\n",
+						  x, y, cap_r, cap_g, cap_b, fb_r, fb_g, fb_b);
+			}
+		}
+	}
+
+	igt_put_cairo_ctx(cr);
+
+	if (mismatches > 0) {
+		int total = frame->width * frame->height;
+
+		igt_info("Exact comparison FAILED: %d mismatches out of %d pixels (%.2f%%)\n",
+			 mismatches, total, 100.0 * mismatches / total);
+	} else {
+		igt_info("Exact comparison PASSED: all pixels match\n");
+	}
+
+	return mismatches == 0;
+}
+
+#define CAPTURE_MAX_RETRIES 5
+#define CAPTURE_RETRY_DELAY_S 5
+
+/*
+ * Capture a frame with retry logic.
+ * DumpPixels can fail with V4L.Timeout if the signal hasn't stabilized
+ * after a mode switch. We retry with increasing delay.
+ */
+static struct chamelium_v3_frame *capture_frame_with_retry(
+	struct frames_test_data *data,
+	chamelium_v3_port_id port_id)
+{
+	struct chamelium_v3_frame *frame;
+	int retry;
+
+	for (retry = 0; retry < CAPTURE_MAX_RETRIES; retry++) {
+		frame = chamelium_v3_capture_frame(data->chamelium, port_id);
+		if (frame)
+			return frame;
+
+		igt_warn("Frame capture attempt %d/%d failed, retrying in %ds...\n",
+			 retry + 1, CAPTURE_MAX_RETRIES,
+			 CAPTURE_RETRY_DELAY_S + retry);
+		sleep(CAPTURE_RETRY_DELAY_S + retry);
+	}
+
+	return NULL;
+}
+
+/*
+ * Capture a frame from Chamelium and compare against the given framebuffer.
+ * Repeats capture `count` times and verifies all captures match.
+ *
+ * Returns true on success.  When skip_on_capture_fail is set, a
+ * DumpPixels timeout (after all retries) returns false instead of
+ * asserting, so the caller can skip the problematic mode.
+ */
+static bool do_test_display(struct frames_test_data *data,
+			    chamelium_v3_port_id port_id,
+			    igt_output_t *output,
+			    igt_plane_t *primary,
+			    drmModeModeInfo *mode,
+			    uint32_t fourcc,
+			    int tolerance,
+			    int count,
+			    bool skip_on_capture_fail)
+{
+	struct igt_fb pattern_fb, frame_fb;
+	struct chamelium_v3_frame *frame;
+	int i, fb_id, frame_id;
+	bool success = true;
+
+	/* Create a pattern framebuffer in XRGB8888 */
+	fb_id = igt_create_color_pattern_fb(data->drm_fd,
+					    mode->hdisplay, mode->vdisplay,
+					    DRM_FORMAT_XRGB8888,
+					    DRM_FORMAT_MOD_LINEAR,
+					    0, 0, 0, &pattern_fb);
+	igt_assert(fb_id > 0);
+
+	/* Convert to the requested format if different */
+	if (fourcc != DRM_FORMAT_XRGB8888) {
+		frame_id = igt_fb_convert(&frame_fb, &pattern_fb, fourcc,
+					  DRM_FORMAT_MOD_LINEAR);
+		igt_assert(frame_id > 0);
+	} else {
+		frame_fb = pattern_fb;
+	}
+
+	/* Enable output with the display framebuffer */
+	enable_output(data, output, primary, &frame_fb);
+
+	igt_info("Capturing %d frame(s) at %dx%d for format %s...\n",
+		 count, mode->hdisplay, mode->vdisplay, igt_format_str(fourcc));
+
+	for (i = 0; i < count; i++) {
+		frame = capture_frame_with_retry(data, port_id);
+		if (!frame) {
+			if (skip_on_capture_fail) {
+				igt_warn("Frame capture failed for %dx%d@%d – skipping mode (V4L timeout)\n",
+					 mode->hdisplay, mode->vdisplay,
+					 mode->vrefresh);
+				success = false;
+				goto cleanup;
+			}
+			igt_assert_f(frame, "Failed to capture frame %d/%d\n",
+				     i + 1, count);
+		}
+
+		igt_assert_f(frame->width == mode->hdisplay &&
+			     frame->height == mode->vdisplay,
+			     "Frame size mismatch: captured %dx%d, expected %dx%d\n",
+			     frame->width, frame->height,
+			     mode->hdisplay, mode->vdisplay);
+
+		/* Compare against the original XRGB8888 pattern */
+		igt_assert_f(compare_frame_to_fb(data, frame, &pattern_fb, tolerance),
+			     "Frame %d/%d comparison failed\n", i + 1, count);
+
+		chamelium_v3_free_frame(frame);
+	}
+
+cleanup:
+	if (fourcc != DRM_FORMAT_XRGB8888)
+		igt_remove_fb(data->drm_fd, &frame_fb);
+	igt_remove_fb(data->drm_fd, &pattern_fb);
+	return success;
+}
+
+/*
+ * Frame dump: capture and do exact pixel-by-pixel comparison.
+ * Frame dump comparison test.
+ */
+static void do_test_frame_dump(struct frames_test_data *data,
+			       chamelium_v3_port_id port_id,
+			       igt_output_t *output,
+			       igt_plane_t *primary,
+			       drmModeModeInfo *mode,
+			       bool is_hdmi)
+{
+	struct igt_fb fb;
+	struct chamelium_v3_frame *frame;
+	int fb_id, j;
+	int dump_count = 5;
+
+	fb_id = igt_create_color_pattern_fb(data->drm_fd,
+					    mode->hdisplay, mode->vdisplay,
+					    DRM_FORMAT_XRGB8888,
+					    DRM_FORMAT_MOD_LINEAR,
+					    0, 0, 0, &fb);
+	igt_assert(fb_id > 0);
+
+	enable_output(data, output, primary, &fb);
+
+	igt_info("Dumping %d frames at %dx%d (%s)...\n",
+		 dump_count, mode->hdisplay, mode->vdisplay,
+		 is_hdmi ? "HDMI-tolerant" : "DP-exact");
+
+	for (j = 0; j < dump_count; j++) {
+		frame = capture_frame_with_retry(data, port_id);
+		igt_assert_f(frame, "Failed to capture frame dump %d/%d\n",
+			     j + 1, dump_count);
+
+		igt_assert_f(frame->width == mode->hdisplay &&
+			     frame->height == mode->vdisplay,
+			     "Frame size mismatch: captured %dx%d, expected %dx%d\n",
+			     frame->width, frame->height,
+			     mode->hdisplay, mode->vdisplay);
+
+		/*
+		 * DP is pixel-exact since it's purely digital.
+		 * HDMI may have color space conversion artifacts at
+		 * certain modes, so use tolerant comparison.
+		 */
+		if (is_hdmi)
+			igt_assert_f(compare_frame_to_fb(data, frame, &fb,
+							 FRAME_CRC_TOLERANCE),
+				     "Frame dump %d/%d comparison failed\n",
+				     j + 1, dump_count);
+		else
+			igt_assert_f(compare_frame_to_fb_exact(data, frame, &fb),
+				     "Frame dump %d/%d pixel comparison failed\n",
+				     j + 1, dump_count);
+
+		chamelium_v3_free_frame(frame);
+	}
+
+	igt_remove_fb(data->drm_fd, &fb);
+}
+
+
+static void randomize_plane_stride(struct frames_test_data *data,
+				   uint32_t width, uint32_t height,
+				   uint32_t format, uint64_t modifier,
+				   size_t *stride)
+{
+	size_t stride_min;
+	uint32_t max_tile_w = 4, tile_w, tile_h;
+	int i;
+	struct igt_fb dummy;
+
+	stride_min = width * igt_format_plane_bpp(format, 0) / 8;
+
+	/* Randomize the stride to less than twice the minimum */
+	*stride = (rand() % stride_min) + stride_min;
+
+	/* Determine max tile width for alignment */
+	igt_create_fb(data->drm_fd, 64, 64, format, modifier, &dummy);
+	for (i = 0; i < dummy.num_planes; i++) {
+		igt_get_fb_tile_size(data->drm_fd, modifier,
+				     dummy.plane_bpp[i], &tile_w, &tile_h);
+		if (tile_w > max_tile_w)
+			max_tile_w = tile_w;
+	}
+	igt_remove_fb(data->drm_fd, &dummy);
+
+	/* Align to both 32-bit and tile width */
+	*stride = ALIGN(*stride, max_tile_w);
+}
+
+static void randomize_plane_setup(struct frames_test_data *data,
+				  igt_plane_t *plane,
+				  drmModeModeInfo *mode,
+				  uint32_t *width, uint32_t *height,
+				  uint32_t *format, uint64_t *modifier,
+				  bool allow_yuv)
+{
+	int min_dim;
+	uint32_t idx[plane->format_mod_count];
+	unsigned int count = 0;
+	unsigned int i;
+
+	/* Count supported formats */
+	for (i = 0; i < plane->format_mod_count; i++)
+		if (igt_fb_supported_format(plane->formats[i]) &&
+		    (allow_yuv || !igt_format_is_yuv(plane->formats[i])))
+			idx[count++] = i;
+
+	igt_assert(count > 0);
+
+	i = idx[rand() % count];
+	*format = plane->formats[i];
+	*modifier = plane->modifiers[i];
+
+	/* Use minimum dimension of 16 for YUV, 8 for others */
+	min_dim = igt_format_is_yuv(*format) ? 16 : 8;
+
+	*width = max((rand() % mode->hdisplay) + 1, 2 * min_dim);
+	*height = max((rand() % mode->vdisplay) + 1, 2 * min_dim);
+}
+
+static void configure_plane(igt_plane_t *plane,
+			    uint32_t src_w, uint32_t src_h,
+			    uint32_t src_x, uint32_t src_y,
+			    uint32_t crtc_w, uint32_t crtc_h,
+			    int32_t crtc_x, int32_t crtc_y,
+			    struct igt_fb *fb)
+{
+	igt_plane_set_fb(plane, fb);
+	igt_plane_set_position(plane, crtc_x, crtc_y);
+	igt_plane_set_size(plane, crtc_w, crtc_h);
+	igt_fb_set_position(fb, plane, src_x, src_y);
+	igt_fb_set_size(fb, plane, src_w, src_h);
+}
+
+static void randomize_plane_coordinates(struct frames_test_data *data,
+					igt_plane_t *plane,
+					drmModeModeInfo *mode,
+					struct igt_fb *fb,
+					uint32_t *src_w, uint32_t *src_h,
+					uint32_t *src_x, uint32_t *src_y,
+					uint32_t *crtc_w, uint32_t *crtc_h,
+					int32_t *crtc_x, int32_t *crtc_y,
+					bool allow_scaling)
+{
+	bool is_yuv = igt_format_is_yuv(fb->drm_format);
+	uint32_t width = fb->width, height = fb->height;
+	double ratio;
+	int ret;
+
+	/* Randomize source offset in the first half */
+	*src_x = rand() % (width / 2);
+	*src_y = rand() % (height / 2);
+
+	*src_w = width - *src_x;
+	*src_h = height - *src_y;
+
+	if (allow_scaling) {
+		*crtc_w = (rand() % mode->hdisplay) + 1;
+		*crtc_h = (rand() % mode->vdisplay) + 1;
+
+		/* Limit scaling ratios */
+		ratio = ((double)*crtc_w / *src_w);
+		if (ratio < 0.5)
+			*src_w = *crtc_w * 2;
+		else if (ratio > 0.8 && ratio < 1.2)
+			*crtc_w = *src_w;
+		else if (ratio > 3.0)
+			*crtc_w = *src_w * 3;
+
+		ratio = ((double)*crtc_h / *src_h);
+		if (ratio < 0.5)
+			*src_h = *crtc_h * 2;
+		else if (ratio > 0.8 && ratio < 1.2)
+			*crtc_h = *src_h;
+		else if (ratio > 3.0)
+			*crtc_h = *src_h * 3;
+	} else {
+		*crtc_w = *src_w;
+		*crtc_h = *src_h;
+	}
+
+	if (*crtc_w != *src_w || *crtc_h != *src_h) {
+		if (*crtc_w < mode->hdisplay)
+			*crtc_x = rand() % (mode->hdisplay - *crtc_w);
+		else
+			*crtc_x = 0;
+
+		if (*crtc_h < mode->vdisplay)
+			*crtc_y = rand() % (mode->vdisplay - *crtc_h);
+		else
+			*crtc_y = 0;
+	} else {
+		*crtc_x = (rand() % mode->hdisplay) - *crtc_w / 2;
+		*crtc_y = (rand() % mode->vdisplay) - *crtc_h / 2;
+	}
+
+	configure_plane(plane, *src_w, *src_h, *src_x, *src_y,
+			*crtc_w, *crtc_h, *crtc_x, *crtc_y, fb);
+	ret = igt_display_try_commit_atomic(
+		&data->display,
+		DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET,
+		NULL);
+	if (!ret)
+		return;
+
+	igt_assert_f(ret != -ENOSPC,
+		     "Invalid coordinates on a %ux%u fb\n", width, height);
+
+	/* YUV alignment fixup */
+	if (is_yuv) {
+		*src_x &= ~1;
+		*src_y &= ~1;
+		*src_w &= ~1;
+		*src_h &= ~1;
+		*crtc_w &= ~1;
+		*crtc_h &= ~1;
+
+		if (*crtc_x < 0 && (*crtc_x & 1))
+			(*crtc_x)++;
+		else
+			*crtc_x &= ~1;
+
+		if (*crtc_y < 0 && (*crtc_y & 1))
+			(*crtc_y)++;
+		else
+			*crtc_y &= ~1;
+
+		configure_plane(plane, *src_w, *src_h, *src_x, *src_y,
+				*crtc_w, *crtc_h, *crtc_x, *crtc_y, fb);
+		ret = igt_display_try_commit_atomic(
+			&data->display,
+			DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET,
+			NULL);
+		if (!ret)
+			return;
+	}
+
+	igt_assert(!ret || allow_scaling);
+	igt_info("Scaling ratio %g / %g failed, trying without scaling.\n",
+		 ((double)*crtc_w / *src_w), ((double)*crtc_h / *src_h));
+
+	*crtc_w = *src_w;
+	*crtc_h = *src_h;
+
+	configure_plane(plane, *src_w, *src_h, *src_x, *src_y,
+			*crtc_w, *crtc_h, *crtc_x, *crtc_y, fb);
+	igt_display_commit_atomic(&data->display,
+				  DRM_MODE_ATOMIC_TEST_ONLY |
+				  DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+}
+
+static void blit_plane_cairo(struct frames_test_data *data,
+			     cairo_surface_t *result,
+			     uint32_t src_w, uint32_t src_h,
+			     uint32_t src_x, uint32_t src_y,
+			     uint32_t crtc_w, uint32_t crtc_h,
+			     int32_t crtc_x, int32_t crtc_y,
+			     struct igt_fb *fb)
+{
+	cairo_surface_t *surface;
+	cairo_surface_t *clipped_surface;
+	cairo_t *cr;
+
+	surface = igt_get_cairo_surface(data->drm_fd, fb);
+
+	if (src_x || src_y) {
+		clipped_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
+							     src_w, src_h);
+		cr = cairo_create(clipped_surface);
+		cairo_translate(cr, -1. * src_x, -1. * src_y);
+		cairo_set_source_surface(cr, surface, 0, 0);
+		cairo_paint(cr);
+		cairo_surface_flush(clipped_surface);
+		cairo_destroy(cr);
+	} else {
+		clipped_surface = surface;
+	}
+
+	cr = cairo_create(result);
+	cairo_translate(cr, crtc_x, crtc_y);
+
+	if (src_w != crtc_w || src_h != crtc_h)
+		cairo_scale(cr, (double)crtc_w / src_w, (double)crtc_h / src_h);
+
+	cairo_set_source_surface(cr, clipped_surface, 0, 0);
+	cairo_surface_destroy(clipped_surface);
+
+	if (src_w != crtc_w || src_h != crtc_h) {
+		cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
+		cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_NONE);
+	}
+
+	cairo_paint(cr);
+	cairo_surface_flush(result);
+	cairo_destroy(cr);
+}
+
+static void prepare_randomized_plane(struct frames_test_data *data,
+				     drmModeModeInfo *mode,
+				     igt_plane_t *plane,
+				     struct igt_fb *overlay_fb,
+				     unsigned int index,
+				     cairo_surface_t *result_surface,
+				     bool allow_scaling,
+				     bool allow_yuv)
+{
+	struct igt_fb pattern_fb;
+	uint32_t overlay_fb_w, overlay_fb_h;
+	uint32_t overlay_src_w, overlay_src_h;
+	uint32_t overlay_src_x, overlay_src_y;
+	int32_t overlay_crtc_x, overlay_crtc_y;
+	uint32_t overlay_crtc_w, overlay_crtc_h;
+	uint32_t format;
+	uint64_t modifier;
+	size_t stride;
+	bool tiled;
+	int fb_id;
+
+	randomize_plane_setup(data, plane, mode, &overlay_fb_w, &overlay_fb_h,
+			      &format, &modifier, allow_yuv);
+
+	tiled = (modifier != DRM_FORMAT_MOD_LINEAR);
+	igt_debug("Plane %d: framebuffer size %dx%d %s format (%s)\n", index,
+		  overlay_fb_w, overlay_fb_h, igt_format_str(format),
+		  tiled ? "tiled" : "linear");
+
+	/* Get a pattern framebuffer for the overlay plane */
+	fb_id = igt_create_color_pattern_fb(data->drm_fd,
+					    overlay_fb_w, overlay_fb_h,
+					    DRM_FORMAT_XRGB8888,
+					    DRM_FORMAT_MOD_LINEAR,
+					    0, 0, 0, &pattern_fb);
+	igt_assert(fb_id > 0);
+
+	randomize_plane_stride(data, overlay_fb_w, overlay_fb_h, format,
+			       modifier, &stride);
+
+	igt_debug("Plane %d: stride %ld\n", index, stride);
+
+	fb_id = igt_fb_convert_with_stride(overlay_fb, &pattern_fb, format,
+					   modifier, stride);
+	igt_assert(fb_id > 0);
+
+	randomize_plane_coordinates(data, plane, mode, overlay_fb,
+				    &overlay_src_w, &overlay_src_h,
+				    &overlay_src_x, &overlay_src_y,
+				    &overlay_crtc_w, &overlay_crtc_h,
+				    &overlay_crtc_x, &overlay_crtc_y,
+				    allow_scaling);
+
+	igt_debug("Plane %d: in-framebuffer size %dx%d\n", index,
+		  overlay_src_w, overlay_src_h);
+	igt_debug("Plane %d: in-framebuffer position %dx%d\n", index,
+		  overlay_src_x, overlay_src_y);
+	igt_debug("Plane %d: on-crtc size %dx%d\n", index,
+		  overlay_crtc_w, overlay_crtc_h);
+	igt_debug("Plane %d: on-crtc position %dx%d\n", index,
+		  overlay_crtc_x, overlay_crtc_y);
+
+	blit_plane_cairo(data, result_surface,
+			 overlay_src_w, overlay_src_h,
+			 overlay_src_x, overlay_src_y,
+			 overlay_crtc_w, overlay_crtc_h,
+			 overlay_crtc_x, overlay_crtc_y,
+			 &pattern_fb);
+
+	igt_remove_fb(data->drm_fd, &pattern_fb);
+}
+
+
+/*
+ * Display a test pattern on the first mode and capture/compare frames.
+ * Display one mode test.
+ */
+static void test_display_one_mode(struct frames_test_data *data,
+				  chamelium_v3_port_id port_id,
+				  bool is_hdmi,
+				  uint32_t fourcc,
+				  int tolerance,
+				  int count)
+__attribute__((unused));
+static void test_display_one_mode(struct frames_test_data *data,
+				  chamelium_v3_port_id port_id,
+				  bool is_hdmi,
+				  uint32_t fourcc,
+				  int tolerance,
+				  int count)
+{
+	igt_output_t *output;
+	igt_plane_t *primary;
+	drmModeModeInfo *mode;
+
+	igt_modeset_disable_all_outputs(&data->display);
+
+	output = find_output_for_port(data, port_id, is_hdmi);
+	igt_require_f(output, "No %s output found on DUT\n",
+		      is_hdmi ? "HDMI" : "DP");
+
+	mode = igt_output_get_mode(output);
+	primary = setup_output(data, output, mode);
+	igt_assert(primary);
+
+	igt_require(igt_plane_has_format_mod(primary, fourcc,
+					     DRM_FORMAT_MOD_LINEAR));
+
+	igt_info("Testing first mode: %dx%d@%d\n",
+		 mode->hdisplay, mode->vdisplay, mode->vrefresh);
+
+	do_test_display(data, port_id, output, primary, mode,
+			fourcc, tolerance, count, false);
+
+	disable_output(data, output, primary);
+}
+
+
+/*
+ * Iterate all modes, display a test pattern and capture/compare frames.
+ * Display all modes test.
+ */
+static void test_display_all_modes(struct frames_test_data *data,
+				   chamelium_v3_port_id port_id,
+				   bool is_hdmi,
+				   uint32_t fourcc,
+				   int tolerance,
+				   int count)
+{
+	igt_output_t *output;
+	igt_plane_t *primary;
+	drmModeConnector *connector;
+	int i, count_modes;
+	int tested = 0, skipped = 0;
+
+	output = find_output_for_port(data, port_id, is_hdmi);
+	igt_require_f(output, "No %s output found on DUT\n",
+		      is_hdmi ? "HDMI" : "DP");
+
+	connector = output->config.connector;
+	igt_require_f(connector->count_modes > 0, "No modes available\n");
+
+	igt_info("Testing modes for %s...\n", output->name);
+
+	i = 0;
+	do {
+		drmModeModeInfo local_mode;
+		bool ok;
+
+		/*
+		 * Modes may change due to mode pruning and link issues,
+		 * so refresh the connector each iteration.
+		 * igt_modeset_disable_all_outputs and igt_display_commit2
+		 * may reprobe the connector, invalidating mode pointers.
+		 */
+		igt_modeset_disable_all_outputs(&data->display);
+
+		output = find_output_for_port(data, port_id, is_hdmi);
+		igt_assert(output);
+
+		connector = output->config.connector;
+		count_modes = connector->count_modes;
+		if (i >= count_modes)
+			break;
+
+		/* Copy mode to stack to avoid stale pointer issues */
+		local_mode = connector->modes[i];
+
+		igt_info("Mode %d/%d: %dx%d@%d\n", i + 1,
+			 count_modes,
+			 local_mode.hdisplay, local_mode.vdisplay,
+			 local_mode.vrefresh);
+
+		primary = setup_output(data, output, &local_mode);
+		igt_assert(primary);
+
+		igt_require(igt_plane_has_format_mod(primary, fourcc,
+						     DRM_FORMAT_MOD_LINEAR));
+
+		ok = do_test_display(data, port_id, output, primary,
+				    &local_mode, fourcc, tolerance,
+				    count, true);
+		if (ok)
+			tested++;
+		else
+			skipped++;
+
+		disable_output(data, output, primary);
+	} while (++i < count_modes);
+
+	igt_info("All-modes summary: %d tested, %d skipped (capture timeout) out of %d\n",
+		 tested, skipped, i);
+	igt_assert_f(tested > 0,
+		     "All modes failed to capture – Chamelium capture broken\n");
+}
+
+
+/*
+ * For each mode, capture frames and do pixel-exact comparison.
+ * Frame dump test.
+ */
+static void test_display_frame_dump(struct frames_test_data *data,
+				    chamelium_v3_port_id port_id,
+				    bool is_hdmi)
+{
+	igt_output_t *output;
+	igt_plane_t *primary;
+	drmModeConnector *connector;
+	int i, count_modes;
+
+	output = find_output_for_port(data, port_id, is_hdmi);
+	igt_require_f(output, "No %s output found on DUT\n",
+		      is_hdmi ? "HDMI" : "DP");
+
+	connector = output->config.connector;
+	igt_require_f(connector->count_modes > 0, "No modes available\n");
+
+	igt_info("Frame dump test for %s...\n", output->name);
+
+	i = 0;
+	do {
+		drmModeModeInfo local_mode;
+
+		igt_modeset_disable_all_outputs(&data->display);
+
+		output = find_output_for_port(data, port_id, is_hdmi);
+		igt_assert(output);
+
+		connector = output->config.connector;
+		count_modes = connector->count_modes;
+		if (i >= count_modes)
+			break;
+
+		local_mode = connector->modes[i];
+
+		igt_info("Frame dump mode %d/%d: %dx%d@%d\n", i + 1,
+			 count_modes,
+			 local_mode.hdisplay, local_mode.vdisplay,
+			 local_mode.vrefresh);
+
+		primary = setup_output(data, output, &local_mode);
+		igt_assert(primary);
+
+		do_test_frame_dump(data, port_id, output, primary, &local_mode, is_hdmi);
+
+		disable_output(data, output, primary);
+	} while (++i < count_modes);
+}
+
+
+/*
+ * Test each supported non-planar (RGB) format on HDMI.
+ */
+static void test_display_nonplanar_formats(struct frames_test_data *data,
+					   chamelium_v3_port_id port_id,
+					   bool is_hdmi)
+{
+	igt_output_t *output;
+	igt_plane_t *primary;
+	drmModeModeInfo *mode;
+	unsigned int k;
+
+	igt_modeset_disable_all_outputs(&data->display);
+
+	output = find_output_for_port(data, port_id, is_hdmi);
+	igt_require_f(output, "No %s output found on DUT\n",
+		      is_hdmi ? "HDMI" : "DP");
+
+	mode = igt_output_get_mode(output);
+	primary = setup_output(data, output, mode);
+	igt_assert(primary);
+
+	for (k = 0; k < primary->format_mod_count; k++) {
+		if (!igt_fb_supported_format(primary->formats[k]))
+			continue;
+		if (igt_format_is_yuv(primary->formats[k]))
+			continue;
+		if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR)
+			continue;
+
+		igt_info("Testing non-planar format: %s\n",
+			 igt_format_str(primary->formats[k]));
+
+		do_test_display(data, port_id, output, primary, mode,
+				primary->formats[k], FRAME_FORMAT_TOLERANCE,
+				1, false);
+	}
+
+	disable_output(data, output, primary);
+}
+
+
+/*
+ * Test each supported planar (YUV) format with checkerboard comparison.
+ */
+static void test_display_planar_formats(struct frames_test_data *data,
+					chamelium_v3_port_id port_id,
+					bool is_hdmi)
+{
+	igt_output_t *output;
+	igt_plane_t *primary;
+	drmModeModeInfo *mode;
+	unsigned int k;
+
+	igt_modeset_disable_all_outputs(&data->display);
+
+	output = find_output_for_port(data, port_id, is_hdmi);
+	igt_require_f(output, "No %s output found on DUT\n",
+		      is_hdmi ? "HDMI" : "DP");
+
+	mode = igt_output_get_mode(output);
+	primary = setup_output(data, output, mode);
+	igt_assert(primary);
+
+	for (k = 0; k < primary->format_mod_count; k++) {
+		if (!igt_fb_supported_format(primary->formats[k]))
+			continue;
+		if (!igt_format_is_yuv(primary->formats[k]))
+			continue;
+		if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR)
+			continue;
+
+		igt_info("Testing planar format: %s\n",
+			 igt_format_str(primary->formats[k]));
+
+		do_test_display(data, port_id, output, primary, mode,
+				primary->formats[k],
+				FRAME_CHECKERBOARD_TOLERANCE, 1, false);
+	}
+
+	disable_output(data, output, primary);
+}
+
+
+/*
+ * Set up random overlay planes with random parameters, capture the
+ * composed frame and compare against a software-composed reference.
+ * Random planes test.
+ */
+static void test_display_planes_random(struct frames_test_data *data,
+				       chamelium_v3_port_id port_id,
+				       bool is_hdmi,
+				       int tolerance,
+				       bool allow_scaling,
+				       bool allow_yuv)
+{
+	igt_output_t *output;
+	drmModeModeInfo *mode;
+	igt_plane_t *primary_plane;
+	struct igt_fb primary_fb;
+	struct igt_fb result_fb;
+	struct igt_fb *overlay_fbs;
+	struct chamelium_v3_frame *frame;
+	unsigned int overlay_planes_max = 0;
+	unsigned int overlay_planes_count;
+	cairo_surface_t *result_surface;
+	unsigned int i;
+	unsigned int fb_id;
+
+	srand(time(NULL));
+
+	igt_modeset_disable_all_outputs(&data->display);
+
+	output = find_output_for_port(data, port_id, is_hdmi);
+	igt_require_f(output, "No %s output found on DUT\n",
+		      is_hdmi ? "HDMI" : "DP");
+
+	mode = igt_output_get_mode(output);
+
+	/* Get primary plane */
+	primary_plane = setup_output(data, output, mode);
+	igt_assert(primary_plane);
+
+	/* Create primary FB */
+	fb_id = igt_create_color_pattern_fb(data->drm_fd,
+					    mode->hdisplay, mode->vdisplay,
+					    DRM_FORMAT_XRGB8888,
+					    DRM_FORMAT_MOD_LINEAR,
+					    0, 0, 0, &primary_fb);
+	igt_assert(fb_id > 0);
+
+	/* Create result FB for software composition */
+	fb_id = igt_create_fb(data->drm_fd, mode->hdisplay, mode->vdisplay,
+			      DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR,
+			      &result_fb);
+	igt_assert(fb_id > 0);
+
+	result_surface = igt_get_cairo_surface(data->drm_fd, &result_fb);
+
+	/* Blit primary framebuffer onto result surface */
+	blit_plane_cairo(data, result_surface, 0, 0, 0, 0, 0, 0, 0, 0,
+			 &primary_fb);
+
+	/* Set primary plane */
+	igt_plane_set_fb(primary_plane, &primary_fb);
+
+	overlay_planes_max = igt_output_count_plane_type(output,
+							 DRM_PLANE_TYPE_OVERLAY);
+	overlay_planes_max = min(overlay_planes_max, 4u);
+
+	overlay_planes_count = (rand() % overlay_planes_max) + 1;
+	igt_debug("Using %d overlay planes\n", overlay_planes_count);
+
+	overlay_fbs = calloc(overlay_planes_count, sizeof(struct igt_fb));
+
+	for (i = 0; i < overlay_planes_count; i++) {
+		struct igt_fb *overlay_fb = &overlay_fbs[i];
+		igt_plane_t *plane = igt_output_get_plane_type_index(
+			output, DRM_PLANE_TYPE_OVERLAY, i);
+		igt_assert(plane);
+
+		prepare_randomized_plane(data, mode, plane, overlay_fb, i,
+					result_surface, allow_scaling,
+					allow_yuv);
+	}
+
+	cairo_surface_destroy(result_surface);
+
+	/* Commit all planes */
+	igt_display_commit2(&data->display, COMMIT_ATOMIC);
+
+	/* Wait for stabilization */
+	sleep(2);
+
+	/* Capture and compare */
+	frame = capture_frame_with_retry(data, port_id);
+	igt_assert_f(frame, "Failed to capture frame for planes-random test\n");
+
+	igt_assert_f(compare_frame_to_fb(data, frame, &result_fb, tolerance),
+		     "Random planes frame comparison failed\n");
+
+	chamelium_v3_free_frame(frame);
+
+	/* Cleanup */
+	for (i = 0; i < overlay_planes_count; i++)
+		igt_remove_fb(data->drm_fd, &overlay_fbs[i]);
+
+	free(overlay_fbs);
+	igt_remove_fb(data->drm_fd, &primary_fb);
+	igt_remove_fb(data->drm_fd, &result_fb);
+
+	disable_output(data, output, primary_plane);
+}
+
+
+static enum infoframe_avi_picture_aspect_ratio
+get_infoframe_avi_picture_ar(uint32_t aspect_ratio)
+{
+	switch (aspect_ratio) {
+	case DRM_MODE_PICTURE_ASPECT_4_3:
+		return INFOFRAME_AVI_PIC_AR_4_3;
+	case DRM_MODE_PICTURE_ASPECT_16_9:
+		return INFOFRAME_AVI_PIC_AR_16_9;
+	default:
+		return INFOFRAME_AVI_PIC_AR_UNSPECIFIED;
+	}
+}
+
+/*
+ * Verify AVI InfoFrame aspect ratio.
+ * Aspect ratio test.
+ */
+static void test_display_aspect_ratio(struct frames_test_data *data,
+				      chamelium_v3_port_id port_id)
+{
+	igt_output_t *output;
+	igt_plane_t *primary;
+	drmModeConnector *connector;
+	drmModeModeInfo local_mode;
+	struct igt_fb fb;
+	struct chamelium_v3_infoframe *infoframe;
+	struct infoframe_avi infoframe_avi;
+	uint8_t vic = 16;
+	const struct vic_mode *vic_mode;
+	uint32_t aspect_ratio;
+	enum infoframe_avi_picture_aspect_ratio frame_ar;
+	int i, fb_id;
+	bool found, ok;
+
+	igt_modeset_disable_all_outputs(&data->display);
+
+	output = find_output_for_port(data, port_id, true /* HDMI */);
+	igt_require_f(output, "No HDMI output found on DUT\n");
+
+	connector = output->config.connector;
+
+	vic_mode = &vic_modes[vic];
+	aspect_ratio = vic_mode->picture_ar;
+
+	/*
+	 * Find a mode matching the VIC resolution and refresh rate.
+	 * V3 doesn't inject a special aspect-ratio EDID, so we match on
+	 * resolution/refresh only. The HDMI driver should still send
+	 * the correct VIC and aspect ratio in the AVI InfoFrame for
+	 * CEA modes like 1920x1080@60 (VIC 16).
+	 */
+	found = false;
+	igt_assert(connector->count_modes > 0);
+	for (i = 0; i < connector->count_modes; i++) {
+		if (vic_mode->hactive == connector->modes[i].hdisplay &&
+		    vic_mode->vactive == connector->modes[i].vdisplay &&
+		    vic_mode->vrefresh == connector->modes[i].vrefresh) {
+			local_mode = connector->modes[i];
+			found = true;
+			break;
+		}
+	}
+	igt_require_f(found,
+		      "Failed to find mode matching VIC %d (%dx%d@%d)\n",
+		      vic, vic_mode->hactive, vic_mode->vactive,
+		      vic_mode->vrefresh);
+
+	igt_info("Aspect ratio test: using mode %dx%d@%d (VIC %d)\n",
+		 local_mode.hdisplay, local_mode.vdisplay,
+		 local_mode.vrefresh, vic);
+
+	primary = setup_output(data, output, &local_mode);
+	igt_assert(primary);
+
+	fb_id = igt_create_color_pattern_fb(data->drm_fd,
+					    local_mode.hdisplay,
+					    local_mode.vdisplay,
+					    DRM_FORMAT_XRGB8888,
+					    DRM_FORMAT_MOD_LINEAR,
+					    0, 0, 0, &fb);
+	igt_assert(fb_id > 0);
+
+	enable_output(data, output, primary, &fb);
+
+	/* Read AVI InfoFrame from Chamelium */
+	infoframe = chamelium_v3_get_last_infoframe(data->chamelium, port_id, "avi");
+	igt_require_f(infoframe,
+		      "AVI InfoFrame not available (GetLastInfoFrame may not be implemented on this Chamelium V3)\n");
+
+	ok = infoframe_avi_parse(&infoframe_avi, infoframe->version,
+				 infoframe->payload, infoframe->payload_size);
+	igt_assert_f(ok, "Failed to parse AVI InfoFrame\n");
+
+	frame_ar = get_infoframe_avi_picture_ar(aspect_ratio);
+
+	igt_info("Checking AVI InfoFrame\n");
+	igt_info("  Picture aspect ratio: got %d, expected %d\n",
+		 infoframe_avi.picture_aspect_ratio, frame_ar);
+	igt_info("  Video Identification Code (VIC): got %d, expected %d\n",
+		 infoframe_avi.vic, vic);
+
+	igt_assert_f(infoframe_avi.picture_aspect_ratio == frame_ar,
+		     "AVI InfoFrame aspect ratio mismatch: got %d, expected %d\n",
+		     infoframe_avi.picture_aspect_ratio, frame_ar);
+	igt_assert_f(infoframe_avi.vic == vic,
+		     "AVI InfoFrame VIC mismatch: got %d, expected %d\n",
+		     infoframe_avi.vic, vic);
+
+	chamelium_v3_infoframe_destroy(infoframe);
+	igt_remove_fb(data->drm_fd, &fb);
+	disable_output(data, output, primary);
+}
+
+
+/**
+ * SUBTEST: dp-crc-fast
+ * Description: Display a test pattern on the first DP mode, capture a frame
+ *              via Chamelium V3 DumpPixels, and compare against local reference.
+ */
+static void test_dp_crc_fast(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_dp_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged DP port found\n");
+	test_display_one_mode(data, port, false, DRM_FORMAT_XRGB8888,
+			      FRAME_CRC_TOLERANCE, 1);
+}
+
+/**
+ * SUBTEST: dp-crc-single
+ * Description: For each mode of the base EDID on DP, display a test pattern,
+ *              capture 1 frame, and compare.
+ */
+static void test_dp_crc_single(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_dp_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged DP port found\n");
+	test_display_all_modes(data, port, false, DRM_FORMAT_XRGB8888,
+			       FRAME_CRC_TOLERANCE, 1);
+}
+
+/**
+ * SUBTEST: dp-crc-multiple
+ * Description: For each mode of the base EDID on DP, display a test pattern,
+ *              capture 3 frames, and compare each.
+ */
+static void test_dp_crc_multiple(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_dp_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged DP port found\n");
+	test_display_all_modes(data, port, false, DRM_FORMAT_XRGB8888,
+			       FRAME_CRC_TOLERANCE, 3);
+}
+
+/**
+ * SUBTEST: dp-frame-dump
+ * Description: For each mode of the base EDID on DP, display a test pattern,
+ *              capture frames and compare them pixel-by-pixel to the sent ones.
+ */
+static void test_dp_frame_dump(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_dp_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged DP port found\n");
+	test_display_frame_dump(data, port, false);
+}
+
+/**
+ * SUBTEST: hdmi-crc-fast
+ * Description: Display a test pattern on the first HDMI mode, capture a frame
+ *              via Chamelium V3 DumpPixels, and compare against local reference.
+ */
+static void test_hdmi_crc_fast(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_hdmi_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged HDMI port found\n");
+	test_display_one_mode(data, port, true, DRM_FORMAT_XRGB8888,
+			      FRAME_CRC_TOLERANCE, 1);
+}
+
+/**
+ * SUBTEST: hdmi-crc-single
+ * Description: For each mode of the base EDID on HDMI, display a test pattern,
+ *              capture 1 frame, and compare.
+ */
+static void test_hdmi_crc_single(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_hdmi_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged HDMI port found\n");
+	test_display_all_modes(data, port, true, DRM_FORMAT_XRGB8888,
+			       FRAME_CRC_TOLERANCE, 1);
+}
+
+/**
+ * SUBTEST: hdmi-crc-multiple
+ * Description: For each mode of the base EDID on HDMI, display a test pattern,
+ *              capture 3 frames, and compare each.
+ */
+static void test_hdmi_crc_multiple(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_hdmi_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged HDMI port found\n");
+	test_display_all_modes(data, port, true, DRM_FORMAT_XRGB8888,
+			       FRAME_CRC_TOLERANCE, 3);
+}
+
+/**
+ * SUBTEST: hdmi-crc-nonplanar-formats
+ * Description: For each non-planar pixel format supported by the primary plane,
+ *              display a test pattern and compare captured frame.
+ */
+static void test_hdmi_crc_nonplanar_formats(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_hdmi_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged HDMI port found\n");
+	test_display_nonplanar_formats(data, port, true);
+}
+
+/**
+ * SUBTEST: hdmi-crc-planes-random
+ * Description: Set up random overlay planes on HDMI, capture the composed
+ *              frame and compare against software-composed reference.
+ */
+static void test_hdmi_crc_planes_random(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_hdmi_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged HDMI port found\n");
+	test_display_planes_random(data, port, true,
+				   FRAME_CRC_TOLERANCE, false, false);
+}
+
+/**
+ * SUBTEST: hdmi-cmp-planar-formats
+ * Description: For each planar (YUV) pixel format, display a test pattern
+ *              and compare captured frame with checkerboard tolerance.
+ */
+static void test_hdmi_cmp_planar_formats(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_hdmi_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged HDMI port found\n");
+	test_display_planar_formats(data, port, true);
+}
+
+/**
+ * SUBTEST: hdmi-cmp-planes-random
+ * Description: Set up random overlay planes with YUV formats on HDMI,
+ *              capture the frame and compare with checkerboard tolerance.
+ */
+static void test_hdmi_cmp_planes_random(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_hdmi_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged HDMI port found\n");
+	test_display_planes_random(data, port, true,
+				   FRAME_CHECKERBOARD_TOLERANCE, true, true);
+}
+
+/**
+ * SUBTEST: hdmi-frame-dump
+ * Description: For each mode of the base EDID on HDMI, display a test pattern,
+ *              capture frames and compare pixel-by-pixel.
+ */
+static void test_hdmi_frame_dump(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_hdmi_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged HDMI port found\n");
+	test_display_frame_dump(data, port, true);
+}
+
+/**
+ * SUBTEST: hdmi-aspect-ratio
+ * Description: Set a mode with a specific picture aspect ratio, read the AVI
+ *              InfoFrame from Chamelium and verify the aspect ratio field matches.
+ */
+static void test_hdmi_aspect_ratio(struct frames_test_data *data)
+{
+	chamelium_v3_port_id port = find_hdmi_port(data);
+
+	igt_require_f(port != (chamelium_v3_port_id)-1, "No plugged HDMI port found\n");
+	test_display_aspect_ratio(data, port);
+}
+
+
+int igt_main()
+{
+	struct frames_test_data data = { 0 };
+
+	igt_fixture() {
+		/* Set VT to graphics mode so fbcon doesn't interfere */
+		kmstest_set_vt_graphics_mode();
+
+		/* Open DRM */
+		data.drm_fd = drm_open_driver_master(DRIVER_ANY);
+		igt_require(data.drm_fd >= 0);
+		igt_display_require(&data.display, data.drm_fd);
+		igt_require(data.display.is_atomic);
+
+		/* Initialize Chamelium V3 */
+		data.chamelium = chamelium_v3_init_from_config();
+		igt_require(data.chamelium);
+
+		data.port_count = chamelium_v3_get_supported_ports(
+			data.chamelium, &data.ports);
+		igt_require(data.port_count > 0);
+
+		igt_info("Found %d Chamelium ports\n", data.port_count);
+	}
+
+	/* DP frame/CRC tests */
+	igt_describe("Display test pattern on first DP mode, capture and compare frame");
+	igt_subtest("dp-crc-fast")
+		test_dp_crc_fast(&data);
+
+	igt_describe("For each DP mode, capture 1 frame and compare");
+	igt_subtest("dp-crc-single")
+		test_dp_crc_single(&data);
+
+	igt_describe("For each DP mode, capture 3 frames and compare");
+	igt_subtest("dp-crc-multiple")
+		test_dp_crc_multiple(&data);
+
+	igt_describe("For each DP mode, capture frames and compare pixel-by-pixel");
+	igt_subtest("dp-frame-dump")
+		test_dp_frame_dump(&data);
+
+	/* HDMI frame/CRC tests */
+	igt_describe("Display test pattern on first HDMI mode, capture and compare frame");
+	igt_subtest("hdmi-crc-fast")
+		test_hdmi_crc_fast(&data);
+
+	igt_describe("For each HDMI mode, capture 1 frame and compare");
+	igt_subtest("hdmi-crc-single")
+		test_hdmi_crc_single(&data);
+
+	igt_describe("For each HDMI mode, capture 3 frames and compare");
+	igt_subtest("hdmi-crc-multiple")
+		test_hdmi_crc_multiple(&data);
+
+	igt_describe("CRC check per non-planar pixel format for HDMI");
+	igt_subtest("hdmi-crc-nonplanar-formats")
+		test_hdmi_crc_nonplanar_formats(&data);
+
+	igt_describe("Random overlay plane frame comparison for HDMI");
+	igt_subtest("hdmi-crc-planes-random")
+		test_hdmi_crc_planes_random(&data);
+
+	igt_describe("Checkerboard comparison for YUV planar formats on HDMI");
+	igt_subtest("hdmi-cmp-planar-formats")
+		test_hdmi_cmp_planar_formats(&data);
+
+	igt_describe("Random overlay plane checkerboard comparison for HDMI");
+	igt_subtest("hdmi-cmp-planes-random")
+		test_hdmi_cmp_planes_random(&data);
+
+	igt_describe("Pixel-by-pixel frame dump comparison for HDMI");
+	igt_subtest("hdmi-frame-dump")
+		test_hdmi_frame_dump(&data);
+
+	igt_describe("AVI InfoFrame aspect ratio verification for HDMI");
+	igt_subtest("hdmi-aspect-ratio")
+		test_hdmi_aspect_ratio(&data);
+
+	igt_fixture() {
+		igt_display_fini(&data.display);
+		close(data.drm_fd);
+		free(data.ports);
+		chamelium_v3_uninit(data.chamelium);
+	}
+}
diff --git a/tests/meson.build b/tests/meson.build
index cfb59ecbb..c3dbff694 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -376,6 +376,7 @@ chamelium_v3_progs = [
 	'kms_chamelium_v3_basic',
 	'kms_chamelium_v3_hpd',
 	'kms_chamelium_v3_edid',
+	'kms_chamelium_v3_frames',
 ]
 
 test_deps = [ igt_deps ]
-- 
2.48.1


  parent reply	other threads:[~2026-04-28  4:52 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 ` [PATCH i-g-t v1 22/25] tests/chamelium/v3: Add EDID " Mohammed Bilal
2026-04-28  4:46 ` Mohammed Bilal [this message]
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-24-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