* [PATCH v2 0/6] spi: Implement a atomic transfer as a last resort
@ 2026-11-13 13:50 Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 1/6] spi: Allow spi_delay_exec() to be used from atomic contexts Heiko Stuebner
` (5 more replies)
0 siblings, 6 replies; 8+ messages in thread
From: Heiko Stuebner @ 2026-11-13 13:50 UTC (permalink / raw)
To: broonie
Cc: Heiko Stuebner, linux-spi, linux-arm-kernel, linux-rockchip,
quentin.schulz, Lee Jones, Sebastian Reichel
SPI devices at times are used as PMICs including a power-controller
functionality that should turn off a system.
This happens in an atomic context very late during the shutdown, where
no interrupts are available anymore and conflicts with the asynchronous
nature of the SPI subsystem.
====== History ======
For the RK806 spi-connected PMIC this has already caused interesting
artifacts.
A first change, tried to "fix" the issue by using the
SYS_OFF_MODE_POWER_OFF_PREPARE step of poweroff. While this hides the
issue, on some devices this now turns the pmic off too early before
other important steps are done.
To mitigate that issue for "some" devices, a change [0] tried to use
different poweroff phases for i2c and spi devices, where Lee rightfully
said, that with this no proper solution would be done - and Lee was right,
as that issue now is open since 2024.
The newest change is a simple revert back the old way [1] because of
those issues caused on other devices, but that now breaks the SPI side
again and also did not take into account that long history.
====== Now =======
The I2C framework has an atomic transfer callback for exactly this late
stage transfers since
commit 63b96983a5dd ("i2c: core: introduce callbacks for atomic transfers")
and during the discussion it was suggested implementing something like
this for SPI too.
As this did not seem to have happened yet, I thought I'd try my luck.
Similar to I2C this is meant only for that latest stage so I stole the
gating from there too.
I assume I did not take some SPI specifics into account, but I guess
having some code to discuss is a starting point at least :-)
===== Testing =====
Adding the below diff allows the atomic transfer to also work as main
transfer function for testing purposes. As SPI's ->transfer() call
already lives inside a spinlock_irqsafe context, this allows to find
any sleeping elements easily.
@@ -953,6 +953,9 @@
/* Only register atomic transfer in host-mode */
if (!target_mode) {
+ pm_runtime_resume_and_get(rs->dev);
+ ctlr->transfer = rockchip_spi_transfer_atomic;
+
ctlr->transfer_atomic = rockchip_spi_transfer_atomic;
ret = devm_register_sys_off_handler(&pdev->dev,
===== New =====
changes in v2:
address review comments from Mark Brown
- core: preserve old behaviour for drivers not declaring an atomic
transfer function
- core: don't use message optimization - safer, but still validate the
message to get bits_per_word defaults etc
- core: make spi_delay_exec usable from atomic contexts
- rockchip: only register atomic transfer in host-mode
- rockchip: provide a wait_for_idle_atomic function, to
not require (non-working) jiffies for timeout
- rockchip: register additional restart handler to enable runtime pm
before reboots too
- rockchip: drop runtime-pm from atomic transfer - would sleep anyway
- rockchip: don't prevent power_off, if runtime-resume fails
- rockchip: use core spi's spi_set_cs to also handle gpio-cs
[0] https://lore.kernel.org/lkml/20240730180903.81688-1-sebastian.reichel@collabora.com/
[1] https://lore.kernel.org/all/20260908-veyron-shutdown-v1-1-066ecb80f5f2@gmail.com/
Cc: Heiko Stuebner <heiko@sntech.de>
Cc: linux-spi@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-rockchip@lists.infradead.org
Cc: quentin.schulz@cherry.de
Cc: Lee Jones <lee@kernel.org>
Cc: Sebastian Reichel <sebastian.reichel@collabora.com>
Heiko Stuebner (6):
spi: Allow spi_delay_exec() to be used from atomic contexts
spi: Export spi_set_cs() function
spi: Add infrastructure for atomic sync transfers
spi: rockchip: Fix runtime resume error handling
spi: rockchip: Move to pm_runtime_resume_and_get
spi: rockchip: Add an atomic transfer function
drivers/spi/spi-rockchip.c | 126 +++++++++++++++++++++++++++++++++++--
drivers/spi/spi.c | 77 ++++++++++++++++++++---
include/linux/spi/spi.h | 13 ++++
3 files changed, 204 insertions(+), 12 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 1/6] spi: Allow spi_delay_exec() to be used from atomic contexts
2026-11-13 13:50 [PATCH v2 0/6] spi: Implement a atomic transfer as a last resort Heiko Stuebner
@ 2026-11-13 13:50 ` Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 2/6] spi: Export spi_set_cs() function Heiko Stuebner
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Heiko Stuebner @ 2026-11-13 13:50 UTC (permalink / raw)
To: broonie
Cc: Heiko Stuebner, linux-spi, linux-arm-kernel, linux-rockchip,
quentin.schulz, Lee Jones, Sebastian Reichel, Heiko Stuebner
From: Heiko Stuebner <heiko.stuebner@cherry.de>
When using spi_delay_exec() only the fsleep in the subsequent
_spi_transfer_delay_ns() can actually sleep.
As we might want to use spi_delay_exec() from atomic contexts in the
future, move the might_sleep() up to the actual place that matters
and fall back to the existing ndelay if called from an atomic context.
This also introduces a helper spi_in_atomic_mode() to determine when
that is the case. As this should really only be used in very special
occasions, it really restricts things to late system states.
Signed-off-by: Heiko Stuebner <heiko.stuebner@cherry.de>
---
drivers/spi/spi.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 40cda0037ea5..e79f16aca1a3 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -1700,15 +1700,27 @@ static int spi_transfer_wait(struct spi_controller *ctlr,
return 0;
}
+/*
+ * We only allow atomic transfers for very late communication, e.g. to access a
+ * PMIC when powering down. Atomic transfers are a corner case and not for
+ * generic use!
+ */
+static inline bool spi_in_atomic_mode(void)
+{
+ return system_state > SYSTEM_RUNNING &&
+ (IS_ENABLED(CONFIG_PREEMPT_COUNT) ? !preemptible() : irqs_disabled());
+}
+
static void _spi_transfer_delay_ns(u32 ns)
{
if (!ns)
return;
- if (ns <= NSEC_PER_USEC) {
+ if (ns <= NSEC_PER_USEC || spi_in_atomic_mode()) {
ndelay(ns);
} else {
u32 us = DIV_ROUND_UP(ns, NSEC_PER_USEC);
+ might_sleep();
fsleep(us);
}
}
@@ -1756,8 +1768,6 @@ int spi_delay_exec(struct spi_delay *_delay, struct spi_transfer *xfer)
{
int delay;
- might_sleep();
-
if (!_delay)
return -EINVAL;
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 2/6] spi: Export spi_set_cs() function
2026-11-13 13:50 [PATCH v2 0/6] spi: Implement a atomic transfer as a last resort Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 1/6] spi: Allow spi_delay_exec() to be used from atomic contexts Heiko Stuebner
@ 2026-11-13 13:50 ` Heiko Stuebner
2026-11-13 13:56 ` Heiko Stübner
2026-11-13 13:50 ` [PATCH v2 3/6] spi: Add infrastructure for atomic sync transfers Heiko Stuebner
` (3 subsequent siblings)
5 siblings, 1 reply; 8+ messages in thread
From: Heiko Stuebner @ 2026-11-13 13:50 UTC (permalink / raw)
To: broonie
Cc: Heiko Stuebner, linux-spi, linux-arm-kernel, linux-rockchip,
quentin.schulz, Lee Jones, Sebastian Reichel, Heiko Stuebner
From: Heiko Stuebner <heiko.stuebner@cherry.de>
While spi_set_cs is currently only used when the SPI framework manages
the core transfer function, it contains a lot of functionality that
can be used for the upcoming atomic transfers, where a driver implementing
that atomic transfer could profit from not having to duplicate the
chip-select functionality.
The only sleeping part was the spi_delay_exec function doing the
chip-select delays, which can now also run in atomic contexts.
So export the function.
Signed-off-by: Heiko Stuebner <heiko.stuebner@cherry.de>
---
drivers/spi/spi.c | 3 ++-
include/linux/spi/spi.h | 2 ++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index e79f16aca1a3..b268357e933c 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -1268,7 +1268,7 @@ static void spi_toggle_csgpiod(struct spi_device *spi, u8 idx, bool enable, bool
spi_delay_exec(&spi->cs_inactive, NULL);
}
-static void spi_set_cs(struct spi_device *spi, bool enable, bool force)
+void spi_set_cs(struct spi_device *spi, bool enable, bool force)
{
bool activate = enable;
u8 idx;
@@ -1325,6 +1325,7 @@ static void spi_set_cs(struct spi_device *spi, bool enable, bool force)
spi_delay_exec(&spi->cs_inactive, NULL);
}
}
+EXPORT_SYMBOL_GPL(spi_set_cs);
#ifdef CONFIG_HAS_DMA
static int spi_map_buf_attrs(struct spi_controller *ctlr, struct device *dev,
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index fb4baa0d3398..0bf31776c54c 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -349,6 +349,8 @@ static inline bool spi_is_csgpiod(struct spi_device *spi)
return false;
}
+extern void spi_set_cs(struct spi_device *spi, bool enable, bool force);
+
/**
* struct spi_driver - Host side "protocol" driver
* @id_table: List of SPI devices supported by this driver
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 3/6] spi: Add infrastructure for atomic sync transfers
2026-11-13 13:50 [PATCH v2 0/6] spi: Implement a atomic transfer as a last resort Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 1/6] spi: Allow spi_delay_exec() to be used from atomic contexts Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 2/6] spi: Export spi_set_cs() function Heiko Stuebner
@ 2026-11-13 13:50 ` Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 4/6] spi: rockchip: Fix runtime resume error handling Heiko Stuebner
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Heiko Stuebner @ 2026-11-13 13:50 UTC (permalink / raw)
To: broonie
Cc: Heiko Stuebner, linux-spi, linux-arm-kernel, linux-rockchip,
quentin.schulz, Lee Jones, Sebastian Reichel, Heiko Stuebner
From: Heiko Stuebner <heiko.stuebner@cherry.de>
There are cases where SPI transfers are needed during times without
interrupts like shutdown when a PMIC should turn off the system.
I2C already has atomic transfers for exactly that case, so introduce a
similar concept for SPI. Similar to I2C, this is and should stay a
corner-case, so is only allowed during the shutdown stages of the kernel,
when interrupts are already turned off.
So add an optional transfer_atomic callback, drivers can implement to do
immediate uninterrupted transfers and let spi_sync() determine if the
state is late enough to use it.
For drivers without a transfer_atomic callback defined, the previous
behaviour is kept for backwards compatiblity.
spi_async() does not require a sleeping context for itself, so while it
could in theory also be included, its caller waiting for the completion
can sleep, so would still cause the splat about it.
Signed-off-by: Heiko Stuebner <heiko.stuebner@cherry.de>
---
drivers/spi/spi.c | 58 ++++++++++++++++++++++++++++++++++++++---
include/linux/spi/spi.h | 11 ++++++++
2 files changed, 65 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index b268357e933c..0b1e07d87562 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -5027,6 +5027,37 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message)
return status;
}
+static int __spi_sync_atomic(struct spi_device *spi, struct spi_message *message)
+{
+ struct spi_controller *ctlr = spi->controller;
+ unsigned long flags;
+ int status;
+
+ if (!ctlr->transfer_atomic) {
+ dev_err(&ctlr->dev, "No atomic transfer handler defined\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (__spi_check_suspended(ctlr)) {
+ dev_warn_once(&spi->dev, "Attempted to sync while suspend\n");
+ return -ESHUTDOWN;
+ }
+
+ /* Not using spi_maybe_optimize_message, so just validate */
+ status = __spi_validate(spi, message);
+ if (status)
+ return status;
+
+ spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);
+ status = ctlr->transfer_atomic(spi, message);
+ spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);
+
+ if (status == 0)
+ status = message->status;
+
+ return status;
+}
+
/**
* spi_sync - blocking/synchronous SPI data transfers
* @spi: device with which data will be exchanged
@@ -5037,6 +5068,10 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message)
* is non-interruptible, and has no timeout. Low-overhead controller
* drivers may DMA directly into and out of the message buffers.
*
+ * During the time when the system is being shutdown and a transfer_atomic
+ * function is set, this call may also be used from a context that does
+ * not allow sleeping, i.e. with disabled interrupts.
+ *
* Note that the SPI device's chip select is active during the message,
* and then is normally disabled between messages. Drivers for some
* frequently-used devices may want to minimize costs of selecting a chip,
@@ -5050,11 +5085,17 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message)
*/
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
+ struct spi_controller *ctlr = spi->controller;
int ret;
- mutex_lock(&spi->controller->bus_lock_mutex);
- ret = __spi_sync(spi, message);
- mutex_unlock(&spi->controller->bus_lock_mutex);
+ /* Keep old behaviour for clients that tolerate the warnings */
+ if (spi_in_atomic_mode() && ctlr->transfer_atomic) {
+ ret = __spi_sync_atomic(spi, message);
+ } else {
+ mutex_lock(&spi->controller->bus_lock_mutex);
+ ret = __spi_sync(spi, message);
+ mutex_unlock(&spi->controller->bus_lock_mutex);
+ }
return ret;
}
@@ -5070,6 +5111,10 @@ EXPORT_SYMBOL_GPL(spi_sync);
* is non-interruptible, and has no timeout. Low-overhead controller
* drivers may DMA directly into and out of the message buffers.
*
+ * During the time when the system is being shutdown and a transfer_atomic
+ * function is set, this call may also be used from a context that does
+ * not allow sleeping, i.e. with disabled interrupts.
+ *
* This call should be used by drivers that require exclusive access to the
* SPI bus. It has to be preceded by a spi_bus_lock call. The SPI bus must
* be released by a spi_bus_unlock call when the exclusive access is over.
@@ -5078,7 +5123,12 @@ EXPORT_SYMBOL_GPL(spi_sync);
*/
int spi_sync_locked(struct spi_device *spi, struct spi_message *message)
{
- return __spi_sync(spi, message);
+ struct spi_controller *ctlr = spi->controller;
+
+ if (spi_in_atomic_mode() && ctlr->transfer_atomic)
+ return __spi_sync_atomic(spi, message);
+ else
+ return __spi_sync(spi, message);
}
EXPORT_SYMBOL_GPL(spi_sync_locked);
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 0bf31776c54c..f62b21af5d75 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -461,6 +461,8 @@ extern struct spi_device *devm_spi_new_ancillary_device(struct spi_device *spi,
* controller for configuring specific CS setup time, hold time and inactive
* delay in terms of clock counts
* @transfer: adds a message to the controller's transfer queue.
+ * @transfer_atomic: transfer a message immediately from a late-stage
+ * synchronous transfer
* @cleanup: frees controller-specific state
* @can_dma: determine whether this controller supports DMA
* @dma_map_dev: device which can be used for DMA mapping
@@ -721,6 +723,15 @@ struct spi_controller {
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);
+ /*
+ * Bidirectional bulk atomic transfers
+ *
+ * Special function that allows transfers in contexts where
+ * no interrupts nor DMA are available.
+ */
+ int (*transfer_atomic)(struct spi_device *spi,
+ struct spi_message *mesg);
+
/* Called on deregistration to free memory provided by spi_controller */
void (*cleanup)(struct spi_device *spi);
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 4/6] spi: rockchip: Fix runtime resume error handling
2026-11-13 13:50 [PATCH v2 0/6] spi: Implement a atomic transfer as a last resort Heiko Stuebner
` (2 preceding siblings ...)
2026-11-13 13:50 ` [PATCH v2 3/6] spi: Add infrastructure for atomic sync transfers Heiko Stuebner
@ 2026-11-13 13:50 ` Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 5/6] spi: rockchip: Move to pm_runtime_resume_and_get Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 6/6] spi: rockchip: Add an atomic transfer function Heiko Stuebner
5 siblings, 0 replies; 8+ messages in thread
From: Heiko Stuebner @ 2026-11-13 13:50 UTC (permalink / raw)
To: broonie
Cc: Heiko Stuebner, linux-spi, linux-arm-kernel, linux-rockchip,
quentin.schulz, Lee Jones, Sebastian Reichel, Heiko Stuebner
From: Heiko Stuebner <heiko.stuebner@cherry.de>
When enabling the apb-clock any error does get handled, but when an
error happens enabling the spiclk itself, only the apb-clock is disabled
but the function then returns successful.
Fix this by also reporting the error on enabling the second clock.
Signed-off-by: Heiko Stuebner <heiko.stuebner@cherry.de>
---
drivers/spi/spi-rockchip.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/spi-rockchip.c b/drivers/spi/spi-rockchip.c
index 1a81fd94b439..8bb55d52d346 100644
--- a/drivers/spi/spi-rockchip.c
+++ b/drivers/spi/spi-rockchip.c
@@ -994,8 +994,10 @@ static int rockchip_spi_runtime_resume(struct device *dev)
return ret;
ret = clk_prepare_enable(rs->spiclk);
- if (ret < 0)
+ if (ret < 0) {
clk_disable_unprepare(rs->apb_pclk);
+ return ret;
+ }
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 5/6] spi: rockchip: Move to pm_runtime_resume_and_get
2026-11-13 13:50 [PATCH v2 0/6] spi: Implement a atomic transfer as a last resort Heiko Stuebner
` (3 preceding siblings ...)
2026-11-13 13:50 ` [PATCH v2 4/6] spi: rockchip: Fix runtime resume error handling Heiko Stuebner
@ 2026-11-13 13:50 ` Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 6/6] spi: rockchip: Add an atomic transfer function Heiko Stuebner
5 siblings, 0 replies; 8+ messages in thread
From: Heiko Stuebner @ 2026-11-13 13:50 UTC (permalink / raw)
To: broonie
Cc: Heiko Stuebner, linux-spi, linux-arm-kernel, linux-rockchip,
quentin.schulz, Lee Jones, Sebastian Reichel, Heiko Stuebner
From: Heiko Stuebner <heiko.stuebner@cherry.de>
Currently pm_runtime_get_sync() is used, which increments the usage
count even if the resume was not successful.
Move to the more modern pm_runtime_resume_and_get() which handles the
usage counter better.
Signed-off-by: Heiko Stuebner <heiko.stuebner@cherry.de>
---
drivers/spi/spi-rockchip.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/spi/spi-rockchip.c b/drivers/spi/spi-rockchip.c
index 8bb55d52d346..ce1629e5a513 100644
--- a/drivers/spi/spi-rockchip.c
+++ b/drivers/spi/spi-rockchip.c
@@ -255,7 +255,7 @@ static void rockchip_spi_set_cs(struct spi_device *spi, bool enable)
if (cs_asserted) {
/* Keep things powered as long as CS is asserted */
- pm_runtime_get_sync(rs->dev);
+ pm_runtime_resume_and_get(rs->dev);
if (spi_get_csgpiod(spi, 0))
ROCKCHIP_SPI_SET_BITS(rs->regs + ROCKCHIP_SPI_SER, 1);
@@ -734,7 +734,7 @@ static int rockchip_spi_setup(struct spi_device *spi)
return -EINVAL;
}
- pm_runtime_get_sync(rs->dev);
+ pm_runtime_resume_and_get(rs->dev);
cr0 = readl_relaxed(rs->regs + ROCKCHIP_SPI_CTRLR0);
@@ -924,7 +924,7 @@ static void rockchip_spi_remove(struct platform_device *pdev)
{
struct spi_controller *ctlr = platform_get_drvdata(pdev);
- pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_resume_and_get(&pdev->dev);
spi_unregister_controller(ctlr);
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 6/6] spi: rockchip: Add an atomic transfer function
2026-11-13 13:50 [PATCH v2 0/6] spi: Implement a atomic transfer as a last resort Heiko Stuebner
` (4 preceding siblings ...)
2026-11-13 13:50 ` [PATCH v2 5/6] spi: rockchip: Move to pm_runtime_resume_and_get Heiko Stuebner
@ 2026-11-13 13:50 ` Heiko Stuebner
5 siblings, 0 replies; 8+ messages in thread
From: Heiko Stuebner @ 2026-11-13 13:50 UTC (permalink / raw)
To: broonie
Cc: Heiko Stuebner, linux-spi, linux-arm-kernel, linux-rockchip,
quentin.schulz, Lee Jones, Sebastian Reichel, Heiko Stuebner
From: Heiko Stuebner <heiko.stuebner@cherry.de>
Rockchip SPI controllers are often used to handle SPI connected PMICs that
also handle system power-off events, like the RK806.
When the kernel signals the power-off, all the interrupts are already
disabled, so regular SPI with its mutexes and DMA transfers does not
work anymore.
Implement an atomic transfer function that does SPI transfers polling the
controller in a loop without any DMA or IRQ.
This can successfully handle the power-off command to RK806, but also
read back the 2 chip-id registers just fine before that, as a test.
Signed-off-by: Heiko Stuebner <heiko.stuebner@cherry.de>
---
drivers/spi/spi-rockchip.c | 116 +++++++++++++++++++++++++++++++++++++
1 file changed, 116 insertions(+)
diff --git a/drivers/spi/spi-rockchip.c b/drivers/spi/spi-rockchip.c
index ce1629e5a513..7c9ded3b3720 100644
--- a/drivers/spi/spi-rockchip.c
+++ b/drivers/spi/spi-rockchip.c
@@ -7,12 +7,14 @@
#include <linux/clk.h>
#include <linux/dmaengine.h>
#include <linux/interrupt.h>
+#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/pm_runtime.h>
+#include <linux/reboot.h>
#include <linux/scatterlist.h>
#define DRIVER_NAME "rockchip-spi"
@@ -218,6 +220,19 @@ static inline void wait_for_tx_idle(struct rockchip_spi *rs, bool target_mode)
dev_warn(rs->dev, "spi controller is in busy state!\n");
}
+/* atomic transfers only in host-mode */
+static inline void wait_for_tx_idle_atomic(struct rockchip_spi *rs)
+{
+ u32 sr;
+ int ret;
+
+ ret = readl_relaxed_poll_timeout_atomic(rs->regs + ROCKCHIP_SPI_SR, sr,
+ !(sr & SR_BUSY),
+ 10, 5 * USEC_PER_MSEC);
+ if (ret)
+ dev_warn(rs->dev, "spi controller still in busy state!\n");
+}
+
static u32 get_fifo_len(struct rockchip_spi *rs)
{
u32 ver;
@@ -724,6 +739,69 @@ static bool rockchip_spi_can_dma(struct spi_controller *ctlr,
return xfer->len / bytes_per_word >= rs->fifo_len;
}
+static int rockchip_spi_transfer_atomic(struct spi_device *spi, struct spi_message *msg)
+{
+ struct spi_controller *ctlr = spi->controller;
+ struct rockchip_spi *rs = spi_controller_get_devdata(ctlr);
+ struct spi_transfer *xfer;
+ int ret = 0;
+
+ spi_set_cs(spi, true, false);
+
+ /* make sure all interrupts are masked and status cleared */
+ writel_relaxed(0, rs->regs + ROCKCHIP_SPI_IMR);
+ writel_relaxed(0xffffffff, rs->regs + ROCKCHIP_SPI_ICR);
+
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ WARN_ON(readl_relaxed(rs->regs + ROCKCHIP_SPI_SSIENR) &&
+ (readl_relaxed(rs->regs + ROCKCHIP_SPI_SR) & SR_BUSY));
+
+ rs->n_bytes = xfer->bits_per_word <= 8 ? 1 : 2;
+
+ ret = rockchip_spi_config(rs, spi, xfer, false, false);
+ if (ret < 0)
+ goto out_disable;
+
+ writel_relaxed(0, rs->regs + ROCKCHIP_SPI_RXFTLR);
+
+ rs->tx = xfer->tx_buf;
+ rs->rx = xfer->rx_buf;
+ rs->tx_left = rs->tx ? xfer->len / rs->n_bytes : 0;
+ rs->rx_left = xfer->len / rs->n_bytes;
+
+ spi_enable_chip(rs, true);
+
+ while (rs->tx_left || rs->rx_left) {
+ u32 status = readl_relaxed(rs->regs + ROCKCHIP_SPI_SR);
+
+ if (rs->tx_left && !(status & SR_TF_FULL))
+ rockchip_spi_pio_writer(rs);
+
+ if (rs->rx_left && !(status & SR_RF_EMPTY))
+ rockchip_spi_pio_reader(rs);
+ }
+
+ wait_for_tx_idle_atomic(rs);
+
+ spi_enable_chip(rs, false);
+
+ if (xfer->cs_change && !list_is_last(&xfer->transfer_list, &msg->transfers)) {
+ spi_set_cs(spi, false, false);
+ spi_transfer_cs_change_delay_exec(msg, xfer);
+ spi_set_cs(spi, true, false);
+ }
+ }
+
+out_disable:
+ spi_set_cs(spi, false, false);
+ msg->status = ret;
+
+ if (msg->complete)
+ msg->complete(msg->context);
+
+ return ret;
+}
+
static int rockchip_spi_setup(struct spi_device *spi)
{
struct rockchip_spi *rs = spi_controller_get_devdata(spi->controller);
@@ -752,6 +830,19 @@ static int rockchip_spi_setup(struct spi_device *spi)
return 0;
}
+static int rockchip_spi_power_off(struct sys_off_data *data)
+{
+ struct rockchip_spi *rs = data->cb_data;
+ int ret;
+
+ /* enable controller for possible calls from shutdown handlers */
+ ret = pm_runtime_resume_and_get(rs->dev);
+ if (ret)
+ dev_warn(rs->dev, "spi controller failed to runtime resume: %d\n", ret);
+
+ return 0;
+}
+
static int rockchip_spi_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
@@ -860,6 +951,31 @@ static int rockchip_spi_probe(struct platform_device *pdev)
ctlr->max_transfer_size = rockchip_spi_max_transfer_size;
ctlr->handle_err = rockchip_spi_handle_err;
+ /* Only register atomic transfer in host-mode */
+ if (!target_mode) {
+ ctlr->transfer_atomic = rockchip_spi_transfer_atomic;
+
+ ret = devm_register_sys_off_handler(&pdev->dev,
+ SYS_OFF_MODE_POWER_OFF_PREPARE,
+ SYS_OFF_PRIO_DEFAULT,
+ &rockchip_spi_power_off, rs);
+ if (ret) {
+ dev_err_probe(&pdev->dev, ret,
+ "failed to register poweroff handler\n");
+ goto err_disable_pm_runtime;
+ }
+
+ ret = devm_register_sys_off_handler(&pdev->dev,
+ SYS_OFF_MODE_RESTART_PREPARE,
+ SYS_OFF_PRIO_DEFAULT,
+ &rockchip_spi_power_off, rs);
+ if (ret) {
+ dev_err_probe(&pdev->dev, ret,
+ "failed to register restart handler\n");
+ goto err_disable_pm_runtime;
+ }
+ }
+
ctlr->dma_tx = dma_request_chan(rs->dev, "tx");
if (IS_ERR(ctlr->dma_tx)) {
/* Check tx to see if we need to defer driver probing */
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2 2/6] spi: Export spi_set_cs() function
2026-11-13 13:50 ` [PATCH v2 2/6] spi: Export spi_set_cs() function Heiko Stuebner
@ 2026-11-13 13:56 ` Heiko Stübner
0 siblings, 0 replies; 8+ messages in thread
From: Heiko Stübner @ 2026-11-13 13:56 UTC (permalink / raw)
To: broonie
Cc: linux-spi, linux-arm-kernel, linux-rockchip, quentin.schulz,
Lee Jones, Sebastian Reichel, Heiko Stuebner
Am Freitag, 13. November 2026, 14:50:13 Mitteleuropäische Normalzeit schrieb Heiko Stuebner:
> From: Heiko Stuebner <heiko.stuebner@cherry.de>
>
> While spi_set_cs is currently only used when the SPI framework manages
> the core transfer function, it contains a lot of functionality that
> can be used for the upcoming atomic transfers, where a driver implementing
> that atomic transfer could profit from not having to duplicate the
> chip-select functionality.
>
> The only sleeping part was the spi_delay_exec function doing the
> chip-select delays, which can now also run in atomic contexts.
>
> So export the function.
>
> Signed-off-by: Heiko Stuebner <heiko.stuebner@cherry.de>
> ---
Dang, overlooked spi_toggle_csgpiod() called in spi_set_cs(), which does
gpiod_set_value_cansleep().
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-11-13 13:56 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 13:50 [PATCH v2 0/6] spi: Implement a atomic transfer as a last resort Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 1/6] spi: Allow spi_delay_exec() to be used from atomic contexts Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 2/6] spi: Export spi_set_cs() function Heiko Stuebner
2026-11-13 13:56 ` Heiko Stübner
2026-11-13 13:50 ` [PATCH v2 3/6] spi: Add infrastructure for atomic sync transfers Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 4/6] spi: rockchip: Fix runtime resume error handling Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 5/6] spi: rockchip: Move to pm_runtime_resume_and_get Heiko Stuebner
2026-11-13 13:50 ` [PATCH v2 6/6] spi: rockchip: Add an atomic transfer function Heiko Stuebner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox