Igt-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH i-g-t 2/2] tests/intel: Add mode-rejected-max-dotclock subtest to kms_cdclk
@ 2026-08-14 12:16 Swati Sharma
  0 siblings, 0 replies; 3+ messages in thread
From: Swati Sharma @ 2026-08-14 12:16 UTC (permalink / raw)
  To: igt-dev; +Cc: Swati Sharma

Add a subtest that verifies the driver rejects a modeset when the
requested pixel clock exceeds the platform's maximum dotclock
capability. The test reads the max dotclock from debugfs via
igt_get_max_dotclock(), sets the mode clock 50 MHz above, and
asserts the atomic commit fails.

Signed-off-by: Swati Sharma <swati2.sharma@intel.com>
Assisted-by: GitHub Copilot:Claude Opus 4.6
---
 tests/intel/kms_cdclk.c | 64 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/tests/intel/kms_cdclk.c b/tests/intel/kms_cdclk.c
index 070fba400..33007f6b3 100644
--- a/tests/intel/kms_cdclk.c
+++ b/tests/intel/kms_cdclk.c
@@ -44,6 +44,10 @@
  *
  * SUBTEST: plane-scaling
  * Description: Plane scaling test to validate cdclk frequency change.
+ *
+ * SUBTEST: mode-rejected-max-dotclock
+ * Description: Verify that a mode exceeding the maximum pixel clock
+ *              frequency is rejected by the driver.
  */
 
 IGT_TEST_DESCRIPTION("Test cdclk features : crawling and squashing");
@@ -354,6 +358,62 @@ static void run_cdclk_test(data_t *data, uint32_t flags)
 	}
 }
 
