Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions drivers/flash/Kconfig.stm32_ospi
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,18 @@ config FLASH_STM32_OSPI
$(DT_STM32_OCTOSPI_HAS_DMA)
help
Enable OSPI-NOR support on the STM32 family of processors.

if FLASH_STM32_OSPI

config FLASH_STM32_ERRATUM_U5_STOP2_3_CORRUPT_READ
bool "Workaround for corrupted reads after Stop 2/3 exit"
default y if PM
depends on SOC_STM32U595XX || SOC_STM32U599XX || SOC_STM32U5A5XX || SOC_STM32U5A9XX
help
Enable workaround for erratum "OCTOSPI external memory
read issue after exiting Stop 2 or Stop 3 mode when using
DQS and delay block" documented in section 2.5.13 of errata
sheet ES0553 revision 5.


endif # FLASH_STM32_OSPI
147 changes: 134 additions & 13 deletions drivers/flash/flash_stm32_ospi.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
#include <zephyr/dt-bindings/flash_controller/ospi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/irq.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/pm/policy.h>

#include "spi_nor.h"
#include "jesd216.h"
Expand Down Expand Up @@ -180,8 +183,30 @@ struct flash_stm32_ospi_data {
#if STM32_OSPI_USE_DMA
struct stream dma;
#endif /* STM32_OSPI_USE_DMA */
#if CONFIG_FLASH_STM32_ERRATUM_U5_STOP2_3_CORRUPT_READ
bool errata_u59_2_5_13;
#endif
};


static inline void ospi_pm_get(const struct device *dev)
{
#ifdef CONFIG_PM_DEVICE_RUNTIME
(void)pm_device_runtime_get(dev);
#endif

pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
}

static inline void ospi_pm_put(const struct device *dev)
{
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);

#ifdef CONFIG_PM_DEVICE_RUNTIME
(void)pm_device_runtime_put(dev);
#endif
}

