[PATCH i-g-t 1/2] tests/intel: Add kms_hdmi_audio_bw test

Swati Sharma <[email protected]>
Newsgroups org.freedesktop.lists.igt-dev
Message-ID <[email protected]>
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 <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.