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