Skip to content

stm32: add memory-mapped mode for QUADSPI #72339

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion boards/st/stm32f769i_disco/stm32f769i_disco.dts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@
sw0 = &user_button;
spi-flash0 = &mx25l51245g;
};

quadspi_memory_avail: memory-avail@90000000 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to update board documentation to provide some details on how this could be used.

compatible = "zephyr,memory-region", "mmio-sram";
reg = <0x90000000 DT_SIZE_M(64)>;
zephyr,memory-region = "QSPI_AVAIL";
zephyr,memory-attr = <( DT_MEM_ARM(ATTR_MPU_IO) )>;
};
};

&clk_hse {
Expand Down Expand Up @@ -175,7 +182,7 @@ arduino_serial: &usart6 {};
mx25l51245g: qspi-nor-flash@90000000 {
compatible = "st,stm32-qspi-nor";
reg = <0x90000000 DT_SIZE_M(64)>; /* 512 Mbits */
qspi-max-frequency = <72000000>;
qspi-max-frequency = <DT_FREQ_M(66)>;
status = "okay";

partitions {
Expand Down
2 changes: 1 addition & 1 deletion boards/st/stm32f769i_disco/support/openocd.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ source [find board/stm32f769i-disco.cfg]

$_TARGETNAME configure -event gdb-attach {
echo "Debugger attaching: halting execution"
reset halt
reset init
gdb_breakpoint_override hard
}

Expand Down
7 changes: 7 additions & 0 deletions drivers/flash/Kconfig.stm32
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,11 @@ config FLASH_STM32_BLOCK_REGISTERS
registers improves system security, because flash content (or
protection settings) can't be changed even when exploit was found.

config STM32_MEMMAP
bool "NOR Flash in MemoryMapped for XiP"
depends on XIP
help
This option enables the XIP mode for the external NOR flash
mounted on STM32 boards.

endif # SOC_FLASH_STM32
123 changes: 123 additions & 0 deletions drivers/flash/flash_stm32_qspi.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
#include <zephyr/kernel.h>
#include <zephyr/toolchain.h>
#include <zephyr/arch/common/ffs.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/util.h>
#include <soc.h>
#include <string.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/clock_control/stm32_clock_control.h>
#include <zephyr/drivers/clock_control.h>
Expand Down Expand Up @@ -385,6 +387,66 @@ static bool qspi_address_is_valid(const struct device *dev, off_t addr,
return (addr >= 0) && ((uint64_t)addr + (uint64_t)size <= flash_size);
}

#ifdef CONFIG_STM32_MEMMAP
/* Must be called inside qspi_lock_thread(). */
static int stm32_qspi_set_memory_mapped(const struct device *dev)
{
int ret;
HAL_StatusTypeDef hal_ret;
struct flash_stm32_qspi_data *dev_data = dev->data;

QSPI_CommandTypeDef cmd = {
.Instruction = SPI_NOR_CMD_READ,
.Address = 0,
.InstructionMode = QSPI_INSTRUCTION_1_LINE,
.AddressMode = QSPI_ADDRESS_1_LINE,
.DataMode = QSPI_DATA_1_LINE,
};

qspi_set_address_size(dev, &cmd);
if (IS_ENABLED(STM32_QSPI_USE_QUAD_IO)) {
ret = qspi_prepare_quad_read(dev, &cmd);
if (ret < 0) {
return ret;
}
}

QSPI_MemoryMappedTypeDef mem_mapped = {
.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE,
};

hal_ret = HAL_QSPI_MemoryMapped(&dev_data->hqspi, &cmd, &mem_mapped);
if (hal_ret != 0) {
LOG_ERR("%d: Failed to enable memory mapped", hal_ret);
return -EIO;
}

LOG_DBG("MemoryMap mode enabled");
return 0;
}

static bool stm32_qspi_is_memory_mapped(const struct device *dev)
{
struct flash_stm32_qspi_data *dev_data = dev->data;

return READ_BIT(dev_data->hqspi.Instance->CCR, QUADSPI_CCR_FMODE) == QUADSPI_CCR_FMODE;
}

static int stm32_qspi_abort(const struct device *dev)
{
struct flash_stm32_qspi_data *dev_data = dev->data;
HAL_StatusTypeDef hal_ret;

hal_ret = HAL_QSPI_Abort(&dev_data->hqspi);
if (hal_ret != HAL_OK) {
LOG_ERR("%d: QSPI abort failed", hal_ret);
return -EIO;
}

return 0;
}
#endif

static int flash_stm32_qspi_read(const struct device *dev, off_t addr,
void *data, size_t size)
{
Expand All @@ -401,6 +463,27 @@ static int flash_stm32_qspi_read(const struct device *dev, off_t addr,
return 0;
}

#ifdef CONFIG_STM32_MEMMAP
qspi_lock_thread(dev);

/* Do reads through memory-mapping instead of indirect */
if (!stm32_qspi_is_memory_mapped(dev)) {
ret = stm32_qspi_set_memory_mapped(dev);
if (ret != 0) {
LOG_ERR("READ: failed to set memory mapped");
goto end;
}
}

__ASSERT_NO_MSG(stm32_qspi_is_memory_mapped(dev));

uintptr_t mmap_addr = STM32_QSPI_BASE_ADDRESS + addr;

LOG_DBG("Memory-mapped read from 0x%08lx, len %zu", mmap_addr, size);
memcpy(data, (void *)mmap_addr, size);
ret = 0;
goto end;
#else
QSPI_CommandTypeDef cmd = {
.Instruction = SPI_NOR_CMD_READ,
.Address = addr,
Expand All @@ -420,7 +503,10 @@ static int flash_stm32_qspi_read(const struct device *dev, off_t addr,
qspi_lock_thread(dev);

ret = qspi_read_access(dev, &cmd, data, size);
goto end;
#endif

end:
qspi_unlock_thread(dev);

return ret;
Expand Down Expand Up @@ -482,6 +568,17 @@ static int flash_stm32_qspi_write(const struct device *dev, off_t addr,

qspi_lock_thread(dev);

#ifdef CONFIG_STM32_MEMMAP
if (stm32_qspi_is_memory_mapped(dev)) {
/* Abort ongoing transfer to force CS high/BUSY deasserted */
ret = stm32_qspi_abort(dev);
if (ret != 0) {
LOG_ERR("Failed to abort memory-mapped access before write");
goto end;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion : LOG_DBG("Abort Memory-mapped before write");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That feels much less clear to me. For one it doesn't even mention that something went wrong, and it's also not the memory-mapping that's being aborted, just a latent read access, if any.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the suggestion was to put it after, once abort is completed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the suggestion was to put it after, once abort is completed.

@arbrauns Can you check this comment ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense, but then that LOG_DBG() would trigger on every single flash write/erase - would that make sense?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's already a LOG_DBG on flash_stm32_qspi_read so, that would be coherent.

}
#endif

while (size > 0) {
size_t to_write = size;

Expand Down Expand Up @@ -517,7 +614,9 @@ static int flash_stm32_qspi_write(const struct device *dev, off_t addr,
break;
}
}
goto end;

end:
qspi_unlock_thread(dev);

return ret;
Expand Down Expand Up @@ -555,6 +654,17 @@ static int flash_stm32_qspi_erase(const struct device *dev, off_t addr,
qspi_set_address_size(dev, &cmd_erase);
qspi_lock_thread(dev);

#ifdef CONFIG_STM32_MEMMAP
if (stm32_qspi_is_memory_mapped(dev)) {
/* Abort ongoing transfer to force CS high/BUSY deasserted */
ret = stm32_qspi_abort(dev);
if (ret != 0) {
LOG_ERR("Failed to abort memory-mapped access before erase");
goto end;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion : LOG_DBG("Abort Memory-mapped before erase");

}
#endif

while ((size > 0) && (ret == 0)) {
cmd_erase.Address = addr;
qspi_send_cmd(dev, &cmd_write_en);
Expand Down Expand Up @@ -596,7 +706,9 @@ static int flash_stm32_qspi_erase(const struct device *dev, off_t addr,
}
qspi_wait_until_ready(dev);
}
goto end;

end:
qspi_unlock_thread(dev);

return ret;
Expand Down Expand Up @@ -1367,9 +1479,20 @@ static int flash_stm32_qspi_init(const struct device *dev)
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */

#ifdef CONFIG_STM32_MEMMAP
ret = stm32_qspi_set_memory_mapped(dev);
if (ret != 0) {
LOG_ERR("Failed to enable memory-mapped mode: %d", ret);
return ret;
}
LOG_INF("Memory-mapped NOR quad-flash at 0x%lx (0x%x bytes)",
(long)(STM32_QSPI_BASE_ADDRESS),
dev_cfg->flash_size);
#else
LOG_INF("NOR quad-flash at 0x%lx (0x%x bytes)",
(long)(STM32_QSPI_BASE_ADDRESS),
dev_cfg->flash_size);
#endif

return 0;
}
Expand Down
4 changes: 2 additions & 2 deletions dts/arm/st/f7/stm32f7.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@
};
};

quadspi_memory: memory@90000000 {
quadspi_memory: memory-placeholder@90000000 {
compatible = "zephyr,memory-region", "mmio-sram";
reg = <0x90000000 DT_SIZE_M(256)>;
zephyr,memory-region = "QSPI";
zephyr,memory-region = "QSPI_PLACEHOLDER";
zephyr,memory-attr = <( DT_MEM_ARM(ATTR_MPU_EXTMEM) )>;
};

Expand Down
25 changes: 17 additions & 8 deletions samples/application_development/code_relocation_nocopy/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,14 @@ using a custom linker script.

Differently from the code relocation sample, this sample is relocating the
content of the ext_code.c file to a different FLASH section and the code is XIP
directly from there without the need to copy / relocate the code.
directly from there without the need to copy / relocate the code. All other code
(e.g. main(), Zephyr kernel) stays in the internal flash.

nRF5340 DK platform instructions
********************************

The nRF5340 DK has a 64 Mb external flash memory supporting Quad SPI. It is
possible to do XIP from the external flash memory.

The external flash memory is mapped to 0x10000000.

In this sample we relocate some of the code to the external flash memory with
the remaining Zephyr kernel in the internal flash.
mapped to 0x10000000.

To build and flash the application (including the external memory part):

Expand All @@ -31,7 +27,20 @@ To build and flash the application (including the external memory part):
:goals: build flash
:compact:

Execution output:
STM32F769I-Discovery platform instructions
******************************************

The stm32f769i_disco has 64MB of external flash attached via QSPI. It is mapped
to 0x90000000.

.. zephyr-app-commands::
:zephyr-app: samples/application_development/code_relocation_nocopy
:board: stm32f769i_disco
:goals: build flash
:compact:

Execution output
****************

.. code-block:: console

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CONFIG_FLASH=y
CONFIG_STM32_MEMMAP=y
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
#define EXTFLASH_SIZE DT_PROP_OR(EXTFLASH_NODE, size_in_bytes, \
DT_PROP(EXTFLASH_NODE, size) / 8)

#elif defined(CONFIG_STM32_MEMMAP) && DT_NODE_EXISTS(DT_INST(0, st_stm32_qspi_nor))
/* On stm32 QSPI, external flash is mapped in XIP region at address given by the reg property. */

#define EXTFLASH_NODE DT_INST(0, st_stm32_qspi_nor)
#define EXTFLASH_ADDR DT_REG_ADDR(DT_INST(0, st_stm32_qspi_nor))
#define EXTFLASH_SIZE DT_REG_ADDR_BY_IDX(DT_INST(0, st_stm32_qspi_nor), 1)

#else

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ tests:
platform_allow:
- qemu_cortex_m3
- nrf5340dk/nrf5340/cpuapp
- stm32f769i_disco
integration_platforms:
- qemu_cortex_m3
tags: linker
Expand Down
Loading