static inline void ospi_lock_thread(const struct device *dev)
{
struct flash_stm32_ospi_data *dev_data = dev->data;
Expand Down Expand Up @@ -222,6 +247,18 @@ static int ospi_read_access(const struct device *dev, OSPI_RegularCmdTypeDef *cm
struct flash_stm32_ospi_data *dev_data = dev->data;
HAL_StatusTypeDef hal_ret;

#if CONFIG_FLASH_STM32_ERRATUM_U5_STOP2_3_CORRUPT_READ
int err;

if (dev_data->errata_u59_2_5_13) {
dev_data->errata_u59_2_5_13 = false;
err = ospi_read_access(dev, cmd, data, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FRASTM can you confirm this is fine ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to the errata sheet, it is only needed after exiting stop 2 or 3. but then we need a way to get notified about that, which AFAIK may be possible with PM hooks. should we go that way?

if (err) {
return err;
}
}
Comment on lines +253 to +259
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just trigger a dummy read straight from the PM callback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if the user only intends to write for a certain time?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user is going to read at some point, and my understanding of the erratum is that the first read after Stop 2/3 exit will return incorrect data regardless of what has been done since Stop 2/3 exit (i.e., you'll always pay for the dummy read at some point)

If we could start an asynchronous read whose result is discarded, it would be better, but I don't think a synchronous one-byte read will add much latency to resume path (this is a wet finger guess - please prove me wrong if I am)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just trigger a dummy read straight from the PM callback?

just that we talk about the same thing. Your idea was to do the read in flash_stm32_ospi_pm_action()->flash_stm32_ospi_activate()?

The user is going to read at some point, ...

not necessarily. if you intent to simply store data for a certain time, there's no need to read before write after each wakeup.

#endif

LOG_DBG("Instruction 0x%x", cmd->Instruction);

cmd->NbData = size;
Expand Down Expand Up @@ -1197,6 +1234,8 @@ static int flash_stm32_ospi_erase(const struct device *dev, off_t addr,
.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD,
};

ospi_pm_get(dev);

if (stm32_ospi_mem_ready(dev_data,
dev_cfg->data_mode, dev_cfg->data_rate) != 0) {
LOG_ERR("Erase failed : flash busy");
Expand Down Expand Up @@ -1311,6 +1350,8 @@ static int flash_stm32_ospi_erase(const struct device *dev, off_t addr,
goto end_erase;

end_erase:
ospi_pm_put(dev);

ospi_unlock_thread(dev);

return ret;
Expand Down Expand Up @@ -1413,8 +1454,12 @@ static int flash_stm32_ospi_read(const struct device *dev, off_t addr,
LOG_DBG("OSPI: read %zu data", size);
ospi_lock_thread(dev);

ospi_pm_get(dev);

ret = ospi_read_access(dev, &cmd, data, size);

ospi_pm_put(dev);

ospi_unlock_thread(dev);

#endif /* CONFIG_STM32_MEMMAP */
Expand Down Expand Up @@ -1446,6 +1491,8 @@ static int flash_stm32_ospi_write(const struct device *dev, off_t addr,

ospi_lock_thread(dev);

ospi_pm_get(dev);

#ifdef CONFIG_STM32_MEMMAP
if (stm32_ospi_is_memorymap(dev)) {
/* Abort ongoing transfer to force CS high/BUSY deasserted */
Expand Down Expand Up @@ -1505,6 +1552,8 @@ static int flash_stm32_ospi_write(const struct device *dev, off_t addr,
ret = stm32_ospi_mem_ready(dev_data,
dev_cfg->data_mode, dev_cfg->data_rate);
if (ret != 0) {
ospi_pm_put(dev);

ospi_unlock_thread(dev);
LOG_ERR("OSPI: write not ready");
return -EIO;
Expand Down Expand Up @@ -1552,6 +1601,8 @@ static int flash_stm32_ospi_write(const struct device *dev, off_t addr,
goto end_write;

end_write:
ospi_pm_put(dev);

ospi_unlock_thread(dev);

return ret;
Expand Down Expand Up @@ -2160,6 +2211,49 @@ static int spi_nor_process_bfp(const struct device *dev,
return 0;
}

#ifdef CONFIG_PM_DEVICE

static int flash_stm32_ospi_suspend(const struct device *dev)
{
const struct flash_stm32_ospi_config *dev_cfg = dev->config;

#if DT_CLOCKS_HAS_NAME(STM32_OSPI_NODE, ospi_mgr)
if (clock_control_off(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE),
(clock_control_subsys_t) &dev_cfg->pclken_mgr) != 0) {
LOG_ERR("Could not disable OSPI Manager clock");
return -EIO;
}
#endif
if (clock_control_off(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE),
(clock_control_subsys_t) &dev_cfg->pclken) != 0) {
LOG_ERR("Could not enable OSPI clock");
return -EIO;
}
return 0;
}

#endif

static int flash_stm32_ospi_activate(const struct device *dev)
{
const struct flash_stm32_ospi_config *dev_cfg = dev->config;

/* 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 DT_CLOCKS_HAS_NAME(STM32_OSPI_NODE, ospi_mgr)
if (clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE),
(clock_control_subsys_t) &dev_cfg->pclken_mgr) != 0) {
LOG_ERR("Could not enable OSPI Manager clock");
return -EIO;
}
#endif
return 0;
}

static int flash_stm32_ospi_init(const struct device *dev)
{
const struct flash_stm32_ospi_config *dev_cfg = dev->config;
Expand Down Expand Up @@ -2289,12 +2383,12 @@ static int flash_stm32_ospi_init(const struct device *dev)

#endif /* STM32_OSPI_USE_DMA */

/* 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;
ret = flash_stm32_ospi_activate(dev);
if (ret < 0) {
LOG_ERR("OSPI clock activation failed (%d)", ret);
return ret;
}

/* Alternate clock config for peripheral if any */
#if DT_CLOCKS_HAS_NAME(STM32_OSPI_NODE, ospi_ker)
if (clock_control_configure(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE),
Expand All @@ -2317,13 +2411,6 @@ static int flash_stm32_ospi_init(const struct device *dev)
return -EIO;
}
#endif
#if DT_CLOCKS_HAS_NAME(STM32_OSPI_NODE, ospi_mgr)
if (clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE),
(clock_control_subsys_t) &dev_cfg->pclken_mgr) != 0) {
LOG_ERR("Could not enable OSPI Manager clock");
return -EIO;
}
#endif

for (; prescaler <= STM32_OSPI_CLOCK_PRESCALER_MAX; prescaler++) {
uint32_t clk = STM32_OSPI_CLOCK_COMPUTE(ahb_clock_freq, prescaler);
Expand Down Expand Up @@ -2593,9 +2680,40 @@ static int flash_stm32_ospi_init(const struct device *dev)
dev_cfg->flash_size);
#endif /* CONFIG_STM32_MEMMAP */

#ifdef CONFIG_PM_DEVICE_RUNTIME
(void)pm_device_runtime_enable(dev);
#endif

return 0;
}

#ifdef CONFIG_PM_DEVICE

static int flash_stm32_ospi_pm_action(const struct device *dev, enum pm_device_action action)
{
struct flash_stm32_ospi_data *dev_data = dev->data;

int err;

switch (action) {
case PM_DEVICE_ACTION_RESUME:
#if CONFIG_FLASH_STM32_ERRATUM_U5_STOP2_3_CORRUPT_READ
dev_data->errata_u59_2_5_13 = true;
#endif
err = flash_stm32_ospi_activate(dev);
break;
case PM_DEVICE_ACTION_SUSPEND:
err = flash_stm32_ospi_suspend(dev);
break;
default:
return -ENOTSUP;
}

return err;
}

#endif

#if STM32_OSPI_USE_DMA
#define DMA_CHANNEL_CONFIG(node, dir) \
DT_DMAS_CELL_BY_NAME(node, dir, channel_config)
Expand Down Expand Up @@ -2692,7 +2810,10 @@ static struct flash_stm32_ospi_data flash_stm32_ospi_dev_data = {
OSPI_DMA_CHANNEL(STM32_OSPI_NODE, tx_rx)
};

DEVICE_DT_INST_DEFINE(0, &flash_stm32_ospi_init, NULL,
PM_DEVICE_DT_INST_DEFINE(0, flash_stm32_ospi_pm_action);


DEVICE_DT_INST_DEFINE(0, &flash_stm32_ospi_init, PM_DEVICE_DT_INST_GET(0),
&flash_stm32_ospi_dev_data, &flash_stm32_ospi_cfg,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&flash_stm32_ospi_driver_api);
Expand Down