// 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/gpio/driver.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_mock_gpio {
	unsigned int pin_state;
	struct gpio_chip chip;
};

struct cs35l56_shared_test_priv {
	struct kunit *test;
	struct faux_device *amp_dev;
	struct faux_device *gpio_dev;
	struct cs35l56_shared_test_mock_gpio *gpio_priv;
	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 *)

KUNIT_DEFINE_ACTION_WRAPPER(device_remove_software_node_wrapper,
			    device_remove_software_node,
			    struct device *)

static int cs35l56_shared_test_mock_gpio_get_direction(struct gpio_chip *chip,
						       unsigned int offset)
{
	return GPIO_LINE_DIRECTION_IN;
}

static int cs35l56_shared_test_mock_gpio_direction_in(struct gpio_chip *chip,
						      unsigned int offset)
{
	return 0;
}

static int cs35l56_shared_test_mock_gpio_get(struct gpio_chip *chip, unsigned int offset)
{
	struct cs35l56_shared_test_mock_gpio *gpio_priv = gpiochip_get_data(chip);

	return !!(gpio_priv->pin_state & BIT(offset));
}

static const struct gpio_chip cs35l56_shared_test_mock_gpio_chip = {
	.label			= "cs35l56_shared_test_mock_gpio",
	.owner			= THIS_MODULE,
	.get_direction		= cs35l56_shared_test_mock_gpio_get_direction,
	.direction_input	= cs35l56_shared_test_mock_gpio_direction_in,
	.get			= cs35l56_shared_test_mock_gpio_get,
	.base			= -1,
	.ngpio			= 32,
};

/* software_node referencing the gpio driver */
static const struct software_node cs35l56_shared_test_mock_gpio_swnode = {
	.name = "cs35l56_shared_test_mock_gpio",
};

static int cs35l56_shared_test_mock_gpio_probe(struct faux_device *fdev)
{
	struct cs35l56_shared_test_mock_gpio *gpio_priv;
	struct device *dev = &fdev->dev;
	int ret;

	gpio_priv = devm_kzalloc(dev, sizeof(*gpio_priv), GFP_KERNEL);
	if (!gpio_priv)
		return -ENOMEM;

	ret = device_add_software_node(dev, &cs35l56_shared_test_mock_gpio_swnode);
	if (ret)
		return ret;

	ret = devm_add_action_or_reset(dev, device_remove_software_node_wrapper, dev);
	if (ret)
		return ret;

	/* GPIO core modifies our struct gpio_chip so use a copy */
	gpio_priv->chip = cs35l56_shared_test_mock_gpio_chip;
	gpio_priv->chip.parent = dev;
	ret = devm_gpiochip_add_data(dev, &gpio_priv->chip, gpio_priv);
	if (ret)
		return dev_err_probe(dev, ret, "Failed to add gpiochip\n");

	dev_set_drvdata(dev, gpio_priv);

	return 0;
}

static struct faux_device_ops cs35l56_shared_test_mock_gpio_drv = {
	.probe		= cs35l56_shared_test_mock_gpio_probe,
};

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

	priv->gpio_dev = faux_device_create("cs35l56_shared_test_mock_gpio", NULL,
					    &cs35l56_shared_test_mock_gpio_drv);
	KUNIT_ASSERT_NOT_NULL(test, priv->gpio_dev);
	KUNIT_ASSERT_EQ(test, 0,
			kunit_add_action_or_reset(test,
						  faux_device_destroy_wrapper,
						  priv->gpio_dev));

	priv->gpio_priv = dev_get_drvdata(&priv->gpio_dev->dev);
	KUNIT_ASSERT_NOT_NULL(test, priv->gpio_priv);
}

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);
}

/* simulate cs_amp_get_vendor_spkid() reading a vendor-specific ID of 1 */
static int cs35l56_shared_test_get_vendor_spkid_1(struct device *dev)
{
	return 1;
}

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

	/* Hook cs_amp_get_vendor_spkid() to return an ID of 1 */
	kunit_activate_static_stub(test, cs_amp_get_vendor_spkid,
				   cs35l56_shared_test_get_vendor_spkid_1);

	KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), 1);
}

