Linux SPI subsystem development
 help / color / mirror / Atom feed
From: Joseph CT Chan <jctchan@gmail.com>
To: Mark Brown <broonie@kernel.org>
Cc: linux-spi@vger.kernel.org, David Lechner <dlechner@baylibre.com>,
	Bart Van Assche <bvanassche@acm.org>,
	Joseph CT Chan <josephctchan@google.com>
Subject: [PATCH v2 5/5] spi: spi-mux: add KUnit tests for forwarding errors and settings
Date: Tue,  3 Nov 2026 22:53:08 +0000	[thread overview]
Message-ID: <20261103225308.3633046-6-jctchan@gmail.com> (raw)
In-Reply-To: <20261103225308.3633046-1-jctchan@gmail.com>

From: Joseph CT Chan <josephctchan@google.com>

Add a KUnit test suite (spi_mux) with a Device Tree overlay that
instantiates a fake mux controller, a fake parent SPI controller, the
real spi-mux driver, and two child SPI devices.

The suite covers three areas:
- spi_mux_error_test: forwarding errors (locked parent bus, invalid
  transfer length, mux select failure, and parent spi_setup() failure),
  verifying that the message is finalized, the mux is released, the
  caller's message fields and optimize state are restored, and
  subsequent transfers succeed.
- spi_mux_cs_delay_test: CS setup, hold, and inactive delay merging
  across ns, us, and SCK units, including zero-value SCK delays.
- spi_mux_settings_lifecycle_test: re-applying child mode, speed,
  bits_per_word, and delay changes after spi_setup(), switching
  between children with different delays, and restoring the spi-mux
  device's own delays on driver unbind.

Developed with help from the Claude and Gemini AI assistants.

Signed-off-by: Joseph CT Chan <josephctchan@google.com>
---
v2: Designated initializers for the test tables, trimmed the comments.

 drivers/spi/Kconfig                  |  14 +
 drivers/spi/Makefile                 |   1 +
 drivers/spi/tests/.kunitconfig       |   4 +
 drivers/spi/tests/Makefile           |   2 +
 drivers/spi/tests/kunit_spi_mux.dtso |  42 +++
 drivers/spi/tests/spi-mux-kunit.c    | 506 +++++++++++++++++++++++++++
 6 files changed, 569 insertions(+)
 create mode 100644 drivers/spi/tests/kunit_spi_mux.dtso
 create mode 100644 drivers/spi/tests/spi-mux-kunit.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index feb9265b7d0f..9a36bf9cf94f 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -1386,6 +1386,20 @@ config SPI_MUX
 	  to be chip selects on this controller. It is still necessary to
 	  select one or more specific mux-controller drivers.
 
+config SPI_MUX_KUNIT_TEST
+	tristate "KUnit tests for spi-mux" if !KUNIT_ALL_TESTS
+	depends on KUNIT && SPI_MUX && OF_OVERLAY
+	select DTC
+	default KUNIT_ALL_TESTS
+	help
+	  Build KUnit tests for the SPI multiplexer driver. A device tree
+	  overlay adds a fake parent controller, a test mux chip and an
+	  spi-mux device with two children. The tests check that a message
+	  which cannot be forwarded is completed with an error and releases
+	  the mux, and that child settings and delays reach the parent.
+
+	  If unsure say N.
+
 #
 # There are lots of SPI device types, with sensors and memory
 # being probably the most widely used ones.
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 24497d3936f6..9826626995a9 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_SPI_LOOPBACK_TEST)		+= spi-loopback-test.o
 obj-$(CONFIG_SPI_DMA_KUNIT_TEST)	+= tests/
 obj-$(CONFIG_SPI_CORE_KUNIT_TEST)	+= tests/
 obj-$(CONFIG_SPI_TI_QSPI_KUNIT_TEST)	+= tests/
+obj-$(CONFIG_SPI_MUX_KUNIT_TEST)	+= tests/
 
 # SPI master controller drivers (bus)
 obj-$(CONFIG_SPI_AIROHA_SNFI)		+= spi-airoha-snfi.o
diff --git a/drivers/spi/tests/.kunitconfig b/drivers/spi/tests/.kunitconfig
index 354bafc42c2b..1631a16ce6ec 100644
--- a/drivers/spi/tests/.kunitconfig
+++ b/drivers/spi/tests/.kunitconfig
@@ -6,3 +6,7 @@ CONFIG_SPI_CORE_KUNIT_TEST=y
 CONFIG_COMPILE_TEST=y
 CONFIG_SPI_TI_QSPI=y
 CONFIG_SPI_TI_QSPI_KUNIT_TEST=y
+CONFIG_OF=y
+CONFIG_OF_OVERLAY=y
+CONFIG_SPI_MUX=y
+CONFIG_SPI_MUX_KUNIT_TEST=y
diff --git a/drivers/spi/tests/Makefile b/drivers/spi/tests/Makefile
index a5a8fc9435d4..0ec04ecd4d55 100644
--- a/drivers/spi/tests/Makefile
+++ b/drivers/spi/tests/Makefile
@@ -3,3 +3,5 @@
 obj-$(CONFIG_SPI_DMA_KUNIT_TEST) += spi-dma-kunit.o
 obj-$(CONFIG_SPI_CORE_KUNIT_TEST) += spi-core-kunit.o
 obj-$(CONFIG_SPI_TI_QSPI_KUNIT_TEST) += spi-ti-qspi-kunit.o
+obj-$(CONFIG_SPI_MUX_KUNIT_TEST) += spi-mux-kunit-test.o
+spi-mux-kunit-test-y := spi-mux-kunit.o kunit_spi_mux.dtbo.o
diff --git a/drivers/spi/tests/kunit_spi_mux.dtso b/drivers/spi/tests/kunit_spi_mux.dtso
new file mode 100644
index 000000000000..48e72ba8e217
--- /dev/null
+++ b/drivers/spi/tests/kunit_spi_mux.dtso
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+&{/} {
+	tmux: kunit-spi-mux-chip {
+		compatible = "test,spi-mux-kunit-chip";
+		#mux-control-cells = <0>;
+	};
+
+	kunit-spi-mux-parent {
+		compatible = "test,spi-mux-kunit-parent";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		spi@0 {
+			compatible = "spi-mux";
+			reg = <0>;
+			spi-max-frequency = <1000000>;
+			spi-cs-setup-delay-ns = <1000>;
+			spi-cs-hold-delay-ns = <300>;
+			mux-controls = <&tmux>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			device@0 {
+				compatible = "test,spi-mux-kunit-child";
+				reg = <0>;
+				spi-max-frequency = <1000000>;
+			};
+
+			device@1 {
+				compatible = "test,spi-mux-kunit-child";
+				reg = <1>;
+				spi-max-frequency = <2000000>;
+				spi-cs-setup-delay-ns = <1500>;
+				spi-cs-hold-delay-ns = <100>;
+				spi-cs-inactive-delay-ns = <700>;
+			};
+		};
+	};
+};
diff --git a/drivers/spi/tests/spi-mux-kunit.c b/drivers/spi/tests/spi-mux-kunit.c
new file mode 100644
index 000000000000..f1661bb19f31
--- /dev/null
+++ b/drivers/spi/tests/spi-mux-kunit.c
@@ -0,0 +1,506 @@
+// SPDX-License-Identifier: GPL-2.0
+// KUnit tests for spi-mux error handling and settings forwarding.
+#include <kunit/of.h>
+#include <kunit/platform_device.h>
+#include <kunit/test.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/kthread.h>
+#include <linux/mux/consumer.h>
+#include <linux/mux/driver.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+
+#define PFX "/kunit-spi-mux-parent/spi@0"
+
+struct spi_mux_test_ctx {
+	struct mux_control *mux;
+	struct spi_controller *parent;
+	struct spi_device *child[2], *mux_spi;
+	int opt_cnt, unopt_cnt, mux_err, setup_err;
+	u32 last_speed_hz, last_mode;
+	u8 last_bpw;
+	struct spi_delay last_w, last_s, last_h, last_in;
+	struct spi_transfer xfer;
+	struct spi_message msg;
+	struct completion done;
+	u8 tx[4];
+};
+
+static struct spi_mux_test_ctx *g_mux_ctx;
+
+static void kunit_done(void *p)
+{
+	complete(p);
+}
+
+/* DT defaults: mux_spi (1000, 300, 0, 0), child[1] (1500, 100, 700, 0). */
+static void check_delays(struct kunit *test, struct spi_mux_test_ctx *ctx,
+			 u16 s, u16 h, u16 in, u16 w)
+{
+	KUNIT_EXPECT_EQ(test, ctx->last_s.value, s);
+	KUNIT_EXPECT_EQ(test, ctx->last_h.value, h);
+	KUNIT_EXPECT_EQ(test, ctx->last_in.value, in);
+	KUNIT_EXPECT_EQ(test, ctx->last_w.value, w);
+}
+
+static void check_d(struct kunit *test, struct spi_delay d, struct spi_delay exp)
+{
+	KUNIT_EXPECT_EQ(test, d.value, exp.value);
+	KUNIT_EXPECT_EQ(test, d.unit, exp.unit);
+}
+
+static void check_msg_restored(struct kunit *test, struct spi_mux_test_ctx *ctx,
+			       struct spi_device *spi)
+{
+	kthread_flush_worker(spi->controller->kworker);
+	kthread_flush_worker(ctx->parent->kworker);
+	/* Mux semaphore must be released after a failed transfer. */
+	KUNIT_ASSERT_EQ(test, 0, mux_control_try_select(ctx->mux, 0));
+	mux_control_deselect(ctx->mux);
+	/* Child caller's spi_message fields must be restored. */
+	KUNIT_EXPECT_PTR_EQ(test, ctx->msg.spi, spi);
+	KUNIT_EXPECT_PTR_EQ(test, ctx->msg.complete, &kunit_done);
+	KUNIT_EXPECT_PTR_EQ(test, ctx->msg.context, &ctx->done);
+	/* Parent message optimization state and split transfers must be cleaned up. */
+	KUNIT_EXPECT_FALSE(test, ctx->msg.optimized);
+	KUNIT_EXPECT_TRUE(test, list_empty(&ctx->msg.resources));
+	KUNIT_EXPECT_EQ(test, ctx->opt_cnt, ctx->unopt_cnt);
+}
+
+static int spi_mux_test_chip_set(struct mux_control *mux, int state)
+{
+	return g_mux_ctx ? READ_ONCE(g_mux_ctx->mux_err) : 0;
+}
+
+static const struct mux_control_ops spi_mux_test_chip_ops = {
+	.set = spi_mux_test_chip_set,
+};
+
+static int spi_mux_test_parent_opt(struct spi_message *msg)
+{
+	if (!g_mux_ctx)
+		return -ENODEV;
+	g_mux_ctx->opt_cnt++;
+	return 0;
+}
+
+static int spi_mux_test_parent_unopt(struct spi_message *msg)
+{
+	if (!g_mux_ctx)
+		return -ENODEV;
+	g_mux_ctx->unopt_cnt++;
+	return 0;
+}
+
+static int spi_mux_test_parent_setup(struct spi_device *spi)
+{
+	if (!g_mux_ctx)
+		return -ENODEV;
+	return xchg(&g_mux_ctx->setup_err, 0);
+}
+
+static void spi_mux_test_parent_set_cs(struct spi_device *spi, bool enable)
+{
+	if (!g_mux_ctx)
+		return;
+	g_mux_ctx->last_mode = spi->mode;
+	g_mux_ctx->last_s = spi->cs_setup;
+	g_mux_ctx->last_h = spi->cs_hold;
+	g_mux_ctx->last_in = spi->cs_inactive;
+}
+
+static int spi_mux_parent_xfer(struct spi_controller *c, struct spi_device *s,
+			       struct spi_transfer *x)
+{
+	if (!g_mux_ctx)
+		return -ENODEV;
+	g_mux_ctx->last_speed_hz = x->speed_hz;
+	g_mux_ctx->last_bpw = x->bits_per_word;
+	g_mux_ctx->last_w = x->word_delay;
+	return 0;
+}
+
+static size_t spi_mux_test_parent_max_transfer_size(struct spi_device *spi)
+{
+	return 2;
+}
+
+static int spi_mux_test_probe(struct platform_device *pdev)
+{
+	struct spi_controller *ctlr;
+	struct mux_chip *chip;
+
+	if (!g_mux_ctx)
+		return -ENODEV;
+	if (of_device_is_compatible(pdev->dev.of_node, "test,spi-mux-kunit-chip")) {
+		chip = devm_mux_chip_alloc(&pdev->dev, 1, 0);
+		if (IS_ERR(chip))
+			return PTR_ERR(chip);
+		chip->ops = &spi_mux_test_chip_ops;
+		chip->mux[0].states = 2;
+		g_mux_ctx->mux = &chip->mux[0];
+		return devm_mux_chip_register(&pdev->dev, chip);
+	}
+	ctlr = devm_spi_alloc_host(&pdev->dev, 0);
+	if (!ctlr)
+		return -ENOMEM;
+	ctlr->bus_num = -1;
+	ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_CS_WORD;
+	ctlr->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);
+	ctlr->max_transfer_size = spi_mux_test_parent_max_transfer_size;
+	ctlr->setup = spi_mux_test_parent_setup;
+	ctlr->set_cs = spi_mux_test_parent_set_cs;
+	ctlr->transfer_one = spi_mux_parent_xfer;
+	ctlr->optimize_message = spi_mux_test_parent_opt;
+	ctlr->unoptimize_message = spi_mux_test_parent_unopt;
+	g_mux_ctx->parent = ctlr;
+	return devm_spi_register_controller(&pdev->dev, ctlr);
+}
+
+static const struct of_device_id spi_mux_test_match[] = {
+	{ .compatible = "test,spi-mux-kunit-chip" },
+	{ .compatible = "test,spi-mux-kunit-parent" },
+	{}
+};
+
+static struct platform_driver spi_mux_test_driver = {
+	.probe = spi_mux_test_probe,
+	.driver = {
+		.name = "spi-mux-kunit-driver",
+		.of_match_table = spi_mux_test_match,
+		.probe_type = PROBE_FORCE_SYNCHRONOUS,
+	},
+};
+
+KUNIT_DEFINE_ACTION_WRAPPER(spi_mux_test_put_dev, put_device, struct device *);
+
+static struct spi_device *spi_mux_test_find_dev(struct kunit *test, const char *path)
+{
+	struct device_node *np = of_find_node_by_path(path);
+	struct device *dev = bus_find_device_by_of_node(&spi_bus_type, np);
+
+	of_node_put(np);
+	KUNIT_ASSERT_NOT_NULL(test, dev);
+	KUNIT_ASSERT_EQ(test, 0, kunit_add_action_or_reset(test, spi_mux_test_put_dev, dev));
+	return to_spi_device(dev);
+}
+
+static int spi_mux_test_init(struct kunit *test)
+{
+	struct spi_mux_test_ctx *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+	test->priv = ctx;
+	g_mux_ctx = ctx;
+	init_completion(&ctx->done);
+	ctx->xfer.tx_buf = ctx->tx;
+	ctx->xfer.len = sizeof(ctx->tx);
+	spi_message_init_with_transfers(&ctx->msg, &ctx->xfer, 1);
+	ctx->msg.complete = kunit_done;
+	ctx->msg.context = &ctx->done;
+	KUNIT_ASSERT_EQ(test, 0, kunit_platform_driver_register(test, &spi_mux_test_driver));
+	KUNIT_ASSERT_EQ(test, 0, of_overlay_apply_kunit(test, kunit_spi_mux));
+	ctx->child[0] = spi_mux_test_find_dev(test, PFX "/device@0");
+	ctx->child[1] = spi_mux_test_find_dev(test, PFX "/device@1");
+	ctx->mux_spi = to_spi_device(ctx->child[0]->controller->dev.parent);
+	return 0;
+}
+
+static void spi_mux_test_exit(struct kunit *test)
+{
+	struct spi_mux_test_ctx *ctx = test->priv;
+
+	if (ctx) {
+		if (ctx->child[0])
+			kthread_flush_worker(ctx->child[0]->controller->kworker);
+		if (ctx->parent)
+			kthread_flush_worker(ctx->parent->kworker);
+		if (ctx->mux) {
+			int ret;
+
+			ctx->mux_err = 0;
+			ret = mux_control_try_select(ctx->mux, 0);
+			if (!ret || ret == -EBUSY)
+				mux_control_deselect(ctx->mux);
+		}
+	}
+	g_mux_ctx = NULL;
+}
+
+static int spi_mux_test_send(struct kunit *test, struct spi_device *spi,
+			     unsigned int len, u8 bpw)
+{
+	struct spi_mux_test_ctx *ctx = test->priv;
+	int ret;
+
+	reinit_completion(&ctx->done);
+	ctx->xfer.len = len;
+	ctx->xfer.bits_per_word = bpw;
+	ctx->xfer.speed_hz = 0;
+	ctx->xfer.word_delay = (struct spi_delay){ 0 };
+	ctx->msg.status = -EINPROGRESS;
+	ctx->msg.actual_length = 0;
+	ctx->msg.spi = spi;
+	ret = spi_async(spi, &ctx->msg);
+	if (ret)
+		return ret;
+	return wait_for_completion_timeout(&ctx->done, msecs_to_jiffies(5000)) ?
+		ctx->msg.status : -ETIMEDOUT;
+}
+
+static int spi_mux_test_send_locked(struct kunit *test, struct spi_device *spi,
+				    struct spi_controller *parent,
+				    unsigned int len, u8 bpw)
+{
+	int ret;
+
+	spi_bus_lock(parent);
+	ret = spi_mux_test_send(test, spi, len, bpw);
+	spi_bus_unlock(parent);
+
+	return ret;
+}
+
+static void send_4b(struct kunit *test, struct spi_device *spi)
+{
+	struct spi_mux_test_ctx *ctx = test->priv;
+
+	KUNIT_ASSERT_EQ(test, 0, spi_mux_test_send(test, spi, 4, 0));
+	kthread_flush_worker(spi->controller->kworker);
+	kthread_flush_worker(ctx->parent->kworker);
+}
+
+static void check_sync_resend(struct kunit *test, struct spi_device *spi, u32 exp_hz)
+{
+	struct spi_mux_test_ctx *ctx = test->priv;
+
+	ctx->xfer.len = 4;
+	ctx->xfer.bits_per_word = 0;
+	ctx->xfer.speed_hz = 0;
+	ctx->msg.spi = spi;
+	KUNIT_EXPECT_EQ(test, 0, spi_sync(spi, &ctx->msg));
+	KUNIT_EXPECT_EQ(test, ctx->msg.actual_length, 4);
+	KUNIT_EXPECT_EQ(test, ctx->last_speed_hz, exp_hz);
+}
+
+enum spi_mux_err_type {
+	ERR_LOCKED,
+	ERR_ODD_LEN,
+	ERR_SELECT,
+	ERR_SETUP,
+};
+
+struct spi_mux_err_case {
+	const char *name;
+	enum spi_mux_err_type type;
+	int exp;
+};
+
+static const struct spi_mux_err_case spi_mux_err_cases[] = {
+	{
+		.name = "bus_locked",
+		.type = ERR_LOCKED,
+		.exp = -EBUSY,
+	},
+	{
+		.name = "rejects_odd_len",
+		.type = ERR_ODD_LEN,
+		.exp = -EINVAL,
+	},
+	{
+		.name = "select_fails",
+		.type = ERR_SELECT,
+		.exp = -EIO,
+	},
+	{
+		.name = "parent_setup_fails",
+		.type = ERR_SETUP,
+		.exp = -EIO,
+	},
+};
+
+struct spi_mux_delay_case {
+	const char *name;
+	u8 child;
+	struct spi_delay set_s, set_h, set_in, exp_s, exp_h, exp_in;
+};
+
+#define NS(v)	{ .value = (v), .unit = SPI_DELAY_UNIT_NSECS }
+#define US(v)	{ .value = (v), .unit = SPI_DELAY_UNIT_USECS }
+#define SCK(v)	{ .value = (v), .unit = SPI_DELAY_UNIT_SCK }
+
+static const struct spi_mux_delay_case spi_mux_delay_cases[] = {
+	{
+		.name = "longer",
+		.child = 1,
+		.set_h = NS(500),
+		.exp_s = NS(1500),
+		.exp_h = NS(500),
+		.exp_in = NS(700),
+	},
+	{
+		.name = "tie",
+		.child = 0,
+		.set_s = US(1),
+		.set_in = NS(200),
+		.exp_s = NS(1000),
+		.exp_h = NS(300),
+		.exp_in = NS(200),
+	},
+	{
+		.name = "sck_zero",
+		.child = 0,
+		.set_in = SCK(4),
+		.exp_s = NS(1000),
+		.exp_h = NS(300),
+		.exp_in = SCK(4),
+	},
+	{
+		.name = "sck_set",
+		.child = 0,
+		.set_s = SCK(4),
+		.set_h = NS(500),
+		.exp_s = NS(1000),
+		.exp_h = NS(500),
+	},
+};
+
+static void spi_mux_err_desc(const struct spi_mux_err_case *p, char *desc)
+{
+	strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(spi_mux_err, spi_mux_err_cases, spi_mux_err_desc);
+
+static void spi_mux_delay_desc(const struct spi_mux_delay_case *p, char *desc)
+{
+	strscpy(desc, p->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(spi_mux_delay, spi_mux_delay_cases, spi_mux_delay_desc);
+
+static void spi_mux_error_test(struct kunit *test)
+{
+	const struct spi_mux_err_case *p = test->param_value;
+	struct spi_mux_test_ctx *ctx = test->priv;
+	int child = (p->type == ERR_SELECT || p->type == ERR_SETUP);
+	int len = (p->type == ERR_ODD_LEN) ? 3 : 4;
+	u8 bpw = (p->type == ERR_ODD_LEN) ? 16 : 0;
+	int ret;
+
+	/* Prime child[0] so child[1] forces a channel switch and parent setup. */
+	if (p->type >= ERR_SELECT) {
+		send_4b(test, ctx->child[0]);
+		ctx->mux_err = (p->type == ERR_SELECT) ? -EIO : 0;
+		ctx->setup_err = (p->type == ERR_SETUP) ? -EIO : 0;
+	}
+
+	if (p->type == ERR_LOCKED)
+		ret = spi_mux_test_send_locked(test, ctx->child[child], ctx->parent, len, bpw);
+	else
+		ret = spi_mux_test_send(test, ctx->child[child], len, bpw);
+
+	ctx->mux_err = 0;
+	ctx->setup_err = 0;
+	if (ret == -ETIMEDOUT) {
+		kthread_flush_worker(ctx->child[child]->controller->kworker);
+		kthread_flush_worker(ctx->parent->kworker);
+	}
+	KUNIT_ASSERT_EQ(test, ret, p->exp);
+	check_msg_restored(test, ctx, ctx->child[child]);
+	/* Failed setup on child[1] must invalidate current_cs so child[0] re-applies 1 MHz. */
+	if (p->type == ERR_SETUP)
+		check_sync_resend(test, ctx->child[0], 1000000);
+	check_sync_resend(test, ctx->child[child], child ? 2000000 : 1000000);
+}
+
+static void spi_mux_cs_delay_test(struct kunit *test)
+{
+	const struct spi_mux_delay_case *p = test->param_value;
+	struct spi_mux_test_ctx *ctx = test->priv;
+	struct spi_device *spi = ctx->child[p->child];
+
+	/* Prime child[1] cs_hold = 500 ns first to verify child[0] resets cs_hold to 300 ns. */
+	if (!p->child) {
+		ctx->child[1]->cs_hold = (struct spi_delay)NS(500);
+		send_4b(test, ctx->child[1]);
+	}
+	spi->cs_setup = p->set_s.value ? p->set_s : spi->cs_setup;
+	spi->cs_hold = p->set_h.value ? p->set_h : spi->cs_hold;
+	spi->cs_inactive = p->set_in.value ? p->set_in : spi->cs_inactive;
+	send_4b(test, spi);
+	check_d(test, ctx->last_s, p->exp_s);
+	check_d(test, ctx->last_h, p->exp_h);
+	check_d(test, ctx->last_in, p->exp_in);
+}
+
+static void spi_mux_settings_lifecycle_test(struct kunit *test)
+{
+	struct spi_mux_test_ctx *ctx = test->priv;
+	struct device *dev = &ctx->mux_spi->dev;
+	struct spi_device *spi = ctx->child[0];
+
+	/* Live spi_setup() on the active child must invalidate current_cs and re-apply settings. */
+	send_4b(test, spi);
+	KUNIT_EXPECT_EQ(test, ctx->opt_cnt, 1);
+	KUNIT_EXPECT_EQ(test, ctx->unopt_cnt, 1);
+	spi->mode ^= SPI_CS_HIGH;
+	spi->max_speed_hz = 500000;
+	spi->bits_per_word = 16;
+	spi->cs_setup = (struct spi_delay)NS(1200);
+	KUNIT_ASSERT_EQ(test, 0, spi_setup(spi));
+	send_4b(test, spi);
+	KUNIT_EXPECT_EQ(test, ctx->last_speed_hz, 500000);
+	KUNIT_EXPECT_EQ(test, ctx->last_bpw, 16);
+	KUNIT_EXPECT_TRUE(test, !!(ctx->last_mode & SPI_CS_HIGH));
+	KUNIT_EXPECT_EQ(test, ctx->last_s.value, 1200);
+	spi->cs_setup = (struct spi_delay){};
+	KUNIT_ASSERT_EQ(test, 0, spi_setup(spi));
+	/* Switching between children must merge CS delays and forward word_delay. */
+	ctx->child[1]->cs_hold = (struct spi_delay)NS(500);
+	ctx->child[1]->word_delay = (struct spi_delay)US(2);
+	ctx->child[1]->cs_inactive = (struct spi_delay)NS(700);
+	send_4b(test, ctx->child[1]);
+	KUNIT_EXPECT_EQ(test, ctx->last_bpw, 8);
+	check_delays(test, ctx, 1500, 500, 700, 2);
+	send_4b(test, ctx->child[0]);
+	check_delays(test, ctx, 1000, 300, 0, 0);
+	send_4b(test, ctx->child[1]);
+	check_delays(test, ctx, 1500, 500, 700, 2);
+	kthread_flush_worker(spi->controller->kworker);
+	kthread_flush_worker(ctx->parent->kworker);
+	ctx->child[0] = NULL;
+	ctx->child[1] = NULL;
+	/* Unbinding spi-mux must restore mux_spi's own DT delays before re-probing. */
+	device_release_driver(dev);
+	KUNIT_EXPECT_EQ(test, ctx->mux_spi->cs_setup.value, 1000);
+	KUNIT_EXPECT_EQ(test, ctx->mux_spi->cs_hold.value, 300);
+	KUNIT_EXPECT_EQ(test, ctx->mux_spi->cs_inactive.value, 0);
+	KUNIT_EXPECT_EQ(test, ctx->mux_spi->word_delay.value, 0);
+	KUNIT_ASSERT_EQ(test, 1, device_attach(dev));
+	ctx->child[0] = spi_mux_test_find_dev(test, PFX "/device@0");
+	ctx->child[1] = spi_mux_test_find_dev(test, PFX "/device@1");
+	send_4b(test, ctx->child[0]);
+	check_delays(test, ctx, 1000, 300, 0, 0);
+}
+
+static struct kunit_case spi_mux_test_cases[] = {
+	KUNIT_CASE_PARAM(spi_mux_error_test, spi_mux_err_gen_params),
+	KUNIT_CASE_PARAM(spi_mux_cs_delay_test, spi_mux_delay_gen_params),
+	KUNIT_CASE(spi_mux_settings_lifecycle_test),
+	{}
+};
+
+static struct kunit_suite spi_mux_test_suite = {
+	.name = "spi_mux",
+	.init = spi_mux_test_init,
+	.exit = spi_mux_test_exit,
+	.test_cases = spi_mux_test_cases,
+};
+
+kunit_test_suite(spi_mux_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for spi-mux");
+MODULE_LICENSE("GPL");
-- 
2.56.0.385.gac671c819a-goog


  parent reply	other threads:[~2026-11-03 22:53 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 22:53 [PATCH v2 0/5] spi: spi-mux: error handling and delay fixes Joseph CT Chan
2026-11-03 22:53 ` [PATCH v2 1/5] spi: spi-mux: release mux when parent setup fails Joseph CT Chan
2026-11-03 22:53 ` [PATCH v2 2/5] spi: spi-mux: re-apply settings after child calls spi_setup() Joseph CT Chan
2026-11-03 22:53 ` [PATCH v2 3/5] spi: spi-mux: finalize message when forwarding fails Joseph CT Chan
2026-11-03 22:53 ` [PATCH v2 4/5] spi: spi-mux: pass child CS and word delays to the parent Joseph CT Chan
2026-11-03 22:53 ` Joseph CT Chan [this message]
2026-11-04 15:00 ` [PATCH v2 0/5] spi: spi-mux: error handling and delay fixes Mark Brown
2026-11-05  4:03   ` Joseph CT Chan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261103225308.3633046-6-jctchan@gmail.com \
    --to=jctchan@gmail.com \
    --cc=broonie@kernel.org \
    --cc=bvanassche@acm.org \
    --cc=dlechner@baylibre.com \
    --cc=josephctchan@google.com \
    --cc=linux-spi@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox