diff --git a/drivers/mspi/CMakeLists.txt b/drivers/mspi/CMakeLists.txt index cb2a985ec0739..851959955afcf 100644 --- a/drivers/mspi/CMakeLists.txt +++ b/drivers/mspi/CMakeLists.txt @@ -8,3 +8,6 @@ zephyr_library_sources_ifdef(CONFIG_MSPI_AMBIQ_AP5 mspi_ambiq_ap5.c) zephyr_library_sources_ifdef(CONFIG_MSPI_AMBIQ_TIMING_SCAN mspi_ambiq_timing_scan.c) zephyr_library_sources_ifdef(CONFIG_MSPI_DW mspi_dw.c) zephyr_library_sources_ifdef(CONFIG_MSPI_EMUL mspi_emul.c) +zephyr_library_sources_ifdef(CONFIG_MSPI_STM32_OSPI mspi_stm32_ospi.c) +zephyr_library_sources_ifdef(CONFIG_MSPI_STM32_QSPI mspi_stm32_qspi.c) +zephyr_library_sources_ifdef(CONFIG_MSPI_STM32_XSPI mspi_stm32_xspi.c) diff --git a/drivers/mspi/Kconfig b/drivers/mspi/Kconfig index 269d8d16f04ac..3938312630ef8 100644 --- a/drivers/mspi/Kconfig +++ b/drivers/mspi/Kconfig @@ -62,5 +62,6 @@ source "subsys/logging/Kconfig.template.log_config" source "drivers/mspi/Kconfig.ambiq" source "drivers/mspi/Kconfig.dw" source "drivers/mspi/Kconfig.mspi_emul" +source "drivers/mspi/Kconfig.stm32" endif # MSPI diff --git a/drivers/mspi/Kconfig.stm32 b/drivers/mspi/Kconfig.stm32 new file mode 100644 index 0000000000000..a411656a95338 --- /dev/null +++ b/drivers/mspi/Kconfig.stm32 @@ -0,0 +1,59 @@ +# STM32 MSPI flash driver configuration options + +# Copyright (c) 2025 STMicroelectronics +# SPDX-License-Identifier: Apache-2.0 + +config MSPI_STM32_XSPI + bool "STM32 XSPI Controller driver" + depends on DT_HAS_ST_STM32_XSPI_CONTROLLER_ENABLED + select USE_STM32_HAL_XSPI + select USE_STM32_LL_DLYB + select DMA + select USE_STM32_HAL_DMA + select USE_STM32_HAL_DMA_EX + imply MSPI_XIP + default y + help + Enable driver for STM32 family of processors. + +config MSPI_STM32_QSPI + bool "STM32 QSPI Controller driver" + default y + depends on DT_HAS_ST_STM32_QSPI_CONTROLLER_ENABLED + select USE_STM32_HAL_QSPI + select DMA + select USE_STM32_HAL_DMA + select USE_STM32_HAL_DMA_EX + select USE_STM32_HAL_MDMA + imply MSPI_XIP + help + Enable QSPI driver for STM32 family of processors. + +#DT_STM32_OCTOSPI_HAS_DMA := $(dt_compat_any_has_prop,$(DT_COMPAT_ST_STM32_OSPI),dmas) + +config MSPI_STM32_OSPI + bool "STM32 OSPI Controller driver" + default y + depends on DT_HAS_ST_STM32_OSPI_CONTROLLER_ENABLED + select USE_STM32_HAL_OSPI if !SOC_SERIES_STM32H5X + select USE_STM32_LL_DLYB if (SOC_SERIES_STM32H5X || SOC_SERIES_STM32U5X) + select USE_STM32_HAL_MDMA if SOC_SERIES_STM32H7X + select DMA + select USE_STM32_HAL_DMA + select USE_STM32_HAL_DMA_EX + help + Enable OSPI driver for STM32 family of processors. + +config MSPI_BUFFER_ALIGNMENT + int + default 32 + help + Some MSPI host controllers require alignment of their data buffers + in order for DMA to work correctly. This represents the alignment + of buffers required in bytes. + +config MSPI_STM32_ALLOW_WRITE_IN_MEMMAP + bool "Allow write in MEMMAP mode" + default n + help + Some MSPI host controllers allow to write in memory map mode. diff --git a/drivers/mspi/mspi_stm32.h b/drivers/mspi/mspi_stm32.h new file mode 100644 index 0000000000000..be937c8b7f4bb --- /dev/null +++ b/drivers/mspi/mspi_stm32.h @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2025 EXALT Technologies. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_MSPI_STM32_H_ +#define ZEPHYR_DRIVERS_MSPI_STM32_H_ +/* Macro to check if any xspi device has a domain clock or more */ +#define MSPI_STM32_DOMAIN_CLOCK_INST_SUPPORT(inst) DT_CLOCKS_HAS_IDX(DT_INST_PARENT(inst), 1) || +#define MSPI_STM32_INST_DEV_DOMAIN_CLOCK_SUPPORT \ + (DT_INST_FOREACH_STATUS_OKAY(MSPI_STM32_DOMAIN_CLOCK_INST_SUPPORT) 0) + +/* This symbol takes the value 1 if device instance has a domain clock in its dts */ +#if MSPI_STM32_INST_DEV_DOMAIN_CLOCK_SUPPORT +#define MSPI_STM32_DOMAIN_CLOCK_SUPPORT 1 +#else +#define MSPI_STM32_DOMAIN_CLOCK_SUPPORT 0 +#endif + +#define MSPI_STM32_FIFO_THRESHOLD 4U +#define MSPI_MAX_FREQ 250000000 +#define MSPI_MAX_DEVICE 2 + +#if defined(CONFIG_SOC_SERIES_STM32U5X) +/* Valid range is [1, 256] */ +#define MSPI_STM32_CLOCK_PRESCALER_MIN 1U +#define MSPI_STM32_CLOCK_PRESCALER_MAX 256U +#define MSPI_STM32_CLOCK_COMPUTE(bus_freq, prescaler) ((bus_freq) / (prescaler)) +#else +/* Valid range is [0, 255] */ +#define MSPI_STM32_CLOCK_PRESCALER_MIN 0U +#define MSPI_STM32_CLOCK_PRESCALER_MAX 255U +#define MSPI_STM32_CLOCK_COMPUTE(bus_freq, prescaler) ((bus_freq) / ((prescaler) + 1U)) +#endif + +#define MSPI_STM32_WRITE_REG_MAX_TIME 40U +#define MSPI_STM32_MAX_FREQ 48000000 + +#define MSPI_STM32_CMD_WRSR 0x01 /* Write status register */ +#define MSPI_STM32_CMD_RDSR 0x05 /* Read status register */ +#define MSPI_STM32_CMD_READ 0x03 /* Read data */ +#define MSPI_STM32_CMD_READ_FAST 0x0B /* Read data */ +#define MSPI_STM32_CMD_PP 0x02 /* Page program */ +#define MSPI_STM32_CMD_READ_4B 0x13 /* Read data 4 Byte Address */ +#define MSPI_STM32_CMD_READ_FAST_4B 0x0C /* Fast Read 4 Byte Address */ +#define MSPI_STM32_CMD_PP_4B 0x12 /* Page Program 4 Byte Address */ +#define MSPI_STM32_CMD_WREN 0x06 /* Write enable */ +#define MSPI_STM32_CMD_RDPD 0xAB /* Release from Deep Power Down */ +#define MSPI_STM32_CMD_RD_CFGREG2 0x71 /* Read config register 2 */ +#define MSPI_STM32_CMD_WR_CFGREG2 0x72 /* Write config register 2 */ +#define MSPI_STM32_CMD_SE 0x20 /* Sector erase */ +#define MSPI_STM32_CMD_SE_4B 0x21 /* Sector erase 4 byte address*/ + +#define MSPI_STM32_OCMD_RDSR 0x05FA /* Octal Read status register */ +#define MSPI_STM32_OCMD_RD 0xEC13 /* Octal IO read command */ +#define MSPI_STM32_OCMD_PAGE_PRG 0x12ED /* Octal Page Prog */ +#define MSPI_STM32_OCMD_WREN 0x06F9 /* Octal Write enable */ +#define MSPI_STM32_OCMD_DTR_RD 0xEE11 /* Octal IO DTR read command */ +#define MSPI_STM32_OCMD_WR_CFGREG2 0x728D /* Octal Write configuration Register2 */ +#define MSPI_STM32_OCMD_RD_CFGREG2 0x718E /* Octal Read configuration Register2 */ +#define MSPI_STM32_OCMD_SE 0x21DE /* Octal Sector erase */ + +/* Flash Auto-polling values */ +#define MSPI_STM32_WREN_MATCH 0x02 +#define MSPI_STM32_WREN_MASK 0x02 +#define MSPI_STM32_WEL_MATCH 0x00 +#define MSPI_STM32_WEL_MASK 0x02 +#define MSPI_STM32_MEM_RDY_MATCH 0x00 +#define MSPI_STM32_MEM_RDY_MASK 0x01 +#define MSPI_STM32_AUTO_POLLING_INTERVAL 0x10 + +/* Flash Dummy Cycles values */ +#define MSPI_STM32_DUMMY_REG_OCTAL 4U +#define MSPI_STM32_DUMMY_REG_OCTAL_DTR 5U + +/* Memory registers address */ +#define MSPI_STM32_REG2_ADDR1 0x0000000 +#define MSPI_STM32_CR2_STR_OPI_EN 0x01 +#define MSPI_STM32_CR2_DTR_OPI_EN 0x02 +#define MSPI_STM32_REG2_ADDR3 0x00000300 +#define MSPI_STM32_CR2_DUMMY_CYCLES_66MHZ 0x07 + +typedef void (*irq_config_func_t)(void); + +#ifndef DMA_LOW_PRIORITY_LOW_WEIGHT +#define DMA_LOW_PRIORITY_LOW_WEIGHT DMA_PRIORITY_LOW +#endif + +#ifndef DMA_LOW_PRIORITY_MID_WEIGHT +#define DMA_LOW_PRIORITY_MID_WEIGHT DMA_PRIORITY_MEDIUM +#endif + +#ifndef DMA_LOW_PRIORITY_HIGH_WEIGHT +#define DMA_LOW_PRIORITY_HIGH_WEIGHT DMA_PRIORITY_HIGH +#endif + +#ifndef DMA_HIGH_PRIORITY +#define DMA_HIGH_PRIORITY DMA_PRIORITY_VERY_HIGH +#endif + +enum mspi_access_mode { + MSPI_ACCESS_ASYNC = 1, + MSPI_ACCESS_SYNC = 2, + MSPI_ACCESS_DMA = 3 +}; + +struct mspi_context { + struct mspi_xfer xfer; + int packets_left; + struct k_sem lock; +}; + +struct mspi_stm32_conf { + bool use_dma; + size_t pclk_len; + irq_config_func_t irq_config; + struct mspi_cfg mspicfg; + const struct stm32_pclken *pclken; + const struct pinctrl_dev_config *pcfg; +}; + +/* Lookup table to set dma priority from the DTS */ +static const uint32_t table_priority[] = { + DMA_LOW_PRIORITY_LOW_WEIGHT, + DMA_LOW_PRIORITY_MID_WEIGHT, + DMA_LOW_PRIORITY_HIGH_WEIGHT, + DMA_HIGH_PRIORITY, +}; + +/* Lookup table to set dma channel direction from the DTS */ +static const uint32_t table_direction[] = { + DMA_MEMORY_TO_MEMORY, + DMA_MEMORY_TO_PERIPH, + DMA_PERIPH_TO_MEMORY, +}; + +#if CONFIG_DMA_STM32U5 +static const uint32_t table_src_size[] = { + LL_DMA_SRC_DATAWIDTH_BYTE, + LL_DMA_SRC_DATAWIDTH_HALFWORD, + LL_DMA_SRC_DATAWIDTH_WORD, +}; + +static const uint32_t table_dest_size[] = { + LL_DMA_DEST_DATAWIDTH_BYTE, + LL_DMA_DEST_DATAWIDTH_HALFWORD, + LL_DMA_DEST_DATAWIDTH_WORD, +}; +#else +static const uint32_t table_m_size[] = { + LL_DMA_MDATAALIGN_BYTE, + LL_DMA_MDATAALIGN_HALFWORD, + LL_DMA_MDATAALIGN_WORD, +}; + +static const uint32_t table_p_size[] = { + LL_DMA_PDATAALIGN_BYTE, + LL_DMA_PDATAALIGN_HALFWORD, + LL_DMA_PDATAALIGN_WORD, +}; +#endif /* CONFIG_DMA_STM32U5 */ + +struct stream { + DMA_TypeDef *reg; + const struct device *dev; + uint32_t channel; + struct dma_config cfg; + uint8_t priority; + bool src_addr_increment; + bool dst_addr_increment; +}; + +union hmspi_handle { +#ifdef CONFIG_MSPI_STM32_XSPI + XSPI_HandleTypeDef xspi; +#endif +#ifdef CONFIG_MSPI_STM32_OSPI + OSPI_HandleTypeDef ospi; +#endif +#ifdef CONFIG_MSPI_STM32_QSPI + QSPI_HandleTypeDef qspi; +#endif +}; + +/* mspi data includes the controller specific config variable */ +struct mspi_stm32_data { + union hmspi_handle hmspi; + uint32_t memmap_base_addr; + struct mspi_context ctx; + struct mspi_dev_id *dev_id; + struct k_mutex lock; + struct k_sem sync; + struct mspi_dev_cfg dev_cfg; + struct mspi_xip_cfg xip_cfg; + struct stream dma_tx; + struct stream dma_rx; + struct stream dma; +}; + +#endif /* ZEPHYR_DRIVERS_MSPI_STM32_H_ */ diff --git a/drivers/mspi/mspi_stm32_ospi.c b/drivers/mspi/mspi_stm32_ospi.c new file mode 100644 index 0000000000000..5d6e6410ef92e --- /dev/null +++ b/drivers/mspi/mspi_stm32_ospi.c @@ -0,0 +1,1639 @@ +/* + * Copyright (c) 2025 EXALT Technologies + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ************************************************************************** + * MSPI flash controller driver for stm32 series with multi-SPI peripherals + * This driver is based on the stm32Cube HAL OSPI driver + * with one mspi DTS NODE + * ************************************************************************** + */ +#define DT_DRV_COMPAT st_stm32_ospi_controller + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STM32_OSPI_DLYB_BYPASSED DT_PROP(DT_DRV_INST(0), dlyb_bypass) + +#define DT_OSPI_IO_PORT_PROP_OR(prop, default_value) \ + COND_CODE_1(DT_NODE_HAS_PROP(DT_DRV_INST(0), prop), \ + (_CONCAT(HAL_OSPIM_, DT_STRING_TOKEN(DT_DRV_INST(0), prop))), \ + ((default_value))) + +#define DT_OSPI_PROP_OR(prop, default_value) \ + DT_PROP_OR(DT_DRV_INST(0), prop, default_value) + +LOG_MODULE_REGISTER(ospi_stm32, CONFIG_MSPI_LOG_LEVEL); + +#include "mspi_stm32.h" + +static inline int mspi_stm32_ospi_context_lock(struct mspi_context *ctx, + const struct mspi_dev_id *req, + const struct mspi_xfer *xfer, bool lockon) +{ + int ret = 0; + + if (k_sem_take(&ctx->lock, K_MSEC(xfer->timeout))) { + return -EBUSY; + } + ctx->xfer = *xfer; + ctx->packets_left = ctx->xfer.num_packet; + + return ret; +} + +/** + * Check if the MSPI bus is busy. + * + * @param controller MSPI emulation controller device. + * @return true The MSPI bus is busy. + * @return false The MSPI bus is idle. + */ +static inline bool mspi_stm32_ospi_is_inp(const struct device *controller) +{ + struct mspi_stm32_data *dev_data = controller->data; + + return (k_sem_count_get(&dev_data->ctx.lock) == 0); +} + +static uint32_t mspi_stm32_ospi_hal_address_size(uint8_t address_length) +{ + if (address_length == 4U) { + return HAL_OSPI_ADDRESS_32_BITS; + } + + return HAL_OSPI_ADDRESS_24_BITS; +} + + +/* + * Gives a OSPI_RegularCmdTypeDef with all parameters set + * except Instruction, Address, NbData + */ +static OSPI_RegularCmdTypeDef mspi_stm32_ospi_prepare_cmd(uint8_t cfg_mode, uint8_t cfg_rate) +{ + /* Command empty structure */ + OSPI_RegularCmdTypeDef cmd_tmp = {0}; + + cmd_tmp.OperationType = HAL_OSPI_OPTYPE_COMMON_CFG; + cmd_tmp.InstructionSize = ((cfg_mode == MSPI_IO_MODE_OCTAL) ? HAL_OSPI_INSTRUCTION_16_BITS + : HAL_OSPI_INSTRUCTION_8_BITS); + cmd_tmp.InstructionDtrMode = + ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_OSPI_INSTRUCTION_DTR_ENABLE + : HAL_OSPI_INSTRUCTION_DTR_DISABLE); + cmd_tmp.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE; + cmd_tmp.AddressDtrMode = ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_OSPI_ADDRESS_DTR_ENABLE + : HAL_OSPI_ADDRESS_DTR_DISABLE); + cmd_tmp.DataDtrMode = ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_OSPI_DATA_DTR_ENABLE + : HAL_OSPI_DATA_DTR_DISABLE); + /* AddressWidth must be set to 32bits for init and mem config phase */ + cmd_tmp.AddressSize = HAL_OSPI_ADDRESS_32_BITS; + cmd_tmp.DataDtrMode = ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_OSPI_DATA_DTR_ENABLE + : HAL_OSPI_DATA_DTR_DISABLE); + cmd_tmp.DQSMode = + ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_OSPI_DQS_ENABLE : HAL_OSPI_DQS_DISABLE); + cmd_tmp.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD; + + switch (cfg_mode) { + case MSPI_IO_MODE_OCTAL: { + cmd_tmp.InstructionMode = HAL_OSPI_INSTRUCTION_8_LINES; + cmd_tmp.AddressMode = HAL_OSPI_ADDRESS_8_LINES; + cmd_tmp.DataMode = HAL_OSPI_DATA_8_LINES; + break; + } + case MSPI_IO_MODE_QUAD: { + cmd_tmp.InstructionMode = HAL_OSPI_INSTRUCTION_4_LINES; + cmd_tmp.AddressMode = HAL_OSPI_ADDRESS_4_LINES; + cmd_tmp.DataMode = HAL_OSPI_DATA_4_LINES; + break; + } + case MSPI_IO_MODE_DUAL: { + cmd_tmp.InstructionMode = HAL_OSPI_INSTRUCTION_2_LINES; + cmd_tmp.AddressMode = HAL_OSPI_ADDRESS_2_LINES; + cmd_tmp.DataMode = HAL_OSPI_DATA_2_LINES; + break; + } + default: { + cmd_tmp.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE; + cmd_tmp.AddressMode = HAL_OSPI_ADDRESS_1_LINE; + cmd_tmp.DataMode = HAL_OSPI_DATA_1_LINE; + break; + } + } + + return cmd_tmp; +} + +static bool mspi_stm32_ospi_is_memorymap(const struct device *dev) +{ + struct mspi_stm32_data *dev_data = dev->data; + + return ((READ_BIT(dev_data->hmspi.ospi.Instance->CR, + OCTOSPI_CR_FMODE) == OCTOSPI_CR_FMODE) ? + true : false); +} + +static int mspi_stm32_ospi_memmap_off(const struct device *controller) +{ + struct mspi_stm32_data *dev_data = controller->data; + + if (HAL_OSPI_Abort(&dev_data->hmspi.ospi) != HAL_OK) { + LOG_ERR("MemMapped abort failed"); + return -EIO; + } + return 0; +} + +/* Set the device in MemMapped mode */ +static int mspi_stm32_ospi_memmap_on(const struct device *controller) +{ + struct mspi_stm32_data *dev_data = controller->data; + OSPI_RegularCmdTypeDef s_command = + mspi_stm32_ospi_prepare_cmd(dev_data->dev_cfg.io_mode, dev_data->dev_cfg.data_rate); + OSPI_MemoryMappedTypeDef s_MemMappedCfg; + int ret; + + if (mspi_stm32_ospi_is_memorymap(controller)) { + return 0; + } + /* Configure in MemoryMapped mode */ + if ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) && + + (mspi_stm32_ospi_hal_address_size(dev_data->dev_cfg.addr_length) == + HAL_OSPI_ADDRESS_24_BITS)) { + /* OPI mode and 3-bytes address size not supported by memory */ + LOG_ERR("MSPI_IO_MODE_SINGLE in 3Bytes addressing is not supported"); + return -EIO; + } + + /* Initialize the read command */ + s_command.OperationType = HAL_OSPI_OPTYPE_READ_CFG; + s_command.InstructionMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? HAL_OSPI_INSTRUCTION_1_LINE + : HAL_OSPI_INSTRUCTION_8_LINES) + : HAL_OSPI_INSTRUCTION_8_LINES; + s_command.InstructionDtrMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? HAL_OSPI_INSTRUCTION_DTR_DISABLE + : HAL_OSPI_INSTRUCTION_DTR_ENABLE; + s_command.InstructionSize = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? HAL_OSPI_INSTRUCTION_8_BITS + : HAL_OSPI_INSTRUCTION_16_BITS) + : HAL_OSPI_INSTRUCTION_16_BITS; + s_command.Instruction = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? ((mspi_stm32_ospi_hal_address_size( + dev_data->dev_cfg.addr_length) == + HAL_OSPI_ADDRESS_24_BITS) + ? MSPI_STM32_CMD_READ_FAST + : MSPI_STM32_CMD_READ_FAST_4B) + : dev_data->dev_cfg.read_cmd) + : MSPI_STM32_OCMD_DTR_RD; + s_command.AddressMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? HAL_OSPI_ADDRESS_1_LINE + : HAL_OSPI_ADDRESS_8_LINES) + : HAL_OSPI_ADDRESS_8_LINES; + s_command.AddressDtrMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? HAL_OSPI_ADDRESS_DTR_DISABLE + : HAL_OSPI_ADDRESS_DTR_ENABLE; + s_command.AddressSize = + (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? mspi_stm32_ospi_hal_address_size(dev_data->dev_cfg.addr_length) + : HAL_OSPI_ADDRESS_32_BITS; + s_command.DataMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? HAL_OSPI_DATA_1_LINE + : HAL_OSPI_DATA_8_LINES) + : HAL_OSPI_DATA_8_LINES; + s_command.DataDtrMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? HAL_OSPI_DATA_DTR_DISABLE + : HAL_OSPI_DATA_DTR_ENABLE; + s_command.DummyCycles = dev_data->ctx.xfer.rx_dummy; + s_command.DQSMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? HAL_OSPI_DQS_DISABLE + : HAL_OSPI_DQS_ENABLE; + + + ret = HAL_OSPI_Command(&dev_data->hmspi.ospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE); + if (ret != HAL_OK) { + LOG_ERR("Failed to set memory map %d", dev_data->hmspi.ospi.ErrorCode); + return -EIO; + } + + /* Initialize the program command */ + s_command.OperationType = HAL_OSPI_OPTYPE_WRITE_CFG; + if (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) { + s_command.Instruction = (dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? ((mspi_stm32_ospi_hal_address_size( + dev_data->ctx.xfer.addr_length) == + HAL_OSPI_ADDRESS_24_BITS) + ? MSPI_STM32_CMD_PP + : MSPI_STM32_CMD_PP_4B) + : MSPI_STM32_OCMD_PAGE_PRG; + } else { + s_command.Instruction = MSPI_STM32_OCMD_PAGE_PRG; + } + s_command.DQSMode = HAL_OSPI_DQS_DISABLE; + ret = HAL_OSPI_Command(&dev_data->hmspi.ospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE); + if (ret != HAL_OK) { + LOG_ERR("Failed to set memory mapped"); + return -EIO; + } + + /* Enable the memory-mapping */ + s_MemMappedCfg.TimeOutActivation = HAL_OSPI_TIMEOUT_COUNTER_DISABLE; + ret = HAL_OSPI_MemoryMapped(&dev_data->hmspi.ospi, &s_MemMappedCfg); + if (ret != HAL_OK) { + LOG_ERR("Failed to enable memory mapped"); + return -EIO; + } + + LOG_INF("MEMORY MAPPED MODE ENABLED"); + + return 0; +} + + + +/* Send a Command to the NOR and Receive/Transceive data if relevant in IT or DMA mode */ +static int mspi_stm32_ospi_access(const struct device *dev, const struct mspi_xfer_packet *packet, + uint8_t access_mode) +{ + + struct mspi_stm32_data *dev_data = dev->data; + + HAL_StatusTypeDef hal_ret; + + if (dev_data->xip_cfg.enable) { + if (mspi_stm32_ospi_is_memorymap(dev) && packet->dir == MSPI_TX) { + + hal_ret = mspi_stm32_ospi_memmap_off(dev); + if (hal_ret != 0) { + LOG_ERR("Failed to abort mempry-mapped before write"); + goto end; + } + } else if (packet->dir == MSPI_RX) { + if (!mspi_stm32_ospi_is_memorymap(dev)) { + hal_ret = mspi_stm32_ospi_memmap_on(dev); + if (hal_ret != 0) { + LOG_ERR("Failed to set mempry-mapped before read"); + goto end; + } + } + memcpy(packet->data_buf, + (uint8_t *)dev_data->memmap_base_addr + packet->address, + packet->num_bytes); + goto end; + } + } + + OSPI_RegularCmdTypeDef cmd = + mspi_stm32_ospi_prepare_cmd(dev_data->dev_cfg.io_mode, dev_data->dev_cfg.data_rate); + + cmd.NbData = packet->num_bytes; + cmd.Instruction = packet->cmd; + if (packet->dir == MSPI_TX) { + cmd.DummyCycles = dev_data->ctx.xfer.tx_dummy; + } else { + cmd.DummyCycles = dev_data->ctx.xfer.rx_dummy; + } + cmd.Address = packet->address; + cmd.AddressSize = mspi_stm32_ospi_hal_address_size(dev_data->ctx.xfer.addr_length); + if (cmd.NbData == 0) { + cmd.DataMode = HAL_OSPI_DATA_NONE; + } + + if ((cmd.Instruction == MSPI_STM32_CMD_WREN) || (cmd.Instruction == MSPI_STM32_OCMD_WREN)) { + cmd.AddressMode = HAL_OSPI_ADDRESS_NONE; + } + + LOG_DBG("MSPI access Instruction 0x%x", cmd.Instruction); + + hal_ret = HAL_OSPI_Command(&dev_data->hmspi.ospi, &cmd, HAL_OSPI_TIMEOUT_DEFAULT_VALUE); + if (hal_ret != HAL_OK) { + LOG_ERR("%d: Failed to send OSPI instruction", hal_ret); + return -EIO; + } + + if (packet->num_bytes == 0) { + return 0; + } + + if (packet->dir == MSPI_RX) { + /* Receive the data */ + switch (access_mode) { + case MSPI_ACCESS_SYNC: + hal_ret = HAL_OSPI_Receive(&dev_data->hmspi.ospi, packet->data_buf, + HAL_OSPI_TIMEOUT_DEFAULT_VALUE); + goto e_access; + case MSPI_ACCESS_ASYNC: + hal_ret = HAL_OSPI_Receive_IT(&dev_data->hmspi.ospi, packet->data_buf); + break; + case MSPI_ACCESS_DMA: + hal_ret = HAL_OSPI_Receive_DMA(&dev_data->hmspi.ospi, packet->data_buf); + break; + default: + /* Not correct */ + hal_ret = HAL_BUSY; + break; + } + } else { + + /* Transmit the data */ + switch (access_mode) { + case MSPI_ACCESS_SYNC: + hal_ret = HAL_OSPI_Transmit(&dev_data->hmspi.ospi, packet->data_buf, + HAL_OSPI_TIMEOUT_DEFAULT_VALUE); + goto e_access; + case MSPI_ACCESS_ASYNC: + hal_ret = HAL_OSPI_Transmit_IT(&dev_data->hmspi.ospi, packet->data_buf); + break; + case MSPI_ACCESS_DMA: + hal_ret = HAL_OSPI_Transmit_DMA(&dev_data->hmspi.ospi, packet->data_buf); + break; + default: + /* Not correct */ + LOG_INF("default"); + hal_ret = HAL_BUSY; + break; + } + } + if (hal_ret != HAL_OK) { + LOG_ERR("%d: Failed to access data", hal_ret); + return -EIO; + } + + /* Lock again expecting the IRQ for end of Tx or Rx */ + if (k_sem_take(&dev_data->sync, K_FOREVER)) { + LOG_ERR("%d: Failed to access data", hal_ret); + return -EIO; + } + +e_access: + LOG_DBG("Access %zu data at 0x%lx", packet->num_bytes, (long)(packet->address)); +end: + return 0; +} + +/* Start Automatic-Polling mode to wait until the memory is setting mask/value bit */ +static int mspi_stm32_ospi_wait_auto_polling(const struct device *dev, uint8_t match_value, + uint8_t match_mask, uint32_t timeout_ms) +{ + struct mspi_stm32_data *dev_data = dev->data; + OSPI_AutoPollingTypeDef s_config; + + /* Set the match to check if the bit is Reset */ + s_config.Match = match_value; + s_config.Mask = match_mask; + + s_config.MatchMode = HAL_OSPI_MATCH_MODE_AND; + s_config.Interval = MSPI_STM32_AUTO_POLLING_INTERVAL; + s_config.AutomaticStop = HAL_OSPI_AUTOMATIC_STOP_ENABLE; + + if (HAL_OSPI_AutoPolling_IT(&dev_data->hmspi.ospi, &s_config) != HAL_OK) { + LOG_ERR("OSPI AutoPoll failed"); + return -EIO; + } + + if (k_sem_take(&dev_data->sync, K_MSEC(timeout_ms)) != 0) { + LOG_ERR("OSPI AutoPoll wait failed"); + HAL_OSPI_Abort(&dev_data->hmspi.ospi); + k_sem_reset(&dev_data->sync); + return -EIO; + } + + /* HAL_OSPI_AutoPolling_IT enables transfer error interrupt which sets + * cmd_status. + */ + return 0; +} + +/* + * Function to Read the status reg of the device + * Send the RDSR command (according to io_mode/data_rate + * Then set the Autopolling mode with match mask/value bit + * --> Blocking + */ +static int mspi_stm32_ospi_status_reg(const struct device *controller, const struct mspi_xfer *xfer) +{ + int ret = 0; + struct mspi_stm32_data *dev_data = controller->data; + + if (dev_data->xip_cfg.enable) { + if (mspi_stm32_ospi_is_memorymap(controller)) { + /* Abort Memory mapped mode before write*/ + ret = mspi_stm32_ospi_memmap_off(controller); + if (ret != 0) { + LOG_ERR("Failed to abort memory mapped mode before write"); + return -EIO; + } + } + } + + struct mspi_context *ctx = &dev_data->ctx; + + if (xfer->num_packet == 0 || !xfer->packets) { + LOG_ERR("Status Reg.: wrong parameters"); + return -EFAULT; + } + + /* Lock with the expected timeout value = ctx->xfer.timeout */ + ret = mspi_stm32_ospi_context_lock(ctx, dev_data->dev_id, xfer, true); + /** For async, user must make sure when cfg_flag = 0 the dummy and instr addr length + * in mspi_xfer of the two calls are the same if the first one has not finished yet. + */ + if (ret) { + goto status_err; + } + + OSPI_RegularCmdTypeDef cmd = + mspi_stm32_ospi_prepare_cmd(dev_data->dev_cfg.io_mode, dev_data->dev_cfg.data_rate); + /* with this command for tstaus Reg, only one packet containing 2 bytes match/mask */ + if (dev_data->dev_cfg.io_mode == MSPI_IO_MODE_OCTAL) { + cmd.Instruction = MSPI_STM32_OCMD_RDSR; + cmd.DummyCycles = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_DUAL) ? + MSPI_STM32_DUMMY_REG_OCTAL_DTR : MSPI_STM32_DUMMY_REG_OCTAL; + } else { + cmd.Instruction = MSPI_STM32_CMD_RDSR; + cmd.AddressMode = HAL_OSPI_ADDRESS_NONE; + cmd.DataMode = HAL_OSPI_DATA_1_LINE; /* 1-line DataMode for any non-OSPI transfer */ + /* DummyCycle to give to the mspi_stm32_read_access/mspi_stm32_write_access */ + cmd.DummyCycles = 0; + cmd.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE; + } + cmd.NbData = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_DUAL) ? 2U : 1U; + cmd.Address = 0U; + + LOG_DBG("MSPI poll status reg."); + + if (HAL_OSPI_Command(&dev_data->hmspi.ospi, &cmd, HAL_OSPI_TIMEOUT_DEFAULT_VALUE)) { + LOG_ERR("%d: Failed to send OSPI instruction", ret); + ret = -EIO; + goto status_err; + } + + ret = mspi_stm32_ospi_wait_auto_polling(controller, + MSPI_STM32_MEM_RDY_MATCH, + MSPI_STM32_MEM_RDY_MASK, + HAL_OSPI_TIMEOUT_DEFAULT_VALUE); + +status_err: + k_sem_give(&ctx->lock); + return ret; +} + +/* + * This function Polls the WIP(Write In Progress) bit to become to 0 + * in cfg_mode SPI/OPI MSPI_IO_MODE_SINGLE or MSPI_IO_MODE_OCTAL + * and cfg_rate transfer STR/DTR MSPI_DATA_RATE_SINGLE or MSPI_DATA_RATE_DUAL + */ +static int mspi_stm32_ospi_mem_ready(const struct device *dev, uint8_t cfg_mode, uint8_t cfg_rate) +{ + struct mspi_stm32_data *dev_data = dev->data; + + if (dev_data->xip_cfg.enable) { + if (mspi_stm32_ospi_is_memorymap(dev)) { + /* Abort Memory mapped mode before write*/ + int ret = mspi_stm32_ospi_memmap_off(dev); + + if (ret != 0) { + LOG_ERR("Failed to abort memory mapped mode before write"); + return -EIO; + } + } + } + + OSPI_RegularCmdTypeDef s_command = mspi_stm32_ospi_prepare_cmd(cfg_mode, cfg_rate); + + /* Configure automatic polling mode command to wait for memory ready */ + if (cfg_mode == MSPI_IO_MODE_OCTAL) { + s_command.Instruction = MSPI_STM32_OCMD_RDSR; + s_command.DummyCycles = (cfg_rate == MSPI_DATA_RATE_DUAL) + ? MSPI_STM32_DUMMY_REG_OCTAL_DTR + : MSPI_STM32_DUMMY_REG_OCTAL; + } else { + s_command.Instruction = MSPI_STM32_CMD_RDSR; + /* force 1-line InstructionMode for any non-OSPI transfer */ + s_command.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE; + s_command.AddressMode = HAL_OSPI_ADDRESS_NONE; + /* force 1-line DataMode for any non-OSPI transfer */ + s_command.DataMode = HAL_OSPI_DATA_1_LINE; + s_command.DummyCycles = 0; + } + s_command.NbData = ((cfg_rate == MSPI_DATA_RATE_DUAL) ? 2U : 1U); + s_command.Address = 0U; + + if (HAL_OSPI_Command(&dev_data->hmspi.ospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("MSPI AutoPoll command failed"); + return -EIO; + } + /* Set the match to 0x00 to check if the WIP bit is Reset */ + LOG_DBG("MSPI read status reg MemRdy"); + return mspi_stm32_ospi_wait_auto_polling(dev, + MSPI_STM32_MEM_RDY_MATCH, + MSPI_STM32_MEM_RDY_MASK, + HAL_OSPI_TIMEOUT_DEFAULT_VALUE); +} + +/* Enables writing to the memory sending a Write Enable and wait it is effective */ +static int mspi_stm32_ospi_write_enable(const struct device *dev, uint8_t cfg_mode, + uint8_t cfg_rate) +{ + struct mspi_stm32_data *dev_data = dev->data; + + if (dev_data->xip_cfg.enable) { + if (mspi_stm32_ospi_is_memorymap(dev)) { + /* Abort Memory mapped mode before write*/ + int ret = mspi_stm32_ospi_memmap_off(dev); + + if (ret != 0) { + LOG_ERR("Failed to abort memory mapped mode before write"); + return -EIO; + } + } + } + + OSPI_RegularCmdTypeDef s_command = mspi_stm32_ospi_prepare_cmd(cfg_mode, cfg_rate); + /* Initialize the write enable command */ + if (cfg_mode == MSPI_IO_MODE_OCTAL) { + s_command.Instruction = MSPI_STM32_OCMD_WREN; + } else { + s_command.Instruction = MSPI_STM32_CMD_WREN; + /* force 1-line InstructionMode for any non-OSPI transfer */ + s_command.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE; + } + s_command.AddressMode = HAL_OSPI_ADDRESS_NONE; + s_command.DataMode = HAL_OSPI_DATA_NONE; + s_command.DummyCycles = 0U; + + if (HAL_OSPI_Command(&dev_data->hmspi.ospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("MSPI flash write enable cmd failed"); + return -EIO; + } + LOG_DBG("MSPI write enable"); + + /* New command to Configure automatic polling mode to wait for write enabling */ + if (cfg_mode == MSPI_IO_MODE_OCTAL) { + s_command.Instruction = MSPI_STM32_OCMD_RDSR; + s_command.AddressMode = HAL_OSPI_ADDRESS_8_LINES; + s_command.DataMode = HAL_OSPI_DATA_8_LINES; + s_command.DummyCycles = (cfg_rate == MSPI_DATA_RATE_DUAL) + ? MSPI_STM32_DUMMY_REG_OCTAL_DTR + : MSPI_STM32_DUMMY_REG_OCTAL; + } else { + s_command.Instruction = MSPI_STM32_CMD_RDSR; + /* force 1-line DataMode for any non-OSPI transfer */ + s_command.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE; + s_command.AddressMode = HAL_OSPI_ADDRESS_1_LINE; + s_command.DataMode = HAL_OSPI_DATA_1_LINE; + s_command.DummyCycles = 0; + + /* DummyCycles remains 0 */ + } + s_command.NbData = (cfg_rate == MSPI_DATA_RATE_DUAL) ? 2U : 1U; + s_command.Address = 0U; + + if (HAL_OSPI_Command(&dev_data->hmspi.ospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("MSPI config auto polling cmd failed"); + return -EIO; + } + LOG_DBG("MSPI read status reg"); + + return mspi_stm32_ospi_wait_auto_polling(dev, + MSPI_STM32_WREN_MATCH, + MSPI_STM32_WREN_MASK, + HAL_OSPI_TIMEOUT_DEFAULT_VALUE); +} + +/* Write Flash configuration register 2 with new dummy cycles */ +static int mspi_stm32_opsi_write_cfg2reg_dummy(const struct device *dev, uint8_t cfg_mode, + uint8_t cfg_rate) +{ + + int ret = 0; + struct mspi_stm32_data *dev_data = dev->data; + + if (dev_data->xip_cfg.enable) { + if (mspi_stm32_ospi_is_memorymap(dev)) { + ret = mspi_stm32_ospi_memmap_off(dev); + if (ret != 0) { + LOG_ERR("Failed to abort mempry-mapped before write"); + goto end_write; + } + } + } + + uint8_t transmit_data = MSPI_STM32_CR2_DUMMY_CYCLES_66MHZ; + OSPI_RegularCmdTypeDef s_command = mspi_stm32_ospi_prepare_cmd(cfg_mode, cfg_rate); + + /* Initialize the writing of configuration register 2 */ + s_command.Instruction = (cfg_mode == MSPI_IO_MODE_SINGLE) ? MSPI_STM32_CMD_WR_CFGREG2 + : MSPI_STM32_OCMD_WR_CFGREG2; + s_command.Address = MSPI_STM32_REG2_ADDR3; + s_command.DummyCycles = 0U; + s_command.NbData = (cfg_mode == MSPI_IO_MODE_SINGLE) + ? 1U + : ((cfg_rate == MSPI_DATA_RATE_DUAL) ? 2U : 1U); + + if (HAL_OSPI_Command(&dev_data->hmspi.ospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("MSPI transmit cmd"); + return -EIO; + } + + if (HAL_OSPI_Transmit(&dev_data->hmspi.ospi, &transmit_data, + HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { + LOG_ERR("MSPI transmit "); + return -EIO; + } +end_write: + return 0; +} + +/* Write Flash configuration register 2 with new single or octal SPI protocol */ +static int mspi_stm32_ospi_write_cfg2reg_io(const struct device *dev, uint8_t cfg_mode, + uint8_t cfg_rate, uint8_t op_enable) +{ + int ret = 0; + struct mspi_stm32_data *dev_data = dev->data; + + if (dev_data->xip_cfg.enable) { + if (mspi_stm32_ospi_is_memorymap(dev)) { + /* Abort Memory mapped mode before write*/ + ret = mspi_stm32_ospi_memmap_off(dev); + if (ret != 0) { + LOG_ERR("Failed to abort memory mapped mode before write"); + return -EIO; + } + } + } + OSPI_RegularCmdTypeDef s_command = mspi_stm32_ospi_prepare_cmd(cfg_mode, cfg_rate); + + /* Initialize the writing of configuration register 2 */ + s_command.Instruction = (cfg_mode == MSPI_IO_MODE_SINGLE) + ? MSPI_STM32_CMD_WR_CFGREG2 + : MSPI_STM32_OCMD_WR_CFGREG2; + s_command.Address = MSPI_STM32_REG2_ADDR1; + s_command.DummyCycles = 0U; + s_command.NbData = (cfg_mode == MSPI_IO_MODE_SINGLE) + ? 1U + : ((cfg_rate == MSPI_DATA_RATE_DUAL) ? 2U : 1U); + + if (HAL_OSPI_Command(&dev_data->hmspi.ospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("Write Flash configuration reg2 failed"); + return -EIO; + } + + if (HAL_OSPI_Transmit(&dev_data->hmspi.ospi, &op_enable, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("Write Flash configuration reg2 failed"); + return -EIO; + } + + return ret; +} + +/* Read Flash configuration register 2 with new single or octal SPI protocol */ +static int mspi_stm32_ospi_read_cfg2reg(const struct device *dev, uint8_t cfg_mode, + uint8_t cfg_rate, uint8_t *value) +{ + int ret = 0; + struct mspi_stm32_data *dev_data = dev->data; + + if (dev_data->xip_cfg.enable) { + if (mspi_stm32_ospi_is_memorymap(dev)) { + /* Abort Memory mapped mode before write*/ + ret = mspi_stm32_ospi_memmap_off(dev); + if (ret != 0) { + LOG_ERR("Failed to abort memory mapped mode before write"); + return ret; + } + } + } + + OSPI_RegularCmdTypeDef s_command = mspi_stm32_ospi_prepare_cmd(cfg_mode, cfg_rate); + + /* Initialize the writing of configuration register 2 */ + s_command.Instruction = (cfg_mode == MSPI_IO_MODE_SINGLE) ? MSPI_STM32_CMD_RD_CFGREG2 + : MSPI_STM32_OCMD_RD_CFGREG2; + s_command.Address = MSPI_STM32_REG2_ADDR1; + s_command.DummyCycles = + (cfg_mode == MSPI_IO_MODE_SINGLE) + ? 0U + : ((cfg_rate == MSPI_DATA_RATE_DUAL) + ? MSPI_STM32_DUMMY_REG_OCTAL_DTR + : MSPI_STM32_DUMMY_REG_OCTAL); + s_command.NbData = (cfg_rate == MSPI_DATA_RATE_DUAL) ? 2U : 1U; + + if (HAL_OSPI_Command(&dev_data->hmspi.ospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("Write Flash configuration reg2 failed"); + return -EIO; + } + + if (HAL_OSPI_Receive(&dev_data->hmspi.ospi, value, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("Write Flash configuration reg2 failed"); + return -EIO; + } + + return 0; +} + +static int mspi_stm32_ospi_config_mem(const struct device *dev, uint8_t cfg_mode, uint8_t cfg_rate) +{ + struct mspi_stm32_data *dev_data = dev->data; + uint8_t reg[2]; + + if ((cfg_mode == MSPI_IO_MODE_SINGLE) && (cfg_rate == MSPI_DATA_RATE_SINGLE)) { + return 0; + } + + if (mspi_stm32_opsi_write_cfg2reg_dummy(dev, MSPI_IO_MODE_SINGLE, MSPI_DATA_RATE_SINGLE) != + 0) { + LOG_ERR("OSPI write CFGR2 failed"); + return -EIO; + } + if (mspi_stm32_ospi_mem_ready(dev, MSPI_IO_MODE_SINGLE, MSPI_DATA_RATE_SINGLE) != 0) { + LOG_ERR("OSPI autopolling failed"); + return -EIO; + } + if (mspi_stm32_ospi_write_enable(dev, MSPI_IO_MODE_SINGLE, MSPI_DATA_RATE_SINGLE) != 0) { + LOG_ERR("OSPI write Enable 2 failed"); + return -EIO; + } + + uint8_t mode_enable = ((cfg_rate == MSPI_DATA_RATE_DUAL) + ? MSPI_STM32_CR2_DTR_OPI_EN + : MSPI_STM32_CR2_STR_OPI_EN); + if (mspi_stm32_ospi_write_cfg2reg_io(dev, MSPI_IO_MODE_SINGLE, MSPI_DATA_RATE_SINGLE, + mode_enable) != 0) { + LOG_ERR("OSPI write CFGR2 failed"); + return -EIO; + } + + /* Wait that the configuration is effective and check that memory is ready */ + k_busy_wait(MSPI_STM32_WRITE_REG_MAX_TIME * USEC_PER_MSEC); + + /* Reconfigure the memory type of the peripheral */ + dev_data->hmspi.ospi.Init.MemoryType = HAL_OSPI_MEMTYPE_MACRONIX; + dev_data->hmspi.ospi.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_ENABLE; + + if (HAL_OSPI_Init(&dev_data->hmspi.ospi) != HAL_OK) { + LOG_ERR("OSPI mem type MACRONIX failed"); + return -EIO; + } + + if (mspi_stm32_ospi_mem_ready(dev, MSPI_IO_MODE_OCTAL, cfg_rate) != 0) { + /* Check Flash busy ? */ + LOG_ERR("OSPI flash busy failed"); + return -EIO; + } + if (mspi_stm32_ospi_read_cfg2reg(dev, MSPI_IO_MODE_OCTAL, cfg_rate, reg) != 0) { + LOG_ERR("MSPI flash config read failed"); + return -EIO; + } + + LOG_INF("OSPI flash config is OCTO / %s", ((cfg_rate == MSPI_DATA_RATE_SINGLE) ? + (char *)"STR" : + (char *)"DTR")); + + return 0; +} + +static void mspi_stm32_ospi_isr(const struct device *dev) +{ + struct mspi_stm32_data *dev_data = dev->data; + + k_sem_give(&dev_data->sync); + + HAL_OSPI_IRQHandler(&dev_data->hmspi.ospi); +} + +#if !defined(CONFIG_SOC_SERIES_STM32H7X) +/* weak function required for HAL compilation */ +__weak HAL_StatusTypeDef HAL_DMA_Abort_IT(DMA_HandleTypeDef *hdma) +{ + return HAL_OK; +} + +/* weak function required for HAL compilation */ +__weak HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma) +{ + return HAL_OK; +} +#endif /* !CONFIG_SOC_SERIES_STM32H7X */ + +static void mspi_stm32_ospi_dma_callback(const struct device *dev, void *arg, uint32_t channel, + int status) +{ + DMA_HandleTypeDef *hdma = arg; + + ARG_UNUSED(dev); + + if (status < 0) { + LOG_ERR("DMA callback error with channel %d.", channel); + } + + HAL_DMA_IRQHandler(hdma); +} +/** + * Check and save dev_cfg to controller data->dev_cfg. + * + * @param controller Pointer to the device structure for the driver instance. + * @param param_mask Macro definition of what to be configured in cfg. + * @param dev_cfg The device runtime configuration for the MSPI controller. + * @return 0 MSPI device configuration successful. + * @return -Error MSPI device configuration fail. + */ +static int mspi_stm32_ospi_dev_cfg_save(const struct device *controller, + const enum mspi_dev_cfg_mask param_mask, + const struct mspi_dev_cfg *dev_cfg) +{ + const struct mspi_stm32_conf *cfg = controller->config; + struct mspi_stm32_data *data = controller->data; + + if (param_mask & MSPI_DEVICE_CONFIG_CE_NUM) { + data->dev_cfg.ce_num = dev_cfg->ce_num; + } + + if (param_mask & MSPI_DEVICE_CONFIG_FREQUENCY) { + if (dev_cfg->freq > MSPI_MAX_FREQ) { + LOG_ERR("%u, freq is too large.", __LINE__); + return -ENOTSUP; + } + data->dev_cfg.freq = dev_cfg->freq; + } + + if (param_mask & MSPI_DEVICE_CONFIG_IO_MODE) { + if (dev_cfg->io_mode >= MSPI_IO_MODE_MAX) { + LOG_ERR("%u, Invalid io_mode.", __LINE__); + return -EINVAL; + } + data->dev_cfg.io_mode = dev_cfg->io_mode; + } + + if (param_mask & MSPI_DEVICE_CONFIG_DATA_RATE) { + if (dev_cfg->data_rate >= MSPI_DATA_RATE_MAX) { + LOG_ERR("%u, Invalid data_rate.", __LINE__); + return -EINVAL; + } + data->dev_cfg.data_rate = dev_cfg->data_rate; + } + + if (param_mask & MSPI_DEVICE_CONFIG_CPP) { + if (dev_cfg->cpp > MSPI_CPP_MODE_3) { + LOG_ERR("%u, Invalid cpp.", __LINE__); + return -EINVAL; + } + data->dev_cfg.cpp = dev_cfg->cpp; + } + + if (param_mask & MSPI_DEVICE_CONFIG_ENDIAN) { + if (dev_cfg->endian > MSPI_XFER_BIG_ENDIAN) { + LOG_ERR("%u, Invalid endian.", __LINE__); + return -EINVAL; + } + data->dev_cfg.endian = dev_cfg->endian; + } + + if (param_mask & MSPI_DEVICE_CONFIG_CE_POL) { + if (dev_cfg->ce_polarity > MSPI_CE_ACTIVE_HIGH) { + LOG_ERR("%u, Invalid ce_polarity.", __LINE__); + return -EINVAL; + } + data->dev_cfg.ce_polarity = dev_cfg->ce_polarity; + } + + if (param_mask & MSPI_DEVICE_CONFIG_DQS) { + if (dev_cfg->dqs_enable && !cfg->mspicfg.dqs_support) { + LOG_ERR("%u, DQS mode not supported.", __LINE__); + return -ENOTSUP; + } + data->dev_cfg.dqs_enable = dev_cfg->dqs_enable; + } + + if (param_mask & MSPI_DEVICE_CONFIG_RX_DUMMY) { + data->dev_cfg.rx_dummy = dev_cfg->rx_dummy; + } + + if (param_mask & MSPI_DEVICE_CONFIG_TX_DUMMY) { + data->dev_cfg.tx_dummy = dev_cfg->tx_dummy; + } + + if (param_mask & MSPI_DEVICE_CONFIG_READ_CMD) { + data->dev_cfg.read_cmd = dev_cfg->read_cmd; + } + + if (param_mask & MSPI_DEVICE_CONFIG_WRITE_CMD) { + data->dev_cfg.write_cmd = dev_cfg->write_cmd; + } + + if (param_mask & MSPI_DEVICE_CONFIG_CMD_LEN) { + data->dev_cfg.cmd_length = dev_cfg->cmd_length; + } + + if (param_mask & MSPI_DEVICE_CONFIG_ADDR_LEN) { + data->dev_cfg.addr_length = dev_cfg->addr_length; + } + + if (param_mask & MSPI_DEVICE_CONFIG_MEM_BOUND) { + data->dev_cfg.mem_boundary = dev_cfg->mem_boundary; + } + + if (param_mask & MSPI_DEVICE_CONFIG_BREAK_TIME) { + data->dev_cfg.time_to_break = dev_cfg->time_to_break; + } + + return 0; +} + +/** + * Verify if the device with dev_id is on this MSPI bus. + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @return 0 The device is on this MSPI bus. + * @return -ENODEV The device is not on this MSPI bus. + */ +static inline int mspi_stm32_ospi_verify_device(const struct device *controller, + const struct mspi_dev_id *dev_id) +{ + const struct mspi_stm32_conf *cfg = controller->config; + int device_index = cfg->mspicfg.num_periph; + int ret = 0; + + if (cfg->mspicfg.num_ce_gpios != 0) { + for (int i = 0; i < cfg->mspicfg.num_periph; i++) { + if (dev_id->ce.port == cfg->mspicfg.ce_group[i].port && + dev_id->ce.pin == cfg->mspicfg.ce_group[i].pin && + dev_id->ce.dt_flags == cfg->mspicfg.ce_group[i].dt_flags) { + device_index = i; + } + } + + if (device_index >= cfg->mspicfg.num_periph || device_index != dev_id->dev_idx) { + LOG_ERR("%u, invalid device ID.", __LINE__); + return -ENODEV; + } + } else { + if (dev_id->dev_idx >= cfg->mspicfg.num_periph) { + LOG_ERR("%u, invalid device ID.", __LINE__); + return -ENODEV; + } + } + + return ret; +} + +/** + * API implementation of mspi_dev_config : controller device specific configuration + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @param param_mask Macro definition of what to be configured in cfg. + * @param dev_cfg The device runtime configuration for the MSPI controller. + * + * @retval 0 if successful. + * @retval -EINVAL invalid capabilities, failed to configure device. + * @retval -ENOTSUP capability not supported by MSPI peripheral. + */ +static int mspi_stm32_ospi_dev_config(const struct device *controller, + const struct mspi_dev_id *dev_id, + const enum mspi_dev_cfg_mask param_mask, + const struct mspi_dev_cfg *dev_cfg) +{ + const struct mspi_stm32_conf *cfg = controller->config; + struct mspi_stm32_data *data = controller->data; + int ret = 0; + + if (data->dev_id != dev_id) { + if (k_mutex_lock(&data->lock, K_MSEC(CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE))) { + LOG_ERR("MSPI config failed to access controller."); + return -EBUSY; + } + + ret = mspi_stm32_ospi_verify_device(controller, dev_id); + if (ret) { + goto e_return; + } + } + + if (mspi_stm32_ospi_is_inp(controller)) { + ret = -EBUSY; + goto e_return; + } + + if (param_mask == MSPI_DEVICE_CONFIG_NONE && !cfg->mspicfg.sw_multi_periph) { + /* Do nothing except obtaining the controller lock */ + data->dev_id = (struct mspi_dev_id *)dev_id; + return ret; + } + + /* Proceed step by step in configuration */ + if (param_mask & (MSPI_DEVICE_CONFIG_IO_MODE | MSPI_DEVICE_CONFIG_DATA_RATE)) { + /* Going to set the OSPI mode and transfer rate */ + ret = mspi_stm32_ospi_config_mem(controller, dev_cfg->io_mode, dev_cfg->data_rate); + if (ret) { + goto e_return; + } + LOG_DBG("MSPI confg'd in %d / %d", dev_cfg->io_mode, dev_cfg->data_rate); + } + + /* + * The SFDP is able to change the addr_length 4bytes or 3bytes + * this is reflected by the serial_cfg + */ + data->dev_id = (struct mspi_dev_id *)dev_id; + /* Go on with other parameters if supported */ + if (mspi_stm32_ospi_dev_cfg_save(controller, param_mask, dev_cfg)) { + LOG_ERR("failed to set device config"); + return -EIO; + } + +e_return: + k_mutex_unlock(&data->lock); + + return ret; +} + +/** + * API implementation of mspi_xip_config : XIP configuration + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @param xip_cfg The controller XIP configuration for MSPI. + * + * @retval 0 if successful. + * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. + */ +static int mspi_stm32_ospi_xip_config(const struct device *controller, + const struct mspi_dev_id *dev_id, + const struct mspi_xip_cfg *xip_cfg) +{ + struct mspi_stm32_data *dev_data = controller->data; + int ret = 0; + + if (dev_id != dev_data->dev_id) { + LOG_ERR("dev_id don't match"); + return -ESTALE; + } + + if (!xip_cfg->enable) { + /* This is for aborting */ + ret = mspi_stm32_ospi_memmap_off(controller); + } else { + ret = mspi_stm32_ospi_memmap_on(controller); + } + + if (ret == 0) { + dev_data->xip_cfg = *xip_cfg; + LOG_INF("XIP configured %d", xip_cfg->enable); + } + + return ret; +} + +/** + * API implementation of mspi_get_channel_status. + * + * @param controller Pointer to the device structure for the driver instance. + * @param ch Not used. + * + * @retval 0 if successful. + * @retval -EBUSY MSPI bus is busy + */ +static int mspi_stm32_ospi_get_channel_status(const struct device *controller, uint8_t ch) +{ + struct mspi_stm32_data *dev_data = controller->data; + int ret = 0; + + ARG_UNUSED(ch); + + if (mspi_stm32_ospi_is_inp(controller) || + __HAL_OSPI_GET_FLAG(&dev_data->hmspi.ospi, HAL_OSPI_FLAG_BUSY) == SET) { + ret = -EBUSY; + } + + dev_data->dev_id = NULL; + + return ret; +} + +static int mspi_stm32_ospi_pio_transceive(const struct device *controller, + const struct mspi_xfer *xfer) +{ + int ret = 0; + uint32_t packet_idx; + struct mspi_stm32_data *dev_data = controller->data; + struct mspi_context *ctx = &dev_data->ctx; + const struct mspi_xfer_packet *packet; + + + if (xfer->num_packet == 0 || !xfer->packets || + xfer->timeout > CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE) { + LOG_ERR("Transfer: wrong parameters"); + return -EFAULT; + } + + /* DummyCycle to give to the mspi_stm32_read_access/mspi_stm32_write_access */ + ret = mspi_stm32_ospi_context_lock(ctx, dev_data->dev_id, xfer, true); + /** For async, user must make sure when cfg_flag = 0 the dummy and instr addr length + * in mspi_xfer of the two calls are the same if the first one has not finished yet. + */ + if (ret) { + + goto pio_end; + } + + /* Asynchronous transfer call read/write with IT and callback function */ + while (ctx->packets_left > 0) { + packet_idx = ctx->xfer.num_packet - ctx->packets_left; + packet = &ctx->xfer.packets[packet_idx]; + + ret = mspi_stm32_ospi_access(controller, packet, + (ctx->xfer.async == true) ? MSPI_ACCESS_ASYNC + : MSPI_ACCESS_SYNC); + + ctx->packets_left--; + if (ret) { + ret = -EIO; + goto pio_end; + } + } + +pio_end: + k_sem_give(&ctx->lock); + return ret; +} + +static int mspi_stm32_ospi_dma_transceive(const struct device *controller, + const struct mspi_xfer *xfer) +{ + int ret = 0; + struct mspi_stm32_data *dev_data = controller->data; + struct mspi_context *ctx = &dev_data->ctx; + const struct mspi_xfer_packet *packet; + + if (xfer->num_packet == 0 || !xfer->packets || + xfer->timeout > CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE) { + return -EFAULT; + } + + ret = mspi_stm32_ospi_context_lock(ctx, dev_data->dev_id, xfer, true); + /** For async, user must make sure when cfg_flag = 0 the dummy and instr addr length + * in mspi_xfer of the two calls are the same if the first one has not finished yet. + */ + if (ret) { + goto dma_end; + } + + /* TODO: enable DMA it */ + while (ctx->packets_left > 0) { + uint32_t packet_idx = ctx->xfer.num_packet - ctx->packets_left; + + packet = &ctx->xfer.packets[packet_idx]; + + ret = mspi_stm32_ospi_access(controller, packet, MSPI_ACCESS_DMA); + + ctx->packets_left--; + if (ret) { + ret = -EIO; + goto dma_end; + } + } + +dma_end: + k_sem_give(&ctx->lock); + return ret; +} + +/** + * API implementation of mspi_transceive. + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @param xfer Pointer to the MSPI transfer started by dev_id. + * + * @retval 0 if successful. + * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. + * @retval -Error transfer failed. + */ +static int mspi_stm32_ospi_transceive(const struct device *controller, + const struct mspi_dev_id *dev_id, const struct mspi_xfer *xfer) +{ + const struct mspi_stm32_conf *dev_conf = controller->config; + struct mspi_stm32_data *dev_data = controller->data; + + if (dev_id != dev_data->dev_id) { + LOG_ERR("transceive : dev_id don't match"); + return -ESTALE; + } + + /* Need to map the xfer to the data context */ + dev_data->ctx.xfer = *xfer; + + /* + * async + MSPI_PIO : Use callback on Irq if PIO + * sync + MSPI_PIO use timeout (mainly for NOR command and param + * MSPI_DMA : async/sync is meaningless with DMA (no DMA IT function)t + */ + if ((xfer->packets->cmd == MSPI_STM32_OCMD_RDSR) || + (xfer->packets->cmd == MSPI_STM32_CMD_RDSR)) { + /* This is a command and an autopolling on the status register */ + return mspi_stm32_ospi_status_reg(controller, xfer); + } + if (dev_conf->use_dma) { + /* Do not care about xfer->async */ + return mspi_stm32_ospi_dma_transceive(controller, xfer); + } else if (xfer->xfer_mode == MSPI_PIO) { + + return mspi_stm32_ospi_pio_transceive(controller, xfer); + } else { + return -EIO; + } +} + +/** + * API implementation of mspi_config : controller configuration. + * + * @param spec Pointer to MSPI device tree spec. + * @return 0 if successful. + * @return -Error if fail. + */ +static int mspi_stm32_ospi_config(const struct mspi_dt_spec *spec) +{ + const struct mspi_cfg *config = &spec->config; + const struct mspi_stm32_conf *dev_cfg = spec->bus->config; + struct mspi_stm32_data *dev_data = spec->bus->data; + + uint32_t ahb_clock_freq; + uint32_t prescaler = MSPI_STM32_CLOCK_PRESCALER_MIN; + int ret = 0; + + /* Only Controller mode is supported */ + if (config->op_mode != MSPI_OP_MODE_CONTROLLER) { + LOG_ERR("Only support MSPI controller mode."); + return -ENOTSUP; + } + + /* Check the max possible freq. */ + if (config->max_freq > MSPI_STM32_MAX_FREQ) { + LOG_ERR("Max_freq %d too large.", config->max_freq); + return -ENOTSUP; + } + + if (config->duplex != MSPI_HALF_DUPLEX) { + LOG_ERR("Only support half duplex mode."); + return -ENOTSUP; + } + + if (config->num_periph > MSPI_MAX_DEVICE) { + LOG_ERR("Invalid MSPI peripheral number."); + return -ENOTSUP; + } + + /* Signals configuration */ + ret = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("MSPI pinctrl setup failed"); + return ret; + } + + if (dev_data->dev_cfg.dqs_enable && !dev_cfg->mspicfg.dqs_support) { + LOG_ERR("MSPI dqs mismatch (not supported but enabled)"); + return -ENOTSUP; + } + + if (!device_is_ready(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE))) { + LOG_ERR("clock control device not ready"); + return -ENODEV; + } + dev_cfg->irq_config(); + + /* Max 3 domain clock are expected */ + if (dev_cfg->pclk_len > 3) { + LOG_ERR("Could not select %d OSPI domain clock", dev_cfg->pclk_len); + return -EIO; + } + + /* Clock configuration */ + if (clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[0]) != 0) { + LOG_ERR("Could not enable MSPI clock"); + return -EIO; + } + if (clock_control_get_rate(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[0], + &ahb_clock_freq) < 0) { + LOG_ERR("Failed call clock_control_get_rate(pclken)"); + return -EIO; + } + + /* Alternate clock config for peripheral if any */ + if (IS_ENABLED(MSPI_STM32_DOMAIN_CLOCK_SUPPORT) && (dev_cfg->pclk_len > 1)) { + if (clock_control_configure(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[1], + NULL) != 0) { + LOG_ERR("Could not select MSPI domain clock"); + return -EIO; + } + if (clock_control_get_rate(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[1], + &ahb_clock_freq) < 0) { + LOG_ERR("Failed call clock_control_get_rate(pclken)"); + return -EIO; + } + } + + if (IS_ENABLED(MSPI_STM32_DOMAIN_CLOCK_SUPPORT) && (dev_cfg->pclk_len > 2)) { + if (clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[2]) != 0) { + LOG_ERR("Could not enable OSPI Manager clock"); + return -EIO; + } + } + + for (; prescaler <= MSPI_STM32_CLOCK_PRESCALER_MAX; prescaler++) { + dev_data->dev_cfg.freq = MSPI_STM32_CLOCK_COMPUTE(ahb_clock_freq, prescaler); + + if (dev_data->dev_cfg.freq <= dev_cfg->mspicfg.max_freq) { + break; + } + } + __ASSERT_NO_MSG(prescaler >= MSPI_STM32_CLOCK_PRESCALER_MIN && + prescaler <= MSPI_STM32_CLOCK_PRESCALER_MAX); + + /* Initialize XSPI HAL structure completely */ + dev_data->hmspi.ospi.Init.ClockPrescaler = prescaler; + /** The stm32 hal_mspi driver does not reduce DEVSIZE before writing the DCR1 + * dev_data->hmspi.ospi.Init.MemorySize = find_lsb_set(dev_cfg->reg_size) - 2; + * dev_data->hmspi.ospi.Init.MemorySize is mandatory now (BUSY = 0) for HAL_XSPI Init + * give the value from the child node + */ +#if defined(XSPI_DCR2_WRAPSIZE) + dev_data->hmspi.ospi.Init.WrapSize = HAL_XSPI_WRAP_NOT_SUPPORTED; +#endif /* XSPI_DCR2_WRAPSIZE */ + /* STR mode else Macronix for DTR mode */ + if (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_DUAL) { + dev_data->hmspi.ospi.Init.MemoryType = HAL_OSPI_MEMTYPE_MACRONIX; + dev_data->hmspi.ospi.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_ENABLE; + } else { + dev_data->hmspi.ospi.Init.MemoryType = HAL_OSPI_MEMTYPE_MICRON; + dev_data->hmspi.ospi.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_DISABLE; + } +#if MSPI_STM32_DLYB_BYPASSED + dev_data->hmspi.ospi.Init.DelayBlockBypass = HAL_OSPI_DELAY_BLOCK_BYPASSED; + LOG_INF("HERE"); +#else + dev_data->hmspi.ospi.Init.DelayBlockBypass = HAL_OSPI_DELAY_BLOCK_USED; +#endif /* MSPI_STM32_DLYB_BYPASSED */ + + if (HAL_OSPI_Init(&dev_data->hmspi.ospi) != HAL_OK) { + LOG_ERR("MSPI Init failed"); + return -EIO; + } + + LOG_DBG("MSPI Init'd"); +#if defined(OCTOSPIM) + /* OCTOSPI I/O manager init Function */ + OSPIM_CfgTypeDef ospi_mgr_cfg = {0}; + + if (dev_data->hmspi.ospi.Instance == OCTOSPI1) { + ospi_mgr_cfg.ClkPort = DT_OSPI_PROP_OR(clk_port, 1); + ospi_mgr_cfg.DQSPort = DT_OSPI_PROP_OR(dqs_port, 1); + ospi_mgr_cfg.NCSPort = DT_OSPI_PROP_OR(ncs_port, 1); + ospi_mgr_cfg.IOLowPort = DT_OSPI_IO_PORT_PROP_OR(io_low_port, + HAL_OSPIM_IOPORT_1_LOW); + ospi_mgr_cfg.IOHighPort = DT_OSPI_IO_PORT_PROP_OR(io_high_port, + HAL_OSPIM_IOPORT_1_HIGH); + } else if (dev_data->hmspi.ospi.Instance == OCTOSPI2) { + ospi_mgr_cfg.ClkPort = DT_OSPI_PROP_OR(clk_port, 2); + ospi_mgr_cfg.DQSPort = DT_OSPI_PROP_OR(dqs_port, 2); + ospi_mgr_cfg.NCSPort = DT_OSPI_PROP_OR(ncs_port, 2); + ospi_mgr_cfg.IOLowPort = DT_OSPI_IO_PORT_PROP_OR(io_low_port, + HAL_OSPIM_IOPORT_2_LOW); + ospi_mgr_cfg.IOHighPort = DT_OSPI_IO_PORT_PROP_OR(io_high_port, + HAL_OSPIM_IOPORT_2_HIGH); + } +#if defined(OCTOSPIM_CR_MUXEN) + ospi_mgr_cfg.Req2AckTime = 1; +#endif /* OCTOSPIM_CR_MUXEN */ + if (HAL_OSPIM_Config(&dev_data->hmspi.ospi, &ospi_mgr_cfg, + HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { + LOG_ERR("OSPI M config failed"); + return -EIO; + } +#if defined(CONFIG_SOC_SERIES_STM32U5X) + /* OCTOSPI2 delay block init Function */ + HAL_OSPI_DLYB_CfgTypeDef ospi_delay_block_cfg = {0}; + + ospi_delay_block_cfg.Units = 56; + ospi_delay_block_cfg.PhaseSel = 2; + if (HAL_OSPI_DLYB_SetConfig(&dev_data->hmspi.ospi, &ospi_delay_block_cfg) != HAL_OK) { + LOG_ERR("OSPI DelayBlock failed"); + return -EIO; + } +#endif /* CONFIG_SOC_SERIES_STM32U5X */ + +#endif /* OCTOSPIM */ + + if (dev_cfg->use_dma) { + + struct dma_config dma_cfg = dev_data->dma.cfg; + static DMA_HandleTypeDef hdma; + + if (!device_is_ready(dev_data->dma.dev)) { + LOG_ERR("%s device not ready", dev_data->dma.dev->name); + return -ENODEV; + } + + dma_cfg.user_data = &hdma; + dma_cfg.linked_channel = STM32_DMA_HAL_OVERRIDE; + + ret = dma_config(dev_data->dma.dev, dev_data->dma.channel, &dma_cfg); + if (ret != 0) { + LOG_ERR("Failed to configure DMA channel %d", dev_data->dma.channel); + return ret; + } + + if (dma_cfg.source_data_size != dma_cfg.dest_data_size) { + LOG_ERR("Source and Destination data sizes are not aligned"); + return -EINVAL; + } + int index = find_lsb_set(dma_cfg.source_data_size) - 1; + +#if CONFIG_DMA_STM32U5 + /* Fill the structure for dma init */ + hdma.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST; + hdma.Init.SrcInc = DMA_SINC_FIXED; + hdma.Init.DestInc = DMA_DINC_INCREMENTED; + hdma.Init.SrcDataWidth = table_src_size[index]; + hdma.Init.DestDataWidth = table_dest_size[index]; + hdma.Init.SrcBurstLength = 4; + hdma.Init.DestBurstLength = 4; + hdma.Init.TransferAllocatedPort = + DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT1; + hdma.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER; +#else + hdma.Init.PeriphDataAlignment = table_p_size[index]; + hdma.Init.MemDataAlignment = table_m_size[index]; + hdma.Init.PeriphInc = DMA_PINC_DISABLE; + hdma.Init.MemInc = DMA_MINC_ENABLE; +#endif /* CONFIG_DMA_STM32U5 */ + + hdma.Init.Mode = DMA_NORMAL; + hdma.Init.Priority = table_priority[dma_cfg.channel_priority]; + hdma.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma.Instance = STM32_DMA_GET_INSTANCE(dev_data->dma.reg, dev_data->dma.channel); + hdma.Init.Request = dma_cfg.dma_slot; + __HAL_LINKDMA(&dev_data->hmspi.ospi, hdma, hdma); + if (HAL_DMA_Init(&hdma) != HAL_OK) { + LOG_ERR("OSPI DMA Init failed"); + return -EIO; + } + LOG_INF("OSPI with DMA Transfer"); + } + + if (!k_sem_count_get(&dev_data->ctx.lock)) { + k_sem_give(&dev_data->ctx.lock); + } + + if (config->re_init) { + k_mutex_unlock(&dev_data->lock); + } + + LOG_INF("MSPI config'd"); + + return 0; +} + +/** + * Set up a new controller and add its child to the list. + * + * @param dev MSPI emulation controller. + * + * @retval 0 if successful. + */ +static int mspi_stm32_ospi_init(const struct device *controller) +{ + const struct mspi_stm32_conf *cfg = controller->config; + const struct mspi_dt_spec spec = { + .bus = controller, + .config = cfg->mspicfg, + }; + + return mspi_stm32_ospi_config(&spec); +} + +static struct mspi_driver_api mspi_stm32_driver_api = { + .config = mspi_stm32_ospi_config, + .dev_config = mspi_stm32_ospi_dev_config, + .xip_config = mspi_stm32_ospi_xip_config, + .get_channel_status = mspi_stm32_ospi_get_channel_status, + .transceive = mspi_stm32_ospi_transceive, +}; + +#define DMA_CHANNEL_CONFIG(node, dir) DT_DMAS_CELL_BY_NAME(node, dir, channel_config) + +#define OSPI_DMA_CHANNEL_INIT(node, dir) \ + .dev = DEVICE_DT_GET(DT_DMAS_CTLR(node)), \ + .channel = DT_DMAS_CELL_BY_NAME(node, dir, channel), \ + .reg = (DMA_TypeDef *)DT_REG_ADDR(DT_PHANDLE_BY_NAME(node, dmas, dir)), \ + .cfg = { \ + .dma_slot = DT_DMAS_CELL_BY_NAME(node, dir, slot), \ + .source_data_size = \ + STM32_DMA_CONFIG_PERIPHERAL_DATA_SIZE(DMA_CHANNEL_CONFIG(node, dir)), \ + .dest_data_size = \ + STM32_DMA_CONFIG_MEMORY_DATA_SIZE(DMA_CHANNEL_CONFIG(node, dir)), \ + .channel_priority = STM32_DMA_CONFIG_PRIORITY(DMA_CHANNEL_CONFIG(node, dir)), \ + .dma_callback = mspi_stm32_ospi_dma_callback, \ + }, + +#define OSPI_DMA_CHANNEL(node, dir) \ + .dma = {COND_CODE_1(DT_DMAS_HAS_NAME(node, dir), \ + (OSPI_DMA_CHANNEL_INIT(node, dir)), \ + (NULL)) }, + +/* MSPI control config */ +#define MSPI_CONFIG(n) \ + { \ + .channel_num = 0, \ + .op_mode = DT_ENUM_IDX_OR(n, op_mode, MSPI_OP_MODE_CONTROLLER), \ + .duplex = DT_ENUM_IDX_OR(n, duplex, MSPI_HALF_DUPLEX), \ + .max_freq = DT_INST_PROP_OR(n, mspi_max_frequency, MSPI_STM32_MAX_FREQ), \ + .dqs_support = DT_INST_PROP_OR(n, dqs_support, false), \ + .num_periph = DT_INST_CHILD_NUM(n), \ + .sw_multi_periph = DT_INST_PROP_OR(n, software_multiperipheral, false), \ + } + +#define STM32_SMPI_IRQ_HANDLER(index) \ + static void mspi_stm32_irq_config_func_##index(void) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(index), DT_INST_IRQ(index, priority), \ + mspi_stm32_ospi_isr, DEVICE_DT_INST_GET(index), 0); \ + irq_enable(DT_INST_IRQN(index)); \ + } + +#define MSPI_STM32_INIT(index) \ + static const struct stm32_pclken pclken_##index[] = STM32_DT_INST_CLOCKS(index); \ + PINCTRL_DT_INST_DEFINE(index); \ + \ + static struct gpio_dt_spec ce_gpios##index[] = MSPI_CE_GPIOS_DT_SPEC_INST_GET(index); \ + STM32_SMPI_IRQ_HANDLER(index) \ + static const struct mspi_stm32_conf mspi_stm32_dev_conf_##index = { \ + .pclken = pclken_##index, \ + .pclk_len = DT_INST_NUM_CLOCKS(index), \ + .irq_config = mspi_stm32_irq_config_func_##index, \ + .mspicfg = MSPI_CONFIG(index), \ + .pcfg = PINCTRL_DT_DEV_CONFIG_GET(DT_DRV_INST(index)), \ + .mspicfg.num_ce_gpios = ARRAY_SIZE(ce_gpios##index), \ + .use_dma = DT_NODE_HAS_PROP(DT_DRV_INST(index), dmas), \ + }; \ + static struct mspi_stm32_data mspi_stm32_dev_data_##index = { \ + .hmspi.ospi = \ + { \ + .Instance = (OCTOSPI_TypeDef *)DT_INST_REG_ADDR(index), \ + .Init = \ + { \ + .FifoThreshold = MSPI_STM32_FIFO_THRESHOLD, \ + .SampleShifting = (DT_INST_PROP(index, ssht_enable)\ + ? HAL_OSPI_SAMPLE_SHIFTING_HALFCYCLE \ + : HAL_OSPI_SAMPLE_SHIFTING_NONE), \ + .ChipSelectHighTime = 1, \ + .ClockMode = HAL_OSPI_CLOCK_MODE_0, \ + .ChipSelectBoundary = 0, \ + .FreeRunningClock = HAL_OSPI_FREERUNCLK_DISABLE, \ + }, \ + }, \ + .memmap_base_addr = DT_REG_ADDR_BY_IDX(DT_DRV_INST(index), 1), \ + .dev_id = index, \ + .lock = Z_MUTEX_INITIALIZER(mspi_stm32_dev_data_##index.lock), \ + .sync = Z_SEM_INITIALIZER(mspi_stm32_dev_data_##index.sync, 0, 1), \ + .dev_cfg = {0}, \ + .xip_cfg = {0}, \ + .ctx.lock = Z_SEM_INITIALIZER(mspi_stm32_dev_data_##index.ctx.lock, 0, 1), \ + OSPI_DMA_CHANNEL(DT_DRV_INST(index), tx_rx)}; \ + \ + DEVICE_DT_INST_DEFINE(index, &mspi_stm32_ospi_init, NULL, &mspi_stm32_dev_data_##index,\ + &mspi_stm32_dev_conf_##index, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &mspi_stm32_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(MSPI_STM32_INIT) diff --git a/drivers/mspi/mspi_stm32_qspi.c b/drivers/mspi/mspi_stm32_qspi.c new file mode 100644 index 0000000000000..cdf5d522cbdfc --- /dev/null +++ b/drivers/mspi/mspi_stm32_qspi.c @@ -0,0 +1,1041 @@ +/* + * Copyright (c) 2025 EXALT Technologies. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ************************************************************************** + * MSPI flash controller driver for stm32 series with QSPI peripheral + * This driver is based on the stm32Cube HAL QSPI driver + * ************************************************************************** + */ +#define DT_DRV_COMPAT st_stm32_qspi_controller + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mspi_stm32.h" +#include + +LOG_MODULE_REGISTER(mspi_stm32_qspi, CONFIG_MSPI_LOG_LEVEL); + +static inline int mspi_context_lock(struct mspi_context *ctx, const struct mspi_dev_id *req, + const struct mspi_xfer *xfer, bool lockon) +{ + int ret = 0; + if (k_sem_take(&ctx->lock, K_MSEC(xfer->timeout))) { + return -EBUSY; + } + + ctx->xfer = *xfer; + + ctx->packets_left = ctx->xfer.num_packet; + return ret; +} + +/** + * @brief Gives a QSPI_CommandTypeDef with all parameters set except Instruction, Address, NbData + */ +static QSPI_CommandTypeDef mspi_stm32_qspi_prepare_cmd(uint8_t cfg_mode, uint8_t cfg_rate) +{ + /* Command empty structure */ + QSPI_CommandTypeDef cmd_tmp = {0}; + + /* QSPI specific configuration */ + cmd_tmp.AddressSize = QSPI_ADDRESS_24_BITS; + cmd_tmp.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; + cmd_tmp.DdrMode = + ((cfg_rate == MSPI_DATA_RATE_DUAL) ? QSPI_DDR_MODE_ENABLE : QSPI_DDR_MODE_DISABLE); + cmd_tmp.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; + cmd_tmp.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; + + /* Configure based on IO mode */ + switch (cfg_mode) { + case MSPI_IO_MODE_QUAD: { + cmd_tmp.InstructionMode = QSPI_INSTRUCTION_4_LINES; + cmd_tmp.AddressMode = QSPI_ADDRESS_4_LINES; + cmd_tmp.DataMode = QSPI_DATA_4_LINES; + break; + } + case MSPI_IO_MODE_QUAD_1_4_4: { + /* Command uses 1 line, address and data use 4 lines */ + cmd_tmp.InstructionMode = QSPI_INSTRUCTION_1_LINE; + cmd_tmp.AddressMode = QSPI_ADDRESS_4_LINES; + cmd_tmp.DataMode = QSPI_DATA_4_LINES; + break; + } + case MSPI_IO_MODE_QUAD_1_1_4: { + /* Command and address use 1 line, data uses 4 lines */ + cmd_tmp.InstructionMode = QSPI_INSTRUCTION_1_LINE; + cmd_tmp.AddressMode = QSPI_ADDRESS_1_LINE; + cmd_tmp.DataMode = QSPI_DATA_4_LINES; + break; + } + case MSPI_IO_MODE_DUAL: { + /* All phases use 2 lines */ + cmd_tmp.InstructionMode = QSPI_INSTRUCTION_2_LINES; + cmd_tmp.AddressMode = QSPI_ADDRESS_2_LINES; + cmd_tmp.DataMode = QSPI_DATA_2_LINES; + break; + } + case MSPI_IO_MODE_DUAL_1_2_2: { + /* Command uses 1 line, address and data use 2 lines */ + cmd_tmp.InstructionMode = QSPI_INSTRUCTION_1_LINE; + cmd_tmp.AddressMode = QSPI_ADDRESS_2_LINES; + cmd_tmp.DataMode = QSPI_DATA_2_LINES; + break; + } + case MSPI_IO_MODE_DUAL_1_1_2: { + /* Command and address use 1 line, data uses 2 lines */ + cmd_tmp.InstructionMode = QSPI_INSTRUCTION_1_LINE; + cmd_tmp.AddressMode = QSPI_ADDRESS_1_LINE; + cmd_tmp.DataMode = QSPI_DATA_2_LINES; + break; + } + case MSPI_IO_MODE_OCTAL: + /* QSPI doesn't support octal mode, fall through to single */ + LOG_WRN("QSPI doesn't support octal mode, using single line"); + __fallthrough; + case MSPI_IO_MODE_SINGLE: + default: { + /* All phases use 1 line */ + cmd_tmp.InstructionMode = QSPI_INSTRUCTION_1_LINE; + cmd_tmp.AddressMode = QSPI_ADDRESS_1_LINE; + cmd_tmp.DataMode = QSPI_DATA_1_LINE; + break; + } + } + + return cmd_tmp; +} + +/** + * Check if the MSPI bus is busy. + * + * @param controller MSPI emulation controller device. + * @return true The MSPI bus is busy. + * @return false The MSPI bus is idle. + */ +static inline bool mspi_is_inp(const struct device *controller) +{ + struct mspi_stm32_data *dev_data = controller->data; + + return (k_sem_count_get(&dev_data->ctx.lock) == 0); +} + +static uint32_t mspi_stm32_qspi_hal_address_size(uint8_t address_length) +{ + if (address_length == 4U) { + return QSPI_ADDRESS_32_BITS; + } + + return QSPI_ADDRESS_24_BITS; +} + +/* Check if device is in memory-mapped mode */ +static bool mspi_stm32_qspi_is_memmap(const struct device *controller) +{ + struct mspi_stm32_data *dev_data = controller->data; + + /* Check the FMODE bits in CCR register to see if in memory-mapped mode */ + return (READ_BIT(dev_data->hmspi.qspi.Instance->CCR, QUADSPI_CCR_FMODE) == + QUADSPI_CCR_FMODE); +} + +/* Set the device back in command mode */ +static int mspi_stm32_qspi_memmap_off(const struct device *controller) +{ + struct mspi_stm32_data *dev_data = controller->data; + + if (!mspi_stm32_qspi_is_memmap(controller)) { + /* Already in command mode */ + return 0; + } + + if (HAL_QSPI_Abort(&dev_data->hmspi.qspi) != HAL_OK) { + LOG_ERR("QSPI MemMapped abort failed"); + return -EIO; + } + + LOG_DBG("QSPI memory mapped mode disabled"); + return 0; +} + +/* Set the device in MemMapped mode */ +static int mspi_stm32_qspi_memmap_on(const struct device *controller) +{ + struct mspi_stm32_data *dev_data = controller->data; + QSPI_CommandTypeDef s_command; + QSPI_MemoryMappedTypeDef s_MemMappedCfg; + HAL_StatusTypeDef hal_ret; + + if (mspi_stm32_qspi_is_memmap(controller)) { + /* Already in memory-mapped mode */ + return 0; + } + + s_command = + mspi_stm32_qspi_prepare_cmd(dev_data->dev_cfg.io_mode, dev_data->dev_cfg.data_rate); + + /* Set read command - use the configured read command if available */ + if (dev_data->dev_cfg.read_cmd != 0) { + s_command.Instruction = dev_data->dev_cfg.read_cmd; + } else { + /* Fallback to standard fast read commands */ + if (dev_data->dev_cfg.addr_length == 4) { + s_command.Instruction = MSPI_STM32_CMD_READ_FAST_4B; + } else { + s_command.Instruction = MSPI_STM32_CMD_READ_FAST; + } + } + + s_command.AddressSize = mspi_stm32_qspi_hal_address_size(dev_data->dev_cfg.addr_length); + s_command.DummyCycles = dev_data->dev_cfg.rx_dummy; + s_command.Address = 0; + + /* Enable the memory-mapping */ + s_MemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE; + s_MemMappedCfg.TimeOutPeriod = 0; + + hal_ret = HAL_QSPI_MemoryMapped(&dev_data->hmspi.qspi, &s_command, &s_MemMappedCfg); + if (hal_ret != HAL_OK) { + LOG_ERR("Failed to enable QSPI memory mapped mode: %d", hal_ret); + return -EIO; + } + + LOG_DBG("QSPI memory mapped mode enabled"); + return 0; +} + +/** + * @brief Read data in memory-mapped mode (XIP) + * + * Note: Write operations are NOT supported in memory-mapped mode for QSPI. + * Writes must use indirect mode . + * + * @param dev Pointer to the device structure + * @param packet Pointer to transfer packet (must be RX direction) + * @return 0 on success, negative error code otherwise + */ +static int mspi_stm32_qspi_memory_mapped_read(const struct device *dev, + const struct mspi_xfer_packet *packet) +{ + struct mspi_stm32_data *dev_data = dev->data; + int ret; + + if (!mspi_stm32_qspi_is_memmap(dev)) { + ret = mspi_stm32_qspi_memmap_on(dev); + if (ret != 0) { + LOG_ERR("Failed to enable memory mapped mode"); + return ret; + } + } + + uintptr_t mmap_addr = dev_data->memmap_base_addr + packet->address; + + /* Memory-mapped mode is READ-ONLY for QSPI */ + LOG_DBG("Memory-mapped read from 0x%08lx, len %zu", mmap_addr, packet->num_bytes); + memcpy(packet->data_buf, (void *)mmap_addr, packet->num_bytes); + + return 0; +} + +/** + * @brief Send a Command to the NOR and Receive/Transceive data if relevant in IT or DMA mode. + * + */ +static int mspi_stm32_qspi_access(const struct device *dev, const struct mspi_xfer_packet *packet, + uint8_t access_mode) +{ + struct mspi_stm32_data *dev_data = dev->data; + HAL_StatusTypeDef hal_ret; + int ret; + + if (dev_data->xip_cfg.enable) { + ARG_UNUSED(access_mode); + + /* Commands that MUST use indirect mode even in XIP mode */ + if ((packet->cmd == MSPI_STM32_CMD_WREN) || (packet->cmd == MSPI_STM32_CMD_SE) || + (packet->cmd == MSPI_STM32_CMD_SE_4B) || (packet->cmd == MSPI_STM32_CMD_RDSR) || + (packet->dir == MSPI_TX)) { + + LOG_DBG("CMD 0x%02x needs indirect mode", packet->cmd); + + /* Switch to indirect mode temporarily */ + if (mspi_stm32_qspi_is_memmap(dev)) { + ret = mspi_stm32_qspi_memmap_off(dev); + if (ret != 0) { + LOG_ERR("Failed to abort memory-mapped mode"); + return ret; + } + } + } else { + /* Use memory-mapped mode for read operations */ + return mspi_stm32_qspi_memory_mapped_read(dev, packet); + } + } + + QSPI_CommandTypeDef cmd = + mspi_stm32_qspi_prepare_cmd(dev_data->dev_cfg.io_mode, dev_data->dev_cfg.data_rate); + + cmd.NbData = packet->num_bytes; + cmd.Instruction = packet->cmd; + if (packet->dir == MSPI_TX) { + cmd.DummyCycles = dev_data->ctx.xfer.tx_dummy; + } else { + cmd.DummyCycles = dev_data->ctx.xfer.rx_dummy; + } + cmd.Address = packet->address; + cmd.AddressSize = mspi_stm32_qspi_hal_address_size(dev_data->ctx.xfer.addr_length); + if (cmd.NbData == 0) { + cmd.DataMode = QSPI_DATA_NONE; + } + + if (cmd.Instruction == MSPI_STM32_CMD_WREN) { + cmd.AddressMode = QSPI_ADDRESS_NONE; + } + + hal_ret = HAL_QSPI_Command(&dev_data->hmspi.qspi, &cmd, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); + if (hal_ret != HAL_OK) { + LOG_ERR("HAL_QSPI_Command failed: %d", hal_ret); + return -EIO; + } + + if (packet->num_bytes == 0) { + return 0; + } + + if (packet->dir == MSPI_RX) { + /* Receive the data */ + switch (access_mode) { + case MSPI_ACCESS_SYNC: + hal_ret = HAL_QSPI_Receive(&dev_data->hmspi.qspi, packet->data_buf, + HAL_QSPI_TIMEOUT_DEFAULT_VALUE); + goto e_access; + case MSPI_ACCESS_ASYNC: + hal_ret = HAL_QSPI_Receive_IT(&dev_data->hmspi.qspi, packet->data_buf); + break; + default: + /* Not correct */ + hal_ret = HAL_BUSY; + break; + } + } else { + /* Transmit the data */ + switch (access_mode) { + case MSPI_ACCESS_SYNC: + hal_ret = HAL_QSPI_Transmit(&dev_data->hmspi.qspi, packet->data_buf, + HAL_QSPI_TIMEOUT_DEFAULT_VALUE); + goto e_access; + case MSPI_ACCESS_ASYNC: + hal_ret = HAL_QSPI_Transmit_IT(&dev_data->hmspi.qspi, packet->data_buf); + break; + default: + /* Not correct */ + hal_ret = HAL_BUSY; + break; + } + } + + if (hal_ret != HAL_OK) { + LOG_ERR("%d: Failed to access data", hal_ret); + return -EIO; + } + + /* Lock again expecting the IRQ for end of Tx or Rx */ + if (k_sem_take(&dev_data->sync, K_FOREVER)) { + LOG_ERR("%d: Failed to access data", hal_ret); + return -EIO; + } + +e_access: + LOG_DBG("Access %zu data at 0x%lx", packet->num_bytes, (long)(packet->address)); + + return 0; +} + +/** + * API implementation of mspi_config : controller configuration. + * + * @param spec Pointer to MSPI device tree spec. + * @return 0 if successful. + * @return -Error if fail. + */ +static int mspi_stm32_qspi_config(const struct mspi_dt_spec *spec) +{ + const struct device *controller = spec->bus; + const struct mspi_cfg *config = &spec->config; + const struct mspi_stm32_conf *cfg = controller->config; + struct mspi_stm32_data *data = controller->data; + QSPI_HandleTypeDef *hmspi = &data->hmspi.qspi; + HAL_StatusTypeDef hal_ret; + uint32_t ahb_clock_freq; + uint32_t prescaler = MSPI_STM32_CLOCK_PRESCALER_MIN; + int ret; + + LOG_DBG("Configuring QSPI controller"); + + /* Only Controller mode is supported */ + if (config->op_mode != MSPI_OP_MODE_CONTROLLER) { + LOG_ERR("Only support MSPI controller mode."); + return -ENOTSUP; + } + + /* Check the max possible freq. */ + if (config->max_freq > MSPI_MAX_FREQ) { + LOG_ERR("Max_freq %d too large.", config->max_freq); + return -ENOTSUP; + } + + if (config->duplex != MSPI_HALF_DUPLEX) { + LOG_ERR("Only support half duplex mode."); + return -ENOTSUP; + } + + if (config->num_periph > MSPI_MAX_DEVICE) { + LOG_ERR("Invalid MSPI peripheral number."); + return -ENOTSUP; + } + + /* Configure pins */ + ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("MSPI pinctrl setup failed"); + return ret; + } + + if (data->dev_cfg.dqs_enable && !cfg->mspicfg.dqs_support) { + LOG_ERR("MSPI dqs mismatch (not supported but enabled)"); + return -ENOTSUP; + } + + if (!device_is_ready(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE))) { + LOG_ERR("clock control device not ready"); + return -ENODEV; + } + + /* Configure IRQ */ + cfg->irq_config(); + + /* Clock configuration */ + if (clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&cfg->pclken[0]) != 0) { + LOG_ERR("Could not enable MSPI clock"); + return -EIO; + } + if (clock_control_get_rate(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&cfg->pclken[0], &ahb_clock_freq) < 0) { + LOG_ERR("Failed call clock_control_get_rate(pclken)"); + return -EIO; + } + + /* Calculate prescaler based on desired frequency */ + LOG_DBG("AHB clock: %u Hz, Max freq: %u Hz", ahb_clock_freq, cfg->mspicfg.max_freq); + + for (prescaler = 2; prescaler <= MSPI_STM32_CLOCK_PRESCALER_MAX; prescaler++) { + data->dev_cfg.freq = MSPI_STM32_CLOCK_COMPUTE(ahb_clock_freq, prescaler); + + if (data->dev_cfg.freq <= cfg->mspicfg.max_freq) { + break; + } + } + + /* Initialize QSPI handle with calculated prescaler */ + hmspi->Init.ClockPrescaler = prescaler; + hmspi->Init.FifoThreshold = MSPI_STM32_FIFO_THRESHOLD; + hmspi->Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE; + hmspi->Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE; + hmspi->Init.ClockMode = QSPI_CLOCK_MODE_0; + hmspi->Init.FlashID = QSPI_FLASH_ID_1; + hmspi->Init.DualFlash = QSPI_DUALFLASH_DISABLE; + + /* Initialize HAL QSPI */ + hal_ret = HAL_QSPI_Init(hmspi); + if (hal_ret != HAL_OK) { + LOG_ERR("HAL_QSPI_Init failed: %d", hal_ret); + return -EIO; + } + + /* Initialize semaphores */ + if (!k_sem_count_get(&data->ctx.lock)) { + k_sem_give(&data->ctx.lock); + } + + LOG_INF("QSPI controller configured successfully"); + + return 0; +} + +/** + * Verify if the device with dev_id is on this MSPI bus. + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @return 0 The device is on this MSPI bus. + * @return -ENODEV The device is not on this MSPI bus. + */ +static inline int mspi_verify_device(const struct device *controller, + const struct mspi_dev_id *dev_id) +{ + const struct mspi_stm32_conf *cfg = controller->config; + int device_index = cfg->mspicfg.num_periph; + int ret = 0; + + if (cfg->mspicfg.num_ce_gpios != 0) { + for (int i = 0; i < cfg->mspicfg.num_periph; i++) { + if (dev_id->ce.port == cfg->mspicfg.ce_group[i].port && + dev_id->ce.pin == cfg->mspicfg.ce_group[i].pin && + dev_id->ce.dt_flags == cfg->mspicfg.ce_group[i].dt_flags) { + device_index = i; + } + } + + if (device_index >= cfg->mspicfg.num_periph || device_index != dev_id->dev_idx) { + LOG_ERR("%u, invalid device ID.", __LINE__); + return -ENODEV; + } + } else { + if (dev_id->dev_idx >= cfg->mspicfg.num_periph) { + LOG_ERR("%u, invalid device ID.", __LINE__); + return -ENODEV; + } + } + + return ret; +} + +/** + * Validate frequency configuration. + */ +static int mspi_stm32_qspi_validate_freq(uint32_t freq) +{ + if (freq > MSPI_MAX_FREQ) { + LOG_ERR("%u, freq is too large", __LINE__); + return -ENOTSUP; + } + return 0; +} + +/** + * Validate QSPI-specific IO mode constraints. + * QSPI hardware doesn't support octal mode. + */ +static int mspi_stm32_qspi_validate_io_mode(uint32_t io_mode) +{ + if (io_mode == MSPI_IO_MODE_OCTAL) { + LOG_ERR("%u, QSPI doesn't support octal mode", __LINE__); + return -ENOTSUP; + } + + if (io_mode >= MSPI_IO_MODE_MAX) { + LOG_ERR("%u, Invalid io_mode", __LINE__); + return -EINVAL; + } + + return 0; +} + +/** + * Validate QSPI-specific data rate constraints. + * Only single data rate (SDR) is currently supported. + */ +static int mspi_stm32_qspi_validate_data_rate(uint32_t data_rate) +{ + if (data_rate != MSPI_DATA_RATE_SINGLE) { + LOG_ERR("%u, only single data rate supported", __LINE__); + return -ENOTSUP; + } + + if (data_rate >= MSPI_DATA_RATE_MAX) { + LOG_ERR("%u, Invalid data_rate", __LINE__); + return -EINVAL; + } + + return 0; +} + +/** + * Validate CPP (Clock Polarity/Phase) configuration. + */ +static int mspi_stm32_qspi_validate_cpp(uint32_t cpp) +{ + if (cpp > MSPI_CPP_MODE_3) { + LOG_ERR("%u, Invalid cpp", __LINE__); + return -EINVAL; + } + return 0; +} + +/** + * Validate endianness configuration. + */ +static int mspi_stm32_qspi_validate_endian(uint32_t endian) +{ + if (endian > MSPI_XFER_BIG_ENDIAN) { + LOG_ERR("%u, Invalid endian", __LINE__); + return -EINVAL; + } + return 0; +} + +/** + * Validate chip select polarity configuration. + */ +static int mspi_stm32_qspi_validate_ce_polarity(uint32_t ce_polarity) +{ + if (ce_polarity > MSPI_CE_ACTIVE_HIGH) { + LOG_ERR("%u, Invalid ce_polarity", __LINE__); + return -EINVAL; + } + return 0; +} + +/** + * Validate DQS (Data Strobe) configuration against hardware capabilities. + */ +static int mspi_stm32_qspi_validate_dqs(bool dqs_enable, bool dqs_support) +{ + if (dqs_enable && !dqs_support) { + LOG_ERR("%u, DQS mode not supported", __LINE__); + return -ENOTSUP; + } + return 0; +} + +/** + * Check and save dev_cfg to controller data->dev_cfg. + * + * @param controller Pointer to the device structure for the driver instance. + * @param param_mask Macro definition of what to be configured in cfg. + * @param dev_cfg The device runtime configuration for the MSPI controller. + * @return 0 MSPI device configuration successful. + * @return -Error MSPI device configuration fail. + */ +static int mspi_stm32_qspi_dev_cfg_save(const struct device *controller, + const enum mspi_dev_cfg_mask param_mask, + const struct mspi_dev_cfg *dev_cfg) +{ + const struct mspi_stm32_conf *cfg = controller->config; + struct mspi_stm32_data *data = controller->data; + int ret = 0; + + if (param_mask & MSPI_DEVICE_CONFIG_CE_NUM) { + data->dev_cfg.ce_num = dev_cfg->ce_num; + } + + if (param_mask & MSPI_DEVICE_CONFIG_FREQUENCY) { + ret = mspi_stm32_qspi_validate_freq(dev_cfg->freq); + if (ret) { + goto end; + } + data->dev_cfg.freq = dev_cfg->freq; + } + + if (param_mask & MSPI_DEVICE_CONFIG_IO_MODE) { + ret = mspi_stm32_qspi_validate_io_mode(dev_cfg->io_mode); + if (ret) { + goto end; + } + data->dev_cfg.io_mode = dev_cfg->io_mode; + } + + if (param_mask & MSPI_DEVICE_CONFIG_DATA_RATE) { + ret = mspi_stm32_qspi_validate_data_rate(dev_cfg->data_rate); + if (ret) { + goto end; + } + data->dev_cfg.data_rate = dev_cfg->data_rate; + } + + if (param_mask & MSPI_DEVICE_CONFIG_CPP) { + ret = mspi_stm32_qspi_validate_cpp(dev_cfg->cpp); + if (ret) { + goto end; + } + data->dev_cfg.cpp = dev_cfg->cpp; + } + + if (param_mask & MSPI_DEVICE_CONFIG_ENDIAN) { + ret = mspi_stm32_qspi_validate_endian(dev_cfg->endian); + if (ret) { + goto end; + } + data->dev_cfg.endian = dev_cfg->endian; + } + + if (param_mask & MSPI_DEVICE_CONFIG_CE_POL) { + ret = mspi_stm32_qspi_validate_ce_polarity(dev_cfg->ce_polarity); + if (ret) { + goto end; + } + data->dev_cfg.ce_polarity = dev_cfg->ce_polarity; + } + + if (param_mask & MSPI_DEVICE_CONFIG_DQS) { + ret = mspi_stm32_qspi_validate_dqs(dev_cfg->dqs_enable, cfg->mspicfg.dqs_support); + if (ret) { + goto end; + } + data->dev_cfg.dqs_enable = dev_cfg->dqs_enable; + } + + if (param_mask & MSPI_DEVICE_CONFIG_RX_DUMMY) { + data->dev_cfg.rx_dummy = dev_cfg->rx_dummy; + } + + if (param_mask & MSPI_DEVICE_CONFIG_TX_DUMMY) { + data->dev_cfg.tx_dummy = dev_cfg->tx_dummy; + } + + if (param_mask & MSPI_DEVICE_CONFIG_READ_CMD) { + data->dev_cfg.read_cmd = dev_cfg->read_cmd; + } + + if (param_mask & MSPI_DEVICE_CONFIG_WRITE_CMD) { + data->dev_cfg.write_cmd = dev_cfg->write_cmd; + } + + if (param_mask & MSPI_DEVICE_CONFIG_CMD_LEN) { + data->dev_cfg.cmd_length = dev_cfg->cmd_length; + } + + if (param_mask & MSPI_DEVICE_CONFIG_ADDR_LEN) { + data->dev_cfg.addr_length = dev_cfg->addr_length; + } + + if (param_mask & MSPI_DEVICE_CONFIG_MEM_BOUND) { + data->dev_cfg.mem_boundary = dev_cfg->mem_boundary; + } + + if (param_mask & MSPI_DEVICE_CONFIG_BREAK_TIME) { + data->dev_cfg.time_to_break = dev_cfg->time_to_break; + } + +end: + return ret; +} + +/** + * API implementation of mspi_dev_config : controller device specific configuration + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @param param_mask Macro definition of what to be configured in cfg. + * @param dev_cfg The device runtime configuration for the MSPI controller. + * + * @retval 0 if successful. + * @retval -EINVAL invalid capabilities, failed to configure device. + * @retval -ENOTSUP capability not supported by MSPI peripheral. + */ +static int mspi_stm32_qspi_dev_config(const struct device *controller, + const struct mspi_dev_id *dev_id, + const enum mspi_dev_cfg_mask param_mask, + const struct mspi_dev_cfg *dev_cfg) +{ + const struct mspi_stm32_conf *cfg = controller->config; + struct mspi_stm32_data *data = controller->data; + int ret = 0; + + /* Check if device ID has changed and lock accordingly */ + if (data->dev_id != dev_id) { + if (k_mutex_lock(&data->lock, K_MSEC(CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE))) { + LOG_ERR("Failed to acquire lock for device config"); + return -EBUSY; + } + + ret = mspi_verify_device(controller, dev_id); + if (ret) { + goto e_return; + } + } + + if (mspi_is_inp(controller)) { + ret = -EBUSY; + goto e_return; + } + + if (param_mask == MSPI_DEVICE_CONFIG_NONE && !cfg->mspicfg.sw_multi_periph) { + /* Just obtaining the controller lock */ + data->dev_id = (struct mspi_dev_id *)dev_id; + return ret; + } + + data->dev_id = (struct mspi_dev_id *)dev_id; + /* Validate and save device configuration */ + ret = mspi_stm32_qspi_dev_cfg_save(controller, param_mask, dev_cfg); + if (ret) { + LOG_ERR("failed to change device cfg"); + goto e_return; + } + +e_return: + k_mutex_unlock(&data->lock); + + return ret; +} + +/** + * API implementation of mspi_xip_config : XIP configuration + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @param xip_cfg The controller XIP configuration for MSPI. + * + * @retval 0 if successful. + * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. + */ +static int mspi_stm32_qspi_xip_config(const struct device *controller, + const struct mspi_dev_id *dev_id, + const struct mspi_xip_cfg *xip_cfg) +{ + struct mspi_stm32_data *dev_data = controller->data; + int ret = 0; + + if (dev_id != dev_data->dev_id) { + LOG_ERR("xip_config: dev_id don't match"); + return -ESTALE; + } + + if (!xip_cfg->enable) { + /* This is for aborting memory mapped mode */ + ret = mspi_stm32_qspi_memmap_off(controller); + } else { + ret = mspi_stm32_qspi_memmap_on(controller); + } + + if (ret == 0) { + dev_data->xip_cfg = *xip_cfg; + LOG_INF("QSPI XIP configured %d", xip_cfg->enable); + } + return ret; +} + +/** + * API implementation of mspi_get_channel_status. + * + * @param controller Pointer to the device structure for the driver instance. + * @param ch Not used. + * + * @retval 0 if successful. + * @retval -EBUSY MSPI bus is busy + */ +static int mspi_stm32_qspi_get_channel_status(const struct device *controller, uint8_t ch) +{ + struct mspi_stm32_data *data = controller->data; + QSPI_HandleTypeDef *hmspi = &data->hmspi.qspi; + int ret = 0; + + ARG_UNUSED(ch); + + if (mspi_is_inp(controller) || (hmspi->Instance->SR & QUADSPI_SR_BUSY) != 0) { + ret = -EBUSY; + } + + data->dev_id = NULL; + + k_mutex_unlock(&data->lock); + + return ret; +} + +static int mspi_stm32_qspi_pio_transceive(const struct device *controller, + const struct mspi_xfer *xfer) +{ + struct mspi_stm32_data *dev_data = controller->data; + struct mspi_context *ctx = &dev_data->ctx; + const struct mspi_xfer_packet *packet; + uint32_t packet_idx; + int ret = 0; + + if (xfer->num_packet == 0 || !xfer->packets || + xfer->timeout > CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE) { + LOG_ERR("Transfer: wrong parameters"); + return -EFAULT; + } + + /* DummyCycle to give to the mspi_stm32_read_access/mspi_stm32_write_access */ + ret = mspi_context_lock(ctx, dev_data->dev_id, xfer, true); + if (ret) { + goto pio_err; + } + + while (ctx->packets_left > 0) { + packet_idx = ctx->xfer.num_packet - ctx->packets_left; + packet = &ctx->xfer.packets[packet_idx]; + + /* + * Always starts with a command, + * then payload is given by the xfer->num_packet + */ + ret = mspi_stm32_qspi_access(controller, packet, + (ctx->xfer.async == true) ? MSPI_ACCESS_ASYNC + : MSPI_ACCESS_SYNC); + + ctx->packets_left--; + + if (ret) { + LOG_ERR("QSPI access failed for packet %d: %d", packet_idx, ret); + ret = -EIO; + goto pio_err; + } + } + +pio_err: + k_sem_give(&ctx->lock); + return ret; +} + +/** + * API implementation of mspi_transceive. + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @param xfer Pointer to the MSPI transfer started by dev_id. + * + * @retval 0 if successful. + * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. + * @retval -Error transfer failed. + */ +static int mspi_stm32_qspi_transceive(const struct device *controller, + const struct mspi_dev_id *dev_id, + const struct mspi_xfer *xfer) +{ + struct mspi_stm32_data *data = controller->data; + + /* Verify device ID matches */ + if (dev_id != data->dev_id) { + LOG_ERR("transceive : dev_id don't match"); + return -ESTALE; + } + + /* Need to map the xfer to the data context */ + data->ctx.xfer = *xfer; + + if (xfer->xfer_mode == MSPI_PIO) { + return mspi_stm32_qspi_pio_transceive(controller, xfer); + } else { + return -EIO; + } +} + +/* + * HAL MSP Init - Required by STM32 HAL + */ +void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hmspi) +{ + /* The clocks and pins are already configured in mspi_stm32_init */ +} + +void HAL_QSPI_MspDeInit(QSPI_HandleTypeDef *hmspi) +{ + /* No-op for Zephyr implementation */ +} + +/** + * @brief QSPI ISR function + */ +static void mspi_stm32_qspi_isr(const struct device *dev) +{ + struct mspi_stm32_data *dev_data = dev->data; + + HAL_QSPI_IRQHandler(&dev_data->hmspi.qspi); +} + +/* + * Driver Initialization + */ +static int mspi_stm32_qspi_init(const struct device *controller) +{ + const struct mspi_stm32_conf *cfg = controller->config; + + LOG_DBG("Initializing QSPI driver"); + + /* Initialize QSPI with basic configuration */ + const struct mspi_dt_spec spec = { + .bus = controller, + .config = cfg->mspicfg, + }; + + return mspi_stm32_qspi_config(&spec); +} + +static struct mspi_driver_api mspi_stm32_qspi_driver_api = { + .config = mspi_stm32_qspi_config, + .dev_config = mspi_stm32_qspi_dev_config, + .xip_config = mspi_stm32_qspi_xip_config, + .get_channel_status = mspi_stm32_qspi_get_channel_status, + .transceive = mspi_stm32_qspi_transceive, +}; + +/* MSPI QSPI control config */ +#define MSPI_QSPI_CONFIG(n) \ + { \ + .channel_num = 0, \ + .op_mode = DT_ENUM_IDX_OR(n, op_mode, MSPI_OP_MODE_CONTROLLER), \ + .duplex = DT_ENUM_IDX_OR(n, duplex, MSPI_HALF_DUPLEX), \ + .max_freq = DT_INST_PROP_OR(n, mspi_max_frequency, MSPI_MAX_FREQ), \ + .dqs_support = false, /* QSPI typically doesn't support DQS */ \ + .num_periph = DT_INST_CHILD_NUM(n), \ + .sw_multi_periph = DT_INST_PROP_OR(n, software_multiperipheral, false), \ + .num_ce_gpios = ARRAY_SIZE(ce_gpios##n), \ + .ce_group = ce_gpios##n, \ + } + +#define STM32_MSPI_QSPI_IRQ_HANDLER(index) \ + static void mspi_stm32_irq_config_func_##index(void) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(index), DT_INST_IRQ(index, priority), \ + mspi_stm32_qspi_isr, DEVICE_DT_INST_GET(index), 0); \ + irq_enable(DT_INST_IRQN(index)); \ + } + +#define MSPI_STM32_QSPI_INIT(index) \ + static const struct stm32_pclken pclken_##index[] = STM32_DT_INST_CLOCKS(index); \ + static const struct gpio_dt_spec ce_gpios##index[] = \ + MSPI_CE_GPIOS_DT_SPEC_INST_GET(index); \ + PINCTRL_DT_INST_DEFINE(index); \ + STM32_MSPI_QSPI_IRQ_HANDLER(index) \ + static const struct mspi_stm32_conf mspi_stm32_qspi_dev_conf_##index = { \ + .pclken = pclken_##index, \ + .pclk_len = DT_INST_NUM_CLOCKS(index), \ + .irq_config = mspi_stm32_irq_config_func_##index, \ + .mspicfg = MSPI_QSPI_CONFIG(index), \ + .pcfg = PINCTRL_DT_DEV_CONFIG_GET(DT_DRV_INST(index)), \ + }; \ + static struct mspi_stm32_data mspi_stm32_qspi_dev_data_##index = { \ + .hmspi.qspi = \ + { \ + .Instance = (QUADSPI_TypeDef *)DT_INST_REG_ADDR(index), \ + .Init = \ + { \ + .ClockPrescaler = 0, \ + .FifoThreshold = MSPI_STM32_FIFO_THRESHOLD, \ + .SampleShifting = QSPI_SAMPLE_SHIFTING_NONE, \ + .FlashSize = 0x19, \ + .ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE, \ + .ClockMode = QSPI_CLOCK_MODE_0, \ + .FlashID = QSPI_FLASH_ID_1, \ + .DualFlash = QSPI_DUALFLASH_DISABLE, \ + }, \ + }, \ + .memmap_base_addr = DT_REG_ADDR_BY_IDX(DT_DRV_INST(index), 1), \ + .dev_id = index, \ + .lock = Z_MUTEX_INITIALIZER(mspi_stm32_qspi_dev_data_##index.lock), \ + .sync = Z_SEM_INITIALIZER(mspi_stm32_qspi_dev_data_##index.sync, 0, 1), \ + .dev_cfg = {0}, \ + .xip_cfg = {0}, \ + .ctx.lock = Z_SEM_INITIALIZER(mspi_stm32_qspi_dev_data_##index.ctx.lock, 0, 1), \ + }; \ + DEVICE_DT_INST_DEFINE(index, &mspi_stm32_qspi_init, NULL, \ + &mspi_stm32_qspi_dev_data_##index, \ + &mspi_stm32_qspi_dev_conf_##index, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &mspi_stm32_qspi_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(MSPI_STM32_QSPI_INIT) diff --git a/drivers/mspi/mspi_stm32_xspi.c b/drivers/mspi/mspi_stm32_xspi.c new file mode 100644 index 0000000000000..6c6a3c9381479 --- /dev/null +++ b/drivers/mspi/mspi_stm32_xspi.c @@ -0,0 +1,1725 @@ +/* + * Copyright (c) 2025 EXALT Technologies. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ************************************************************************** + * MSPI flash controller driver for stm32 serie with multi-SPI periherals + * This driver is based on the stm32Cube HAL XSPI driver + * ************************************************************************** + */ + +#define DT_DRV_COMPAT st_stm32_xspi_controller + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mspi_stm32.h" + +LOG_MODULE_REGISTER(mspi_stm32_xspi, CONFIG_MSPI_LOG_LEVEL); + +static uint32_t mspi_stm32_xspi_hal_address_size(uint8_t address_length) +{ + if (address_length == 4U) { + return HAL_XSPI_ADDRESS_32_BITS; + } + + return HAL_XSPI_ADDRESS_24_BITS; +} + +/** + * @brief Gives XSPI_RegularCmdTypeDef with all parameters set except Instruction, Address, NbData + */ +static XSPI_RegularCmdTypeDef mspi_stm32_xspi_prepare_cmd(uint8_t cfg_mode, uint8_t cfg_rate) +{ + /* Command empty structure */ + XSPI_RegularCmdTypeDef cmd_tmp = {0}; + + cmd_tmp.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG; + cmd_tmp.InstructionWidth = ((cfg_mode == MSPI_IO_MODE_OCTAL) ? HAL_XSPI_INSTRUCTION_16_BITS + : HAL_XSPI_INSTRUCTION_8_BITS); + cmd_tmp.InstructionDTRMode = + ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_XSPI_INSTRUCTION_DTR_ENABLE + : HAL_XSPI_INSTRUCTION_DTR_DISABLE); + cmd_tmp.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE; + cmd_tmp.AddressDTRMode = ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_XSPI_ADDRESS_DTR_ENABLE + : HAL_XSPI_ADDRESS_DTR_DISABLE); + cmd_tmp.DataDTRMode = ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_XSPI_DATA_DTR_ENABLE + : HAL_XSPI_DATA_DTR_DISABLE); + /* AddressWidth must be set to 32bits for init and mem config phase */ + cmd_tmp.AddressWidth = HAL_XSPI_ADDRESS_32_BITS; + cmd_tmp.DataDTRMode = ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_XSPI_DATA_DTR_ENABLE + : HAL_XSPI_DATA_DTR_DISABLE); + cmd_tmp.DQSMode = + ((cfg_rate == MSPI_DATA_RATE_DUAL) ? HAL_XSPI_DQS_ENABLE : HAL_XSPI_DQS_DISABLE); + cmd_tmp.SIOOMode = HAL_XSPI_SIOO_INST_EVERY_CMD; + + switch (cfg_mode) { + case MSPI_IO_MODE_OCTAL: { + cmd_tmp.InstructionMode = HAL_XSPI_INSTRUCTION_8_LINES; + cmd_tmp.AddressMode = HAL_XSPI_ADDRESS_8_LINES; + cmd_tmp.DataMode = HAL_XSPI_DATA_8_LINES; + break; + } + case MSPI_IO_MODE_QUAD: { + cmd_tmp.InstructionMode = HAL_XSPI_INSTRUCTION_4_LINES; + cmd_tmp.AddressMode = HAL_XSPI_ADDRESS_4_LINES; + cmd_tmp.DataMode = HAL_XSPI_DATA_4_LINES; + break; + } + case MSPI_IO_MODE_DUAL: { + cmd_tmp.InstructionMode = HAL_XSPI_INSTRUCTION_2_LINES; + cmd_tmp.AddressMode = HAL_XSPI_ADDRESS_2_LINES; + cmd_tmp.DataMode = HAL_XSPI_DATA_2_LINES; + break; + } + default: { + cmd_tmp.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE; + cmd_tmp.AddressMode = HAL_XSPI_ADDRESS_1_LINE; + cmd_tmp.DataMode = HAL_XSPI_DATA_1_LINE; + break; + } + } + + return cmd_tmp; +} + +/** + * @brief Checks if the flash is currently operating in memory-mapped mode. + */ +static bool mspi_stm32_xspi_is_memorymap(const struct device *dev) +{ + struct mspi_stm32_data *dev_data = dev->data; + + return READ_BIT(dev_data->hmspi.xspi.Instance->CR, XSPI_CR_FMODE) == XSPI_CR_FMODE; +} + +/** + * @brief Sets the device back in indirect mode. + */ +static int mspi_stm32_xspi_memmap_off(const struct device *controller) +{ + struct mspi_stm32_data *dev_data = controller->data; + + if (HAL_XSPI_Abort(&dev_data->hmspi.xspi) != HAL_OK) { + LOG_ERR("MemMapped abort failed: %x", dev_data->hmspi.xspi.ErrorCode); + return -EIO; + } + return 0; +} + +/** + * @brief Sets the device in Memory-Mapped mode. + */ +static int mspi_stm32_xspi_memmap_on(const struct device *controller) +{ + HAL_StatusTypeDef ret; + struct mspi_stm32_data *dev_data = controller->data; + XSPI_MemoryMappedTypeDef s_MemMappedCfg; + + if (mspi_stm32_xspi_is_memorymap(controller)) { + return 0; + } + + /* Configure in MemoryMapped mode */ + if ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) && + + (mspi_stm32_xspi_hal_address_size(dev_data->dev_cfg.addr_length) == + HAL_XSPI_ADDRESS_24_BITS)) { + /* OPI mode and 3-bytes address size not supported by memory */ + LOG_ERR("MSPI_IO_MODE_SINGLE in 3Bytes addressing is not supported"); + return -EIO; + } + + XSPI_RegularCmdTypeDef s_command = mspi_stm32_xspi_prepare_cmd(dev_data->dev_cfg.io_mode, dev_data->dev_cfg.data_rate); + + /* Initialize the read command */ + s_command.OperationType = HAL_XSPI_OPTYPE_READ_CFG; + s_command.InstructionMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? HAL_XSPI_INSTRUCTION_1_LINE + : HAL_XSPI_INSTRUCTION_8_LINES) + : HAL_XSPI_INSTRUCTION_8_LINES; + s_command.InstructionDTRMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? HAL_XSPI_INSTRUCTION_DTR_DISABLE + : HAL_XSPI_INSTRUCTION_DTR_ENABLE; + s_command.InstructionWidth = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? HAL_XSPI_INSTRUCTION_8_BITS + : HAL_XSPI_INSTRUCTION_16_BITS) + : HAL_XSPI_INSTRUCTION_16_BITS; + s_command.Instruction = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? ((mspi_stm32_xspi_hal_address_size( + dev_data->ctx.xfer.addr_length) == + HAL_XSPI_ADDRESS_24_BITS) + ? MSPI_STM32_CMD_READ_FAST + : MSPI_STM32_CMD_READ_FAST_4B) + : dev_data->dev_cfg.read_cmd) + : MSPI_STM32_OCMD_DTR_RD; + s_command.AddressMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? HAL_XSPI_ADDRESS_1_LINE + : HAL_XSPI_ADDRESS_8_LINES) + : HAL_XSPI_ADDRESS_8_LINES; + s_command.AddressDTRMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? HAL_XSPI_ADDRESS_DTR_DISABLE + : HAL_XSPI_ADDRESS_DTR_ENABLE; + s_command.AddressWidth = + (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? mspi_stm32_xspi_hal_address_size(dev_data->ctx.xfer.addr_length) + : HAL_XSPI_ADDRESS_32_BITS; + s_command.DataMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? ((dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? HAL_XSPI_DATA_1_LINE + : HAL_XSPI_DATA_8_LINES) + : HAL_XSPI_DATA_8_LINES; + s_command.DataDTRMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? HAL_XSPI_DATA_DTR_DISABLE + : HAL_XSPI_DATA_DTR_ENABLE; + s_command.DummyCycles = dev_data->ctx.xfer.rx_dummy; + s_command.DQSMode = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) + ? HAL_XSPI_DQS_DISABLE + : HAL_XSPI_DQS_ENABLE; + + #ifdef XSPI_CCR_SIOO + s_command.SIOOMode = HAL_XSPI_SIOO_INST_EVERY_CMD; + #endif /* XSPI_CCR_SIOO */ + + ret = HAL_XSPI_Command(&dev_data->hmspi.xspi, &s_command, HAL_XSPI_TIMEOUT_DEFAULT_VALUE); + if (ret != HAL_OK) { + LOG_ERR("Failed to set memory mapped mode"); + return -EIO; + } + + /* Initializes the program command */ + s_command.OperationType = HAL_XSPI_OPTYPE_WRITE_CFG; + if (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_SINGLE) { + s_command.Instruction = + (dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE) + ? ((mspi_stm32_xspi_hal_address_size(dev_data->ctx.xfer.addr_length) == + HAL_XSPI_ADDRESS_24_BITS) + ? MSPI_STM32_CMD_PP + : MSPI_STM32_CMD_PP_4B) + : MSPI_STM32_OCMD_PAGE_PRG; + } else { + s_command.Instruction = MSPI_STM32_OCMD_PAGE_PRG; + } + + s_command.DQSMode = HAL_XSPI_DQS_DISABLE; + ret = HAL_XSPI_Command(&dev_data->hmspi.xspi, &s_command, HAL_XSPI_TIMEOUT_DEFAULT_VALUE); + if (ret != HAL_OK) { + LOG_ERR("Failed to set memory mapped mode"); + return -EIO; + } + + #ifdef XSPI_CR_NOPREF + s_MemMappedCfg.NoPrefetchData = HAL_XSPI_AUTOMATIC_PREFETCH_ENABLE; + #ifdef XSPI_CR_NOPREF_AXI + s_MemMappedCfg.NoPrefetchAXI = HAL_XSPI_AXI_PREFETCH_DISABLE; + #endif /* XSPI_CR_NOPREF_AXI */ + #endif /* XSPI_CR_NOPREF */ + + /* Enables the memory-mapping */ + s_MemMappedCfg.TimeOutActivation = HAL_XSPI_TIMEOUT_COUNTER_DISABLE; + ret = HAL_XSPI_MemoryMapped(&dev_data->hmspi.xspi, &s_MemMappedCfg); + if (ret != HAL_OK) { + LOG_ERR("Failed to enable memory mapped mode"); + return -EIO; + } + + return 0; +} + +static inline int mspi_stm32_xspi_context_lock(struct mspi_context *ctx, const struct mspi_dev_id *req, + const struct mspi_xfer *xfer, bool lockon) +{ + if (k_sem_take(&ctx->lock, K_MSEC(xfer->timeout))) { + return -EBUSY; + } + + ctx->xfer = *xfer; + ctx->packets_left = ctx->xfer.num_packet; + return 0; +} + +/** + * Check if the MSPI bus is busy. + * + * @param controller MSPI controller device. + * @return true The MSPI bus is busy. + * @return false The MSPI bus is idle. + */ +static inline bool mspi_stm32_xspi_is_inp(const struct device *controller) +{ + struct mspi_stm32_data *dev_data = controller->data; + + return (k_sem_count_get(&dev_data->ctx.lock) == 0); +} + +/** + * @brief Reads/Writes in memory mapped mode. + * + */ +static int read_write_in_memory_map_mode(const struct device *dev, struct mspi_xfer_packet *packet) +{ + int ret; + struct mspi_stm32_data *dev_data = dev->data; + + if (packet->data_buf == NULL) { + LOG_ERR("data buf is null :%x", packet->cmd); + return -EIO; + } + + if (!mspi_stm32_xspi_is_memorymap(dev)) { + ret = mspi_stm32_xspi_memmap_on(dev); + if (ret) { + LOG_ERR("Failed to set memory mapped"); + return ret; + } + } + + uintptr_t mmap_addr = dev_data->memmap_base_addr + packet->address; + + if (packet->dir == MSPI_RX) { + LOG_INF("Memory-mapped read from 0x%08lx, len %zu", mmap_addr, packet->num_bytes); + memcpy(packet->data_buf, (void *)mmap_addr, packet->num_bytes); + k_sleep(K_MSEC(1)); + return 0; + } + #if defined(CONFIG_MSPI_STM32_ALLOW_WRITE_IN_MEMMAP) + LOG_INF("Memory-mapped write from 0x%08lx, len %zu", mmap_addr, packet->num_bytes); + memcpy((void *)mmap_addr, packet->data_buf, packet->num_bytes); + k_sleep(K_MSEC(1)); + return 0; + #else + if (mspi_stm32_xspi_is_memorymap(dev)) { + ret = mspi_stm32_xspi_memmap_off(dev); + if (ret) { + LOG_ERR("Failed to abort memory-mapped access"); + return ret; + } + } + return -EPROTONOSUPPORT; + #endif +} + +/** + * @brief Sends a Command to the NOR and Receive/Transceive data if relevant in IT or DMA mode. + * + */ +static int mspi_stm32_xspi_access(const struct device *dev, struct mspi_xfer_packet *packet, + uint8_t access_mode) +{ + int ret; + struct mspi_stm32_data *dev_data = dev->data; + + if (dev_data->xip_cfg.enable) { + ARG_UNUSED(access_mode); + + if ((packet->cmd == MSPI_STM32_CMD_WREN) || (packet->cmd == MSPI_STM32_OCMD_WREN) || + (packet->cmd == MSPI_STM32_CMD_SE_4B) || (packet->cmd == MSPI_STM32_CMD_SE) || + (packet->cmd == MSPI_STM32_OCMD_SE) || + ((mspi_stm32_xspi_hal_address_size(dev_data->dev_cfg.addr_length) == HAL_XSPI_ADDRESS_24_BITS) && (dev_data->dev_cfg.io_mode == MSPI_IO_MODE_SINGLE))) { + LOG_DBG(" MSPI_IO_MODE_SINGLE in 3Bytes addressing is not supported in memory map mode, switching to indirect mode"); + if (mspi_stm32_xspi_is_memorymap(dev)) { + if (mspi_stm32_xspi_memmap_off(dev)) { + LOG_ERR("Failed to abort memory-mapped access"); + goto e_access; + } + } + goto indirect; + } + + if (read_write_in_memory_map_mode(dev, packet) == -EPROTONOSUPPORT) { + goto indirect; + } + return 0; + } + +indirect: + XSPI_RegularCmdTypeDef cmd = + mspi_stm32_xspi_prepare_cmd(dev_data->dev_cfg.io_mode, dev_data->dev_cfg.data_rate); + + cmd.DataLength = packet->num_bytes; + cmd.Instruction = packet->cmd; + if (packet->dir == MSPI_TX) { + cmd.DummyCycles = dev_data->ctx.xfer.tx_dummy; + } else { + cmd.DummyCycles = dev_data->ctx.xfer.rx_dummy; + } + cmd.Address = packet->address; /* AddressSize is 32bits in OPSI mode */ + cmd.AddressWidth = mspi_stm32_xspi_hal_address_size(dev_data->ctx.xfer.addr_length); + if (cmd.DataLength == 0) { + cmd.DataMode = HAL_XSPI_DATA_NONE; + } + + if ((cmd.Instruction == MSPI_STM32_CMD_WREN) || (cmd.Instruction == MSPI_STM32_OCMD_WREN)) { + /* Write Enable only accepts HAL_XSPI_ADDRESS_NONE */ + cmd.AddressMode = HAL_XSPI_ADDRESS_NONE; + } + + LOG_DBG("MSPI access Instruction 0x%x", cmd.Instruction); + + ret = HAL_XSPI_Command(&dev_data->hmspi.xspi, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE); + if (ret != HAL_OK) { + LOG_ERR("%d: Failed to send XSPI instruction", ret); + return -EIO; + } + + if (packet->num_bytes == 0) { + return 0; + } + + if (packet->dir == MSPI_RX) { + /* Receive the data */ + switch (access_mode) { + case MSPI_ACCESS_SYNC: + ret = HAL_XSPI_Receive(&dev_data->hmspi.xspi, packet->data_buf, + HAL_XSPI_TIMEOUT_DEFAULT_VALUE); + goto e_access; + case MSPI_ACCESS_ASYNC: + ret = HAL_XSPI_Receive_IT(&dev_data->hmspi.xspi, packet->data_buf); + break; + case MSPI_ACCESS_DMA: + uint8_t *dma_buf = k_aligned_alloc(CONFIG_MSPI_BUFFER_ALIGNMENT, packet->num_bytes); + + if (!dma_buf) { + LOG_ERR("DMA buffer allocation failed"); + return -ENOMEM; + } + + ret = HAL_XSPI_Receive_DMA(&dev_data->hmspi.xspi, dma_buf); + if (ret == HAL_OK) { + if (k_sem_take(&dev_data->sync, K_FOREVER)) { + LOG_ERR("%d: Failed to take sem", dev_data->hmspi.xspi.ErrorCode); + k_free(dma_buf); + return -EIO; + } + memcpy(packet->data_buf, dma_buf, packet->num_bytes); + } + + k_free(dma_buf); + goto e_access; + default: + /* Not correct */ + ret = HAL_BUSY; + break; + } + } else { + /* Transmit the data */ + switch (access_mode) { + case MSPI_ACCESS_SYNC: + ret = HAL_XSPI_Transmit(&dev_data->hmspi.xspi, packet->data_buf, + HAL_XSPI_TIMEOUT_DEFAULT_VALUE); + goto e_access; + case MSPI_ACCESS_ASYNC: + ret = HAL_XSPI_Transmit_IT(&dev_data->hmspi.xspi, packet->data_buf); + break; + case MSPI_ACCESS_DMA: + ret = HAL_XSPI_Transmit_DMA(&dev_data->hmspi.xspi, packet->data_buf); + break; + default: + /* Not correct */ + ret = HAL_BUSY; + break; + } + } + + if (ret != HAL_OK) { + LOG_ERR("%d: Failed to access data", ret); + return -EIO; + } + + /* Lock again expecting the IRQ for end of Tx or Rx */ + if (k_sem_take(&dev_data->sync, K_FOREVER)) { + LOG_ERR("%d: Failed to access data", ret); + return -EIO; + } + +e_access: + LOG_DBG("Access %zu data at 0x%lx", packet->num_bytes, (long)(packet->address)); + + return 0; +} + +/* Start Automatic-Polling mode to wait until the memory is setting mask/value bit */ +static int mspi_stm32_xspi_wait_auto_polling(const struct device *dev, uint8_t match_value, + uint8_t match_mask, uint32_t timeout_ms) +{ + struct mspi_stm32_data *dev_data = dev->data; + XSPI_AutoPollingTypeDef s_config; + + /* Set the match to check if the bit is Reset */ + s_config.MatchValue = match_value; + s_config.MatchMask = match_mask; + + s_config.MatchMode = HAL_XSPI_MATCH_MODE_AND; + s_config.IntervalTime = MSPI_STM32_AUTO_POLLING_INTERVAL; + s_config.AutomaticStop = HAL_XSPI_AUTOMATIC_STOP_ENABLE; + + if (HAL_XSPI_AutoPolling_IT(&dev_data->hmspi.xspi, &s_config) != HAL_OK) { + LOG_ERR("XSPI AutoPoll failed"); + return -EIO; + } + + if (k_sem_take(&dev_data->sync, K_MSEC(timeout_ms)) != 0) { + LOG_ERR("XSPI AutoPoll wait failed"); + HAL_XSPI_Abort(&dev_data->hmspi.xspi); + k_sem_reset(&dev_data->sync); + return -EIO; + } + + return 0; +} + +/* + * Reads the status reg of the device + * Send the RDSR command (according to io_mode/data_rate) + * Then sets the Autopolling mode with match mask/value bit. + */ +static int mspi_stm32_xspi_status_reg(const struct device *controller, const struct mspi_xfer *xfer) +{ + int ret = 0; + struct mspi_stm32_data *dev_data = controller->data; + struct mspi_context *ctx = &dev_data->ctx; + + if (xfer->num_packet == 0 || !xfer->packets) { + LOG_ERR("Status Reg.: wrong parameters"); + return -EFAULT; + } + + ret = mspi_stm32_xspi_context_lock(ctx, dev_data->dev_id, xfer, true); + if (ret) { + goto status_err; + } + + XSPI_RegularCmdTypeDef cmd = + mspi_stm32_xspi_prepare_cmd(dev_data->dev_cfg.io_mode, dev_data->dev_cfg.data_rate); + if (dev_data->dev_cfg.io_mode == MSPI_IO_MODE_OCTAL) { + cmd.Instruction = MSPI_STM32_OCMD_RDSR; + cmd.DummyCycles = (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_DUAL) ? + MSPI_STM32_DUMMY_REG_OCTAL_DTR : MSPI_STM32_DUMMY_REG_OCTAL; + } else { + cmd.Instruction = MSPI_STM32_CMD_RDSR; + cmd.AddressMode = HAL_XSPI_ADDRESS_NONE; + cmd.DataMode = HAL_XSPI_DATA_1_LINE; + cmd.DummyCycles = 0; + cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE; + } + cmd.Address = 0U; + LOG_DBG("MSPI poll status reg"); + + XSPI_AutoPollingTypeDef s_config; + + /* Set the match to check if the bit is Reset */ + s_config.MatchValue = MSPI_STM32_WEL_MATCH; + s_config.MatchMask = MSPI_STM32_WEL_MASK; + + s_config.MatchMode = HAL_XSPI_MATCH_MODE_AND; + s_config.IntervalTime = MSPI_STM32_AUTO_POLLING_INTERVAL; + s_config.AutomaticStop = HAL_XSPI_AUTOMATIC_STOP_ENABLE; + + if (mspi_stm32_xspi_is_memorymap(controller)) { + ret = mspi_stm32_xspi_memmap_off(controller); + if (ret != 0) { + LOG_ERR("Failed to abort memory-mapped access before sending status command"); + return -EIO; + } + } + + if (HAL_XSPI_Command(&dev_data->hmspi.xspi, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE)) { + LOG_ERR("%d: Failed to send XSPI instruction", ret); + ret = -EIO; + goto status_err; + } + + ret = mspi_stm32_xspi_wait_auto_polling(controller, + MSPI_STM32_MEM_RDY_MATCH, + MSPI_STM32_MEM_RDY_MASK, + HAL_XSPI_TIMEOUT_DEFAULT_VALUE); + +status_err: + k_sem_give(&ctx->lock); + return ret; +} + +/* + * Polls the WIP(Write In Progress) bit to become to 0 + * in cfg_mode SPI/OPI MSPI_IO_MODE_SINGLE or MSPI_IO_MODE_OCTAL + * and cfg_rate transfer STR/DTR MSPI_DATA_RATE_SINGLE or MSPI_DATA_RATE_DUAL + */ +static int mspi_stm32_xspi_mem_ready(const struct device *dev, uint8_t cfg_mode, uint8_t cfg_rate) +{ + struct mspi_stm32_data *dev_data = dev->data; + + XSPI_RegularCmdTypeDef s_command = mspi_stm32_xspi_prepare_cmd(cfg_mode, cfg_rate); + + /* Configure automatic polling mode command to wait for memory ready */ + if (cfg_mode == MSPI_IO_MODE_OCTAL) { + s_command.Instruction = MSPI_STM32_OCMD_RDSR; + s_command.DummyCycles = (cfg_rate == MSPI_DATA_RATE_DUAL) + ? MSPI_STM32_DUMMY_REG_OCTAL_DTR + : MSPI_STM32_DUMMY_REG_OCTAL; + } else { + s_command.Instruction = MSPI_STM32_CMD_RDSR; + /* force 1-line InstructionMode for any non-OSPI transfer */ + s_command.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE; + s_command.AddressMode = HAL_XSPI_ADDRESS_NONE; + /* force 1-line DataMode for any non-OSPI transfer */ + s_command.DataMode = HAL_XSPI_DATA_1_LINE; + s_command.DummyCycles = 0; + } + s_command.DataLength = ((cfg_rate == MSPI_DATA_RATE_DUAL) ? 2U : 1U); + s_command.Address = 0U; + + if (HAL_XSPI_Command(&dev_data->hmspi.xspi, &s_command, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("MSPI AutoPoll command failed"); + return -EIO; + } + + LOG_DBG("MSPI read status reg MemRdy"); + return mspi_stm32_xspi_wait_auto_polling(dev, + MSPI_STM32_MEM_RDY_MATCH, + MSPI_STM32_MEM_RDY_MASK, + HAL_XSPI_TIMEOUT_DEFAULT_VALUE); +} + +/* Enables writing to the memory sending a Write Enable and wait till it is effective */ +static int mspi_stm32_xspi_write_enable(const struct device *dev, uint8_t cfg_mode, uint8_t cfg_rate) +{ + struct mspi_stm32_data *dev_data = dev->data; + XSPI_RegularCmdTypeDef s_command = mspi_stm32_xspi_prepare_cmd(cfg_mode, cfg_rate); + + if (cfg_mode == MSPI_IO_MODE_OCTAL) { + s_command.Instruction = MSPI_STM32_OCMD_WREN; + } else { + s_command.Instruction = MSPI_STM32_CMD_WREN; + /* force 1-line InstructionMode for any non-OSPI transfer */ + s_command.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE; + } + s_command.AddressMode = HAL_XSPI_ADDRESS_NONE; + s_command.DataMode = HAL_XSPI_DATA_NONE; + s_command.DummyCycles = 0U; + + if (HAL_XSPI_Command(&dev_data->hmspi.xspi, &s_command, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("MSPI flash write enable cmd failed"); + return -EIO; + } + LOG_DBG("MSPI write enable"); + + /* New command to Configure automatic polling mode to wait for write enabling */ + if (cfg_mode == MSPI_IO_MODE_OCTAL) { + s_command.Instruction = MSPI_STM32_OCMD_RDSR; + s_command.AddressMode = HAL_XSPI_ADDRESS_8_LINES; + s_command.DataMode = HAL_XSPI_DATA_8_LINES; + s_command.DummyCycles = (cfg_rate == MSPI_DATA_RATE_DUAL) + ? MSPI_STM32_DUMMY_REG_OCTAL_DTR + : MSPI_STM32_DUMMY_REG_OCTAL; + } else { + s_command.Instruction = MSPI_STM32_CMD_RDSR; + /* force 1-line DataMode for any non-OSPI transfer */ + s_command.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE; + s_command.AddressMode = HAL_XSPI_ADDRESS_1_LINE; + s_command.DataMode = HAL_XSPI_DATA_1_LINE; + s_command.DummyCycles = 0; + } + s_command.DataLength = (cfg_rate == MSPI_DATA_RATE_DUAL) ? 2U : 1U; + s_command.Address = 0U; + + if (HAL_XSPI_Command(&dev_data->hmspi.xspi, &s_command, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("MSPI config auto polling cmd failed"); + return -EIO; + } + LOG_DBG("MSPI read status reg"); + + return mspi_stm32_xspi_wait_auto_polling(dev, + MSPI_STM32_WREN_MATCH, + MSPI_STM32_WREN_MASK, + HAL_XSPI_TIMEOUT_DEFAULT_VALUE); +} + +/* Writes Flash configuration register 2 with new dummy cycles */ +static int mspi_stm32_xspi_write_cfg2reg_dummy(const struct device *dev, uint8_t cfg_mode, + uint8_t cfg_rate) +{ + struct mspi_stm32_data *dev_data = dev->data; + uint8_t transmit_data = MSPI_STM32_CR2_DUMMY_CYCLES_66MHZ; + XSPI_RegularCmdTypeDef s_command = mspi_stm32_xspi_prepare_cmd(cfg_mode, cfg_rate); + + /* Initializes the writing of configuration register 2 */ + s_command.Instruction = (cfg_mode == MSPI_IO_MODE_SINGLE) ? MSPI_STM32_CMD_WR_CFGREG2 + : MSPI_STM32_OCMD_WR_CFGREG2; + s_command.Address = MSPI_STM32_REG2_ADDR3; + s_command.DummyCycles = 0U; + + if (cfg_mode == MSPI_IO_MODE_SINGLE) { + s_command.DataLength = 1U; + } else if (cfg_rate == MSPI_DATA_RATE_DUAL) { + s_command.DataLength = 2U; + } else { + s_command.DataLength = 1U; + } + + if (mspi_stm32_xspi_is_memorymap(dev)) { + int ret = mspi_stm32_xspi_memmap_off(dev); + + if (ret != 0) { + LOG_ERR("Failed to abort memory-mapped access before writing cfg reg"); + return -1; + } + } + + if (HAL_XSPI_Command(&dev_data->hmspi.xspi, &s_command, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("MSPI transmit cmd"); + return -EIO; + } + + if (HAL_XSPI_Transmit(&dev_data->hmspi.xspi, &transmit_data, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("MSPI transmit"); + return -EIO; + } + + return 0; +} + +/* Write Flash configuration register 2 with new single or octal SPI protocol */ +static int mspi_stm32_xspi_write_cfg2reg_io(const struct device *dev, uint8_t cfg_mode, uint8_t cfg_rate, + uint8_t op_enable) +{ + int ret = 0; + struct mspi_stm32_data *dev_data = dev->data; + XSPI_RegularCmdTypeDef s_command = mspi_stm32_xspi_prepare_cmd(cfg_mode, cfg_rate); + + /* Initializes the writing of configuration register 2 */ + s_command.Instruction = (cfg_mode == MSPI_IO_MODE_SINGLE) + ? MSPI_STM32_CMD_WR_CFGREG2 + : MSPI_STM32_OCMD_WR_CFGREG2; + s_command.Address = MSPI_STM32_REG2_ADDR1; + s_command.DummyCycles = 0U; + + if (cfg_mode == MSPI_IO_MODE_SINGLE) { + s_command.DataLength = 1U; + } else if (cfg_rate == MSPI_DATA_RATE_DUAL) { + s_command.DataLength = 2U; + } else { + s_command.DataLength = 1U; + } + + if (mspi_stm32_xspi_is_memorymap(dev)) { + ret = mspi_stm32_xspi_memmap_off(dev); + if (ret != 0) { + LOG_ERR("Failed to abort memory-mapped access before write"); + return ret; + } + } + + if (HAL_XSPI_Command(&dev_data->hmspi.xspi, &s_command, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { + LOG_ERR("Write Flash configuration reg2 failed"); + return -EIO; + } + + if (HAL_XSPI_Transmit(&dev_data->hmspi.xspi, &op_enable, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { + LOG_ERR("Write Flash configuration reg2 failed"); + return -EIO; + } + + return ret; +} + +/* Reads Flash configuration register 2 with new single or octal SPI protocol */ +static int mspi_stm32_xspi_read_cfg2reg(const struct device *dev, uint8_t cfg_mode, uint8_t cfg_rate, + uint8_t *value) +{ + int ret = 0; + struct mspi_stm32_data *dev_data = dev->data; + XSPI_RegularCmdTypeDef s_command = mspi_stm32_xspi_prepare_cmd(cfg_mode, cfg_rate); + + /* Initializes the writing of configuration register 2 */ + s_command.Instruction = (cfg_mode == MSPI_IO_MODE_SINGLE) ? MSPI_STM32_CMD_RD_CFGREG2 + : MSPI_STM32_OCMD_RD_CFGREG2; + s_command.Address = MSPI_STM32_REG2_ADDR1; + + if (cfg_mode == MSPI_IO_MODE_SINGLE) { + s_command.DummyCycles = 0U; + } else if (cfg_rate == MSPI_DATA_RATE_DUAL) { + s_command.DummyCycles = MSPI_STM32_DUMMY_REG_OCTAL_DTR; + } else { + s_command.DummyCycles = MSPI_STM32_DUMMY_REG_OCTAL; + } + + s_command.DataLength = (cfg_rate == MSPI_DATA_RATE_DUAL) ? 2U : 1U; + if (mspi_stm32_xspi_is_memorymap(dev)) { + ret = mspi_stm32_xspi_memmap_off(dev); + if (ret != 0) { + LOG_ERR("Failed to abort memory-mapped access before write"); + return ret; + } + } + if (HAL_XSPI_Command(&dev_data->hmspi.xspi, &s_command, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != + HAL_OK) { + LOG_ERR("Write Flash configuration reg2 failed"); + return -EIO; + } + + if (HAL_XSPI_Receive(&dev_data->hmspi.xspi, value, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { + LOG_ERR("Write Flash configuration reg2 failed"); + return -EIO; + } + + return ret; +} + +/* Sends the command to configure the device according to the DTS */ +static int mspi_stm32_xspi_config_mem(const struct device *dev, uint8_t cfg_mode, uint8_t cfg_rate) +{ + struct mspi_stm32_data *dev_data = dev->data; + uint8_t reg[2]; + + /* MSPI_IO_MODE_SINGLE/MSPI_DATA_RATE_SINGLE is already done */ + if ((cfg_mode == MSPI_IO_MODE_SINGLE) && (cfg_rate == MSPI_DATA_RATE_SINGLE)) { + return 0; + } + + /* Write Configuration register 2 (with new dummy cycles) */ + if (mspi_stm32_xspi_write_cfg2reg_dummy(dev, MSPI_IO_MODE_SINGLE, MSPI_DATA_RATE_SINGLE) != 0) { + LOG_ERR("XSPI write CFGR2 failed"); + return -EIO; + } + if (mspi_stm32_xspi_mem_ready(dev, MSPI_IO_MODE_SINGLE, MSPI_DATA_RATE_SINGLE) != 0) { + LOG_ERR("XSPI autopolling failed"); + return -EIO; + } + if (mspi_stm32_xspi_write_enable(dev, MSPI_IO_MODE_SINGLE, MSPI_DATA_RATE_SINGLE) != 0) { + LOG_ERR("XSPI write Enable 2 failed"); + return -EIO; + } + + /* Write Configuration register 2 (with Octal I/O SPI protocol : choose STR or DTR) */ + uint8_t mode_enable = ((cfg_rate == MSPI_DATA_RATE_DUAL) + ? MSPI_STM32_CR2_DTR_OPI_EN + : MSPI_STM32_CR2_STR_OPI_EN); + if (mspi_stm32_xspi_write_cfg2reg_io(dev, MSPI_IO_MODE_SINGLE, MSPI_DATA_RATE_SINGLE, + mode_enable) != 0) { + LOG_ERR("XSPI write CFGR2 failed"); + return -EIO; + } + + /* Wait till the configuration is effective and check that memory is ready */ + k_busy_wait(MSPI_STM32_WRITE_REG_MAX_TIME * USEC_PER_MSEC); + + /* Reconfigure the memory type of the peripheral */ + dev_data->hmspi.xspi.Init.MemoryType = HAL_XSPI_MEMTYPE_MACRONIX; + dev_data->hmspi.xspi.Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_ENABLE; + if (HAL_XSPI_Init(&dev_data->hmspi.xspi) != HAL_OK) { + LOG_ERR("XSPI mem type MACRONIX failed"); + return -EIO; + } + + if (mspi_stm32_xspi_mem_ready(dev, MSPI_IO_MODE_OCTAL, cfg_rate) != 0) { + LOG_ERR("XSPI flash busy failed"); + return -EIO; + } + + if (mspi_stm32_xspi_read_cfg2reg(dev, MSPI_IO_MODE_OCTAL, cfg_rate, reg) != 0) { + LOG_ERR("MSPI flash config read failed"); + return -EIO; + } + + LOG_DBG("XSPI flash config is OCTO / %s", ((cfg_rate == MSPI_DATA_RATE_SINGLE) ? + (char *)"STR" : + (char *)"DTR")); + + return 0; +} + +static void mspi_stm32_xspi_isr(const struct device *dev) +{ + struct mspi_stm32_data *dev_data = dev->data; + + k_sem_give(&dev_data->sync); + + HAL_XSPI_IRQHandler(&dev_data->hmspi.xspi); +} + +#if !defined(CONFIG_SOC_SERIES_STM32H7X) +/* weak function required for HAL compilation */ +__weak HAL_StatusTypeDef HAL_DMA_Abort_IT(DMA_HandleTypeDef *hdma) +{ + return HAL_OK; +} + +/* weak function required for HAL compilation */ +__weak HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma) +{ + return HAL_OK; +} +#endif /* !CONFIG_SOC_SERIES_STM32H7X */ + +static void mspi_stm32_xspi_dma_callback(const struct device *dev, void *arg, + uint32_t channel, int status) +{ + DMA_HandleTypeDef *hdma = arg; + + ARG_UNUSED(dev); + + if (status < 0) { + LOG_ERR("DMA callback error with channel %d", channel); + } + + HAL_DMA_IRQHandler(hdma); +} + +/** + * Verify if the device with dev_id is on this MSPI bus. + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @return 0 The device is on this MSPI bus. + * @return -ENODEV The device is not on this MSPI bus. + */ +static inline int mspi_stm32_xspi_verify_device(const struct device *controller, + const struct mspi_dev_id *dev_id) +{ + const struct mspi_stm32_conf *cfg = controller->config; + int device_index = cfg->mspicfg.num_periph; + + if (cfg->mspicfg.num_ce_gpios != 0) { + for (int i = 0; i < cfg->mspicfg.num_periph; i++) { + if (dev_id->ce.port == cfg->mspicfg.ce_group[i].port && + dev_id->ce.pin == cfg->mspicfg.ce_group[i].pin && + dev_id->ce.dt_flags == cfg->mspicfg.ce_group[i].dt_flags) { + device_index = i; + } + } + + if (device_index >= cfg->mspicfg.num_periph || device_index != dev_id->dev_idx) { + LOG_ERR("%u, invalid device ID", __LINE__); + return -ENODEV; + } + } else { + if (dev_id->dev_idx >= cfg->mspicfg.num_periph) { + LOG_ERR("%u, invalid device ID", __LINE__); + return -ENODEV; + } + } + + return 0; +} + +static int mspi_stm32_xspi_validate_freq(uint32_t freq) +{ + if (freq > MSPI_MAX_FREQ) { + LOG_ERR("%u, freq is too large", __LINE__); + return -ENOTSUP; + } + return 0; +} + +static int mspi_stm32_xspi_validate_io_mode(uint32_t io_mode) +{ + if (io_mode >= MSPI_IO_MODE_MAX) { + LOG_ERR("%u, Invalid io_mode", __LINE__); + return -EINVAL; + } + return 0; +} + + +static int mspi_stm32_xspi_validate_data_rate(uint32_t data_rate) +{ + if (data_rate >= MSPI_DATA_RATE_MAX) { + LOG_ERR("%u, Invalid data_rate", __LINE__); + return -EINVAL; + } + return 0; +} + +static int mspi_stm32_xspi_validate_cpp(uint32_t cpp) +{ + if (cpp > MSPI_CPP_MODE_3) { + LOG_ERR("%u, Invalid cpp", __LINE__); + return -EINVAL; + } + return 0; +} + +static int mspi_stm32_xspi_validate_endian(uint32_t endian) +{ + if (endian > MSPI_XFER_BIG_ENDIAN) { + LOG_ERR("%u, Invalid endian", __LINE__); + return -EINVAL; + } + return 0; +} + +static int mspi_stm32_xspi_validate_ce_polarity(uint32_t ce_polarity) +{ + if (ce_polarity > MSPI_CE_ACTIVE_HIGH) { + LOG_ERR("%u, Invalid ce_polarity", __LINE__); + return -EINVAL; + } + return 0; +} + +static int mspi_stm32_xspi_validate_dqs(bool dqs_enable, bool dqs_support) +{ + if (dqs_enable && !dqs_support) { + LOG_ERR("%u, DQS mode not supported", __LINE__); + return -ENOTSUP; + } + return 0; +} + +/** + * Check and save dev_cfg to controller data->dev_cfg. + * + * @param controller Pointer to the device structure for the driver instance. + * @param param_mask Macro definition of what to be configured in cfg. + * @param dev_cfg The device runtime configuration for the MSPI controller. + * @return 0 MSPI device configuration successful. + * @return -Error MSPI device configuration fail. + */ +static int mspi_stm32_xspi_dev_cfg_save(const struct device *controller, + const enum mspi_dev_cfg_mask param_mask, + const struct mspi_dev_cfg *dev_cfg) +{ + const struct mspi_stm32_conf *cfg = controller->config; + struct mspi_stm32_data *data = controller->data; + int ret; + + if (param_mask & MSPI_DEVICE_CONFIG_CE_NUM) { + data->dev_cfg.ce_num = dev_cfg->ce_num; + } + + if (param_mask & MSPI_DEVICE_CONFIG_FREQUENCY) { + ret = mspi_stm32_xspi_validate_freq(dev_cfg->freq); + if (ret) { + goto end; + } + data->dev_cfg.freq = dev_cfg->freq; + } + + if (param_mask & MSPI_DEVICE_CONFIG_IO_MODE) { + ret = mspi_stm32_xspi_validate_io_mode(dev_cfg->io_mode); + if (ret) { + goto end; + } + data->dev_cfg.io_mode = dev_cfg->io_mode; + } + + if (param_mask & MSPI_DEVICE_CONFIG_DATA_RATE) { + ret = mspi_stm32_xspi_validate_data_rate(dev_cfg->data_rate); + if (ret) { + goto end; + } + data->dev_cfg.data_rate = dev_cfg->data_rate; + } + + if (param_mask & MSPI_DEVICE_CONFIG_CPP) { + ret = mspi_stm32_xspi_validate_cpp(dev_cfg->cpp); + if (ret) { + goto end; + } + data->dev_cfg.cpp = dev_cfg->cpp; + } + + if (param_mask & MSPI_DEVICE_CONFIG_ENDIAN) { + ret = mspi_stm32_xspi_validate_endian(dev_cfg->endian); + if (ret) { + goto end; + } + data->dev_cfg.endian = dev_cfg->endian; + } + + if (param_mask & MSPI_DEVICE_CONFIG_CE_POL) { + ret = mspi_stm32_xspi_validate_ce_polarity(dev_cfg->ce_polarity); + if (ret) { + goto end; + } + data->dev_cfg.ce_polarity = dev_cfg->ce_polarity; + } + + if (param_mask & MSPI_DEVICE_CONFIG_DQS) { + ret = mspi_stm32_xspi_validate_dqs(dev_cfg->dqs_enable, cfg->mspicfg.dqs_support); + if (ret) { + goto end; + } + data->dev_cfg.dqs_enable = dev_cfg->dqs_enable; + } + + /* Simple assignments (no validation needed) */ + if (param_mask & MSPI_DEVICE_CONFIG_RX_DUMMY) { + data->dev_cfg.rx_dummy = dev_cfg->rx_dummy; + } + + if (param_mask & MSPI_DEVICE_CONFIG_TX_DUMMY) { + data->dev_cfg.tx_dummy = dev_cfg->tx_dummy; + } + + if (param_mask & MSPI_DEVICE_CONFIG_READ_CMD) { + data->dev_cfg.read_cmd = dev_cfg->read_cmd; + } + + if (param_mask & MSPI_DEVICE_CONFIG_WRITE_CMD) { + data->dev_cfg.write_cmd = dev_cfg->write_cmd; + } + + if (param_mask & MSPI_DEVICE_CONFIG_CMD_LEN) { + data->dev_cfg.cmd_length = dev_cfg->cmd_length; + } + + if (param_mask & MSPI_DEVICE_CONFIG_ADDR_LEN) { + data->dev_cfg.addr_length = dev_cfg->addr_length; + } + + if (param_mask & MSPI_DEVICE_CONFIG_MEM_BOUND) { + data->dev_cfg.mem_boundary = dev_cfg->mem_boundary; + } + + if (param_mask & MSPI_DEVICE_CONFIG_BREAK_TIME) { + data->dev_cfg.time_to_break = dev_cfg->time_to_break; + } + +end: + return ret; +} + +/** + * API implementation of mspi_dev_config : controller device specific configuration + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @param param_mask Macro definition of what to be configured in cfg. + * @param dev_cfg The device runtime configuration for the MSPI controller. + * + * @retval 0 if successful. + * @retval -EINVAL invalid capabilities, failed to configure device. + * @retval -ENOTSUP capability not supported by MSPI peripheral. + */ +static int mspi_stm32_xspi_dev_config(const struct device *controller, const struct mspi_dev_id *dev_id, + const enum mspi_dev_cfg_mask param_mask, + const struct mspi_dev_cfg *dev_cfg) +{ + int ret = 0; + const struct mspi_stm32_conf *cfg = controller->config; + struct mspi_stm32_data *data = controller->data; + + if (data->dev_id != dev_id) { + if (k_mutex_lock(&data->lock, K_MSEC(CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE))) { + LOG_ERR("MSPI config failed to access controller"); + return -EBUSY; + } + + ret = mspi_stm32_xspi_verify_device(controller, dev_id); + if (ret) { + goto e_return; + } + } + + if (mspi_stm32_xspi_is_inp(controller)) { + ret = -EBUSY; + goto e_return; + } + + if (param_mask == MSPI_DEVICE_CONFIG_NONE && !cfg->mspicfg.sw_multi_periph) { + data->dev_id = (const struct mspi_dev_id *)dev_id; + goto e_return; + } + + if (param_mask & (MSPI_DEVICE_CONFIG_IO_MODE | MSPI_DEVICE_CONFIG_DATA_RATE)) { + /* Going to set the XSPI mode and transfer rate */ + ret = mspi_stm32_xspi_config_mem(controller, dev_cfg->io_mode, dev_cfg->data_rate); + if (ret) { + goto e_return; + } + LOG_DBG("MSPI confg'd in %d / %d", dev_cfg->io_mode, dev_cfg->data_rate); + } + + data->dev_id = (const struct mspi_dev_id *)dev_id; + /* Go on with other parameters if supported */ + if (mspi_stm32_xspi_dev_cfg_save(controller, param_mask, dev_cfg)) { + LOG_ERR("failed to change device cfg"); + ret = -EIO; + } + +e_return: + k_mutex_unlock(&data->lock); + return ret; +} + +/** + * API implementation of mspi_xip_config : XIP configuration + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @param xip_cfg The controller XIP configuration for MSPI. + * + * @retval 0 if successful. + * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. + */ +static int mspi_stm32_xspi_xip_config(const struct device *controller, const struct mspi_dev_id *dev_id, + const struct mspi_xip_cfg *xip_cfg) +{ + int ret = 0; + struct mspi_stm32_data *dev_data = controller->data; + + if (dev_id != dev_data->dev_id) { + LOG_ERR("dev_id don't match"); + return -ESTALE; + } + + if (!xip_cfg->enable) { + /* This is for aborting */ + ret = mspi_stm32_xspi_memmap_off(controller); + } else { + ret = mspi_stm32_xspi_memmap_on(controller); + } + + if (ret == 0) { + dev_data->xip_cfg = *xip_cfg; + LOG_INF("XIP configured %d", xip_cfg->enable); + } + return ret; +} + +/** + * API implementation of mspi_get_channel_status. + * + * @param controller Pointer to the device structure for the driver instance. + * @param ch Not used. + * + * @retval 0 if successful. + * @retval -EBUSY MSPI bus is busy + */ +static int mspi_stm32_xspi_get_channel_status(const struct device *controller, uint8_t ch) +{ + struct mspi_stm32_data *dev_data = controller->data; + int ret = 0; + + ARG_UNUSED(ch); + + if (mspi_stm32_xspi_is_inp(controller) || (HAL_XSPI_GET_FLAG(&dev_data->hmspi.xspi, HAL_XSPI_FLAG_BUSY) == SET)) { + ret = -EBUSY; + } + + return ret; +} + +static int mspi_stm32_xspi_pio_dma_transceive(const struct device *controller, const struct mspi_xfer *xfer) +{ + int ret = 0; + uint32_t packet_idx; + struct mspi_stm32_data *dev_data = controller->data; + const struct mspi_stm32_conf *dev_cfg = controller->config; + struct mspi_context *ctx = &dev_data->ctx; + struct mspi_xfer_packet *packet; + + if (xfer->num_packet == 0 || !xfer->packets || + xfer->timeout > CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE) { + LOG_ERR("Transfer: wrong parameters"); + return -EFAULT; + } + + ret = mspi_stm32_xspi_context_lock(ctx, dev_data->dev_id, xfer, true); + if (ret) { + LOG_ERR("Failed to lock MSPI context"); + goto end; + } + + while (ctx->packets_left > 0) { + packet_idx = ctx->xfer.num_packet - ctx->packets_left; + packet = (struct mspi_xfer_packet *)&ctx->xfer.packets[packet_idx]; + if (!dev_cfg->use_dma) { + ret = mspi_stm32_xspi_access(controller, packet, (ctx->xfer.async == true) ? MSPI_ACCESS_ASYNC : MSPI_ACCESS_SYNC); + } else { + ret = mspi_stm32_xspi_access(controller, packet, MSPI_ACCESS_DMA); + } + + ctx->packets_left--; + if (ret) { + ret = -EIO; + goto end; + } + } + +end: + k_sem_give(&ctx->lock); + return ret; +} + +/** + * API implementation of mspi_transceive. + * + * @param controller Pointer to the device structure for the driver instance. + * @param dev_id Pointer to the device ID structure from a device. + * @param xfer Pointer to the MSPI transfer started by dev_id. + * + * @retval 0 if successful. + * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. + * @retval -Error transfer failed. + */ +static int mspi_stm32_xspi_transceive(const struct device *controller, const struct mspi_dev_id *dev_id, + const struct mspi_xfer *xfer) +{ + struct mspi_stm32_data *dev_data = controller->data; + + if (dev_id != dev_data->dev_id) { + LOG_ERR("transceive : dev_id don't match"); + return -ESTALE; + } + + /* Need to map the xfer to the data context */ + dev_data->ctx.xfer = *xfer; + + if (((xfer->packets->cmd == MSPI_STM32_OCMD_RDSR) || (xfer->packets->cmd == MSPI_STM32_CMD_RDSR))) { + return mspi_stm32_xspi_status_reg(controller, xfer); + } + + return mspi_stm32_xspi_pio_dma_transceive(controller, xfer); + +} + +static int mspi_stm32_xspi_dma_init(DMA_HandleTypeDef *hdma, struct stream *dma_stream) +{ + int ret; + /* + * DMA configuration + * Due to use of XSPI HAL API in current driver, + * both HAL and Zephyr DMA drivers should be configured. + * The required configuration for Zephyr DMA driver should only provide + * the minimum information to inform the DMA slot will be in used and + * how to route callbacks. + */ + + if (!device_is_ready(dma_stream->dev)) { + LOG_ERR("DMA %s device not ready", dma_stream->dev->name); + return -ENODEV; + } + + dma_stream->cfg.user_data = hdma; + /* This field is used to inform driver that it is overridden */ + dma_stream->cfg.linked_channel = STM32_DMA_HAL_OVERRIDE; + ret = dma_config(dma_stream->dev, dma_stream->channel, &dma_stream->cfg); + if (ret != 0) { + LOG_ERR("Failed to configure DMA channel %d", dma_stream->channel); + return ret; + } + + /* Proceed to the HAL DMA driver init */ + if (dma_stream->cfg.source_data_size != dma_stream->cfg.dest_data_size) { + LOG_ERR("DMA Source and destination data sizes not aligned"); + return -EINVAL; + } + + hdma->Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE; + hdma->Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE; + hdma->Init.SrcInc = (dma_stream->src_addr_increment) + ? DMA_SINC_INCREMENTED + : DMA_SINC_FIXED; + hdma->Init.DestInc = (dma_stream->dst_addr_increment) + ? DMA_DINC_INCREMENTED + : DMA_DINC_FIXED; + hdma->Init.SrcBurstLength = 4; + hdma->Init.DestBurstLength = 4; + hdma->Init.Priority = table_priority[dma_stream->cfg.channel_priority]; + hdma->Init.Direction = table_direction[dma_stream->cfg.channel_direction]; + hdma->Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0 | DMA_SRC_ALLOCATED_PORT1; + hdma->Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER; + hdma->Init.Mode = DMA_NORMAL; + hdma->Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST; + hdma->Init.Request = dma_stream->cfg.dma_slot; + + /* + * HAL expects a valid DMA channel. + * The channel is from 0 to 7 because of the STM32_DMA_STREAM_OFFSET + * in the dma_stm32 driver + */ + hdma->Instance = LL_DMA_GET_CHANNEL_INSTANCE(dma_stream->reg, + dma_stream->channel); + + /* Initialize DMA HAL */ + if (HAL_DMA_Init(hdma) != HAL_OK) { + LOG_ERR("XSPI DMA Init failed"); + return -EIO; + } + + if (HAL_DMA_ConfigChannelAttributes(hdma, DMA_CHANNEL_NPRIV) != HAL_OK) { + LOG_ERR("XSPI DMA Init failed"); + return -EIO; + } + + LOG_DBG("XSPI with DMA transfer"); + return 0; +} +static int mspi_validate_config(const struct mspi_cfg *config) +{ + if (config->op_mode != MSPI_OP_MODE_CONTROLLER) { + LOG_ERR("Only support MSPI controller mode"); + return -ENOTSUP; + } + + if (config->max_freq > MSPI_STM32_MAX_FREQ) { + LOG_ERR("Max_freq %d too large", config->max_freq); + return -ENOTSUP; + } + + if (config->duplex != MSPI_HALF_DUPLEX) { + LOG_ERR("Only support half duplex mode"); + return -ENOTSUP; + } + + if (config->num_periph > MSPI_MAX_DEVICE) { + LOG_ERR("Invalid MSPI peripheral number"); + return -ENOTSUP; + } + + return 0; +} + +static int mspi_config_clocks(const struct mspi_stm32_conf *dev_cfg, + struct mspi_stm32_data *dev_data, + uint32_t *ahb_clock_freq) +{ + if (!device_is_ready(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE))) { + LOG_ERR("clock control device not ready"); + return -ENODEV; + } + + /* Max 3 domain clock are expected */ + if (dev_cfg->pclk_len > 3) { + LOG_ERR("Could not select %d XSPI domain clock", dev_cfg->pclk_len); + return -EIO; + } + + /* Main clock */ + if (clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[0]) != 0) { + LOG_ERR("Could not enable MSPI clock"); + return -EIO; + } + + if (clock_control_get_rate(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[0], + ahb_clock_freq) < 0) { + LOG_ERR("Failed call clock_control_get_rate(pclken)"); + return -EIO; + } + + /* Optional alternate clocks */ + if (IS_ENABLED(MSPI_STM32_DOMAIN_CLOCK_SUPPORT) && (dev_cfg->pclk_len > 1)) { + if (clock_control_configure(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[1], + NULL) != 0) { + LOG_ERR("Could not select MSPI domain clock"); + return -EIO; + } + + if (clock_control_get_rate(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[1], + ahb_clock_freq) < 0) { + LOG_ERR("Failed call clock_control_get_rate(pclken)"); + return -EIO; + } + } + + if (IS_ENABLED(MSPI_STM32_DOMAIN_CLOCK_SUPPORT) && (dev_cfg->pclk_len > 2)) { + if (clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), + (clock_control_subsys_t)&dev_cfg->pclken[2]) != 0) { + LOG_ERR("Could not enable XSPI Manager clock"); + return -EIO; + } + } + + return 0; +} + +static int mspi_hal_init(const struct mspi_stm32_conf *dev_cfg, + struct mspi_stm32_data *dev_data, + uint32_t ahb_clock_freq) +{ + uint32_t prescaler = MSPI_STM32_CLOCK_PRESCALER_MIN; + + for (; prescaler <= MSPI_STM32_CLOCK_PRESCALER_MAX; prescaler++) { + dev_data->dev_cfg.freq = MSPI_STM32_CLOCK_COMPUTE(ahb_clock_freq, prescaler); + if (dev_data->dev_cfg.freq <= dev_cfg->mspicfg.max_freq) { + break; + } + } + __ASSERT_NO_MSG(prescaler >= MSPI_STM32_CLOCK_PRESCALER_MIN && + prescaler <= MSPI_STM32_CLOCK_PRESCALER_MAX); + + dev_data->hmspi.xspi.Init.ClockPrescaler = prescaler; + +#if defined(XSPI_DCR2_WRAPSIZE) + dev_data->hmspi.xspi.Init.WrapSize = HAL_XSPI_WRAP_NOT_SUPPORTED; +#endif + + if (dev_data->dev_cfg.data_rate == MSPI_DATA_RATE_DUAL) { + dev_data->hmspi.xspi.Init.MemoryType = HAL_XSPI_MEMTYPE_MACRONIX; + dev_data->hmspi.xspi.Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_ENABLE; + } else { + dev_data->hmspi.xspi.Init.MemoryType = HAL_XSPI_MEMTYPE_MICRON; + dev_data->hmspi.xspi.Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_DISABLE; + } + + dev_data->hmspi.xspi.Init.DelayBlockBypass = HAL_XSPI_DELAY_BLOCK_ON; + + if (HAL_XSPI_Init(&dev_data->hmspi.xspi) != HAL_OK) { + LOG_ERR("MSPI Init failed"); + return -EIO; + } + + LOG_DBG("MSPI Init'd"); + return 0; +} + +static int mspi_dma_setup(const struct mspi_stm32_conf *dev_cfg, + struct mspi_stm32_data *dev_data) +{ + if (!dev_cfg->use_dma) { + return 0; + } + + static DMA_HandleTypeDef hdma_tx; + static DMA_HandleTypeDef hdma_rx; + + if (mspi_stm32_xspi_dma_init(&hdma_tx, &dev_data->dma_tx) != 0) { + LOG_ERR("XSPI DMA Tx init failed"); + return -EIO; + } + __HAL_LINKDMA(&dev_data->hmspi.xspi, hdmatx, hdma_tx); + + if (mspi_stm32_xspi_dma_init(&hdma_rx, &dev_data->dma_rx) != 0) { + LOG_ERR("XSPI DMA Rx init failed"); + return -EIO; + } + __HAL_LINKDMA(&dev_data->hmspi.xspi, hdmarx, hdma_rx); + + return 0; +} + +/** + * API implementation of mspi_config : controller configuration. + * + * @param spec Pointer to MSPI device tree spec. + * @return 0 if successful. + * @return -Error if fail. + */ +static int mspi_stm32_xspi_config(const struct mspi_dt_spec *spec) +{ + int ret; + const struct mspi_cfg *config = &spec->config; + const struct mspi_stm32_conf *dev_cfg = spec->bus->config; + struct mspi_stm32_data *dev_data = spec->bus->data; + uint32_t ahb_clock_freq; + + ret = mspi_validate_config(config); + if (ret) { + return ret; + } + + /* pinctrl */ + ret = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("MSPI pinctrl setup failed"); + return ret; + } + + if (dev_data->dev_cfg.dqs_enable && !dev_cfg->mspicfg.dqs_support) { + LOG_ERR("MSPI dqs mismatch (not supported but enabled)"); + return -ENOTSUP; + } + + dev_cfg->irq_config(); + + ret = mspi_config_clocks(dev_cfg, dev_data, &ahb_clock_freq); + if (ret) { + return ret; + } + + ret = mspi_hal_init(dev_cfg, dev_data, ahb_clock_freq); + if (ret) { + return ret; + } + +#if defined(HAL_XSPIM_IOPORT_1) || defined(HAL_XSPIM_IOPORT_2) + /* XSPI I/O manager config */ + XSPIM_CfgTypeDef mspi_mgr_cfg; + + if (dev_data->hmspi.xspi.Instance == XSPI1) { + mspi_mgr_cfg.IOPort = HAL_XSPIM_IOPORT_1; + } + if (dev_data->hmspi.xspi.Instance == XSPI2) { + mspi_mgr_cfg.IOPort = HAL_XSPIM_IOPORT_2; + } + mspi_mgr_cfg.nCSOverride = HAL_XSPI_CSSEL_OVR_DISABLED; + mspi_mgr_cfg.Req2AckTime = 1; + if (HAL_XSPIM_Config(&dev_data->hmspi.xspi, &mspi_mgr_cfg, + HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { + LOG_ERR("XSPI M config failed"); + return -EIO; + } +#endif + +#if defined(DLYB_XSPI1) || defined(DLYB_XSPI2) || defined(DLYB_OCTOSPI1) || defined(DLYB_OCTOSPI2) + HAL_XSPI_DLYB_CfgTypeDef mspi_delay_block_cfg = {0}; + (void)HAL_XSPI_DLYB_GetClockPeriod(&dev_data->hmspi.xspi, &mspi_delay_block_cfg); + mspi_delay_block_cfg.PhaseSel /= 4; + if (HAL_XSPI_DLYB_SetConfig(&dev_data->hmspi.xspi, &mspi_delay_block_cfg) != HAL_OK) { + LOG_ERR("XSPI DelayBlock failed"); + return -EIO; + } + LOG_DBG("Delay Block Init"); +#endif + + ret = mspi_dma_setup(dev_cfg, dev_data); + if (ret) { + return ret; + } + + if (!k_sem_count_get(&dev_data->ctx.lock)) { + k_sem_give(&dev_data->ctx.lock); + } + if (config->re_init) { + k_mutex_unlock(&dev_data->lock); + } + + LOG_INF("MSPI config'd"); + return 0; +} + +/** + * Set up and config a new controller. + * + * @param dev MSPI controller. + * + * @retval 0 if successful. + */ +static int mspi_stm32_init(const struct device *controller) +{ + const struct mspi_stm32_conf *cfg = controller->config; + const struct mspi_dt_spec spec = { + .bus = controller, + .config = cfg->mspicfg, + }; + + return mspi_stm32_xspi_config(&spec); +} + +static struct mspi_driver_api mspi_stm32_driver_api = { + .config = mspi_stm32_xspi_config, + .dev_config = mspi_stm32_xspi_dev_config, + .xip_config = mspi_stm32_xspi_xip_config, + .get_channel_status = mspi_stm32_xspi_get_channel_status, + .transceive = mspi_stm32_xspi_transceive, +}; + +#define DMA_CHANNEL_CONFIG(node, dir) \ + DT_DMAS_CELL_BY_NAME(node, dir, channel_config) + +#define XSPI_DMA_CHANNEL_INIT(node, dir, dir_cap, src_dev, dest_dev) \ + .dev = DEVICE_DT_GET(DT_DMAS_CTLR(node)), \ + .channel = DT_DMAS_CELL_BY_NAME(node, dir, channel), \ + .reg = (DMA_TypeDef *)DT_REG_ADDR( \ + DT_PHANDLE_BY_NAME(node, dmas, dir)), \ + .cfg = { \ + .dma_slot = DT_DMAS_CELL_BY_NAME(node, dir, slot), \ + .channel_direction = STM32_DMA_CONFIG_DIRECTION( \ + DMA_CHANNEL_CONFIG(node, dir)), \ + .channel_priority = STM32_DMA_CONFIG_PRIORITY( \ + DMA_CHANNEL_CONFIG(node, dir)), \ + .dma_callback = mspi_stm32_xspi_dma_callback, \ + }, \ + .src_addr_increment = STM32_DMA_CONFIG_##src_dev##_ADDR_INC( \ + DMA_CHANNEL_CONFIG(node, dir)), \ + .dst_addr_increment = STM32_DMA_CONFIG_##dest_dev##_ADDR_INC( \ + DMA_CHANNEL_CONFIG(node, dir)), + +#define XSPI_DMA_CHANNEL(node, dir, DIR, src, dest) \ + .dma_##dir = { \ + COND_CODE_1(DT_DMAS_HAS_NAME(node, dir), \ + (XSPI_DMA_CHANNEL_INIT(node, dir, DIR, src, dest)), \ + (NULL)) \ + }, + +/* MSPI control config */ +#define MSPI_CONFIG(n) \ + { \ + .channel_num = 0, .op_mode = DT_ENUM_IDX_OR(n, op_mode, MSPI_OP_MODE_CONTROLLER), \ + .duplex = DT_ENUM_IDX_OR(n, duplex, MSPI_HALF_DUPLEX), \ + .max_freq = DT_INST_PROP_OR(n, mspi_max_frequency, MSPI_STM32_MAX_FREQ), \ + .dqs_support = DT_INST_PROP_OR(n, dqs_support, false), \ + .num_periph = DT_INST_CHILD_NUM(n), \ + .sw_multi_periph = DT_INST_PROP_OR(n, software_multiperipheral, false), \ + } + +#define STM32_SMPI_IRQ_HANDLER(index) \ + static void mspi_stm32_irq_config_func_##index(void) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(index), DT_INST_IRQ(index, priority), mspi_stm32_xspi_isr, \ + DEVICE_DT_INST_GET(index), 0); \ + irq_enable(DT_INST_IRQN(index)); \ + } + +#define MSPI_STM32_INIT(index) \ + static const struct stm32_pclken pclken_##index[] = STM32_DT_INST_CLOCKS(index); \ + PINCTRL_DT_INST_DEFINE(index); \ + \ + static struct gpio_dt_spec ce_gpios##index[] = MSPI_CE_GPIOS_DT_SPEC_INST_GET(index); \ + STM32_SMPI_IRQ_HANDLER(index) \ + static const struct mspi_stm32_conf mspi_stm32_dev_conf_##index = { \ + .pclken = pclken_##index, \ + .pclk_len = DT_INST_NUM_CLOCKS(index), \ + .irq_config = mspi_stm32_irq_config_func_##index, \ + .mspicfg = MSPI_CONFIG(index), \ + .pcfg = PINCTRL_DT_DEV_CONFIG_GET(DT_DRV_INST(index)), \ + .mspicfg.num_ce_gpios = ARRAY_SIZE(ce_gpios##index), \ + .use_dma = DT_NODE_HAS_PROP(DT_DRV_INST(index), dmas), \ + }; \ + static struct mspi_stm32_data mspi_stm32_dev_data_##index = { \ + .hmspi.xspi = { \ + .Instance = (XSPI_TypeDef *)DT_INST_REG_ADDR(index), \ + .Init = { \ + .FifoThresholdByte = MSPI_STM32_FIFO_THRESHOLD, \ + .SampleShifting = (DT_INST_PROP(index, ssht_enable) \ + ? HAL_XSPI_SAMPLE_SHIFT_HALFCYCLE \ + : HAL_XSPI_SAMPLE_SHIFT_NONE), \ + .ChipSelectHighTimeCycle = 1, \ + .ClockMode = HAL_XSPI_CLOCK_MODE_0, \ + .ChipSelectBoundary = 0, \ + .MemoryMode = HAL_XSPI_SINGLE_MEM, \ + .MemorySize = 0x19, \ + .FreeRunningClock = HAL_XSPI_FREERUNCLK_DISABLE, \ + }, \ + }, \ + .memmap_base_addr = DT_REG_ADDR_BY_IDX(DT_DRV_INST(index), 1), \ + .dev_id = index, \ + .lock = Z_MUTEX_INITIALIZER(mspi_stm32_dev_data_##index.lock), \ + .sync = Z_SEM_INITIALIZER(mspi_stm32_dev_data_##index.sync, 0, 1), \ + .dev_cfg = {0}, \ + .xip_cfg = {0}, \ + .ctx.lock = Z_SEM_INITIALIZER(mspi_stm32_dev_data_##index.ctx.lock, 0, 1), \ + XSPI_DMA_CHANNEL(DT_DRV_INST(index), tx, TX, MEMORY, PERIPHERAL) \ + XSPI_DMA_CHANNEL(DT_DRV_INST(index), rx, RX, PERIPHERAL, MEMORY) \ + }; \ + \ + DEVICE_DT_INST_DEFINE(index, &mspi_stm32_init, NULL, &mspi_stm32_dev_data_##index, \ + &mspi_stm32_dev_conf_##index, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &mspi_stm32_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(MSPI_STM32_INIT) diff --git a/dts/bindings/mspi/st,stm32-ospi-controller.yaml b/dts/bindings/mspi/st,stm32-ospi-controller.yaml new file mode 100644 index 0000000000000..d373ad1f59372 --- /dev/null +++ b/dts/bindings/mspi/st,stm32-ospi-controller.yaml @@ -0,0 +1,166 @@ +# Copyright (c) 2024 STMicroelectronics +# SPDX-License-Identifier: Apache-2.0 + +description: | + STM32 OSPI controller representation. Enabling a stm32 ospi node in a board + description would typically requires this: (pinning depends on the stm32 mcu) + + &ospi { + pinctrl-0 = <&octospi_clk_pe9 &octospi_ncs_pe10 &octospi_dqs_pe11 + &octospi_io0_pe12 &octospi_io1_pe13 + &octospi_io2_pe14 &octospi_io3_pe15 + &octospi_io4_pe16 &octospi_io5_pe17 + &octospi_io6_pe18 &octospi_io7_pe19>; + + dmas = <&dma1 5 41 0x10000>; + dma-names = "tx_rx"; + + status = "okay"; + }; + +compatible: "st,stm32-ospi-controller" + +include: [base.yaml, pinctrl-device.yaml, mspi-controller.yaml] + +bus: mspi + +properties: + reg: + required: true + + interrupts: + required: true + + pinctrl-0: + required: true + + pinctrl-names: + required: true + + clock-names: + required: true + + dmas: + description: | + Optional DMA channel specifier, required for DMA transactions. + For example dmas for TX/RX on MSPI + dmas = <&dma1 5 41 0x10000>; + + With, in each cell of the dmas specifier: + - &dma1: dma controller phandle + - 5: channel number (0 to Max-Channel minus 1). From 0 to 15 on stm32u5x. + - 41: slot number (request which could be given by the DMAMUX) + - 0x10000: channel configuration (only for srce/dest data size, priority) + + Notes: + - On series supporting DMAMUX, the DMA phandle should be provided + but DMAMUX node should also be enabled in the DTS. + - For channel configuration, only the config bits priority and + periph/mem datasize are used. The periph/mem datasize must be equal, + 0 is a correct value. + - There is no Fifo used by this DMA peripheral. + + For example dmas for TX/RX on MSPI + dmas = <&dma1 5 41 0x10000>; + + dma-names: + description: | + DMA channel name. If DMA should be used, expected value is "tx_rx". + + For example + dma-names = "tx_rx"; + + dlyb-bypass: + type: boolean + description: | + Enables Delay Block (DLYB) Bypass. + + ssht-enable: + type: boolean + description: | + Enables Sample Shifting half-cycle. + + It is recommended to be enabled in STR mode and disabled in DTR mode. + + io-low-port: + type: string + enum: + - "IOPORT_NONE" + - "IOPORT_1_LOW" + - "IOPORT_1_HIGH" + - "IOPORT_2_LOW" + - "IOPORT_2_HIGH" + description: | + Specifies which port of the OCTOSPI IO Manager is used for the IO[3:0] pins. + + If absent, then `IOPORT__LOW` is used where `n` is the OSPI + instance number. + + Note: You might need to enable the OCTOSPI I/O manager clock to use the + property. Please refer to Reference Manual. + The clock can be enabled in the devicetree. + + io-high-port: + type: string + enum: + - "IOPORT_NONE" + - "IOPORT_1_LOW" + - "IOPORT_1_HIGH" + - "IOPORT_2_LOW" + - "IOPORT_2_HIGH" + description: | + Specifies which port of the OCTOSPI IO Manager is used for the IO[7:4] pins. + + If absent, then `IOPORT__HIGH` is used where `n` is the OSPI + instance number. + + Can be set to `IOPORT_NONE` for Single SPI, Dual SPI and Quad SPI. + + Note: You might need to enable the OCTOSPI I/O manager clock to use the + property. Please refer to Reference Manual. + The clock can be enabled in the devicetree. + + clk-port: + type: int + enum: + - 1 + - 2 + description: | + Specifies which port of the OCTOSPI IO Manager is used for the clk pin. + + If absent, then n is used where `n` is the OSPI + instance number. + + Note: You might need to enable the OCTOSPI I/O manager clock to use the + property. Please refer to Reference Manual. + The clock can be enabled in the devicetree. + + dqs-port: + type: int + enum: + - 1 + - 2 + description: | + Specifies which port of the OCTOSPI IO Manager is used for the dqs pin. + + If absent, then n is used where `n` is the OSPI + instance number. + + Note: You might need to enable the OCTOSPI I/O manager clock to use the + property. Please refer to Reference Manual. + The clock can be enabled in the devicetree. + + ncs-port: + type: int + enum: + - 1 + - 2 + description: | + Specifies which port of the OCTOSPI IO Manager is used for the ncs pin. + + If absent, then n is used where `n` is the OSPI + instance number. + + Note: You might need to enable the OCTOSPI I/O manager clock to use the + property. Please refer to Reference Manual. + The clock can be enabled in the devicetree. diff --git a/dts/bindings/mspi/st,stm32-qspi-controller.yaml b/dts/bindings/mspi/st,stm32-qspi-controller.yaml new file mode 100644 index 0000000000000..a742b62e7066a --- /dev/null +++ b/dts/bindings/mspi/st,stm32-qspi-controller.yaml @@ -0,0 +1,76 @@ +# Copyright (c) 2025 EXALT Technologies. +# SPDX-License-Identifier: Apache-2.0 + +description: | + STM32 QSPI controller representation for MSPI interface. Enabling a stm32 qspi node + in a board description would typically require this: (pinning depends on the stm32 mcu) + + &qspi { + pinctrl-0 = <&quadspi_clk_pe10 &quadspi_ncs_pe11 + &quadspi_io0_pe12 &quadspi_io1_pe13 + &quadspi_io2_pe14 &quadspi_io3_pe15>; + + dmas = <&dma1 5 41 0x10000>; + dma-names = "tx_rx"; + + status = "okay"; + }; + +compatible: "st,stm32-qspi-controller" + +include: [base.yaml, pinctrl-device.yaml, mspi-controller.yaml] + +bus: mspi + +properties: + reg: + required: true + + interrupts: + required: true + + pinctrl-0: + required: true + + pinctrl-names: + required: true + + clock-names: + required: true + + dmas: + description: | + Optional DMA channel specifier, required for DMA transactions. + For example dmas for TX/RX on QSPI + dmas = <&dma1 5 41 0x10000>; + + With, in each cell of the dmas specifier: + - &dma1: dma controller phandle + - 5: channel number (0 to Max-Channel minus 1). From 0 to 15 on stm32u5x. + - 41: slot number (request which could be given by the DMAMUX) + - 0x10000: channel configuration (only for src/dest data size, priority) + + Notes: + - On series supporting DMAMUX, the DMA phandle should be provided + but DMAMUX node should also be enabled in the DTS. + - For channel configuration, only the config bits priority and + periph/mem datasize are used. The periph/mem datasize must be equal, + 0 is a correct value. + - There is no Fifo used by this DMA peripheral. + + For example dmas for TX/RX on QSPI + dmas = <&dma1 5 41 0x10000>; + + dma-names: + description: | + DMA channel name. If DMA should be used, expected value is "tx_rx". + + For example + dma-names = "tx_rx"; + + ssht-enable: + type: boolean + description: | + Enables Sample Shifting half-cycle. + + It is recommended to be enabled in STR mode and disabled in DTR mode. diff --git a/dts/bindings/mspi/st,stm32-xspi-controller.yaml b/dts/bindings/mspi/st,stm32-xspi-controller.yaml new file mode 100644 index 0000000000000..e6aae36a648a1 --- /dev/null +++ b/dts/bindings/mspi/st,stm32-xspi-controller.yaml @@ -0,0 +1,78 @@ +# Copyright (c) 2025 EXALT Technologies. +# SPDX-License-Identifier: Apache-2.0 + +description: | + STM32 XSPI controller representation. Enabling a stm32 xspi node in a board + description would typically requires this: (pinning depends on the stm32 mcu) + + &xspi { + pinctrl-0 = <&octospi_clk_pe9 &octospi_ncs_pe10 &octospi_dqs_pe11 + &octospi_io0_pe12 &octospi_io1_pe13 + &octospi_io2_pe14 &octospi_io3_pe15 + &octospi_io4_pe16 &octospi_io5_pe17 + &octospi_io6_pe18 &octospi_io7_pe19>; + + dmas = <&dma1 5 41 0x10000>; + dma-names = "tx_rx"; + + status = "okay"; + }; + +compatible: "st,stm32-xspi-controller" + +include: [base.yaml, pinctrl-device.yaml, mspi-controller.yaml] + +bus: mspi + +properties: + reg: + required: true + + interrupts: + required: true + + pinctrl-0: + required: true + + pinctrl-names: + required: true + + clock-names: + required: true + + dmas: + description: | + Optional DMA channel specifier, required for DMA transactions. + For example dmas for TX/RX on xspi + dmas = <&dma1 5 41 0x10000>; + + With, in each cell of the dmas specifier: + - &dma1: dma controller phandle + - 5: channel number (0 to Max-Channel minus 1). From 0 to 15 on stm32u5x. + - 41: slot number (request which could be given by the DMAMUX) + - 0x10000: channel configuration (only for srce/dest data size, priority) + + Notes: + - On series supporting DMAMUX, the DMA phandle should be provided + but DMAMUX node should also be enabled in the DTS. + - For channel configuration, only the config bits priority and + periph/mem datasize are used. The periph/mem datasize must be equal, + 0 is a correct value. + - There is no Fifo used by this DMA peripheral. + + For example dmas for TX/RX on xspi + dmas = <&dma1 5 41 0x10000>; + + dma-names: + description: | + DMA channel name. If DMA should be used, expected value is "tx_rx". + + For example + dma-names = "tx_rx"; + + ssht-enable: + type: boolean + description: | + Enables Sample Shifting half-cycle. + + It is recommended to be enabled in STR mode and disabled in DTR mode. diff --git a/samples/drivers/mspi/mspi_flash/boards/arduino_giga_r1_stm32h747xx_m7.conf b/samples/drivers/mspi/mspi_flash/boards/arduino_giga_r1_stm32h747xx_m7.conf new file mode 100644 index 0000000000000..a02b1b92379fc --- /dev/null +++ b/samples/drivers/mspi/mspi_flash/boards/arduino_giga_r1_stm32h747xx_m7.conf @@ -0,0 +1,9 @@ +# Copyright (c) 2025 EXALT Technologies. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_LOG=y +CONFIG_MSPI_LOG_LEVEL_INF=y +CONFIG_USB_DEVICE_STACK=y +CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_USB_CDC_ACM=y diff --git a/samples/drivers/mspi/mspi_flash/boards/arduino_giga_r1_stm32h747xx_m7.overlay b/samples/drivers/mspi/mspi_flash/boards/arduino_giga_r1_stm32h747xx_m7.overlay new file mode 100644 index 0000000000000..35973d6734cd2 --- /dev/null +++ b/samples/drivers/mspi/mspi_flash/boards/arduino_giga_r1_stm32h747xx_m7.overlay @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025 EXALT Technologies. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Replace the quadspi by a "st,stm32-qspi-controller" compatible */ +/delete-node/ &n25q128a1; + +/ { + chosen { + zephyr,console = &cdc_acm_uart0; + zephyr,shell-uart = &cdc_acm_uart0; + zephyr,uart-mcumgr = &cdc_acm_uart0; + }; + + aliases { + /* The sample gets the DT_ALIAS(flash0) as the mspi device */ + flash0 = &n25q128a1; + }; + + qspi_memory: qspi-memory@90000000 { + compatible = "zephyr,memory-region"; + device_type = "memory"; + reg = <0x90000000 DT_SIZE_M(256)>; + zephyr,memory-region = "QSPI"; + zephyr,memory-attr = <( DT_MEM_ARM(ATTR_MPU_RAM_NOCACHE) )>; + }; + +}; + + +&usbotg_fs { + status = "okay"; + pinctrl-0 = <&usb_otg_fs_dm_pa11 &usb_otg_fs_dp_pa12>; + pinctrl-names = "default"; + + cdc_acm_uart0: cdc_acm_uart0 { + status = "okay"; + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +&cdc_acm_uart0 { + status = "okay"; +}; + +&quadspi { + compatible = "st,stm32-qspi-controller"; + reg = <0x52005000 0x1000>, <0x90000000 DT_SIZE_M(256)>; + interrupts = <92 0>; + clocks = <&rcc STM32_CLOCK(AHB3, 14U)>; + clock-names = "qspix"; + #address-cells = <1>; + #size-cells = <0>; + clock-frequency = ; + op-mode = "MSPI_CONTROLLER"; + duplex = "MSPI_HALF_DUPLEX"; + + status = "okay"; + pinctrl-0 = <&quadspi_clk_pf10 &quadspi_bk1_ncs_pg6 + &quadspi_bk1_io0_pd11 &quadspi_bk1_io1_pd12 + &quadspi_bk1_io2_pe2 &quadspi_bk1_io3_pf6>; + pinctrl-names = "default"; + + n25q128a1: qspi-nor-flash@0 { + compatible = "jedec,mspi-nor"; + reg = <0>; + size = ; /* 128 Mbits */ + status = "okay"; + + mspi-max-frequency = ; + mspi-io-mode = "MSPI_IO_MODE_SINGLE"; + mspi-data-rate = "MSPI_DATA_RATE_SINGLE"; + mspi-hardware-ce-num = <0>; + read-command = <0x0b>; + write-command = <0x02>; + command-length = "INSTR_1_BYTE"; + address-length = "ADDR_3_BYTE"; + rx-dummy = <8>; + tx-dummy = <0>; + jedec-id = [1f 89 01]; + xip-config = <1 0x00000000 DT_SIZE_M(16) 0>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + slot1_partition: partition@0 { + label = "image-1"; + reg = <0x00000000 DT_SIZE_M(1)>; + }; + + storage_partition: partition@100000 { + label = "storage"; + reg = <0x00100000 DT_SIZE_M(15)>; + }; + }; + }; +}; diff --git a/samples/drivers/mspi/mspi_flash/boards/b_u585i_iot02a.conf b/samples/drivers/mspi/mspi_flash/boards/b_u585i_iot02a.conf new file mode 100644 index 0000000000000..5cc453f41baad --- /dev/null +++ b/samples/drivers/mspi/mspi_flash/boards/b_u585i_iot02a.conf @@ -0,0 +1,2 @@ +CONFIG_LOG=y +CONFIG_MSPI_LOG_LEVEL_INF=y diff --git a/samples/drivers/mspi/mspi_flash/boards/b_u585i_iot02a.overlay b/samples/drivers/mspi/mspi_flash/boards/b_u585i_iot02a.overlay new file mode 100644 index 0000000000000..4170b9ba16eb6 --- /dev/null +++ b/samples/drivers/mspi/mspi_flash/boards/b_u585i_iot02a.overlay @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 EXALT Technologies. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Replace the octospi2 by a "st,stm32-ospi-controller" compatible */ +/delete-node/ &octospi2; +/ { + aliases { + /* The sample gets the DT_ALIAS(flash0) as the mspi device */ + flash0 = &mx25lm51245; + }; + soc{ + octospi2: spi@420d2400 { + compatible = "st,stm32-ospi-controller"; + reg = <0x420d2400 0x400>, <0x70000000 DT_SIZE_M(256)>; + interrupts = <120 0>; + clock-names = "ospix", "ospi-ker", "ospi-mgr"; + clocks = <&rcc STM32_CLOCK(AHB2_2, 8)>, + <&rcc STM32_SRC_SYSCLK OCTOSPI_SEL(0)>, + <&rcc STM32_CLOCK(AHB2, 21)>; + #address-cells = <1>; + #size-cells = <0>; + op-mode = "MSPI_CONTROLLER"; + status = "okay"; + pinctrl-0 = <&octospim_p2_clk_pf4 &octospim_p2_ncs_pi5 + &octospim_p2_io0_pf0 &octospim_p2_io1_pf1 + &octospim_p2_io2_pf2 &octospim_p2_io3_pf3 + &octospim_p2_io4_ph9 &octospim_p2_io5_ph10 + &octospim_p2_io6_ph11 &octospim_p2_io7_ph12 + &octospim_p2_dqs_pf12>; + pinctrl-names = "default"; + + dmas = <&gpdma1 12 41 0x10480>; /* request 41 for OCTOSPI2 */ + dma-names = "tx_rx"; + + mx25lm51245: ospi-nor-flash@0 { + compatible = "jedec,mspi-nor"; + reg = <0>; + size = ; /* 512 Megabits */ + mspi-max-frequency = ; + status = "okay"; + mspi-io-mode = "MSPI_IO_MODE_OCTAL"; + mspi-data-rate = "MSPI_DATA_RATE_DUAL"; /* as first step */ + mspi-hardware-ce-num = <0>; + read-command = <0xEE11>; /* ReaD 4Bytes */ + command-length ="INSTR_2_BYTE"; + write-command = <0x12ED>; /* WRite 4Bytes */ + rx-dummy = <20>; + jedec-id = [ c2 85 3a ]; + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "nor"; + reg = <0x00000000 DT_SIZE_M(4)>; + }; + }; + }; + }; + }; +}; diff --git a/samples/drivers/mspi/mspi_flash/boards/stm32h573i_dk.conf b/samples/drivers/mspi/mspi_flash/boards/stm32h573i_dk.conf new file mode 100644 index 0000000000000..2bc82d0a582b4 --- /dev/null +++ b/samples/drivers/mspi/mspi_flash/boards/stm32h573i_dk.conf @@ -0,0 +1,3 @@ +CONFIG_LOG=y +CONFIG_MSPI_LOG_LEVEL_INF=y +CONFIG_HEAP_MEM_POOL_SIZE=8192 diff --git a/samples/drivers/mspi/mspi_flash/boards/stm32h573i_dk.overlay b/samples/drivers/mspi/mspi_flash/boards/stm32h573i_dk.overlay new file mode 100644 index 0000000000000..da960819dfb6c --- /dev/null +++ b/samples/drivers/mspi/mspi_flash/boards/stm32h573i_dk.overlay @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Replace the xspi1 by a "st,stm32-mspi-controller" compatible */ +/delete-node/ &xspi1; + +/ { + aliases { + /* The sample gets the DT_ALIAS(flash0) as the mspi device */ + flash0 = &mx25lm51245; + }; + + soc { + xspi1: mspi@47001400 { + compatible = "st,stm32-xspi-controller"; + reg = <0x47001400 0x400>, <0x90000000 DT_SIZE_M(256)>; + interrupts = <78 0>; + clock-names = "xspix", "xspi-ker"; + clocks = <&rcc STM32_CLOCK(AHB4, 20U)>, + <&rcc STM32_SRC_PLL1_Q OCTOSPI1_SEL(1)>; + #address-cells = <1>; + #size-cells = <0>; + clock-frequency = < 0x17d7840 >; + op-mode = "MSPI_CONTROLLER"; + status = "okay"; + pinctrl-0 = <&octospi1_io0_pb1 &octospi1_io1_pd12 + &octospi1_io2_pc2 &octospi1_io3_pd13 + &octospi1_io4_ph2 &octospi1_io5_ph3 + &octospi1_io6_pg9 &octospi1_io7_pc0 + &octospi1_clk_pf10 &octospi1_ncs_pg6 + &octospi1_dqs_pb2>; + pinctrl-names = "default"; + dmas = <&gpdma1 4 57 STM32_DMA_PERIPH_TX + &gpdma1 5 57 STM32_DMA_PERIPH_RX>; + dma-names = "tx", "rx"; + + mx25lm51245: ospi-nor-flash@0 { + compatible = "jedec,mspi-nor"; + reg = <0>; + size = ; /* 512 Mbits */ + status = "okay"; + read-command = <0xEC13>; + write-command = <0x12ED>; + rx-dummy = <20>; + mspi-max-frequency = ; + mspi-io-mode = "MSPI_IO_MODE_OCTAL"; + mspi-data-rate = "MSPI_DATA_RATE_SINGLE"; /* as first step */ + mspi-hardware-ce-num = <0>; + command-length = "INSTR_2_BYTE"; + jedec-id = [c2 85 3a]; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + slot0_partition: partition@0 { + label = "nor"; + reg = <0x00000000 DT_SIZE_M(64)>; + }; + }; + }; + }; + }; +}; + +&gpdma1 { + status = "okay"; +}; diff --git a/samples/drivers/mspi/mspi_flash/boards/stm32h735g_disco.conf b/samples/drivers/mspi/mspi_flash/boards/stm32h735g_disco.conf new file mode 100644 index 0000000000000..b44228b1b2295 --- /dev/null +++ b/samples/drivers/mspi/mspi_flash/boards/stm32h735g_disco.conf @@ -0,0 +1,3 @@ +CONFIG_LOG=y +CONFIG_MSPI_LOG_LEVEL_INF=y +CONFIG_ARM_MPU=n diff --git a/samples/drivers/mspi/mspi_flash/boards/stm32h735g_disco.overlay b/samples/drivers/mspi/mspi_flash/boards/stm32h735g_disco.overlay new file mode 100644 index 0000000000000..dcf067630524c --- /dev/null +++ b/samples/drivers/mspi/mspi_flash/boards/stm32h735g_disco.overlay @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 EXALT Technologies. + * + * SPDX-License-Identifier: Apache-2.0 + */ +/* Replace the octospi1 by a "st,stm32-ospi-controller" compatible */ +/delete-node/ &octospi1; +/ { + aliases { + flash0 = &mx25lm51245; + }; + + soc{ + octospi1: spi@52005000 { + compatible = "st,stm32-ospi-controller"; + reg = <0x52005000 0x1000>, <0x90000000 DT_SIZE_M(256)>; + interrupts = <92 0>; + clock-names = "ospix", "ospi-ker"; + clocks = <&rcc STM32_CLOCK(AHB3, 14)>, + <&rcc STM32_SRC_PLL1_Q OSPI_SEL(1)>; + #address-cells = <1>; + #size-cells = <0>; + op-mode = "MSPI_CONTROLLER"; + pinctrl-0 = <&octospim_p1_clk_pf10 &octospim_p1_ncs_pg6 + &octospim_p1_io0_pd11 &octospim_p1_io1_pd12 + &octospim_p1_io2_pe2 &octospim_p1_io3_pd13 + &octospim_p1_io4_pd4 &octospim_p1_io5_pd5 + &octospim_p1_io6_pg9 &octospim_p1_io7_pd7 + &octospim_p1_dqs_pb2>; + pinctrl-names = "default"; + status = "okay"; + + mx25lm51245: ospi-nor-flash@0 { + compatible = "jedec,mspi-nor"; + reg = <0>; + size = ; + status = "okay"; + mspi-max-frequency = ; + mspi-io-mode = "MSPI_IO_MODE_OCTAL"; + mspi-data-rate = "MSPI_DATA_RATE_SINGLE"; + mspi-hardware-ce-num = <0>; + read-command = <0xEC13>; + write-command = <0x12ED>; + command-length ="INSTR_2_BYTE"; + rx-dummy = <20>; + jedec-id = [ c2 85 3a ]; + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "nor"; + reg = <0x00000000 DT_SIZE_M(4)>; + }; + }; + }; + }; + }; +};