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 4/5] spi: spi-mux: pass child CS and word delays to the parent
Date: Tue, 3 Nov 2026 22:53:07 +0000 [thread overview]
Message-ID: <20261103225308.3633046-5-jctchan@gmail.com> (raw)
In-Reply-To: <20261103225308.3633046-1-jctchan@gmail.com>
From: Joseph CT Chan <josephctchan@google.com>
spi_mux_select() copies only max_speed_hz, mode and bits_per_word from
the child to the spi-mux device on the parent controller. The child's
cs_setup, cs_hold, cs_inactive and word_delay are never passed on, so
the parent controller uses the spi-mux device's own delays (usually
zero) for every child. A child that needs a CS setup time or a delay
between words, for example from spi-cs-setup-delay-ns in its device
tree node, does not get it when it sits behind a mux.
Save the spi-mux device's own delays at probe time, and when switching
to a child, merge them with the child's in a new helper,
spi_mux_merge_cs_delay(): use the longer CS delay of the two, so that
the needs of the mux and of the child are both met; on a tie the
spi-mux delay is kept. A delay given in SCK cycles cannot be converted
to time without a transfer, so it cannot be compared: a child delay in
SCK cycles is used when the spi-mux delay is zero, and otherwise the
spi-mux delay is kept and a warning is printed once. If only the
spi-mux delay is in SCK cycles, it is kept without a warning; device
tree only gives delays in ns or us, so this case does not come from
DT. The child's word_delay is used when it is set, else the spi-mux
device's own. The delays are written before spi_setup() on the parent,
so they reach both set_cs_timing() and the software CS delays in
spi_set_cs().
spi_mux_select() overwrites the delays of the spi-mux device, so put
its own delays back when the driver unbinds. Otherwise a later probe
would save the last child's merged delays as the spi-mux device's own.
Unlike a device directly on a controller, whose CS delays are read on
every message, the delays are now copied when the mux switches to the
child or after the child calls spi_setup(). A child that changes its
delays must call spi_setup() for the change to take effect.
This depends on patch 2 of this series ("spi: spi-mux: re-apply
settings after child calls spi_setup()"), which makes the copy happen
again after the child calls spi_setup().
Tested with KUnit (spi_mux: spi_mux_cs_delay_test and
spi_mux_settings_lifecycle_test, added later in this series) on ARCH=um;
they fail without this patch. Builds with W=1 on x86_64. Not tested on
hardware.
Developed with help from the Claude and Gemini AI assistants.
Fixes: e9e40543ad5b ("spi: Add generic SPI multiplexer")
Fixes: 8c33ebfeeb59 ("spi: move cs spi_delay to spi_device")
Signed-off-by: Joseph CT Chan <josephctchan@google.com>
---
v2: Updated the patch reference in the commit message after the split.
drivers/spi/spi-mux.c | 83 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/drivers/spi/spi-mux.c b/drivers/spi/spi-mux.c
index 0e770137b5bb..45d8920727e0 100644
--- a/drivers/spi/spi-mux.c
+++ b/drivers/spi/spi-mux.c
@@ -34,6 +34,10 @@
* @child_msg_dev: Used to store the spi_device pointer to the child
* @mux: mux_control structure used to provide chip selects for
* downstream spi devices
+ * @mux_cs_setup: CS setup delay of the spi-mux device itself
+ * @mux_cs_hold: CS hold delay of the spi-mux device itself
+ * @mux_cs_inactive: CS inactive delay of the spi-mux device itself
+ * @mux_word_delay: word delay of the spi-mux device itself
*/
struct spi_mux_priv {
struct spi_device *spi;
@@ -43,8 +47,55 @@ struct spi_mux_priv {
void *child_msg_context;
struct spi_device *child_msg_dev;
struct mux_control *mux;
+
+ struct spi_delay mux_cs_setup;
+ struct spi_delay mux_cs_hold;
+ struct spi_delay mux_cs_inactive;
+ struct spi_delay mux_word_delay;
};
+/*
+ * Pick the CS delay for the parent from the spi-mux device's own delay and
+ * the child's: the longer of the two, so that both are honored; on a tie the
+ * spi-mux delay is kept. A delay in SCK cycles cannot be converted to time
+ * without a transfer, so it cannot be compared:
+ * - a child delay in SCK cycles is used when the spi-mux delay is zero;
+ * otherwise the spi-mux delay is kept and a warning is printed once;
+ * - if only the spi-mux delay is in SCK cycles, it is kept. Device tree
+ * only gives delays in ns or us, so this case does not come from DT.
+ */
+static void spi_mux_merge_cs_delay(struct spi_mux_priv *priv,
+ struct spi_delay *dst,
+ struct spi_delay *mux,
+ struct spi_delay *child)
+{
+ int mux_ns, child_ns;
+
+ if (!child->value) {
+ *dst = *mux;
+ return;
+ }
+
+ mux_ns = spi_delay_to_ns(mux, NULL);
+ child_ns = spi_delay_to_ns(child, NULL);
+
+ if (mux_ns >= 0 && child_ns >= 0) {
+ *dst = child_ns > mux_ns ? *child : *mux;
+ return;
+ }
+
+ if (child_ns < 0) {
+ if (!mux->value) {
+ *dst = *child;
+ return;
+ }
+ dev_warn_once(&priv->spi->dev,
+ "child CS delay in SCK cycles ignored, using the mux delay\n");
+ }
+
+ *dst = *mux;
+}
+
/* should not get called when the parent controller is doing a transfer */
static int spi_mux_select(struct spi_device *spi)
{
@@ -65,6 +116,14 @@ static int spi_mux_select(struct spi_device *spi)
priv->spi->max_speed_hz = spi->max_speed_hz;
priv->spi->mode = spi->mode;
priv->spi->bits_per_word = spi->bits_per_word;
+ spi_mux_merge_cs_delay(priv, &priv->spi->cs_setup, &priv->mux_cs_setup,
+ &spi->cs_setup);
+ spi_mux_merge_cs_delay(priv, &priv->spi->cs_hold, &priv->mux_cs_hold,
+ &spi->cs_hold);
+ spi_mux_merge_cs_delay(priv, &priv->spi->cs_inactive,
+ &priv->mux_cs_inactive, &spi->cs_inactive);
+ priv->spi->word_delay = spi->word_delay.value ? spi->word_delay :
+ priv->mux_word_delay;
ret = spi_setup(priv->spi);
if (ret) {
@@ -156,10 +215,26 @@ static int spi_mux_transfer_one_message(struct spi_controller *ctlr,
return 0;
}
+/*
+ * spi_mux_select() overwrites the delays of the spi-mux device with the
+ * merged ones. Put its own delays back when the driver unbinds, so that a
+ * later probe does not take the last child's delays as its own.
+ */
+static void spi_mux_restore_delays(void *data)
+{
+ struct spi_mux_priv *priv = data;
+
+ priv->spi->cs_setup = priv->mux_cs_setup;
+ priv->spi->cs_hold = priv->mux_cs_hold;
+ priv->spi->cs_inactive = priv->mux_cs_inactive;
+ priv->spi->word_delay = priv->mux_word_delay;
+}
+
static int spi_mux_probe(struct spi_device *spi)
{
struct spi_controller *ctlr;
struct spi_mux_priv *priv;
+ int ret;
ctlr = devm_spi_alloc_host(&spi->dev, sizeof(*priv));
if (!ctlr)
@@ -168,6 +243,14 @@ static int spi_mux_probe(struct spi_device *spi)
spi_set_drvdata(spi, ctlr);
priv = spi_controller_get_devdata(ctlr);
priv->spi = spi;
+ priv->mux_cs_setup = spi->cs_setup;
+ priv->mux_cs_hold = spi->cs_hold;
+ priv->mux_cs_inactive = spi->cs_inactive;
+ priv->mux_word_delay = spi->word_delay;
+
+ ret = devm_add_action_or_reset(&spi->dev, spi_mux_restore_delays, priv);
+ if (ret)
+ return ret;
/*
* Increase lockdep class as these lock are taken while the parent bus
--
2.56.0.385.gac671c819a-goog
next prev 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 ` Joseph CT Chan [this message]
2026-11-03 22:53 ` [PATCH v2 5/5] spi: spi-mux: add KUnit tests for forwarding errors and settings Joseph CT Chan
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-5-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.