+static void test_mode_rejected_max_dotclock(data_t *data)
+{
+	igt_display_t *display = &data->display;
+	igt_output_t *output;
+	igt_crtc_t *crtc;
+	int max_dotclock, ret;
+	struct igt_fb fb;
+
+	max_dotclock = igt_get_max_dotclock(data->drm_fd);
+	igt_require_f(max_dotclock > 0,
+		      "Could not read max pixel clock\n");
+
+	for_each_crtc_with_valid_output(display, crtc, output) {
+		drmModeModeInfo mode = *igt_output_get_mode(output);
+
+		igt_output_set_crtc(output, crtc);
+		if (!intel_pipe_output_combo_valid(display)) {
+			igt_output_set_crtc(output, NULL);
+			continue;
+		}
+
+		/* Set clock above PHY max */
+		mode.clock = max_dotclock + 50000;
+
+		igt_display_reset(display);
+		igt_output_set_crtc(output, crtc);
+		igt_output_override_mode(output, &mode);
+
+		igt_create_pattern_fb(data->drm_fd,
+				      mode.hdisplay, mode.vdisplay,
+				      DRM_FORMAT_XRGB8888,
+				      DRM_FORMAT_MOD_LINEAR, &fb);
+		igt_plane_set_fb(igt_output_get_plane_type(output,
+				 DRM_PLANE_TYPE_PRIMARY), &fb);
+
+		ret = igt_display_try_commit_atomic(display,
+						    DRM_MODE_ATOMIC_ALLOW_MODESET,
+						    NULL);
+
+		igt_info("Output %s: clock=%dkHz (max=%dkHz) -> %s\n",
+			 output->name, mode.clock, max_dotclock,
+			 ret ? "rejected" : "accepted");
+
+		igt_assert_f(ret != 0,
+			     "Mode with clock=%dkHz exceeding max=%dkHz "
+			     "should be rejected on %s\n",
+			     mode.clock, max_dotclock, output->name);
+
+		igt_plane_set_fb(igt_output_get_plane_type(output,
+				 DRM_PLANE_TYPE_PRIMARY), NULL);
+		igt_output_set_crtc(output, NULL);
+		igt_remove_fb(data->drm_fd, &fb);
+		break;
+	}
+}
+
 int igt_main()
 {
 	data_t data = {};
@@ -384,6 +444,10 @@ int igt_main()
 	igt_subtest("mode-transition-all-outputs")
 		test_mode_transition_on_all_outputs(&data);
 
+	igt_describe("Verify that a mode exceeding max pixel clock is rejected.");
+	igt_subtest("mode-rejected-max-dotclock")
+		test_mode_rejected_max_dotclock(&data);
+
 	igt_fixture() {
 		igt_display_fini(&data.display);
 		drm_close_driver(data.drm_fd);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread
* [PATCH i-g-t 1/2] tests/intel: Add kms_hdmi_audio_bw test
@ 2026-08-19  7:38 Swati Sharma
  2026-08-19  7:38 ` [PATCH i-g-t 2/2] tests/intel: Add mode-rejected-max-dotclock subtest to kms_cdclk Swati Sharma
  0 siblings, 1 reply; 3+ messages in thread
From: Swati Sharma @ 2026-08-19  7:38 UTC (permalink / raw)
  To: igt-dev; +Cc: Swati Sharma

Add a new IGT test to validate HDMI TMDS audio bandwidth constraints
under constrained horizontal blanking intervals.

The test injects EDIDs declaring all 7 CEA sample rates (32kHz-192kHz)
and observes which rates the driver exposes via ELD (EDID-Like Data)
under varying BPC and channel configurations.

Subtests:
- audio-bw-supported: Baseline with hblank=160 where audio bandwidth
  is sufficient for all configurations.
- audio-bw-pruned: Constrained hblank=80 (CVT RB2) where the driver
  must prune unsustainable sample rates or disable audio entirely.
- suspend-s3/s4-audio-recovery: Verify audio state and sample rates
  are preserved across system suspend/resume.
- runtime-suspend-audio-recovery: Verify audio state is preserved
  across DPMS off/on cycles.

The test uses the HDMI TMDS bandwidth formula from the spec:
  pkts_avail = FLOOR((CEIL(hblank * bpc/8) - overhead) / 32)
  pkts_reqd  = CEIL(R_AP * T_line)
where overhead=74 (HDCP 1.4 rekey always reserved by the driver).

Assertions verify:
- Audio is active when bandwidth is available (pkts_avail > 0)
- Audio is inactive when bandwidth is exhausted (pkts_avail == 0)
- No sample rate requiring more packets than available appears in ELD

Signed-off-by: Swati Sharma <swati2.sharma@intel.com>
Assisted-by: GitHub Copilot:Claude Opus 4.6
---
 tests/intel/kms_hdmi_audio_bw.c | 626 ++++++++++++++++++++++++++++++++
 tests/meson.build               |   1 +
 2 files changed, 627 insertions(+)
 create mode 100644 tests/intel/kms_hdmi_audio_bw.c

diff --git a/tests/intel/kms_hdmi_audio_bw.c b/tests/intel/kms_hdmi_audio_bw.c
new file mode 100644
index 000000000..4b6f0d001
--- /dev/null
+++ b/tests/intel/kms_hdmi_audio_bw.c
@@ -0,0 +1,626 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+/**
+ * TEST: kms hdmi audio bw
+ * Category: Display
+ * Description: Validate HDMI TMDS audio bandwidth constraints by injecting
+ *              EDIDs with all sample rates declared and observing which rates
+ *              the driver exposes (via ELD) under varying BPC / channel /
+ *              hblank configurations.
+ * Driver requirement: i915, xe
+ * Mega feature: Display Audio
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <string.h>
+
+#include "igt.h"
+#include "igt_edid.h"
+#include "igt_eld.h"
+#include "igt_aux.h"
+#include "xe/xe_query.h"
+
+/**
+ * SUBTEST: audio-bw-supported
+ * Description: Baseline test with hblank=160 where audio bandwidth is
+ *              sufficient for all BPC and channel combinations. Verifies
+ *              that no sample rates are pruned.
+ *
+ * SUBTEST: audio-bw-pruned
+ * Description: Constrained test with hblank=80 (CVT RB2) where audio
+ *              bandwidth is limited. Logs which sample rates are pruned
+ *              per BPC and channel combination.
+ *
+ * SUBTEST: suspend-%s-audio-recovery
+ * Description: Validate audio state restoration after %arg[1] with
+ *              constrained hblank=80 and 12bpc.
+ *
+ * arg[1]:
+ *
+ * @s3:  S3 (suspend to RAM)
+ * @s4:  S4 (hibernate)
+ *
+ * SUBTEST: runtime-suspend-audio-recovery
+ * Description: Validate audio state restoration after runtime suspend/resume
+ *              with constrained hblank=80 and 12bpc.
+ */
+
+IGT_TEST_DESCRIPTION("Validate HDMI TMDS audio bandwidth constraints. "
+		      "EDIDs declare all sample rates (32k-192k); the test "
+		      "observes which rates survive in the ELD under "
+		      "constrained hblank timings.");
+
+typedef struct {
+	int drm_fd;
+	igt_display_t display;
+	igt_output_t *output;
+	igt_crtc_t *crtc;
+	struct igt_fb fb;
+} data_t;
+
+/* All sample rates declared in the EDID SAD */
+#define ALL_SAMPLE_RATES (CEA_SAD_SAMPLING_RATE_32KHZ | \
+			  CEA_SAD_SAMPLING_RATE_44KHZ | \
+			  CEA_SAD_SAMPLING_RATE_48KHZ | \
+			  CEA_SAD_SAMPLING_RATE_88KHZ | \
+			  CEA_SAD_SAMPLING_RATE_96KHZ | \
+			  CEA_SAD_SAMPLING_RATE_176KHZ | \
+			  CEA_SAD_SAMPLING_RATE_192KHZ)
+
+struct rate_info {
+	unsigned int flag;
+	const char *name;
+	int freq_hz;
+};
+
+static const struct rate_info rate_table[] = {
+	{ CEA_SAD_SAMPLING_RATE_32KHZ,  "32k",   32000 },
+	{ CEA_SAD_SAMPLING_RATE_44KHZ,  "44.1k", 44100 },
+	{ CEA_SAD_SAMPLING_RATE_48KHZ,  "48k",   48000 },
+	{ CEA_SAD_SAMPLING_RATE_88KHZ,  "88k",   88200 },
+	{ CEA_SAD_SAMPLING_RATE_96KHZ,  "96k",   96000 },
+	{ CEA_SAD_SAMPLING_RATE_176KHZ, "176k",  176400 },
+	{ CEA_SAD_SAMPLING_RATE_192KHZ, "192k",  192000 },
+};
+
+#define ACR_RATE_MAX		1500
+#define TOLERANCE_AUDIOCLK_PPM	1000
+#define TOLERANCE_PIXELCLK	0.005
+#define HBLANK_OVERHEAD_STD	30
+#define HBLANK_OVERHEAD_HDCP14	74
+#define DI_PACKET_SIZE		32
+
+static void rates_to_str(unsigned int rates, char *buf, size_t len)
+{
+	int pos = 0;
+
+	buf[0] = '\0';
+	for (int i = 0; i < ARRAY_SIZE(rate_table); i++) {
+		if (!(rates & rate_table[i].flag))
+			continue;
+		if (pos > 0)
+			pos += snprintf(buf + pos, len - pos, ",");
+		pos += snprintf(buf + pos, len - pos, "%s", rate_table[i].name);
+	}
+	if (pos == 0)
+		snprintf(buf, len, "none");
+}
+
+static const int bpc_values[] = { 8, 10, 12 };
+static const int channel_values[] = { 2, 8 };
+
+/*
+ * 1920x1080@60Hz CVT RB2 — hblank=80 (constrained)
+ * Available Packets/Line = FLOOR(((BPC/8)*80 - 74) / 32)
+ *   8bpc=0, 10bpc=0, 12bpc=1
+ */
+static const drmModeModeInfo mode_1080p_hblank80 = {
+	.clock = 133320,
+	.hdisplay = 1920,
+	.hsync_start = 1928,
+	.hsync_end = 1960,
+	.htotal = 2000,		/* hblank = 80 */
+	.vdisplay = 1080,
+	.vsync_start = 1097,
+	.vsync_end = 1105,
+	.vtotal = 1111,
+	.vrefresh = 60,
+	.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC,
+	.type = DRM_MODE_TYPE_DRIVER,
+	.name = "1920x1080",
+};
+
+/*
+ * 1920x1080@60Hz with hblank=160 (relaxed baseline)
+ * Enough hblank for audio at any BPC.
+ */
+static const drmModeModeInfo mode_1080p_hblank160 = {
+	.clock = 148500,
+	.hdisplay = 1920,
+	.hsync_start = 1968,
+	.hsync_end = 2000,
+	.htotal = 2080,		/* hblank = 160 */
+	.vdisplay = 1080,
+	.vsync_start = 1097,
+	.vsync_end = 1105,
+	.vtotal = 1111,
+	.vrefresh = 60,
+	.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC,
+	.type = DRM_MODE_TYPE_DRIVER,
+	.name = "1920x1080",
+};
+
+static igt_output_t *find_hdmi_output(igt_display_t *display)
+{
+	igt_output_t *output;
+
+	for_each_connected_output(display, output) {
+		drmModeConnector *c = output->config.connector;
+
+		if (c->connector_type == DRM_MODE_CONNECTOR_HDMIA ||
+		    c->connector_type == DRM_MODE_CONNECTOR_HDMIB)
+			return output;
+	}
+
+	return NULL;
+}
+
+static int hblank_of(const drmModeModeInfo *mode)
+{
+	return mode->htotal - mode->hdisplay;
+}
+
+/*
+ * Driver always reserves HDCP 1.4 rekey overhead (74 clocks) even when
+ * HDCP is not active: 30 (standard) + 44 (HDCP 1.4 rekey quiet period).
+ * FIXME: once driver exposes HDCP state, use 30 for no HDCP, 74 for HDCP 1.4.
+ */
+static int avail_pkts_per_line(int bpc, int hblank)
+{
+	int overhead = HBLANK_OVERHEAD_HDCP14;
+	int tb_blank = (bpc * hblank + 7) / 8; /* CEIL(hblank * bpc/8) */
+	int avail = (tb_blank - overhead) / DI_PACKET_SIZE;
+
+	return avail > 0 ? avail : 0;
+}
+
+/* Packets required per line for a given audio rate and channel layout */
+static int required_pkts_per_line(const drmModeModeInfo *mode, int freq_hz,
+				 int channels)
+{
+	double ap = (channels <= 2) ? 0.25 : 1.0;
+	double f_pixel_max = mode->clock * 1000.0 * (1 + TOLERANCE_PIXELCLK);
+	double t_line = mode->htotal / f_pixel_max;
+	double r_ap = ((freq_hz * ap) + (2 * ACR_RATE_MAX)) *
+		      (1 + TOLERANCE_AUDIOCLK_PPM / 1e6);
+
+	double avg_pkts = r_ap * t_line;
+
+	return (int)avg_pkts + (avg_pkts > (int)avg_pkts ? 1 : 0);
+}
+
+/*
+ * Build a CEA EDID declaring all 7 sample rates in the SAD.
+ * Deep-color flags in HDMI VSDB match the requested bpc.
+ */
+static const struct edid *
+build_edid(int bpc, int audio_channels)
+{
+	static unsigned char raw_edid[2 * EDID_BLOCK_SIZE];
+	struct edid *edid;
+	struct edid_ext *ext;
+	struct edid_cea *cea;
+	struct edid_cea_data_block *block;
+	struct cea_sad sad;
+	struct hdmi_vsdb hdmi;
+	struct cea_speaker_alloc speakers;
+	size_t offset = 0;
+
+	memset(raw_edid, 0, sizeof(raw_edid));
+	edid = (struct edid *)raw_edid;
+	memcpy(edid, igt_kms_get_base_edid(), sizeof(struct edid));
+	edid->extensions_len = 1;
+
+	ext = &edid->extensions[0];
+	cea = &ext->data.cea;
+
+	if (audio_channels > 0) {
+		cea_sad_init_pcm(&sad,
+				 audio_channels,
+				 ALL_SAMPLE_RATES,
+				 CEA_SAD_SAMPLE_SIZE_16 |
+				 CEA_SAD_SAMPLE_SIZE_24);
+		block = (struct edid_cea_data_block *)&cea->data[offset];
+		offset += edid_cea_data_block_set_sad(block, &sad, 1);
+	}
+
+	memset(&hdmi, 0, sizeof(hdmi));
+	hdmi.src_phy_addr[0] = 0x10;
+	hdmi.src_phy_addr[1] = 0x00;
+	hdmi.flags1 = HDMI_VSDB_SUPPORTS_AI;
+	hdmi.max_tdms_clock = 340000000 / (5 * 1000000);
+
+	switch (bpc) {
+	case 12:
+		hdmi.flags1 |= HDMI_VSDB_DC_36BIT;
+		/* fall through */
+	case 10:
+		hdmi.flags1 |= HDMI_VSDB_DC_30BIT;
+		/* fall through */
+	case 8:
+		break;
+	}
+
+	block = (struct edid_cea_data_block *)&cea->data[offset];
+	offset += edid_cea_data_block_set_hdmi_vsdb(block, &hdmi,
+						    sizeof(hdmi));
+
+	memset(&speakers, 0, sizeof(speakers));
+	speakers.speakers = CEA_SPEAKER_FRONT_LEFT_RIGHT;
+	if (audio_channels > 2)
+		speakers.speakers |= CEA_SPEAKER_FRONT_CENTER |
+				     CEA_SPEAKER_LFE |
+				     CEA_SPEAKER_REAR_LEFT_RIGHT;
+	block = (struct edid_cea_data_block *)&cea->data[offset];
+	offset += edid_cea_data_block_set_speaker_alloc(block, &speakers);
+
+	edid_ext_set_cea(ext, offset, 0,
+			 EDID_CEA_BASIC_AUDIO | EDID_CEA_UNDERSCAN |
+			 EDID_CEA_YCBCR444 | EDID_CEA_YCBCR422);
+	edid_update_checksum(edid);
+
+	return edid;
+}
+
+static void force_edid_and_connector(data_t *data, const struct edid *edid)
+{
+	kmstest_force_edid(data->drm_fd, data->output->config.connector, edid);
+	igt_skip_on_f(!kmstest_force_connector(data->drm_fd,
+					       data->output->config.connector,
+					       FORCE_CONNECTOR_ON),
+		      "Could not force HDMI connector on\n");
+}
+
+static void cleanup_connector(data_t *data)
+{
+	if (data->output->pending_crtc) {
+		igt_plane_t *primary;
+
+		primary = igt_output_get_plane_type(data->output,
+						    DRM_PLANE_TYPE_PRIMARY);
+		igt_plane_set_fb(primary, NULL);
+		igt_output_set_crtc(data->output, NULL);
+		igt_display_commit2(&data->display, COMMIT_ATOMIC);
+	}
+
+	igt_remove_fb(data->drm_fd, &data->fb);
+
+	kmstest_force_connector(data->drm_fd,
+				data->output->config.connector,
+				FORCE_CONNECTOR_UNSPECIFIED);
+	kmstest_force_edid(data->drm_fd,
+			   data->output->config.connector, NULL);
+}
+
+static int try_modeset(data_t *data, const drmModeModeInfo *mode)
+{
+	igt_plane_t *primary;
+	int ret;
+
+	igt_display_reset(&data->display);
+
+	igt_output_set_crtc(data->output, data->crtc);
+	igt_output_override_mode(data->output, mode);
+
+	primary = igt_output_get_plane_type(data->output,
+					    DRM_PLANE_TYPE_PRIMARY);
+
+	igt_create_pattern_fb(data->drm_fd,
+			      mode->hdisplay, mode->vdisplay,
+			      DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR,
+			      &data->fb);
+	igt_plane_set_fb(primary, &data->fb);
+
+	ret = igt_display_try_commit_atomic(&data->display,
+					    DRM_MODE_ATOMIC_ALLOW_MODESET,
+					    NULL);
+	if (ret) {
+		igt_plane_set_fb(primary, NULL);
+		igt_output_set_crtc(data->output, NULL);
+		igt_remove_fb(data->drm_fd, &data->fb);
+	}
+
+	return ret;
+}
+
+static bool audio_is_active(void)
+{
+	if (!eld_is_supported())
+		return false;
+
+	return eld_has_igt();
+}
+
+static unsigned int get_eld_rates(void)
+{
+	struct eld_entry eld;
+
+	if (!eld_get_igt(&eld))
+		return 0;
+
+	if (eld.sads_len == 0)
+		return 0;
+
+	return eld.sads[0].rates;
+}
+
+static void log_eld_rates(unsigned int declared, unsigned int eld_rates)
+{
+	char decl_str[128], eld_str[128], pruned_str[128];
+	unsigned int pruned = declared & ~eld_rates;
+
+	rates_to_str(declared, decl_str, sizeof(decl_str));
+	rates_to_str(eld_rates, eld_str, sizeof(eld_str));
+	rates_to_str(pruned, pruned_str, sizeof(pruned_str));
+
+	igt_info("    SAD declared: %s\n", decl_str);
+	igt_info("    ELD reports:  %s\n", eld_str);
+	if (pruned)
+		igt_info("    Pruned:       %s\n", pruned_str);
+}
+
+static void assert_per_rate(const drmModeModeInfo *mode, int channels,
+			   int pkts_avail, unsigned int eld_rates)
+{
+	for (int i = 0; i < ARRAY_SIZE(rate_table); i++) {
+		int req = required_pkts_per_line(mode, rate_table[i].freq_hz,
+						 channels);
+		bool in_eld = eld_rates & rate_table[i].flag;
+
+		/* A rate that can't fit must not appear in ELD */
+		igt_assert_f(!(req > pkts_avail && in_eld),
+			     "%s: req=%d > avail=%d but rate present in ELD\n",
+			     rate_table[i].name, req, pkts_avail);
+	}
+}
+
+static void log_per_rate_analysis(const drmModeModeInfo *mode,
+				  int bpc, int channels,
+				  int pkts_avail, unsigned int eld_rates)
+{
+	const char *layout = (channels <= 2) ? "L0" : "L1";
+
+	igt_info("    %-6s %-3s  pkts: avail=%d\n",
+		 "Rate", layout, pkts_avail);
+
+	for (int i = 0; i < ARRAY_SIZE(rate_table); i++) {
+		int req = required_pkts_per_line(mode, rate_table[i].freq_hz,
+						 channels);
+		const char *expect = (req <= pkts_avail && pkts_avail > 0) ?
+				     "fit" : "NO";
+		const char *eld_has = (eld_rates & rate_table[i].flag) ?
+				     "yes" : "no";
+
+		igt_info("      %5s: req=%d fit=%s  (ELD: %s)\n",
+			 rate_table[i].name, req, expect, eld_has);
+	}
+}
+
+/* Run the BPC × channels matrix for a given mode/hblank. */
+static void test_audio_bw_matrix(data_t *data, const drmModeModeInfo *mode)
+{
+	int hblank = hblank_of(mode);
+
+	igt_info("=== Audio BW matrix: %s hblank=%d ===\n",
+		 mode->name, hblank);
+
+	for (int b = 0; b < ARRAY_SIZE(bpc_values); b++) {
+		int bpc = bpc_values[b];
+
+		for (int c = 0; c < ARRAY_SIZE(channel_values); c++) {
+			int channels = channel_values[c];
+			const struct edid *edid;
+			int pkts, ret;
+			bool audio;
+			unsigned int eld_rates;
+
+			edid = build_edid(bpc, channels);
+			force_edid_and_connector(data, edid);
+
+			igt_output_set_prop_value(data->output,
+						  IGT_CONNECTOR_MAX_BPC, bpc);
+
+			pkts = avail_pkts_per_line(bpc, hblank);
+
+			igt_info("\n  %dbpc %dch hblank=%d avail_pkts=%d\n",
+				 bpc, channels, hblank, pkts);
+
+			ret = try_modeset(data, mode);
+
+			if (ret) {
+				igt_info("    modeset: REJECTED\n");
+				cleanup_connector(data);
+				continue;
+			}
+
+			/* Allow ELD to propagate */
+			usleep(200 * 1000);
+
+			audio = audio_is_active();
+			eld_rates = audio ? get_eld_rates() : 0;
+
+			igt_info("    modeset: OK\n");
+			igt_info("    audio:   %s\n", audio ? "active" : "inactive");
+
+			igt_assert_f(!(pkts == 0 && audio),
+				     "Audio active with 0 available packets\n");
+			igt_assert_f(!(pkts > 0 && !audio),
+				     "Audio inactive with %d available packets\n",
+				     pkts);
+
+			if (audio) {
+				log_eld_rates(ALL_SAMPLE_RATES, eld_rates);
+				assert_per_rate(mode, channels, pkts,
+						eld_rates);
+			}
+
+			log_per_rate_analysis(mode, bpc, channels,
+					      pkts, eld_rates);
+
+			cleanup_connector(data);
+		}
+	}
+
+	igt_info("\n=== End matrix ===\n");
+}
+
+static void test_audio_bw_supported(data_t *data)
+{
+	test_audio_bw_matrix(data, &mode_1080p_hblank160);
+}
+
+static void test_audio_bw_pruned(data_t *data)
+{
+	test_audio_bw_matrix(data, &mode_1080p_hblank80);
+}
+
+static void test_suspend_audio_recovery(data_t *data,
+					enum igt_suspend_state state)
+{
+	const struct edid *edid;
+	bool audio_before, audio_after;
+	unsigned int rates_before, rates_after;
+	char before_str[128], after_str[128];
+	int ret;
+
+	edid = build_edid(12, 2);
+	force_edid_and_connector(data, edid);
+
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 12);
+
+	ret = try_modeset(data, &mode_1080p_hblank80);
+	igt_require(ret == 0);
+
+	usleep(200 * 1000);
+
+	audio_before = audio_is_active();
+	rates_before = audio_before ? get_eld_rates() : 0;
+	rates_to_str(rates_before, before_str, sizeof(before_str));
+	igt_info("Before suspend: audio=%d rates=%s\n",
+		 audio_before, before_str);
+
+	igt_system_suspend_autoresume(state, SUSPEND_TEST_NONE);
+
+	usleep(200 * 1000);
+
+	audio_after = audio_is_active();
+	rates_after = audio_after ? get_eld_rates() : 0;
+	rates_to_str(rates_after, after_str, sizeof(after_str));
+	igt_info("After suspend:  audio=%d rates=%s\n",
+		 audio_after, after_str);
+
+	igt_assert_eq(audio_before, audio_after);
+	if (audio_before)
+		igt_assert_eq(rates_before, rates_after);
+
+	cleanup_connector(data);
+}
+
+static void test_runtime_suspend_audio(data_t *data)
+{
+	const struct edid *edid;
+	bool audio_before, audio_after;
+	unsigned int rates_before, rates_after;
+	char before_str[128], after_str[128];
+	int ret;
+
+	edid = build_edid(12, 2);
+	force_edid_and_connector(data, edid);
+
+	igt_output_set_prop_value(data->output, IGT_CONNECTOR_MAX_BPC, 12);
+
+	ret = try_modeset(data, &mode_1080p_hblank80);
+	igt_require(ret == 0);
+
+	usleep(200 * 1000);
+
+	audio_before = audio_is_active();
+	rates_before = audio_before ? get_eld_rates() : 0;
+	rates_to_str(rates_before, before_str, sizeof(before_str));
+	igt_info("Before runtime suspend: audio=%d rates=%s\n",
+		 audio_before, before_str);
+
+	kmstest_set_connector_dpms(data->drm_fd,
+				   data->output->config.connector,
+				   DRM_MODE_DPMS_OFF);
+	usleep(500 * 1000);
+	kmstest_set_connector_dpms(data->drm_fd,
+				   data->output->config.connector,
+				   DRM_MODE_DPMS_ON);
+	usleep(500 * 1000);
+
+	audio_after = audio_is_active();
+	rates_after = audio_after ? get_eld_rates() : 0;
+	rates_to_str(rates_after, after_str, sizeof(after_str));
+	igt_info("After runtime suspend:  audio=%d rates=%s\n",
+		 audio_after, after_str);
+
+	igt_assert_eq(audio_before, audio_after);
+	if (audio_before)
+		igt_assert_eq(rates_before, rates_after);
+
+	cleanup_connector(data);
+}
+
+int igt_main()
+{
+	data_t data = {};
+
+	igt_fixture() {
+		data.drm_fd = drm_open_driver_master(DRIVER_INTEL | DRIVER_XE);
+		igt_require(is_intel_device(data.drm_fd));
+		kmstest_set_vt_graphics_mode();
+		igt_display_require(&data.display, data.drm_fd);
+
+		data.output = find_hdmi_output(&data.display);
+		igt_require_f(data.output, "No HDMI connector found\n");
+
+		data.crtc = igt_first_crtc(&data.display);
+		igt_require_f(data.crtc, "No usable CRTC found\n");
+	}
+
+	igt_describe("Baseline: hblank=160, audio should be fully supported "
+		     "for all BPC and channel configurations.");
+	igt_subtest("audio-bw-supported")
+		test_audio_bw_supported(&data);
+
+	igt_describe("Constrained: hblank=80 (CVT RB2), audio may be pruned "
+		     "or disabled depending on BPC.");
+	igt_subtest("audio-bw-pruned")
+		test_audio_bw_pruned(&data);
+
+	igt_describe("Validate audio recovery after S3 suspend with "
+		     "constrained hblank.");
+	igt_subtest("suspend-s3-audio-recovery")
+		test_suspend_audio_recovery(&data, SUSPEND_STATE_MEM);
+
+	igt_describe("Validate audio recovery after S4 hibernate with "
+		     "constrained hblank.");
+	igt_subtest("suspend-s4-audio-recovery")
+		test_suspend_audio_recovery(&data, SUSPEND_STATE_DISK);
+
+	igt_describe("Validate audio recovery after runtime suspend with "
+		     "constrained hblank.");
+	igt_subtest("runtime-suspend-audio-recovery")
+		test_runtime_suspend_audio(&data);
+
+	igt_fixture() {
+		igt_display_fini(&data.display);
+		drm_close_driver(data.drm_fd);
+	}
+}
diff --git a/tests/meson.build b/tests/meson.build
index a62f447df..facb7ab5d 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -259,6 +259,7 @@ intel_kms_progs = [
 	'kms_fbc_dirty_rect',
 	'kms_fbcon_fbt',
 	'kms_fence_pin_leak',
+	'kms_hdmi_audio_bw',
 	'kms_flip_scaled_crc',
 	'kms_flip_tiling',
 	'kms_frontbuffer_tracking',
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-08-31  9:33 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-14 12:16 [PATCH i-g-t 2/2] tests/intel: Add mode-rejected-max-dotclock subtest to kms_cdclk Swati Sharma
  -- strict thread matches above, loose matches on Subject: below --
2026-08-19  7:38 [PATCH i-g-t 1/2] tests/intel: Add kms_hdmi_audio_bw test Swati Sharma
2026-08-19  7:38 ` [PATCH i-g-t 2/2] tests/intel: Add mode-rejected-max-dotclock subtest to kms_cdclk Swati Sharma
2026-08-31  9:32   ` Borah, Chaitanya Kumar

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox