diff options
Diffstat (limited to 'drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c')
-rw-r--r-- | drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c | 3282 |
1 files changed, 3282 insertions, 0 deletions
diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c b/drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c new file mode 100644 index 000000000000..83386cc978e3 --- /dev/null +++ b/drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c @@ -0,0 +1,3282 @@ +// 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_cache_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_cache_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 const char * const cs_dsp_ctl_cache_test_fw_names[] = { + "misc", "mbc/vss", "haps", +}; + +static int _find_alg_entry(struct kunit *test, unsigned int alg_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs_dsp_ctl_cache_test_algs); ++i) { + if (cs_dsp_ctl_cache_test_algs[i].id == alg_id) + break; + } + + KUNIT_ASSERT_LT(test, i, ARRAY_SIZE(cs_dsp_ctl_cache_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_cache_test_algs[alg_index].xm_base_words; + case WMFW_ADSP2_YM: + return cs_dsp_ctl_cache_test_algs[alg_index].ym_base_words; + case WMFW_ADSP2_ZM: + return cs_dsp_ctl_cache_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; +} + +/* + * Memory allocated for control cache must be large enough. + * This creates multiple controls of different sizes so only works on + * wmfw V2 and later. + */ +static void cs_dsp_ctl_v2_cache_alloc(struct kunit *test) +{ + 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; + unsigned int reg, alg_base_words, alg_size_bytes; + struct cs_dsp_coeff_ctl *ctl; + struct firmware *wmfw; + char ctl_name[4]; + u32 *reg_vals; + int num_ctls; + + /* Create some DSP data to initialize the control cache */ + alg_base_words = _get_alg_mem_base_words(test, 0, WMFW_ADSP2_YM); + alg_size_bytes = cs_dsp_ctl_cache_test_algs[0].ym_size_words * + cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv); + reg_vals = kunit_kzalloc(test, alg_size_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals); + reg = cs_dsp_mock_base_addr_for_mem(priv, WMFW_ADSP2_YM); + reg += alg_base_words * cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv); + regmap_raw_write(dsp->regmap, reg, reg_vals, alg_size_bytes); + + cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder, + cs_dsp_ctl_cache_test_algs[0].id, + "dummyalg", NULL); + + /* Create controls of different sizes */ + def.mem_type = WMFW_ADSP2_YM; + def.shortname = ctl_name; + num_ctls = 0; + for (def.length_bytes = 4; def.length_bytes <= 64; def.length_bytes += 4) { + snprintf(ctl_name, ARRAY_SIZE(ctl_name), "%x", def.length_bytes); + cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def); + num_ctls++; + def.offset_dsp_words += def.length_bytes / sizeof(u32); + } + 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); + + KUNIT_EXPECT_EQ(test, list_count_nodes(&dsp->ctl_list), num_ctls); + + /* Check that the block allocated for the cache is large enough */ + list_for_each_entry(ctl, &dsp->ctl_list, list) + KUNIT_EXPECT_GE(test, ksize(ctl->cache), ctl->len); +} + +/* + * Content of registers backing a control should be read into the + * control cache when the firmware is downloaded. + */ +static void cs_dsp_ctl_cache_init(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_kmalloc(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 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); + get_random_bytes(reg_vals, 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_cache_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); + + /* + * The data should have been populated into the control cache + * so should be readable through the control. + */ + 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); +} + +/* + * For a non-volatile write-only control the cache should be zero-filled + * when the firmware is downloaded. + */ +static void cs_dsp_ctl_cache_init_write_only(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 *readback, *zeros; + + zeros = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, zeros); + + readback = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + /* Create a non-volatile write-only control */ + 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_cache_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); + + /* + * The control cache should have been zero-filled so should be + * readable through the control. + */ + get_random_bytes(readback, param->len_bytes); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, zeros, param->len_bytes); +} + +/* + * Multiple different firmware with identical controls. + * This is legal because different firmwares could contain the same + * algorithm. + * The control cache should be initialized only with the data from + * the firmware containing it. + */ +static void cs_dsp_ctl_cache_init_multiple_fw_same_controls(struct kunit *test) +{ + struct cs_dsp_test *priv = test->priv; + struct cs_dsp *dsp = priv->dsp; + struct cs_dsp_mock_coeff_def def = mock_coeff_template; + struct cs_dsp_mock_wmfw_builder *builder[3]; + unsigned int reg, alg_base_words; + struct cs_dsp_coeff_ctl *walkctl, *ctl[3]; + struct firmware *wmfw; + u32 *reg_vals[3], *readback; + int i; + + static_assert(ARRAY_SIZE(ctl) == ARRAY_SIZE(builder)); + static_assert(ARRAY_SIZE(reg_vals) == ARRAY_SIZE(builder)); + static_assert(ARRAY_SIZE(cs_dsp_ctl_cache_test_fw_names) >= ARRAY_SIZE(builder)); + + /* Create an identical control in each firmware but with different alg id */ + for (i = 0; i < ARRAY_SIZE(builder); i++) { + builder[i] = _create_dummy_wmfw(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, builder[i]); + + cs_dsp_mock_wmfw_start_alg_info_block(builder[i], + cs_dsp_ctl_cache_test_algs[0].id, + "dummyalg", NULL); + cs_dsp_mock_wmfw_add_coeff_desc(builder[i], &def); + cs_dsp_mock_wmfw_end_alg_info_block(builder[i]); + } + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + reg_vals[i] = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals[i]); + } + + readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + /* + * For each firmware create random content in the register backing + * the control. Then download, start, stop and power-down. + */ + for (i = 0; i < ARRAY_SIZE(builder); i++) { + alg_base_words = _get_alg_mem_base_words(test, 0, def.mem_type); + reg = cs_dsp_mock_base_addr_for_mem(priv, def.mem_type); + reg += (alg_base_words + def.offset_dsp_words) * + cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv); + + get_random_bytes(reg_vals[i], def.length_bytes); + regmap_raw_write(dsp->regmap, reg, reg_vals[i], def.length_bytes); + wmfw = cs_dsp_mock_wmfw_get_firmware(builder[i]); + KUNIT_ASSERT_EQ(test, + cs_dsp_power_up(dsp, wmfw, + cs_dsp_ctl_cache_test_fw_names[i], + NULL, NULL, + cs_dsp_ctl_cache_test_fw_names[i]), + 0); + KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0); + cs_dsp_stop(dsp); + cs_dsp_power_down(dsp); + } + + /* There should now be 3 controls */ + KUNIT_ASSERT_EQ(test, list_count_nodes(&dsp->ctl_list), 3); + + /* + * There's no requirement for the control list to be in any + * particular order, so don't assume the order. + */ + for (i = 0; i < ARRAY_SIZE(ctl); i++) + ctl[i] = NULL; + + list_for_each_entry(walkctl, &dsp->ctl_list, list) { + if (strcmp(walkctl->fw_name, cs_dsp_ctl_cache_test_fw_names[0]) == 0) + ctl[0] = walkctl; + else if (strcmp(walkctl->fw_name, cs_dsp_ctl_cache_test_fw_names[1]) == 0) + ctl[1] = walkctl; + else if (strcmp(walkctl->fw_name, cs_dsp_ctl_cache_test_fw_names[2]) == 0) + ctl[2] = walkctl; + } + + KUNIT_ASSERT_NOT_NULL(test, ctl[0]); + KUNIT_ASSERT_NOT_NULL(test, ctl[1]); + KUNIT_ASSERT_NOT_NULL(test, ctl[2]); + + /* + * The data should have been populated into the control cache + * so should be readable through the control. + */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[0], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[0], def.length_bytes); + + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[1], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[1], def.length_bytes); + + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[2], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[2], def.length_bytes); +} + +/* + * Multiple different firmware with controls identical except for alg id. + * This is legal because the controls are qualified by algorithm id. + * The control cache should be initialized only with the data from + * the firmware containing it. + */ +static void cs_dsp_ctl_cache_init_multiple_fwalgid_same_controls(struct kunit *test) +{ + struct cs_dsp_test *priv = test->priv; + struct cs_dsp *dsp = priv->dsp; + struct cs_dsp_mock_coeff_def def = mock_coeff_template; + struct cs_dsp_mock_wmfw_builder *builder[3]; + unsigned int reg, alg_base_words; + struct cs_dsp_coeff_ctl *walkctl, *ctl[3]; + struct firmware *wmfw; + u32 *reg_vals[3], *readback; + int i; + + static_assert(ARRAY_SIZE(ctl) == ARRAY_SIZE(builder)); + static_assert(ARRAY_SIZE(reg_vals) == ARRAY_SIZE(builder)); + static_assert(ARRAY_SIZE(cs_dsp_ctl_cache_test_fw_names) >= ARRAY_SIZE(builder)); + + /* Create an identical control in each firmware but with different alg id */ + for (i = 0; i < ARRAY_SIZE(builder); i++) { + builder[i] = _create_dummy_wmfw(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, builder[i]); + + cs_dsp_mock_wmfw_start_alg_info_block(builder[i], + cs_dsp_ctl_cache_test_algs[i].id, + "dummyalg", NULL); + cs_dsp_mock_wmfw_add_coeff_desc(builder[i], &def); + cs_dsp_mock_wmfw_end_alg_info_block(builder[i]); + } + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + reg_vals[i] = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals[i]); + } + + readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + /* + * For each firmware create random content in the register backing + * the control. Then download, start, stop and power-down. + */ + for (i = 0; i < ARRAY_SIZE(builder); i++) { + alg_base_words = _get_alg_mem_base_words(test, i, def.mem_type); + reg = cs_dsp_mock_base_addr_for_mem(priv, def.mem_type); + reg += (alg_base_words + def.offset_dsp_words) * + cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv); + + get_random_bytes(reg_vals[i], def.length_bytes); + regmap_raw_write(dsp->regmap, reg, reg_vals[i], def.length_bytes); + wmfw = cs_dsp_mock_wmfw_get_firmware(builder[i]); + KUNIT_ASSERT_EQ(test, + cs_dsp_power_up(dsp, wmfw, + cs_dsp_ctl_cache_test_fw_names[i], + NULL, NULL, + cs_dsp_ctl_cache_test_fw_names[i]), + 0); + KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0); + cs_dsp_stop(dsp); + cs_dsp_power_down(dsp); + } + + /* There should now be 3 controls */ + KUNIT_ASSERT_EQ(test, list_count_nodes(&dsp->ctl_list), 3); + + /* + * There's no requirement for the control list to be in any + * particular order, so don't assume the order. + */ + for (i = 0; i < ARRAY_SIZE(ctl); i++) + ctl[i] = NULL; + + list_for_each_entry(walkctl, &dsp->ctl_list, list) { + if (cs_dsp_ctl_cache_test_algs[0].id == walkctl->alg_region.alg) + ctl[0] = walkctl; + else if (cs_dsp_ctl_cache_test_algs[1].id == walkctl->alg_region.alg) + ctl[1] = walkctl; + else if (cs_dsp_ctl_cache_test_algs[2].id == walkctl->alg_region.alg) + ctl[2] = walkctl; + } + + KUNIT_ASSERT_NOT_NULL(test, ctl[0]); + KUNIT_ASSERT_NOT_NULL(test, ctl[1]); + KUNIT_ASSERT_NOT_NULL(test, ctl[2]); + + /* + * The data should have been populated into the control cache + * so should be readable through the control. + */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[0], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[0], def.length_bytes); + + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[1], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[1], def.length_bytes); + + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[2], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[2], def.length_bytes); +} + +/* + * Firmware with controls at the same position in different memories. + * The control cache should be initialized with content from the + * correct memory region. + */ +static void cs_dsp_ctl_cache_init_multiple_mems(struct kunit *test) +{ + struct cs_dsp_test *priv = test->priv; + struct cs_dsp *dsp = priv->dsp; + struct cs_dsp_test_local *local = priv->local; + struct cs_dsp_mock_coeff_def def = mock_coeff_template; + unsigned int reg, alg_base_words; + struct cs_dsp_coeff_ctl *walkctl, *ctl[3]; + struct firmware *wmfw; + u32 *reg_vals[3], *readback; + int i; + + static_assert(ARRAY_SIZE(ctl) == ARRAY_SIZE(reg_vals)); + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + reg_vals[i] = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals[i]); + get_random_bytes(reg_vals[i], def.length_bytes); + } + + readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder, + cs_dsp_ctl_cache_test_algs[0].id, + "dummyalg", NULL); + + /* Create controls identical except for memory region */ + def.mem_type = WMFW_ADSP2_YM; + cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def); + + def.mem_type = WMFW_ADSP2_XM; + cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def); + + if (cs_dsp_mock_has_zm(priv)) { + def.mem_type = WMFW_ADSP2_ZM; + cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def); + } + + cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder); + + /* Create random content in the registers backing each control */ + alg_base_words = _get_alg_mem_base_words(test, 0, WMFW_ADSP2_YM); + reg = cs_dsp_mock_base_addr_for_mem(priv, WMFW_ADSP2_YM); + reg += (alg_base_words + def.offset_dsp_words) * + cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv); + regmap_raw_write(dsp->regmap, reg, reg_vals[0], def.length_bytes); + + alg_base_words = _get_alg_mem_base_words(test, 0, WMFW_ADSP2_XM); + reg = cs_dsp_mock_base_addr_for_mem(priv, WMFW_ADSP2_XM); + reg += (alg_base_words + def.offset_dsp_words) * + cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv); + regmap_raw_write(dsp->regmap, reg, reg_vals[1], def.length_bytes); + + if (cs_dsp_mock_has_zm(priv)) { + alg_base_words = _get_alg_mem_base_words(test, 0, WMFW_ADSP2_ZM); + reg = cs_dsp_mock_base_addr_for_mem(priv, WMFW_ADSP2_ZM); + reg += (alg_base_words + def.offset_dsp_words) * + cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv); + regmap_raw_write(dsp->regmap, reg, reg_vals[2], def.length_bytes); + } + + /* Download, run, stop and power-down the firmware */ + wmfw = cs_dsp_mock_wmfw_get_firmware(local->wmfw_builder); + KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0); + KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0); + cs_dsp_stop(dsp); + cs_dsp_power_down(dsp); + + /* There should now be 2 or 3 controls */ + KUNIT_ASSERT_EQ(test, list_count_nodes(&dsp->ctl_list), + cs_dsp_mock_has_zm(priv) ? 3 : 2); + + /* + * There's no requirement for the control list to be in any + * particular order, so don't assume the order. + */ + for (i = 0; i < ARRAY_SIZE(ctl); i++) + ctl[i] = NULL; + + list_for_each_entry(walkctl, &dsp->ctl_list, list) { + if (walkctl->alg_region.type == WMFW_ADSP2_YM) + ctl[0] = walkctl; + if (walkctl->alg_region.type == WMFW_ADSP2_XM) + ctl[1] = walkctl; + if (walkctl->alg_region.type == WMFW_ADSP2_ZM) + ctl[2] = walkctl; + } + + + /* + * The data should have been populated into the control cache + * so should be readable through the control. + */ + KUNIT_ASSERT_NOT_NULL(test, ctl[0]); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[0], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[0], def.length_bytes); + + KUNIT_ASSERT_NOT_NULL(test, ctl[1]); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[1], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[1], def.length_bytes); + + if (cs_dsp_mock_has_zm(priv)) { + KUNIT_ASSERT_NOT_NULL(test, ctl[2]); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[2], 0, readback, + def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[2], def.length_bytes); + } +} + +/* + * Firmware with controls at the same position in different algorithms + * The control cache should be initialized with content from the + * memory of the algorithm it points to. + */ +static void cs_dsp_ctl_cache_init_multiple_algs(struct kunit *test) +{ + struct cs_dsp_test *priv = test->priv; + struct cs_dsp *dsp = priv->dsp; + struct cs_dsp_test_local *local = priv->local; + struct cs_dsp_mock_coeff_def def = mock_coeff_template; + unsigned int reg, alg_base_words; + struct cs_dsp_coeff_ctl *walkctl, *ctl[3]; + struct firmware *wmfw; + u32 *reg_vals[3], *readback; + int i; + + static_assert(ARRAY_SIZE(ctl) == ARRAY_SIZE(reg_vals)); + static_assert(ARRAY_SIZE(reg_vals) <= ARRAY_SIZE(cs_dsp_ctl_cache_test_algs)); + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + reg_vals[i] = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals[i]); + get_random_bytes(reg_vals[i], def.length_bytes); + } + + readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + /* Create controls identical except for algorithm */ + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder, + cs_dsp_ctl_cache_test_algs[i].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); + } + + /* Create random content in the registers backing each control */ + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + alg_base_words = _get_alg_mem_base_words(test, i, def.mem_type); + reg = cs_dsp_mock_base_addr_for_mem(priv, def.mem_type); + reg += (alg_base_words + def.offset_dsp_words) * + cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv); + regmap_raw_write(dsp->regmap, reg, reg_vals[i], def.length_bytes); + } + + /* Download, run, stop and power-down the firmware */ + wmfw = cs_dsp_mock_wmfw_get_firmware(local->wmfw_builder); + KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0); + KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0); + cs_dsp_stop(dsp); + cs_dsp_power_down(dsp); + + /* There should now be 3 controls */ + KUNIT_ASSERT_EQ(test, list_count_nodes(&dsp->ctl_list), 3); + + /* + * There's no requirement for the control list to be in any + * particular order, so don't assume the order. + */ + for (i = 0; i < ARRAY_SIZE(ctl); i++) + ctl[i] = NULL; + + list_for_each_entry(walkctl, &dsp->ctl_list, list) { + if (walkctl->alg_region.alg == cs_dsp_ctl_cache_test_algs[0].id) + ctl[0] = walkctl; + if (walkctl->alg_region.alg == cs_dsp_ctl_cache_test_algs[1].id) + ctl[1] = walkctl; + if (walkctl->alg_region.alg == cs_dsp_ctl_cache_test_algs[2].id) + ctl[2] = walkctl; + } + + KUNIT_ASSERT_NOT_NULL(test, ctl[0]); + KUNIT_ASSERT_NOT_NULL(test, ctl[1]); + KUNIT_ASSERT_NOT_NULL(test, ctl[2]); + + /* + * The data should have been populated into the control cache + * so should be readable through the control. + */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[0], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[0], def.length_bytes); + + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[1], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[1], def.length_bytes); + + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[2], 0, readback, + def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[2], def.length_bytes); +} + +/* + * Firmware with controls in the same algorithm and memory but at + * different offsets. + * The control cache should be initialized with content from the + * correct offset. + * Only for wmfw format V2 and later. V1 only supports one control per + * memory per algorithm. + */ +static void cs_dsp_ctl_cache_init_multiple_offsets(struct kunit *test) +{ + struct cs_dsp_test *priv = test->priv; + struct cs_dsp *dsp = priv->dsp; + struct cs_dsp_test_local *local = priv->local; + struct cs_dsp_mock_coeff_def def = mock_coeff_template; + unsigned int reg, alg_base_words, alg_base_reg; + struct cs_dsp_coeff_ctl *walkctl, *ctl[3]; + struct firmware *wmfw; + u32 *reg_vals[3], *readback; + int i; + + static_assert(ARRAY_SIZE(ctl) == ARRAY_SIZE(reg_vals)); + static_assert(ARRAY_SIZE(reg_vals) <= ARRAY_SIZE(cs_dsp_ctl_cache_test_algs)); + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + reg_vals[i] = kunit_kmalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, reg_vals[i]); + get_random_bytes(reg_vals[i], def.length_bytes); + } + + readback = kunit_kzalloc(test, def.length_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + cs_dsp_mock_wmfw_start_alg_info_block(local->wmfw_builder, + cs_dsp_ctl_cache_test_algs[0].id, + "dummyalg", NULL); + + /* Create controls identical except for offset */ + def.length_bytes = 8; + def.offset_dsp_words = 0; + def.shortname = "CtlA"; + cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def); + + def.offset_dsp_words = 5; + def.shortname = "CtlB"; + cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def); + + def.offset_dsp_words = 8; + def.shortname = "CtlC"; + cs_dsp_mock_wmfw_add_coeff_desc(local->wmfw_builder, &def); + + cs_dsp_mock_wmfw_end_alg_info_block(local->wmfw_builder); + + /* Create random content in the registers backing each control */ + alg_base_words = _get_alg_mem_base_words(test, 0, def.mem_type); + alg_base_reg = cs_dsp_mock_base_addr_for_mem(priv, def.mem_type); + alg_base_reg += alg_base_words * cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv); + + reg = alg_base_reg; + regmap_raw_write(dsp->regmap, reg, reg_vals[0], def.length_bytes); + reg = alg_base_reg + (5 * cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv)); + regmap_raw_write(dsp->regmap, reg, reg_vals[1], def.length_bytes); + reg = alg_base_reg + (8 * cs_dsp_mock_reg_addr_inc_per_unpacked_word(priv)); + regmap_raw_write(dsp->regmap, reg, reg_vals[2], def.length_bytes); + + /* Download, run, stop and power-down the firmware */ + wmfw = cs_dsp_mock_wmfw_get_firmware(local->wmfw_builder); + KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0); + KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0); + cs_dsp_stop(dsp); + cs_dsp_power_down(dsp); + + /* There should now be 3 controls */ + KUNIT_ASSERT_EQ(test, list_count_nodes(&dsp->ctl_list), 3); + + /* + * There's no requirement for the control list to be in any + * particular order, so don't assume the order. + */ + for (i = 0; i < ARRAY_SIZE(ctl); i++) + ctl[i] = NULL; + + list_for_each_entry(walkctl, &dsp->ctl_list, list) { + if (walkctl->offset == 0) + ctl[0] = walkctl; + if (walkctl->offset == 5) + ctl[1] = walkctl; + if (walkctl->offset == 8) + ctl[2] = walkctl; + } + + KUNIT_ASSERT_NOT_NULL(test, ctl[0]); + KUNIT_ASSERT_NOT_NULL(test, ctl[1]); + KUNIT_ASSERT_NOT_NULL(test, ctl[2]); + + /* + * The data should have been populated into the control cache + * so should be readable through the control. + */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[0], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[0], def.length_bytes); + + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[1], 0, readback, def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[1], def.length_bytes); + + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl[2], 0, readback, + def.length_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals[2], def.length_bytes); +} + +/* + * Read from a cached control before the firmware is started. + * Should return the data in the cache. + */ +static void cs_dsp_ctl_cache_read_not_started(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_kmalloc(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 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); + get_random_bytes(reg_vals, 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_cache_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 but don't start firmware */ + 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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the data from the control cache */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + 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 cached control after the firmware has been stopped. + * Should return the data in the cache. + */ +static void cs_dsp_ctl_cache_read_stopped(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_kmalloc(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 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); + get_random_bytes(reg_vals, 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_cache_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); + + /* 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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the data from the control cache */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + 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 cached control after the DSP has been powered-up and + * then powered-down without running. + * Should return the data in the cache. + */ +static void cs_dsp_ctl_cache_read_powered_down(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_kmalloc(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 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); + get_random_bytes(reg_vals, 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_cache_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 then power-down */ + 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); + 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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the data from the control cache */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + 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 cached control after the firmware has been run and + * stopped, then the DSP has been powered-down. + * Should return the data in the cache. + */ +static void cs_dsp_ctl_cache_read_stopped_powered_down(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_kmalloc(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 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); + get_random_bytes(reg_vals, 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_cache_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); + + /* 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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the data from the control cache */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + 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 cached control when a different firmware is currently + * loaded into the DSP. + * Should return the data in the cache. + */ +static void cs_dsp_ctl_cache_read_not_current_loaded_fw(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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, *readback; + + reg_vals = kunit_kmalloc(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 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); + get_random_bytes(reg_vals, 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_cache_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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the data from the control cache */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + 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 cached control when a different firmware is currently + * running. + * Should return the data in the cache. + */ +static void cs_dsp_ctl_cache_read_not_current_running_fw(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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, *readback; + + reg_vals = kunit_kmalloc(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 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); + get_random_bytes(reg_vals, 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_cache_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 then power-down */ + 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); + cs_dsp_power_down(dsp); + + /* Power-up with a different firmware and run it */ + 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); + 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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the data from the control cache */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + 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 cached control with non-zero flags while the firmware is + * running. + * Should return the data in the cache, not from the registers. + */ +static void cs_dsp_ctl_cache_read_running(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 *init_reg_vals, *new_reg_vals, *readback; + + init_reg_vals = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, init_reg_vals); + + new_reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_reg_vals); + + readback = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + /* Create data in the registers backing the control */ + 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(init_reg_vals, param->len_bytes); + regmap_raw_write(dsp->regmap, reg, init_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_cache_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); + + /* Start the firmware running */ + 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); + + /* + * Change the values in the registers backing the control then drop + * them from the regmap cache. This allows checking that the control + * read is returning values from the control cache and not accessing + * the registers. + */ + KUNIT_ASSERT_EQ(test, + regmap_raw_write(dsp->regmap, reg, new_reg_vals, param->len_bytes), + 0); + cs_dsp_mock_regmap_drop_bytes(priv, reg, param->len_bytes); + + /* Control should readback the origin data from its cache */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, init_reg_vals, param->len_bytes); + + /* Stop and power-down the DSP */ + kunit_release_action(test, _cs_dsp_stop_wrapper, dsp); + cs_dsp_power_down(dsp); + + /* Control should readback from the cache */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, init_reg_vals, param->len_bytes); +} + +/* + * Read from a cached control with flags == 0 while the firmware is + * running. + * Should behave as volatile and read from the registers. + * (This is for backwards compatibility with old firmware versions) + */ +static void cs_dsp_ctl_cache_read_running_zero_flags(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 *init_reg_vals, *new_reg_vals, *readback; + + init_reg_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, init_reg_vals); + + new_reg_vals = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_reg_vals); + + readback = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + /* Zero-fill the registers backing the control */ + 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, init_reg_vals, param->len_bytes); + + /* Create control pointing to this data */ + def.flags = 0; + 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_cache_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); + + /* Start the firmware running */ + 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); + + /* Change the values in the registers backing the control */ + get_random_bytes(new_reg_vals, param->len_bytes); + regmap_raw_write(dsp->regmap, reg, new_reg_vals, param->len_bytes); + + /* Control should readback the new data from the registers */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, new_reg_vals, param->len_bytes); + + /* Stop and power-down the DSP */ + kunit_release_action(test, _cs_dsp_stop_wrapper, dsp); + cs_dsp_power_down(dsp); + + /* Change the values in the registers backing the control */ + regmap_raw_write(dsp->regmap, reg, init_reg_vals, param->len_bytes); + + /* Control should readback from the cache */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, new_reg_vals, param->len_bytes); +} + +/* + * Write to a cached control while the firmware is running. + * This should be a writethrough operation, writing to the cache and + * the registers. + */ +static void cs_dsp_ctl_cache_writethrough(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_kmalloc(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 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); + 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_cache_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 */ + 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); +} + +/* + * Write unchanged data to a cached control while the firmware is running. + * The control write should return 0 to indicate that the content + * didn't change. + */ +static void cs_dsp_ctl_cache_writethrough_unchanged(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_kmalloc(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 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); + get_random_bytes(reg_vals, 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_cache_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); + + /* + * If the control is write-only the cache will have been zero-initialized + * so the first write will always indicate a change. + */ + if (def.flags && !(def.flags & WMFW_CTL_FLAG_READABLE)) { + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, + param->len_bytes), + 1); + } + + /* + * Write the same data to the control, cs_dsp_coeff_lock_and_write_ctrl() + * should return 0 to indicate the content didn't change. + */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, param->len_bytes), + 0); + 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); +} + +/* + * Write unchanged data to a cached control while the firmware is not started. + * The control write should return 0 to indicate that the cache content + * didn't change. + */ +static void cs_dsp_ctl_cache_write_unchanged_not_started(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_kmalloc(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 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); + get_random_bytes(reg_vals, 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_cache_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); + + /* + * If the control is write-only the cache will have been zero-initialized + * so the first write will always indicate a change. + */ + if (def.flags && !(def.flags & WMFW_CTL_FLAG_READABLE)) { + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, + param->len_bytes), + 1); + } + + /* + * Write the same data to the control, cs_dsp_coeff_lock_and_write_ctrl() + * should return 0 to indicate the content didn't change. + */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, reg_vals, param->len_bytes), + 0); + 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); +} + +/* + * Write to a cached control while the firmware is loaded but not + * started. + * This should write to the cache only. + */ +static void cs_dsp_ctl_cache_write_not_started(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 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; + 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_cache_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 but don't start firmware */ + 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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Write new data to the control, it should not be written to the registers */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + 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); + + /* Registers should not have been written so regmap cache should still be clean */ + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the new data from the control cache */ + 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); +} + +/* + * Write to a cached control after the firmware has been loaded, + * started and stopped. + * This should write to the cache only. + */ +static void cs_dsp_ctl_cache_write_stopped(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 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; + 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_cache_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); + + /* 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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Write new data to the control, it should not be written to the registers */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + 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); + + /* Registers should not have been written so regmap cache should still be clean */ + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the new data from the control cache */ + 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); +} + +/* + * Write to a cached control after the firmware has been loaded, + * then the DSP powered-down. + * This should write to the cache only. + */ +static void cs_dsp_ctl_cache_write_powered_down(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 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; + 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_cache_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 then power-down */ + 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); + 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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Write new data to the control, it should not be written to the registers */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + 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); + + /* Registers should not have been written so regmap cache should still be clean */ + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the new data from the control cache */ + 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); +} + +/* + * Write to a cached control after the firmware has been loaded, + * started, stopped, and then the DSP powered-down. + * This should write to the cache only. + */ +static void cs_dsp_ctl_cache_write_stopped_powered_down(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 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; + 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_cache_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); + + /* 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); + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Write new data to the control, it should not be written to the registers */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + 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); + + /* Registers should not have been written so regmap cache should still be clean */ + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the new data from the control cache */ + 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); +} + +/* + * Write to a cached control that is not in the currently loaded firmware. + * This should write to the cache only. + */ +static void cs_dsp_ctl_cache_write_not_current_loaded_fw(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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, *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 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; + 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_cache_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); + + /* Get the control */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + /* 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); + + /* Control from unloaded firmware should be disabled */ + KUNIT_EXPECT_FALSE(test, ctl->enabled); + + /* 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)); + + /* + * It should be possible to write new data to the control from + * the first firmware. But this should not be written to the + * registers. + */ + 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); + + /* Registers should not have been written so regmap cache should still be clean */ + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the new data from the control cache */ + 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); +} + +/* + * Write to a cached control that is not in the currently running firmware. + * This should write to the cache only. + */ +static void cs_dsp_ctl_cache_write_not_current_running_fw(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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, *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 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; + 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_cache_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 then power-down */ + 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); + cs_dsp_power_down(dsp); + + /* Get the control */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + /* Power-up with a different firmware and run it */ + 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); + 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); + + /* Control from unloaded firmware should be disabled */ + KUNIT_EXPECT_FALSE(test, ctl->enabled); + + /* 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)); + + /* + * It should be possible to write new data to the control from + * the first firmware. But this should not be written to the + * registers. + */ + 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); + + /* Registers should not have been written so regmap cache should still be clean */ + KUNIT_EXPECT_FALSE(test, cs_dsp_mock_regmap_is_dirty(priv, true)); + + /* Control should readback the new data from the control cache */ + 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); +} + +/* + * Write to a cached control before running the firmware. + * The value written to the cache should be synced out to the registers + * backing the control when the firmware is run. + */ +static void cs_dsp_ctl_cache_sync_write_before_run(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 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; + 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_cache_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 but don't start firmware */ + 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); + + /* Write new data to the control, it should not be written to the registers */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + 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_EXPECT_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMNEQ(test, readback, reg_vals, param->len_bytes); + + /* Start the firmware and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, param->len_bytes); + + /* Control should readback the new data from the control cache */ + 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); +} + +/* + * Write to a cached control while the firmware is running. + * The value written should be synced out to the registers + * backing the control when the firmware is next run. + */ +static void cs_dsp_ctl_cache_sync_write_while_running(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 *init_vals, *ctl_vals, *readback; + + init_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, init_vals); + + ctl_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctl_vals); + + readback = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + /* Zero-fill the registers backing the control */ + 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, init_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_cache_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 and start firmware */ + 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); + 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 */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + get_random_bytes(ctl_vals, param->len_bytes); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, ctl_vals, param->len_bytes), + 1); + + /* Stop firmware and zero the registers backing the control */ + kunit_release_action(test, _cs_dsp_stop_wrapper, dsp); + regmap_raw_write(dsp->regmap, reg, init_vals, param->len_bytes); + KUNIT_ASSERT_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, init_vals, param->len_bytes); + + /* Start the firmware and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); + + /* Control should readback the new data from the control cache */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); +} + +/* + * Write to a cached control after stopping the firmware. + * The value written to the cache should be synced out to the registers + * backing the control when the firmware is next run. + */ +static void cs_dsp_ctl_cache_sync_write_after_stop(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 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; + 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_cache_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 but don't start firmware */ + 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); + + /* Write new data to the control, it should not be written to the registers */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + 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_EXPECT_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMNEQ(test, readback, reg_vals, param->len_bytes); + + /* Start the firmware and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, param->len_bytes); + + /* Control should readback the new data from the control cache */ + 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); +} + +/* + * Write to a cached control that is not in the currently loaded firmware. + * The value written to the cache should be synced out to the registers + * backing the control the next time the firmware containing the + * control is run. + */ +static void cs_dsp_ctl_cache_sync_write_not_current_fw(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_mock_wmfw_builder *builder2 = _create_dummy_wmfw(test); + 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 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; + 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_cache_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 but don't start firmware */ + 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); + + /* Get the control */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + /* 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); + + /* Write new data to the control, it should not be written to the registers */ + 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_EXPECT_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMNEQ(test, readback, reg_vals, param->len_bytes); + + /* Power-down DSP then power-up with the original firmware */ + cs_dsp_power_down(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); + + /* Start the firmware and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, reg_vals, param->len_bytes); + + /* Control should readback the new data from the control cache */ + 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); +} + +/* + * The value in the control cache should be synced out to the registers + * backing the control every time the firmware containing the control + * is run. + */ +static void cs_dsp_ctl_cache_sync_reapply_every_run(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 *init_vals, *readback, *ctl_vals; + + init_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, init_vals); + + readback = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + ctl_vals = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctl_vals); + + /* Zero-fill the registers backing the control */ + 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, init_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_cache_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 but don't start firmware */ + 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); + + /* Write new data to the control */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + get_random_bytes(ctl_vals, param->len_bytes); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, ctl_vals, param->len_bytes), + 1); + + /* Start the firmware and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); + + /* Stop the firmware and reset the registers */ + kunit_release_action(test, _cs_dsp_stop_wrapper, dsp); + regmap_raw_write(dsp->regmap, reg, init_vals, param->len_bytes); + + /* Start the firmware again and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); + + /* Control should readback the new data from the control cache */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); +} + +/* + * The value in the control cache should be retained if the same + * firmware is downloaded again. It should be synced out to the + * registers backing the control after the firmware containing the + * control is downloaded again and run. + */ +static void cs_dsp_ctl_cache_sync_reapply_after_fw_reload(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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 *init_vals, *readback, *ctl_vals; + + init_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, init_vals); + + readback = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + ctl_vals = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctl_vals); + + /* Zero-fill the registers backing the control */ + 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, init_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_cache_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 but don't start firmware */ + 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); + + /* Write new data to the control */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + get_random_bytes(ctl_vals, param->len_bytes); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, ctl_vals, param->len_bytes), + 1); + + /* Start the firmware and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); + + /* Stop the firmware and power-down the DSP */ + kunit_release_action(test, _cs_dsp_stop_wrapper, dsp); + cs_dsp_power_down(dsp); + + /* Reset the registers */ + regmap_raw_write(dsp->regmap, reg, init_vals, param->len_bytes); + + /* Download the firmware again, the cache content should not change */ + KUNIT_ASSERT_EQ(test, cs_dsp_power_up(dsp, wmfw, "mock_fw", NULL, NULL, "misc"), 0); + + /* Start the firmware and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); + + /* Control should readback the new data from the control cache */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); +} + +/* + * The value in the control cache should be retained after a different + * firmware is downloaded. + * When the firmware containing the control is downloaded and run + * the value in the control cache should be synced out to the registers + * backing the control. + */ +static void cs_dsp_ctl_cache_sync_reapply_after_fw_swap(struct kunit *test) +{ + const struct cs_dsp_ctl_cache_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_mock_wmfw_builder *builder2 = _create_dummy_wmfw(test); + unsigned int reg, alg_base_words; + struct cs_dsp_coeff_ctl *ctl; + struct firmware *wmfw; + u32 *init_vals, *readback, *ctl_vals; + + init_vals = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, init_vals); + + readback = kunit_kzalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, readback); + + ctl_vals = kunit_kmalloc(test, param->len_bytes, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctl_vals); + + /* Zero-fill the registers backing the control */ + 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, init_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_cache_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 but don't start firmware */ + 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); + + /* Write new data to the control */ + ctl = list_first_entry_or_null(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + KUNIT_ASSERT_NOT_NULL(test, ctl); + + get_random_bytes(ctl_vals, param->len_bytes); + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_write_ctrl(ctl, 0, ctl_vals, param->len_bytes), + 1); + + /* Start the firmware and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); + + /* Stop the firmware and power-down the DSP */ + kunit_release_action(test, _cs_dsp_stop_wrapper, dsp); + cs_dsp_power_down(dsp); + + /* Reset the registers */ + regmap_raw_write(dsp->regmap, reg, init_vals, param->len_bytes); + + /* Download and run a different firmware */ + 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); + KUNIT_ASSERT_EQ(test, cs_dsp_run(dsp), 0); + cs_dsp_power_down(dsp); + + /* Reset the registers */ + regmap_raw_write(dsp->regmap, reg, init_vals, param->len_bytes); + + /* Download the original firmware again */ + 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); + KUNIT_EXPECT_TRUE(test, ctl->set); + + /* Start the firmware and the cached data should be written to registers */ + 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_EQ(test, regmap_raw_read(dsp->regmap, reg, readback, param->len_bytes), 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); + + /* Control should readback the new data from the control cache */ + KUNIT_EXPECT_EQ(test, + cs_dsp_coeff_lock_and_read_ctrl(ctl, 0, readback, param->len_bytes), + 0); + KUNIT_EXPECT_MEMEQ(test, readback, ctl_vals, param->len_bytes); +} + +static int cs_dsp_ctl_cache_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_cache_test_algs, + ARRAY_SIZE(cs_dsp_ctl_cache_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_cache_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_cache_test_common_init(test, dsp, 3); +} + +static int cs_dsp_ctl_cache_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_cache_test_common_init(test, dsp, wmfw_ver); +} + +static int cs_dsp_ctl_cache_test_adsp2_32bit_wmfw1_init(struct kunit *test) +{ + return cs_dsp_ctl_cache_test_adsp2_32bit_init(test, 1); +} + +static int cs_dsp_ctl_cache_test_adsp2_32bit_wmfw2_init(struct kunit *test) +{ + return cs_dsp_ctl_cache_test_adsp2_32bit_init(test, 2); +} + +static int cs_dsp_ctl_cache_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_cache_test_common_init(test, dsp, wmfw_ver); +} + +static int cs_dsp_ctl_cache_test_adsp2_16bit_wmfw1_init(struct kunit *test) +{ + return cs_dsp_ctl_cache_test_adsp2_16bit_init(test, 1); +} + +static int cs_dsp_ctl_cache_test_adsp2_16bit_wmfw2_init(struct kunit *test) +{ + return cs_dsp_ctl_cache_test_adsp2_16bit_init(test, 2); +} + +static void cs_dsp_ctl_all_param_desc(const struct cs_dsp_ctl_cache_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_cache_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_cache_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_cache_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_cache_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_cache_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 + * non-volatile readable control + */ +static const struct cs_dsp_ctl_cache_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 + * non-volatile readable control, except flags==0 + */ +static const struct cs_dsp_ctl_cache_test_param all_pop_nonvol_readable_nonzero_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_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_nonzero_flags, + all_pop_nonvol_readable_nonzero_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_cache_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 + * non-volatile write-only control of varying lengths + */ +static const struct cs_dsp_ctl_cache_test_param all_pop_nonvol_write_only_length_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 = 512, + .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 = 512, + .flags = WMFW_CTL_FLAG_SYS | WMFW_CTL_FLAG_WRITEABLE, + }, +}; +KUNIT_ARRAY_PARAM(all_pop_nonvol_write_only_length, + all_pop_nonvol_write_only_length_cases, + cs_dsp_ctl_all_param_desc); + +static struct kunit_case cs_dsp_ctl_cache_test_cases_v1[] = { + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_len_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_offset_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_xy_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_z_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_alg_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_nonvol_readable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init_write_only, + all_pop_nonvol_write_only_length_gen_params), + + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_fw_same_controls), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_fwalgid_same_controls), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_mems), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_algs), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_not_started, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_stopped, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_powered_down, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_stopped_powered_down, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_not_current_loaded_fw, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_not_current_running_fw, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_running, + all_pop_nonvol_readable_nonzero_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_running_zero_flags, + all_pop_varying_len_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_len_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_offset_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_xy_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_z_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_alg_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_nonvol_writeable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_len_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_offset_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_xy_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_z_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_alg_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_nonvol_writeable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_unchanged_not_started, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_not_started, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_stopped, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_powered_down, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_stopped_powered_down, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_not_current_loaded_fw, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_not_current_running_fw, + all_pop_nonvol_writeable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_before_run, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_while_running, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_after_stop, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_not_current_fw, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_reapply_every_run, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_reapply_after_fw_reload, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_reapply_after_fw_swap, + all_pop_nonvol_writeable_flags_gen_params), + + { } /* terminator */ +}; + +static struct kunit_case cs_dsp_ctl_cache_test_cases_v2[] = { + KUNIT_CASE(cs_dsp_ctl_v2_cache_alloc), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_len_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_offset_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_xy_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_z_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_alg_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_nonvol_readable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init_write_only, + all_pop_nonvol_write_only_length_gen_params), + + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_fw_same_controls), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_fwalgid_same_controls), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_mems), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_algs), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_offsets), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_not_started, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_stopped, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_powered_down, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_stopped_powered_down, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_not_current_loaded_fw, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_not_current_running_fw, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_running, + all_pop_nonvol_readable_nonzero_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_running_zero_flags, + all_pop_varying_len_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_len_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_offset_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_xy_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_z_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_alg_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_nonvol_writeable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_len_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_offset_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_xy_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_z_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_alg_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_nonvol_writeable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_unchanged_not_started, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_not_started, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_stopped, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_powered_down, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_stopped_powered_down, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_not_current_loaded_fw, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_not_current_running_fw, + all_pop_nonvol_writeable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_before_run, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_while_running, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_after_stop, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_not_current_fw, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_reapply_every_run, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_reapply_after_fw_reload, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_reapply_after_fw_swap, + all_pop_nonvol_writeable_flags_gen_params), + + { } /* terminator */ +}; + +static struct kunit_case cs_dsp_ctl_cache_test_cases_v3[] = { + KUNIT_CASE(cs_dsp_ctl_v2_cache_alloc), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_len_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_offset_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_xy_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_varying_alg_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init, all_pop_nonvol_readable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_init_write_only, + all_pop_nonvol_write_only_length_gen_params), + + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_fw_same_controls), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_fwalgid_same_controls), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_mems), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_algs), + KUNIT_CASE(cs_dsp_ctl_cache_init_multiple_offsets), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_not_started, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_stopped, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_powered_down, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_stopped_powered_down, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_not_current_loaded_fw, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_not_current_running_fw, + all_pop_nonvol_readable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_read_running, + all_pop_nonvol_readable_nonzero_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_len_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_offset_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_xy_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_varying_alg_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough, all_pop_nonvol_writeable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_len_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_offset_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_xy_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_varying_alg_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_writethrough_unchanged, + all_pop_nonvol_writeable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_unchanged_not_started, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_not_started, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_stopped, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_powered_down, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_stopped_powered_down, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_not_current_loaded_fw, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_write_not_current_running_fw, + all_pop_nonvol_writeable_flags_gen_params), + + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_before_run, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_while_running, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_after_stop, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_write_not_current_fw, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_reapply_every_run, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_reapply_after_fw_reload, + all_pop_nonvol_writeable_flags_gen_params), + KUNIT_CASE_PARAM(cs_dsp_ctl_cache_sync_reapply_after_fw_swap, + all_pop_nonvol_writeable_flags_gen_params), + + { } /* terminator */ +}; + +static struct kunit_suite cs_dsp_ctl_cache_test_halo = { + .name = "cs_dsp_ctl_cache_wmfwV3_halo", + .init = cs_dsp_ctl_cache_test_halo_init, + .test_cases = cs_dsp_ctl_cache_test_cases_v3, +}; + +static struct kunit_suite cs_dsp_ctl_cache_test_adsp2_32bit_wmfw1 = { + .name = "cs_dsp_ctl_cache_wmfwV1_adsp2_32bit", + .init = cs_dsp_ctl_cache_test_adsp2_32bit_wmfw1_init, + .test_cases = cs_dsp_ctl_cache_test_cases_v1, +}; + +static struct kunit_suite cs_dsp_ctl_cache_test_adsp2_32bit_wmfw2 = { + .name = "cs_dsp_ctl_cache_wmfwV2_adsp2_32bit", + .init = cs_dsp_ctl_cache_test_adsp2_32bit_wmfw2_init, + .test_cases = cs_dsp_ctl_cache_test_cases_v2, +}; + +static struct kunit_suite cs_dsp_ctl_cache_test_adsp2_16bit_wmfw1 = { + .name = "cs_dsp_ctl_cache_wmfwV1_adsp2_16bit", + .init = cs_dsp_ctl_cache_test_adsp2_16bit_wmfw1_init, + .test_cases = cs_dsp_ctl_cache_test_cases_v1, +}; + +static struct kunit_suite cs_dsp_ctl_cache_test_adsp2_16bit_wmfw2 = { + .name = "cs_dsp_ctl_cache_wmfwV2_adsp2_16bit", + .init = cs_dsp_ctl_cache_test_adsp2_16bit_wmfw2_init, + .test_cases = cs_dsp_ctl_cache_test_cases_v2, +}; + +kunit_test_suites(&cs_dsp_ctl_cache_test_halo, + &cs_dsp_ctl_cache_test_adsp2_32bit_wmfw1, + &cs_dsp_ctl_cache_test_adsp2_32bit_wmfw2, + &cs_dsp_ctl_cache_test_adsp2_16bit_wmfw1, + &cs_dsp_ctl_cache_test_adsp2_16bit_wmfw2); |