/****************************************************************************
 * arch/arm/src/stm32h7/stm32h743xx_flash.c
 *
 *   Copyright (C) 2019 Gregory Nutt. All rights reserved.
 *   Authors: Gregory Nutt <gnutt@nuttx.org>
 *            David Sidrane <david.sidrane@nscdg.com>
 *
 * Ported from stm32f7_flash.c, this is the original license:
 *
 *   Copyright (C) 2018 Wolpike LLC. All rights reserved.
 *   Author: Evgeniy Bobkov <evgen@wolpike.com>
 *
 * Ported from stm32f20xxf40xx_flash.c, this is the original license:
 *
 *   Copyright (C) 2011 Uros Platise. All rights reserved.
 *   Author: Uros Platise <uros.platise@isotel.eu>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/* Provides standard flash access functions, to be used by the flash mtd
 * driver.  The interface is defined in the include/nuttx/progmem.h
 *
 * Requirements during write/erase operations on FLASH:
 *  - HSI must be ON.
 *  - Low Power Modes are not permitted during write/erase
 */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>

#include <stdbool.h>
#include <assert.h>
#include <errno.h>

#include "barriers.h"

#include "hardware/stm32_flash.h"
#include "arm_internal.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Flash size is known from the chip selection:
 *
 *   When CONFIG_STM32H7_FLASH_OVERRIDE_DEFAULT is set the
 *   CONFIG_STM32H7_FLASH_CONFIG_x selects the default FLASH size based on
 *   the chip part number. This value can be overridden with
 *   CONFIG_STM32H7_FLASH_OVERRIDE_x
 *
 *   Parts STM32H74xxE have 512Kb of FLASH
 *   Parts STM32H74xxG have 1024Kb of FLASH
 *   Parts STM32H74xxI have 2048Kb of FLASH
 *
 *   N.B. Only Single bank mode is supported
 */

#define _K(x) ((x)*1024)
#define FLASH_SECTOR_SIZE  _K(128)
#define FLASH_PAGE_SIZE        32

#if !defined(CONFIG_STM32H7_FLASH_OVERRIDE_DEFAULT) && \
    !defined(CONFIG_STM32H7_FLASH_OVERRIDE_B) && \
    !defined(CONFIG_STM32H7_FLASH_OVERRIDE_G) && \
    !defined(CONFIG_STM32H7_FLASH_OVERRIDE_I) && \
    !defined(CONFIG_STM32H7_FLASH_CONFIG_B) && \
    !defined(CONFIG_STM32H7_FLASH_CONFIG_G) && \
    !defined(CONFIG_STM32H7_FLASH_CONFIG_I)
#  define CONFIG_STM32H7_FLASH_OVERRIDE_B
#  warning "Flash size not defined defaulting to 128KiB (B)"
#endif

#if !defined(CONFIG_STM32H7_FLASH_OVERRIDE_DEFAULT)

#  undef CONFIG_STM32H7_FLASH_CONFIG_B
#  undef CONFIG_STM32H7_FLASH_CONFIG_G
#  undef CONFIG_STM32H7_FLASH_CONFIG_I

#  if defined(CONFIG_STM32H7_FLASH_OVERRIDE_B)

#    define CONFIG_STM32H7_FLASH_CONFIG_B

#  elif defined(CONFIG_STM32H7_FLASH_OVERRIDE_G)

#    define CONFIG_STM32H7_FLASH_CONFIG_G

#  elif defined(CONFIG_STM32H7_FLASH_OVERRIDE_I)

#    define CONFIG_STM32H7_FLASH_CONFIG_I

#  endif
#endif

#if defined(CONFIG_STM32H7_FLASH_CONFIG_B)

#  define STM32_FLASH_NBLOCKS      1
#  define STM32_FLASH_SIZE        _K(1 * 128)

#elif defined(CONFIG_STM32H7_FLASH_CONFIG_G)

#  define STM32_FLASH_NBLOCKS      8
#  define STM32_FLASH_SIZE        _K(8 * 128)

#elif defined(CONFIG_STM32H7_FLASH_CONFIG_I)

#  define STM32_FLASH_NBLOCKS      16
#  define STM32_FLASH_SIZE        _K(16 * 128)
#  define STM32_DUAL_BANK         1

#endif

#ifndef CONFIG_STM32H7_FLASH_CR_PSIZE
#define FLASH_CR_PSIZE FLASH_CR_PSIZE_X64
#else
#define FLASH_CR_PSIZE (CONFIG_STM32H7_FLASH_CR_PSIZE << FLASH_CR_PSIZE_SHIFT)
#endif

#define FLASH_KEY1           0x45670123
#define FLASH_KEY2           0xcdef89ab
#define FLASH_OPTKEY1        0x08192a3b
#define FLASH_OPTKEY2        0x4c5d6e7f
#define FLASH_ERASEDVALUE    0xffu
#define FLASH_ERASEDVALUE_DW 0xffffffffu
#define PROGMEM_NBLOCKS      STM32_FLASH_NBLOCKS
#define FLASH_NPAGES         (STM32_FLASH_SIZE / FLASH_PAGE_SIZE)

#define FLASH_TIMEOUT_VALUE 5000000  /* 5s */

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct stm32h7_flash_priv_s
{
  mutex_t  lock;    /* Bank exclusive */
  uint32_t ifbase;  /* FLASHIF interface base address */
  uint32_t base;    /* FLASH base address */
  uint32_t stblock; /* The first Block Number */
  uint32_t stpage;  /* The first Page Number */
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct stm32h7_flash_priv_s stm32h7_flash_bank1_priv =
{
  .lock    = NXMUTEX_INITIALIZER,
  .ifbase  = STM32_FLASHIF_BASE + STM32_FLASH_BANK1_OFFSET,
  .base    = STM32_FLASH_BANK1,
  .stblock = 0,
  .stpage  = 0,
};
#if STM32_DUAL_BANK
static struct stm32h7_flash_priv_s stm32h7_flash_bank2_priv =
{
  .lock    = NXMUTEX_INITIALIZER,
  .ifbase  = STM32_FLASHIF_BASE + STM32_FLASH_BANK2_OFFSET,
  .base    = STM32_FLASH_BANK2,
  .stblock = PROGMEM_NBLOCKS / 2,
  .stpage  = FLASH_NPAGES / 2,
};
#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: stm32h7_flash_getreg32
 *
 * Description:
 *   Get a 32-bit register value by offset
 *
 ****************************************************************************/

static inline uint32_t stm32h7_flash_getreg32(struct stm32h7_flash_priv_s
                                            *priv, uint32_t offset)
{
  return getreg32(priv->ifbase + offset);
}

/****************************************************************************
 * Name: stm32h7_flash_putreg32
 *
 * Description:
 *  Put a 32-bit register value by offset
 *
 ****************************************************************************/

static inline void stm32h7_flash_putreg32(struct stm32h7_flash_priv_s
                                          *priv, uint32_t offset,
                                          uint32_t value)
{
  putreg32(value, priv->ifbase + offset);
}

/****************************************************************************
 * Name: stm32h7_flash_modifyreg32
 *
 * Description:
 *   Modify a 32-bit register value by offset
 *
 ****************************************************************************/

static inline void stm32h7_flash_modifyreg32(struct stm32h7_flash_priv_s
                                             *priv, uint32_t offset,
                                             uint32_t clearbits,
                                             uint32_t setbits)
{
  modifyreg32(priv->ifbase + offset, clearbits, setbits);
}

/****************************************************************************
 * Name: stm32h7_unlock_flash
 *
 * Description:
 *   Unlock the Bank
 *
 ****************************************************************************/

static void stm32h7_unlock_flash(struct stm32h7_flash_priv_s *priv)
{
  while (stm32h7_flash_getreg32(priv, STM32_FLASH_SR1_OFFSET) & FLASH_SR_BSY)
    {
    }

  if (stm32h7_flash_getreg32(priv, STM32_FLASH_CR1_OFFSET) & FLASH_CR_LOCK)
    {
      /* Unlock sequence */

      stm32h7_flash_putreg32(priv, STM32_FLASH_KEYR1_OFFSET, FLASH_KEY1);
      stm32h7_flash_putreg32(priv, STM32_FLASH_KEYR1_OFFSET, FLASH_KEY2);
    }
}

/****************************************************************************
 * Name: stm32h7_lock_flash
 *
 * Description:
 *   Lock the Bank
 *
 ****************************************************************************/

static void stm32h7_lock_flash(struct stm32h7_flash_priv_s *priv)
{
  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET, 0, FLASH_CR_LOCK);
}

/****************************************************************************
 * Name: stm32h7_flash_size
 *
 * Description:
 *   Returns the size in bytes of FLASH
 *
 ****************************************************************************/

static inline uint32_t stm32h7_flash_size(
    struct stm32h7_flash_priv_s *priv)
{
  return FLASH_SECTOR_SIZE * PROGMEM_NBLOCKS;
}

/****************************************************************************
 * Name: stm32h7_flash_bank
 *
 * Description:
 *   Returns the priv pointer to the correct bank
 *
 ****************************************************************************/

static inline
struct stm32h7_flash_priv_s * stm32h7_flash_bank(size_t address)
{
  struct stm32h7_flash_priv_s *priv = NULL;

  uint32_t bank_size;
#ifdef STM32_DUAL_BANK
  bank_size = stm32h7_flash_size(priv) / 2;
#else
  bank_size = stm32h7_flash_size(priv);
#endif

  if (address >= stm32h7_flash_bank1_priv.base &&
      address < stm32h7_flash_bank1_priv.base + bank_size)
    {
      priv = &stm32h7_flash_bank1_priv;
    }

#ifdef STM32_DUAL_BANK
  else if (address >= stm32h7_flash_bank2_priv.base &&
           address < stm32h7_flash_bank2_priv.base + bank_size)
    {
      priv = &stm32h7_flash_bank2_priv;
    }
#endif

  return priv;
}

/****************************************************************************
 * Name: stm32h7_israngeerased
 *
 * Description:
 *   Returns count of non-erased words
 *
 ****************************************************************************/

static int stm32h7_israngeerased(size_t startaddress, size_t size)
{
  uint32_t *addr;
  uint8_t *baddr;
  size_t count = 0;
  size_t bwritten = 0;

  if (!stm32h7_flash_bank(startaddress) ||
      !stm32h7_flash_bank(startaddress + size - 1))
    {
      return -EIO;
    }

  addr = (uint32_t *)startaddress;
  while (count + 4 <= size)
    {
      if (getreg32(addr) != FLASH_ERASEDVALUE_DW)
        {
          bwritten++;
        }

      addr++;
      count += 4;
    }

  baddr = (uint8_t *)addr;
  while (count < size)
    {
      if (getreg8(baddr) != FLASH_ERASEDVALUE)
        {
          bwritten++;
        }

      baddr++;
      count++;
    }

  return bwritten;
}

/****************************************************************************
 * Name: stm32h7_wait_for_last_operation()
 *
 * Description:
 *   Wait for last write/erase operation to finish
 *   Return error in case of timeout
 *
 * Input Parameters:
 *   priv  - Flash bank based config
 *
 * Returned Value:
 *     Zero or error value
 *
 *     ETIME:  Timeout while waiting for previous write/erase operation to
 *             complete
 *
 ****************************************************************************/

static int stm32h7_wait_for_last_operation(struct stm32h7_flash_priv_s
                                           *priv)
{
  int i;
  bool timeout = true;

  ARM_DSB();

  for (i = 0; i < FLASH_TIMEOUT_VALUE; i++)
    {
      if (!(stm32h7_flash_getreg32(priv, STM32_FLASH_SR1_OFFSET) &
          (FLASH_SR_QW | FLASH_SR_BSY | FLASH_SR_WBNE)))
        {
          timeout = false;
          break;
        }

      up_udelay(1);
    }

  if (timeout)
    {
      return -EBUSY;
    }

  return 0;
}

/****************************************************************************
 * Name: stm32h7_unlock_flashopt
 *
 * Description:
 *   Unlock the flash option bytes
 *   Returns true if the flash was locked before, false otherwise
 *
 ****************************************************************************/

static bool stm32h7_unlock_flashopt(struct stm32h7_flash_priv_s *priv)
{
  bool ret = false;

  while (stm32h7_flash_getreg32(priv, STM32_FLASH_SR1_OFFSET) & FLASH_SR_BSY)
    {
    }

  if (stm32h7_flash_getreg32(priv, STM32_FLASH_OPTCR_OFFSET) &
                             FLASH_OPTCR_OPTLOCK)
    {
      /* Unlock sequence */

      stm32h7_flash_putreg32(priv, STM32_FLASH_OPTKEYR_OFFSET,
                             FLASH_OPTKEY1);
      stm32h7_flash_putreg32(priv, STM32_FLASH_OPTKEYR_OFFSET,
                             FLASH_OPTKEY2);

      /* Was locked before and now unlocked */

      ret = true;
    }

  return ret;
}

/****************************************************************************
 * Name: stm32h7_lock_flashopt
 *
 * Description:
 *   Lock the flash option bytes
 *
 ****************************************************************************/

static void stm32h7_lock_flashopt(struct stm32h7_flash_priv_s *priv)
{
  stm32h7_flash_modifyreg32(priv, STM32_FLASH_OPTCR_OFFSET, 0,
                            FLASH_OPTCR_OPTLOCK);
}

