| /* |
| * Copyright (c) 2020 Piotr Mienkowski |
| * Copyright (c) 2020 Linaro Limited |
| * Copyright (c) 2022 Georgij Cernysiov |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT st_stm32_qspi_nor |
| |
| #include <errno.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/toolchain.h> |
| #include <zephyr/arch/common/ffs.h> |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/sys/util.h> |
| #include <soc.h> |
| #include <string.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/clock_control/stm32_clock_control.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/flash.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/drivers/dma/dma_stm32.h> |
| #include <zephyr/drivers/gpio.h> |
| |
| #if DT_INST_NODE_HAS_PROP(0, spi_bus_width) && \ |
| DT_INST_PROP(0, spi_bus_width) == 4 |
| #define STM32_QSPI_USE_QUAD_IO 1 |
| #else |
| #define STM32_QSPI_USE_QUAD_IO 0 |
| #endif |
| |
| /* Get the base address of the flash from the DTS node */ |
| #define STM32_QSPI_BASE_ADDRESS DT_INST_REG_ADDR(0) |
| |
| #define STM32_QSPI_RESET_GPIO DT_INST_NODE_HAS_PROP(0, reset_gpios) |
| #define STM32_QSPI_RESET_CMD DT_INST_PROP(0, reset_cmd) |
| |
| #include <stm32_ll_dma.h> |
| |
| #include "spi_nor.h" |
| #include "jesd216.h" |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/irq.h> |
| 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_UNKNOWN_MODE (0xFF) |
| |
| #define STM32_QSPI_USE_DMA DT_NODE_HAS_PROP(DT_INST_PARENT(0), dmas) |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_qspi_nor) |
| |
| /* In dual-flash mode, total size is twice the size of one flash component */ |
| #define STM32_QSPI_DOUBLE_FLASH DT_PROP(DT_NODELABEL(quadspi), dual_flash) |
| |
| #if STM32_QSPI_USE_DMA |
| 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, |
| }; |
| |
| /* Lookup table to set dma priority from the DTS */ |
| static const uint32_t table_priority[] = { |
| DMA_PRIORITY_LOW, |
| DMA_PRIORITY_MEDIUM, |
| DMA_PRIORITY_HIGH, |
| DMA_PRIORITY_VERY_HIGH, |
| }; |
| #endif /* STM32_QSPI_USE_DMA */ |
| |
| typedef void (*irq_config_func_t)(const struct device *dev); |
| |
| struct stream { |
| 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 pinctrl_dev_config *pcfg; |
| #if STM32_QSPI_RESET_GPIO |
| const struct gpio_dt_spec reset; |
| #endif |
| #if DT_NODE_HAS_PROP(DT_INST(0, st_stm32_qspi_nor), jedec_id) |
| uint8_t jedec_id[DT_INST_PROP_LEN(0, jedec_id)]; |
| #endif /* jedec_id */ |
| }; |
| |
| 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; |
| enum jesd216_dw15_qer_type qer_type; |
| enum jesd216_mode_type mode; |
| int cmd_status; |
| struct stream dma; |
| uint8_t qspi_write_cmd; |
| uint8_t qspi_read_cmd; |
| uint8_t qspi_read_cmd_latency; |
| /* |
| * If set addressed operations should use 32-bit rather than |
| * 24-bit addresses. |
| */ |
| bool flag_access_32bit: 1; |
| }; |
| |
| static const QSPI_CommandTypeDef cmd_write_en = { |
| .Instruction = SPI_NOR_CMD_WREN, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE |
| }; |
| |
| static inline void qspi_lock_thread(const struct device *dev) |
| { |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| |
| 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; |
| |
| k_sem_give(&dev_data->sem); |
| } |
| |
| static inline void qspi_set_address_size(const struct device *dev, |
| QSPI_CommandTypeDef *cmd) |
| { |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| |
| if (dev_data->flag_access_32bit) { |
| cmd->AddressSize = QSPI_ADDRESS_32_BITS; |
| return; |
| } |
| |
| cmd->AddressSize = QSPI_ADDRESS_24_BITS; |
| } |
| |
| static inline int qspi_prepare_quad_read(const struct device *dev, |
| QSPI_CommandTypeDef *cmd) |
| { |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| |
| __ASSERT_NO_MSG(dev_data->mode == JESD216_MODE_114 || |
| dev_data->mode == JESD216_MODE_144); |
| |
| cmd->Instruction = dev_data->qspi_read_cmd; |
| cmd->AddressMode = ((dev_data->mode == JESD216_MODE_114) |
| ? QSPI_ADDRESS_1_LINE |
| : QSPI_ADDRESS_4_LINES); |
| cmd->DataMode = QSPI_DATA_4_LINES; |
| cmd->DummyCycles = dev_data->qspi_read_cmd_latency; |
| |
| return 0; |
| } |
| |
| static inline int qspi_prepare_quad_program(const struct device *dev, |
| QSPI_CommandTypeDef *cmd) |
| { |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| |
| __ASSERT_NO_MSG(dev_data->qspi_write_cmd == SPI_NOR_CMD_PP_1_1_4 || |
| dev_data->qspi_write_cmd == SPI_NOR_CMD_PP_1_4_4); |
| |
| cmd->Instruction = dev_data->qspi_write_cmd; |
| #if defined(CONFIG_USE_MICROCHIP_QSPI_FLASH_WITH_STM32) |
| /* Microchip qspi-NOR flash, does not follow the standard rules */ |
| if (cmd->Instruction == SPI_NOR_CMD_PP_1_1_4) { |
| cmd->AddressMode = QSPI_ADDRESS_4_LINES; |
| } |
| #else |
| cmd->AddressMode = ((cmd->Instruction == SPI_NOR_CMD_PP_1_1_4) |
| ? QSPI_ADDRESS_1_LINE |
| : QSPI_ADDRESS_4_LINES); |
| #endif /* CONFIG_USE_MICROCHIP_QSPI_FLASH_WITH_STM32 */ |
| cmd->DataMode = QSPI_DATA_4_LINES; |
| cmd->DummyCycles = 0; |
| |
| return 0; |
| } |
| |
| /* |
| * Send a command over QSPI bus. |
| */ |
| static int qspi_send_cmd(const struct device *dev, const QSPI_CommandTypeDef *cmd) |
| { |
| const struct flash_stm32_qspi_config *dev_cfg = dev->config; |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| 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, (QSPI_CommandTypeDef *)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->config; |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| 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->config; |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| 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; |
| } |
| |
| #if defined(CONFIG_FLASH_JESD216_API) |
| /* |
| * Read Serial Flash ID : |
| * perform a read access over SPI bus for read Identification (DataMode is already set) |
| * and compare to the jedec-id from the DTYS table exists |
| */ |
| static int qspi_read_jedec_id(const struct device *dev, uint8_t *id) |
| { |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| uint8_t data[JESD216_READ_ID_LEN]; |
| |
| QSPI_CommandTypeDef cmd = { |
| .Instruction = JESD216_CMD_READ_ID, |
| .AddressSize = QSPI_ADDRESS_NONE, |
| .DummyCycles = 8, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| .AddressMode = QSPI_ADDRESS_1_LINE, |
| .DataMode = QSPI_DATA_1_LINE, |
| .NbData = JESD216_READ_ID_LEN, |
| }; |
| |
| HAL_StatusTypeDef hal_ret; |
| |
| hal_ret = HAL_QSPI_Command_IT(&dev_data->hqspi, &cmd); |
| |
| if (hal_ret != HAL_OK) { |
| LOG_ERR("%d: Failed to send OSPI instruction", hal_ret); |
| return -EIO; |
| } |
| |
| hal_ret = HAL_QSPI_Receive(&dev_data->hqspi, data, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); |
| if (hal_ret != HAL_OK) { |
| LOG_ERR("%d: Failed to read data", hal_ret); |
| return -EIO; |
| } |
| |
| LOG_DBG("Read JESD216-ID"); |
| |
| dev_data->cmd_status = 0; |
| memcpy(id, data, JESD216_READ_ID_LEN); |
| |
| return 0; |
| } |
| #endif /* CONFIG_FLASH_JESD216_API */ |
| |
| static int qspi_write_unprotect(const struct device *dev) |
| { |
| int ret = 0; |
| QSPI_CommandTypeDef cmd_unprotect = { |
| .Instruction = SPI_NOR_CMD_ULBPR, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| }; |
| |
| if (IS_ENABLED(DT_INST_PROP(0, requires_ulbpr))) { |
| ret = qspi_send_cmd(dev, &cmd_write_en); |
| |
| if (ret != 0) { |
| return ret; |
| } |
| |
| ret = qspi_send_cmd(dev, &cmd_unprotect); |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * Read Serial Flash Discovery Parameter |
| */ |
| static int qspi_read_sfdp(const struct device *dev, off_t addr, void *data, |
| size_t size) |
| { |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| HAL_StatusTypeDef hal_ret; |
| |
| __ASSERT(data != NULL, "null destination"); |
| |
| LOG_INF("Reading SFDP"); |
| |
| 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, |
| .NbData = size, |
| }; |
| |
| hal_ret = HAL_QSPI_Command(&dev_data->hqspi, &cmd, |
| HAL_QSPI_TIMEOUT_DEFAULT_VALUE); |
| if (hal_ret != HAL_OK) { |
| LOG_ERR("%d: Failed to send SFDP instruction", hal_ret); |
| return -EIO; |
| } |
| |
| hal_ret = HAL_QSPI_Receive(&dev_data->hqspi, (uint8_t *)data, |
| HAL_QSPI_TIMEOUT_DEFAULT_VALUE); |
| if (hal_ret != HAL_OK) { |
| LOG_ERR("%d: Failed to read SFDP", hal_ret); |
| return -EIO; |
| } |
| |
| dev_data->cmd_status = 0; |
| |
| return 0; |
| } |
| |
| 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->config; |
| size_t flash_size = dev_cfg->flash_size; |
| |
| return (addr >= 0) && ((uint64_t)addr + (uint64_t)size <= flash_size); |
| } |
| |
| #ifdef CONFIG_STM32_MEMMAP |
| /* Must be called inside qspi_lock_thread(). */ |
| static int stm32_qspi_set_memory_mapped(const struct device *dev) |
| { |
| int ret; |
| HAL_StatusTypeDef hal_ret; |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| |
| QSPI_CommandTypeDef cmd = { |
| .Instruction = SPI_NOR_CMD_READ, |
| .Address = 0, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| .AddressMode = QSPI_ADDRESS_1_LINE, |
| .DataMode = QSPI_DATA_1_LINE, |
| }; |
| |
| qspi_set_address_size(dev, &cmd); |
| if (IS_ENABLED(STM32_QSPI_USE_QUAD_IO)) { |
| ret = qspi_prepare_quad_read(dev, &cmd); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| QSPI_MemoryMappedTypeDef mem_mapped = { |
| .TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE, |
| }; |
| |
| hal_ret = HAL_QSPI_MemoryMapped(&dev_data->hqspi, &cmd, &mem_mapped); |
| if (hal_ret != 0) { |
| LOG_ERR("%d: Failed to enable memory mapped", hal_ret); |
| return -EIO; |
| } |
| |
| LOG_DBG("MemoryMap mode enabled"); |
| return 0; |
| } |
| |
| static bool stm32_qspi_is_memory_mapped(const struct device *dev) |
| { |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| |
| return READ_BIT(dev_data->hqspi.Instance->CCR, QUADSPI_CCR_FMODE) == QUADSPI_CCR_FMODE; |
| } |
| |
| static int stm32_qspi_abort(const struct device *dev) |
| { |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| HAL_StatusTypeDef hal_ret; |
| |
| hal_ret = HAL_QSPI_Abort(&dev_data->hqspi); |
| if (hal_ret != HAL_OK) { |
| LOG_ERR("%d: QSPI abort failed", hal_ret); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static int flash_stm32_qspi_read(const struct device *dev, off_t addr, |
| void *data, size_t size) |
| { |
| 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; |
| } |
| |
| /* read non-zero size */ |
| if (size == 0) { |
| return 0; |
| } |
| |
| #ifdef CONFIG_STM32_MEMMAP |
| qspi_lock_thread(dev); |
| |
| /* Do reads through memory-mapping instead of indirect */ |
| if (!stm32_qspi_is_memory_mapped(dev)) { |
| ret = stm32_qspi_set_memory_mapped(dev); |
| if (ret != 0) { |
| LOG_ERR("READ: failed to set memory mapped"); |
| goto end; |
| } |
| } |
| |
| __ASSERT_NO_MSG(stm32_qspi_is_memory_mapped(dev)); |
| |
| uintptr_t mmap_addr = STM32_QSPI_BASE_ADDRESS + addr; |
| |
| LOG_DBG("Memory-mapped read from 0x%08lx, len %zu", mmap_addr, size); |
| memcpy(data, (void *)mmap_addr, size); |
| ret = 0; |
| goto end; |
| #else |
| QSPI_CommandTypeDef cmd = { |
| .Instruction = SPI_NOR_CMD_READ, |
| .Address = addr, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| .AddressMode = QSPI_ADDRESS_1_LINE, |
| .DataMode = QSPI_DATA_1_LINE, |
| }; |
| |
| qspi_set_address_size(dev, &cmd); |
| if (IS_ENABLED(STM32_QSPI_USE_QUAD_IO)) { |
| ret = qspi_prepare_quad_read(dev, &cmd); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| qspi_lock_thread(dev); |
| |
| ret = qspi_read_access(dev, &cmd, data, size); |
| goto end; |
| #endif |
| |
| end: |
| 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) |
| { |
| int ret = 0; |
| |
| 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; |
| } |
| |
| /* write non-zero size */ |
| if (size == 0) { |
| return 0; |
| } |
| |
| QSPI_CommandTypeDef cmd_pp = { |
| .Instruction = SPI_NOR_CMD_PP, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| .AddressMode = QSPI_ADDRESS_1_LINE, |
| .DataMode = QSPI_DATA_1_LINE, |
| }; |
| |
| qspi_set_address_size(dev, &cmd_pp); |
| if (IS_ENABLED(STM32_QSPI_USE_QUAD_IO)) { |
| ret = qspi_prepare_quad_program(dev, &cmd_pp); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| qspi_lock_thread(dev); |
| |
| #ifdef CONFIG_STM32_MEMMAP |
| if (stm32_qspi_is_memory_mapped(dev)) { |
| /* Abort ongoing transfer to force CS high/BUSY deasserted */ |
| ret = stm32_qspi_abort(dev); |
| if (ret != 0) { |
| LOG_ERR("Failed to abort memory-mapped access before write"); |
| goto end; |
| } |
| } |
| #endif |
| |
| 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; |
| } |
| } |
| goto end; |
| |
| end: |
| 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->config; |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| int ret = 0; |
| |
| 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; |
| } |
| |
| /* erase non-zero size */ |
| if (size == 0) { |
| return 0; |
| } |
| |
| QSPI_CommandTypeDef cmd_erase = { |
| .Instruction = 0, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| .AddressMode = QSPI_ADDRESS_1_LINE, |
| }; |
| |
| qspi_set_address_size(dev, &cmd_erase); |
| qspi_lock_thread(dev); |
| |
| #ifdef CONFIG_STM32_MEMMAP |
| if (stm32_qspi_is_memory_mapped(dev)) { |
| /* Abort ongoing transfer to force CS high/BUSY deasserted */ |
| ret = stm32_qspi_abort(dev); |
| if (ret != 0) { |
| LOG_ERR("Failed to abort memory-mapped access before erase"); |
| goto end; |
| } |
| } |
| #endif |
| |
| 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); |
| } |
| goto end; |
| |
| end: |
| qspi_unlock_thread(dev); |
| |
| return ret; |
| } |
| |
| 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; |
| |
| 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; |
| |
| ARG_UNUSED(dev); |
| |
| if (status < 0) { |
| LOG_ERR("DMA callback error with channel %d.", channel); |
| |
| } |
| |
| HAL_DMA_IRQHandler(hdma); |
| } |
| #endif |
| |
| __weak HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma) |
| { |
| return HAL_OK; |
| } |
| |
| __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; |
| |
| *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, |
| .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_JESD216_API) |
| .sfdp_read = qspi_read_sfdp, |
| .read_jedec_id = qspi_read_jedec_id, |
| #endif /* CONFIG_FLASH_JESD216_API */ |
| }; |
| |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| static int setup_pages_layout(const struct device *dev) |
| { |
| const struct flash_stm32_qspi_config *dev_cfg = dev->config; |
| struct flash_stm32_qspi_data *data = dev->data; |
| 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) << STM32_QSPI_DOUBLE_FLASH; |
| |
| /* 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 qspi_program_addr_4b(const struct device *dev, bool write_enable) |
| { |
| int ret; |
| |
| /* Send write enable command, if required */ |
| if (write_enable) { |
| ret = qspi_send_cmd(dev, &cmd_write_en); |
| if (ret != 0) { |
| return ret; |
| } |
| } |
| |
| /* Program the flash memory to use 4 bytes addressing */ |
| QSPI_CommandTypeDef cmd = { |
| .Instruction = SPI_NOR_CMD_4BA, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| }; |
| |
| /* |
| * No need to Read control register afterwards to verify if 4byte addressing mode |
| * is enabled as the effect of the command is immediate |
| * and the SPI_NOR_CMD_RDCR is vendor-specific : |
| * SPI_NOR_4BYTE_BIT is BIT 5 for Macronix and 0 for Micron or Windbond |
| * Moreover bit value meaning is also vendor-specific |
| */ |
| |
| return qspi_send_cmd(dev, &cmd); |
| } |
| |
| static int qspi_read_status_register(const struct device *dev, uint8_t reg_num, uint8_t *reg) |
| { |
| QSPI_CommandTypeDef cmd = { |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| .DataMode = QSPI_DATA_1_LINE, |
| }; |
| |
| switch (reg_num) { |
| case 1U: |
| cmd.Instruction = SPI_NOR_CMD_RDSR; |
| break; |
| case 2U: |
| cmd.Instruction = SPI_NOR_CMD_RDSR2; |
| break; |
| case 3U: |
| cmd.Instruction = SPI_NOR_CMD_RDSR3; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return qspi_read_access(dev, &cmd, reg, sizeof(*reg)); |
| } |
| |
| static int qspi_write_status_register(const struct device *dev, uint8_t reg_num, uint8_t reg) |
| { |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| size_t size; |
| uint8_t regs[4] = { 0 }; |
| uint8_t *regs_p; |
| int ret; |
| |
| QSPI_CommandTypeDef cmd = { |
| .Instruction = SPI_NOR_CMD_WRSR, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| .DataMode = QSPI_DATA_1_LINE, |
| }; |
| |
| if (reg_num == 1) { |
| size = 1U; |
| regs[0] = reg; |
| regs_p = ®s[0]; |
| /* 1 byte write clears SR2, write SR2 as well */ |
| if (dev_data->qer_type == JESD216_DW15_QER_S2B1v1) { |
| ret = qspi_read_status_register(dev, 2, ®s[1]); |
| if (ret < 0) { |
| return ret; |
| } |
| size = 2U; |
| } |
| } else if (reg_num == 2) { |
| cmd.Instruction = SPI_NOR_CMD_WRSR2; |
| size = 1U; |
| regs[1] = reg; |
| regs_p = ®s[1]; |
| /* if SR2 write needs SR1 */ |
| if ((dev_data->qer_type == JESD216_DW15_QER_VAL_S2B1v1) || |
| (dev_data->qer_type == JESD216_DW15_QER_VAL_S2B1v4) || |
| (dev_data->qer_type == JESD216_DW15_QER_VAL_S2B1v5)) { |
| ret = qspi_read_status_register(dev, 1, ®s[0]); |
| if (ret < 0) { |
| return ret; |
| } |
| cmd.Instruction = SPI_NOR_CMD_WRSR; |
| size = 2U; |
| regs_p = ®s[0]; |
| } |
| } else if (reg_num == 3) { |
| cmd.Instruction = SPI_NOR_CMD_WRSR3; |
| size = 1U; |
| regs[2] = reg; |
| regs_p = ®s[2]; |
| } else { |
| return -EINVAL; |
| } |
| |
| return qspi_write_access(dev, &cmd, regs_p, size); |
| } |
| |
| static int qspi_write_enable(const struct device *dev) |
| { |
| uint8_t reg; |
| int ret; |
| |
| ret = qspi_send_cmd(dev, &cmd_write_en); |
| if (ret) { |
| return ret; |
| } |
| |
| do { |
| ret = qspi_read_status_register(dev, 1U, ®); |
| } while (!ret && !(reg & SPI_NOR_WEL_BIT)); |
| |
| return ret; |
| } |
| |
| static int qspi_program_quad_io(const struct device *dev) |
| { |
| struct flash_stm32_qspi_data *data = dev->data; |
| uint8_t qe_reg_num; |
| uint8_t qe_bit; |
| uint8_t reg; |
| int ret; |
| |
| switch (data->qer_type) { |
| case JESD216_DW15_QER_NONE: |
| /* no QE bit, device detects reads based on opcode */ |
| return 0; |
| case JESD216_DW15_QER_S1B6: |
| qe_reg_num = 1U; |
| qe_bit = BIT(6U); |
| break; |
| case JESD216_DW15_QER_S2B7: |
| qe_reg_num = 2U; |
| qe_bit = BIT(7U); |
| break; |
| case JESD216_DW15_QER_S2B1v1: |
| __fallthrough; |
| case JESD216_DW15_QER_S2B1v4: |
| __fallthrough; |
| case JESD216_DW15_QER_S2B1v5: |
| __fallthrough; |
| case JESD216_DW15_QER_S2B1v6: |
| qe_reg_num = 2U; |
| qe_bit = BIT(1U); |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| ret = qspi_read_status_register(dev, qe_reg_num, ®); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* exit early if QE bit is already set */ |
| if ((reg & qe_bit) != 0U) { |
| return 0; |
| } |
| |
| reg |= qe_bit; |
| |
| ret = qspi_write_enable(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = qspi_write_status_register(dev, qe_reg_num, reg); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = qspi_wait_until_ready(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* validate that QE bit is set */ |
| ret = qspi_read_status_register(dev, qe_reg_num, ®); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if ((reg & qe_bit) == 0U) { |
| LOG_ERR("Status Register %u [0x%02x] not set", qe_reg_num, reg); |
| return -EIO; |
| } |
| |
| return ret; |
| } |
| |
| 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->config; |
| struct flash_stm32_qspi_data *data = dev->data; |
| struct jesd216_erase_type *etp = data->erase_types; |
| uint8_t addr_mode; |
| const size_t flash_size = (jesd216_bfp_density(bfp) / 8U) << STM32_QSPI_DOUBLE_FLASH; |
| int rc; |
| |
| 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); |
| |
| addr_mode = jesd216_bfp_addrbytes(bfp); |
| if (addr_mode == JESD216_SFDP_BFP_DW1_ADDRBYTES_VAL_3B4B) { |
| struct jesd216_bfp_dw16 dw16; |
| |
| if (jesd216_bfp_decode_dw16(php, bfp, &dw16) == 0) { |
| /* |
| * According to JESD216, the bit0 of dw16.enter_4ba |
| * portion of flash description register 16 indicates |
| * if it is enough to use 0xB7 instruction without |
| * write enable to switch to 4 bytes addressing mode. |
| * If bit 1 is set, a write enable is needed. |
| */ |
| if (dw16.enter_4ba & 0x3) { |
| rc = qspi_program_addr_4b(dev, dw16.enter_4ba & 2); |
| if (rc == 0) { |
| data->flag_access_32bit = true; |
| LOG_INF("Flash - address mode: 4B"); |
| } else { |
| LOG_ERR("Unable to enter 4B mode: %d\n", rc); |
| return rc; |
| } |
| } |
| } |
| } |
| if (addr_mode == JESD216_SFDP_BFP_DW1_ADDRBYTES_VAL_4B) { |
| data->flag_access_32bit = true; |
| LOG_INF("Flash - address mode: 4B"); |
| } |
| |
| /* |
| * Only check if the 1-4-4 (i.e. 4READ) or 1-1-4 (QREAD) |
| * is supported - other modes are not. |
| */ |
| if (IS_ENABLED(STM32_QSPI_USE_QUAD_IO)) { |
| const enum jesd216_mode_type supported_modes[] = { JESD216_MODE_114, |
| JESD216_MODE_144 }; |
| struct jesd216_bfp_dw15 dw15; |
| struct jesd216_instr res; |
| |
| /* reset active mode */ |
| data->mode = STM32_QSPI_UNKNOWN_MODE; |
| |
| /* query supported read modes, begin from the slowest */ |
| for (size_t i = 0; i < ARRAY_SIZE(supported_modes); ++i) { |
| rc = jesd216_bfp_read_support(php, bfp, supported_modes[i], &res); |
| if (rc >= 0) { |
| LOG_INF("Quad read mode %d instr [0x%x] supported", |
| supported_modes[i], res.instr); |
| |
| data->mode = supported_modes[i]; |
| data->qspi_read_cmd = res.instr; |
| data->qspi_read_cmd_latency = res.wait_states; |
| |
| if (res.mode_clocks) { |
| data->qspi_read_cmd_latency += res.mode_clocks; |
| } |
| } |
| } |
| |
| /* don't continue when there is no supported mode */ |
| if (data->mode == STM32_QSPI_UNKNOWN_MODE) { |
| LOG_ERR("No supported flash read mode found"); |
| return -ENOTSUP; |
| } |
| |
| LOG_INF("Quad read mode %d instr [0x%x] will be used", data->mode, res.instr); |
| |
| /* try to decode QE requirement type */ |
| rc = jesd216_bfp_decode_dw15(php, bfp, &dw15); |
| if (rc < 0) { |
| /* will use QER from DTS or default (refer to device data) */ |
| LOG_WRN("Unable to decode QE requirement [DW15]: %d", rc); |
| } else { |
| /* bypass DTS QER value */ |
| data->qer_type = dw15.qer; |
| } |
| |
| LOG_INF("QE requirement mode: %x", data->qer_type); |
| |
| /* enable QE */ |
| rc = qspi_program_quad_io(dev); |
| if (rc < 0) { |
| LOG_ERR("Failed to enable Quad mode: %d", rc); |
| return rc; |
| } |
| |
| LOG_INF("Quad mode enabled"); |
| } |
| |
| return 0; |
| } |
| |
| #if STM32_QSPI_RESET_GPIO |
| static void flash_stm32_qspi_gpio_reset(const struct device *dev) |
| { |
| const struct flash_stm32_qspi_config *dev_cfg = dev->config; |
| |
| /* Generate RESETn pulse for the flash memory */ |
| gpio_pin_configure_dt(&dev_cfg->reset, GPIO_OUTPUT_ACTIVE); |
| k_msleep(DT_INST_PROP(0, reset_gpios_duration)); |
| gpio_pin_set_dt(&dev_cfg->reset, 0); |
| } |
| #endif |
| |
| #if STM32_QSPI_RESET_CMD |
| static int flash_stm32_qspi_send_reset(const struct device *dev) |
| { |
| QSPI_CommandTypeDef cmd = { |
| .Instruction = SPI_NOR_CMD_RESET_EN, |
| .InstructionMode = QSPI_INSTRUCTION_1_LINE, |
| }; |
| int ret; |
| |
| ret = qspi_send_cmd(dev, &cmd); |
| if (ret != 0) { |
| LOG_ERR("%d: Failed to send RESET_EN", ret); |
| return ret; |
| } |
| |
| cmd.Instruction = SPI_NOR_CMD_RESET_MEM; |
| ret = qspi_send_cmd(dev, &cmd); |
| if (ret != 0) { |
| LOG_ERR("%d: Failed to send RESET_MEM", ret); |
| return ret; |
| } |
| |
| LOG_DBG("Send Reset command"); |
| |
| return 0; |
| } |
| #endif |
| |
| static int flash_stm32_qspi_init(const struct device *dev) |
| { |
| const struct flash_stm32_qspi_config *dev_cfg = dev->config; |
| struct flash_stm32_qspi_data *dev_data = dev->data; |
| uint32_t ahb_clock_freq; |
| uint32_t prescaler = 0; |
| int ret; |
| |
| /* Signals configuration */ |
| ret = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret < 0) { |
| LOG_ERR("QSPI pinctrl setup failed (%d)", ret); |
| return ret; |
| } |
| |
| #if STM32_QSPI_RESET_GPIO |
| flash_stm32_qspi_gpio_reset(dev); |
| #endif |
| #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 (!device_is_ready(dev_data->dma.dev)) { |
| LOG_ERR("%s device not ready", dev_data->dma.dev->name); |
| return -ENODEV; |
| } |
| |
| /* 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 = table_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 a DMAMUX channel) */ |
| hdma.Instance = __LL_DMA_GET_CHANNEL_INSTANCE(dev_data->dma.reg, |
| dev_data->dma.channel); |
| #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 */ |
| if (clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), |
| (clock_control_subsys_t) &dev_cfg->pclken) != 0) { |
| LOG_DBG("Could not enable QSPI clock"); |
| return -EIO; |
| } |
| |
| if (clock_control_get_rate(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), |
| (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; |
| /* Give a bit position from 0 to 31 to the HAL init minus 1 for the DCR1 reg */ |
| dev_data->hqspi.Init.FlashSize = find_lsb_set(dev_cfg->flash_size) - 2; |
| #if DT_PROP(DT_NODELABEL(quadspi), dual_flash) && defined(QUADSPI_CR_DFM) |
| /* |
| * When the DTS has <dual-flash>, it means Dual Flash Mode |
| * Even in DUAL flash config, the SDFP is read from one single quad-NOR |
| * else the magic nb is wrong (0x46465353) |
| * That means that the Dual Flash config is set after the SFDP sequence |
| */ |
| dev_data->hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; |
| dev_data->hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_3_CYCLE; |
| dev_data->hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE; |
| /* Set Dual Flash Mode only on MemoryMapped */ |
| dev_data->hqspi.Init.FlashID = QSPI_FLASH_ID_1; |
| #endif /* dual_flash */ |
| |
| HAL_QSPI_Init(&dev_data->hqspi); |
| |
| #if DT_NODE_HAS_PROP(DT_NODELABEL(quadspi), flash_id) && \ |
| defined(QUADSPI_CR_FSEL) |
| /* |
| * Some stm32 mcu with quadspi (like stm32l47x or stm32l48x) |
| * does not support Dual-Flash Mode |
| */ |
| uint8_t qspi_flash_id = DT_PROP(DT_NODELABEL(quadspi), flash_id); |
| |
| HAL_QSPI_SetFlashID(&dev_data->hqspi, |
| (qspi_flash_id - 1) << QUADSPI_CR_FSEL_Pos); |
| #endif |
| /* 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); |
| |
| #if STM32_QSPI_RESET_CMD |
| flash_stm32_qspi_send_reset(dev); |
| k_busy_wait(DT_INST_PROP(0, reset_cmd_wait)); |
| #endif |
| |
| /* 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[20]; |
| struct jesd216_bfp bfp; |
| } u2; |
| const struct jesd216_bfp *bfp = &u2.bfp; |
| |
| ret = qspi_read_sfdp(dev, jesd216_param_addr(php), |
| (uint8_t *)u2.dw, |
| MIN(sizeof(uint32_t) * php->len_dw, sizeof(u2.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 */ |
| |
| ret = qspi_write_unprotect(dev); |
| if (ret != 0) { |
| LOG_ERR("write unprotect failed: %d", ret); |
| return -ENODEV; |
| } |
| LOG_DBG("Write Un-protected"); |
| |
| #ifdef CONFIG_STM32_MEMMAP |
| #if DT_PROP(DT_NODELABEL(quadspi), dual_flash) && defined(QUADSPI_CR_DFM) |
| /* |
| * When the DTS has dual_flash, it means Dual Flash Mode for Memory MAPPED |
| * Force Dual Flash mode now, after the SFDP sequence which is reading |
| * one quad-NOR only |
| */ |
| MODIFY_REG(dev_data->hqspi.Instance->CR, (QUADSPI_CR_DFM), QSPI_DUALFLASH_ENABLE); |
| LOG_DBG("Dual Flash Mode"); |
| #endif /* dual_flash */ |
| |
| ret = stm32_qspi_set_memory_mapped(dev); |
| if (ret != 0) { |
| LOG_ERR("Failed to enable memory-mapped mode: %d", ret); |
| return ret; |
| } |
| LOG_INF("Memory-mapped NOR quad-flash at 0x%lx (0x%x bytes)", |
| (long)(STM32_QSPI_BASE_ADDRESS), |
| dev_cfg->flash_size); |
| #else |
| LOG_INF("NOR quad-flash at 0x%lx (0x%x bytes)", |
| (long)(STM32_QSPI_BASE_ADDRESS), |
| dev_cfg->flash_size); |
| #endif |
| |
| return 0; |
| } |
| |
| #define DMA_CHANNEL_CONFIG(node, dir) \ |
| DT_DMAS_CELL_BY_NAME(node, dir, channel_config) |
| |
| #define QSPI_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 = 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); |
| |
| #define DT_WRITEOC_PROP_OR(inst, default_value) \ |
| COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, writeoc), \ |
| (_CONCAT(SPI_NOR_CMD_, DT_STRING_TOKEN(DT_DRV_INST(inst), writeoc))), \ |
| ((default_value))) |
| |
| #define DT_QER_PROP_OR(inst, default_value) \ |
| COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, quad_enable_requirements), \ |
| (_CONCAT(JESD216_DW15_QER_VAL_, \ |
| DT_STRING_TOKEN(DT_DRV_INST(inst), quad_enable_requirements))), \ |
| ((default_value))) |
| |
| #define STM32_QSPI_NODE DT_INST_PARENT(0) |
| |
| PINCTRL_DT_DEFINE(STM32_QSPI_NODE); |
| |
| 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_REG_ADDR_BY_IDX(0, 1) << STM32_QSPI_DOUBLE_FLASH, |
| .max_frequency = DT_INST_PROP(0, qspi_max_frequency), |
| .pcfg = PINCTRL_DT_DEV_CONFIG_GET(STM32_QSPI_NODE), |
| #if STM32_QSPI_RESET_GPIO |
| .reset = GPIO_DT_SPEC_INST_GET(0, reset_gpios), |
| #endif |
| #if DT_NODE_HAS_PROP(DT_INST(0, st_stm32_qspi_nor), jedec_id) |
| .jedec_id = DT_INST_PROP(0, jedec_id), |
| #endif /* jedec_id */ |
| }; |
| |
| 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, |
| }, |
| }, |
| .qer_type = DT_QER_PROP_OR(0, JESD216_DW15_QER_VAL_S1B6), |
| .qspi_write_cmd = DT_WRITEOC_PROP_OR(0, SPI_NOR_CMD_PP_1_4_4), |
| QSPI_DMA_CHANNEL(STM32_QSPI_NODE, tx_rx) |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, &flash_stm32_qspi_init, NULL, |
| &flash_stm32_qspi_dev_data, &flash_stm32_qspi_cfg, |
| POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY, |
| &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 |