diff --git a/boards/arm/disco_l475_iot1/disco_l475_iot1.dts b/boards/arm/disco_l475_iot1/disco_l475_iot1.dts index a69ae20346bdd..f695560e09b83 100644 --- a/boards/arm/disco_l475_iot1/disco_l475_iot1.dts +++ b/boards/arm/disco_l475_iot1/disco_l475_iot1.dts @@ -19,6 +19,7 @@ zephyr,sram = &sram0; zephyr,flash = &flash0; zephyr,code-partition = &slot0_partition; + zephyr,flash-controller = &mx25r6435f; }; leds { @@ -173,21 +174,13 @@ slot0_partition: partition@20000 { label = "image-0"; - reg = <0x00020000 0x0006C000>; - }; - slot1_partition: partition@8c000 { - label = "image-1"; - reg = <0x0008C000 0x0006C000>; + reg = <0x00020000 0x000D8000>; }; + scratch_partition: partition@f8000 { label = "image-scratch"; reg = <0x000F8000 0x00004000>; }; - - storage_partition: partition@fc000 { - label = "storage"; - reg = <0x000fc000 0x00004000>; - }; }; }; @@ -228,3 +221,43 @@ &adc1_in14_pc5>; status = "okay"; }; + +&dma1 { + status = "okay"; +}; + +&quadspi { + pinctrl-0 = <&quadspi_clk_pe10 &quadspi_ncs_pe11 + &quadspi_bk1_io0_pe12 &quadspi_bk1_io1_pe13 + &quadspi_bk1_io2_pe14 &quadspi_bk1_io3_pe15>; + dmas = <&dma1 5 5 0x0000 0x03>; + dma-names = "tx_rx"; + + status = "okay"; + + mx25r6435f: qspi-nor-flash@0 { + compatible = "st,stm32-qspi-nor"; + label = "MX25R6435F"; + reg = <0>; + qspi-max-frequency = <50000000>; + /* 64 Megabits = 8 Megabytes */ + size = <0x4000000>; + status = "okay"; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + slot1_partition: partition@0 { + label = "image-1"; + reg = <0x00000000 0x000D8000>; + }; + + storage_partition: partition@D8000 { + label = "storage"; + reg = <0x000D8000 DT_SIZE_M(7)>; + }; + }; + }; +}; diff --git a/boards/arm/disco_l475_iot1/doc/index.rst b/boards/arm/disco_l475_iot1/doc/index.rst index 9eb77fe7f2585..4ef0ad1ebbea5 100644 --- a/boards/arm/disco_l475_iot1/doc/index.rst +++ b/boards/arm/disco_l475_iot1/doc/index.rst @@ -125,6 +125,8 @@ The Zephyr Disco L475 IoT board configuration supports the following hardware fe +-----------+------------+-------------------------------------+ | ADC | on-chip | adc | +-----------+------------+-------------------------------------+ +| QSPI NOR | on-chip | flash | ++-----------+------------+-------------------------------------+ Other hardware features are not yet supported on this Zephyr port. diff --git a/doc/guides/kconfig/preprocessor-functions.rst b/doc/guides/kconfig/preprocessor-functions.rst index 164bccd197029..f73ce3624845d 100644 --- a/doc/guides/kconfig/preprocessor-functions.rst +++ b/doc/guides/kconfig/preprocessor-functions.rst @@ -39,6 +39,7 @@ while the ``*_hex`` version returns a hexadecimal value starting with ``0x``. $(dt_compat_enabled,) $(dt_chosen_enabled,) $(dt_node_has_bool_prop,,) + $(dt_node_has_prop,,) Example Usage diff --git a/drivers/clock_control/clock_stm32_ll_common.c b/drivers/clock_control/clock_stm32_ll_common.c index 122d6bbb9a43e..a047f681ff347 100644 --- a/drivers/clock_control/clock_stm32_ll_common.c +++ b/drivers/clock_control/clock_stm32_ll_common.c @@ -71,10 +71,11 @@ static void config_bus_clk_init(LL_UTILS_ClkInitTypeDef *clk_init) clk_init->APB1CLKDivider = apb1_prescaler( CONFIG_CLOCK_STM32_APB1_PRESCALER); -#if !defined (CONFIG_SOC_SERIES_STM32F0X) && !defined (CONFIG_SOC_SERIES_STM32G0X) +#if !defined (CONFIG_SOC_SERIES_STM32F0X) && \ + !defined (CONFIG_SOC_SERIES_STM32G0X) clk_init->APB2CLKDivider = apb2_prescaler( CONFIG_CLOCK_STM32_APB2_PRESCALER); -#endif /* CONFIG_SOC_SERIES_STM32F0X && CONFIG_SOC_SERIES_STM32G0X */ +#endif } static uint32_t get_bus_clock(uint32_t clock, uint32_t prescaler) @@ -95,7 +96,7 @@ static inline int stm32_clock_control_on(const struct device *dev, break; #if defined(CONFIG_SOC_SERIES_STM32L4X) || \ defined(CONFIG_SOC_SERIES_STM32L5X) || \ - defined(CONFIG_SOC_SERIES_STM32F4X) && !defined(CONFIG_SOC_STM32F410RX) || \ + defined(CONFIG_SOC_SERIES_STM32F4X) && defined(RCC_AHB2_SUPPORT) || \ defined(CONFIG_SOC_SERIES_STM32F7X) || \ defined(CONFIG_SOC_SERIES_STM32F2X) || \ defined(CONFIG_SOC_SERIES_STM32WBX) || \ @@ -103,6 +104,17 @@ static inline int stm32_clock_control_on(const struct device *dev, case STM32_CLOCK_BUS_AHB2: LL_AHB2_GRP1_EnableClock(pclken->enr); break; +#endif /* CONFIG_SOC_SERIES_STM32_* */ +#if defined(CONFIG_SOC_SERIES_STM32L4X) || \ + defined(CONFIG_SOC_SERIES_STM32L5X) || \ + defined(CONFIG_SOC_SERIES_STM32F4X) && defined(RCC_AHB3_SUPPORT) || \ + defined(CONFIG_SOC_SERIES_STM32F7X) || \ + defined(CONFIG_SOC_SERIES_STM32F2X) || \ + defined(CONFIG_SOC_SERIES_STM32WBX) || \ + defined(CONFIG_SOC_SERIES_STM32G4X) + case STM32_CLOCK_BUS_AHB3: + LL_AHB3_GRP1_EnableClock(pclken->enr); + break; #endif /* CONFIG_SOC_SERIES_STM32_* */ case STM32_CLOCK_BUS_APB1: LL_APB1_GRP1_EnableClock(pclken->enr); @@ -120,12 +132,13 @@ static inline int stm32_clock_control_on(const struct device *dev, case STM32_CLOCK_BUS_APB2: LL_APB2_GRP1_EnableClock(pclken->enr); break; -#endif /* CONFIG_SOC_SERIES_STM32F0X */ -#if defined (CONFIG_SOC_SERIES_STM32L0X) || defined (CONFIG_SOC_SERIES_STM32G0X) +#endif +#if defined (CONFIG_SOC_SERIES_STM32L0X) || \ + defined (CONFIG_SOC_SERIES_STM32G0X) case STM32_CLOCK_BUS_IOP: LL_IOP_GRP1_EnableClock(pclken->enr); break; -#endif /* CONFIG_SOC_SERIES_STM32L0X || CONFIG_SOC_SERIES_STM32G0X */ +#endif default: return -ENOTSUP; } @@ -147,7 +160,7 @@ static inline int stm32_clock_control_off(const struct device *dev, break; #if defined(CONFIG_SOC_SERIES_STM32L4X) || \ defined(CONFIG_SOC_SERIES_STM32L5X) || \ - defined(CONFIG_SOC_SERIES_STM32F4X) && !defined(CONFIG_SOC_STM32F410RX) || \ + defined(CONFIG_SOC_SERIES_STM32F4X) && defined(RCC_AHB2_SUPPORT) || \ defined(CONFIG_SOC_SERIES_STM32F7X) || \ defined(CONFIG_SOC_SERIES_STM32F2X) || \ defined(CONFIG_SOC_SERIES_STM32WBX) || \ @@ -155,6 +168,17 @@ static inline int stm32_clock_control_off(const struct device *dev, case STM32_CLOCK_BUS_AHB2: LL_AHB2_GRP1_DisableClock(pclken->enr); break; +#endif /* CONFIG_SOC_SERIES_STM32_* */ +#if defined(CONFIG_SOC_SERIES_STM32L4X) || \ + defined(CONFIG_SOC_SERIES_STM32L5X) || \ + defined(CONFIG_SOC_SERIES_STM32F4X) && defined(RCC_AHB3_SUPPORT) || \ + defined(CONFIG_SOC_SERIES_STM32F7X) || \ + defined(CONFIG_SOC_SERIES_STM32F2X) || \ + defined(CONFIG_SOC_SERIES_STM32WBX) || \ + defined(CONFIG_SOC_SERIES_STM32G4X) + case STM32_CLOCK_BUS_AHB3: + LL_AHB3_GRP1_EnableClock(pclken->enr); + break; #endif /* CONFIG_SOC_SERIES_STM32_* */ case STM32_CLOCK_BUS_APB1: LL_APB1_GRP1_DisableClock(pclken->enr); @@ -172,12 +196,13 @@ static inline int stm32_clock_control_off(const struct device *dev, case STM32_CLOCK_BUS_APB2: LL_APB2_GRP1_DisableClock(pclken->enr); break; -#endif /* CONFIG_SOC_SERIES_STM32F0X */ -#if defined (CONFIG_SOC_SERIES_STM32L0X) || defined (CONFIG_SOC_SERIES_STM32G0X) +#endif +#if defined (CONFIG_SOC_SERIES_STM32L0X) || \ + defined (CONFIG_SOC_SERIES_STM32G0X) case STM32_CLOCK_BUS_IOP: LL_IOP_GRP1_DisableClock(pclken->enr); break; -#endif /* CONFIG_SOC_SERIES_STM32L0X || CONFIG_SOC_SERIES_STM32G0X */ +#endif default: return -ENOTSUP; } @@ -200,19 +225,22 @@ static int stm32_clock_control_get_subsys_rate(const struct device *clock, uint32_t ahb_clock = SystemCoreClock; uint32_t apb1_clock = get_bus_clock(ahb_clock, CONFIG_CLOCK_STM32_APB1_PRESCALER); -#if !defined (CONFIG_SOC_SERIES_STM32F0X) && !defined (CONFIG_SOC_SERIES_STM32G0X) +#if !defined (CONFIG_SOC_SERIES_STM32F0X) && \ + !defined (CONFIG_SOC_SERIES_STM32G0X) uint32_t apb2_clock = get_bus_clock(ahb_clock, CONFIG_CLOCK_STM32_APB2_PRESCALER); -#endif /* CONFIG_SOC_SERIES_STM32F0X && CONFIG_SOC_SERIES_STM32G0X */ +#endif ARG_UNUSED(clock); switch (pclken->bus) { case STM32_CLOCK_BUS_AHB1: case STM32_CLOCK_BUS_AHB2: -#if defined (CONFIG_SOC_SERIES_STM32L0X) || defined (CONFIG_SOC_SERIES_STM32G0X) + case STM32_CLOCK_BUS_AHB3: +#if defined (CONFIG_SOC_SERIES_STM32L0X) || \ + defined (CONFIG_SOC_SERIES_STM32G0X) case STM32_CLOCK_BUS_IOP: -#endif /* (CONFIG_SOC_SERIES_STM32L0X) || defined (CONFIG_SOC_SERIES_STM32G0X) */ +#endif *rate = ahb_clock; break; case STM32_CLOCK_BUS_APB1: @@ -232,11 +260,12 @@ static int stm32_clock_control_get_subsys_rate(const struct device *clock, #endif /* CONFIG_SOC_SERIES_STM32G0X */ *rate = apb1_clock; break; -#if !defined (CONFIG_SOC_SERIES_STM32F0X) && !defined (CONFIG_SOC_SERIES_STM32G0X) +#if !defined (CONFIG_SOC_SERIES_STM32F0X) && \ + !defined (CONFIG_SOC_SERIES_STM32G0X) case STM32_CLOCK_BUS_APB2: *rate = apb2_clock; break; -#endif /* CONFIG_SOC_SERIES_STM32F0X && CONFIG_SOC_SERIES_STM32G0X */ +#endif default: return -ENOTSUP; } @@ -450,9 +479,10 @@ static int stm32_clock_control_init(const struct device *dev) /* Set APB1 & APB2 prescaler*/ LL_RCC_SetAPB1Prescaler(s_ClkInitStruct.APB1CLKDivider); -#if !defined (CONFIG_SOC_SERIES_STM32F0X) && !defined (CONFIG_SOC_SERIES_STM32G0X) +#if !defined (CONFIG_SOC_SERIES_STM32F0X) && \ + !defined (CONFIG_SOC_SERIES_STM32G0X) LL_RCC_SetAPB2Prescaler(s_ClkInitStruct.APB2CLKDivider); -#endif /* CONFIG_SOC_SERIES_STM32F0X && CONFIG_SOC_SERIES_STM32G0X */ +#endif /* If freq not increased, set flash latency after all clock setting */ if (new_hclk_freq <= old_hclk_freq) { @@ -543,7 +573,8 @@ static int stm32_clock_control_init(const struct device *dev) /* Set APB1 & APB2 prescaler*/ LL_RCC_SetAPB1Prescaler(s_ClkInitStruct.APB1CLKDivider); -#if !defined (CONFIG_SOC_SERIES_STM32F0X) && !defined (CONFIG_SOC_SERIES_STM32G0X) +#if !defined (CONFIG_SOC_SERIES_STM32F0X) && \ + !defined (CONFIG_SOC_SERIES_STM32G0X) LL_RCC_SetAPB2Prescaler(s_ClkInitStruct.APB2CLKDivider); #endif /* CONFIG_SOC_SERIES_STM32F0X && CONFIG_SOC_SERIES_STM32G0X */ diff --git a/drivers/dma/dma_stm32.c b/drivers/dma/dma_stm32.c index c1635d080e97f..3344c5a5f02e2 100644 --- a/drivers/dma/dma_stm32.c +++ b/drivers/dma/dma_stm32.c @@ -15,6 +15,7 @@ #include #include +#include #include LOG_MODULE_REGISTER(dma_stm32, CONFIG_DMA_LOG_LEVEL); @@ -101,14 +102,21 @@ static void dma_stm32_irq_handler(const struct device *dev, uint32_t id) } /* the dma stream id is in range from STREAM_OFFSET.. */ - if (dma_stm32_is_tc_active(dma, id)) { - dma_stm32_clear_tc(dma, id); + if (dma_stm32_is_ht_active(dma, id)) { + /* Let HAL DMA handle flags on its own */ + if (!stream->hal_override) { + dma_stm32_clear_ht(dma, id); + } + stream->dma_callback(dev, stream->user_data, callback_arg, 0); + } else if (dma_stm32_is_tc_active(dma, id)) { #ifdef CONFIG_DMAMUX_STM32 stream->busy = false; #endif + /* Let HAL DMA handle flags on its own */ + if (!stream->hal_override) { + dma_stm32_clear_tc(dma, id); + } stream->dma_callback(dev, stream->user_data, callback_arg, 0); - } else if (dma_stm32_is_ht_active(dma, id)) { - dma_stm32_clear_ht(dma, id); } else if (stm32_dma_is_unexpected_irq_happened(dma, id)) { LOG_ERR("Unexpected irq happened."); stream->dma_callback(dev, stream->user_data, @@ -255,6 +263,19 @@ DMA_STM32_EXPORT_API int dma_stm32_configure(const struct device *dev, /* give channel from index 0 */ id = id - STREAM_OFFSET; + /* Check potential DMA override */ + if (config->linked_channel == STM32_DMA_HAL_OVERRIDE) { + /* DMA channel is overridden by HAL DMA + * Retain that the channel is busy and proceed to the minimal + * configuration to properly route the IRQ + */ + stream->busy = true; + stream->hal_override = true; + stream->dma_callback = config->dma_callback; + stream->user_data = config->user_data; + return 0; + } + if (id >= dev_config->max_streams) { LOG_ERR("cannot configure the dma stream %d.", id); return -EINVAL; diff --git a/drivers/dma/dma_stm32.h b/drivers/dma/dma_stm32.h index bc65739f3326b..aef91b2c0cbc3 100644 --- a/drivers/dma/dma_stm32.h +++ b/drivers/dma/dma_stm32.h @@ -21,6 +21,7 @@ struct dma_stm32_stream { int mux_channel; /* stores the dmamux channel */ #endif /* CONFIG_DMAMUX_STM32 */ bool source_periph; + bool hal_override; volatile bool busy; uint32_t src_size; uint32_t dst_size; diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 4e6fee8554d9e..310672ae75223 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -16,6 +16,7 @@ zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_SAM flash_sam.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_NIOS2_QSPI soc_flash_nios2_qspi.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_GECKO flash_gecko.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_RV32M1 soc_flash_rv32m1.c) +zephyr_library_sources_ifdef(CONFIG_FLASH_STM32_QSPI flash_stm32_qspi.c) if(CONFIG_SOC_FLASH_STM32) if(CONFIG_SOC_SERIES_STM32H7X) diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index 03c52b937a135..ddb13e0cf3fe5 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -72,6 +72,8 @@ source "drivers/flash/Kconfig.nor" source "drivers/flash/Kconfig.stm32" +source "drivers/flash/Kconfig.stm32_qspi" + source "drivers/flash/Kconfig.sam0" source "drivers/flash/Kconfig.sam" diff --git a/drivers/flash/Kconfig.stm32_qspi b/drivers/flash/Kconfig.stm32_qspi new file mode 100644 index 0000000000000..892b9758f0425 --- /dev/null +++ b/drivers/flash/Kconfig.stm32_qspi @@ -0,0 +1,22 @@ +# STM32 Quad SPI flash driver configuration options + +# Copyright (c) 2020 Piotr Mienkowski +# Copyright (c) 2020 Linaro Limited +# SPDX-License-Identifier: Apache-2.0 + +DT_COMPAT_ST_STM32_QSPI_NOR := st,stm32-qspi-nor +DT_STM32_QUADSPI_HAS_DMA := $(dt_node_has_prop,quadspi,dmas) + +config FLASH_STM32_QSPI + bool "STM32 Quad SPI Flash driver" + depends on SOC_FAMILY_STM32 + default $(dt_compat_enabled,$(DT_COMPAT_ST_STM32_QSPI_NOR)) + select USE_STM32_HAL_QSPI + select FLASH_HAS_DRIVER_ENABLED + select FLASH_JESD216 + select FLASH_PAGE_LAYOUT + select FLASH_HAS_PAGE_LAYOUT + select DMA if $(DT_STM32_QUADSPI_HAS_DMA) + select USE_STM32_HAL_DMA if $(DT_STM32_QUADSPI_HAS_DMA) + help + Enable QSPI-NOR support on the STM32 family of processors. diff --git a/drivers/flash/flash_stm32_qspi.c b/drivers/flash/flash_stm32_qspi.c new file mode 100644 index 0000000000000..4ed739ea44dc9 --- /dev/null +++ b/drivers/flash/flash_stm32_qspi.c @@ -0,0 +1,936 @@ +/* + * Copyright (c) 2020 Piotr Mienkowski + * Copyright (c) 2020 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT st_stm32_qspi_nor + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "spi_nor.h" +#include "jesd216.h" + +#include +LOG_MODULE_REGISTER(flash_stm32_qspi, CONFIG_FLASH_LOG_LEVEL); + +#define STM32_QSPI_FIFO_THRESHOLD 8 +#define STM32_QSPI_CLOCK_PRESCALER_MAX 255 + +#define STM32_QSPI_USE_DMA DT_NODE_HAS_PROP(DT_PARENT(DT_DRV_INST(0)), dmas) + +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_qspi_nor) + +uint32_t table_m_size[] = { + LL_DMA_MDATAALIGN_BYTE, + LL_DMA_MDATAALIGN_HALFWORD, + LL_DMA_MDATAALIGN_WORD, +}; + +uint32_t table_p_size[] = { + LL_DMA_PDATAALIGN_BYTE, + LL_DMA_PDATAALIGN_HALFWORD, + LL_DMA_PDATAALIGN_WORD, +}; + +typedef void (*irq_config_func_t)(const struct device *dev); + + +struct stream { + const char *name; + DMA_TypeDef *reg; + const struct device *dev; + uint32_t channel; + struct dma_config cfg; +}; + +struct flash_stm32_qspi_config { + QUADSPI_TypeDef *regs; + struct stm32_pclken pclken; + irq_config_func_t irq_config; + size_t flash_size; + uint32_t max_frequency; + const struct soc_gpio_pinctrl *pinctrl_list; + size_t pinctrl_list_size; +}; + +struct flash_stm32_qspi_data { + QSPI_HandleTypeDef hqspi; + struct k_sem sem; + struct k_sem sync; +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + struct flash_pages_layout layout; +#endif + struct jesd216_erase_type erase_types[JESD216_NUM_ERASE_TYPES]; + /* Number of bytes per page */ + uint16_t page_size; + bool write_protection; + int cmd_status; + struct stream dma; +}; + +#define DEV_NAME(dev) ((dev)->name) +#define DEV_CFG(dev) \ + (const struct flash_stm32_qspi_config * const)(dev->config) +#define DEV_DATA(dev) \ + (struct flash_stm32_qspi_data * const)(dev->data) + +static inline void qspi_lock_thread(const struct device *dev) +{ + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + + k_sem_take(&dev_data->sem, K_FOREVER); +} + +static inline void qspi_unlock_thread(const struct device *dev) +{ + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + + k_sem_give(&dev_data->sem); +} + +/* + * Send a command over QSPI bus. + */ +static int qspi_send_cmd(const struct device *dev, QSPI_CommandTypeDef *cmd) +{ + const struct flash_stm32_qspi_config *dev_cfg = DEV_CFG(dev); + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + HAL_StatusTypeDef hal_ret; + + ARG_UNUSED(dev_cfg); + + LOG_DBG("Instruction 0x%x", cmd->Instruction); + + dev_data->cmd_status = 0; + + hal_ret = HAL_QSPI_Command_IT(&dev_data->hqspi, cmd); + if (hal_ret != HAL_OK) { + LOG_ERR("%d: Failed to send QSPI instruction", hal_ret); + return -EIO; + } + LOG_DBG("CCR 0x%x", dev_cfg->regs->CCR); + + k_sem_take(&dev_data->sync, K_FOREVER); + + return dev_data->cmd_status; +} + +/* + * Perform a read access over QSPI bus. + */ +static int qspi_read_access(const struct device *dev, QSPI_CommandTypeDef *cmd, + uint8_t *data, size_t size) +{ + const struct flash_stm32_qspi_config *dev_cfg = DEV_CFG(dev); + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + HAL_StatusTypeDef hal_ret; + + ARG_UNUSED(dev_cfg); + + cmd->NbData = size; + + dev_data->cmd_status = 0; + + hal_ret = HAL_QSPI_Command_IT(&dev_data->hqspi, cmd); + if (hal_ret != HAL_OK) { + LOG_ERR("%d: Failed to send QSPI instruction", hal_ret); + return -EIO; + } + +#if STM32_QSPI_USE_DMA + hal_ret = HAL_QSPI_Receive_DMA(&dev_data->hqspi, data); +#else + hal_ret = HAL_QSPI_Receive_IT(&dev_data->hqspi, data); +#endif + if (hal_ret != HAL_OK) { + LOG_ERR("%d: Failed to read data", hal_ret); + return -EIO; + } + + k_sem_take(&dev_data->sync, K_FOREVER); + + return dev_data->cmd_status; +} + +/* + * Perform a write access over QSPI bus. + */ +static int qspi_write_access(const struct device *dev, QSPI_CommandTypeDef *cmd, + const uint8_t *data, size_t size) +{ + const struct flash_stm32_qspi_config *dev_cfg = DEV_CFG(dev); + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + HAL_StatusTypeDef hal_ret; + + ARG_UNUSED(dev_cfg); + + LOG_DBG("Instruction 0x%x", cmd->Instruction); + + cmd->NbData = size; + + dev_data->cmd_status = 0; + + hal_ret = HAL_QSPI_Command_IT(&dev_data->hqspi, cmd); + if (hal_ret != HAL_OK) { + LOG_ERR("%d: Failed to send QSPI instruction", hal_ret); + return -EIO; + } + +#if STM32_QSPI_USE_DMA + hal_ret = HAL_QSPI_Transmit_DMA(&dev_data->hqspi, (uint8_t *)data); +#else + hal_ret = HAL_QSPI_Transmit_IT(&dev_data->hqspi, (uint8_t *)data); +#endif + if (hal_ret != HAL_OK) { + LOG_ERR("%d: Failed to read data", hal_ret); + return -EIO; + } + LOG_DBG("CCR 0x%x", dev_cfg->regs->CCR); + + k_sem_take(&dev_data->sync, K_FOREVER); + + return dev_data->cmd_status; +} + +/* + * Read Serial Flash Discovery Parameter + */ +static int qspi_read_sfdp(const struct device *dev, off_t addr, uint8_t *data, + size_t size) +{ + QSPI_CommandTypeDef cmd = { + .Instruction = JESD216_CMD_READ_SFDP, + .Address = addr, + .AddressSize = QSPI_ADDRESS_24_BITS, + .DummyCycles = 8, + .InstructionMode = QSPI_INSTRUCTION_1_LINE, + .AddressMode = QSPI_ADDRESS_1_LINE, + .DataMode = QSPI_DATA_1_LINE, + }; + + return qspi_read_access(dev, &cmd, data, size); +} + +static bool qspi_address_is_valid(const struct device *dev, off_t addr, + size_t size) +{ + const struct flash_stm32_qspi_config *dev_cfg = DEV_CFG(dev); + size_t flash_size = dev_cfg->flash_size; + + return (addr >= 0) && ((uint64_t)addr + (uint64_t)size <= flash_size); +} + +static int flash_stm32_qspi_read(const struct device *dev, off_t addr, + void *data, size_t size) +{ + int ret; + + if (!qspi_address_is_valid(dev, addr, size)) { + LOG_DBG("Error: address or size exceeds expected values: " + "addr 0x%lx, size %zu", (long)addr, size); + return -EINVAL; + } + + QSPI_CommandTypeDef cmd = { + .Instruction = SPI_NOR_CMD_READ, + .Address = addr, + .AddressSize = QSPI_ADDRESS_24_BITS, + .InstructionMode = QSPI_INSTRUCTION_1_LINE, + .AddressMode = QSPI_ADDRESS_1_LINE, + .DataMode = QSPI_DATA_1_LINE, + }; + + qspi_lock_thread(dev); + + ret = qspi_read_access(dev, &cmd, data, size); + + qspi_unlock_thread(dev); + + return ret; +} + +static int qspi_wait_until_ready(const struct device *dev) +{ + uint8_t reg; + int ret; + + QSPI_CommandTypeDef cmd = { + .Instruction = SPI_NOR_CMD_RDSR, + .InstructionMode = QSPI_INSTRUCTION_1_LINE, + .DataMode = QSPI_DATA_1_LINE, + }; + + do { + ret = qspi_read_access(dev, &cmd, ®, sizeof(reg)); + } while (!ret && (reg & SPI_NOR_WIP_BIT)); + + return ret; +} + +static int flash_stm32_qspi_write(const struct device *dev, off_t addr, + const void *data, size_t size) +{ + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + int ret = 0; + + if (dev_data->write_protection) { + return -EACCES; + } + + if (!qspi_address_is_valid(dev, addr, size)) { + LOG_DBG("Error: address or size exceeds expected values: " + "addr 0x%lx, size %zu", (long)addr, size); + return -EINVAL; + } + + QSPI_CommandTypeDef cmd_write_en = { + .Instruction = SPI_NOR_CMD_WREN, + .InstructionMode = QSPI_INSTRUCTION_1_LINE, + }; + + QSPI_CommandTypeDef cmd_pp = { + .Instruction = SPI_NOR_CMD_PP, + .AddressSize = QSPI_ADDRESS_24_BITS, + .InstructionMode = QSPI_INSTRUCTION_1_LINE, + .AddressMode = QSPI_ADDRESS_1_LINE, + .DataMode = QSPI_DATA_1_LINE, + }; + + qspi_lock_thread(dev); + + while (size > 0) { + size_t to_write = size; + + /* Don't write more than a page. */ + if (to_write >= SPI_NOR_PAGE_SIZE) { + to_write = SPI_NOR_PAGE_SIZE; + } + + /* Don't write across a page boundary */ + if (((addr + to_write - 1U) / SPI_NOR_PAGE_SIZE) + != (addr / SPI_NOR_PAGE_SIZE)) { + to_write = SPI_NOR_PAGE_SIZE - + (addr % SPI_NOR_PAGE_SIZE); + } + + ret = qspi_send_cmd(dev, &cmd_write_en); + if (ret != 0) { + break; + } + + cmd_pp.Address = addr; + ret = qspi_write_access(dev, &cmd_pp, data, to_write); + if (ret != 0) { + break; + } + + size -= to_write; + data = (const uint8_t *)data + to_write; + addr += to_write; + + ret = qspi_wait_until_ready(dev); + if (ret != 0) { + break; + } + } + + qspi_unlock_thread(dev); + + return ret; +} + +static int flash_stm32_qspi_erase(const struct device *dev, off_t addr, + size_t size) +{ + const struct flash_stm32_qspi_config *dev_cfg = DEV_CFG(dev); + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + int ret = 0; + + if (dev_data->write_protection) { + return -EACCES; + } + + if (!qspi_address_is_valid(dev, addr, size)) { + LOG_DBG("Error: address or size exceeds expected values: " + "addr 0x%lx, size %zu", (long)addr, size); + return -EINVAL; + } + + QSPI_CommandTypeDef cmd_write_en = { + .Instruction = SPI_NOR_CMD_WREN, + .InstructionMode = QSPI_INSTRUCTION_1_LINE, + }; + + QSPI_CommandTypeDef cmd_erase = { + .Instruction = 0, + .AddressSize = QSPI_ADDRESS_24_BITS, + .InstructionMode = QSPI_INSTRUCTION_1_LINE, + .AddressMode = QSPI_ADDRESS_1_LINE, + }; + + qspi_lock_thread(dev); + + while ((size > 0) && (ret == 0)) { + cmd_erase.Address = addr; + qspi_send_cmd(dev, &cmd_write_en); + + if (size == dev_cfg->flash_size) { + /* chip erase */ + cmd_erase.Instruction = SPI_NOR_CMD_CE; + cmd_erase.AddressMode = QSPI_ADDRESS_NONE; + qspi_send_cmd(dev, &cmd_erase); + size -= dev_cfg->flash_size; + } else { + const struct jesd216_erase_type *erase_types = + dev_data->erase_types; + const struct jesd216_erase_type *bet = NULL; + + for (uint8_t ei = 0; + ei < JESD216_NUM_ERASE_TYPES; ++ei) { + const struct jesd216_erase_type *etp = + &erase_types[ei]; + + if ((etp->exp != 0) + && SPI_NOR_IS_ALIGNED(addr, etp->exp) + && SPI_NOR_IS_ALIGNED(size, etp->exp) + && ((bet == NULL) + || (etp->exp > bet->exp))) { + bet = etp; + cmd_erase.Instruction = bet->cmd; + } + } + if (bet != NULL) { + qspi_send_cmd(dev, &cmd_erase); + addr += BIT(bet->exp); + size -= BIT(bet->exp); + } else { + LOG_ERR("Can't erase %zu at 0x%lx", + size, (long)addr); + ret = -EINVAL; + } + } + qspi_wait_until_ready(dev); + } + + qspi_unlock_thread(dev); + + return ret; +} + +static int flash_stm32_qspi_write_protection_set(const struct device *dev, + bool write_protect) +{ + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + + dev_data->write_protection = write_protect; + + return 0; +} + +static const struct flash_parameters flash_stm32_qspi_parameters = { + .write_block_size = 1, + .erase_value = 0xff +}; + +static const struct flash_parameters * +flash_stm32_qspi_get_parameters(const struct device *dev) +{ + ARG_UNUSED(dev); + + return &flash_stm32_qspi_parameters; +} + +static void flash_stm32_qspi_isr(const struct device *dev) +{ + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + + HAL_QSPI_IRQHandler(&dev_data->hqspi); +} + +/* This function is executed in the interrupt context */ +#if STM32_QSPI_USE_DMA +static void qspi_dma_callback(const struct device *dev, void *arg, + uint32_t channel, int status) +{ + DMA_HandleTypeDef *hdma = arg; + + if (status != 0) { + LOG_ERR("DMA callback error with channel %d.", channel); + + } + + HAL_DMA_IRQHandler(hdma); +} +#endif + +__weak HAL_StatusTypeDef HAL_DMA_Abort_IT(DMA_HandleTypeDef *hdma) +{ + return HAL_OK; +} + +/* + * Transfer Error callback. + */ +void HAL_QSPI_ErrorCallback(QSPI_HandleTypeDef *hqspi) +{ + struct flash_stm32_qspi_data *dev_data = + CONTAINER_OF(hqspi, struct flash_stm32_qspi_data, hqspi); + + LOG_DBG("Enter"); + + dev_data->cmd_status = -EIO; + + k_sem_give(&dev_data->sync); +} + +/* + * Command completed callback. + */ +void HAL_QSPI_CmdCpltCallback(QSPI_HandleTypeDef *hqspi) +{ + struct flash_stm32_qspi_data *dev_data = + CONTAINER_OF(hqspi, struct flash_stm32_qspi_data, hqspi); + + k_sem_give(&dev_data->sync); +} + +/* + * Rx Transfer completed callback. + */ +void HAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef *hqspi) +{ + struct flash_stm32_qspi_data *dev_data = + CONTAINER_OF(hqspi, struct flash_stm32_qspi_data, hqspi); + + k_sem_give(&dev_data->sync); +} + +/* + * Tx Transfer completed callback. + */ +void HAL_QSPI_TxCpltCallback(QSPI_HandleTypeDef *hqspi) +{ + struct flash_stm32_qspi_data *dev_data = + CONTAINER_OF(hqspi, struct flash_stm32_qspi_data, hqspi); + + k_sem_give(&dev_data->sync); +} + +/* + * Status Match callback. + */ +void HAL_QSPI_StatusMatchCallback(QSPI_HandleTypeDef *hqspi) +{ + struct flash_stm32_qspi_data *dev_data = + CONTAINER_OF(hqspi, struct flash_stm32_qspi_data, hqspi); + + k_sem_give(&dev_data->sync); +} + +/* + * Timeout callback. + */ +void HAL_QSPI_TimeOutCallback(QSPI_HandleTypeDef *hqspi) +{ + struct flash_stm32_qspi_data *dev_data = + CONTAINER_OF(hqspi, struct flash_stm32_qspi_data, hqspi); + + LOG_DBG("Enter"); + + dev_data->cmd_status = -EIO; + + k_sem_give(&dev_data->sync); +} + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) +static void flash_stm32_qspi_pages_layout(const struct device *dev, + const struct flash_pages_layout **layout, + size_t *layout_size) +{ + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + + *layout = &dev_data->layout; + *layout_size = 1; +} +#endif + +static const struct flash_driver_api flash_stm32_qspi_driver_api = { + .read = flash_stm32_qspi_read, + .write = flash_stm32_qspi_write, + .erase = flash_stm32_qspi_erase, + .write_protection = flash_stm32_qspi_write_protection_set, + .get_parameters = flash_stm32_qspi_get_parameters, +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + .page_layout = flash_stm32_qspi_pages_layout, +#endif +}; + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) +static int setup_pages_layout(const struct device *dev) +{ + const struct flash_stm32_qspi_config *dev_cfg = DEV_CFG(dev); + struct flash_stm32_qspi_data *data = DEV_DATA(dev); + const size_t flash_size = dev_cfg->flash_size; + uint32_t layout_page_size = data->page_size; + uint8_t exp = 0; + int rv = 0; + + /* Find the smallest erase size. */ + for (size_t i = 0; i < ARRAY_SIZE(data->erase_types); ++i) { + const struct jesd216_erase_type *etp = &data->erase_types[i]; + + if ((etp->cmd != 0) + && ((exp == 0) || (etp->exp < exp))) { + exp = etp->exp; + } + } + + if (exp == 0) { + return -ENOTSUP; + } + + uint32_t erase_size = BIT(exp); + + /* We need layout page size to be compatible with erase size */ + if ((layout_page_size % erase_size) != 0) { + LOG_DBG("layout page %u not compatible with erase size %u", + layout_page_size, erase_size); + LOG_DBG("erase size will be used as layout page size"); + layout_page_size = erase_size; + } + + /* Warn but accept layout page sizes that leave inaccessible + * space. + */ + if ((flash_size % layout_page_size) != 0) { + LOG_INF("layout page %u wastes space with device size %zu", + layout_page_size, flash_size); + } + + data->layout.pages_size = layout_page_size; + data->layout.pages_count = flash_size / layout_page_size; + LOG_DBG("layout %u x %u By pages", data->layout.pages_count, + data->layout.pages_size); + + return rv; +} +#endif /* CONFIG_FLASH_PAGE_LAYOUT */ + +static int spi_nor_process_bfp(const struct device *dev, + const struct jesd216_param_header *php, + const struct jesd216_bfp *bfp) +{ + const struct flash_stm32_qspi_config *dev_cfg = DEV_CFG(dev); + struct flash_stm32_qspi_data *data = DEV_DATA(dev); + struct jesd216_erase_type *etp = data->erase_types; + const size_t flash_size = jesd216_bfp_density(bfp) / 8U; + + if (flash_size != dev_cfg->flash_size) { + LOG_ERR("Unexpected flash size: %u", flash_size); + } + + LOG_INF("%s: %u MiBy flash", dev->name, (uint32_t)(flash_size >> 20)); + + /* Copy over the erase types, preserving their order. (The + * Sector Map Parameter table references them by index.) + */ + memset(data->erase_types, 0, sizeof(data->erase_types)); + for (uint8_t ti = 1; ti <= ARRAY_SIZE(data->erase_types); ++ti) { + if (jesd216_bfp_erase(bfp, ti, etp) == 0) { + LOG_DBG("Erase %u with %02x", + (uint32_t)BIT(etp->exp), etp->cmd); + } + ++etp; + } + + data->page_size = jesd216_bfp_page_size(php, bfp); + + LOG_DBG("Page size %u bytes", data->page_size); + LOG_DBG("Flash size %u bytes", flash_size); + return 0; +} + +static int flash_stm32_qspi_init(const struct device *dev) +{ + const struct flash_stm32_qspi_config *dev_cfg = DEV_CFG(dev); + struct flash_stm32_qspi_data *dev_data = DEV_DATA(dev); + uint32_t ahb_clock_freq; + uint32_t prescaler = 0; + int ret; + + /* Signals configuration */ + ret = stm32_dt_pinctrl_configure(dev_cfg->pinctrl_list, + dev_cfg->pinctrl_list_size, + (uint32_t)dev_cfg->regs); + if (ret < 0) { + LOG_ERR("QSPI pinctrl setup failed (%d)", ret); + return ret; + } + +#if STM32_QSPI_USE_DMA + /* + * DMA configuration + * Due to use of QSPI 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. + */ + struct dma_config dma_cfg = dev_data->dma.cfg; + static DMA_HandleTypeDef hdma; + + if (dev_data->dma.name != NULL) { + dev_data->dma.dev = device_get_binding(dev_data->dma.name); + if (!dev_data->dma.dev) { + LOG_ERR("%s device not found", dev_data->dma.name); + return -ENODEV; + } + } else { + return -EINVAL; + } + + /* Proceed to the minimum Zephyr DMA driver init */ + dma_cfg.user_data = &hdma; + /* HACK: This field is used to inform driver that it is overridden */ + dma_cfg.linked_channel = STM32_DMA_HAL_OVERRIDE; + ret = dma_config(dev_data->dma.dev, dev_data->dma.channel, &dma_cfg); + if (ret != 0) { + return ret; + } + + /* Proceed to the HAL DMA driver init */ + if (dma_cfg.source_data_size != dma_cfg.dest_data_size) { + LOG_ERR("Source and destination data sizes not aligned"); + return -EINVAL; + } + + int index = find_lsb_set(dma_cfg.source_data_size) - 1; + + 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; + hdma.Init.Mode = DMA_NORMAL; + hdma.Init.Priority = dma_cfg.channel_priority; +#ifdef CONFIG_DMA_STM32_V1 + /* TODO: Not tested in this configuration */ + hdma.Init.Channel = dma_cfg.dma_slot; + hdma.Instance = __LL_DMA_GET_STREAM_INSTANCE(dev_data->dma.reg, + dev_data->dma.channel); +#else + hdma.Init.Request = dma_cfg.dma_slot; +#ifdef CONFIG_DMAMUX_STM32 + /* HAL expects a valid DMA channel (not DAMMUX) */ + /* TODO: Get DMA instance from DT */ + hdma.Instance = __LL_DMA_GET_CHANNEL_INSTANCE(DMA1, + dev_data->dma.channel+1); +#else + hdma.Instance = __LL_DMA_GET_CHANNEL_INSTANCE(dev_data->dma.reg, + dev_data->dma.channel-1); +#endif +#endif /* CONFIG_DMA_STM32_V1 */ + + /* Initialize DMA HAL */ + __HAL_LINKDMA(&dev_data->hqspi, hdma, hdma); + HAL_DMA_Init(&hdma); + +#endif /* STM32_QSPI_USE_DMA */ + + /* Clock configuration */ + __ASSERT_NO_MSG(device_get_binding(STM32_CLOCK_CONTROL_NAME)); + + if (clock_control_on(device_get_binding(STM32_CLOCK_CONTROL_NAME), + (clock_control_subsys_t) &dev_cfg->pclken) != 0) { + LOG_DBG("Could not enable QSPI clock"); + return -EIO; + } + + if (clock_control_get_rate(device_get_binding(STM32_CLOCK_CONTROL_NAME), + (clock_control_subsys_t) &dev_cfg->pclken, + &ahb_clock_freq) < 0) { + LOG_DBG("Failed to get AHB clock frequency"); + return -EIO; + } + + for (; prescaler <= STM32_QSPI_CLOCK_PRESCALER_MAX; prescaler++) { + uint32_t clk = ahb_clock_freq / (prescaler + 1); + + if (clk <= dev_cfg->max_frequency) { + break; + } + } + __ASSERT_NO_MSG(prescaler <= STM32_QSPI_CLOCK_PRESCALER_MAX); + /* Initialize QSPI HAL */ + dev_data->hqspi.Init.ClockPrescaler = prescaler; + dev_data->hqspi.Init.FlashSize = find_lsb_set(dev_cfg->flash_size); + + HAL_QSPI_Init(&dev_data->hqspi); + + /* Initialize semaphores */ + k_sem_init(&dev_data->sem, 1, 1); + k_sem_init(&dev_data->sync, 0, 1); + + /* Run IRQ init */ + dev_cfg->irq_config(dev); + + /* Run NOR init */ + const uint8_t decl_nph = 2; + union { + /* We only process BFP so use one parameter block */ + uint8_t raw[JESD216_SFDP_SIZE(decl_nph)]; + struct jesd216_sfdp_header sfdp; + } u; + const struct jesd216_sfdp_header *hp = &u.sfdp; + + ret = qspi_read_sfdp(dev, 0, u.raw, sizeof(u.raw)); + if (ret != 0) { + LOG_ERR("SFDP read failed: %d", ret); + return ret; + } + + uint32_t magic = jesd216_sfdp_magic(hp); + + if (magic != JESD216_SFDP_MAGIC) { + LOG_ERR("SFDP magic %08x invalid", magic); + return -EINVAL; + } + + LOG_INF("%s: SFDP v %u.%u AP %x with %u PH", dev->name, + hp->rev_major, hp->rev_minor, hp->access, 1 + hp->nph); + + const struct jesd216_param_header *php = hp->phdr; + const struct jesd216_param_header *phpe = php + + MIN(decl_nph, 1 + hp->nph); + + while (php != phpe) { + uint16_t id = jesd216_param_id(php); + + LOG_INF("PH%u: %04x rev %u.%u: %u DW @ %x", + (php - hp->phdr), id, php->rev_major, php->rev_minor, + php->len_dw, jesd216_param_addr(php)); + + if (id == JESD216_SFDP_PARAM_ID_BFP) { + union { + uint32_t dw[MIN(php->len_dw, 20)]; + struct jesd216_bfp bfp; + } u; + const struct jesd216_bfp *bfp = &u.bfp; + + ret = qspi_read_sfdp(dev, jesd216_param_addr(php), + (uint8_t *)u.dw, sizeof(u.dw)); + if (ret == 0) { + ret = spi_nor_process_bfp(dev, php, bfp); + } + + if (ret != 0) { + LOG_ERR("SFDP BFP failed: %d", ret); + break; + } + } + ++php; + } + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + ret = setup_pages_layout(dev); + if (ret != 0) { + LOG_ERR("layout setup failed: %d", ret); + return -ENODEV; + } +#endif /* CONFIG_FLASH_PAGE_LAYOUT */ + + LOG_INF("Device %s initialized", DEV_NAME(dev)); + + return 0; +} + +#define DMA_CHANNEL_CONFIG(node, dir) \ + DT_DMAS_CELL_BY_NAME(node, dir, channel_config) + +#define QSPI_DMA_CHANNEL_INIT(node, dir) \ + .name = DT_DMAS_LABEL_BY_NAME(node, dir), \ + .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 = qspi_dma_callback, \ + }, \ + +#define QSPI_DMA_CHANNEL(node, dir) \ + .dma = { \ + COND_CODE_1(DT_DMAS_HAS_NAME(node, dir), \ + (QSPI_DMA_CHANNEL_INIT(node, dir)), \ + (NULL)) \ + }, + +#define QSPI_FLASH_MODULE(drv_id, flash_id) \ + (DT_DRV_INST(drv_id), qspi_nor_flash_##flash_id) + +static void flash_stm32_qspi_irq_config_func(const struct device *dev); + +static const struct soc_gpio_pinctrl qspi_pins[] = + ST_STM32_DT_PINCTRL(quadspi, 0); + +#define STM32_QSPI_NODE DT_PARENT(DT_DRV_INST(0)) + +static const struct flash_stm32_qspi_config flash_stm32_qspi_cfg = { + .regs = (QUADSPI_TypeDef *)DT_REG_ADDR(STM32_QSPI_NODE), + .pclken = { + .enr = DT_CLOCKS_CELL(STM32_QSPI_NODE, bits), + .bus = DT_CLOCKS_CELL(STM32_QSPI_NODE, bus) + }, + .irq_config = flash_stm32_qspi_irq_config_func, + .flash_size = DT_INST_PROP(0, size) / 8U, + .max_frequency = DT_INST_PROP(0, qspi_max_frequency), + .pinctrl_list = qspi_pins, + .pinctrl_list_size = ARRAY_SIZE(qspi_pins), +}; + +static struct flash_stm32_qspi_data flash_stm32_qspi_dev_data = { + .hqspi = { + .Instance = (QUADSPI_TypeDef *)DT_REG_ADDR(STM32_QSPI_NODE), + .Init = { + .FifoThreshold = STM32_QSPI_FIFO_THRESHOLD, + .SampleShifting = QSPI_SAMPLE_SHIFTING_NONE, + .ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE, + .ClockMode = QSPI_CLOCK_MODE_0, + }, + }, + QSPI_DMA_CHANNEL(STM32_QSPI_NODE, tx_rx) +}; + +DEVICE_DT_INST_DEFINE(0, &flash_stm32_qspi_init, device_pm_control_nop, + &flash_stm32_qspi_dev_data, &flash_stm32_qspi_cfg, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &flash_stm32_qspi_driver_api); + +static void flash_stm32_qspi_irq_config_func(const struct device *dev) +{ + IRQ_CONNECT(DT_IRQN(STM32_QSPI_NODE), DT_IRQ(STM32_QSPI_NODE, priority), + flash_stm32_qspi_isr, DEVICE_DT_INST_GET(0), 0); + irq_enable(DT_IRQN(STM32_QSPI_NODE)); +} + +#endif diff --git a/dts/arm/st/l4/stm32l4.dtsi b/dts/arm/st/l4/stm32l4.dtsi index b95c2f26696af..9bad0af50a8b1 100644 --- a/dts/arm/st/l4/stm32l4.dtsi +++ b/dts/arm/st/l4/stm32l4.dtsi @@ -171,6 +171,17 @@ label= "I2C_3"; }; + quadspi: quadspi@a0001000 { + compatible = "st,stm32-qspi"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xa0001000 0x400>; + interrupts = <71 0>; + clocks = <&rcc STM32_CLOCK_BUS_AHB3 0x00000100>; + status = "disabled"; + label = "QUADSPI"; + }; + spi1: spi@40013000 { compatible = "st,stm32-spi-fifo", "st,stm32-spi"; #address-cells = <1>; diff --git a/dts/arm/st/wb/stm32wb.dtsi b/dts/arm/st/wb/stm32wb.dtsi index cdf382e2d10b1..91cc2f64d6e32 100644 --- a/dts/arm/st/wb/stm32wb.dtsi +++ b/dts/arm/st/wb/stm32wb.dtsi @@ -352,6 +352,18 @@ status = "disabled"; label = "USB"; }; + + quadspi: quadspi@a0001000 { + compatible = "st,stm32-qspi"; + #address-cells = <0x1>; + #size-cells = <0x0>; + reg = <0xa0001000 0x400>; + interrupts = <0x32 0x0>; + clocks = <&rcc STM32_CLOCK_BUS_AHB3 0x100>; + status = "disabled"; + label = "QUADSPI"; + }; + }; usb_fs_phy: usbphy { diff --git a/dts/bindings/flash_controller/st,stm32-qspi-nor.yaml b/dts/bindings/flash_controller/st,stm32-qspi-nor.yaml new file mode 100644 index 0000000000000..1e8f5bd3dba67 --- /dev/null +++ b/dts/bindings/flash_controller/st,stm32-qspi-nor.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2020, Linaro limited +# SPDX-License-Identifier: Apache-2.0 + +description: | + STM32 QSPI Flash controller supporting the JEDEC CFI interface + + Representation of a serial flash on a quadspi bus: + + mx25r6435f: qspi-nor-flash@0 { + compatible = "st,stm32-qspi-nor"; + label = "MX25R6435F"; + reg = <0>; + qspi-max-frequency = <80000000>; + size = <0x4000000>; + status = "okay"; + }; + +compatible: "st,stm32-qspi-nor" + +include: ["flash-controller.yaml", "jedec,jesd216.yaml"] + +on-bus: qspi + +properties: + reg: + required: true + qspi-max-frequency: + type: int + required: true + description: Maximum clock frequency of device's QSPI interface in Hz + label: + required: true + size: + required: true + description: Flash Memory size in bits diff --git a/dts/bindings/qspi/st,stm32-qspi.yaml b/dts/bindings/qspi/st,stm32-qspi.yaml new file mode 100644 index 0000000000000..8281cfb9e8d1e --- /dev/null +++ b/dts/bindings/qspi/st,stm32-qspi.yaml @@ -0,0 +1,50 @@ +# Copyright (c) 2020, Linaro limited +# SPDX-License-Identifier: Apache-2.0 + +description: | + STM32 QSPI device representation. A stm32 quadspi node would typically + looks to this: + + &quadspi { + pinctrl-0 = <&quadspi_clk_pe10 &quadspi_ncs_pe11 + &quadspi_bk1_io0_pe12 &quadspi_bk1_io1_pe13 + &quadspi_bk1_io2_pe14 &quadspi_bk1_io3_pe15>; + + dmas = <&dma1 5 5 0x0000 0x03>; + dma-names = "tx_rx"; + + status = "okay"; + }; + +compatible: "st,stm32-qspi" + +include: base.yaml + +bus: qspi + +properties: + reg: + required: true + + interrupts: + required: true + + dmas: + description: | + Optional DMA channel specifier. If DMA should be used, specifier should + hold a phandle reference to the dma controller, the channel number, + the slot number, channel configuration and finally features. + + For example dmas for TX/RX on QSPI + dmas = <&dma1 5 5 0x0000 0x03>; + + dma-names: + description: | + DMA channel name. If DMA should be used, expected value is "tx_rx". + + For example + dma-names = "tx_rx"; + + pinctrl-0: + type: phandles + required: true diff --git a/include/drivers/dma/dma_stm32.h b/include/drivers/dma/dma_stm32.h new file mode 100644 index 0000000000000..601f5d7fa796d --- /dev/null +++ b/include/drivers/dma/dma_stm32.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_DMA_STM32_H_ +#define ZEPHYR_INCLUDE_DRIVERS_DMA_STM32_H_ + +/* @brief linked_channel value to inform zephyr dma driver that + * DMA channel will be handled by HAL + */ +#define STM32_DMA_HAL_OVERRIDE 0x7F + +#endif /* ZEPHYR_INCLUDE_DRIVERS_DMA_STM32_H_ */ diff --git a/samples/drivers/spi_flash/sample.yaml b/samples/drivers/spi_flash/sample.yaml index f650b318a3d61..088e908ba018c 100644 --- a/samples/drivers/spi_flash/sample.yaml +++ b/samples/drivers/spi_flash/sample.yaml @@ -3,7 +3,7 @@ sample: tests: sample.drivers.spi.flash: tags: spi flash - filter: dt_compat_enabled("jedec,spi-nor") + filter: dt_compat_enabled("jedec,spi-nor") or dt_compat_enabled("st,stm32-qspi-nor") harness: console harness_config: type: multi_line diff --git a/samples/drivers/spi_flash/src/main.c b/samples/drivers/spi_flash/src/main.c index 2931d0f01bfb9..cf6c81dfd57c9 100644 --- a/samples/drivers/spi_flash/src/main.c +++ b/samples/drivers/spi_flash/src/main.c @@ -19,6 +19,9 @@ DT_NODE_HAS_STATUS(DT_INST(0, nordic_qspi_nor), okay) #define FLASH_DEVICE DT_LABEL(DT_INST(0, nordic_qspi_nor)) #define FLASH_NAME "JEDEC QSPI-NOR" +#elif DT_NODE_HAS_STATUS(DT_INST(0, st_stm32_qspi_nor), okay) +#define FLASH_DEVICE DT_LABEL(DT_INST(0, st_stm32_qspi_nor)) +#define FLASH_NAME "JEDEC QSPI-NOR" #else #error Unsupported flash driver #endif diff --git a/samples/subsys/fs/littlefs/sample.yaml b/samples/subsys/fs/littlefs/sample.yaml index fe40befc4da8e..eec5ff72eb8df 100644 --- a/samples/subsys/fs/littlefs/sample.yaml +++ b/samples/subsys/fs/littlefs/sample.yaml @@ -3,5 +3,5 @@ sample: tests: sample.filesystem.littlefs: build_only: true - platform_allow: nrf52840dk_nrf52840 particle_xenon + platform_allow: nrf52840dk_nrf52840 particle_xenon disco_l475_iot1 tags: filesystem diff --git a/scripts/kconfig/kconfigfunctions.py b/scripts/kconfig/kconfigfunctions.py index 17ba32dc5e79a..e4d5739cb2f07 100644 --- a/scripts/kconfig/kconfigfunctions.py +++ b/scripts/kconfig/kconfigfunctions.py @@ -324,6 +324,29 @@ def dt_node_has_bool_prop(kconf, _, path, prop): return "n" +def dt_node_has_prop(kconf, _, label, prop): + """ + This function takes a 'label' and looks for an EDT node for that label. If + it finds an EDT node, it will look to see if that node has a property + by the name of 'prop'. If the 'prop' exists it will return "y" otherwise + we return "n". + """ + + if doc_mode or edt is None: + return "n" + + try: + node = edt.label2node.get(label) + except edtlib.EDTError: + return "n" + + if node is None: + return "n" + + if prop in node.props: + return "y" + + return "n" def dt_node_int_prop(kconf, name, path, prop): """ @@ -445,6 +468,7 @@ def shields_list_contains(kconf, _, shield): "dt_node_reg_size_int": (dt_node_reg, 1, 3), "dt_node_reg_size_hex": (dt_node_reg, 1, 3), "dt_node_has_bool_prop": (dt_node_has_bool_prop, 2, 2), + "dt_node_has_prop": (dt_node_has_prop, 2, 2), "dt_node_int_prop_int": (dt_node_int_prop, 2, 2), "dt_node_int_prop_hex": (dt_node_int_prop, 2, 2), "dt_nodelabel_has_compat": (dt_nodelabel_has_compat, 2, 2), diff --git a/west.yml b/west.yml index cb79a5b1b93c4..36271ef465474 100644 --- a/west.yml +++ b/west.yml @@ -75,7 +75,7 @@ manifest: revision: b52fdbf4b62439be9fab9bb4bae9690a42d2fb14 path: modules/hal/st - name: hal_stm32 - revision: 5a10f27be1b21c597ec5dc0bd8b90d2abe9fca69 + revision: cc8731dba4fd9c57d7fe8ea6149828b89c2bd635 path: modules/hal/stm32 - name: hal_ti revision: 277d70a65ab14d46bf1ec0935cf9bb28bbaa8ab9