/****************************************************************************
 * Name: stm32h7_save_flashopt
 *
 * Description:
 *   Save the flash option bytes to non-volatile storage.
 *
 ****************************************************************************/

static void stm32h7_save_flashopt(struct stm32h7_flash_priv_s *priv)
{
  while (stm32h7_flash_getreg32(priv, STM32_FLASH_SR1_OFFSET) &
        (FLASH_SR_BSY | FLASH_SR_CRCBUSY))
    {
    }
  while (stm32h7_flash_getreg32(priv, STM32_FLASH_SR2_OFFSET) &
        (FLASH_SR_BSY | FLASH_SR_CRCBUSY))
    {
    }

  /* Can only write flash options if the option control reg is unlocked */

  if (!(stm32h7_flash_getreg32(priv, STM32_FLASH_OPTCR_OFFSET) &
                               FLASH_OPTCR_OPTLOCK))
    {
      stm32h7_flash_modifyreg32(priv, STM32_FLASH_OPTCR_OFFSET, 0,
                                FLASH_OPTCR_OPTSTRT);
    }

  /* Wait for the update to complete */

  while (stm32h7_flash_getreg32(priv, STM32_FLASH_OPTSR_CUR_OFFSET) &
                                      FLASH_OPTSR_BUSYV)
    {
    }
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: stm32h7_flash_unlock
 *
 * Description:
 *   Unlocks a bank
 *
 ****************************************************************************/

int stm32h7_flash_unlock(size_t addr)
{
  int ret = -ENODEV;
  struct stm32h7_flash_priv_s *priv = stm32h7_flash_bank(addr);

  if (priv)
    {
      ret = nxmutex_lock(&priv->lock);
      if (ret < 0)
        {
          return ret;
        }

      stm32h7_unlock_flash(priv);
      nxmutex_unlock(&priv->lock);
    }

  return ret;
}

/****************************************************************************
 * Name: stm32h7_flash_lock
 *
 * Description:
 *   Locks a bank
 *
 ****************************************************************************/

int stm32h7_flash_lock(size_t addr)
{
  int ret = -ENODEV;
  struct stm32h7_flash_priv_s *priv = stm32h7_flash_bank(addr);

  if (priv)
    {
      ret = nxmutex_lock(&priv->lock);
      if (ret < 0)
        {
          return ret;
        }

      stm32h7_lock_flash(priv);
      nxmutex_unlock(&priv->lock);
    }

  return ret;
}

/****************************************************************************
 * Name: stm32h7_flash_writeprotect
 *
 * Description:
 *   Enable or disable the write protection of a flash sector.
 *
 ****************************************************************************/

int stm32h7_flash_writeprotect(size_t block, bool enabled)
{
  struct stm32h7_flash_priv_s *priv;
  uint32_t setbits   = 0;
  uint32_t clearbits = 0;
  int      rv        = -ENODEV;

  if (block >= PROGMEM_NBLOCKS)
    {
      return -EFAULT;
    }

  priv = stm32h7_flash_bank(STM32_FLASH_BANK1 + (block * FLASH_SECTOR_SIZE));

  if (priv)
    {
      if (enabled)
        {
          clearbits = 1 << block % (STM32_FLASH_NBLOCKS / 2);
        }
      else
        {
          setbits = 1 << block % (STM32_FLASH_NBLOCKS / 2);
        }

      stm32h7_flash_modifyreg32(priv, STM32_FLASH_WPSN_PRG1R_OFFSET,
                                clearbits, setbits);
      rv  = OK;
    }

  return rv;
}

/****************************************************************************
 * Name: stm32h7_flash_getopt
 *
 * Description:
 *   Returns the current flash option bytes from the FLASH_OPTSR_CR register.
 *
 ****************************************************************************/

uint32_t stm32h7_flash_getopt(void)
{
  struct stm32h7_flash_priv_s *priv;
  priv = stm32h7_flash_bank(STM32_FLASH_BANK1);
  if (priv)
    {
      return stm32h7_flash_getreg32(priv, STM32_FLASH_OPTSR_CUR_OFFSET);
    }

  return 0;
}

/****************************************************************************
 * Name: stm32h7_flash_optmodify
 *
 * Description:
 *   Modifies the current flash option bytes, given bits to set and clear.
 *
 ****************************************************************************/

void stm32h7_flash_optmodify(uint32_t clear, uint32_t set)
{
  struct stm32h7_flash_priv_s *priv;
  bool was_locked;

  priv = stm32h7_flash_bank(STM32_FLASH_BANK1);
  if (priv)
    {
      was_locked = stm32h7_unlock_flashopt(priv);
      stm32h7_flash_modifyreg32(priv, STM32_FLASH_OPTSR_PRG_OFFSET,
                                clear, set);
      stm32h7_save_flashopt(priv);
      if (was_locked)
        {
          stm32h7_lock_flashopt(priv);
        }
    }
}

/****************************************************************************
 * Name: stm32h7_flash_swapbanks
 *
 * Description:
 *   Swaps banks 1 and 2 in the processor's memory map.  Takes effect
 *   the next time the system is reset.
 *
 ****************************************************************************/

void stm32h7_flash_swapbanks(void)
{
  uint32_t opts = stm32h7_flash_getopt();
  if (opts & FLASH_OPTCR_SWAPBANK)
    {
      stm32h7_flash_optmodify(FLASH_OPTCR_SWAPBANK, 0);
    }
  else
    {
      stm32h7_flash_optmodify(0, FLASH_OPTCR_SWAPBANK);
    }
}

size_t up_progmem_pagesize(size_t page)
{
  return FLASH_PAGE_SIZE;
}

ssize_t up_progmem_getpage(size_t addr)
{
  struct stm32h7_flash_priv_s *priv;

  priv = stm32h7_flash_bank(addr);

  if (priv == NULL)
    {
      return -EFAULT;
    }

  return  priv->stpage + ((addr - priv->base) / FLASH_PAGE_SIZE);
}

size_t up_progmem_getaddress(size_t page)
{
  struct stm32h7_flash_priv_s *priv;
  if (page >= FLASH_NPAGES)
    {
      return SIZE_MAX;
    }

  priv = stm32h7_flash_bank(STM32_FLASH_BANK1 + (page * FLASH_PAGE_SIZE));
  return priv->base + (page - priv->stpage) * FLASH_PAGE_SIZE;
}

size_t up_progmem_neraseblocks(void)
{
  return PROGMEM_NBLOCKS;
}

bool up_progmem_isuniform(void)
{
  return true;
}

ssize_t up_progmem_ispageerased(size_t page)
{
  size_t addr;
  size_t count;
  size_t bwritten = 0;

  if (page >= FLASH_NPAGES)
    {
      return -EFAULT;
    }

  /* Verify */

  for (addr = up_progmem_getaddress(page), count = up_progmem_pagesize(page);
       count; count--, addr++)
    {
      if (getreg8(addr) != FLASH_ERASEDVALUE)
        {
          bwritten++;
        }
    }

  return bwritten;
}

size_t up_progmem_erasesize(size_t block)
{
  return FLASH_SECTOR_SIZE;
}

ssize_t up_progmem_eraseblock(size_t block)
{
  struct stm32h7_flash_priv_s *priv;
  int ret;
  size_t block_address = STM32_FLASH_BANK1 + (block * FLASH_SECTOR_SIZE);

  if (block >= PROGMEM_NBLOCKS)
    {
      return -EFAULT;
    }

  priv = stm32h7_flash_bank(block_address);

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return (ssize_t)ret;
    }

  if (stm32h7_wait_for_last_operation(priv))
    {
      ret = -EIO;
      goto exit_with_lock;
    }

  /* Get flash ready and begin erasing single block */

  stm32h7_unlock_flash(priv);

  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET, 0, FLASH_CR_SER);
  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET, FLASH_CR_SNB_MASK,
                            FLASH_CR_SNB(block - priv->stblock));

  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET, 0, FLASH_CR_START);

  /* Wait for erase operation to complete */

  if (stm32h7_wait_for_last_operation(priv))
    {
      ret = -EIO;
      goto exit_with_unlock;
    }

  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET, FLASH_CR_SER, 0);
  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET, FLASH_CR_SNB_MASK,
                            0);

  ret = 0;

  up_invalidate_dcache(block_address, block_address + FLASH_SECTOR_SIZE);

exit_with_unlock:
  stm32h7_lock_flash(priv);

exit_with_lock:
  nxmutex_unlock(&priv->lock);

  /* Verify */

  if (ret == 0 &&
      stm32h7_israngeerased(block_address, up_progmem_erasesize(block)) == 0)
    {
      ret = up_progmem_erasesize(block); /* success */
    }
  else
    {
      ret = -EIO; /* failure */
    }

  return ret;
}

ssize_t up_progmem_write(size_t addr, const void *buf, size_t count)
{
  struct stm32h7_flash_priv_s *priv;
  uint32_t     *fp;
  uint32_t     *rp;
  uint32_t     *ll        = (uint32_t *)buf;
  size_t       faddr;
  size_t       written    = count;
  int          ret;
  const size_t pagesize   = up_progmem_pagesize(0); /* 256 bit, 32 bytes per page */
  const size_t llperpage  = pagesize / sizeof(uint32_t);
  size_t       pcount     = count / pagesize;
  uint32_t     sr;

  priv = stm32h7_flash_bank(addr);

  if (priv == NULL)
    {
      return -EFAULT;
    }

  /* Check for valid address range */

  if (addr < priv->base ||
      addr + count > priv->base + (STM32_FLASH_SIZE / 2))
    {
      return -EFAULT;
    }

  ret = nxmutex_lock(&priv->lock);
  if (ret < 0)
    {
      return (ssize_t)ret;
    }

  /* Check address and count alignment */

  DEBUGASSERT(!(addr % pagesize));
  DEBUGASSERT(!(count % pagesize));

  if (stm32h7_wait_for_last_operation(priv))
    {
      written = -EIO;
      goto exit_with_lock;
    }

  /* Get flash ready and begin flashing */

  stm32h7_unlock_flash(priv);

  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET,
                            FLASH_CR_PSIZE_MASK, FLASH_CR_PSIZE);

  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET, 0, FLASH_CR_PG);

  for (ll = (uint32_t *)buf, faddr = addr; pcount;
      pcount -= 1, ll += llperpage, faddr += pagesize)
    {
      fp = (uint32_t *)faddr;
      rp = ll;

      ARM_DSB();
      ARM_ISB();

      /* Write 8 32 bit word and wait to complete */

      *fp++ = *rp++;
      *fp++ = *rp++;
      *fp++ = *rp++;
      *fp++ = *rp++;
      *fp++ = *rp++;
      *fp++ = *rp++;
      *fp++ = *rp++;
      *fp++ = *rp++;

      /* Data synchronous Barrier (DSB) just after the write operation. This
       * will force the CPU to respect the sequence of instruction (no
       * optimization).
       */

      ARM_DSB();
      ARM_ISB();

      if (stm32h7_wait_for_last_operation(priv))
        {
          written = -EIO;
          goto exit_with_unlock;
        }

      sr = stm32h7_flash_getreg32(priv, STM32_FLASH_SR1_OFFSET);
      if (sr & (FLASH_SR_SNECCERR | FLASH_SR_DBECCERR))
        {
          stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET,
                                    FLASH_CR_PG,
                                    0);

          stm32h7_flash_modifyreg32(priv, STM32_FLASH_CCR1_OFFSET,
                                    0, ~0);
          ret = -EIO;
          goto exit_with_unlock;
        }
    }

  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CR1_OFFSET, FLASH_CR_PG, 0);

  stm32h7_flash_modifyreg32(priv, STM32_FLASH_CCR1_OFFSET,
                            0, ~0);
exit_with_unlock:
  stm32h7_lock_flash(priv);

  /* Verify */

  if (written > 0)
    {
      for (ll = (uint32_t *)buf, faddr = addr, pcount = count / pagesize;
          pcount; pcount -= 1, ll += llperpage, faddr += pagesize)
        {
          fp = (uint32_t *)faddr;
          rp = ll;

          stm32h7_flash_modifyreg32(priv, STM32_FLASH_CCR1_OFFSET,
                                    0, ~0);
          if ((*fp++ != *rp++) ||
              (*fp++ != *rp++) ||
              (*fp++ != *rp++) ||
              (*fp++ != *rp++) ||
              (*fp++ != *rp++) ||
              (*fp++ != *rp++) ||
              (*fp++ != *rp++) ||
              (*fp++ != *rp++))
            {
              written = -EIO;
              break;
            }

          sr = stm32h7_flash_getreg32(priv, STM32_FLASH_SR1_OFFSET);
          if (sr & (FLASH_SR_SNECCERR | FLASH_SR_DBECCERR))
            {
              written = -EIO;
              break;
            }
        }

      stm32h7_flash_modifyreg32(priv, STM32_FLASH_CCR1_OFFSET,
                                0, ~0);
    }

exit_with_lock:
  nxmutex_unlock(&priv->lock);
  return written;
}

uint8_t up_progmem_erasestate(void)
{
  return FLASH_ERASEDVALUE;
}
