* [PATCH v3 1/7] spi: spi-mem: Add support for panic mem writes
2026-10-14 15:42 [PATCH v3 0/7] spi: Add support for panic mem writes Paul Cercueil
@ 2026-10-14 15:42 ` Paul Cercueil
2026-10-14 15:42 ` [PATCH v3 2/7] mtd: spi-nor: Wait for NOR ready with poll_timeout_us() Paul Cercueil
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Paul Cercueil @ 2026-10-14 15:42 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown,
Thomas Petazzoni
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, linux-mtd,
linux-kernel, linux-spi, Bharat Bhushan, Sunil Kovvuri Goutham,
Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Add a 'panic_write' flag to the controller's capabilities struct.
When set, the SPI controller indicates that it will be able to send
commands to a connected NOR or NAND memory in the context of a kernel
panic, which means without sleeping and without relying on IRQs.
Whether or not a given memory operation is a panic mem write is given by
the spi_mem_op.panic_write flag.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: - Don't call spi_mem_access_start/end on panic. We don't need to
flush the SPI queue at that point, and we don't need to lock the
mutexes.
- Check that the SPI controller's PM status is active, and bail out
otherwise.
v3: - Use pm_runtime_active() instead of pm_runtime_get_if_active(). The
latter cannot be used in a panic context.
- Forbid fallback to regular SPI on panic
drivers/spi/spi-mem.c | 42 +++++++++++++++++++++++++++++++++----
include/linux/spi/spi-mem.h | 5 +++++
2 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index b94680b60af9..9ad42dd9de70 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -398,6 +398,31 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
u8 *tmpbuf;
int ret;
+ if (op->panic_write) {
+ if (!spi_mem_controller_is_capable(ctlr, panic_write))
+ return -EOPNOTSUPP;
+
+ /*
+ * Only support panic writes if we'll go through .exec_op().
+ * The regular SPI path (spi_sync(), etc.) explicitly mentions
+ * that it can only be used from a context that may sleep, which
+ * is not the case on a kernel panic.
+ * If this changes in the future, this could be reworked, but
+ * for now this will have to do.
+ */
+ if (WARN_ON_ONCE(!ctlr->mem_ops || !ctlr->mem_ops->exec_op))
+ return -EOPNOTSUPP;
+
+ if (spi_get_csgpiod(mem->spi, 0))
+ return -EPERM;
+
+ /* On panic IRQs are disabled so we cannot wait on locks */
+ if (mutex_is_locked(&ctlr->bus_lock_mutex) ||
+ mutex_is_locked(&ctlr->io_mutex)) {
+ return -EPERM;
+ }
+ }
+
/* Make sure the operation frequency is correct before going futher */
spi_mem_adjust_op_freq(mem, (struct spi_mem_op *)op);
@@ -420,15 +445,20 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
return -EOPNOTSUPP;
if (ctlr->mem_ops && ctlr->mem_ops->exec_op && !spi_get_csgpiod(mem->spi, 0)) {
- ret = spi_mem_access_start(mem);
- if (ret)
- return ret;
+ if (!op->panic_write) {
+ ret = spi_mem_access_start(mem);
+ if (ret)
+ return ret;
+ } else if (!pm_runtime_active(ctlr->dev.parent)) {
+ return -EPERM;
+ }
trace_spi_mem_start_op(mem, op);
ret = ctlr->mem_ops->exec_op(mem, op);
trace_spi_mem_stop_op(mem, op);
- spi_mem_access_end(mem);
+ if (!op->panic_write)
+ spi_mem_access_end(mem);
/*
* Some controllers only optimize specific paths (typically the
@@ -441,6 +471,10 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
return ret;
}
+
+ /* On panic write we can't fall back to the fallback SPI path */
+ if (op->panic_write)
+ return ret;
}
tmpbufsize = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index 0cce6b57242a..2633307bfad5 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -169,6 +169,7 @@ enum spi_mem_data_dir {
* operation does not involve transferring data
* @data.buf.in: input buffer (must be DMA-able)
* @data.buf.out: output buffer (must be DMA-able)
+ * @panic_write: The command is executed in the context of a kernel panic
* @max_freq: frequency limitation wrt this operation. 0 means there is no
* specific constraint and the highest achievable frequency can be
* attempted.
@@ -211,6 +212,7 @@ struct spi_mem_op {
} buf;
} data;
+ bool panic_write;
unsigned int max_freq;
};
@@ -392,6 +394,8 @@ struct spi_controller_mem_ops {
* is a pause in the transfer (eg. internal bus contention or
* DMA arbitration on an interconnect). Features such as NAND
* continuous reads shall not be leveraged.
+ * @panic_write: Set to true if the controller driver supports writing to a
+ * connected NAND/NOR memory in a kernel panic context
*/
struct spi_controller_mem_caps {
bool dtr;
@@ -400,6 +404,7 @@ struct spi_controller_mem_caps {
bool per_op_freq;
bool secondary_op_tmpl;
bool no_cs_assertion;
+ bool panic_write;
};
#define spi_mem_controller_is_capable(ctlr, cap) \
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v3 2/7] mtd: spi-nor: Wait for NOR ready with poll_timeout_us()
2026-10-14 15:42 [PATCH v3 0/7] spi: Add support for panic mem writes Paul Cercueil
2026-10-14 15:42 ` [PATCH v3 1/7] spi: spi-mem: " Paul Cercueil
@ 2026-10-14 15:42 ` Paul Cercueil
2026-10-14 15:42 ` [PATCH v3 3/7] mtd: spi-nor: Add support for panic writes Paul Cercueil
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Paul Cercueil @ 2026-10-14 15:42 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown,
Thomas Petazzoni
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, linux-mtd,
linux-kernel, linux-spi, Bharat Bhushan, Sunil Kovvuri Goutham,
Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Instead of polling for the spi_nor_ready() with a custom loop, use
poll_timeout_us(). The sleep period between two polls is set to 100 us
which I think should be fine.
This will make it simpler to support waiting for the SPI NOR in atomic
context later.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: New patch
v3: Set 'timeout_ms' parameter to u64 to prevent u32 overflow
drivers/mtd/spi-nor/core.c | 40 +++++++++++++++-----------------------
1 file changed, 16 insertions(+), 24 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 8bc117b46e02..58257cc21c68 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -12,6 +12,7 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
+#include <linux/iopoll.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
@@ -32,13 +33,13 @@
* For everything but full-chip erase; probably could be much smaller, but kept
* around for safety for now
*/
-#define DEFAULT_READY_WAIT_JIFFIES (40UL * HZ)
+#define DEFAULT_READY_TIMEOUT_MS 40000UL
/*
* For full-chip erase, calibrated to a 2MB flash (M25P16); should be scaled up
* for larger flash
*/
-#define CHIP_ERASE_2MB_READY_WAIT_JIFFIES (40UL * HZ)
+#define CHIP_ERASE_2MB_READY_TIMEOUT_MS 40000UL
#define SPI_NOR_MAX_ADDR_NBYTES 4
@@ -711,30 +712,21 @@ static int spi_nor_ready(struct spi_nor *nor)
*
* Return: 0 on success, -errno otherwise.
*/
-static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor,
- unsigned long timeout_jiffies)
+static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor, u64 timeout_ms)
{
- unsigned long deadline;
- int timeout = 0, ret;
+ int ret, status;
- deadline = jiffies + timeout_jiffies;
-
- while (!timeout) {
- if (time_after_eq(jiffies, deadline))
- timeout = 1;
-
- ret = spi_nor_ready(nor);
- if (ret < 0)
- return ret;
- if (ret)
- return 0;
-
- cond_resched();
+ ret = poll_timeout_us(status = spi_nor_ready(nor), status,
+ 100, timeout_ms * 1000, false);
+ if (ret < 0) {
+ dev_dbg(nor->dev, "flash operation timed out\n");
+ return ret;
}
- dev_dbg(nor->dev, "flash operation timed out\n");
+ if (status < 0)
+ return status;
- return -ETIMEDOUT;
+ return 0;
}
/**
@@ -747,7 +739,7 @@ static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor,
int spi_nor_wait_till_ready(struct spi_nor *nor)
{
return spi_nor_wait_till_ready_with_timeout(nor,
- DEFAULT_READY_WAIT_JIFFIES);
+ DEFAULT_READY_TIMEOUT_MS);
}
/**
@@ -1781,8 +1773,8 @@ static int spi_nor_erase_dice(struct spi_nor *nor, loff_t addr,
* pull these from CFI/SFDP, but these values should be good
* enough for now.
*/
- timeout = max(CHIP_ERASE_2MB_READY_WAIT_JIFFIES,
- CHIP_ERASE_2MB_READY_WAIT_JIFFIES *
+ timeout = max(CHIP_ERASE_2MB_READY_TIMEOUT_MS,
+ CHIP_ERASE_2MB_READY_TIMEOUT_MS *
(unsigned long)(nor->mtd.size / SZ_2M));
do {
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v3 3/7] mtd: spi-nor: Add support for panic writes
2026-10-14 15:42 [PATCH v3 0/7] spi: Add support for panic mem writes Paul Cercueil
2026-10-14 15:42 ` [PATCH v3 1/7] spi: spi-mem: " Paul Cercueil
2026-10-14 15:42 ` [PATCH v3 2/7] mtd: spi-nor: Wait for NOR ready with poll_timeout_us() Paul Cercueil
@ 2026-10-14 15:42 ` Paul Cercueil
2026-10-14 15:42 ` [PATCH v3 4/7] spi: cadence-xspi: Clear "SDMA ready" IRQ flag after polling Paul Cercueil
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Paul Cercueil @ 2026-10-14 15:42 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown,
Thomas Petazzoni
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, linux-mtd,
linux-kernel, linux-spi, Bharat Bhushan, Sunil Kovvuri Goutham,
Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
If the SPI controller supports it, provide the functionality of doing a
panic write.
Update the spi-nor core code and controllers to make sure they won't try
to grab locked mutexes or sleep when doing a panic write, as preemption
and IRQs are disabled.
The information is carried down to the SPI controller driver through the
spi_mem_op structure. If the corresponding flag is set there, and the
SPI driver does support panic writes, it then has to respect the same
rules.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: - Update spi_nor_wait_till_ready_with_timeout() to use
poll_timeout_us_atomic() on panic
- Alloc temp buffer with GFP_ATOMIC in spi_nor_octal_dtr_write()
on panic
- Bail out if nor->rww.ongoing_pe is set in spi_nor_panic_write()
v3: Use GFP_KERNEL xor GFP_ATOMIC
drivers/mtd/spi-nor/core.c | 75 +++++++++++++++++++++++++++++++++++---
1 file changed, 70 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 58257cc21c68..d5e572287bb7 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -97,6 +97,9 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor,
if (op->data.nbytes)
op->data.buswidth = spi_nor_get_protocol_data_nbits(proto);
+ if (nor->mtd.oops_panic_write)
+ op->panic_write = true;
+
if (spi_nor_protocol_is_dtr(proto)) {
/*
* SPIMEM supports mixed DTR modes, but right now we can only
@@ -282,7 +285,7 @@ static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to,
if (spi_nor_spimem_bounce(nor, &op))
memcpy(nor->bouncebuf, buf, op.data.nbytes);
- if (nor->dirmap.wdesc) {
+ if (nor->dirmap.wdesc && !nor->mtd.oops_panic_write) {
nbytes = spi_mem_dirmap_write(nor->dirmap.wdesc, op.addr.val,
op.data.nbytes, op.data.buf.out);
} else {
@@ -665,6 +668,9 @@ static void spi_nor_rww_end_rdst(struct spi_nor *nor)
static int spi_nor_lock_rdst(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
if (spi_nor_use_parallel_locking(nor))
return spi_nor_rww_start_rdst(nor);
@@ -673,6 +679,9 @@ static int spi_nor_lock_rdst(struct spi_nor *nor)
static void spi_nor_unlock_rdst(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (spi_nor_use_parallel_locking(nor)) {
spi_nor_rww_end_rdst(nor);
wake_up(&nor->rww.wait);
@@ -716,8 +725,14 @@ static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor, u64 timeout
{
int ret, status;
- ret = poll_timeout_us(status = spi_nor_ready(nor), status,
- 100, timeout_ms * 1000, false);
+ if (nor->mtd.oops_panic_write) {
+ ret = poll_timeout_us_atomic(status = spi_nor_ready(nor), status,
+ 100, timeout_ms * 1000, false);
+ } else {
+ ret = poll_timeout_us(status = spi_nor_ready(nor), status,
+ 100, timeout_ms * 1000, false);
+ }
+
if (ret < 0) {
dev_dbg(nor->dev, "flash operation timed out\n");
return ret;
@@ -1283,6 +1298,9 @@ static void spi_nor_rww_end_io(struct spi_nor *nor)
static int spi_nor_lock_device(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
if (!spi_nor_use_parallel_locking(nor))
return 0;
@@ -1291,6 +1309,9 @@ static int spi_nor_lock_device(struct spi_nor *nor)
static void spi_nor_unlock_device(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (spi_nor_use_parallel_locking(nor)) {
spi_nor_rww_end_io(nor);
wake_up(&nor->rww.wait);
@@ -1328,6 +1349,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
{
int ret;
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
ret = spi_nor_prep(nor);
if (ret)
return ret;
@@ -1343,6 +1367,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
void spi_nor_unlock_and_unprep(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (!spi_nor_use_parallel_locking(nor)) {
mutex_unlock(&nor->lock);
} else {
@@ -1399,6 +1426,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
{
int ret;
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
ret = spi_nor_prep(nor);
if (ret)
return ret;
@@ -1414,6 +1444,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
static void spi_nor_unlock_and_unprep_pe(struct spi_nor *nor, loff_t start, size_t len)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (!spi_nor_use_parallel_locking(nor)) {
mutex_unlock(&nor->lock);
} else {
@@ -1472,6 +1505,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
{
int ret;
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
ret = spi_nor_prep(nor);
if (ret)
return ret;
@@ -1487,6 +1523,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
static void spi_nor_unlock_and_unprep_rd(struct spi_nor *nor, loff_t start, size_t len)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (!spi_nor_use_parallel_locking(nor)) {
mutex_unlock(&nor->lock);
} else {
@@ -2203,7 +2242,8 @@ static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len,
if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2))
return spi_nor_write_data(nor, to, len, buf);
- tmp_buf = kmalloc(nor->params->page_size, GFP_KERNEL);
+ tmp_buf = kmalloc(nor->params->page_size,
+ nor->mtd.oops_panic_write ? GFP_ATOMIC : GFP_KERNEL);
if (!tmp_buf)
return -ENOMEM;
@@ -3385,6 +3425,26 @@ static void spi_nor_soft_reset(struct spi_nor *nor)
usleep_range(SPI_NOR_SRST_SLEEP_MIN, SPI_NOR_SRST_SLEEP_MAX);
}
+static int spi_nor_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+ /*
+ * At this point preemption and local interrupts are disabled, so we
+ * can't get the lock if it's taken.
+ */
+ if (mutex_is_locked(&nor->lock))
+ return -EPERM;
+
+ if (spi_nor_use_parallel_locking(nor) &&
+ (nor->rww.ongoing_io || nor->rww.ongoing_rd || nor->rww.ongoing_pe)) {
+ return -EPERM;
+ }
+
+ return spi_nor_write(mtd, to, len, retlen, buf);
+}
+
/* mtd suspend handler */
static int spi_nor_suspend(struct mtd_info *mtd)
{
@@ -3588,8 +3648,13 @@ static int spi_nor_set_mtd_info(struct spi_nor *nor)
mtd->size = nor->params->size;
mtd->_read = spi_nor_read;
/* Might be already set by some SST flashes. */
- if (!mtd->_write)
+ if (!mtd->_write) {
mtd->_write = spi_nor_write;
+ if (nor->spimem &&
+ spi_mem_controller_is_capable(nor->spimem->spi->controller, panic_write)) {
+ mtd->_panic_write = spi_nor_panic_write;
+ }
+ }
mtd->_suspend = spi_nor_suspend;
mtd->_resume = spi_nor_resume;
mtd->_get_device = spi_nor_get_device;
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v3 4/7] spi: cadence-xspi: Clear "SDMA ready" IRQ flag after polling
2026-10-14 15:42 [PATCH v3 0/7] spi: Add support for panic mem writes Paul Cercueil
` (2 preceding siblings ...)
2026-10-14 15:42 ` [PATCH v3 3/7] mtd: spi-nor: Add support for panic writes Paul Cercueil
@ 2026-10-14 15:42 ` Paul Cercueil
2026-10-14 15:42 ` [PATCH v3 5/7] spi: cadence-xspi: Add irq-less support Paul Cercueil
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Paul Cercueil @ 2026-10-14 15:42 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown,
Thomas Petazzoni
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, linux-mtd,
linux-kernel, linux-spi, Bharat Bhushan, Sunil Kovvuri Goutham,
Tanmay Jagdale, stable, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
When polling for the "SDMA ready" event, once the corresponding bit is
set in the CDNS_XSPI_SDMA_TRIGGER we should reset it to zero. Otherwise,
any subsequent call to cdns_xspi_is_sdma_ready() will just return early.
Fixes: 931e389ded0f ("spi: cadence: Add Marvell xfer operation support")
Cc: stable@vger.kernel.org
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: New patch
v3: No change
drivers/spi/spi-cadence-xspi.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 39c868a5b171..095bf640b379 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -1068,12 +1068,20 @@ static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- return !readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
- ctrl_stat,
- (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
+ if (readl_relaxed_poll_timeout
+ (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ ctrl_stat,
+ (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
+ sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
+ sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
+ return false;
+ }
+
+ /* Clear the flag */
+ writel(CDNS_XSPI_SDMA_TRIGGER,
+ cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
+
+ return true;
}
static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v3 5/7] spi: cadence-xspi: Add irq-less support
2026-10-14 15:42 [PATCH v3 0/7] spi: Add support for panic mem writes Paul Cercueil
` (3 preceding siblings ...)
2026-10-14 15:42 ` [PATCH v3 4/7] spi: cadence-xspi: Clear "SDMA ready" IRQ flag after polling Paul Cercueil
@ 2026-10-14 15:42 ` Paul Cercueil
2026-10-14 15:43 ` [PATCH v3 6/7] spi: cadence-xspi: Add support for panic writes Paul Cercueil
2026-10-14 15:43 ` [PATCH v3 7/7] spi: dt-bindings: cadence: Make XSPI's IRQ line optional Paul Cercueil
6 siblings, 0 replies; 8+ messages in thread
From: Paul Cercueil @ 2026-10-14 15:42 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown,
Thomas Petazzoni
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, linux-mtd,
linux-kernel, linux-spi, Bharat Bhushan, Sunil Kovvuri Goutham,
Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Make the interrupt line optional. If not provided, the driver will
instead resort to polling registers.
This can be useful in itself, but will be used later to support write on
kernel panic.
Note the cdns_xspi_is_{sdma,stig}_ready() seem to have moved for no
reason; they were actually moved out of a conditional compilation block
(#ifdef CONFIG_64BIT), as they are needed in conf-independent paths now.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: Make sure we only enable interrupts if not using polling mode
v3: No change
drivers/spi/spi-cadence-xspi.c | 104 ++++++++++++++++++---------------
1 file changed, 58 insertions(+), 46 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 095bf640b379..2a2a40bf0a4c 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -365,6 +365,38 @@ static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
100, 1000);
}
+static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
+{
+ u32 ctrl_stat;
+
+ return !readl_relaxed_poll_timeout
+ (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat,
+ ((ctrl_stat & BIT(3)) == 0),
+ sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
+ sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
+}
+
+static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
+{
+ u32 ctrl_stat;
+
+ if (readl_relaxed_poll_timeout
+ (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ ctrl_stat,
+ (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
+ sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
+ sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
+ return false;
+ }
+
+ /* Clear the flag */
+ writel(CDNS_XSPI_SDMA_TRIGGER,
+ cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
+
+ return true;
+}
+
static void cdns_xspi_trigger_command(struct cdns_xspi_dev *cdns_xspi,
u32 cmd_regs[6])
{
@@ -532,7 +564,8 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, CDNS_XSPI_WORK_MODE_STIG),
cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
- cdns_xspi->set_interrupts_handler(cdns_xspi, true);
+ if (cdns_xspi->irq >= 0)
+ cdns_xspi->set_interrupts_handler(cdns_xspi, true);
cdns_xspi->sdma_error = false;
memset(cmd_regs, 0, sizeof(cmd_regs));
@@ -562,16 +595,25 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
- wait_for_completion(&cdns_xspi->sdma_complete);
- if (cdns_xspi->sdma_error) {
- cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ if (cdns_xspi->irq >= 0) {
+ wait_for_completion(&cdns_xspi->sdma_complete);
+ if (cdns_xspi->sdma_error) {
+ cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ return -EIO;
+ }
+ } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, true)) {
return -EIO;
}
+
cdns_xspi->sdma_handler(cdns_xspi);
}
- wait_for_completion(&cdns_xspi->cmd_complete);
- cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ if (cdns_xspi->irq >= 0) {
+ wait_for_completion(&cdns_xspi->cmd_complete);
+ cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ } else if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) {
+ return -EIO;
+ }
cmd_status = cdns_xspi_check_command_status(cdns_xspi);
if (cmd_status)
@@ -1052,38 +1094,6 @@ static int cdns_xspi_prepare_transfer(int cs, int dir, int len, u32 *cmd_regs)
return 0;
}
-static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
-{
- u32 ctrl_stat;
-
- return !readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
- ctrl_stat,
- ((ctrl_stat & BIT(3)) == 0),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
-}
-
-static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
-{
- u32 ctrl_stat;
-
- if (readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
- ctrl_stat,
- (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
- return false;
- }
-
- /* Clear the flag */
- writel(CDNS_XSPI_SDMA_TRIGGER,
- cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
-
- return true;
-}
-
static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
struct spi_message *m)
{
@@ -1276,15 +1286,17 @@ static int cdns_xspi_probe(struct platform_device *pdev)
}
#endif
- cdns_xspi->irq = platform_get_irq(pdev, 0);
- if (cdns_xspi->irq < 0)
- return -ENXIO;
+ cdns_xspi->irq = platform_get_irq_optional(pdev, 0);
+ if (cdns_xspi->irq < 0 && cdns_xspi->irq != -ENXIO)
+ return cdns_xspi->irq;
- ret = devm_request_irq(dev, cdns_xspi->irq, cdns_xspi_irq_handler,
- IRQF_SHARED, pdev->name, cdns_xspi);
- if (ret) {
- dev_err(dev, "Failed to request IRQ: %d\n", cdns_xspi->irq);
- return ret;
+ if (cdns_xspi->irq >= 0) {
+ ret = devm_request_irq(dev, cdns_xspi->irq, cdns_xspi_irq_handler,
+ IRQF_SHARED, pdev->name, cdns_xspi);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ: %d\n", cdns_xspi->irq);
+ return ret;
+ }
}
#ifdef CONFIG_64BIT
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v3 6/7] spi: cadence-xspi: Add support for panic writes
2026-10-14 15:42 [PATCH v3 0/7] spi: Add support for panic mem writes Paul Cercueil
` (4 preceding siblings ...)
2026-10-14 15:42 ` [PATCH v3 5/7] spi: cadence-xspi: Add irq-less support Paul Cercueil
@ 2026-10-14 15:43 ` Paul Cercueil
2026-10-14 15:43 ` [PATCH v3 7/7] spi: dt-bindings: cadence: Make XSPI's IRQ line optional Paul Cercueil
6 siblings, 0 replies; 8+ messages in thread
From: Paul Cercueil @ 2026-10-14 15:43 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown,
Thomas Petazzoni
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, linux-mtd,
linux-kernel, linux-spi, Bharat Bhushan, Sunil Kovvuri Goutham,
Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Advertise support for panic writes. When a memory op is received with
the 'panic' flag set, the driver will then work without relying on
interrupts and without sleeping.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: - Don't enable interrupts if doing a panic write
- Use a new cdns_readl_poll() macro that will resolve to calling
readl_relaxed_poll_timeout_atomic() when doing a panic write, or
readl_relaxed_poll_timeout() otherwise.
- Drop cdns_xspi->panic_write. We don't actually need to check it in
cdns_xspi_is_dll_locked(), because this code is never called in
the kernel panic path.
v3: No change
drivers/spi/spi-cadence-xspi.c | 52 ++++++++++++++++++----------------
1 file changed, 27 insertions(+), 25 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 2a2a40bf0a4c..a7fd715c45d5 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -287,6 +287,10 @@
#define GENERIC_CMD_REG_3_NEEDED(len) (len > 7)
#define GENERIC_CMD_REG_2_NEEDED(len) (len > 3)
+#define cdns_readl_poll(addr, val, cond, delay_us, timeout_us, can_sleep) \
+ ((can_sleep) ? readl_relaxed_poll_timeout(addr, val, cond, delay_us, timeout_us) \
+ : readl_relaxed_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us))
+
enum cdns_xspi_stig_instr_type {
CDNS_XSPI_STIG_INSTR_TYPE_0,
CDNS_XSPI_STIG_INSTR_TYPE_1,
@@ -353,40 +357,33 @@ struct cdns_xspi_dev {
int current_xfer_qword;
};
-static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
+static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- return readl_relaxed_poll_timeout(cdns_xspi->iobase +
- CDNS_XSPI_CTRL_STATUS_REG,
- ctrl_stat,
- ((ctrl_stat &
- CDNS_XSPI_CTRL_BUSY) == 0),
- 100, 1000);
+ return cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat, !(ctrl_stat & CDNS_XSPI_CTRL_BUSY),
+ 100, 1000, sleep);
}
static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- return !readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
- ctrl_stat,
- ((ctrl_stat & BIT(3)) == 0),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
+ return !cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat, !(ctrl_stat & BIT(3)),
+ MRVL_XSPI_POLL_DELAY_US,
+ MRVL_XSPI_POLL_TIMEOUT_US, sleep);
}
static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- if (readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
- ctrl_stat,
- (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
+ if (cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ ctrl_stat, (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
+ MRVL_XSPI_POLL_DELAY_US, MRVL_XSPI_POLL_TIMEOUT_US,
+ sleep)) {
return false;
}
@@ -557,14 +554,14 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
int ret;
int dummybytes = op->dummy.nbytes;
- ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
+ ret = cdns_xspi_wait_for_controller_idle(cdns_xspi, !op->panic_write);
if (ret < 0)
return -EIO;
writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, CDNS_XSPI_WORK_MODE_STIG),
cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
- if (cdns_xspi->irq >= 0)
+ if (cdns_xspi->irq >= 0 && !op->panic_write)
cdns_xspi->set_interrupts_handler(cdns_xspi, true);
cdns_xspi->sdma_error = false;
@@ -595,23 +592,23 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
- if (cdns_xspi->irq >= 0) {
+ if (cdns_xspi->irq >= 0 && !op->panic_write) {
wait_for_completion(&cdns_xspi->sdma_complete);
if (cdns_xspi->sdma_error) {
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
return -EIO;
}
- } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, true)) {
+ } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, !op->panic_write)) {
return -EIO;
}
cdns_xspi->sdma_handler(cdns_xspi);
}
- if (cdns_xspi->irq >= 0) {
+ if (cdns_xspi->irq >= 0 && !op->panic_write) {
wait_for_completion(&cdns_xspi->cmd_complete);
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
- } else if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) {
+ } else if (!cdns_xspi_is_stig_ready(cdns_xspi, !op->panic_write)) {
return -EIO;
}
@@ -1195,6 +1192,10 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
}
#endif
+static const struct spi_controller_mem_caps cdns_xspi_mem_caps = {
+ .panic_write = true,
+};
+
static int cdns_xspi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1315,6 +1316,7 @@ static int cdns_xspi_probe(struct platform_device *pdev)
}
host->num_chipselect = 1 << cdns_xspi->hw_num_banks;
+ host->mem_caps = &cdns_xspi_mem_caps;
ret = devm_spi_register_controller(dev, host);
if (ret) {
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v3 7/7] spi: dt-bindings: cadence: Make XSPI's IRQ line optional
2026-10-14 15:42 [PATCH v3 0/7] spi: Add support for panic mem writes Paul Cercueil
` (5 preceding siblings ...)
2026-10-14 15:43 ` [PATCH v3 6/7] spi: cadence-xspi: Add support for panic writes Paul Cercueil
@ 2026-10-14 15:43 ` Paul Cercueil
6 siblings, 0 replies; 8+ messages in thread
From: Paul Cercueil @ 2026-10-14 15:43 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown,
Thomas Petazzoni
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, linux-mtd,
linux-kernel, linux-spi, Bharat Bhushan, Sunil Kovvuri Goutham,
Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
The spi-cadence-xspi driver will happily work without an IRQ line
registered, which means that this IRQ line is optional.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: New patch
v3: No change
Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 1 -
1 file changed, 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
index 38a5795589de..dd8d03c07e68 100644
--- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
+++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
@@ -43,7 +43,6 @@ properties:
required:
- compatible
- reg
- - interrupts
allOf:
- $ref: spi-controller.yaml#
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread