arch/arm/stm32h5: Add OTP/eFuse support
Some checks are pending
Build Documentation / build-html (push) Waiting to run
MemBrowse Memory Report / changes-filter (push) Waiting to run
MemBrowse Memory Report / load-targets (push) Waiting to run
MemBrowse Memory Report / identical (push) Blocked by required conditions
MemBrowse Memory Report / analyze (push) Blocked by required conditions

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 <darryl@bluerobotics.com>
This commit is contained in:
Darryl Ring 2026-09-11 08:57:36 -07:00 • committed by Xiang Xiao
parent 04c7eb43e8
commit 16b8a74417
9 changed files with 964 additions and 46 deletions

View file

@ -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
------

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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. */

View file

@ -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 */

View file

@ -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 */

View file

@ -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.

View file

@ -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,6 +368,79 @@ static void flash_lock_opt(void)
modifyreg32(STM32_FLASH_OPTCR, 0, FLASH_OPTCR_OPTLOCK);
}
#if defined(CONFIG_STM32_EDATA) || defined(CONFIG_STM32H5_OTP_WORD)
/****************************************************************************
* Name: flash_read_eccsafe16
*
* Description:
* 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.
*
* 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 flash_read_eccsafe16(uintptr_t addr, uint32_t eccd,
bool *eccerr)
{
irqstate_t flags;
uint16_t value;
uint32_t eccnmir;
#ifdef CONFIG_STM32_ICACHE
bool icache;
#endif
flags = up_irq_save();
#ifdef CONFIG_STM32_ICACHE
icache = stm32_icache_enabled();
if (icache)
{
stm32_disable_icache();
}
#endif
eccnmir = getreg32(STM32_SBS_ECCNMIR);
putreg32(eccnmir | SBS_ECCNMIR_ECCNMI_MASK_EN, STM32_SBS_ECCNMIR);
value = getreg16(addr);
UP_DSB();
*eccerr = (getreg32(STM32_FLASH_ECCDETR) & eccd) == eccd;
if (*eccerr)
{
value = getreg32(STM32_FLASH_ECCDR) & FLASH_ECCDR_DATA_ECC_MASK;
putreg32(FLASH_ECCDETR_ECCD, STM32_FLASH_ECCDETR);
}
putreg32(eccnmir, STM32_SBS_ECCNMIR);
#ifdef CONFIG_STM32_ICACHE
if (icache)
{
stm32_enable_icache();
}
#endif
up_irq_restore(flags);
return value;
}
#endif /* CONFIG_STM32_EDATA || CONFIG_STM32H5_OTP_WORD */
#ifdef CONFIG_STM32_EDATA
/****************************************************************************
@ -394,56 +471,12 @@ static int edata_logical_bank(int bank)
* 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.
*
****************************************************************************/
static uint16_t edata_read_hword(uintptr_t addr)
{
irqstate_t flags;
uint16_t value;
uint32_t eccnmir;
bool eccerr = false;
#ifdef CONFIG_STM32_ICACHE
bool icache;
#endif
flags = up_irq_save();
#ifdef CONFIG_STM32_ICACHE
icache = stm32_icache_enabled();
if (icache)
{
stm32_disable_icache();
}
#endif
eccnmir = getreg32(STM32_SBS_ECCNMIR);
putreg32(eccnmir | SBS_ECCNMIR_ECCNMI_MASK_EN, STM32_SBS_ECCNMIR);
value = getreg16(addr);
UP_DSB();
if ((getreg32(STM32_FLASH_ECCDETR) & EDATA_ECCD) == EDATA_ECCD)
{
value = getreg32(STM32_FLASH_ECCDR) & FLASH_ECCDR_DATA_ECC_MASK;
eccerr = true;
putreg32(FLASH_ECCDETR_ECCD, STM32_FLASH_ECCDETR);
}
putreg32(eccnmir, STM32_SBS_ECCNMIR);
#ifdef CONFIG_STM32_ICACHE
if (icache)
{
stm32_enable_icache();
}
#endif
up_irq_restore(flags);
bool eccerr;
uint16_t value = flash_read_eccsafe16(addr, EDATA_ECCD, &eccerr);
if (eccerr && value != EDATA_ERASEDVALUE)
{
@ -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