[PATCH v2 3/3] spi: cadence_qspi: Use APB write protection

Ralph Siemsen <[email protected]>
Newsgroups gmane.comp.boot-loaders.u-boot
Message-ID <20260814-rzn1-2026-10-spi-v2-3-973d5fa092f2__12178.3320488007$1786732158$gmane$org@linaro.org>
Add an option to make use of hardware write protection in the QSPI
controller. This helps prevent flash corruption due to stray writes.
No stray writes have been observed, this is defensive code.

When enabled, the controller is configured to block writes to flash. Due
to known errata [1] the full 32-bit address range is protected, even
though the flash occupies only a portion.

The protection gets temporarily lifted while intentionally writing
to flash, for example using "sf write" command.

[1] https://www.renesas.com/en/document/tcu/qspi-contoller-issue

Signed-off-by: Ralph Siemsen <[email protected]>
---
Changes in v2:
- patch was split from the series "Renesas RZ/N1 additional drivers"
  https://lore.kernel.org/u-boot/[email protected]/
- combine the enable()/disable() into a single function
- move the IS_ENABLED() check inside the function
- reword the commit message
- considering to drop this feature, since Linux disables it
---
 drivers/spi/Kconfig            | 11 +++++++++++
 drivers/spi/cadence_qspi.h     |  6 ++++++
 drivers/spi/cadence_qspi_apb.c | 23 +++++++++++++++++++++++
 3 files changed, 40 insertions(+)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 007ad5e7733..19f8d48507b 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -168,6 +168,17 @@ config CADENCE_QSPI
 	  used to access the SPI NOR flash on platforms embedding this
 	  Cadence IP core.
 
+config CADENCE_QSPI_WRITE_PROTECT
+	bool "Cadence QSPI write protection"
+	depends on CADENCE_QSPI
+	help
+	  Enable hardware write protection in the QSPI controller. Helps avoid
+	  flash corruption due to stray writes. The direct access region is kept
+	  locked by default, and only gets unlocked during "sf write".
+
+	  This is similar to SYS_FLASH_PROTECTION however blocking is done by
+	  the controller, rather than by the flash device.
+
 config HAS_CQSPI_REF_CLK
 	bool "Cadence QSPI static reference clock"
 	depends on CADENCE_QSPI
diff --git a/drivers/spi/cadence_qspi.h b/drivers/spi/cadence_qspi.h
index 1e9081c2d17..6b3ed46977a 100644
--- a/drivers/spi/cadence_qspi.h
+++ b/drivers/spi/cadence_qspi.h
@@ -119,6 +119,12 @@
 #define CQSPI_REG_IRQSTATUS                     0x40
 #define CQSPI_REG_IRQMASK                       0x44
 
+#define CQSPI_REG_LOWER_WRITE_PROTECT           0x50
+#define CQSPI_REG_UPPER_WRITE_PROTECT           0x54
+
+#define CQSPI_REG_WRITE_PROTECT_CTRL            0x58
+#define CQSPI_REG_WRPROT_ENABLE                 BIT(1)
+
 #define CQSPI_REG_INDIRECTRD                    0x60
 #define CQSPI_REG_INDIRECTRD_START              BIT(0)
 #define CQSPI_REG_INDIRECTRD_CANCEL             BIT(1)
diff --git a/drivers/spi/cadence_qspi_apb.c b/drivers/spi/cadence_qspi_apb.c
index 0d4bc685f5d..fc4649916ad 100644
--- a/drivers/spi/cadence_qspi_apb.c
+++ b/drivers/spi/cadence_qspi_apb.c
@@ -325,6 +325,15 @@ void cadence_qspi_apb_delay(void *reg_base,
 	cadence_qspi_apb_controller_enable(reg_base);
 }
 
+static void cadence_qspi_apb_write_protect(void *reg_base, bool enable)
+{
+	if (!IS_ENABLED(CONFIG_CADENCE_QSPI_WRITE_PROTECT))
+		return;
+
+	writel(enable ? CQSPI_REG_WRPROT_ENABLE : 0,
+	       reg_base + CQSPI_REG_WRITE_PROTECT_CTRL);
+}
+
 void cadence_qspi_apb_controller_init(struct cadence_spi_priv *priv)
 {
 	unsigned reg;
@@ -346,6 +355,18 @@ void cadence_qspi_apb_controller_init(struct cadence_spi_priv *priv)
 	/* Indirect mode configurations */
 	writel(priv->fifo_depth / 2, priv->regbase + CQSPI_REG_SRAMPARTITION);
 
+	if (IS_ENABLED(CONFIG_CADENCE_QSPI_WRITE_PROTECT)) {
+		/*
+		 * Enable AHB write protection, to reduce the chance of corrupting
+		 * flash memory due to a stray write within the DAC region.
+		 * Protect the entire address range, regardless of the flash size.
+		 * During "sf write" the protection will be temporarily disabled.
+		 */
+		writel(0, priv->regbase + CQSPI_REG_LOWER_WRITE_PROTECT);
+		writel(~0, priv->regbase + CQSPI_REG_UPPER_WRITE_PROTECT);
+		cadence_qspi_apb_write_protect(priv->regbase, true);
+	}
+
 	/* Disable all interrupts */
 	writel(0, priv->regbase + CQSPI_REG_IRQMASK);
 
@@ -955,7 +976,9 @@ int cadence_qspi_apb_write_execute(struct cadence_spi_priv *priv,
 	 */
 	cadence_qspi_apb_enable_linear_mode(true);
 	if (!priv->dtr && priv->use_dac_mode && (to + len < priv->ahbsize)) {
+		cadence_qspi_apb_write_protect(priv->regbase, false);
 		memcpy_toio(priv->ahbbase + to, buf, len);
+		cadence_qspi_apb_write_protect(priv->regbase, true);
 		if (!cadence_qspi_wait_idle(priv->regbase))
 			return -EIO;
 		return 0;

-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.