root/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c
// SPDX-License-Identifier: GPL-2.0-only
//
// Mock regmap for cs_dsp KUnit tests.
//
// Copyright (C) 2024 Cirrus Logic, Inc. and
//                    Cirrus Logic International Semiconductor Ltd.

#include <kunit/test.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/regmap.h>

static int cs_dsp_mock_regmap_read(void *context, const void *reg_buf,
                                   const size_t reg_size, void *val_buf,
                                   size_t val_size)
{
        struct cs_dsp_test *priv = context;

        /* Should never get here because the regmap is cache-only */
        KUNIT_FAIL(priv->test, "Unexpected bus read @%#x", *(u32 *)reg_buf);

        return -EIO;
}

static int cs_dsp_mock_regmap_gather_write(void *context,
                                           const void *reg_buf, size_t reg_size,
                                           const void *val_buf, size_t val_size)
{
        struct cs_dsp_test *priv = context;

        priv->saw_bus_write = true;

        /* Should never get here because the regmap is cache-only */
        KUNIT_FAIL(priv->test, "Unexpected bus gather_write @%#x", *(u32 *)reg_buf);

        return -EIO;
}

static int cs_dsp_mock_regmap_write(void *context, const void *val_buf, size_t val_size)
{
        struct cs_dsp_test *priv = context;

        priv->saw_bus_write = true;

        /* Should never get here because the regmap is cache-only */
        KUNIT_FAIL(priv->test, "Unexpected bus write @%#x", *(u32 *)val_buf);

        return -EIO;
}

static const struct regmap_bus cs_dsp_mock_regmap_bus = {
        .read = cs_dsp_mock_regmap_read,
        .write = cs_dsp_mock_regmap_write,
        .gather_write = cs_dsp_mock_regmap_gather_write,
        .reg_format_endian_default = REGMAP_ENDIAN_LITTLE,
        .val_format_endian_default = REGMAP_ENDIAN_LITTLE,
};

static const struct reg_default adsp2_32bit_register_defaults[] = {
        { 0xffe00, 0x0000 }, /* CONTROL */
        { 0xffe02, 0x0000 }, /* CLOCKING */
        { 0xffe04, 0x0001 }, /* STATUS1: RAM_RDY=1 */
        { 0xffe30, 0x0000 }, /* WDMW_CONFIG_1 */
        { 0xffe32, 0x0000 }, /* WDMA_CONFIG_2 */
        { 0xffe34, 0x0000 }, /* RDMA_CONFIG_1 */
        { 0xffe40, 0x0000 }, /* SCRATCH_0_1 */
        { 0xffe42, 0x0000 }, /* SCRATCH_2_3 */
};

static const struct regmap_range adsp2_32bit_registers[] = {
        regmap_reg_range(0x80000, 0x88ffe), /* PM */
        regmap_reg_range(0xa0000, 0xa9ffe), /* XM */
        regmap_reg_range(0xc0000, 0xc1ffe), /* YM */
        regmap_reg_range(0xe0000, 0xe1ffe), /* ZM */
        regmap_reg_range(0xffe00, 0xffe7c), /* CORE CTRL */
};

const unsigned int cs_dsp_mock_adsp2_32bit_sysbase = 0xffe00;
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_adsp2_32bit_sysbase, "FW_CS_DSP_KUNIT_TEST_UTILS");

static const struct regmap_access_table adsp2_32bit_rw = {
        .yes_ranges = adsp2_32bit_registers,
        .n_yes_ranges = ARRAY_SIZE(adsp2_32bit_registers),
};

static const struct regmap_config cs_dsp_mock_regmap_adsp2_32bit = {
        .reg_bits = 32,
        .val_bits = 32,
        .reg_stride = 2,
        .reg_format_endian = REGMAP_ENDIAN_LITTLE,
        .val_format_endian = REGMAP_ENDIAN_BIG,
        .wr_table = &adsp2_32bit_rw,
        .rd_table = &adsp2_32bit_rw,
        .max_register = 0xffe7c,
        .reg_defaults = adsp2_32bit_register_defaults,
        .num_reg_defaults = ARRAY_SIZE(adsp2_32bit_register_defaults),
        .cache_type = REGCACHE_MAPLE,
};

