root/sound/soc/codecs/cs35l56-shared-test.c
// SPDX-License-Identifier: GPL-2.0-only
//
// KUnit test for the Cirrus Logic cs35l56-shared module.
//
// Copyright (C) 2026 Cirrus Logic, Inc. and
//                    Cirrus Logic International Semiconductor Ltd.

#include <kunit/resource.h>
#include <kunit/test.h>
#include <kunit/static_stub.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/device/faux.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/regmap.h>
#include <linux/seq_buf.h>
#include <sound/cs35l56.h>

struct cs35l56_shared_test_priv {
        struct kunit *test;
        struct faux_device *amp_dev;
        struct regmap *registers;
        struct cs35l56_base *cs35l56_base;
        u8 applied_pad_pull_state[CS35L56_MAX_GPIO];
};

struct cs35l56_shared_test_param {
        int spkid_gpios[4];
        int spkid_pulls[4];
        unsigned long gpio_status;
        int spkid;
};

KUNIT_DEFINE_ACTION_WRAPPER(faux_device_destroy_wrapper, faux_device_destroy,
                            struct faux_device *)

KUNIT_DEFINE_ACTION_WRAPPER(regmap_exit_wrapper, regmap_exit, struct regmap *)

static const struct regmap_config cs35l56_shared_test_mock_registers_regmap = {
        .reg_bits = 32,
        .val_bits = 32,
        .reg_stride = 4,
        .max_register = CS35L56_DSP1_PMEM_5114,
        .cache_type = REGCACHE_MAPLE,
};

static const struct regmap_bus cs35l56_shared_test_mock_registers_regmap_bus = {
        /* No handlers because it is always in cache-only */
};

static unsigned int cs35l56_shared_test_read_gpio_status(struct cs35l56_shared_test_priv *priv)
{
        const struct cs35l56_shared_test_param *param = priv->test->param_value;
        unsigned int reg_offs, pad_cfg, val;
        unsigned int status = 0;
        unsigned int mask = 1;

        for (reg_offs = 0; reg_offs < CS35L56_MAX_GPIO * sizeof(u32); reg_offs += sizeof(u32)) {
                regmap_read(priv->registers, CS35L56_SYNC_GPIO1_CFG + reg_offs, &pad_cfg);
                regmap_read(priv->registers, CS35L56_GPIO1_CTRL1 + reg_offs, &val);

                /* Only read a value if set as an input pin and as a GPIO */
                val &= (CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_MASK);
                if ((pad_cfg & CS35L56_PAD_GPIO_IE) &&
                    (val == (CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_GPIO)))
                        status |= (param->gpio_status & mask);

                mask <<= 1;
        }

        return status;
}

static int cs35l56_shared_test_updt_gpio_pres(struct cs35l56_shared_test_priv *priv,
                                              unsigned int reg, unsigned int val)
{
        int i, ret;

        ret = regmap_write(priv->registers, reg, val);
        if (ret)
                return ret;

        if (val & CS35L56_UPDT_GPIO_PRES) {
                /* Simulate transferring register state to internal latches */
                for (i = 0; i < ARRAY_SIZE(priv->applied_pad_pull_state); i++) {
                        reg = CS35L56_SYNC_GPIO1_CFG + (i * sizeof(u32));
                        regmap_read(priv->registers, reg, &val);
                        val = FIELD_GET(CS35L56_PAD_GPIO_PULL_MASK, val);
                        priv->applied_pad_pull_state[i] = val;
                }
        }

        return 0;
}

static int cs35l56_shared_test_reg_read(void *context, unsigned int reg, unsigned int *val)
{
        struct cs35l56_shared_test_priv *priv = context;

        switch (reg) {
        case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG:
        case CS35L56_GPIO1_CTRL1 ... CS35L56_GPIO13_CTRL1:
                return regmap_read(priv->registers, reg, val);
        case CS35L56_UPDATE_REGS:
                *val = 0;
                return 0;
        case CS35L56_GPIO_STATUS1:
                *val = cs35l56_shared_test_read_gpio_status(priv);
                return 0;
        default:
                kunit_fail_current_test("Bad regmap read address %#x\n", reg);
                return -EINVAL;
        }
}

