Re: [PATCH 03/17] spi: cadence_qspi: Use APB write protection

Marek Vasut via U-Boot <[email protected]> Sun, 2 Aug 2026 05:33:45 +0200
Newsgroups gmane.comp.boot-loaders.u-boot
Message-ID <[email protected]>
On 7/31/26 6:33 PM, Ralph Siemsen wrote:
> Prevent random memory writes from corrupting flash memory.
> 
> When using the Direct Access Controller (DAC), the QSPI flash appears as
> a memory-mapped device. A stray write within the DAC region can corrupt
> flash contents.
> 
> To prevent this, enable the hardware write protection feature. Due to
> known errata [1] of the QSPI controller, the entire address range is
> protected, and only gets unlocked during intentional QSPI writes.
> 
> [1] https://www.renesas.com/en/document/tcu/qspi-contoller-issue
> 
> Signed-off-by: Ralph Siemsen <[email protected]>
> ---
>   drivers/spi/Kconfig            | 11 +++++++++++
>   drivers/spi/cadence_qspi.h     |  6 ++++++
>   drivers/spi/cadence_qspi_apb.c | 27 +++++++++++++++++++++++++++
>   3 files changed, 44 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..353b57a46af 100644
> --- a/drivers/spi/cadence_qspi_apb.c
> +++ b/drivers/spi/cadence_qspi_apb.c
> @@ -325,6 +325,17 @@ void cadence_qspi_apb_delay(void *reg_base,
>   	cadence_qspi_apb_controller_enable(reg_base);
>   }
>   
> +static void cadence_qspi_apb_write_protect_enable(void *reg_base)
> +{
> +	writel(CQSPI_REG_WRPROT_ENABLE,
> +	       reg_base + CQSPI_REG_WRITE_PROTECT_CTRL);
> +}
> +
> +static void cadence_qspi_apb_write_protect_disable(void *reg_base)
> +{
> +	writel(0, reg_base + CQSPI_REG_WRITE_PROTECT_CTRL);
> +}
> +
>   void cadence_qspi_apb_controller_init(struct cadence_spi_priv *priv)
>   {
>   	unsigned reg;
> @@ -346,6 +357,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_enable(priv->regbase);
> +	}
> +
>   	/* Disable all interrupts */
>   	writel(0, priv->regbase + CQSPI_REG_IRQMASK);
>   
> @@ -955,7 +978,11 @@ 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)) {
> +		if (IS_ENABLED(CONFIG_CADENCE_QSPI_WRITE_PROTECT))
> +			cadence_qspi_apb_write_protect_disable(priv->regbase);
>   		memcpy_toio(priv->ahbbase + to, buf, len);
> +		if (IS_ENABLED(CONFIG_CADENCE_QSPI_WRITE_PROTECT))
> +			cadence_qspi_apb_write_protect_enable(priv->regbase);

Wrap the if (IS_ENABLED()) into 
cadence_qspi_apb_write_protect_enable()/disable() and simply do

if (!IS_ENABLED()) return 0;

in there.

Also, maybe fold the two functions into a single one 
cadence_qspi_apb_write_protect(..., bool enable);