From: Heiko Stuebner <heiko@sntech.de>
To: broonie@kernel.org
Cc: Heiko Stuebner <heiko@sntech.de>,
linux-spi@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-rockchip@lists.infradead.org, quentin.schulz@cherry.de,
Lee Jones <lee@kernel.org>,
Sebastian Reichel <sebastian.reichel@collabora.com>,
Heiko Stuebner <heiko.stuebner@cherry.de>
Subject: [PATCH v2 6/6] spi: rockchip: Add an atomic transfer function
Date: Fri, 13 Nov 2026 14:50:17 +0100 [thread overview]
Message-ID: <20261113135017.1047959-7-heiko@sntech.de> (raw)
In-Reply-To: <20261113135017.1047959-1-heiko@sntech.de>
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
prev parent reply other threads:[~2026-11-13 13:51 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Heiko Stuebner [this message]
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=20261113135017.1047959-7-heiko@sntech.de \
--to=heiko@sntech.de \
--cc=broonie@kernel.org \
--cc=heiko.stuebner@cherry.de \
--cc=lee@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-rockchip@lists.infradead.org \
--cc=linux-spi@vger.kernel.org \
--cc=quentin.schulz@cherry.de \
--cc=sebastian.reichel@collabora.com \
/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