You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
906 lines
23 KiB
906 lines
23 KiB
/* |
|
* 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 <errno.h> |
|
#include <kernel.h> |
|
#include <toolchain.h> |
|
#include <arch/common/ffs.h> |
|
#include <sys/util.h> |
|
#include <soc.h> |
|
#include <pinmux/pinmux_stm32.h> |
|
#include <drivers/clock_control/stm32_clock_control.h> |
|
#include <drivers/clock_control.h> |
|
#include <drivers/flash.h> |
|
#include <drivers/dma.h> |
|
#include <drivers/dma/dma_stm32.h> |
|
|
|
#include <stm32_ll_dma.h> |
|
|
|
#include "spi_nor.h" |
|
#include "jesd216.h" |
|
|
|
#include <logging/log.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_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 { |
|
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; |
|
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) |
|
{ |
|
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; |
|
} |
|
|
|
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 (!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 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, |
|
.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 (!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 = 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 */ |
|
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; |
|
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) \ |
|
.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); |
|
|
|
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, NULL, |
|
&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
|
|
|