From 7fc26fd50c2b3efed794db696e3821a644eb4bed Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sat, 25 Jul 2026 10:53:03 +0200 Subject: [PATCH] rp23xx: Add an OTP driver on the efuse interface. The rp2350 holds 4096 rows of 24 bit one-time-programmable memory, which the port did not expose at all. This adds a driver on the NuttX efuse interface, registered by the common board bringup as /dev/efuse. The driver uses the ECC interpretation of a row, in which 16 bits carry data and the remaining 8 carry a Hamming code, so the OTP appears as a flat space of 4096 * 16 bits for the efuse field descriptors to index: a descriptor at bit offset N refers to bit N % 16 of row N / 16. That matches the row numbers already listed in hardware/rp23xx_otp_data.h. Reads come from the chip's ECC-translated window and have no side effects. Rows are locked in pages of 64; a page locked against reads would raise a bus fault, so the lock is checked first and reported as an error instead. Programming needs the separate RP23XX_OTP_WRITE option, which defaults to off; without it a write returns EPERM and no programming code is built at all. When enabled, a row is programmed as a whole through the bootrom, since the ECC bits cover the whole row. For the same reason a row that already holds data cannot be modified, and such a write is rejected rather than left to corrupt the row's ECC. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- Documentation/platforms/arm/rp23xx/index.rst | 44 ++ arch/arm/src/rp23xx/CMakeLists.txt | 4 + arch/arm/src/rp23xx/Kconfig | 45 ++ arch/arm/src/rp23xx/Make.defs | 4 + arch/arm/src/rp23xx/rp23xx_otp.c | 474 ++++++++++++++++++ arch/arm/src/rp23xx/rp23xx_otp.h | 88 ++++ .../rp23xx/common/src/rp23xx_common_bringup.c | 12 + 7 files changed, 671 insertions(+) create mode 100644 arch/arm/src/rp23xx/rp23xx_otp.c create mode 100644 arch/arm/src/rp23xx/rp23xx_otp.h diff --git a/Documentation/platforms/arm/rp23xx/index.rst b/Documentation/platforms/arm/rp23xx/index.rst index 2b673aba40e..cf1142c46bc 100644 --- a/Documentation/platforms/arm/rp23xx/index.rst +++ b/Documentation/platforms/arm/rp23xx/index.rst @@ -46,6 +46,7 @@ Flash MTD Working Unused flash tail as an MTD device, answers BIOC_ Timer Working /dev/timerN on TIMER0/TIMER1, 1 us resolution Tickless Working Optional, RP2350 TIMER via the alarm/oneshot RTC Working POWMAN always-on timer, backs the system clock +OTP Working Reads via /dev/efuse; programming is opt-in ============== ============ ===== Installation @@ -342,6 +343,49 @@ Any MTD-based filesystem can be layered on the device, for example:: nsh> mksmartfs /dev/rpflash nsh> mount -t smartfs /dev/rpflash /mnt +OTP +=== + +The rp2350 has 4096 rows of 24 bit one-time-programmable memory. Enabling +``RP23XX_OTP`` exposes it through the NuttX efuse interface, registered by the +common board bringup as ``/dev/efuse``. + +The driver uses the ECC interpretation of a row, in which 16 bits carry data +and the remaining 8 carry a Hamming code. The OTP therefore appears as a flat +space of 4096 * 16 bits, which is what the efuse field descriptors index: a +descriptor at bit offset N refers to bit ``N % 16`` of row ``N / 16``. The +row numbers of the predefined fields are in +``arch/arm/src/rp23xx/hardware/rp23xx_otp_data.h``. + +Reads use the chip's ECC-translated window and have no side effects. Single +bit errors are corrected in hardware. Rows live in pages of 64, each of which +can be locked; a page locked against reads is reported as ``EPERM`` rather than +being allowed to raise a bus fault. + +To read a field, fill in a ``struct efuse_param_s`` with a NULL terminated list +of field descriptors and call ``EFUSEIOC_READ_FIELD``. The bits are returned +packed from bit 0 of the first byte. For example, reading the 64 bit chip +identifier held in rows 0 to 3:: + + static const efuse_desc_t chipid = { 0, 64 }; /* row 0, 4 * 16 bits */ + static const efuse_desc_t *chipid_field[] = { &chipid, NULL }; + + uint8_t data[8]; + struct efuse_param_s param = + { + .field = chipid_field, .size = 64, .data = data + }; + + ioctl(fd, EFUSEIOC_READ_FIELD, (unsigned long)¶m); + +Programming requires the separate ``RP23XX_OTP_WRITE`` option, which is off by +default; without it ``EFUSEIOC_WRITE_FIELD`` fails with ``EPERM``. Programming +is **irreversible**: a bit can only go from zero to one, and since the ECC bits +cover the whole row, a row can only be programmed once -- the driver rejects a +write to a row that already holds data rather than corrupting its ECC. Note +that some rows control boot and debug behaviour, so an incorrect write can make +a board unbootable or permanently lock out the debugger. + Supported Boards ================ diff --git a/arch/arm/src/rp23xx/CMakeLists.txt b/arch/arm/src/rp23xx/CMakeLists.txt index 6bd1eeaf358..90387a8bb8b 100644 --- a/arch/arm/src/rp23xx/CMakeLists.txt +++ b/arch/arm/src/rp23xx/CMakeLists.txt @@ -113,4 +113,8 @@ if(CONFIG_RP23XX_RTC) list(APPEND SRCS rp23xx_rtc.c) endif() +if(CONFIG_RP23XX_OTP) + list(APPEND SRCS rp23xx_otp.c) +endif() + target_sources(arch PRIVATE ${SRCS}) diff --git a/arch/arm/src/rp23xx/Kconfig b/arch/arm/src/rp23xx/Kconfig index 7fd81b6f909..39c79b3d08d 100644 --- a/arch/arm/src/rp23xx/Kconfig +++ b/arch/arm/src/rp23xx/Kconfig @@ -1254,3 +1254,48 @@ config RP23XX_FLASH_MTD_SAFE_XIP choice when bringing up a new board. endif # RP23XX_FLASH_MTD + +##################################################################### +# OTP Configuration +##################################################################### + +config RP23XX_OTP + bool "OTP (one-time-programmable memory) driver" + default n + select EFUSE + ---help--- + Expose the RP2350 OTP through the NuttX efuse interface as a + character device, by default /dev/efuse. + + The OTP is an array of 4096 rows of 24 bits. This driver uses the + ECC interpretation of a row, where 16 bits carry data and the rest + carry a Hamming code, so it presents a flat space of 4096 * 16 bits + that the efuse field descriptors index into: a field at bit offset + N refers to bit N % 16 of row N / 16. + + Reads come straight from the chip's ECC-translated window, and are + free of side effects. A page locked against reads is reported as + an error rather than being allowed to raise a bus fault. + + Programming is a separate option below, and is off by default. + +if RP23XX_OTP + +config RP23XX_OTP_WRITE + bool "Allow programming the OTP" + default n + ---help--- + Build the code that programs OTP rows, through the bootrom. + + Leave this off unless the application really has to burn fuses. + Programming is IRREVERSIBLE: a bit can only ever go from zero to + one, and because the ECC bits are computed over the whole row, a + row can only be programmed once. A mistake here permanently + damages the part, and some rows control boot and debug behaviour, + so a wrong write can make a board unbootable or lock out the + debugger for good. + + With this option off, EFUSEIOC_WRITE_FIELD fails with EPERM and + reads still work normally. + +endif # RP23XX_OTP diff --git a/arch/arm/src/rp23xx/Make.defs b/arch/arm/src/rp23xx/Make.defs index 75366113e42..47e790bd17b 100644 --- a/arch/arm/src/rp23xx/Make.defs +++ b/arch/arm/src/rp23xx/Make.defs @@ -117,3 +117,7 @@ endif ifeq ($(CONFIG_RP23XX_RTC),y) CHIP_CSRCS += rp23xx_rtc.c endif + +ifeq ($(CONFIG_RP23XX_OTP),y) +CHIP_CSRCS += rp23xx_otp.c +endif diff --git a/arch/arm/src/rp23xx/rp23xx_otp.c b/arch/arm/src/rp23xx/rp23xx_otp.c new file mode 100644 index 00000000000..7e9a4dde37a --- /dev/null +++ b/arch/arm/src/rp23xx/rp23xx_otp.c @@ -0,0 +1,474 @@ +/**************************************************************************** + * arch/arm/src/rp23xx/rp23xx_otp.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 RP2350 one-time-programmable memory exposed through the NuttX efuse + * interface. + * + * The OTP is an array of 4096 rows of 24 bits. Software normally uses the + * ECC interpretation, where 16 of those bits carry data and the remaining 8 + * carry a Hamming code, giving single-bit correction and double-bit + * detection. The chip maps that interpretation into the address space at + * RP23XX_OTP_DATA_BASE, one halfword per row, so a read is just a halfword + * load. This driver uses that window, and therefore presents the OTP as a + * flat bit address space of 4096 * 16 bits, which is what the efuse field + * descriptors index into. + * + * Reading is free of side effects. Programming is not: a bit can only ever + * go from zero to one, and with ECC in use a row can only be programmed + * once, because the Hamming code is computed over the whole 16 bit value. + * Programming goes through the bootrom, which owns the programming voltage + * and timing, and is compiled out unless CONFIG_RP23XX_OTP_WRITE is set. + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "arm_internal.h" +#include "rp23xx_otp.h" +#include "hardware/rp23xx_memorymap.h" +#include "hardware/rp23xx_otp.h" + +#ifdef CONFIG_RP23XX_OTP_WRITE +/* Only the programming path needs the bootrom lookup helpers */ + +# include "rp23xx_rom.h" +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Software lock bits, per page of 64 rows. The two bit field encodes + * 0 = read and write, 1 = read only, 2 and 3 = inaccessible. NuttX runs in + * the Secure state on this part, so the SEC field is the one that applies. + */ + +#define OTP_SW_LOCK_SEC_SHIFT 0 +#define OTP_SW_LOCK_NO_WRITE 1 +#define OTP_SW_LOCK_NO_READ 2 + +/* Bootrom otp_access() command word: row in bits 15:0, then the direction + * and the ECC translation. + */ + +#define OTP_CMD_ROW_MASK 0x0000ffff +#define OTP_CMD_IS_WRITE (1 << 16) +#define OTP_CMD_IS_ECC (1 << 17) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* The bootrom entry point used for programming */ + +typedef int (*rom_otp_access_fn)(FAR uint8_t *buf, uint32_t buf_len, + uint32_t cmd); + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int rp23xx_otp_read_field(FAR struct efuse_lowerhalf_s *lower, + FAR const efuse_desc_t *field[], + FAR uint8_t *data, size_t bit_size); +static int rp23xx_otp_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 rp23xx_otp_ioctl(FAR struct efuse_lowerhalf_s *lower, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct efuse_ops_s g_rp23xx_otp_ops = +{ + .read_field = rp23xx_otp_read_field, + .write_field = rp23xx_otp_write_field, + .ioctl = rp23xx_otp_ioctl, +}; + +static struct efuse_lowerhalf_s g_rp23xx_otp_lower = +{ + .ops = &g_rp23xx_otp_ops, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rp23xx_otp_page_lock + * + * Description: + * Return the Secure software lock state of the page holding a row. + * + ****************************************************************************/ + +static uint32_t rp23xx_otp_page_lock(uint32_t row) +{ + uint32_t page = row / RP23XX_OTP_PAGE_ROWS; + uint32_t lock = getreg32(RP23XX_OTP_SW_LOCK(page)); + + return (lock & RP23XX_OTP_SW_LOCK_SEC_MASK) >> OTP_SW_LOCK_SEC_SHIFT; +} + +/**************************************************************************** + * Name: rp23xx_otp_read_row + * + * Description: + * Read one row through the ECC window. Reading a page that is locked + * against reads raises a bus fault, so the lock is checked first and + * reported as an error instead. + * + * Input Parameters: + * row - Row number, 0 to RP23XX_OTP_NUM_ROWS - 1 + * value - Receives the 16 bits of data held in the row + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int rp23xx_otp_read_row(uint32_t row, FAR uint16_t *value) +{ + if (row >= RP23XX_OTP_NUM_ROWS) + { + return -EINVAL; + } + + if (rp23xx_otp_page_lock(row) >= OTP_SW_LOCK_NO_READ) + { + finfo("OTP row %" PRIu32 " is in a page locked against reads\n", row); + return -EPERM; + } + + *value = getreg16(RP23XX_OTP_DATA_BASE + row * sizeof(uint16_t)); + return OK; +} + +/**************************************************************************** + * Name: rp23xx_otp_field_bits + * + * Description: + * Total number of bits described by a NULL terminated field list. + * + ****************************************************************************/ + +static size_t rp23xx_otp_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: rp23xx_otp_check_field + * + * Description: + * Verify that every descriptor lies inside the OTP bit address space. + * + ****************************************************************************/ + +static int rp23xx_otp_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 > + RP23XX_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: rp23xx_otp_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 rp23xx_otp_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_row = 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 = rp23xx_otp_check_field(field); + if (ret < 0) + { + return ret; + } + + request = rp23xx_otp_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 row = flat / RP23XX_OTP_ROW_BITS; + + if (row != cached_row) + { + ret = rp23xx_otp_read_row(row, &cached_val); + if (ret < 0) + { + return ret; + } + + cached_row = row; + } + + if ((cached_val & (1u << (flat % RP23XX_OTP_ROW_BITS))) != 0) + { + data[written / 8] |= 1u << (written % 8); + } + } + } + + return OK; +} + +/**************************************************************************** + * Name: rp23xx_otp_write_field + * + * Description: + * Program the bits named by the field list, taking the data in the same + * packed layout that rp23xx_otp_read_field() produces. + * + * This is destructive and irreversible. Each affected row is programmed + * as a whole through the bootrom, because the ECC bits are computed over + * the whole row, which also means a row that has already been programmed + * cannot be modified: such a write is rejected here rather than left to + * corrupt the row's ECC. + * + ****************************************************************************/ + +static int rp23xx_otp_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_RP23XX_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 + rom_otp_access_fn otp_access; + size_t consumed = 0; + size_t request; + int ret; + int i; + + if (field == NULL || data == NULL) + { + return -EINVAL; + } + + ret = rp23xx_otp_check_field(field); + if (ret < 0) + { + return ret; + } + + otp_access = (rom_otp_access_fn)rom_func_lookup(ROM_FUNC_OTP_ACCESS); + if (otp_access == NULL) + { + ferr("ERROR: the bootrom has no OTP access function\n"); + return -ENOTSUP; + } + + request = rp23xx_otp_field_bits(field); + if (bit_size != 0 && bit_size < request) + { + request = bit_size; + } + + /* Walk the field list a row at a time, building the value to program */ + + for (i = 0; field[i] != NULL && consumed < request; i++) + { + size_t bit = 0; + + while (bit < field[i]->bit_count && consumed < request) + { + size_t flat = field[i]->bit_offset + bit; + uint32_t row = flat / RP23XX_OTP_ROW_BITS; + uint16_t value = 0; + uint16_t current; + + /* Collect every bit of this field that falls in this row */ + + while (bit < field[i]->bit_count && consumed < request && + (field[i]->bit_offset + bit) / RP23XX_OTP_ROW_BITS == row) + { + size_t pos = (field[i]->bit_offset + bit) % + RP23XX_OTP_ROW_BITS; + + if ((data[consumed / 8] & (1u << (consumed % 8))) != 0) + { + value |= 1u << pos; + } + + bit++; + consumed++; + } + + if (rp23xx_otp_page_lock(row) >= OTP_SW_LOCK_NO_WRITE) + { + ferr("ERROR: OTP row %" PRIu32 " is locked against writes\n", + row); + return -EPERM; + } + + ret = rp23xx_otp_read_row(row, ¤t); + if (ret < 0) + { + return ret; + } + + if ((uint16_t)(current | value) == current) + { + /* Every bit requested is already programmed, so there is + * nothing to do for this row. + */ + + continue; + } + + /* Anything else asked of a row that already holds data would need + * its ECC recomputed, and the array can only set bits, never clear + * them. Refuse here rather than handing the bootrom a write that + * would leave the row's ECC inconsistent. Note this catches a + * field covering the whole row as well as a partial one: any + * non-blank row is off limits. + */ + + if (current != 0) + { + ferr("ERROR: OTP row %" PRIu32 " already programmed (0x%04x), " + "its ECC cannot be recomputed\n", row, current); + return -EROFS; + } + + ret = otp_access((FAR uint8_t *)&value, sizeof(value), + (row & OTP_CMD_ROW_MASK) | OTP_CMD_IS_WRITE | + OTP_CMD_IS_ECC); + if (ret < 0) + { + ferr("ERROR: bootrom refused to program row %" PRIu32 ": %d\n", + row, ret); + return -EIO; + } + } + } + + return OK; +#endif /* CONFIG_RP23XX_OTP_WRITE */ +} + +/**************************************************************************** + * Name: rp23xx_otp_ioctl + ****************************************************************************/ + +static int rp23xx_otp_ioctl(FAR struct efuse_lowerhalf_s *lower, int cmd, + unsigned long arg) +{ + return -ENOTTY; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rp23xx_otp_initialize + * + * Description: + * Register the OTP as an efuse character device. + * + ****************************************************************************/ + +int rp23xx_otp_initialize(FAR const char *devpath) +{ + FAR void *handle; + + handle = efuse_register(devpath, &g_rp23xx_otp_lower); + if (handle == NULL) + { + ferr("ERROR: failed to register the OTP at %s\n", devpath); + return -ENODEV; + } + + return OK; +} diff --git a/arch/arm/src/rp23xx/rp23xx_otp.h b/arch/arm/src/rp23xx/rp23xx_otp.h new file mode 100644 index 00000000000..1c4edaf20f6 --- /dev/null +++ b/arch/arm/src/rp23xx/rp23xx_otp.h @@ -0,0 +1,88 @@ +/**************************************************************************** + * arch/arm/src/rp23xx/rp23xx_otp.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_RP23XX_RP23XX_OTP_H +#define __ARCH_ARM_SRC_RP23XX_RP23XX_OTP_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The OTP holds 4096 rows. Through the ECC translation used by this driver + * each row carries 16 bits of data, so the flat bit address space that the + * efuse field descriptors index into is: + * + * bit = row * RP23XX_OTP_ROW_BITS + bit_within_row + */ + +#define RP23XX_OTP_NUM_ROWS 4096 +#define RP23XX_OTP_ROW_BITS 16 +#define RP23XX_OTP_TOTAL_BITS (RP23XX_OTP_NUM_ROWS * RP23XX_OTP_ROW_BITS) + +/* Rows are locked in pages of 64 rows */ + +#define RP23XX_OTP_PAGE_ROWS 64 + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: rp23xx_otp_initialize + * + * Description: + * Register the OTP as an efuse character device. + * + * 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 rp23xx_otp_initialize(FAR const char *devpath); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __ARCH_ARM_SRC_RP23XX_RP23XX_OTP_H */ diff --git a/boards/arm/rp23xx/common/src/rp23xx_common_bringup.c b/boards/arm/rp23xx/common/src/rp23xx_common_bringup.c index 33645f16490..0130f633b5c 100644 --- a/boards/arm/rp23xx/common/src/rp23xx_common_bringup.c +++ b/boards/arm/rp23xx/common/src/rp23xx_common_bringup.c @@ -58,6 +58,10 @@ # include "rp23xx_flash_mtd.h" #endif +#ifdef CONFIG_RP23XX_OTP +# include "rp23xx_otp.h" +#endif + #if defined(CONFIG_RP23XX_ROMFS_ROMDISK_DEVNAME) # include #endif @@ -438,6 +442,14 @@ int rp23xx_common_bringup(void) } #endif +#ifdef CONFIG_RP23XX_OTP + ret = rp23xx_otp_initialize("/dev/efuse"); + if (ret < 0) + { + syslog(LOG_ERR, "Failed to initialize the OTP: %d\n", ret); + } +#endif + #ifdef CONFIG_RP23XX_I2S ret = board_i2sdev_initialize(0); if (ret < 0)