static int cs35l56_shared_test_reg_write(void *context, unsigned int reg, unsigned int val)
{
        struct cs35l56_shared_test_priv *priv = context;

        switch (reg) {
        case CS35L56_UPDATE_REGS:
                return cs35l56_shared_test_updt_gpio_pres(priv, reg, val);
        case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG:
        case CS35L56_GPIO1_CTRL1 ... CS35L56_GPIO13_CTRL1:
                return regmap_write(priv->registers, reg, val);
        default:
                kunit_fail_current_test("Bad regmap write address %#x\n", reg);
                return -EINVAL;
        }
}

static const struct regmap_bus cs35l56_shared_test_regmap_bus = {
        .reg_read = cs35l56_shared_test_reg_read,
        .reg_write = cs35l56_shared_test_reg_write,
        .reg_format_endian_default = REGMAP_ENDIAN_LITTLE,
        .val_format_endian_default = REGMAP_ENDIAN_LITTLE,
};

/*
 * Self-test that the mock GPIO registers obey the configuration bits.
 * Other tests rely on the mocked registers only returning a GPIO state
 * if the pin is correctly set as a GPIO input.
 */
static void cs35l56_shared_test_mock_gpio_status_selftest(struct kunit *test)
{
        const struct cs35l56_shared_test_param *param = test->param_value;
        struct cs35l56_shared_test_priv *priv = test->priv;
        struct cs35l56_base *cs35l56_base = priv->cs35l56_base;
        unsigned int reg, val;

        KUNIT_ASSERT_NOT_NULL(test, param);

        /* Set all pins non-GPIO and output. Mock GPIO_STATUS should read 0 */
        for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0));

        /* Set all pads as inputs */
        for (reg = CS35L56_SYNC_GPIO1_CFG; reg <= CS35L56_ASP2_DIO_GPIO13_CFG; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, CS35L56_PAD_GPIO_IE));

        KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val));
        KUNIT_EXPECT_EQ(test, val, 0);

        /* Set all pins as GPIO outputs. Mock GPIO_STATUS should read 0 */
        for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, CS35L56_GPIO_FN_GPIO));

        KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val));
        KUNIT_EXPECT_EQ(test, val, 0);

        /* Set all pins as non-GPIO inputs. Mock GPIO_STATUS should read 0 */
        for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, CS35L56_GPIO_DIR_MASK));

        KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val));
        KUNIT_EXPECT_EQ(test, val, 0);

        /* Set all pins as GPIO inputs. Mock GPIO_STATUS should match param->gpio_status */
        for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0,
                                regmap_write(priv->registers, reg,
                                             CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_GPIO));

        KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val));
        KUNIT_EXPECT_EQ(test, val, param->gpio_status);

        /* Set all pads as outputs. Mock GPIO_STATUS should read 0 */
        for (reg = CS35L56_SYNC_GPIO1_CFG; reg <= CS35L56_ASP2_DIO_GPIO13_CFG; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0));

        KUNIT_ASSERT_EQ(test, 0, regmap_read(cs35l56_base->regmap, CS35L56_GPIO_STATUS1, &val));
        KUNIT_EXPECT_EQ(test, val, 0);
}

/* Test that the listed chip pins are assembled into a speaker ID integer. */
static void cs35l56_shared_test_get_onchip_speaker_id(struct kunit *test)
{
        const struct cs35l56_shared_test_param *param = test->param_value;
        struct cs35l56_shared_test_priv *priv = test->priv;
        struct cs35l56_base *cs35l56_base = priv->cs35l56_base;
        unsigned int i, reg;

        /* Set all pins non-GPIO and output */
        for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0));

        for (reg = CS35L56_SYNC_GPIO1_CFG; reg <= CS35L56_ASP2_DIO_GPIO13_CFG; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0));

        /* Init GPIO array */
        for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) {
                if (param->spkid_gpios[i] < 0)
                        break;

                cs35l56_base->onchip_spkid_gpios[i] = param->spkid_gpios[i] - 1;
                cs35l56_base->num_onchip_spkid_gpios++;
        }

        cs35l56_base->num_onchip_spkid_pulls = 0;

        KUNIT_EXPECT_EQ(test, cs35l56_configure_onchip_spkid_pads(cs35l56_base), 0);
        KUNIT_EXPECT_EQ(test, cs35l56_read_onchip_spkid(cs35l56_base), param->spkid);
}

