diff mbox series

[09/12] firmware: cs_dsp: Add KUnit testing of control read/write

Message ID 20241212143725.1381013-10-rf@opensource.cirrus.com (mailing list archive)
State Accepted
Commit fe54fd5474f746f07f2b587f2f5e3311bf611970
Headers show
Series firmware: cirrus: Add KUnit tests for cs_dsp | expand

Commit Message

Richard Fitzgerald Dec. 12, 2024, 2:37 p.m. UTC
Add KUnit test cases for control read/write.

Tests cases cover general reading and writing of controls:
1) Read/write at offset position in control.
2) Read/write of various lengths less than length of the control.
3) Rejecting illegal arguments.

The test cases are run for ADSP2 with 16-bit registers, ADSP2
with 32-bit registers and Halo Core with 32-bit registers. The
ADSP2 cases are further divided into runs for V1 and V2 format
WMFW files, because there are differences in how V1 and V2
defines controls.

The obsolete V0 format does not have controls, so no testing of
that format is needed.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 drivers/firmware/cirrus/test/Makefile         |    1 +
 .../cirrus/test/cs_dsp_test_control_rw.c      | 2669 +++++++++++++++++
 2 files changed, 2670 insertions(+)
 create mode 100644 drivers/firmware/cirrus/test/cs_dsp_test_control_rw.c
diff mbox series

Patch

diff --git a/drivers/firmware/cirrus/test/Makefile b/drivers/firmware/cirrus/test/Makefile
index 5e6328097b2b..998f67df6dc6 100644
--- a/drivers/firmware/cirrus/test/Makefile
+++ b/drivers/firmware/cirrus/test/Makefile
@@ -12,6 +12,7 @@  cs_dsp_test-objs :=	\
 		cs_dsp_test_bin.o \
 		cs_dsp_test_control_parse.o \
 		cs_dsp_test_control_cache.o \
+		cs_dsp_test_control_rw.o \
 		cs_dsp_test_wmfw.o \
 		cs_dsp_tests.o
 
diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_control_rw.c b/drivers/firmware/cirrus/test/cs_dsp_test_control_rw.c
new file mode 100644
index 000000000000..bda00a95d4f9
--- /dev/null
+++ b/drivers/firmware/cirrus/test/cs_dsp_test_control_rw.c
@@ -0,0 +1,2669 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// KUnit tests for cs_dsp.
+//
+// Copyright (C) 2024 Cirrus Logic, Inc. and
+//                    Cirrus Logic International Semiconductor Ltd.
+
+#include <kunit/device.h>
+#include <kunit/resource.h>
+#include <kunit/test.h>
+#include <linux/build_bug.h>
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/firmware/cirrus/cs_dsp_test_utils.h>
+#include <linux/firmware/cirrus/wmfw.h>
+#include <linux/list.h>
+#include <linux/random.h>
+#include <linux/regmap.h>
+
+KUNIT_DEFINE_ACTION_WRAPPER(_put_device_wrapper, put_device, struct device *);
+KUNIT_DEFINE_ACTION_WRAPPER(_cs_dsp_stop_wrapper, cs_dsp_stop, struct cs_dsp *);
+KUNIT_DEFINE_ACTION_WRAPPER(_cs_dsp_remove_wrapper, cs_dsp_remove, struct cs_dsp *);
+
+struct cs_dsp_test_local {
+	struct cs_dsp_mock_xm_header *xm_header;
+	struct cs_dsp_mock_wmfw_builder *wmfw_builder;
+	int wmfw_version;
+};
+
+struct cs_dsp_ctl_rw_test_param {
+	int mem_type;
+	int alg_id;
+	unsigned int offs_words;
+	unsigned int len_bytes;
+	u16 ctl_type;
+	u16 flags;
+};
+
+static const struct cs_dsp_mock_alg_def cs_dsp_ctl_rw_test_algs[] = {
+	{
+		.id = 0xfafa,
+		.ver = 0x100000,
+		.xm_base_words = 60,
+		.xm_size_words = 1000,
+		.ym_base_words = 0,
+		.ym_size_words = 1000,
+		.zm_base_words = 0,
+		.zm_size_words = 1000,
+	},
+	{
+		.id = 0xb,
+		.ver = 0x100001,
+		.xm_base_words = 1060,
+		.xm_size_words = 1000,
+		.ym_base_words = 1000,
+		.ym_size_words = 1000,
+		.zm_base_words = 1000,
+		.zm_size_words = 1000,
+	},
+	{
+		.id = 0x9f1234,
+		.ver = 0x100500,
+		.xm_base_words = 2060,
+		.xm_size_words = 32,
+		.ym_base_words = 2000,
+		.ym_size_words = 32,
+		.zm_base_words = 2000,
+		.zm_size_words = 32,
+	},
+	{
+		.id = 0xff00ff,
+		.ver = 0x300113,
+		.xm_base_words = 2100,
+		.xm_size_words = 32,
+		.ym_base_words = 2032,
+		.ym_size_words = 32,
+		.zm_base_words = 2032,
+		.zm_size_words = 32,
+	},
+};
+
+static const struct cs_dsp_mock_coeff_def mock_coeff_template = {
+	.shortname = "Dummy Coeff",
+	.type = WMFW_CTL_TYPE_BYTES,
+	.mem_type = WMFW_ADSP2_YM,
+	.flags = WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	.length_bytes = 4,
+};
+
+static int _find_alg_entry(struct kunit *test, unsigned int alg_id)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs_dsp_ctl_rw_test_algs); ++i) {
+		if (cs_dsp_ctl_rw_test_algs[i].id == alg_id)
+			break;
+	}
+
+	KUNIT_ASSERT_LT(test, i, ARRAY_SIZE(cs_dsp_ctl_rw_test_algs));
+
+	return i;
+}
+
+static int _get_alg_mem_base_words(struct kunit *test, int alg_index, int mem_type)
+{
+	switch (mem_type) {
+	case WMFW_ADSP2_XM:
+		return cs_dsp_ctl_rw_test_algs[alg_index].xm_base_words;
+	case WMFW_ADSP2_YM:
+		return cs_dsp_ctl_rw_test_algs[alg_index].ym_base_words;
+	case WMFW_ADSP2_ZM:
+		return cs_dsp_ctl_rw_test_algs[alg_index].zm_base_words;
+	default:
+		KUNIT_FAIL(test, "Bug in test: illegal memory type %d\n", mem_type);
+		return 0;
+	}
+}
+
+static struct cs_dsp_mock_wmfw_builder *_create_dummy_wmfw(struct kunit *test)
+{
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp_mock_wmfw_builder *builder;
+
+	builder = cs_dsp_mock_wmfw_init(priv, local->wmfw_version);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, builder);
+
+	/* Init an XM header */
+	cs_dsp_mock_wmfw_add_data_block(builder,
+					WMFW_ADSP2_XM, 0,
+					local->xm_header->blob_data,
+					local->xm_header->blob_size_bytes);
+
+	return builder;
+}
+
+/*
+ * Write to a control while the firmware is running.
+ * This should write to the underlying registers.
+ */
+static void cs_dsp_ctl_write_running(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback;
+
+	reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	memset(reg_vals, 0, param->len_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/*
+	 * Write new data to the control, it should be written to the registers
+	 * and cs_dsp_coeff_lock_and_write_ctrl() should return 1 to indicate
+	 * that the control content changed.
+	 */
+	get_random_bytes(reg_vals, param->len_bytes);
+	KUNIT_EXPECT_EQ(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			1);
+	KUNIT_ASSERT_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0);
+	KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, param->len_bytes);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+/*
+ * Read from a volatile control while the firmware is running.
+ * This should return the current state of the underlying registers.
+ */
+static void cs_dsp_ctl_read_volatile_running(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback;
+
+	reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	memset(reg_vals, 0, param->len_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Read the control, it should return the current register content */
+	KUNIT_EXPECT_EQ(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes),
+			0);
+	KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, param->len_bytes);
+
+	/*
+	 * Change the register content and read the control, it should return
+	 * the new register content
+	 */
+	get_random_bytes(reg_vals, param->len_bytes);
+	KUNIT_ASSERT_EQ(test, regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes), 0);
+	KUNIT_EXPECT_EQ(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes),
+			0);
+	KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, param->len_bytes);
+}
+
+/*
+ * Read from a volatile control before the firmware is started.
+ * This should return an error.
+ */
+static void cs_dsp_ctl_read_volatile_not_started(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Read the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+}
+
+/*
+ * Read from a volatile control after the firmware has stopped.
+ * This should return an error.
+ */
+static void cs_dsp_ctl_read_volatile_stopped(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Start and stop the firmware */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	cs_dsp_stop(dsp);
+
+	/* Read the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+}
+
+/*
+ * Read from a volatile control after the DSP has been powered down.
+ * This should return an error.
+ */
+static void cs_dsp_ctl_read_volatile_stopped_powered_down(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Start and stop the firmware then power down */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	cs_dsp_stop(dsp);
+	cs_dsp_power_down(dsp);
+
+	/* Read the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+}
+
+/*
+ * Read from a volatile control when a different firmware is currently
+ * loaded into the DSP.
+ * Should return an error.
+ */
+static void cs_dsp_ctl_read_volatile_not_current_loaded_fw(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	struct cs_dsp_mock_wmfw_builder *builder2 = _create_dummy_wmfw(test);
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some DSP data to be read into the control cache */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	/* Power-up DSP */
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Power-down DSP then power-up with a different firmware */
+	cs_dsp_power_down(dsp);
+	wmfw = cs_dsp_mock_wmfw_get_firmware(builder2);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw2", NULL, NULL, "mbc.vss"), 0);
+
+	/* Read the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+}
+
+/*
+ * Read from a volatile control when a different firmware is currently
+ * running.
+ * Should return an error.
+ */
+static void cs_dsp_ctl_read_volatile_not_current_running_fw(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	struct cs_dsp_mock_wmfw_builder *builder2 = _create_dummy_wmfw(test);
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some DSP data to be read into the control cache */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	/* Power-up DSP */
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Power-down DSP then power-up with a different firmware */
+	cs_dsp_power_down(dsp);
+	wmfw = cs_dsp_mock_wmfw_get_firmware(builder2);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw2", NULL, NULL, "mbc.vss"), 0);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Read the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+}
+
+/*
+ * Write to a volatile control before the firmware is started.
+ * This should return an error.
+ */
+static void cs_dsp_ctl_write_volatile_not_started(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+
+	/* Write the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+
+	/* Should not have been any writes to registers */
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+/*
+ * Write to a volatile control after the firmware has stopped.
+ * This should return an error.
+ */
+static void cs_dsp_ctl_write_volatile_stopped(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Start and stop the firmware */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	cs_dsp_stop(dsp);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+
+	/* Write the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+
+	/* Should not have been any writes to registers */
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+/*
+ * Write to a volatile control after the DSP has been powered down.
+ * This should return an error.
+ */
+static void cs_dsp_ctl_write_volatile_stopped_powered_down(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Start and stop the firmware then power down */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	cs_dsp_stop(dsp);
+	cs_dsp_power_down(dsp);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+
+	/* Write the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+
+	/* Should not have been any writes to registers */
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+/*
+ * Write to a volatile control when a different firmware is currently
+ * loaded into the DSP.
+ * Should return an error.
+ */
+static void cs_dsp_ctl_write_volatile_not_current_loaded_fw(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	struct cs_dsp_mock_wmfw_builder *builder2 = _create_dummy_wmfw(test);
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some DSP data to be read into the control cache */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	/* Power-up DSP */
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Power-down DSP then power-up with a different firmware */
+	cs_dsp_power_down(dsp);
+	wmfw = cs_dsp_mock_wmfw_get_firmware(builder2);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw2", NULL, NULL, "mbc.vss"), 0);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+
+	/* Write the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+
+	/* Should not have been any writes to registers */
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+/*
+ * Write to a volatile control when a different firmware is currently
+ * running.
+ * Should return an error.
+ */
+static void cs_dsp_ctl_write_volatile_not_current_running_fw(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	struct cs_dsp_mock_wmfw_builder *builder2 = _create_dummy_wmfw(test);
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	reg_vals = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some DSP data to be read into the control cache */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, param->len_bytes);
+
+	/* Create control pointing to this data */
+	def.flags = param->flags | WMFW_CTL_FLAG_VOLATILE;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	/* Power-up DSP */
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	/* Power-down DSP then power-up with a different firmware */
+	cs_dsp_power_down(dsp);
+	wmfw = cs_dsp_mock_wmfw_get_firmware(builder2);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw2", NULL, NULL, "mbc.vss"), 0);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+
+	/* Write the control, it should return an error */
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, param->len_bytes),
+			0);
+
+	/* Should not have been any writes to registers */
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+/*
+ * Read from an offset into the control data. Should return only the
+ * portion of data from the offset position.
+ */
+static void cs_dsp_ctl_read_with_seek(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback;
+	unsigned int seek_words;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = 48;
+
+	reg_vals = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	get_random_bytes(reg_vals, def.length_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	for (seek_words = 1; seek_words < (def.length_bytes / sizeof(u32)); seek_words++) {
+		unsigned int len_bytes = def.length_bytes - (seek_words * sizeof(u32));
+
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, seek_words,
+								readback, len_bytes),
+				0);
+		KUNIT_EXPECT_MEMEQ(test, readback, &reg_vals[seek_words], len_bytes);
+	}
+}
+
+/*
+ * Read from an offset into the control cache. Should return only the
+ * portion of data from the offset position.
+ * Same as cs_dsp_ctl_read_with_seek() except the control is cached
+ * and the firmware is not running.
+ */
+static void cs_dsp_ctl_read_cache_with_seek(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback;
+	unsigned int seek_words;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = 48;
+
+	reg_vals = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	get_random_bytes(reg_vals, def.length_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start and stop the firmware so the read will come from the cache */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	cs_dsp_stop(dsp);
+
+	for (seek_words = 1; seek_words < (def.length_bytes / sizeof(u32)); seek_words++) {
+		unsigned int len_bytes = def.length_bytes - (seek_words * sizeof(u32));
+
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, seek_words,
+								readback, len_bytes),
+				0);
+		KUNIT_EXPECT_MEMEQ(test, readback, &reg_vals[seek_words], len_bytes);
+	}
+}
+
+/*
+ * Read less than the full length of data from a control. Should return
+ * only the requested number of bytes.
+ */
+static void cs_dsp_ctl_read_truncated(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback;
+	unsigned int len_bytes;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = 48;
+
+	reg_vals = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	get_random_bytes(reg_vals, def.length_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Reads are only allowed to be a multiple of the DSP word length */
+	for (len_bytes = sizeof(u32); len_bytes < def.length_bytes; len_bytes += sizeof(u32)) {
+		memset(readback, 0, def.length_bytes);
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, len_bytes),
+				0);
+		KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, len_bytes);
+		KUNIT_EXPECT_MEMNEQ(test,
+				    (u8 *)readback + len_bytes,
+				    (u8 *)reg_vals + len_bytes,
+				    def.length_bytes - len_bytes);
+	}
+}
+
+/*
+ * Read less than the full length of data from a cached control.
+ * Should return only the requested number of bytes.
+ * Same as cs_dsp_ctl_read_truncated() except the control is cached
+ * and the firmware is not running.
+ */
+static void cs_dsp_ctl_read_cache_truncated(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback;
+	unsigned int len_bytes;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = 48;
+
+	reg_vals = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	get_random_bytes(reg_vals, def.length_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start and stop the firmware so the read will come from the cache */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	cs_dsp_stop(dsp);
+
+	/* Reads are only allowed to be a multiple of the DSP word length */
+	for (len_bytes = sizeof(u32); len_bytes < def.length_bytes; len_bytes += sizeof(u32)) {
+		memset(readback, 0, def.length_bytes);
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, len_bytes),
+				0);
+		KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, len_bytes);
+		KUNIT_EXPECT_MEMNEQ(test,
+				    (u8 *)readback + len_bytes,
+				    (u8 *)reg_vals + len_bytes,
+				    def.length_bytes - len_bytes);
+	}
+}
+
+/*
+ * Write to an offset into the control data. Should only change the
+ * portion of data from the offset position.
+ */
+static void cs_dsp_ctl_write_with_seek(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback, *new_data;
+	unsigned int seek_words;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = 48;
+
+	reg_vals = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	new_data = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_data);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	get_random_bytes(reg_vals, def.length_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	for (seek_words = 1; seek_words < (def.length_bytes / sizeof(u32)); seek_words++) {
+		unsigned int len_bytes = def.length_bytes - (seek_words * sizeof(u32));
+
+		/* Reset the register values to the test data */
+		regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+		get_random_bytes(new_data, def.length_bytes);
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, seek_words,
+								 new_data, len_bytes),
+				1);
+		KUNIT_ASSERT_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, def.length_bytes),
+				0);
+		/* Initial portion of readback should be unchanged */
+		KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, seek_words * sizeof(u32));
+		KUNIT_EXPECT_MEMEQ(test, &readback[seek_words], new_data, len_bytes);
+	}
+}
+
+/*
+ * Write to an offset into the control cache. Should only change the
+ * portion of data from the offset position.
+ * Same as cs_dsp_ctl_write_with_seek() except the control is cached
+ * and the firmware is not running.
+ */
+static void cs_dsp_ctl_write_cache_with_seek(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback, *new_data;
+	unsigned int seek_words;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = 48;
+
+	reg_vals = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	new_data = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_data);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	get_random_bytes(reg_vals, def.length_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start and stop the firmware so the read will come from the cache */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	cs_dsp_stop(dsp);
+
+	for (seek_words = 1; seek_words < (def.length_bytes / sizeof(u32)); seek_words++) {
+		unsigned int len_bytes = def.length_bytes - (seek_words * sizeof(u32));
+
+		/* Reset the cache to the test data */
+		KUNIT_EXPECT_GE(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals,
+								 def.length_bytes),
+				0);
+
+		get_random_bytes(new_data, def.length_bytes);
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, seek_words,
+								 new_data, len_bytes),
+				1);
+
+		memset(readback, 0, def.length_bytes);
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback,
+								def.length_bytes),
+				0);
+		/* Initial portion of readback should be unchanged */
+		KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, seek_words * sizeof(u32));
+		KUNIT_EXPECT_MEMEQ(test, &readback[seek_words], new_data, len_bytes);
+	}
+}
+
+/*
+ * Write less than the full length of data to a control. Should only
+ * change the requested number of bytes.
+ */
+static void cs_dsp_ctl_write_truncated(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback, *new_data;
+	unsigned int len_bytes;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = 48;
+
+	reg_vals = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	new_data = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_data);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	get_random_bytes(reg_vals, def.length_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Writes are only allowed to be a multiple of the DSP word length */
+	for (len_bytes = sizeof(u32); len_bytes < def.length_bytes; len_bytes += sizeof(u32)) {
+		/* Reset the register values to the test data */
+		regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+		get_random_bytes(new_data, def.length_bytes);
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, new_data, len_bytes),
+				1);
+
+		memset(readback, 0, def.length_bytes);
+		KUNIT_ASSERT_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, def.length_bytes),
+				0);
+		KUNIT_EXPECT_MEMEQ(test, readback, new_data, len_bytes);
+		KUNIT_EXPECT_MEMEQ(test,
+				   (u8 *)readback + len_bytes,
+				   (u8 *)reg_vals + len_bytes,
+				   def.length_bytes - len_bytes);
+	}
+}
+
+/*
+ * Write less than the full length of data to a cached control.
+ * Should only change the requested number of bytes.
+ * Same as cs_dsp_ctl_write_truncated() except the control is cached
+ * and the firmware is not running.
+ */
+static void cs_dsp_ctl_write_cache_truncated(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals, *readback, *new_data;
+	unsigned int len_bytes;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = 48;
+
+	reg_vals = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	readback = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	new_data = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_data);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	get_random_bytes(reg_vals, def.length_bytes);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start and stop the firmware so the read will come from the cache */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	cs_dsp_stop(dsp);
+
+	/* Writes are only allowed to be a multiple of the DSP word length */
+	for (len_bytes = sizeof(u32); len_bytes < def.length_bytes; len_bytes += sizeof(u32)) {
+		/* Reset the cache to the test data */
+		KUNIT_EXPECT_GE(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals,
+								 def.length_bytes),
+				0);
+
+		get_random_bytes(new_data, def.length_bytes);
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, new_data, len_bytes),
+				1);
+
+		memset(readback, 0, def.length_bytes);
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback,
+								def.length_bytes),
+				0);
+		KUNIT_EXPECT_MEMEQ(test, readback, new_data, len_bytes);
+		KUNIT_EXPECT_MEMEQ(test,
+				   (u8 *)readback + len_bytes,
+				   (u8 *)reg_vals + len_bytes,
+				   def.length_bytes - len_bytes);
+	}
+}
+
+/*
+ * Read from an offset that is beyond the end of the control data.
+ * Should return an error.
+ */
+static void cs_dsp_ctl_read_with_seek_oob(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+	unsigned int seek_words;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	reg_vals = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	seek_words = def.length_bytes / sizeof(u32);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, seek_words,
+							reg_vals, def.length_bytes),
+			0);
+
+	if (!(def.flags & WMFW_CTL_FLAG_VOLATILE)) {
+		/* Stop firmware and repeat the read from the cache */
+		kunit_release_action(test, _cs_dsp_stop_wrapper, dsp);
+		KUNIT_ASSERT_FALSE(test, dsp->running);
+
+		KUNIT_EXPECT_LT(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, seek_words,
+								reg_vals, def.length_bytes),
+				0);
+	}
+}
+
+/*
+ * Read more data than the length of the control data.
+ * Should return an error.
+ */
+static void cs_dsp_ctl_read_with_length_overflow(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	reg_vals = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, reg_vals, def.length_bytes + 1),
+			0);
+
+	if (!(def.flags & WMFW_CTL_FLAG_VOLATILE)) {
+		/* Stop firmware and repeat the read from the cache */
+		kunit_release_action(test, _cs_dsp_stop_wrapper, dsp);
+		KUNIT_ASSERT_FALSE(test, dsp->running);
+
+		KUNIT_EXPECT_LT(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, reg_vals,
+								def.length_bytes + 1),
+				0);
+	}
+}
+
+/*
+ * Read with a seek and length that ends beyond the end of control data.
+ * Should return an error.
+ */
+static void cs_dsp_ctl_read_with_seek_and_length_oob(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	reg_vals = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/*
+	 * Read full control length but at a start offset of 1 so that
+	 * offset + length exceeds the length of the control.
+	 */
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 1, reg_vals, def.length_bytes),
+			0);
+
+	if (!(def.flags & WMFW_CTL_FLAG_VOLATILE)) {
+		/* Stop firmware and repeat the read from the cache */
+		kunit_release_action(test, _cs_dsp_stop_wrapper, dsp);
+		KUNIT_ASSERT_FALSE(test, dsp->running);
+
+		KUNIT_EXPECT_LT(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, 1, reg_vals,
+								def.length_bytes),
+				0);
+	}
+}
+
+/*
+ * Write to an offset that is beyond the end of the control data.
+ * Should return an error without touching any registers.
+ */
+static void cs_dsp_ctl_write_with_seek_oob(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+	unsigned int seek_words;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	reg_vals = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+
+	get_random_bytes(reg_vals, def.length_bytes);
+	seek_words = def.length_bytes / sizeof(u32);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, seek_words,
+							 reg_vals, def.length_bytes),
+			0);
+
+	if (!(def.flags & WMFW_CTL_FLAG_VOLATILE)) {
+		/* Stop firmware and repeat the write to the cache */
+		kunit_release_action(test, _cs_dsp_stop_wrapper, dsp);
+		KUNIT_ASSERT_FALSE(test, dsp->running);
+
+		get_random_bytes(reg_vals, def.length_bytes);
+		KUNIT_EXPECT_LT(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, seek_words,
+								 reg_vals, def.length_bytes),
+				0);
+	}
+
+	/* Check that it didn't write any registers */
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+/*
+ * Write more data than the length of the control data.
+ * Should return an error.
+ */
+static void cs_dsp_ctl_write_with_length_overflow(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	reg_vals = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+
+	get_random_bytes(reg_vals, def.length_bytes);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, def.length_bytes + 1),
+			0);
+
+	if (!(def.flags & WMFW_CTL_FLAG_VOLATILE)) {
+		/* Stop firmware and repeat the write to the cache */
+		kunit_release_action(test, _cs_dsp_stop_wrapper, dsp);
+		KUNIT_ASSERT_FALSE(test, dsp->running);
+
+		get_random_bytes(reg_vals, def.length_bytes);
+		KUNIT_EXPECT_LT(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals,
+								 def.length_bytes + 1),
+				0);
+	}
+
+	/* Check that it didn't write any registers */
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+/*
+ * Write with a seek and length that ends beyond the end of control data.
+ * Should return an error.
+ */
+static void cs_dsp_ctl_write_with_seek_and_length_oob(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	reg_vals = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+
+	/*
+	 * Write full control length but at a start offset of 1 so that
+	 * offset + length exceeeds the length of the control.
+	 */
+	get_random_bytes(reg_vals, def.length_bytes);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 1, reg_vals, def.length_bytes),
+			0);
+
+	if (!(def.flags & WMFW_CTL_FLAG_VOLATILE)) {
+		/* Stop firmware and repeat the write to the cache */
+		kunit_release_action(test, _cs_dsp_stop_wrapper, dsp);
+		KUNIT_ASSERT_FALSE(test, dsp->running);
+
+		get_random_bytes(reg_vals, def.length_bytes);
+		KUNIT_EXPECT_LT(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, 1, reg_vals,
+								 def.length_bytes),
+				0);
+	}
+
+	/* Check that it didn't write any registers */
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+/*
+ * Read from a write-only control. This is legal because controls can
+ * always be read. Write-only only indicates that it is not useful to
+ * populate the cache from the DSP memory.
+ */
+static void cs_dsp_ctl_read_from_writeonly(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *ctl_vals, *readback;
+
+	/* Sanity check parameters */
+	KUNIT_ASSERT_TRUE(test, param->flags & WMFW_CTL_FLAG_WRITEABLE);
+	KUNIT_ASSERT_FALSE(test, param->flags & WMFW_CTL_FLAG_READABLE);
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	ctl_vals = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctl_vals);
+
+	readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Write some test data to the control */
+	get_random_bytes(ctl_vals, def.length_bytes);
+	KUNIT_EXPECT_EQ(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, ctl_vals, def.length_bytes),
+			1);
+
+	/* Read back the data */
+	KUNIT_EXPECT_EQ(test,
+			cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, def.length_bytes),
+			0);
+	KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, def.length_bytes);
+
+	if (!(def.flags & WMFW_CTL_FLAG_VOLATILE)) {
+		/* Stop firmware and repeat the read from the cache */
+		kunit_release_action(test, _cs_dsp_stop_wrapper, dsp);
+		KUNIT_ASSERT_FALSE(test, dsp->running);
+
+		memset(readback, 0, def.length_bytes);
+		KUNIT_EXPECT_EQ(test,
+				cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback,
+								def.length_bytes),
+				0);
+		KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, def.length_bytes);
+	}
+}
+
+/*
+ * Write to a read-only control.
+ * This should return an error without writing registers.
+ */
+static void cs_dsp_ctl_write_to_readonly(struct kunit *test)
+{
+	const struct cs_dsp_ctl_rw_test_param *param = test->param_value;
+	struct cs_dsp_test *priv = test->priv;
+	struct cs_dsp_test_local *local = priv->local;
+	struct cs_dsp *dsp = priv->dsp;
+	struct cs_dsp_mock_coeff_def def = mock_coeff_template;
+	int alg_idx = _find_alg_entry(test, param->alg_id);
+	unsigned int reg, alg_base_words;
+	struct cs_dsp_coeff_ctl *ctl;
+	struct firmware *wmfw;
+	u32 *reg_vals;
+
+	/* Sanity check parameters */
+	KUNIT_ASSERT_FALSE(test, param->flags & WMFW_CTL_FLAG_WRITEABLE);
+	KUNIT_ASSERT_TRUE(test, param->flags & WMFW_CTL_FLAG_READABLE);
+
+	def.flags = param->flags;
+	def.mem_type = param->mem_type;
+	def.offset_dsp_words = param->offs_words;
+	def.length_bytes = param->len_bytes;
+
+	reg_vals = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals);
+
+	/* Create some initial register content */
+	alg_base_words = _get_alg_mem_base_words(test, alg_idx, param->mem_type);
+	reg = cs_dsp_mock_base_addr_for_mem(priv, param->mem_type);
+	reg += (alg_base_words + param->offs_words) *
+		cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv);
+	regmap_raw_write(dsp->regmap, reg, reg_vals, def.length_bytes);
+
+	/* Create control pointing to this data */
+	cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder,
+					      cs_dsp_ctl_rw_test_algs[alg_idx].id,
+					      "dummyalg", NULL);
+	cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def);
+	cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder);
+
+	wmfw = cs_dsp_mock_wmfw_get_firmware(priv->local->wmfw_builder);
+	KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0);
+
+	ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list);
+	KUNIT_ASSERT_NOT_NULL(test, ctl);
+
+	/* Start the firmware and add an action to stop it during cleanup */
+	KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0);
+	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, _cs_dsp_stop_wrapper, dsp), 0);
+
+	/* Drop expected writes and the regmap cache should be clean */
+	cs_dsp_mock_xm_header_drop_from_regmap_cache(priv);
+	cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes);
+
+	get_random_bytes(reg_vals, def.length_bytes);
+	KUNIT_EXPECT_LT(test,
+			cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, def.length_bytes),
+			0);
+
+	if (!(def.flags & WMFW_CTL_FLAG_VOLATILE)) {
+		/* Stop firmware and repeat the write to the cache */
+		kunit_release_action(test, _cs_dsp_stop_wrapper, dsp);
+		KUNIT_ASSERT_FALSE(test, dsp->running);
+
+		get_random_bytes(reg_vals, def.length_bytes);
+		KUNIT_EXPECT_LT(test,
+				cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals,
+								 def.length_bytes),
+				0);
+	}
+
+	/* Check that it didn't write any registers */
+	KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true));
+}
+
+static int cs_dsp_ctl_rw_test_common_init(struct kunit *test, struct cs_dsp *dsp,
+					  int wmfw_version)
+{
+	struct cs_dsp_test *priv;
+	struct cs_dsp_test_local *local;
+	struct device *test_dev;
+	int ret;
+
+	priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	local = kunit_kzalloc(test, sizeof(struct cs_dsp_test_local), GFP_KERNEL);
+	if (!local)
+		return -ENOMEM;
+
+	priv->test = test;
+	priv->dsp = dsp;
+	test->priv = priv;
+	priv->local = local;
+	priv->local->wmfw_version = wmfw_version;
+
+	/* Create dummy struct device */
+	test_dev = kunit_device_register(test, "cs_dsp_test_drv");
+	if (IS_ERR(test_dev))
+		return PTR_ERR(test_dev);
+
+	dsp->dev = get_device(test_dev);
+	if (!dsp->dev)
+		return -ENODEV;
+
+	ret = kunit_add_action_or_reset(test, _put_device_wrapper, dsp->dev);
+	if (ret)
+		return ret;
+
+	dev_set_drvdata(dsp->dev, priv);
+
+	/* Allocate regmap */
+	ret = cs_dsp_mock_regmap_init(priv);
+	if (ret)
+		return ret;
+
+	/*
+	 * There must always be a XM header with at least 1 algorithm, so create
+	 * a dummy one that tests can use and extract it to a data blob.
+	 */
+	local->xm_header = cs_dsp_create_mock_xm_header(priv,
+							cs_dsp_ctl_rw_test_algs,
+							ARRAY_SIZE(cs_dsp_ctl_rw_test_algs));
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, local->xm_header);
+
+	/* Create wmfw builder */
+	local->wmfw_builder = _create_dummy_wmfw(test);
+
+	/* Init cs_dsp */
+	dsp->client_ops = kunit_kzalloc(test, sizeof(*dsp->client_ops), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dsp->client_ops);
+
+	switch (dsp->type) {
+	case WMFW_ADSP2:
+		ret = cs_dsp_adsp2_init(dsp);
+		break;
+	case WMFW_HALO:
+		ret = cs_dsp_halo_init(dsp);
+		break;
+	default:
+		KUNIT_FAIL(test, "Untested DSP type %d\n", dsp->type);
+		return -EINVAL;
+	}
+
+	if (ret)
+		return ret;
+
+	/* Automatically call cs_dsp_remove() when test case ends */
+	return kunit_add_action_or_reset(priv->test, _cs_dsp_remove_wrapper, dsp);
+}
+
+static int cs_dsp_ctl_rw_test_halo_init(struct kunit *test)
+{
+	struct cs_dsp *dsp;
+
+	/* Fill in cs_dsp and initialize */
+	dsp = kunit_kzalloc(test, sizeof(*dsp), GFP_KERNEL);
+	if (!dsp)
+		return -ENOMEM;
+
+	dsp->num = 1;
+	dsp->type = WMFW_HALO;
+	dsp->mem = cs_dsp_mock_halo_dsp1_regions;
+	dsp->num_mems = cs_dsp_mock_count_regions(cs_dsp_mock_halo_dsp1_region_sizes);
+	dsp->base = cs_dsp_mock_halo_core_base;
+	dsp->base_sysinfo = cs_dsp_mock_halo_sysinfo_base;
+
+	return cs_dsp_ctl_rw_test_common_init(test, dsp, 3);
+}
+
+static int cs_dsp_ctl_rw_test_adsp2_32bit_init(struct kunit *test, int wmfw_ver)
+{
+	struct cs_dsp *dsp;
+
+	/* Fill in cs_dsp and initialize */
+	dsp = kunit_kzalloc(test, sizeof(*dsp), GFP_KERNEL);
+	if (!dsp)
+		return -ENOMEM;
+
+	dsp->num = 1;
+	dsp->type = WMFW_ADSP2;
+	dsp->rev = 1;
+	dsp->mem = cs_dsp_mock_adsp2_32bit_dsp1_regions;
+	dsp->num_mems = cs_dsp_mock_count_regions(cs_dsp_mock_adsp2_32bit_dsp1_region_sizes);
+	dsp->base = cs_dsp_mock_adsp2_32bit_sysbase;
+
+	return cs_dsp_ctl_rw_test_common_init(test, dsp, wmfw_ver);
+}
+
+static int cs_dsp_ctl_rw_test_adsp2_32bit_wmfw1_init(struct kunit *test)
+{
+	return cs_dsp_ctl_rw_test_adsp2_32bit_init(test, 1);
+}
+
+static int cs_dsp_ctl_rw_test_adsp2_32bit_wmfw2_init(struct kunit *test)
+{
+	return cs_dsp_ctl_rw_test_adsp2_32bit_init(test, 2);
+}
+
+static int cs_dsp_ctl_rw_test_adsp2_16bit_init(struct kunit *test, int wmfw_ver)
+{
+	struct cs_dsp *dsp;
+
+	/* Fill in cs_dsp and initialize */
+	dsp = kunit_kzalloc(test, sizeof(*dsp), GFP_KERNEL);
+	if (!dsp)
+		return -ENOMEM;
+
+	dsp->num = 1;
+	dsp->type = WMFW_ADSP2;
+	dsp->rev = 0;
+	dsp->mem = cs_dsp_mock_adsp2_16bit_dsp1_regions;
+	dsp->num_mems = cs_dsp_mock_count_regions(cs_dsp_mock_adsp2_16bit_dsp1_region_sizes);
+	dsp->base = cs_dsp_mock_adsp2_16bit_sysbase;
+
+	return cs_dsp_ctl_rw_test_common_init(test, dsp, wmfw_ver);
+}
+
+static int cs_dsp_ctl_rw_test_adsp2_16bit_wmfw1_init(struct kunit *test)
+{
+	return cs_dsp_ctl_rw_test_adsp2_16bit_init(test, 1);
+}
+
+static int cs_dsp_ctl_rw_test_adsp2_16bit_wmfw2_init(struct kunit *test)
+{
+	return cs_dsp_ctl_rw_test_adsp2_16bit_init(test, 2);
+}
+
+static void cs_dsp_ctl_all_param_desc(const struct cs_dsp_ctl_rw_test_param *param,
+				      char *desc)
+{
+	snprintf(desc, KUNIT_PARAM_DESC_SIZE, "alg:%#x %s@%u len:%u flags:%#x",
+		 param->alg_id, cs_dsp_mem_region_name(param->mem_type),
+		 param->offs_words, param->len_bytes, param->flags);
+}
+
+/* All parameters populated, with various lengths */
+static const struct cs_dsp_ctl_rw_test_param all_pop_varying_len_cases[] = {
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 8 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 12 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 16 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 48 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 100 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 512 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 1000 },
+};
+KUNIT_ARRAY_PARAM(all_pop_varying_len, all_pop_varying_len_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/* All parameters populated, with various offsets */
+static const struct cs_dsp_ctl_rw_test_param all_pop_varying_offset_cases[] = {
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 0,   .len_bytes = 4 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1,   .len_bytes = 4 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 2,   .len_bytes = 4 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 3,   .len_bytes = 4 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 8,   .len_bytes = 4 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 10,  .len_bytes = 4 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 128, .len_bytes = 4 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 180, .len_bytes = 4 },
+};
+KUNIT_ARRAY_PARAM(all_pop_varying_offset, all_pop_varying_offset_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/* All parameters populated, with various X and Y memory regions */
+static const struct cs_dsp_ctl_rw_test_param all_pop_varying_xy_cases[] = {
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_XM, .offs_words = 1, .len_bytes = 4 },
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4 },
+};
+KUNIT_ARRAY_PARAM(all_pop_varying_xy, all_pop_varying_xy_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/* All parameters populated, using ZM */
+static const struct cs_dsp_ctl_rw_test_param all_pop_z_cases[] = {
+	{ .alg_id = 0xfafa, .mem_type = WMFW_ADSP2_ZM, .offs_words = 1, .len_bytes = 4 },
+};
+KUNIT_ARRAY_PARAM(all_pop_z, all_pop_z_cases, cs_dsp_ctl_all_param_desc);
+
+/* All parameters populated, with various algorithm ids */
+static const struct cs_dsp_ctl_rw_test_param all_pop_varying_alg_cases[] = {
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4 },
+	{ .alg_id = 0xb,      .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4 },
+	{ .alg_id = 0x9f1234, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4 },
+	{ .alg_id = 0xff00ff, .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4 },
+};
+KUNIT_ARRAY_PARAM(all_pop_varying_alg, all_pop_varying_alg_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/*
+ * All parameters populated, with all combinations of flags for a
+ * readable control.
+ */
+static const struct cs_dsp_ctl_rw_test_param all_pop_readable_flags_cases[] = {
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = 0
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS |
+		   WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+};
+KUNIT_ARRAY_PARAM(all_pop_readable_flags,
+		  all_pop_readable_flags_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/*
+ * All parameters populated, with all combinations of flags for a
+ * read-only control
+ */
+static const struct cs_dsp_ctl_rw_test_param all_pop_readonly_flags_cases[] = {
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE,
+	},
+};
+KUNIT_ARRAY_PARAM(all_pop_readonly_flags,
+		  all_pop_readonly_flags_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/*
+ * All parameters populated, with all combinations of flags for a
+ * non-volatile readable control
+ */
+static const struct cs_dsp_ctl_rw_test_param all_pop_nonvol_readable_flags_cases[] = {
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = 0
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+};
+KUNIT_ARRAY_PARAM(all_pop_nonvol_readable_flags,
+		  all_pop_nonvol_readable_flags_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/*
+ * All parameters populated, with all combinations of flags for a
+ * writeable control
+ */
+static const struct cs_dsp_ctl_rw_test_param all_pop_writeable_flags_cases[] = {
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = 0
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS |
+		   WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+};
+KUNIT_ARRAY_PARAM(all_pop_writeable_flags,
+		  all_pop_writeable_flags_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/*
+ * All parameters populated, with all combinations of flags for a
+ * write-only control
+ */
+static const struct cs_dsp_ctl_rw_test_param all_pop_writeonly_flags_cases[] = {
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_WRITEABLE,
+	},
+};
+KUNIT_ARRAY_PARAM(all_pop_writeonly_flags,
+		  all_pop_writeonly_flags_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/*
+ * All parameters populated, with all combinations of flags for a
+ * non-volatile writeable control
+ */
+static const struct cs_dsp_ctl_rw_test_param all_pop_nonvol_writeable_flags_cases[] = {
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = 0
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+};
+KUNIT_ARRAY_PARAM(all_pop_nonvol_writeable_flags,
+		  all_pop_nonvol_writeable_flags_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/*
+ * All parameters populated, with all combinations of flags for a
+ * volatile readable control.
+ */
+static const struct cs_dsp_ctl_rw_test_param all_pop_volatile_readable_flags_cases[] = {
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = 0 /* flags == 0 is volatile while firmware is running */
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_READABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS |
+		   WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+};
+KUNIT_ARRAY_PARAM(all_pop_volatile_readable_flags,
+		  all_pop_volatile_readable_flags_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+/*
+ * All parameters populated, with all combinations of flags for a
+ * volatile readable control.
+ */
+static const struct cs_dsp_ctl_rw_test_param all_pop_volatile_writeable_flags_cases[] = {
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = 0 /* flags == 0 is volatile while firmware is running */
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_WRITEABLE,
+	},
+	{ .alg_id = 0xfafa,   .mem_type = WMFW_ADSP2_YM, .offs_words = 1, .len_bytes = 4,
+	  .flags = WMFW_CTL_FLAG_VOLATILE | WMFW_CTL_FLAG_SYS |
+		   WMFW_CTL_FLAG_READABLE | WMFW_CTL_FLAG_WRITEABLE,
+	},
+};
+KUNIT_ARRAY_PARAM(all_pop_volatile_writeable_flags,
+		  all_pop_volatile_writeable_flags_cases,
+		  cs_dsp_ctl_all_param_desc);
+
+static struct kunit_case cs_dsp_ctl_rw_test_cases_adsp[] = {
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_varying_offset_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_varying_xy_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_z_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_varying_alg_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_writeable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_running, all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_running, all_pop_varying_offset_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_running, all_pop_varying_xy_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_running, all_pop_z_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_running,
+			 all_pop_volatile_readable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_not_started,
+			 all_pop_volatile_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_stopped,
+			 all_pop_volatile_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_stopped_powered_down,
+			 all_pop_volatile_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_not_current_loaded_fw,
+			 all_pop_volatile_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_not_current_running_fw,
+			 all_pop_volatile_readable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_not_started,
+			 all_pop_volatile_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_stopped,
+			 all_pop_volatile_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_stopped_powered_down,
+			 all_pop_volatile_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_not_current_loaded_fw,
+			 all_pop_volatile_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_not_current_running_fw,
+			 all_pop_volatile_writeable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_with_seek,
+			 all_pop_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_cache_with_seek,
+			 all_pop_nonvol_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_truncated,
+			 all_pop_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_cache_truncated,
+			 all_pop_nonvol_readable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_with_seek,
+			 all_pop_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_cache_with_seek,
+			 all_pop_nonvol_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_truncated,
+			 all_pop_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_cache_truncated,
+			 all_pop_nonvol_writeable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_with_seek_oob,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_with_length_overflow,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_with_seek_and_length_oob,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_with_seek_oob,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_with_length_overflow,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_with_seek_and_length_oob,
+			 all_pop_varying_len_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_from_writeonly,
+			 all_pop_writeonly_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_to_readonly,
+			 all_pop_readonly_flags_gen_params),
+
+	{ } /* terminator */
+};
+
+static struct kunit_case cs_dsp_ctl_rw_test_cases_halo[] = {
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_varying_offset_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_varying_xy_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_varying_alg_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_running, all_pop_writeable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_running, all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_running, all_pop_varying_offset_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_running, all_pop_varying_xy_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_running,
+			 all_pop_volatile_readable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_not_started,
+			 all_pop_volatile_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_stopped,
+			 all_pop_volatile_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_stopped_powered_down,
+			 all_pop_volatile_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_not_current_loaded_fw,
+			 all_pop_volatile_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_volatile_not_current_running_fw,
+			 all_pop_volatile_readable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_not_started,
+			 all_pop_volatile_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_stopped,
+			 all_pop_volatile_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_stopped_powered_down,
+			 all_pop_volatile_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_not_current_loaded_fw,
+			 all_pop_volatile_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_volatile_not_current_running_fw,
+			 all_pop_volatile_writeable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_with_seek,
+			 all_pop_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_cache_with_seek,
+			 all_pop_nonvol_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_truncated,
+			 all_pop_readable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_cache_truncated,
+			 all_pop_nonvol_readable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_with_seek,
+			 all_pop_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_cache_with_seek,
+			 all_pop_nonvol_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_truncated,
+			 all_pop_writeable_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_cache_truncated,
+			 all_pop_nonvol_writeable_flags_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_with_seek_oob,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_with_length_overflow,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_with_seek_and_length_oob,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_with_seek_oob,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_with_length_overflow,
+			 all_pop_varying_len_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_with_seek_and_length_oob,
+			 all_pop_varying_len_gen_params),
+
+	KUNIT_CASE_PARAM(cs_dsp_ctl_read_from_writeonly,
+			 all_pop_writeonly_flags_gen_params),
+	KUNIT_CASE_PARAM(cs_dsp_ctl_write_to_readonly,
+			 all_pop_readonly_flags_gen_params),
+
+	{ } /* terminator */
+};
+
+static struct kunit_suite cs_dsp_ctl_rw_test_halo = {
+	.name = "cs_dsp_ctl_rw_wmfwV3_halo",
+	.init = cs_dsp_ctl_rw_test_halo_init,
+	.test_cases = cs_dsp_ctl_rw_test_cases_halo,
+};
+
+static struct kunit_suite cs_dsp_ctl_rw_test_adsp2_32bit_wmfw1 = {
+	.name = "cs_dsp_ctl_rw_wmfwV1_adsp2_32bit",
+	.init = cs_dsp_ctl_rw_test_adsp2_32bit_wmfw1_init,
+	.test_cases = cs_dsp_ctl_rw_test_cases_adsp,
+};
+
+static struct kunit_suite cs_dsp_ctl_rw_test_adsp2_32bit_wmfw2 = {
+	.name = "cs_dsp_ctl_rw_wmfwV2_adsp2_32bit",
+	.init = cs_dsp_ctl_rw_test_adsp2_32bit_wmfw2_init,
+	.test_cases = cs_dsp_ctl_rw_test_cases_adsp,
+};
+
+static struct kunit_suite cs_dsp_ctl_rw_test_adsp2_16bit_wmfw1 = {
+	.name = "cs_dsp_ctl_rw_wmfwV1_adsp2_16bit",
+	.init = cs_dsp_ctl_rw_test_adsp2_16bit_wmfw1_init,
+	.test_cases = cs_dsp_ctl_rw_test_cases_adsp,
+};
+
+static struct kunit_suite cs_dsp_ctl_rw_test_adsp2_16bit_wmfw2 = {
+	.name = "cs_dsp_ctl_rw_wmfwV2_adsp2_16bit",
+	.init = cs_dsp_ctl_rw_test_adsp2_16bit_wmfw2_init,
+	.test_cases = cs_dsp_ctl_rw_test_cases_adsp,
+};
+
+kunit_test_suites(&cs_dsp_ctl_rw_test_halo,
+		  &cs_dsp_ctl_rw_test_adsp2_32bit_wmfw1,
+		  &cs_dsp_ctl_rw_test_adsp2_32bit_wmfw2,
+		  &cs_dsp_ctl_rw_test_adsp2_16bit_wmfw1,
+		  &cs_dsp_ctl_rw_test_adsp2_16bit_wmfw2);