static const struct reg_default adsp2_16bit_register_defaults[] = {
        { 0x1100, 0x0000 }, /* CONTROL */
        { 0x1101, 0x0000 }, /* CLOCKING */
        { 0x1104, 0x0001 }, /* STATUS1: RAM_RDY=1 */
        { 0x1130, 0x0000 }, /* WDMW_CONFIG_1 */
        { 0x1131, 0x0000 }, /* WDMA_CONFIG_2 */
        { 0x1134, 0x0000 }, /* RDMA_CONFIG_1 */
        { 0x1140, 0x0000 }, /* SCRATCH_0 */
        { 0x1141, 0x0000 }, /* SCRATCH_1 */
        { 0x1142, 0x0000 }, /* SCRATCH_2 */
        { 0x1143, 0x0000 }, /* SCRATCH_3 */
};

static const struct regmap_range adsp2_16bit_registers[] = {
        regmap_reg_range(0x001100, 0x001143), /* CORE CTRL */
        regmap_reg_range(0x100000, 0x105fff), /* PM */
        regmap_reg_range(0x180000, 0x1807ff), /* ZM */
        regmap_reg_range(0x190000, 0x1947ff), /* XM */
        regmap_reg_range(0x1a8000, 0x1a97ff), /* YM */
};

const unsigned int cs_dsp_mock_adsp2_16bit_sysbase = 0x001100;
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_adsp2_16bit_sysbase, "FW_CS_DSP_KUNIT_TEST_UTILS");

static const struct regmap_access_table adsp2_16bit_rw = {
        .yes_ranges = adsp2_16bit_registers,
        .n_yes_ranges = ARRAY_SIZE(adsp2_16bit_registers),
};

static const struct regmap_config cs_dsp_mock_regmap_adsp2_16bit = {
        .reg_bits = 32,
        .val_bits = 16,
        .reg_stride = 1,
        .reg_format_endian = REGMAP_ENDIAN_LITTLE,
        .val_format_endian = REGMAP_ENDIAN_BIG,
        .wr_table = &adsp2_16bit_rw,
        .rd_table = &adsp2_16bit_rw,
        .max_register = 0x1a97ff,
        .reg_defaults = adsp2_16bit_register_defaults,
        .num_reg_defaults = ARRAY_SIZE(adsp2_16bit_register_defaults),
        .cache_type = REGCACHE_MAPLE,
};

static const struct reg_default halo_register_defaults[] = {
        /* CORE */
        { 0x2b80010, 0 },       /* HALO_CORE_SOFT_RESET */
        { 0x2b805c0, 0 },       /* HALO_SCRATCH1 */
        { 0x2b805c8, 0 },       /* HALO_SCRATCH2 */
        { 0x2b805d0, 0 },       /* HALO_SCRATCH3 */
        { 0x2b805c8, 0 },       /* HALO_SCRATCH4 */
        { 0x2bc1000, 0 },       /* HALO_CCM_CORE_CONTROL */
        { 0x2bc7000, 0 },       /* HALO_WDT_CONTROL */

        /* SYSINFO */
        { 0x25e2040, 0 },       /* HALO_AHBM_WINDOW_DEBUG_0 */
        { 0x25e2044, 0 },       /* HALO_AHBM_WINDOW_DEBUG_1 */
};

static const struct regmap_range halo_readable_registers[] = {
        regmap_reg_range(0x2000000, 0x208fff0), /* XM_PACKED */
        regmap_reg_range(0x25e0000, 0x25e004f), /* SYSINFO */
        regmap_reg_range(0x25e2000, 0x25e2047), /* SYSINFO */
        regmap_reg_range(0x2800000, 0x28bfff4), /* XM */
        regmap_reg_range(0x2b80000, 0x2bc700b), /* CORE CTRL */
        regmap_reg_range(0x2c00000, 0x2c8fff0), /* YM_PACKED */
        regmap_reg_range(0x3400000, 0x34bfff4), /* YM */
        regmap_reg_range(0x3800000, 0x3804fff), /* PM_PACKED */
};

static const struct regmap_range halo_writeable_registers[] = {
        regmap_reg_range(0x2000000, 0x208fff0), /* XM_PACKED */
        regmap_reg_range(0x2800000, 0x28bfff4), /* XM */
        regmap_reg_range(0x2b80000, 0x2bc700b), /* CORE CTRL */
        regmap_reg_range(0x2c00000, 0x2c8fff0), /* YM_PACKED */
        regmap_reg_range(0x3400000, 0x34bfff4), /* YM */
        regmap_reg_range(0x3800000, 0x3804fff), /* PM_PACKED */
};

const unsigned int cs_dsp_mock_halo_core_base = 0x2b80000;
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_halo_core_base, "FW_CS_DSP_KUNIT_TEST_UTILS");

const unsigned int cs_dsp_mock_halo_sysinfo_base = 0x25e0000;
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_halo_sysinfo_base, "FW_CS_DSP_KUNIT_TEST_UTILS");

static const struct regmap_access_table halo_readable = {
        .yes_ranges = halo_readable_registers,
        .n_yes_ranges = ARRAY_SIZE(halo_readable_registers),
};

static const struct regmap_access_table halo_writeable = {
        .yes_ranges = halo_writeable_registers,
        .n_yes_ranges = ARRAY_SIZE(halo_writeable_registers),
};

static const struct regmap_config cs_dsp_mock_regmap_halo = {
        .reg_bits = 32,
        .val_bits = 32,
        .reg_stride = 4,
        .reg_format_endian = REGMAP_ENDIAN_LITTLE,
        .val_format_endian = REGMAP_ENDIAN_BIG,
        .wr_table = &halo_writeable,
        .rd_table = &halo_readable,
        .max_register = 0x3804ffc,
        .reg_defaults = halo_register_defaults,
        .num_reg_defaults = ARRAY_SIZE(halo_register_defaults),
        .cache_type = REGCACHE_MAPLE,
};

/**
 * cs_dsp_mock_regmap_drop_range() - drop a range of registers from the cache.
 *
 * @priv:       Pointer to struct cs_dsp_test object.
 * @first_reg:  Address of first register to drop.
 * @last_reg:   Address of last register to drop.
 */
void cs_dsp_mock_regmap_drop_range(struct cs_dsp_test *priv,
                                   unsigned int first_reg, unsigned int last_reg)
{
        regcache_drop_region(priv->dsp->regmap, first_reg, last_reg);
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_regmap_drop_range, "FW_CS_DSP_KUNIT_TEST_UTILS");

/**
 * cs_dsp_mock_regmap_drop_regs() - drop a number of registers from the cache.
 *
 * @priv:       Pointer to struct cs_dsp_test object.
 * @first_reg:  Address of first register to drop.
 * @num_regs:   Number of registers to drop.
 */
void cs_dsp_mock_regmap_drop_regs(struct cs_dsp_test *priv,
                                  unsigned int first_reg, size_t num_regs)
{
        int stride = regmap_get_reg_stride(priv->dsp->regmap);
        unsigned int last = first_reg + (stride * (num_regs - 1));

        cs_dsp_mock_regmap_drop_range(priv, first_reg, last);
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_regmap_drop_regs, "FW_CS_DSP_KUNIT_TEST_UTILS");

/**
 * cs_dsp_mock_regmap_drop_bytes() - drop a number of bytes from the cache.
 *
 * @priv:       Pointer to struct cs_dsp_test object.
 * @first_reg:  Address of first register to drop.
 * @num_bytes:  Number of bytes to drop from the cache. Will be rounded
 *              down to a whole number of registers. Trailing bytes that
 *              are not a multiple of the register size will not be dropped.
 *              (This is intended to help detect math errors in test code.)
 */
void cs_dsp_mock_regmap_drop_bytes(struct cs_dsp_test *priv,
                                   unsigned int first_reg, size_t num_bytes)
{
        size_t num_regs = num_bytes / regmap_get_val_bytes(priv->dsp->regmap);

        cs_dsp_mock_regmap_drop_regs(priv, first_reg, num_regs);
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_regmap_drop_bytes, "FW_CS_DSP_KUNIT_TEST_UTILS");

/**
 * cs_dsp_mock_regmap_drop_system_regs() - Drop DSP system registers from the cache.
 *
 * @priv:       Pointer to struct cs_dsp_test object.
 *
 * Drops all DSP system registers from the regmap cache.
 */
void cs_dsp_mock_regmap_drop_system_regs(struct cs_dsp_test *priv)
{
        switch (priv->dsp->type) {
        case WMFW_ADSP2:
                if (priv->dsp->base) {
                        regcache_drop_region(priv->dsp->regmap,
                                             priv->dsp->base,
                                             priv->dsp->base + 0x7c);
                }
                return;
        case WMFW_HALO:
                if (priv->dsp->base) {
                        regcache_drop_region(priv->dsp->regmap,
                                             priv->dsp->base,
                                             priv->dsp->base + 0x47000);
                }

                /* sysinfo registers are read-only so don't drop them */
                return;
        default:
                return;
        }
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_regmap_drop_system_regs, "FW_CS_DSP_KUNIT_TEST_UTILS");

/**
 * cs_dsp_mock_regmap_is_dirty() - Test for dirty registers in the cache.
 *
 * @priv:               Pointer to struct cs_dsp_test object.
 * @drop_system_regs:   If true the DSP system regs will be dropped from
 *                      the cache before checking for dirty.
 *
 * All registers that are expected to be written must have been dropped
 * from the cache (DSP system registers can be dropped by passing
 * drop_system_regs == true). If any unexpected registers were written
 * there will still be dirty entries in the cache and a cache sync will
 * cause a write.
 *
 * Returns: true if there were dirty entries, false if not.
 */
bool cs_dsp_mock_regmap_is_dirty(struct cs_dsp_test *priv, bool drop_system_regs)
{
        if (drop_system_regs)
                cs_dsp_mock_regmap_drop_system_regs(priv);

        priv->saw_bus_write = false;
        regcache_cache_only(priv->dsp->regmap, false);
        regcache_sync(priv->dsp->regmap);
        regcache_cache_only(priv->dsp->regmap, true);

        return priv->saw_bus_write;
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_regmap_is_dirty, "FW_CS_DSP_KUNIT_TEST_UTILS");

/**
 * cs_dsp_mock_regmap_init() - Initialize a mock regmap.
 *
 * @priv:       Pointer to struct cs_dsp_test object. This must have a
 *              valid pointer to a struct cs_dsp in which the type and
 *              rev fields are set to the type of DSP to be simulated.
 *
 * On success the priv->dsp->regmap will point to the created
 * regmap instance.
 *
 * Return: zero on success, else negative error code.
 */
int cs_dsp_mock_regmap_init(struct cs_dsp_test *priv)
{
        const struct regmap_config *config;
        int ret;

        switch (priv->dsp->type) {
        case WMFW_HALO:
                config = &cs_dsp_mock_regmap_halo;
                break;
        case WMFW_ADSP2:
                if (priv->dsp->rev == 0)
                        config = &cs_dsp_mock_regmap_adsp2_16bit;
                else
                        config = &cs_dsp_mock_regmap_adsp2_32bit;
                break;
        default:
                config = NULL;
                break;
        }

        priv->dsp->regmap = devm_regmap_init(priv->dsp->dev,
                                             &cs_dsp_mock_regmap_bus,
                                             priv,
                                             config);
        if (IS_ERR(priv->dsp->regmap)) {
                ret = PTR_ERR(priv->dsp->regmap);
                kunit_err(priv->test, "Failed to allocate register map: %d\n", ret);
                return ret;
        }

        /* Put regmap in cache-only so it accumulates the writes done by cs_dsp */
        regcache_cache_only(priv->dsp->regmap, true);

        return 0;
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_regmap_init, "FW_CS_DSP_KUNIT_TEST_UTILS");