/* Test that the listed chip pins and the corresponding pads are configured correctly. */
static void cs35l56_shared_test_onchip_speaker_id_pad_config(struct kunit *test)
{
        const struct cs35l56_shared_test_param *param = test->param_value;
        struct cs35l56_shared_test_priv *priv = test->priv;
        struct cs35l56_base *cs35l56_base = priv->cs35l56_base;
        unsigned int i, reg, val;

        /* Init values in all pin registers */
        for (reg = CS35L56_GPIO1_CTRL1; reg <= CS35L56_GPIO13_CTRL1; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0));

        for (reg = CS35L56_SYNC_GPIO1_CFG; reg <= CS35L56_ASP2_DIO_GPIO13_CFG; reg += sizeof(u32))
                KUNIT_ASSERT_EQ(test, 0, regmap_write(priv->registers, reg, 0));

        /* Init GPIO array */
        for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) {
                if (param->spkid_gpios[i] < 0)
                        break;

                cs35l56_base->onchip_spkid_gpios[i] = param->spkid_gpios[i] - 1;
                cs35l56_base->num_onchip_spkid_gpios++;
        }

        /* Init pulls array */
        for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++) {
                if (param->spkid_pulls[i] < 0)
                        break;

                cs35l56_base->onchip_spkid_pulls[i] = param->spkid_pulls[i];
                cs35l56_base->num_onchip_spkid_pulls++;
        }

        KUNIT_EXPECT_EQ(test, cs35l56_configure_onchip_spkid_pads(cs35l56_base), 0);

        for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) {
                if (param->spkid_gpios[i] < 0)
                        break;

                /* Pad should be an input */
                reg = CS35L56_SYNC_GPIO1_CFG + ((param->spkid_gpios[i] - 1) * sizeof(u32));
                KUNIT_EXPECT_EQ(test, regmap_read(priv->registers, reg, &val), 0);
                KUNIT_EXPECT_EQ(test, val & CS35L56_PAD_GPIO_IE, CS35L56_PAD_GPIO_IE);

                /* Specified pulls should be set, others should be none */
                if (i < cs35l56_base->num_onchip_spkid_pulls) {
                        KUNIT_EXPECT_EQ(test, val & CS35L56_PAD_GPIO_PULL_MASK,
                                        FIELD_PREP(CS35L56_PAD_GPIO_PULL_MASK,
                                                   param->spkid_pulls[i]));
                } else {
                        KUNIT_EXPECT_EQ(test, val & CS35L56_PAD_GPIO_PULL_MASK,
                                        CS35L56_PAD_PULL_NONE);
                }

                /* Pulls for all specfied GPIOs should have been transferred to AO latch */
                if (i < cs35l56_base->num_onchip_spkid_pulls) {
                        KUNIT_EXPECT_EQ(test,
                                        priv->applied_pad_pull_state[param->spkid_gpios[i] - 1],
                                        param->spkid_pulls[i]);
                } else {
                        KUNIT_EXPECT_EQ(test,
                                        priv->applied_pad_pull_state[param->spkid_gpios[i] - 1],
                                        CS35L56_PAD_PULL_NONE);
                }
        }
}

/* Test that the listed chip pins are stashed correctly. */
static void cs35l56_shared_test_stash_onchip_spkid_pins(struct kunit *test)
{
        const struct cs35l56_shared_test_param *param = test->param_value;
        struct cs35l56_shared_test_priv *priv = test->priv;
        struct cs35l56_base *cs35l56_base = priv->cs35l56_base;
        u32 gpios[5], pulls[5];
        int i, num_gpios, num_pulls;

        static_assert(ARRAY_SIZE(gpios) >= ARRAY_SIZE(param->spkid_gpios));
        static_assert(ARRAY_SIZE(pulls) >= ARRAY_SIZE(param->spkid_pulls));

        num_gpios = 0;
        for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) {
                if (param->spkid_gpios[i] < 0)
                        break;

                gpios[i] = (u32)param->spkid_gpios[i];
                num_gpios++;
        }

        num_pulls = 0;
        for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++) {
                if (param->spkid_pulls[i] < 0)
                        break;

                pulls[i] = (u32)param->spkid_pulls[i];
                num_pulls++;
        }

        cs35l56_base->num_onchip_spkid_gpios = 0;
        cs35l56_base->num_onchip_spkid_pulls = 0;

        KUNIT_ASSERT_LE(test, num_gpios, ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios));
        KUNIT_ASSERT_LE(test, num_pulls, ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls));

        KUNIT_EXPECT_EQ(test,
                        cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base,
                                                                  gpios, num_gpios,
                                                                  pulls, num_pulls),
                        0);

        KUNIT_EXPECT_EQ(test, cs35l56_base->num_onchip_spkid_gpios, num_gpios);
        KUNIT_EXPECT_EQ(test, cs35l56_base->num_onchip_spkid_pulls, num_pulls);

        /* GPIO numbers are adjusted from 1-based to 0-based */
        for (i = 0; i < num_gpios; i++)
                KUNIT_EXPECT_EQ(test, cs35l56_base->onchip_spkid_gpios[i], gpios[i] - 1);

        for (i = 0; i < num_pulls; i++)
                KUNIT_EXPECT_EQ(test, cs35l56_base->onchip_spkid_pulls[i], pulls[i]);
}

/* Test that illegal GPIO numbers are rejected. */
static void cs35l56_shared_test_stash_onchip_spkid_pins_reject_invalid(struct kunit *test)
{
        struct cs35l56_shared_test_priv *priv = test->priv;
        struct cs35l56_base *cs35l56_base = priv->cs35l56_base;
        u32 gpios[8] = { }, pulls[8] = { };

        KUNIT_EXPECT_LE(test,
                        cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base,
                                                                  gpios, 1,
                                                                  pulls, 0),
                        0);

        switch (cs35l56_base->type) {
        case 0x54:
        case 0x56:
        case 0x57:
                gpios[0] = CS35L56_MAX_GPIO + 1;
                break;
        case 0x63:
                gpios[0] = CS35L63_MAX_GPIO + 1;
                break;
        default:
                kunit_fail_current_test("Unsupported type:%#x\n", cs35l56_base->type);
                return;
        }
        KUNIT_EXPECT_LE(test,
                        cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base,
                                                                  gpios, 1,
                                                                  pulls, 0),
                        0);

        gpios[0] = 1;
        pulls[0] = 3;
        KUNIT_EXPECT_LE(test,
                        cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base,
                                                                  gpios, 1,
                                                                  pulls, 1),
                        0);

        static_assert(ARRAY_SIZE(gpios) > ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios));
        static_assert(ARRAY_SIZE(pulls) > ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls));
        KUNIT_EXPECT_EQ(test,
                        cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base,
                                                                  gpios, ARRAY_SIZE(gpios),
                                                                  pulls, 0),
                        -EOVERFLOW);
        KUNIT_EXPECT_EQ(test,
                        cs35l56_check_and_save_onchip_spkid_gpios(cs35l56_base,
                                                                  gpios, 1,
                                                                  pulls, ARRAY_SIZE(pulls)),
                        -EOVERFLOW);
}

static void cs35l56_shared_test_onchip_speaker_id_not_defined(struct kunit *test)
{
        struct cs35l56_shared_test_priv *priv = test->priv;
        struct cs35l56_base *cs35l56_base = priv->cs35l56_base;

        memset(cs35l56_base->onchip_spkid_gpios, 0, sizeof(cs35l56_base->onchip_spkid_gpios));
        memset(cs35l56_base->onchip_spkid_pulls, 0, sizeof(cs35l56_base->onchip_spkid_pulls));
        cs35l56_base->num_onchip_spkid_gpios = 0;
        cs35l56_base->num_onchip_spkid_pulls = 0;
        KUNIT_EXPECT_EQ(test, cs35l56_configure_onchip_spkid_pads(cs35l56_base), 0);
        KUNIT_EXPECT_EQ(test, cs35l56_read_onchip_spkid(cs35l56_base), -ENOENT);
}

static int cs35l56_shared_test_case_regmap_init(struct kunit *test,
                                                const struct regmap_config *regmap_config)
{
        struct cs35l56_shared_test_priv *priv = test->priv;
        struct cs35l56_base *cs35l56_base;

        /*
         * Create a dummy regmap to simulate a register map by holding the
         * values of all simulated registers in the regmap cache.
         */
        priv->registers = regmap_init(&priv->amp_dev->dev,
                                      &cs35l56_shared_test_mock_registers_regmap_bus,
                                      priv,
                                      &cs35l56_shared_test_mock_registers_regmap);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->registers);
        KUNIT_ASSERT_EQ(test, 0,
                        kunit_add_action_or_reset(test, regmap_exit_wrapper,
                                                  priv->registers));
        regcache_cache_only(priv->registers, true);

        /* Create dummy regmap for cs35l56 driver */
        cs35l56_base = priv->cs35l56_base;
        cs35l56_base->regmap = regmap_init(cs35l56_base->dev,
                                           &cs35l56_shared_test_regmap_bus,
                                           priv,
                                           regmap_config);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cs35l56_base->regmap);
        KUNIT_ASSERT_EQ(test, 0,
                        kunit_add_action_or_reset(test, regmap_exit_wrapper,
                                                  cs35l56_base->regmap));

        return 0;
}

static int cs35l56_shared_test_case_base_init(struct kunit *test, u8 type, u8 rev,
                                              const struct regmap_config *regmap_config)
{
        struct cs35l56_shared_test_priv *priv;
        int ret;

        KUNIT_ASSERT_NOT_NULL(test, cs_amp_test_hooks);

        priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
        if (!priv)
                return -ENOMEM;

        test->priv = priv;
        priv->test = test;

        /* Create dummy amp driver dev */
        priv->amp_dev = faux_device_create("cs35l56_shared_test_drv", NULL, NULL);
        KUNIT_ASSERT_NOT_NULL(test, priv->amp_dev);
        KUNIT_ASSERT_EQ(test, 0,
                        kunit_add_action_or_reset(test,
                                                  faux_device_destroy_wrapper,
                                                  priv->amp_dev));

        priv->cs35l56_base = kunit_kzalloc(test, sizeof(*priv->cs35l56_base), GFP_KERNEL);
        KUNIT_ASSERT_NOT_NULL(test, priv->cs35l56_base);
        priv->cs35l56_base->dev = &priv->amp_dev->dev;
        priv->cs35l56_base->type = type;
        priv->cs35l56_base->rev = rev;

        if (regmap_config) {
                ret = cs35l56_shared_test_case_regmap_init(test, regmap_config);
                if (ret)
                        return ret;
        }

        return 0;
}

static int cs35l56_shared_test_case_regmap_init_L56_B0_sdw(struct kunit *test)
{
        return cs35l56_shared_test_case_base_init(test, 0x56, 0xb0, &cs35l56_regmap_sdw);
}

static int cs35l56_shared_test_case_regmap_init_L56_B0_spi(struct kunit *test)
{
        return cs35l56_shared_test_case_base_init(test, 0x56, 0xb0, &cs35l56_regmap_spi);
}

static int cs35l56_shared_test_case_regmap_init_L56_B0_i2c(struct kunit *test)
{
        return cs35l56_shared_test_case_base_init(test, 0x56, 0xb0, &cs35l56_regmap_i2c);
}

static int cs35l56_shared_test_case_regmap_init_L56_B2_sdw(struct kunit *test)
{
        return cs35l56_shared_test_case_base_init(test, 0x56, 0xb2, &cs35l56_regmap_sdw);
}

static int cs35l56_shared_test_case_regmap_init_L56_B2_spi(struct kunit *test)
{
        return cs35l56_shared_test_case_base_init(test, 0x56, 0xb2, &cs35l56_regmap_spi);
}

static int cs35l56_shared_test_case_regmap_init_L56_B2_i2c(struct kunit *test)
{
        return cs35l56_shared_test_case_base_init(test, 0x56, 0xb2, &cs35l56_regmap_i2c);
}

static int cs35l56_shared_test_case_regmap_init_L63_A1_sdw(struct kunit *test)
{
        return cs35l56_shared_test_case_base_init(test, 0x63, 0xa1, &cs35l63_regmap_sdw);
}

static void cs35l56_shared_test_gpio_param_desc(const struct cs35l56_shared_test_param *param,
                                                char *desc)
{
        DECLARE_SEQ_BUF(gpios, 1 + (2 * ARRAY_SIZE(param->spkid_gpios)));
        DECLARE_SEQ_BUF(pulls, 1 + (2 * ARRAY_SIZE(param->spkid_pulls)));
        int i;

        for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++) {
                if (param->spkid_gpios[i] < 0)
                        break;

                seq_buf_printf(&gpios, "%s%d", (i == 0) ? "" : ",", param->spkid_gpios[i]);
        }

        for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++) {
                if (param->spkid_pulls[i] < 0)
                        break;

                seq_buf_printf(&pulls, "%s%d", (i == 0) ? "" : ",", param->spkid_pulls[i]);
        }

        snprintf(desc, KUNIT_PARAM_DESC_SIZE, "gpios:{%s} pulls:{%s} status:%#lx spkid:%d",
                 seq_buf_str(&gpios), seq_buf_str(&pulls), param->gpio_status, param->spkid);
}

static const struct cs35l56_shared_test_param cs35l56_shared_test_gpios_selftest_cases[] = {
        { .spkid_gpios = { -1 }, .gpio_status = GENMASK(12, 0) },
};
KUNIT_ARRAY_PARAM(cs35l56_shared_test_gpios_selftest,
                  cs35l56_shared_test_gpios_selftest_cases,
                  cs35l56_shared_test_gpio_param_desc);

static const struct cs35l56_shared_test_param cs35l56_shared_test_onchip_spkid_cases[] = {
        { .spkid_gpios = { 1, -1 },       .gpio_status = 0,                     .spkid = 0 },
        { .spkid_gpios = { 1, -1 },       .gpio_status = ~BIT(0),               .spkid = 0 },
        { .spkid_gpios = { 1, -1 },       .gpio_status = BIT(0),                .spkid = 1 },

        { .spkid_gpios = { 7, -1 },       .gpio_status = 0,                     .spkid = 0 },
        { .spkid_gpios = { 7, -1 },       .gpio_status = ~BIT(6),               .spkid = 0 },
        { .spkid_gpios = { 7, -1 },       .gpio_status = BIT(6),                .spkid = 1 },

        { .spkid_gpios = { 1, 7, -1 },    .gpio_status = 0,                     .spkid = 0 },
        { .spkid_gpios = { 1, 7, -1 },    .gpio_status = ~(BIT(0) | BIT(6)),    .spkid = 0 },
        { .spkid_gpios = { 1, 7, -1 },    .gpio_status = BIT(6),                .spkid = 1 },
        { .spkid_gpios = { 1, 7, -1 },    .gpio_status = BIT(0),                .spkid = 2 },
        { .spkid_gpios = { 1, 7, -1 },    .gpio_status = BIT(6) | BIT(0),       .spkid = 3 },

        { .spkid_gpios = { 7, 1, -1 },    .gpio_status = 0,                     .spkid = 0 },
        { .spkid_gpios = { 7, 1, -1 },    .gpio_status = ~(BIT(6) | BIT(0)),    .spkid = 0 },
        { .spkid_gpios = { 7, 1, -1 },    .gpio_status = BIT(0),                .spkid = 1 },
        { .spkid_gpios = { 7, 1, -1 },    .gpio_status = BIT(6),                .spkid = 2 },
        { .spkid_gpios = { 7, 1, -1 },    .gpio_status = BIT(6) | BIT(0),       .spkid = 3 },

        { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = 0,                        .spkid = 0 },
        { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(0),                   .spkid = 1 },
        { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(6),                   .spkid = 2 },
        { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(6) | BIT(0),          .spkid = 3 },
        { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(2),                   .spkid = 4 },
        { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(2) | BIT(0),          .spkid = 5 },
        { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(2) | BIT(6),          .spkid = 6 },
        { .spkid_gpios = { 3, 7, 1, -1 }, .gpio_status = BIT(2) | BIT(6) | BIT(0), .spkid = 7 },
};
KUNIT_ARRAY_PARAM(cs35l56_shared_test_onchip_spkid, cs35l56_shared_test_onchip_spkid_cases,
                  cs35l56_shared_test_gpio_param_desc);

static const struct cs35l56_shared_test_param cs35l56_shared_test_onchip_spkid_pull_cases[] = {
        { .spkid_gpios = { 1, -1 },             .spkid_pulls = { 1, -1 }, },
        { .spkid_gpios = { 1, -1 },             .spkid_pulls = { 2, -1 }, },

        { .spkid_gpios = { 7, -1 },             .spkid_pulls = { 1, -1 }, },
        { .spkid_gpios = { 7, -1 },             .spkid_pulls = { 2, -1 }, },

        { .spkid_gpios = { 1, 7, -1 },          .spkid_pulls = { 1, 1, -1 }, },
        { .spkid_gpios = { 1, 7, -1 },          .spkid_pulls = { 2, 2, -1 }, },

        { .spkid_gpios = { 7, 1, -1 },          .spkid_pulls = { 1, 1, -1 }, },
        { .spkid_gpios = { 7, 1, -1 },          .spkid_pulls = { 2, 2, -1 }, },

        { .spkid_gpios = { 3, 7, 1, -1 },       .spkid_pulls = { 1, 1, 1, -1 }, },
        { .spkid_gpios = { 3, 7, 1, -1 },       .spkid_pulls = { 2, 2, 2, -1 }, },
};
KUNIT_ARRAY_PARAM(cs35l56_shared_test_onchip_spkid_pull,
                  cs35l56_shared_test_onchip_spkid_pull_cases,
                  cs35l56_shared_test_gpio_param_desc);

static struct kunit_case cs35l56_shared_test_cases[] = {
        /* Tests for speaker id */
        KUNIT_CASE_PARAM(cs35l56_shared_test_mock_gpio_status_selftest,
                         cs35l56_shared_test_gpios_selftest_gen_params),
        KUNIT_CASE_PARAM(cs35l56_shared_test_get_onchip_speaker_id,
                         cs35l56_shared_test_onchip_spkid_gen_params),
        KUNIT_CASE_PARAM(cs35l56_shared_test_onchip_speaker_id_pad_config,
                         cs35l56_shared_test_onchip_spkid_gen_params),
        KUNIT_CASE_PARAM(cs35l56_shared_test_onchip_speaker_id_pad_config,
                         cs35l56_shared_test_onchip_spkid_pull_gen_params),
        KUNIT_CASE_PARAM(cs35l56_shared_test_stash_onchip_spkid_pins,
                         cs35l56_shared_test_onchip_spkid_pull_gen_params),
        KUNIT_CASE(cs35l56_shared_test_stash_onchip_spkid_pins_reject_invalid),
        KUNIT_CASE(cs35l56_shared_test_onchip_speaker_id_not_defined),
        { }
};

static struct kunit_suite cs35l56_shared_test_suite_L56_B0_sdw = {
        .name = "snd-soc-cs35l56-shared-test_L56_B0_sdw",
        .init = cs35l56_shared_test_case_regmap_init_L56_B0_sdw,
        .test_cases = cs35l56_shared_test_cases,
};

static struct kunit_suite cs35l56_shared_test_suite_L56_B2_sdw = {
        .name = "snd-soc-cs35l56-shared-test_L56_B2_sdw",
        .init = cs35l56_shared_test_case_regmap_init_L56_B2_sdw,
        .test_cases = cs35l56_shared_test_cases,
};

static struct kunit_suite cs35l56_shared_test_suite_L63_A1_sdw = {
        .name = "snd-soc-cs35l56-shared-test_L63_A1_sdw",
        .init = cs35l56_shared_test_case_regmap_init_L63_A1_sdw,
        .test_cases = cs35l56_shared_test_cases,
};

static struct kunit_suite cs35l56_shared_test_suite_L56_B0_spi = {
        .name = "snd-soc-cs35l56-shared-test_L56_B0_spi",
        .init = cs35l56_shared_test_case_regmap_init_L56_B0_spi,
        .test_cases = cs35l56_shared_test_cases,
};

static struct kunit_suite cs35l56_shared_test_suite_L56_B2_spi = {
        .name = "snd-soc-cs35l56-shared-test_L56_B2_spi",
        .init = cs35l56_shared_test_case_regmap_init_L56_B2_spi,
        .test_cases = cs35l56_shared_test_cases,
};

static struct kunit_suite cs35l56_shared_test_suite_L56_B0_i2c = {
        .name = "snd-soc-cs35l56-shared-test_L56_B0_i2c",
        .init = cs35l56_shared_test_case_regmap_init_L56_B0_i2c,
        .test_cases = cs35l56_shared_test_cases,
};

static struct kunit_suite cs35l56_shared_test_suite_L56_B2_i2c = {
        .name = "snd-soc-cs35l56-shared-test_L56_B2_i2c",
        .init = cs35l56_shared_test_case_regmap_init_L56_B2_i2c,
        .test_cases = cs35l56_shared_test_cases,
};

kunit_test_suites(
        &cs35l56_shared_test_suite_L56_B0_sdw,
        &cs35l56_shared_test_suite_L56_B2_sdw,
        &cs35l56_shared_test_suite_L63_A1_sdw,

        &cs35l56_shared_test_suite_L56_B0_spi,
        &cs35l56_shared_test_suite_L56_B2_spi,

        &cs35l56_shared_test_suite_L56_B0_i2c,
        &cs35l56_shared_test_suite_L56_B2_i2c,
);

MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED");
MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB");
MODULE_DESCRIPTION("KUnit test for cs35l56-shared module");
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
MODULE_LICENSE("GPL");