static void cs35l56_shared_test_get_speaker_id_property(struct kunit *test)
{
	struct cs35l56_shared_test_priv *priv = test->priv;
	const struct property_entry dev_props[] = {
		PROPERTY_ENTRY_U32("cirrus,speaker-id", 2),
		{ }
	};
	const struct software_node dev_node = SOFTWARE_NODE("SPK1", dev_props, NULL);

	KUNIT_ASSERT_EQ(test, device_add_software_node(priv->cs35l56_base->dev, &dev_node), 0);
	KUNIT_ASSERT_EQ(test, 0,
			kunit_add_action_or_reset(test,
						  device_remove_software_node_wrapper,
						  priv->cs35l56_base->dev));

	KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), 2);
}

/*
 * Create software nodes equivalent to ACPI structure
 *
 * Device(GSPK) {
 *	Name(_DSD, ...) {
 *	Package() {
 *		cs-gpios {
 *			GPIO, n, 0,
 *			...
 *		}
 *	}
 */
static void _cs35l56_shared_test_create_spkid_swnode(struct kunit *test,
						     struct device *dev,
						     const struct software_node_ref_args *args,
						     int num_args)
{
	struct cs35l56_shared_test_priv *priv = test->priv;
	const struct property_entry props_template[] = {
		PROPERTY_ENTRY_REF_ARRAY_LEN("spk-id-gpios", args, num_args),
		{ }
	};
	struct property_entry *props;
	struct software_node *node;

	props = kunit_kzalloc(test, sizeof(props_template), GFP_KERNEL);
	KUNIT_ASSERT_NOT_NULL(test, props);
	memcpy(props, props_template, sizeof(props_template));

	node = kunit_kzalloc(test, sizeof(*node), GFP_KERNEL);
	KUNIT_ASSERT_NOT_NULL(test, node);
	*node = SOFTWARE_NODE("GSPK", props, NULL);

	KUNIT_ASSERT_EQ(test, device_add_software_node(dev, node), 0);
	KUNIT_ASSERT_EQ(test, 0,
			kunit_add_action_or_reset(test,
						  device_remove_software_node_wrapper,
						  priv->cs35l56_base->dev));
}

static void cs35l56_shared_test_get_speaker_id_from_host_gpio(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;
	struct software_node_ref_args *ref;
	int i;

	if (!IS_REACHABLE(CONFIG_GPIOLIB)) {
		kunit_skip(test, "Requires CONFIG_GPIOLIB");
		return;
	}

	_cs35l56_shared_test_create_dummy_gpio(test);

	ref = kunit_kcalloc(test, ARRAY_SIZE(param->spkid_gpios), sizeof(*ref), GFP_KERNEL);
	KUNIT_ASSERT_NOT_NULL(test, ref);

	for (i = 0; param->spkid_gpios[i] >= 0; i++) {
		ref[i] = SOFTWARE_NODE_REFERENCE(&cs35l56_shared_test_mock_gpio_swnode,
						 param->spkid_gpios[i], 0);
	}
	_cs35l56_shared_test_create_spkid_swnode(test, cs35l56_base->dev, ref, i);

	priv->gpio_priv->pin_state = param->gpio_status;
	KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), param->spkid);
}

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);

/* Note: spk-id-gpios property bit order is LSbit...MSbit */
static const struct cs35l56_shared_test_param cs35l56_shared_test_host_gpio_spkid_cases[] = {
	{ .spkid_gpios = { 0, -1 },	  .gpio_status = 0,			.spkid = 0 },
	{ .spkid_gpios = { 0, -1 },	  .gpio_status = ~BIT(0),		.spkid = 0 },
	{ .spkid_gpios = { 0, -1 },	  .gpio_status = BIT(0),		.spkid = 1 },

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

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

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

	{ .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = 0,			   .spkid = 0 },
	{ .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(0),		   .spkid = 1 },
	{ .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(6),		   .spkid = 2 },
	{ .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(6) | BIT(0),	   .spkid = 3 },
	{ .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(2),		   .spkid = 4 },
	{ .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(2) | BIT(0),	   .spkid = 5 },
	{ .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(2) | BIT(6),	   .spkid = 6 },
	{ .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(2) | BIT(6) | BIT(0), .spkid = 7 },
};
KUNIT_ARRAY_PARAM(cs35l56_shared_test_host_gpio_spkid, cs35l56_shared_test_host_gpio_spkid_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),

	KUNIT_CASE(cs35l56_shared_test_get_speaker_id_vendor),
	KUNIT_CASE(cs35l56_shared_test_get_speaker_id_property),
	KUNIT_CASE_PARAM_ATTR(cs35l56_shared_test_get_speaker_id_from_host_gpio,
			      cs35l56_shared_test_host_gpio_spkid_gen_params,
			      { KUNIT_SPEED_SLOW }),

	{ }
};

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");
