This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 16b8a744174 arch/arm/stm32h5: Add OTP/eFuse support
16b8a744174 is described below

commit 16b8a744174264be833c402bc94465f013a10f0e
Author: Darryl Ring <[email protected]>
AuthorDate: Fri Sep 11 08:57:36 2026 -0700

    arch/arm/stm32h5: Add OTP/eFuse support
    
    This adds support for the OTP flash region in the STM32H5 via both
    low-level functions and an eFuse lower half driver.
    
    Assisted-by: Claude:claude-sonnet-5
    Signed-off-by: Darryl Ring <[email protected]>
---
 Documentation/platforms/arm/stm32h5/index.rst      |  57 +++
 arch/arm/src/stm32h5/CMakeLists.txt                |   4 +
 arch/arm/src/stm32h5/Kconfig                       |  46 +++
 arch/arm/src/stm32h5/Make.defs                     |   4 +
 .../src/stm32h5/hardware/stm32h5xxx_memorymap.h    |   1 +
 arch/arm/src/stm32h5/stm32_efuse.c                 | 347 +++++++++++++++++
 arch/arm/src/stm32h5/stm32_efuse.h                 |  79 ++++
 arch/arm/src/stm32h5/stm32_flash.h                 |  30 ++
 arch/arm/src/stm32h5/stm32h563xx_flash.c           | 418 +++++++++++++++++++--
 9 files changed, 952 insertions(+), 34 deletions(-)

diff --git a/Documentation/platforms/arm/stm32h5/index.rst 
b/Documentation/platforms/arm/stm32h5/index.rst
index ee1768faf9b..12e67af65bb 100644
--- a/Documentation/platforms/arm/stm32h5/index.rst
+++ b/Documentation/platforms/arm/stm32h5/index.rst
@@ -114,6 +114,16 @@ STM32H5 parts have a 2 KiB one-time programmable (OTP) 
area. It's organized
 into 32 blocks of 32 16-bit words. Each word can be successfully programmed 
once.
 Each block may be permanently locked at any point. Written words may be read.
 
+There are two APIs for it: a block-oriented API with locking, and a lower-level
+word API. An optional eFuse character device is built on top of the word API.
+
+Block API
+~~~~~~~~~
+
+This API is organized into 32 blocks of 32 16-bit words. Each word can be
+successfully programmed once. Each block may be permanently locked at any
+point. Written words may be read.
+
 Writing the same word more than once is unsupported. Doing so may cause 
corruption.
 Reading an unwritten word raises an exception.
 To simplify the programming model, the OTP API
@@ -135,6 +145,53 @@ of the block size and count when partitioning the OTP area 
for their needs.
 ``len`` is the number of bytes - not words. It has no alignment requirement. 
``offset`` is
 the offset in bytes. It must be a multiple of 4.
 
+Word API
+~~~~~~~~
+
+``CONFIG_STM32H5_OTP_WORD`` builds direct 16- or 32-bit word access to the
+OTP area, independent of the block API above -- there is no locking, and
+no relation between a "word" index here and the block API's byte
+``offset``:
+
+.. code:: c
+
+   int stm32_otp_word_read16(uint32_t word, uint16_t *value);
+   int stm32_otp_word_read32(uint32_t word, uint32_t *value);
+
+Unlike the block API, reading a blank (never programmed) word does not
+raise an exception: it legitimately reads back as
+``0xffff``/``0xffffffff``, which is returned through ``*value`` either
+way. Since whether a word has been written is known, that is reported
+through the return value: ``-ENODATA`` for a blank word, ``OK`` for one
+that holds real data. ``-EIO`` is returned only when a word's ECC does
+not check out at all, i.e. it is neither blank nor the value its own
+program operation wrote.
+
+With ``CONFIG_STM32H5_OTP_WRITE`` also set:
+
+.. code:: c
+
+   int stm32_otp_word_write16(uint32_t word, uint16_t value);
+   int stm32_otp_word_write32(uint32_t word, uint32_t value);
+
+Each word may be programmed once. Writing a word that already holds
+exactly the value requested is a harmless no-op that returns ``OK``.
+Writing a word that already holds a different value returns ``-EEXIST``.
+A hardware programming failure, or a post-write readback mismatch,
+returns ``-EIO``.
+
+eFuse Character Device
+~~~~~~~~~~~~~~~~~~~~~~
+
+``CONFIG_STM32H5_EFUSE`` (which selects ``CONFIG_STM32H5_OTP_WORD``)
+registers the OTP area as a NuttX efuse character device, by default
+``/dev/efuse``, built on the word API above. See
+:doc:`/components/drivers/character/efuse` for the ``EFUSEIOC_READ_FIELD``/
+``EFUSEIOC_WRITE_FIELD`` ioctl interface. Field bit offsets index into the
+flat bit space of the OTP area at 16 bits per word, the same as the word
+API's ``word`` index. Writing a field requires ``CONFIG_STM32H5_OTP_WRITE``;
+without it, writes are refused with ``-EPERM``.
+
 Clocks
 ------
 
diff --git a/arch/arm/src/stm32h5/CMakeLists.txt 
b/arch/arm/src/stm32h5/CMakeLists.txt
index 654455e0780..2ba3d3f6e8d 100644
--- a/arch/arm/src/stm32h5/CMakeLists.txt
+++ b/arch/arm/src/stm32h5/CMakeLists.txt
@@ -81,6 +81,10 @@ if(CONFIG_STM32_ICACHE)
   list(APPEND SRCS stm32_icache.c)
 endif()
 
+if(CONFIG_STM32H5_EFUSE)
+  list(APPEND SRCS stm32_efuse.c)
+endif()
+
 if(CONFIG_STM32_SPI)
   list(APPEND SRCS stm32_spi.c)
 endif()
diff --git a/arch/arm/src/stm32h5/Kconfig b/arch/arm/src/stm32h5/Kconfig
index d83f93f4d56..ec3780c926f 100644
--- a/arch/arm/src/stm32h5/Kconfig
+++ b/arch/arm/src/stm32h5/Kconfig
@@ -229,6 +229,52 @@ config STM32H5_IO_CONFIG_A
        bool
        default n
 
+comment "STM32H5 OTP Options"
+
+config STM32H5_OTP_WORD
+       bool "OTP word read/write"
+       default n
+       ---help---
+               Build stm32_otp_word_read16()/read32() (and, with
+               STM32H5_OTP_WRITE below, write16()/write32()) in
+               stm32h563xx_flash.c: direct 16- or 32-bit word access to the
+               STM32H5 OTP area (RM0481, "OTP area"), independent of the
+               existing block/locking API (stm32_otp_write()/
+               stm32_otp_read()/stm32_otp_getlockstatus()) in that same
+               file, which is always built.
+
+               The OTP area's base address and size are fixed by the
+               silicon, so they are plain constants in
+               arch/arm/src/stm32h5/hardware/stm32h5xxx_memorymap.h rather
+               than configuration here.
+
+if STM32H5_OTP_WORD
+
+config STM32H5_OTP_WRITE
+       bool "Allow OTP word programming"
+       default n
+       ---help---
+               Build the code that programs OTP words.
+
+               Programming is IRREVERSIBLE: OTP bits can only be cleared, and
+               because ECC is computed over each 16-bit word, a word that
+               already holds a different value can never be reprogrammed to
+               match -- see stm32_otp_word_write16() in
+               arch/arm/src/stm32h5/stm32h563xx_flash.c.  Reads work normally
+               either way.
+
+endif # STM32H5_OTP_WORD
+
+config STM32H5_EFUSE
+       bool "eFuse character device for the OTP area"
+       default n
+       depends on EFUSE
+       select STM32H5_OTP_WORD
+       ---help---
+               Expose the OTP area through the NuttX efuse interface as a
+               character device, by default /dev/efuse, built on
+               stm32_otp_word_read16()/write16() -- see stm32_efuse.c.
+
 comment "STM32H5 SRAM2 Options"
 
 config STM32_SBS
diff --git a/arch/arm/src/stm32h5/Make.defs b/arch/arm/src/stm32h5/Make.defs
index 4dead210dc0..2488ff9f82a 100644
--- a/arch/arm/src/stm32h5/Make.defs
+++ b/arch/arm/src/stm32h5/Make.defs
@@ -76,6 +76,10 @@ ifeq ($(CONFIG_STM32_ICACHE),y)
 CHIP_CSRCS += stm32_icache.c
 endif
 
+ifeq ($(CONFIG_STM32H5_EFUSE),y)
+CHIP_CSRCS += stm32_efuse.c
+endif
+
 ifeq ($(CONFIG_STM32_SPI),y)
 CHIP_CSRCS += stm32_spi.c
 endif
diff --git a/arch/arm/src/stm32h5/hardware/stm32h5xxx_memorymap.h 
b/arch/arm/src/stm32h5/hardware/stm32h5xxx_memorymap.h
index 33a90712aa0..a3c67989553 100644
--- a/arch/arm/src/stm32h5/hardware/stm32h5xxx_memorymap.h
+++ b/arch/arm/src/stm32h5/hardware/stm32h5xxx_memorymap.h
@@ -63,6 +63,7 @@
 
 #define STM32_SYSMEM_MEM     0x0bf80000
 #define STM32_OTP_BASE       0x08FFF000     /* One-Time Programmable (OTP) 
memory base address */
+#define STM32_OTP_SIZE       2048
 #define STM32_SYSMEM_UID     0x08FFF800     /* The 96-bit unique device 
identifier */
 #define STM32_SYSMEM_FSIZE   0x08FFF80C     /* Size of Flash memory in Kbytes. 
*/
 #define STM32_SYSMEM_PACKAGE 0x08FFF80E     /* Indicates the device's package 
type. */
diff --git a/arch/arm/src/stm32h5/stm32_efuse.c 
b/arch/arm/src/stm32h5/stm32_efuse.c
new file mode 100644
index 00000000000..93d4739c353
--- /dev/null
+++ b/arch/arm/src/stm32h5/stm32_efuse.c
@@ -0,0 +1,347 @@
+/****************************************************************************
+ * arch/arm/src/stm32h5/stm32_efuse.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * The STM32H5 one-time-programmable area exposed through the NuttX efuse
+ * interface.
+ *
+ * All the hardware access -- including the ICACHE/ECC NMI handling a blank
+ * OTP word needs -- lives in stm32_otp_word_read16()/write16()
+ * (stm32h563xx_flash.c).  This file is just the efuse_ops_s adapter: it
+ * packs/unpacks the field descriptors' bit ranges into and out of those
+ * two word-level primitives, the same shape as stm32_flash_edata_*() is
+ * to the MTD driver in stm32_edata.c.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <string.h>
+#include <debug.h>
+#include <errno.h>
+
+#include <nuttx/efuse/efuse.h>
+
+#include "stm32_flash.h"
+#include "stm32_efuse.h"
+
+#ifdef CONFIG_STM32H5_EFUSE
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int stm32_efuse_read_field(FAR struct efuse_lowerhalf_s *lower,
+                                  FAR const efuse_desc_t *field[],
+                                  FAR uint8_t *data, size_t bit_size);
+static int stm32_efuse_write_field(FAR struct efuse_lowerhalf_s *lower,
+                                   FAR const efuse_desc_t *field[],
+                                   FAR const uint8_t *data, size_t bit_size);
+static int stm32_efuse_ioctl(FAR struct efuse_lowerhalf_s *lower, int cmd,
+                             unsigned long arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct efuse_ops_s g_stm32_efuse_ops =
+{
+  .read_field  = stm32_efuse_read_field,
+  .write_field = stm32_efuse_write_field,
+  .ioctl       = stm32_efuse_ioctl,
+};
+
+static struct efuse_lowerhalf_s g_stm32_efuse_lower =
+{
+  .ops = &g_stm32_efuse_ops,
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: stm32_efuse_field_bits
+ *
+ * Description:
+ *   Total number of bits described by a NULL terminated field list.
+ *
+ ****************************************************************************/
+
+static size_t stm32_efuse_field_bits(FAR const efuse_desc_t *field[])
+{
+  size_t bits = 0;
+  int i;
+
+  for (i = 0; field[i] != NULL; i++)
+    {
+      bits += field[i]->bit_count;
+    }
+
+  return bits;
+}
+
+/****************************************************************************
+ * Name: stm32_efuse_check_field
+ *
+ * Description:
+ *   Verify that every descriptor lies inside the OTP bit address space.
+ *
+ ****************************************************************************/
+
+static int stm32_efuse_check_field(FAR const efuse_desc_t *field[])
+{
+  int i;
+
+  for (i = 0; field[i] != NULL; i++)
+    {
+      if ((size_t)field[i]->bit_offset + field[i]->bit_count >
+          STM32_OTP_TOTAL_BITS)
+        {
+          ferr("ERROR: field %d [%u,+%u) is outside the OTP\n", i,
+               field[i]->bit_offset, field[i]->bit_count);
+          return -EINVAL;
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_efuse_read_field
+ *
+ * Description:
+ *   Read the bits named by the field list.  The bits are packed towards
+ *   the start of the caller's buffer: the first bit of the first
+ *   descriptor lands in bit 0 of data[0], the next in bit 1, and so on
+ *   across descriptor boundaries.
+ *
+ ****************************************************************************/
+
+static int stm32_efuse_read_field(FAR struct efuse_lowerhalf_s *lower,
+                                  FAR const efuse_desc_t *field[],
+                                  FAR uint8_t *data, size_t bit_size)
+{
+  uint32_t cached_word = UINT32_MAX;
+  uint16_t cached_val = 0;
+  size_t written = 0;
+  size_t request;
+  int ret;
+  int i;
+
+  if (field == NULL || data == NULL)
+    {
+      return -EINVAL;
+    }
+
+  ret = stm32_efuse_check_field(field);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  request = stm32_efuse_field_bits(field);
+  if (bit_size != 0 && bit_size < request)
+    {
+      request = bit_size;
+    }
+
+  memset(data, 0, (request + 7) / 8);
+
+  for (i = 0; field[i] != NULL && written < request; i++)
+    {
+      size_t bit;
+
+      for (bit = 0; bit < field[i]->bit_count && written < request;
+           bit++, written++)
+        {
+          size_t   flat = field[i]->bit_offset + bit;
+          uint32_t word = flat / STM32_OTP_WORD_BITS;
+
+          if (word != cached_word)
+            {
+              ret = stm32_otp_word_read16(word, &cached_val);
+              if (ret < 0 && ret != -ENODATA)
+                {
+                  return ret;
+                }
+
+              cached_word = word;
+            }
+
+          if ((cached_val & (1u << (flat % STM32_OTP_WORD_BITS))) != 0)
+            {
+              data[written / 8] |= 1u << (written % 8);
+            }
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_efuse_write_field
+ *
+ * Description:
+ *   Program the bits named by the field list, taking the data in the same
+ *   packed layout that stm32_efuse_read_field() produces.
+ *
+ *   This is destructive and irreversible.  Programming happens a whole
+ *   16-bit word at a time, via stm32_otp_word_write16(), which is also
+ *   where a word that already holds a conflicting value is rejected.
+ *
+ ****************************************************************************/
+
+static int stm32_efuse_write_field(FAR struct efuse_lowerhalf_s *lower,
+                                   FAR const efuse_desc_t *field[],
+                                   FAR const uint8_t *data, size_t bit_size)
+{
+#ifndef CONFIG_STM32H5_OTP_WRITE
+  /* Programming is not built in.  Refuse rather than silently doing
+   * nothing, so a caller cannot mistake this for a successful burn.
+   */
+
+  return -EPERM;
+#else
+  uint32_t word = UINT32_MAX;
+  uint16_t value = 0;
+  bool dirty = false;
+  size_t consumed = 0;
+  size_t request;
+  int ret;
+  int i;
+
+  if (field == NULL || data == NULL)
+    {
+      return -EINVAL;
+    }
+
+  ret = stm32_efuse_check_field(field);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  request = stm32_efuse_field_bits(field);
+  if (bit_size != 0 && bit_size < request)
+    {
+      request = bit_size;
+    }
+
+  for (i = 0; field[i] != NULL && consumed < request; i++)
+    {
+      size_t bit;
+
+      for (bit = 0; bit < field[i]->bit_count && consumed < request;
+           bit++, consumed++)
+        {
+          size_t   flat     = field[i]->bit_offset + bit;
+          uint32_t new_word = flat / STM32_OTP_WORD_BITS;
+          size_t   wordbit  = flat % STM32_OTP_WORD_BITS;
+
+          if (new_word != word)
+            {
+              if (dirty)
+                {
+                  ret = stm32_otp_word_write16(word, value);
+                  if (ret < 0)
+                    {
+                      return ret;
+                    }
+                }
+
+              /* Seed "value" with the word's current contents, so bits
+               * this field does not touch are preserved -- blank reads
+               * back as 0xffff, which is exactly the starting point a
+               * never-written word needs.
+               */
+
+              ret = stm32_otp_word_read16(new_word, &value);
+              if (ret < 0 && ret != -ENODATA)
+                {
+                  return ret;
+                }
+
+              word  = new_word;
+              dirty = false;
+            }
+
+          if ((data[consumed / 8] & (1u << (consumed % 8))) == 0)
+            {
+              value &= ~(1u << wordbit);
+              dirty = true;
+            }
+        }
+    }
+
+  if (dirty)
+    {
+      ret = stm32_otp_word_write16(word, value);
+      if (ret < 0)
+        {
+          return ret;
+        }
+    }
+
+  return OK;
+#endif /* CONFIG_STM32H5_OTP_WRITE */
+}
+
+/****************************************************************************
+ * Name: stm32_efuse_ioctl
+ ****************************************************************************/
+
+static int stm32_efuse_ioctl(FAR struct efuse_lowerhalf_s *lower, int cmd,
+                             unsigned long arg)
+{
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: stm32_efuse_initialize
+ ****************************************************************************/
+
+int stm32_efuse_initialize(FAR const char *devpath)
+{
+  FAR void *handle;
+
+  handle = efuse_register(devpath, &g_stm32_efuse_lower);
+  if (handle == NULL)
+    {
+      ferr("ERROR: failed to register the OTP at %s\n", devpath);
+      return -ENODEV;
+    }
+
+  return OK;
+}
+
+#endif /* CONFIG_STM32H5_EFUSE */
diff --git a/arch/arm/src/stm32h5/stm32_efuse.h 
b/arch/arm/src/stm32h5/stm32_efuse.h
new file mode 100644
index 00000000000..cc086a73afa
--- /dev/null
+++ b/arch/arm/src/stm32h5/stm32_efuse.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+ * arch/arm/src/stm32h5/stm32_efuse.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_STM32H5_STM32_EFUSE_H
+#define __ARCH_ARM_SRC_STM32H5_STM32_EFUSE_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#ifdef CONFIG_STM32H5_EFUSE
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifndef __ASSEMBLY__
+
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: stm32_efuse_initialize
+ *
+ * Description:
+ *   Register the OTP area as an efuse character device, built on
+ *   stm32_otp_word_read16()/write16() (stm32_flash.h).  Those two are a
+ *   bare register access and an nxmutex-protected program sequence
+ *   respectively, so unlike this function they have no dependency on
+ *   driver init order; this one allocates upper-half driver state and
+ *   creates an inode, so call it once from board bring-up, after the
+ *   usual driver/GPIO initialization has run.
+ *
+ * Input Parameters:
+ *   devpath - The path to the device, e.g. "/dev/efuse"
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int stm32_efuse_initialize(FAR const char *devpath);
+
+#undef EXTERN
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* CONFIG_STM32H5_EFUSE */
+#endif /* __ARCH_ARM_SRC_STM32H5_STM32_EFUSE_H */
diff --git a/arch/arm/src/stm32h5/stm32_flash.h 
b/arch/arm/src/stm32h5/stm32_flash.h
index ffa548fc0bd..76241f16129 100644
--- a/arch/arm/src/stm32h5/stm32_flash.h
+++ b/arch/arm/src/stm32h5/stm32_flash.h
@@ -33,6 +33,21 @@
 #include <sys/types.h>
 
 #include "hardware/stm32_flash.h"
+#include "hardware/stm32_memorymap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* STM32_OTP_BASE/STM32_OTP_SIZE (hardware/stm32h5xxx_memorymap.h) are the
+ * silicon facts from RM0481, "OTP area".  These are the same facts recast
+ * as a 16-bit word count, which is what stm32_otp_word_read16()/write16()
+ * and the efuse driver built on them index by.
+ */
+
+#define STM32_OTP_NWORDS     (STM32_OTP_SIZE / 2)
+#define STM32_OTP_WORD_BITS  16
+#define STM32_OTP_TOTAL_BITS (STM32_OTP_NWORDS * STM32_OTP_WORD_BITS)
 
 /****************************************************************************
  * Public Function Prototypes
@@ -66,6 +81,21 @@ int stm32_otp_read(uint16_t *data, uint16_t len, uint32_t 
offset);
 
 uint32_t stm32_otp_getlockstatus(void);
 
+#ifdef CONFIG_STM32H5_OTP_WORD
+
+int stm32_otp_word_read16(uint32_t word, FAR uint16_t *value);
+
+int stm32_otp_word_read32(uint32_t word, FAR uint32_t *value);
+
+#ifdef CONFIG_STM32H5_OTP_WRITE
+
+int stm32_otp_word_write16(uint32_t word, uint16_t value);
+
+int stm32_otp_word_write32(uint32_t word, uint32_t value);
+
+#endif /* CONFIG_STM32H5_OTP_WRITE */
+#endif /* CONFIG_STM32H5_OTP_WORD */
+
 /* Flash high-cycle data (EDATA) low-level access.
  *
  * EDATA can be enabled on the last 1..8 sectors of each physical bank.
diff --git a/arch/arm/src/stm32h5/stm32h563xx_flash.c 
b/arch/arm/src/stm32h5/stm32h563xx_flash.c
index 26fead7b263..ab7a962c4af 100644
--- a/arch/arm/src/stm32h5/stm32h563xx_flash.c
+++ b/arch/arm/src/stm32h5/stm32h563xx_flash.c
@@ -128,6 +128,10 @@
 #define FLASH_OTP_WORDS_PER_BLOCK   32             /* 32 words per block */
 #define OTP_WORD_SIZE               2              /* 16-bit words as per 
manual */
 
+#define OTP_ERASEDVALUE16    0xffffu
+#define OTP_ERASEDVALUE32    0xffffffffu
+#define OTP_ECCD             (FLASH_ECCDETR_ECCD | FLASH_ECCDETR_OTP_ECC)
+
 #define FLASH_NSSR_ALL_ERRORS  (FLASH_NSSR_WRPERR | FLASH_NSSR_PGSERR |  \
                                 FLASH_NSSR_STRBERR | FLASH_NSSR_INCERR | \
                                 FLASH_NSSR_OBKERR | FLASH_NSSR_OBKWERR | \
@@ -364,49 +368,36 @@ static void flash_lock_opt(void)
   modifyreg32(STM32_FLASH_OPTCR, 0, FLASH_OPTCR_OPTLOCK);
 }
 
-#ifdef CONFIG_STM32_EDATA
+#if defined(CONFIG_STM32_EDATA) || defined(CONFIG_STM32H5_OTP_WORD)
 
 /****************************************************************************
- * Name: edata_logical_bank
+ * Name: flash_read_eccsafe16
  *
  * Description:
- *   Returns the logical bank (1 or 2) a physical bank is currently mapped
- *   to.  The swap only takes effect at reset, so this is only valid until
- *   the SWAP_BANK option is next changed.
+ *   Read one 16-bit half-word of EDATA or OTP.  Both only support 16 and
+ *   32-bit reads, so the ICACHE, which would fill whole lines, is disabled
+ *   for the read.  Reading a blank (erased, never programmed) half-word
+ *   raises the Flash ECC NMI, which is masked in the SBS first and handled
+ *   instead by checking ECCDETR afterwards.  Both are restored before
+ *   returning.
  *
- ****************************************************************************/
-
-static int edata_logical_bank(int bank)
-{
-  if (getreg32(STM32_FLASH_OPTSR_CUR) & FLASH_OPTSR_CUR_SWAP_BANK)
-    {
-      return 3 - bank;
-    }
-
-  return bank;
-}
-
-/****************************************************************************
- * Name: edata_read_hword
- *
- * Description:
- *   Read one EDATA half-word.  A blank (erased, never programmed) half-word
- *   reads as 0xffff.  If the half-word is corrupt, for example because
- *   power was lost while it was being programmed, the raw data is returned.
- *
- *   EDATA only supports 16 and 32-bit reads, so the ICACHE, which would
- *   fill whole lines, is disabled for the read.  The ECC NMI that a blank
- *   half-word would raise is masked in the SBS and handled by checking
- *   ECCDETR instead.  Both are restored afterwards.
+ * Input Parameters:
+ *   addr    - Address of the half-word
+ *   eccd    - ECCDETR bits that flag an ECC error for this memory
+ *             (EDATA_ECCD or OTP_ECCD)
+ *   eccerr  - Set to true if ECCDETR flagged this read, whether because
+ *             the half-word was blank or genuinely corrupt.  The returned
+ *             value in that case comes from ECCDR, not from the bus, since
+ *             a flagged read's data is not to be trusted.
  *
  ****************************************************************************/
 
-static uint16_t edata_read_hword(uintptr_t addr)
+static uint16_t flash_read_eccsafe16(uintptr_t addr, uint32_t eccd,
+                                     bool *eccerr)
 {
   irqstate_t flags;
   uint16_t   value;
   uint32_t   eccnmir;
-  bool       eccerr = false;
 #ifdef CONFIG_STM32_ICACHE
   bool       icache;
 #endif
@@ -427,10 +418,10 @@ static uint16_t edata_read_hword(uintptr_t addr)
   value = getreg16(addr);
   UP_DSB();
 
-  if ((getreg32(STM32_FLASH_ECCDETR) & EDATA_ECCD) == EDATA_ECCD)
+  *eccerr = (getreg32(STM32_FLASH_ECCDETR) & eccd) == eccd;
+  if (*eccerr)
     {
-      value  = getreg32(STM32_FLASH_ECCDR) & FLASH_ECCDR_DATA_ECC_MASK;
-      eccerr = true;
+      value = getreg32(STM32_FLASH_ECCDR) & FLASH_ECCDR_DATA_ECC_MASK;
       putreg32(FLASH_ECCDETR_ECCD, STM32_FLASH_ECCDETR);
     }
 
@@ -445,6 +436,48 @@ static uint16_t edata_read_hword(uintptr_t addr)
 
   up_irq_restore(flags);
 
+  return value;
+}
+
+#endif /* CONFIG_STM32_EDATA || CONFIG_STM32H5_OTP_WORD */
+
+#ifdef CONFIG_STM32_EDATA
+
+/****************************************************************************
+ * Name: edata_logical_bank
+ *
+ * Description:
+ *   Returns the logical bank (1 or 2) a physical bank is currently mapped
+ *   to.  The swap only takes effect at reset, so this is only valid until
+ *   the SWAP_BANK option is next changed.
+ *
+ ****************************************************************************/
+
+static int edata_logical_bank(int bank)
+{
+  if (getreg32(STM32_FLASH_OPTSR_CUR) & FLASH_OPTSR_CUR_SWAP_BANK)
+    {
+      return 3 - bank;
+    }
+
+  return bank;
+}
+
+/****************************************************************************
+ * Name: edata_read_hword
+ *
+ * Description:
+ *   Read one EDATA half-word.  A blank (erased, never programmed) half-word
+ *   reads as 0xffff.  If the half-word is corrupt, for example because
+ *   power was lost while it was being programmed, the raw data is returned.
+ *
+ ****************************************************************************/
+
+static uint16_t edata_read_hword(uintptr_t addr)
+{
+  bool eccerr;
+  uint16_t value = flash_read_eccsafe16(addr, EDATA_ECCD, &eccerr);
+
   if (eccerr && value != EDATA_ERASEDVALUE)
     {
       ferr("ERROR: EDATA ECC error at %08" PRIxPTR ": %04x\n", addr, value);
@@ -497,6 +530,47 @@ static int edata_erase(int bank, unsigned int sector)
 
 #endif /* CONFIG_STM32_EDATA */
 
+#ifdef CONFIG_STM32H5_OTP_WORD
+
+/****************************************************************************
+ * Name: otp_read_eccsafe16
+ *
+ * Description:
+ *   Read one 16-bit OTP word; see flash_read_eccsafe16().
+ *
+ ****************************************************************************/
+
+static uint16_t otp_read_eccsafe16(uintptr_t addr, bool *eccerr)
+{
+  return flash_read_eccsafe16(addr, OTP_ECCD, eccerr);
+}
+
+/****************************************************************************
+ * Name: otp_read_eccsafe32
+ *
+ * Description:
+ *   32-bit counterpart of otp_read_eccsafe16().  ECC is computed per
+ *   16-bit word (FLASH_ECCDR only ever holds 16 bits of recovered data),
+ *   so a 32-bit read is done as its two halves, each independently
+ *   recovered: a single native 32-bit access could only ever recover
+ *   whichever half ECCDETR last reported and would have to discard the
+ *   other half's real contents.
+ *
+ ****************************************************************************/
+
+static uint32_t otp_read_eccsafe32(uintptr_t addr, FAR bool *eccerr)
+{
+  bool erclo;
+  bool erchi;
+  uint16_t lo = otp_read_eccsafe16(addr, &erclo);
+  uint16_t hi = otp_read_eccsafe16(addr + sizeof(uint16_t), &erchi);
+
+  *eccerr = erclo || erchi;
+  return (uint32_t)lo | ((uint32_t)hi << 16);
+}
+
+#endif /* CONFIG_STM32H5_OTP_WORD */
+
 /****************************************************************************
  * Name: stm32h5_otp_is_space_available
  *
@@ -1506,6 +1580,282 @@ exit_with_lock:
 
 #endif /* CONFIG_STM32_EDATA */
 
+#ifdef CONFIG_STM32H5_OTP_WORD
+
+/****************************************************************************
+ * Name: stm32_otp_word_read16
+ *
+ * Description:
+ *   Read one 16-bit OTP word.  This is independent of, and does not
+ *   interact with, the block-oriented stm32_otp_write()/stm32_otp_read()
+ *   API above: no locking is involved or required, since reading never
+ *   conflicts with anything.
+ *
+ *   A blank (never programmed) word reads back as 0xffff.  Since whether a
+ *   word has been written is known (that is exactly what trips its ECC),
+ *   that is reported through the return value as -ENODATA rather than as
+ *   OK, even though *value is filled in either way.  -EIO is reserved for
+ *   a word whose ECC genuinely does not check out: neither blank nor the
+ *   value its own program operation wrote.
+ *
+ * Input Parameters:
+ *   word  - 16-bit word index, 0 to (FLASH_OTP_SIZE / 2) - 1
+ *   value - Receives the word's contents
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value otherwise.  *value is set
+ *   in every case except -EINVAL:
+ *
+ *     -EINVAL:  value is NULL, or word is out of range
+ *     -ENODATA: The word has never been programmed; *value is 0xffff
+ *     -EIO:     The word's ECC does not check out
+ *
+ ****************************************************************************/
+
+int stm32_otp_word_read16(uint32_t word, FAR uint16_t *value)
+{
+  bool eccerr;
+  uint16_t raw;
+
+  if (value == NULL || word >= FLASH_OTP_SIZE / OTP_WORD_SIZE)
+    {
+      return -EINVAL;
+    }
+
+  raw = otp_read_eccsafe16(STM32_OTP_BASE + word * OTP_WORD_SIZE, &eccerr);
+  *value = raw;
+
+  if (eccerr)
+    {
+      if (raw != OTP_ERASEDVALUE16)
+        {
+          ferr("ERROR: OTP word %" PRIu32 " ECC error: %04x\n", word, raw);
+          return -EIO;
+        }
+
+      return -ENODATA;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: stm32_otp_word_read32
+ *
+ * Description:
+ *   32-bit counterpart of stm32_otp_word_read16().  Note that "word" here
+ *   is a 32-bit word index: it does not line up with the index used by
+ *   the 16-bit functions, the same as the attached reference driver this
+ *   was ported from.
+ *
+ ****************************************************************************/
+
+int stm32_otp_word_read32(uint32_t word, FAR uint32_t *value)
+{
+  bool eccerr;
+  uint32_t raw;
+
+  if (value == NULL || word >= FLASH_OTP_SIZE / sizeof(uint32_t))
+    {
+      return -EINVAL;
+    }
+
+  raw = otp_read_eccsafe32(STM32_OTP_BASE + word * sizeof(uint32_t),
+                           &eccerr);
+  *value = raw;
+
+  if (eccerr)
+    {
+      if (raw != OTP_ERASEDVALUE32)
+        {
+          ferr("ERROR: OTP word %" PRIu32 " ECC error: %08" PRIx32 "\n",
+               word, raw);
+          return -EIO;
+        }
+
+      return -ENODATA;
+    }
+
+  return OK;
+}
+
+#ifdef CONFIG_STM32H5_OTP_WRITE
+
+/****************************************************************************
+ * Name: stm32_otp_word_write16
+ *
+ * Description:
+ *   Program one 16-bit OTP word.  Programming is IRREVERSIBLE: a word
+ *   that already holds a value other than the one requested cannot be
+ *   reprogrammed, because bits can only move from 1 to 0 and ECC was
+ *   already computed over its current contents.  Writing a word that
+ *   already holds exactly the requested value is a harmless no-op that
+ *   returns success, so this is safe to call unconditionally for a value
+ *   that may or may not have been written before.
+ *
+ * Returned Value:
+ *   Zero (OK) on success (including the no-op case above); a negated
+ *   errno value on failure:
+ *
+ *     -EINVAL: word is out of range
+ *     -EEXIST: The word already holds a different value
+ *     -EIO:    Programming failed, or the post-write readback did not
+ *              match
+ *
+ ****************************************************************************/
+
+int stm32_otp_word_write16(uint32_t word, uint16_t value)
+{
+  uintptr_t addr;
+  uint16_t  current;
+  bool      eccerr;
+  int       ret;
+
+  if (word >= FLASH_OTP_SIZE / OTP_WORD_SIZE)
+    {
+      return -EINVAL;
+    }
+
+  addr = STM32_OTP_BASE + word * OTP_WORD_SIZE;
+
+  ret = nxmutex_lock(&g_lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  current = otp_read_eccsafe16(addr, &eccerr);
+  if (!eccerr || current != OTP_ERASEDVALUE16)
+    {
+      /* Not blank: either already holds this exact value (success, the
+       * word is already in the requested state) or holds something else
+       * (this word can never be reprogrammed to the new value).
+       */
+
+      ret = current == value ? OK : -EEXIST;
+      goto exit_with_lock;
+    }
+
+  if (flash_wait_for_operation())
+    {
+      ret = -EIO;
+      goto exit_with_lock;
+    }
+
+  flash_unlock_nscr();
+  modifyreg32(STM32_FLASH_NSCCR, 0, ~0);
+
+  modifyreg32(STM32_FLASH_NSCR, 0, FLASH_NSCR_PG);
+  UP_MB();
+
+  putreg16(value, addr);
+  UP_MB();
+
+  ret = OK;
+  if (flash_wait_for_operation() ||
+      (getreg32(STM32_FLASH_NSSR) & FLASH_NSSR_ALL_ERRORS))
+    {
+      ret = -EIO;
+    }
+
+  modifyreg32(STM32_FLASH_NSCR, FLASH_NSCR_PG, 0);
+  modifyreg32(STM32_FLASH_NSCCR, 0, ~0);
+  flash_lock_nscr();
+
+  if (ret == OK)
+    {
+      current = otp_read_eccsafe16(addr, &eccerr);
+      if (eccerr || current != value)
+        {
+          ret = -EIO;
+        }
+    }
+
+exit_with_lock:
+  nxmutex_unlock(&g_lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: stm32_otp_word_write32
+ *
+ * Description:
+ *   32-bit counterpart of stm32_otp_word_write16(); see there for the
+ *   full explanation.  As with stm32_otp_word_read32(), "word" is a
+ *   32-bit word index here.
+ *
+ ****************************************************************************/
+
+int stm32_otp_word_write32(uint32_t word, uint32_t value)
+{
+  uintptr_t addr;
+  uint32_t  current;
+  bool      eccerr;
+  int       ret;
+
+  if (word >= FLASH_OTP_SIZE / sizeof(uint32_t))
+    {
+      return -EINVAL;
+    }
+
+  addr = STM32_OTP_BASE + word * sizeof(uint32_t);
+
+  ret = nxmutex_lock(&g_lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  current = otp_read_eccsafe32(addr, &eccerr);
+  if (!eccerr || current != OTP_ERASEDVALUE32)
+    {
+      ret = current == value ? OK : -EEXIST;
+      goto exit_with_lock;
+    }
+
+  if (flash_wait_for_operation())
+    {
+      ret = -EIO;
+      goto exit_with_lock;
+    }
+
+  flash_unlock_nscr();
+  modifyreg32(STM32_FLASH_NSCCR, 0, ~0);
+
+  modifyreg32(STM32_FLASH_NSCR, 0, FLASH_NSCR_PG);
+  UP_MB();
+
+  putreg32(value, addr);
+  UP_MB();
+
+  ret = OK;
+  if (flash_wait_for_operation() ||
+      (getreg32(STM32_FLASH_NSSR) & FLASH_NSSR_ALL_ERRORS))
+    {
+      ret = -EIO;
+    }
+
+  modifyreg32(STM32_FLASH_NSCR, FLASH_NSCR_PG, 0);
+  modifyreg32(STM32_FLASH_NSCCR, 0, ~0);
+  flash_lock_nscr();
+
+  if (ret == OK)
+    {
+      current = otp_read_eccsafe32(addr, &eccerr);
+      if (eccerr || current != value)
+        {
+          ret = -EIO;
+        }
+    }
+
+exit_with_lock:
+  nxmutex_unlock(&g_lock);
+  return ret;
+}
+
+#endif /* CONFIG_STM32H5_OTP_WRITE */
+#endif /* CONFIG_STM32H5_OTP_WORD */
+
 #ifdef CONFIG_ARCH_HAVE_PROGMEM
 
 /* up_progmem_x functions defined in nuttx/include/nuttx/progmem.h

Reply via email to