From: Joseph CT Chan <jctchan@gmail.com>
To: Mark Brown <broonie@kernel.org>
Cc: Bart Van Assche <bvanassche@acm.org>,
David Lechner <dlechner@baylibre.com>,
Conor Dooley <conor.dooley@microchip.com>,
linux-spi@vger.kernel.org,
Joseph CT Chan <josephctchan@google.com>
Subject: [PATCH 06/14] spi: spi-mux: pass child CS and word delays to the parent
Date: Mon, 2 Nov 2026 16:30:15 +0000 [thread overview]
Message-ID: <20261102163023.2574632-7-jctchan@gmail.com> (raw)
In-Reply-To: <20261102163023.2574632-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 3 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.
Assisted-by: Claude, Gemini
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>
---
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.317.g454f8302e1-goog
next prev parent reply other threads:[~2026-11-02 16:33 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 16:30 [PATCH 00/14] spi: spi-mux, core, ti-qspi: error handling, delay fixes, KUnit tests, and lock context annotations Joseph CT Chan
2026-11-02 16:30 ` [PATCH 01/14] spi: unoptimize message when queuing it fails Joseph CT Chan
2026-11-02 17:00 ` David Lechner
2026-11-02 16:30 ` [PATCH 02/14] spi: spi-mux: release mux when parent setup fails Joseph CT Chan
2026-11-02 16:30 ` [PATCH 03/14] spi: spi-mux: re-apply settings after child calls spi_setup() Joseph CT Chan
2026-11-02 16:30 ` [PATCH 04/14] spi: spi-mux: finalize message when forwarding fails Joseph CT Chan
2026-11-02 16:30 ` [PATCH 05/14] spi: spi-ti-qspi: finalize message on transfer error Joseph CT Chan
2026-11-02 16:30 ` Joseph CT Chan [this message]
2026-11-02 16:30 ` [PATCH 07/14] spi: add KUnit tests for message optimize state on queue errors Joseph CT Chan
2026-11-02 17:29 ` David Lechner
2026-11-03 22:41 ` Joseph CT Chan
2026-11-02 16:30 ` [PATCH 08/14] spi: spi-ti-qspi: add KUnit tests for transfer error finalization Joseph CT Chan
2026-11-02 16:30 ` [PATCH 09/14] spi: spi-mux: add KUnit tests for forwarding errors and settings Joseph CT Chan
2026-11-02 16:30 ` [PATCH 10/14] spi: Split spi_write_then_read() Joseph CT Chan
2026-11-02 16:30 ` [PATCH 11/14] spi: Add lock context annotations for bus_lock_mutex Joseph CT Chan
2026-11-02 16:30 ` [PATCH 12/14] spi: spi-mem: Add lock context annotations Joseph CT Chan
2026-11-02 16:30 ` [PATCH 13/14] spi: microchip-core-qspi: " Joseph CT Chan
2026-11-02 16:30 ` [PATCH 14/14] spi: Enable lock context analysis Joseph CT Chan
2026-11-02 17:07 ` [PATCH 00/14] spi: spi-mux, core, ti-qspi: error handling, delay fixes, KUnit tests, and lock context annotations David Lechner
2026-11-03 22:41 ` 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=20261102163023.2574632-7-jctchan@gmail.com \
--to=jctchan@gmail.com \
--cc=broonie@kernel.org \
--cc=bvanassche@acm.org \
--cc=conor.dooley@microchip.com \
--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