From 16b8a744174264be833c402bc94465f013a10f0e Mon Sep 17 00:00:00 2001 From: Darryl Ring Date: Fri, 11 Sep 2026 08:57:36 -0700 Subject: [PATCH] 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 --- 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 + .../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 | 442 ++++++++++++++++-- 9 files changed, 964 insertions(+), 46 deletions(-) create mode 100644 arch/arm/src/stm32h5/stm32_efuse.c create mode 100644 arch/arm/src/stm32h5/stm32_efuse.h 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 + +#include +#include +#include +#include +#include +#include + +#include + +#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 + +#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 #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,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