084dc3f0dd
Simplifies the driver and Gives a generic function to prepare the Regular commands for each instruction. Signed-off-by: Francois Ramu <francois.ramu@st.com>
1399 lines
40 KiB
C
1399 lines
40 KiB
C
/*
|
|
* Copyright (c) 2022 STMicroelectronics
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT st_stm32_ospi_nor
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <toolchain.h>
|
|
#include <zephyr/arch/common/ffs.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <soc.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/dt-bindings/flash_controller/ospi.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
|
|
#include "spi_nor.h"
|
|
#include "jesd216.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(flash_stm32_ospi, CONFIG_FLASH_LOG_LEVEL);
|
|
|
|
#define STM32_OSPI_RESET_GPIO DT_INST_NODE_HAS_PROP(0, reset_gpios)
|
|
|
|
#define STM32_OSPI_FIFO_THRESHOLD 4
|
|
#define STM32_OSPI_CLOCK_PRESCALER_MAX 255
|
|
|
|
/* Max Time value during reset or erase operation */
|
|
#define STM32_OSPI_RESET_MAX_TIME 100U
|
|
#define STM32_OSPI_BULK_ERASE_MAX_TIME 460000U
|
|
#define STM32_OSPI_SECTOR_ERASE_MAX_TIME 1000U
|
|
#define STM32_OSPI_SUBSECTOR_4K_ERASE_MAX_TIME 400U
|
|
#define STM32_OSPI_WRITE_REG_MAX_TIME 40U
|
|
|
|
typedef void (*irq_config_func_t)(const struct device *dev);
|
|
|
|
struct flash_stm32_ospi_config {
|
|
OCTOSPI_TypeDef *regs;
|
|
struct stm32_pclken pclken;
|
|
irq_config_func_t irq_config;
|
|
size_t flash_size;
|
|
uint32_t max_frequency;
|
|
int data_mode; /* SPI or QSPI or OSPI */
|
|
int data_rate; /* DTR or STR */
|
|
const struct pinctrl_dev_config *pcfg;
|
|
#if STM32_OSPI_RESET_GPIO
|
|
const struct gpio_dt_spec reset;
|
|
#endif /* STM32_OSPI_RESET_GPIO */
|
|
#if DT_NODE_HAS_PROP(DT_INST(0, st_stm32_ospi_nor), sfdp_bfp)
|
|
uint8_t sfdp_bfp[DT_INST_PROP_LEN(0, sfdp_bfp)];
|
|
#endif /* sfdp_bfp */
|
|
};
|
|
|
|
struct flash_stm32_ospi_data {
|
|
OSPI_HandleTypeDef hospi;
|
|
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;
|
|
};
|
|
|
|
static inline void ospi_lock_thread(const struct device *dev)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
|
|
k_sem_take(&dev_data->sem, K_FOREVER);
|
|
}
|
|
|
|
static inline void ospi_unlock_thread(const struct device *dev)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
|
|
k_sem_give(&dev_data->sem);
|
|
}
|
|
|
|
static int ospi_send_cmd(const struct device *dev, OSPI_RegularCmdTypeDef *cmd)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
HAL_StatusTypeDef hal_ret;
|
|
|
|
LOG_DBG("Instruction 0x%x", cmd->Instruction);
|
|
|
|
dev_data->cmd_status = 0;
|
|
|
|
hal_ret = HAL_OSPI_Command(&dev_data->hospi, cmd, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);
|
|
if (hal_ret != HAL_OK) {
|
|
LOG_ERR("%d: Failed to send OSPI instruction", hal_ret);
|
|
return -EIO;
|
|
}
|
|
LOG_DBG("CCR 0x%x", dev_cfg->regs->CCR);
|
|
|
|
return dev_data->cmd_status;
|
|
}
|
|
|
|
static int ospi_read_access(const struct device *dev, OSPI_RegularCmdTypeDef *cmd,
|
|
uint8_t *data, size_t size)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
HAL_StatusTypeDef hal_ret;
|
|
|
|
cmd->NbData = size;
|
|
|
|
dev_data->cmd_status = 0;
|
|
|
|
hal_ret = HAL_OSPI_Command(&dev_data->hospi, cmd, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);
|
|
if (hal_ret != HAL_OK) {
|
|
LOG_ERR("%d: Failed to send OSPI instruction", hal_ret);
|
|
return -EIO;
|
|
}
|
|
|
|
hal_ret = HAL_OSPI_Receive_IT(&dev_data->hospi, data);
|
|
|
|
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;
|
|
}
|
|
|
|
static int ospi_write_access(const struct device *dev, OSPI_RegularCmdTypeDef *cmd,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
HAL_StatusTypeDef hal_ret;
|
|
|
|
LOG_DBG("Instruction 0x%x", cmd->Instruction);
|
|
|
|
cmd->NbData = size;
|
|
|
|
dev_data->cmd_status = 0;
|
|
|
|
/* in OPI/STR the 3-byte AddressSize is not supported by the NOR flash */
|
|
if ((dev_cfg->data_mode == OSPI_OPI_MODE) &&
|
|
(cmd->AddressSize != HAL_OSPI_ADDRESS_32_BITS)) {
|
|
LOG_ERR("OSPI wr in OPI/STR mode is for 32bit address only");
|
|
return -EIO;
|
|
}
|
|
|
|
hal_ret = HAL_OSPI_Command(&dev_data->hospi, cmd, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);
|
|
if (hal_ret != HAL_OK) {
|
|
LOG_ERR("%d: Failed to send OSPI instruction", hal_ret);
|
|
return -EIO;
|
|
}
|
|
|
|
hal_ret = HAL_OSPI_Transmit_IT(&dev_data->hospi, (uint8_t *)data);
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Gives a OSPI_RegularCmdTypeDef with all parameters set
|
|
* except Instruction, Address, DummyCycles, NbData
|
|
*/
|
|
static OSPI_RegularCmdTypeDef ospi_prepare_cmd(uint8_t transfer_mode, uint8_t transfer_rate)
|
|
{
|
|
|
|
OSPI_RegularCmdTypeDef cmd_tmp = {
|
|
.OperationType = HAL_OSPI_OPTYPE_COMMON_CFG,
|
|
.FlashId = HAL_OSPI_FLASH_ID_1,
|
|
.InstructionMode = ((transfer_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_INSTRUCTION_1_LINE
|
|
: HAL_OSPI_INSTRUCTION_8_LINES),
|
|
.InstructionSize = ((transfer_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_INSTRUCTION_8_BITS
|
|
: HAL_OSPI_INSTRUCTION_16_BITS),
|
|
.InstructionDtrMode = ((transfer_rate == OSPI_DTR_TRANSFER)
|
|
? HAL_OSPI_INSTRUCTION_DTR_ENABLE
|
|
: HAL_OSPI_INSTRUCTION_DTR_DISABLE),
|
|
.AddressMode = ((transfer_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_ADDRESS_1_LINE
|
|
: HAL_OSPI_ADDRESS_8_LINES),
|
|
.AddressDtrMode = ((transfer_rate == OSPI_DTR_TRANSFER)
|
|
? HAL_OSPI_ADDRESS_DTR_ENABLE
|
|
: HAL_OSPI_ADDRESS_DTR_DISABLE),
|
|
.AddressSize = HAL_OSPI_ADDRESS_32_BITS,
|
|
.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE,
|
|
.DataMode = ((transfer_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_DATA_1_LINE
|
|
: HAL_OSPI_DATA_8_LINES),
|
|
.DataDtrMode = ((transfer_rate == OSPI_DTR_TRANSFER)
|
|
? HAL_OSPI_DATA_DTR_ENABLE
|
|
: HAL_OSPI_DATA_DTR_DISABLE),
|
|
.DQSMode = (transfer_rate == OSPI_DTR_TRANSFER)
|
|
? HAL_OSPI_DQS_ENABLE
|
|
: HAL_OSPI_DQS_DISABLE,
|
|
.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD,
|
|
};
|
|
|
|
return cmd_tmp;
|
|
}
|
|
|
|
/*
|
|
* Read Serial Flash Discovery Parameter :
|
|
* perform a read access over SPI bus for SDFP (DataMode is already set)
|
|
* or get it from the sdfp table (in the DTS)
|
|
*/
|
|
static int ospi_read_sfdp(const struct device *dev, off_t addr, uint8_t *data,
|
|
size_t size)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
|
|
#if DT_NODE_HAS_PROP(DT_INST(0, st_stm32_ospi_nor), sfdp_bfp)
|
|
/* simulate the SDFP */
|
|
ARG_UNUSED(addr); /* addr is 0 */
|
|
|
|
for (uint8_t i_ind = 0; i_ind < ARRAY_SIZE(dev_cfg->sfdp_bfp); i_ind++) {
|
|
*(data + i_ind) = dev_cfg->sfdp_bfp[i_ind];
|
|
}
|
|
#else /* sfdp_bfp */
|
|
|
|
OSPI_RegularCmdTypeDef cmd = ospi_prepare_cmd(dev_cfg->data_mode,
|
|
dev_cfg->data_rate);
|
|
cmd.Instruction = ((dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
? JESD216_CMD_READ_SFDP
|
|
: JESD216_OCMD_READ_SFDP);
|
|
cmd.Address = addr;
|
|
cmd.AddressSize = ((dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_ADDRESS_24_BITS
|
|
: HAL_OSPI_ADDRESS_32_BITS);
|
|
cmd.DummyCycles = ((dev_cfg->data_mode == OSPI_SPI_MODE) ? 8U : 20U);
|
|
cmd.NbData = size;
|
|
|
|
HAL_StatusTypeDef hal_ret;
|
|
|
|
hal_ret = HAL_OSPI_Command(&dev_data->hospi, &cmd,
|
|
HAL_OSPI_TIMEOUT_DEFAULT_VALUE);
|
|
if (hal_ret != HAL_OK) {
|
|
LOG_ERR("%d: Failed to send OSPI instruction", hal_ret);
|
|
return -EIO;
|
|
}
|
|
|
|
hal_ret = HAL_OSPI_Receive(&dev_data->hospi, data, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);
|
|
if (hal_ret != HAL_OK) {
|
|
LOG_ERR("%d: Failed to read data", hal_ret);
|
|
return -EIO;
|
|
}
|
|
|
|
#endif /* sfdp_bfp */
|
|
dev_data->cmd_status = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool ospi_address_is_valid(const struct device *dev, off_t addr,
|
|
size_t size)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
size_t flash_size = dev_cfg->flash_size;
|
|
|
|
return (addr >= 0) && ((uint64_t)addr + (uint64_t)size <= flash_size);
|
|
}
|
|
|
|
/*
|
|
* This function Polls the WIP(Write In Progress) bit to become to 0
|
|
* in nor_mode SPI/OPI OSPI_SPI_MODE or OSPI_OPI_MODE
|
|
* and nor_rate transfer STR/DTR OSPI_STR_TRANSFER or OSPI_DTR_TRANSFER
|
|
*/
|
|
static int stm32_ospi_mem_ready(OSPI_HandleTypeDef *hospi, uint8_t nor_mode, uint8_t nor_rate)
|
|
{
|
|
OSPI_AutoPollingTypeDef s_config = {0};
|
|
OSPI_RegularCmdTypeDef s_command = ospi_prepare_cmd(nor_mode, nor_rate);
|
|
|
|
/* Configure automatic polling mode command to wait for memory ready */
|
|
s_command.Instruction = (nor_mode == OSPI_SPI_MODE)
|
|
? SPI_NOR_CMD_RDSR
|
|
: SPI_NOR_OCMD_RDSR;
|
|
s_command.Address = 0;
|
|
s_command.DummyCycles = (nor_mode == OSPI_SPI_MODE)
|
|
? 0U
|
|
: ((nor_rate == OSPI_DTR_TRANSFER)
|
|
? SPI_NOR_DUMMY_REG_OCTAL_DTR
|
|
: SPI_NOR_DUMMY_REG_OCTAL);
|
|
s_command.NbData = (nor_rate == OSPI_DTR_TRANSFER) ? 2U : 1U;
|
|
|
|
/* Set the mask to 0x01 to mask all Status REG bits except WIP */
|
|
/* Set the match to 0x00 to check if the WIP bit is Reset */
|
|
s_config.Match = SPI_NOR_MEM_RDY_MATCH;
|
|
s_config.Mask = SPI_NOR_MEM_RDY_MASK; /* Write in progress */
|
|
s_config.MatchMode = HAL_OSPI_MATCH_MODE_AND;
|
|
s_config.Interval = SPI_NOR_AUTO_POLLING_INTERVAL;
|
|
s_config.AutomaticStop = HAL_OSPI_AUTOMATIC_STOP_ENABLE;
|
|
|
|
if (HAL_OSPI_Command(hospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI AutoPoll command failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Start Automatic-Polling mode to wait until the memory is ready WIP=0 */
|
|
if (HAL_OSPI_AutoPolling(hospi, &s_config, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI AutoPoll failed");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Enables writing to the memory sending a Write Enable and wait it is effective */
|
|
static int stm32_ospi_write_enable(OSPI_HandleTypeDef *hospi, uint8_t nor_mode, uint8_t nor_rate)
|
|
{
|
|
OSPI_AutoPollingTypeDef s_config = {0};
|
|
OSPI_RegularCmdTypeDef s_command = ospi_prepare_cmd(nor_mode, nor_rate);
|
|
|
|
/* Initialize the write enable command */
|
|
s_command.Instruction = (nor_mode == OSPI_SPI_MODE)
|
|
? SPI_NOR_CMD_WREN
|
|
: SPI_NOR_OCMD_WREN;
|
|
s_command.AddressMode = HAL_OSPI_ADDRESS_NONE;
|
|
s_command.DataMode = HAL_OSPI_DATA_NONE;
|
|
s_command.DummyCycles = 0U;
|
|
|
|
if (HAL_OSPI_Command(hospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI flash write enable cmd failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Configure automatic polling mode to wait for write enabling */
|
|
s_command.Instruction = (nor_mode == OSPI_SPI_MODE)
|
|
? SPI_NOR_CMD_RDSR
|
|
: SPI_NOR_OCMD_RDSR;
|
|
s_command.AddressMode = (nor_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_ADDRESS_1_LINE
|
|
: HAL_OSPI_ADDRESS_8_LINES;
|
|
s_command.Address = 0U;
|
|
s_command.DummyCycles = (nor_mode == OSPI_SPI_MODE)
|
|
? 0U
|
|
: ((nor_rate == OSPI_DTR_TRANSFER)
|
|
? SPI_NOR_DUMMY_REG_OCTAL_DTR
|
|
: SPI_NOR_DUMMY_REG_OCTAL);
|
|
s_command.DataMode = (nor_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_DATA_1_LINE
|
|
: HAL_OSPI_DATA_8_LINES;
|
|
s_command.NbData = (nor_rate == OSPI_DTR_TRANSFER) ? 2U : 1U;
|
|
|
|
if (HAL_OSPI_Command(hospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI config auto polling cmd failed");
|
|
return -EIO;
|
|
}
|
|
|
|
s_config.Match = SPI_NOR_WREN_MATCH;
|
|
s_config.Mask = SPI_NOR_WREN_MASK;
|
|
s_config.MatchMode = HAL_OSPI_MATCH_MODE_AND;
|
|
s_config.Interval = SPI_NOR_AUTO_POLLING_INTERVAL;
|
|
s_config.AutomaticStop = HAL_OSPI_AUTOMATIC_STOP_ENABLE;
|
|
|
|
if (HAL_OSPI_AutoPolling(hospi, &s_config, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI config auto polling failed");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Write Flash configuration register 2 with new dummy cycles */
|
|
static int stm32_ospi_write_cfg2reg_dummy(OSPI_HandleTypeDef *hospi,
|
|
uint8_t nor_mode, uint8_t nor_rate)
|
|
{
|
|
uint8_t transmit_data = SPI_NOR_CR2_DUMMY_CYCLES_66MHZ;
|
|
OSPI_RegularCmdTypeDef s_command = ospi_prepare_cmd(nor_mode, nor_rate);
|
|
|
|
/* Initialize the writing of configuration register 2 */
|
|
s_command.Instruction = (nor_mode == OSPI_SPI_MODE)
|
|
? SPI_NOR_CMD_WR_CFGREG2
|
|
: SPI_NOR_OCMD_WR_CFGREG2;
|
|
s_command.Address = SPI_NOR_REG2_ADDR3;
|
|
s_command.DummyCycles = 0U;
|
|
s_command.NbData = (nor_mode == OSPI_SPI_MODE) ? 1U
|
|
: ((nor_rate == OSPI_DTR_TRANSFER) ? 2U : 1U);
|
|
|
|
if (HAL_OSPI_Command(hospi, &s_command,
|
|
HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI transmit ");
|
|
return -EIO;
|
|
}
|
|
|
|
if (HAL_OSPI_Transmit(hospi, &transmit_data,
|
|
HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI transmit ");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Write Flash configuration register 2 with new single or octal SPI protocol */
|
|
static int stm32_ospi_write_cfg2reg_io(OSPI_HandleTypeDef *hospi,
|
|
uint8_t nor_mode, uint8_t nor_rate, uint8_t op_enable)
|
|
{
|
|
OSPI_RegularCmdTypeDef s_command = ospi_prepare_cmd(nor_mode, nor_rate);
|
|
|
|
/* Initialize the writing of configuration register 2 */
|
|
s_command.Instruction = (nor_mode == OSPI_SPI_MODE)
|
|
? SPI_NOR_CMD_WR_CFGREG2
|
|
: SPI_NOR_OCMD_WR_CFGREG2;
|
|
s_command.Address = SPI_NOR_REG2_ADDR1;
|
|
s_command.DummyCycles = 0U;
|
|
s_command.NbData = (nor_mode == OSPI_SPI_MODE) ? 1U
|
|
: ((nor_rate == OSPI_DTR_TRANSFER) ? 2U : 1U);
|
|
|
|
if (HAL_OSPI_Command(hospi, &s_command,
|
|
HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("Write Flash configuration reg2 failed");
|
|
return -EIO;
|
|
}
|
|
|
|
if (HAL_OSPI_Transmit(hospi, &op_enable,
|
|
HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("Write Flash configuration reg2 failed");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Read Flash configuration register 2 with new single or octal SPI protocol */
|
|
static int stm32_ospi_read_cfg2reg(OSPI_HandleTypeDef *hospi,
|
|
uint8_t nor_mode, uint8_t nor_rate, uint8_t *value)
|
|
{
|
|
OSPI_RegularCmdTypeDef s_command = ospi_prepare_cmd(nor_mode, nor_rate);
|
|
|
|
/* Initialize the writing of configuration register 2 */
|
|
s_command.Instruction = (nor_mode == OSPI_SPI_MODE)
|
|
? SPI_NOR_CMD_RD_CFGREG2
|
|
: SPI_NOR_OCMD_RD_CFGREG2;
|
|
s_command.Address = SPI_NOR_REG2_ADDR1;
|
|
s_command.DummyCycles = (nor_mode == OSPI_SPI_MODE)
|
|
? 0U
|
|
: ((nor_rate == OSPI_DTR_TRANSFER)
|
|
? SPI_NOR_DUMMY_REG_OCTAL_DTR
|
|
: SPI_NOR_DUMMY_REG_OCTAL);
|
|
s_command.NbData = (nor_rate == OSPI_DTR_TRANSFER) ? 2U : 1U;
|
|
|
|
if (HAL_OSPI_Command(hospi, &s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("Write Flash configuration reg2 failed");
|
|
return -EIO;
|
|
}
|
|
|
|
if (HAL_OSPI_Receive(hospi, value, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("Write Flash configuration reg2 failed");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set the NOR Flash to desired Interface mode : SPI/OSPI and STR/DTR according to the DTS */
|
|
static int stm32_ospi_config_mem(const struct device *dev)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
uint8_t reg[2];
|
|
|
|
/* Going to set the SPI mode and STR transfer rate : done */
|
|
if ((dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
&& (dev_cfg->data_rate == OSPI_STR_TRANSFER)) {
|
|
LOG_INF("OSPI flash config is SPI / STR");
|
|
return 0;
|
|
}
|
|
|
|
/* Going to set the OPI mode (STR or DTR transfer rate) */
|
|
LOG_DBG("OSPI configuring OctoSPI mode");
|
|
|
|
if (stm32_ospi_write_enable(&dev_data->hospi,
|
|
OSPI_SPI_MODE, OSPI_STR_TRANSFER) != 0) {
|
|
LOG_ERR("OSPI write Enable failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Write Configuration register 2 (with new dummy cycles) */
|
|
if (stm32_ospi_write_cfg2reg_dummy(&dev_data->hospi,
|
|
OSPI_SPI_MODE, OSPI_STR_TRANSFER) != 0) {
|
|
LOG_ERR("OSPI write CFGR2 failed");
|
|
return -EIO;
|
|
}
|
|
if (stm32_ospi_mem_ready(&dev_data->hospi,
|
|
OSPI_SPI_MODE, OSPI_STR_TRANSFER) != 0) {
|
|
LOG_ERR("OSPI autopolling failed");
|
|
return -EIO;
|
|
}
|
|
if (stm32_ospi_write_enable(&dev_data->hospi,
|
|
OSPI_SPI_MODE, OSPI_STR_TRANSFER) != 0) {
|
|
LOG_ERR("OSPI write Enable 2 failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Write Configuration register 2 (with Octal I/O SPI protocol : choose STR or DTR) */
|
|
uint8_t mode_enable = ((dev_cfg->data_rate == OSPI_DTR_TRANSFER)
|
|
? SPI_NOR_CR2_DTR_OPI_EN
|
|
: SPI_NOR_CR2_STR_OPI_EN);
|
|
if (stm32_ospi_write_cfg2reg_io(&dev_data->hospi,
|
|
OSPI_SPI_MODE, OSPI_STR_TRANSFER, mode_enable) != 0) {
|
|
LOG_ERR("OSPI write CFGR2 failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Wait that the configuration is effective and check that memory is ready */
|
|
k_msleep(STM32_OSPI_WRITE_REG_MAX_TIME);
|
|
|
|
/* Reconfigure the memory type of the peripheral */
|
|
dev_data->hospi.Init.MemoryType = HAL_OSPI_MEMTYPE_MACRONIX;
|
|
dev_data->hospi.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_ENABLE;
|
|
if (HAL_OSPI_Init(&dev_data->hospi) != HAL_OK) {
|
|
LOG_ERR("OSPI mem type MACRONIX failed");
|
|
return -EIO;
|
|
}
|
|
|
|
if (dev_cfg->data_rate == OSPI_STR_TRANSFER) {
|
|
if (stm32_ospi_mem_ready(&dev_data->hospi,
|
|
OSPI_OPI_MODE, OSPI_STR_TRANSFER) != 0) {
|
|
/* Check Flash busy ? */
|
|
LOG_ERR("OSPI flash busy failed");
|
|
return -EIO;
|
|
}
|
|
|
|
if (stm32_ospi_read_cfg2reg(&dev_data->hospi,
|
|
OSPI_OPI_MODE, OSPI_STR_TRANSFER, reg) != 0) {
|
|
/* Check the configuration has been correctly done on SPI_NOR_REG2_ADDR1 */
|
|
LOG_ERR("OSPI flash config read failed");
|
|
return -EIO;
|
|
}
|
|
|
|
LOG_INF("OSPI flash config is OPI / STR");
|
|
}
|
|
|
|
if (dev_cfg->data_rate == OSPI_DTR_TRANSFER) {
|
|
if (stm32_ospi_mem_ready(&dev_data->hospi,
|
|
OSPI_OPI_MODE, OSPI_DTR_TRANSFER) != 0) {
|
|
/* Check Flash busy ? */
|
|
LOG_ERR("OSPI flash busy failed");
|
|
return -EIO;
|
|
}
|
|
|
|
LOG_INF("OSPI flash config is OPI / DTR");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* gpio or send the different reset command to the NOR flash in SPI/OSPI and STR/DTR */
|
|
static int stm32_ospi_mem_reset(const struct device *dev)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
|
|
#if STM32_OSPI_RESET_GPIO
|
|
/* 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);
|
|
#else
|
|
|
|
/* Reset command sent sucessively for each mode SPI/OPS & STR/DTR */
|
|
OSPI_RegularCmdTypeDef s_command = {
|
|
.OperationType = HAL_OSPI_OPTYPE_COMMON_CFG,
|
|
.FlashId = HAL_OSPI_FLASH_ID_1,
|
|
.AddressMode = HAL_OSPI_ADDRESS_NONE,
|
|
.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE,
|
|
.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE,
|
|
.Instruction = SPI_NOR_CMD_RESET_EN,
|
|
.InstructionSize = HAL_OSPI_INSTRUCTION_8_BITS,
|
|
.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE,
|
|
.DataMode = HAL_OSPI_DATA_NONE,
|
|
.DummyCycles = 0U,
|
|
.DQSMode = HAL_OSPI_DQS_DISABLE,
|
|
.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD,
|
|
};
|
|
|
|
/* Reset enable in SPI mode and STR transfer mode */
|
|
if (HAL_OSPI_Command(&dev_data->hospi,
|
|
&s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI reset enable (SPI/STR) failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Reset memory in SPI mode and STR transfer mode */
|
|
s_command.Instruction = SPI_NOR_CMD_RESET_MEM;
|
|
if (HAL_OSPI_Command(&dev_data->hospi,
|
|
&s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI reset memory (SPI/STR) failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Reset enable in OPI mode and STR transfer mode */
|
|
s_command.InstructionMode = HAL_OSPI_INSTRUCTION_8_LINES;
|
|
s_command.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;
|
|
s_command.Instruction = SPI_NOR_OCMD_RESET_EN;
|
|
s_command.InstructionSize = HAL_OSPI_INSTRUCTION_16_BITS;
|
|
if (HAL_OSPI_Command(&dev_data->hospi,
|
|
&s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI reset enable (OPI/STR) failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Reset memory in OPI mode and STR transfer mode */
|
|
s_command.Instruction = SPI_NOR_OCMD_RESET_MEM;
|
|
if (HAL_OSPI_Command(&dev_data->hospi,
|
|
&s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI reset memory (OPI/STR) failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Reset enable in OPI mode and DTR transfer mode */
|
|
s_command.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_ENABLE;
|
|
s_command.Instruction = SPI_NOR_OCMD_RESET_EN;
|
|
if (HAL_OSPI_Command(&dev_data->hospi,
|
|
&s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI reset enable (OPI/DTR) failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Reset memory in OPI mode and DTR transfer mode */
|
|
s_command.Instruction = SPI_NOR_OCMD_RESET_MEM;
|
|
if (HAL_OSPI_Command(&dev_data->hospi,
|
|
&s_command, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI reset memory (OPI/DTR) failed");
|
|
return -EIO;
|
|
}
|
|
|
|
#endif
|
|
/* After SWreset CMD, wait in case SWReset occurred during erase operation */
|
|
k_msleep(STM32_OSPI_RESET_MAX_TIME);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function to erase the flash : chip or sector with possible OSPI/SPI and STR/DTR
|
|
* to erase the complete chip (using dedicated command) :
|
|
* set size >= flash size
|
|
* set addr = 0
|
|
*/
|
|
static int flash_stm32_ospi_erase(const struct device *dev, off_t addr,
|
|
size_t size)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
int ret = 0;
|
|
|
|
/* Ignore zero size erase */
|
|
if (size == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* Maximise erase size : means the complete chip */
|
|
if (size > dev_cfg->flash_size) {
|
|
/* Reset addr in that case */
|
|
addr = 0;
|
|
size = dev_cfg->flash_size;
|
|
}
|
|
|
|
if (!ospi_address_is_valid(dev, addr, size)) {
|
|
LOG_ERR("Error: address or size exceeds expected values: "
|
|
"addr 0x%lx, size %zu", (long)addr, size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((size != SPI_NOR_SECTOR_SIZE) && (size < dev_cfg->flash_size)) {
|
|
LOG_ERR("Error: wrong sector size 0x%x", size);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
OSPI_RegularCmdTypeDef cmd_erase = {
|
|
.OperationType = HAL_OSPI_OPTYPE_COMMON_CFG,
|
|
.FlashId = HAL_OSPI_FLASH_ID_1,
|
|
.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE,
|
|
.DataMode = HAL_OSPI_DATA_NONE,
|
|
.DummyCycles = 0,
|
|
.DQSMode = HAL_OSPI_DQS_DISABLE,
|
|
.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD,
|
|
};
|
|
|
|
ospi_lock_thread(dev);
|
|
|
|
if (stm32_ospi_mem_ready(&dev_data->hospi,
|
|
dev_cfg->data_mode, dev_cfg->data_rate) != 0) {
|
|
LOG_ERR("Erase failed : flash busy");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (stm32_ospi_write_enable(&dev_data->hospi,
|
|
dev_cfg->data_mode, dev_cfg->data_rate) != 0) {
|
|
LOG_ERR("Erase failed : write enable");
|
|
return -EIO;
|
|
}
|
|
|
|
cmd_erase.InstructionMode = (dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_INSTRUCTION_1_LINE
|
|
: HAL_OSPI_INSTRUCTION_8_LINES;
|
|
cmd_erase.InstructionDtrMode = (dev_cfg->data_rate == OSPI_DTR_TRANSFER)
|
|
? HAL_OSPI_INSTRUCTION_DTR_ENABLE
|
|
: HAL_OSPI_INSTRUCTION_DTR_DISABLE;
|
|
cmd_erase.InstructionSize = (dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_INSTRUCTION_8_BITS
|
|
: HAL_OSPI_INSTRUCTION_16_BITS;
|
|
|
|
while ((size > 0) && (ret == 0)) {
|
|
if (size == dev_cfg->flash_size) {
|
|
/* Chip erase */
|
|
LOG_INF("Chip Erase");
|
|
cmd_erase.Instruction = (dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
? SPI_NOR_CMD_BULKE
|
|
: SPI_NOR_OCMD_BULKE;
|
|
cmd_erase.AddressMode = HAL_OSPI_ADDRESS_NONE;
|
|
/* Full chip erase command */
|
|
ospi_send_cmd(dev, &cmd_erase);
|
|
|
|
size -= dev_cfg->flash_size;
|
|
} else {
|
|
/* Sector erase */
|
|
LOG_INF("Sector Erase");
|
|
cmd_erase.Address = addr;
|
|
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;
|
|
} else {
|
|
/* Use the default sector erase cmd */
|
|
cmd_erase.Instruction =
|
|
(dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
? SPI_NOR_CMD_SE /* Erase sector size 3 bytes */
|
|
: SPI_NOR_OCMD_SE;
|
|
cmd_erase.AddressMode =
|
|
(dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
? HAL_OSPI_ADDRESS_1_LINE
|
|
: HAL_OSPI_ADDRESS_8_LINES;
|
|
cmd_erase.AddressDtrMode =
|
|
(dev_cfg->data_rate == OSPI_DTR_TRANSFER)
|
|
? HAL_OSPI_ADDRESS_DTR_ENABLE
|
|
: HAL_OSPI_ADDRESS_DTR_DISABLE;
|
|
cmd_erase.AddressSize = HAL_OSPI_ADDRESS_32_BITS;
|
|
cmd_erase.Address = addr;
|
|
}
|
|
}
|
|
|
|
ospi_send_cmd(dev, &cmd_erase);
|
|
|
|
if (bet != NULL) {
|
|
addr += BIT(bet->exp);
|
|
size -= BIT(bet->exp);
|
|
} else {
|
|
addr += SPI_NOR_SECTOR_SIZE;
|
|
size -= SPI_NOR_SECTOR_SIZE;
|
|
}
|
|
|
|
ret = stm32_ospi_mem_ready(&dev_data->hospi,
|
|
dev_cfg->data_mode, dev_cfg->data_rate);
|
|
}
|
|
}
|
|
|
|
ospi_unlock_thread(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Function to read the flash with possible OSPI/SPI and STR/DTR */
|
|
static int flash_stm32_ospi_read(const struct device *dev, off_t addr,
|
|
void *data, size_t size)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
int ret;
|
|
|
|
if (!ospi_address_is_valid(dev, addr, size)) {
|
|
LOG_ERR("Error: address or size exceeds expected values: "
|
|
"addr 0x%lx, size %zu", (long)addr, size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Ignore zero size read */
|
|
if (size == 0) {
|
|
return 0;
|
|
}
|
|
|
|
OSPI_RegularCmdTypeDef cmd = ospi_prepare_cmd(dev_cfg->data_mode, dev_cfg->data_rate);
|
|
/* Instruction and DummyCycles are set below */
|
|
cmd.Address = addr;
|
|
/* DataSize is set by the read cmd */
|
|
|
|
LOG_DBG("OSPI: read %u data", size);
|
|
ospi_lock_thread(dev);
|
|
|
|
/* Configure other parameters */
|
|
if (dev_cfg->data_rate == OSPI_DTR_TRANSFER) {
|
|
/* DTR transfer rate (==> Octal mode) */
|
|
cmd.Instruction = SPI_NOR_OCMD_DTR_RD;
|
|
cmd.DummyCycles = SPI_NOR_DUMMY_RD_OCTAL_DTR;
|
|
} else {
|
|
/* STR transfer rate */
|
|
if (dev_cfg->data_mode == OSPI_SPI_MODE) {
|
|
/* SPI and STR : use fast read with addr on 4 bytes */
|
|
cmd.Instruction = SPI_NOR_CMD_READ_FAST_4B;
|
|
cmd.DummyCycles = SPI_NOR_DUMMY_RD;
|
|
} else {
|
|
/* OPI and STR */
|
|
cmd.Instruction = SPI_NOR_OCMD_RD;
|
|
cmd.DummyCycles = SPI_NOR_DUMMY_RD_OCTAL;
|
|
}
|
|
}
|
|
|
|
ret = ospi_read_access(dev, &cmd, data, size);
|
|
|
|
ospi_unlock_thread(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Function to write the flash (page program) : with possible OSPI/SPI and STR/DTR */
|
|
static int flash_stm32_ospi_write(const struct device *dev, off_t addr,
|
|
const void *data, size_t size)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
int ret = 0;
|
|
|
|
if (!ospi_address_is_valid(dev, addr, size)) {
|
|
LOG_ERR("Error: address or size exceeds expected values: "
|
|
"addr 0x%lx, size %zu", (long)addr, size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Ignore zero size write */
|
|
if (size == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* page program for STR or DTR mode */
|
|
OSPI_RegularCmdTypeDef cmd_pp = ospi_prepare_cmd(dev_cfg->data_mode, dev_cfg->data_rate);
|
|
|
|
cmd_pp.DummyCycles = 0;
|
|
|
|
LOG_INF("OSPI: write %u data", size);
|
|
ospi_lock_thread(dev);
|
|
|
|
while ((size > 0) && (ret == 0)) {
|
|
size_t to_write = size;
|
|
|
|
ret = stm32_ospi_mem_ready(&dev_data->hospi,
|
|
dev_cfg->data_mode, dev_cfg->data_rate);
|
|
if (ret != 0) {
|
|
break;
|
|
}
|
|
|
|
ret = stm32_ospi_write_enable(&dev_data->hospi,
|
|
dev_cfg->data_mode, dev_cfg->data_rate);
|
|
if (ret != 0) {
|
|
break;
|
|
}
|
|
/* 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);
|
|
}
|
|
|
|
cmd_pp.Instruction = (dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
? SPI_NOR_CMD_PP_4B
|
|
: SPI_NOR_OCMD_PAGE_PRG;
|
|
cmd_pp.Address = addr;
|
|
ret = ospi_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;
|
|
|
|
/* Configure automatic polling mode to wait for end of program */
|
|
ret = stm32_ospi_mem_ready(&dev_data->hospi,
|
|
dev_cfg->data_mode, dev_cfg->data_rate);
|
|
if (ret != 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ospi_unlock_thread(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct flash_parameters flash_stm32_ospi_parameters = {
|
|
.write_block_size = 1,
|
|
.erase_value = 0xff
|
|
};
|
|
|
|
static const struct flash_parameters *
|
|
flash_stm32_ospi_get_parameters(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return &flash_stm32_ospi_parameters;
|
|
}
|
|
|
|
static void flash_stm32_ospi_isr(const struct device *dev)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
|
|
HAL_OSPI_IRQHandler(&dev_data->hospi);
|
|
}
|
|
|
|
/* weak function required for HAL compilation */
|
|
__weak HAL_StatusTypeDef HAL_DMA_Abort_IT(DMA_HandleTypeDef *hdma)
|
|
{
|
|
return HAL_OK;
|
|
}
|
|
|
|
/* weak function required for HAL compilation */
|
|
__weak HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma)
|
|
{
|
|
return HAL_OK;
|
|
}
|
|
|
|
/*
|
|
* Transfer Error callback.
|
|
*/
|
|
void HAL_OSPI_ErrorCallback(OSPI_HandleTypeDef *hospi)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data =
|
|
CONTAINER_OF(hospi, struct flash_stm32_ospi_data, hospi);
|
|
|
|
LOG_DBG("Error cb");
|
|
|
|
dev_data->cmd_status = -EIO;
|
|
|
|
k_sem_give(&dev_data->sync);
|
|
}
|
|
|
|
/*
|
|
* Command completed callback.
|
|
*/
|
|
void HAL_OSPI_CmdCpltCallback(OSPI_HandleTypeDef *hospi)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data =
|
|
CONTAINER_OF(hospi, struct flash_stm32_ospi_data, hospi);
|
|
|
|
LOG_DBG("Cmd Cplt cb");
|
|
|
|
k_sem_give(&dev_data->sync);
|
|
}
|
|
|
|
/*
|
|
* Rx Transfer completed callback.
|
|
*/
|
|
void HAL_OSPI_RxCpltCallback(OSPI_HandleTypeDef *hospi)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data =
|
|
CONTAINER_OF(hospi, struct flash_stm32_ospi_data, hospi);
|
|
|
|
LOG_DBG("Rx Cplt cb");
|
|
|
|
k_sem_give(&dev_data->sync);
|
|
}
|
|
|
|
/*
|
|
* Tx Transfer completed callback.
|
|
*/
|
|
void HAL_OSPI_TxCpltCallback(OSPI_HandleTypeDef *hospi)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data =
|
|
CONTAINER_OF(hospi, struct flash_stm32_ospi_data, hospi);
|
|
|
|
LOG_DBG("Tx Cplt cb");
|
|
|
|
k_sem_give(&dev_data->sync);
|
|
}
|
|
|
|
/*
|
|
* Status Match callback.
|
|
*/
|
|
void HAL_OSPI_StatusMatchCallback(OSPI_HandleTypeDef *hospi)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data =
|
|
CONTAINER_OF(hospi, struct flash_stm32_ospi_data, hospi);
|
|
|
|
LOG_DBG("Status Match cb");
|
|
|
|
k_sem_give(&dev_data->sync);
|
|
}
|
|
|
|
/*
|
|
* Timeout callback.
|
|
*/
|
|
void HAL_OSPI_TimeOutCallback(OSPI_HandleTypeDef *hospi)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data =
|
|
CONTAINER_OF(hospi, struct flash_stm32_ospi_data, hospi);
|
|
|
|
LOG_DBG("Timeout cb");
|
|
|
|
dev_data->cmd_status = -EIO;
|
|
|
|
k_sem_give(&dev_data->sync);
|
|
}
|
|
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
static void flash_stm32_ospi_pages_layout(const struct device *dev,
|
|
const struct flash_pages_layout **layout,
|
|
size_t *layout_size)
|
|
{
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
|
|
*layout = &dev_data->layout;
|
|
*layout_size = 1;
|
|
}
|
|
#endif
|
|
|
|
static const struct flash_driver_api flash_stm32_ospi_driver_api = {
|
|
.read = flash_stm32_ospi_read,
|
|
.write = flash_stm32_ospi_write,
|
|
.erase = flash_stm32_ospi_erase,
|
|
.get_parameters = flash_stm32_ospi_get_parameters,
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
.page_layout = flash_stm32_ospi_pages_layout,
|
|
#endif
|
|
};
|
|
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
static int setup_pages_layout(const struct device *dev)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
struct flash_stm32_ospi_data *data = dev->data;
|
|
const size_t flash_size = dev_cfg->flash_size;
|
|
uint32_t layout_page_size = data->page_size;
|
|
uint8_t value = 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)
|
|
&& ((value == 0) || (etp->exp < value))) {
|
|
value = etp->exp;
|
|
}
|
|
}
|
|
|
|
uint32_t erase_size = BIT(value);
|
|
|
|
if (erase_size == 0) {
|
|
erase_size = SPI_NOR_SECTOR_SIZE;
|
|
}
|
|
|
|
/* 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_DBG("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_ospi_config *dev_cfg = dev->config;
|
|
struct flash_stm32_ospi_data *data = dev->data;
|
|
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_DBG("Unexpected flash size: %u", flash_size);
|
|
}
|
|
|
|
LOG_DBG("%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_ospi_init(const struct device *dev)
|
|
{
|
|
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
|
|
struct flash_stm32_ospi_data *dev_data = dev->data;
|
|
uint32_t ahb_clock_freq;
|
|
uint32_t prescaler = 0;
|
|
int ret;
|
|
|
|
/* The SPI/DTR is not a valid config of data_mode/data_rate according to the DTS */
|
|
if ((dev_cfg->data_mode == OSPI_SPI_MODE)
|
|
&& (dev_cfg->data_rate == OSPI_DTR_TRANSFER)) {
|
|
/* already the right config, continue */
|
|
LOG_ERR("OSPI mode SPI/DTR is not valid");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Signals configuration */
|
|
ret = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT);
|
|
if (ret < 0) {
|
|
LOG_ERR("OSPI pinctrl setup failed (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Initializes the independent peripherals clock */
|
|
__HAL_RCC_OSPI_CONFIG(RCC_OSPICLKSOURCE_SYSCLK); /* */
|
|
|
|
/* Clock configuration */
|
|
if (clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE),
|
|
(clock_control_subsys_t) &dev_cfg->pclken) != 0) {
|
|
LOG_ERR("Could not enable OSPI 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_ERR("Failed to get AHB clock frequency");
|
|
return -EIO;
|
|
}
|
|
|
|
for (; prescaler <= STM32_OSPI_CLOCK_PRESCALER_MAX; prescaler++) {
|
|
uint32_t clk = ahb_clock_freq / (prescaler + 1);
|
|
|
|
if (clk <= dev_cfg->max_frequency) {
|
|
break;
|
|
}
|
|
}
|
|
__ASSERT_NO_MSG(prescaler <= STM32_OSPI_CLOCK_PRESCALER_MAX);
|
|
|
|
/* Initialize OSPI HAL structure completely */
|
|
dev_data->hospi.Init.FifoThreshold = 4;
|
|
dev_data->hospi.Init.ClockPrescaler = prescaler;
|
|
dev_data->hospi.Init.DeviceSize = find_lsb_set(dev_cfg->flash_size);
|
|
dev_data->hospi.Init.DualQuad = HAL_OSPI_DUALQUAD_DISABLE;
|
|
dev_data->hospi.Init.ChipSelectHighTime = 2;
|
|
dev_data->hospi.Init.FreeRunningClock = HAL_OSPI_FREERUNCLK_DISABLE;
|
|
dev_data->hospi.Init.ClockMode = HAL_OSPI_CLOCK_MODE_0;
|
|
dev_data->hospi.Init.WrapSize = HAL_OSPI_WRAP_NOT_SUPPORTED;
|
|
dev_data->hospi.Init.SampleShifting = HAL_OSPI_SAMPLE_SHIFTING_NONE;
|
|
/* STR mode else Macronix for DTR mode */
|
|
if (dev_cfg->data_rate == OSPI_DTR_TRANSFER) {
|
|
dev_data->hospi.Init.MemoryType = HAL_OSPI_MEMTYPE_MACRONIX;
|
|
dev_data->hospi.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_ENABLE;
|
|
} else {
|
|
dev_data->hospi.Init.MemoryType = HAL_OSPI_MEMTYPE_MICRON;
|
|
dev_data->hospi.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_DISABLE;
|
|
}
|
|
dev_data->hospi.Init.ChipSelectBoundary = 0;
|
|
dev_data->hospi.Init.DelayBlockBypass = HAL_OSPI_DELAY_BLOCK_USED;
|
|
dev_data->hospi.Init.Refresh = 0;
|
|
|
|
if (HAL_OSPI_Init(&dev_data->hospi) != HAL_OK) {
|
|
LOG_ERR("OSPI Init failed");
|
|
return -EIO;
|
|
}
|
|
|
|
#if defined(CONFIG_SOC_SERIES_STM32U5X)
|
|
/* OCTOSPI I/O manager init Function */
|
|
OSPIM_CfgTypeDef ospi_mgr_cfg = {0};
|
|
|
|
__HAL_RCC_OSPIM_CLK_ENABLE();
|
|
if (dev_data->hospi.Instance == OCTOSPI1) {
|
|
ospi_mgr_cfg.ClkPort = 1;
|
|
ospi_mgr_cfg.DQSPort = 1;
|
|
ospi_mgr_cfg.NCSPort = 1;
|
|
ospi_mgr_cfg.IOLowPort = HAL_OSPIM_IOPORT_1_LOW;
|
|
ospi_mgr_cfg.IOHighPort = HAL_OSPIM_IOPORT_1_HIGH;
|
|
} else if (dev_data->hospi.Instance == OCTOSPI2) {
|
|
ospi_mgr_cfg.ClkPort = 2;
|
|
ospi_mgr_cfg.DQSPort = 2;
|
|
ospi_mgr_cfg.NCSPort = 2;
|
|
ospi_mgr_cfg.IOLowPort = HAL_OSPIM_IOPORT_2_LOW;
|
|
ospi_mgr_cfg.IOHighPort = HAL_OSPIM_IOPORT_2_HIGH;
|
|
}
|
|
|
|
ospi_mgr_cfg.Req2AckTime = 1;
|
|
|
|
if (HAL_OSPIM_Config(&dev_data->hospi, &ospi_mgr_cfg,
|
|
HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
|
LOG_ERR("OSPI M config failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* OCTOSPI2 delay block init Function */
|
|
HAL_OSPI_DLYB_CfgTypeDef ospi_delay_block_cfg = {0};
|
|
|
|
ospi_delay_block_cfg.Units = 56;
|
|
ospi_delay_block_cfg.PhaseSel = 2;
|
|
if (HAL_OSPI_DLYB_SetConfig(&dev_data->hospi, &ospi_delay_block_cfg) != HAL_OK) {
|
|
LOG_ERR("OSPI DelayBlock failed");
|
|
return -EIO;
|
|
}
|
|
#endif /* CONFIG_SOC_SERIES_STM32U5X */
|
|
|
|
/* Reset NOR flash memory : still with the SPI/STR config for the NOR */
|
|
if (stm32_ospi_mem_reset(dev) != 0) {
|
|
LOG_ERR("OSPI reset failed");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Check if memory is ready in the SPI/STR mode */
|
|
if (stm32_ospi_mem_ready(&dev_data->hospi,
|
|
OSPI_SPI_MODE, OSPI_STR_TRANSFER) != 0) {
|
|
LOG_ERR("OSPI memory not ready");
|
|
return -EIO;
|
|
}
|
|
|
|
if (stm32_ospi_config_mem(dev) != 0) {
|
|
LOG_ERR("OSPI mode not config'd (%u rate %u)",
|
|
dev_cfg->data_mode, dev_cfg->data_rate);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Send the instruction to read the SFDP */
|
|
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 = ospi_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_DBG("%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_DBG("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 = ospi_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 */
|
|
|
|
/* 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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define OSPI_FLASH_MODULE(drv_id, flash_id) \
|
|
(DT_DRV_INST(drv_id), ospi_nor_flash_##flash_id)
|
|
|
|
static void flash_stm32_ospi_irq_config_func(const struct device *dev);
|
|
|
|
#define STM32_OSPI_NODE DT_INST_PARENT(0)
|
|
|
|
PINCTRL_DT_DEFINE(STM32_OSPI_NODE);
|
|
|
|
static const struct flash_stm32_ospi_config flash_stm32_ospi_cfg = {
|
|
.regs = (OCTOSPI_TypeDef *)DT_REG_ADDR(STM32_OSPI_NODE),
|
|
.pclken = {
|
|
.enr = DT_CLOCKS_CELL(STM32_OSPI_NODE, bits),
|
|
.bus = DT_CLOCKS_CELL(STM32_OSPI_NODE, bus)
|
|
},
|
|
.irq_config = flash_stm32_ospi_irq_config_func,
|
|
.flash_size = DT_INST_PROP(0, size) / 8U,
|
|
.max_frequency = DT_INST_PROP(0, ospi_max_frequency),
|
|
.data_mode = DT_INST_PROP(0, spi_bus_width), /* SPI or OPI */
|
|
.data_rate = DT_INST_PROP(0, data_rate), /* DTR or STR */
|
|
.pcfg = PINCTRL_DT_DEV_CONFIG_GET(STM32_OSPI_NODE),
|
|
#if STM32_OSPI_RESET_GPIO
|
|
.reset = GPIO_DT_SPEC_INST_GET(0, reset_gpios),
|
|
#endif /* STM32_OSPI_RESET_GPIO */
|
|
#if DT_NODE_HAS_PROP(DT_INST(0, st_stm32_ospi_nor), sfdp_bfp)
|
|
.sfdp_bfp = DT_INST_PROP(0, sfdp_bfp),
|
|
#endif /* sfdp_bfp */
|
|
};
|
|
|
|
static struct flash_stm32_ospi_data flash_stm32_ospi_dev_data = {
|
|
.hospi = {
|
|
.Instance = (OCTOSPI_TypeDef *)DT_REG_ADDR(STM32_OSPI_NODE),
|
|
.Init = {
|
|
.FifoThreshold = STM32_OSPI_FIFO_THRESHOLD,
|
|
.SampleShifting = HAL_OSPI_SAMPLE_SHIFTING_NONE,
|
|
.ChipSelectHighTime = 1,
|
|
.ClockMode = HAL_OSPI_CLOCK_MODE_0,
|
|
},
|
|
},
|
|
};
|
|
|
|
DEVICE_DT_INST_DEFINE(0, &flash_stm32_ospi_init, NULL,
|
|
&flash_stm32_ospi_dev_data, &flash_stm32_ospi_cfg,
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
|
|
&flash_stm32_ospi_driver_api);
|
|
|
|
static void flash_stm32_ospi_irq_config_func(const struct device *dev)
|
|
{
|
|
IRQ_CONNECT(DT_IRQN(STM32_OSPI_NODE), DT_IRQ(STM32_OSPI_NODE, priority),
|
|
flash_stm32_ospi_isr, DEVICE_DT_INST_GET(0), 0);
|
|
irq_enable(DT_IRQN(STM32_OSPI_NODE));
|
|
}
|