587 lines
14 KiB
C
587 lines
14 KiB
C
/*
|
|
* Copyright (c) 2017-2024 Nordic Semiconductor ASA
|
|
* Copyright (c) 2016 Linaro Limited
|
|
* Copyright (c) 2016 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/init.h>
|
|
#include <soc.h>
|
|
#include <zephyr/drivers/flash.h>
|
|
#include <string.h>
|
|
#include <nrfx_nvmc.h>
|
|
#include <nrf_erratas.h>
|
|
|
|
#include "soc_flash_nrf.h"
|
|
|
|
#define LOG_LEVEL CONFIG_FLASH_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(flash_nrf);
|
|
|
|
#if DT_NODE_HAS_STATUS(DT_INST(0, nordic_nrf51_flash_controller), okay)
|
|
#define DT_DRV_COMPAT nordic_nrf51_flash_controller
|
|
#elif DT_NODE_HAS_STATUS(DT_INST(0, nordic_nrf52_flash_controller), okay)
|
|
#define DT_DRV_COMPAT nordic_nrf52_flash_controller
|
|
#elif DT_NODE_HAS_STATUS(DT_INST(0, nordic_nrf53_flash_controller), okay)
|
|
#define DT_DRV_COMPAT nordic_nrf53_flash_controller
|
|
#elif DT_NODE_HAS_STATUS(DT_INST(0, nordic_nrf91_flash_controller), okay)
|
|
#define DT_DRV_COMPAT nordic_nrf91_flash_controller
|
|
#else
|
|
#error No matching compatible for soc_flash_nrf.c
|
|
#endif
|
|
|
|
#define SOC_NV_FLASH_NODE DT_INST(0, soc_nv_flash)
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
#define FLASH_SLOT_WRITE 7500
|
|
#if defined(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE)
|
|
#define FLASH_SLOT_ERASE (MAX(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE_MS * 1000, \
|
|
7500))
|
|
#else
|
|
#define FLASH_SLOT_ERASE FLASH_PAGE_ERASE_MAX_TIME_US
|
|
#endif /* CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE */
|
|
|
|
static int write_op(void *context); /* instance of flash_op_handler_t */
|
|
static int write_synchronously(off_t addr, const void *data, size_t len);
|
|
|
|
static int erase_op(void *context); /* instance of flash_op_handler_t */
|
|
static int erase_synchronously(uint32_t addr, uint32_t size);
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
|
|
static const struct flash_parameters flash_nrf_parameters = {
|
|
#if defined(CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS)
|
|
.write_block_size = 1,
|
|
#else
|
|
.write_block_size = 4,
|
|
#endif
|
|
.erase_value = 0xff,
|
|
};
|
|
|
|
#if defined(CONFIG_MULTITHREADING)
|
|
/* semaphore for locking flash resources (tickers) */
|
|
static struct k_sem sem_lock;
|
|
#define SYNC_INIT() k_sem_init(&sem_lock, 1, 1)
|
|
#define SYNC_LOCK() k_sem_take(&sem_lock, K_FOREVER)
|
|
#define SYNC_UNLOCK() k_sem_give(&sem_lock)
|
|
#else
|
|
#define SYNC_INIT()
|
|
#define SYNC_LOCK()
|
|
#define SYNC_UNLOCK()
|
|
#endif
|
|
|
|
#if NRF52_ERRATA_242_PRESENT
|
|
#include <hal/nrf_power.h>
|
|
static int suspend_pofwarn(void);
|
|
static void restore_pofwarn(void);
|
|
|
|
#define SUSPEND_POFWARN() suspend_pofwarn()
|
|
#define RESUME_POFWARN() restore_pofwarn()
|
|
#else
|
|
#define SUSPEND_POFWARN() 0
|
|
#define RESUME_POFWARN()
|
|
#endif /* NRF52_ERRATA_242_PRESENT */
|
|
|
|
static int write(off_t addr, const void *data, size_t len);
|
|
static int erase(uint32_t addr, uint32_t size);
|
|
|
|
static inline bool is_aligned_32(uint32_t data)
|
|
{
|
|
return (data & 0x3) ? false : true;
|
|
}
|
|
|
|
static inline bool is_within_bounds(off_t addr, size_t len, off_t boundary_start,
|
|
size_t boundary_size)
|
|
{
|
|
return (addr >= boundary_start &&
|
|
(addr < (boundary_start + boundary_size)) &&
|
|
(len <= (boundary_start + boundary_size - addr)));
|
|
}
|
|
|
|
static inline bool is_regular_addr_valid(off_t addr, size_t len)
|
|
{
|
|
return is_within_bounds(addr, len, 0, nrfx_nvmc_flash_size_get());
|
|
}
|
|
|
|
static inline bool is_uicr_addr_valid(off_t addr, size_t len)
|
|
{
|
|
#ifdef CONFIG_SOC_FLASH_NRF_UICR
|
|
return is_within_bounds(addr, len, (off_t)NRF_UICR, sizeof(*NRF_UICR));
|
|
#else
|
|
return false;
|
|
#endif /* CONFIG_SOC_FLASH_NRF_UICR */
|
|
}
|
|
|
|
#if CONFIG_SOC_FLASH_NRF_UICR && IS_ENABLED(NRF91_ERRATA_7_ENABLE_WORKAROUND)
|
|
static inline void nrf91_errata_7_enter(void)
|
|
{
|
|
__disable_irq();
|
|
}
|
|
|
|
static inline void nrf91_errata_7_exit(void)
|
|
{
|
|
__DSB();
|
|
__enable_irq();
|
|
}
|
|
|
|
static void nrf_buffer_read_91_uicr(void *data, off_t addr, size_t len)
|
|
{
|
|
nrf91_errata_7_enter();
|
|
nrf_nvmc_buffer_read(data, (uint32_t)addr, len);
|
|
nrf91_errata_7_exit();
|
|
}
|
|
#endif
|
|
|
|
static void nvmc_wait_ready(void)
|
|
{
|
|
while (!nrfx_nvmc_write_done_check()) {
|
|
}
|
|
}
|
|
|
|
static int flash_nrf_read(const struct device *dev, off_t addr,
|
|
void *data, size_t len)
|
|
{
|
|
const bool within_uicr = is_uicr_addr_valid(addr, len);
|
|
|
|
if (is_regular_addr_valid(addr, len)) {
|
|
addr += DT_REG_ADDR(SOC_NV_FLASH_NODE);
|
|
} else if (!within_uicr) {
|
|
LOG_ERR("invalid address: 0x%08lx:%zu",
|
|
(unsigned long)addr, len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!len) {
|
|
return 0;
|
|
}
|
|
|
|
#if CONFIG_SOC_FLASH_NRF_UICR && IS_ENABLED(NRF91_ERRATA_7_ENABLE_WORKAROUND)
|
|
if (within_uicr) {
|
|
nrf_buffer_read_91_uicr(data, (uint32_t)addr, len);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
nrf_nvmc_buffer_read(data, (uint32_t)addr, len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int flash_nrf_write(const struct device *dev, off_t addr,
|
|
const void *data, size_t len)
|
|
{
|
|
int ret;
|
|
|
|
if (is_regular_addr_valid(addr, len)) {
|
|
addr += DT_REG_ADDR(SOC_NV_FLASH_NODE);
|
|
} else if (!is_uicr_addr_valid(addr, len)) {
|
|
LOG_ERR("invalid address: 0x%08lx:%zu",
|
|
(unsigned long)addr, len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if !defined(CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS)
|
|
if (!is_aligned_32(addr) || (len % sizeof(uint32_t))) {
|
|
LOG_ERR("not word-aligned: 0x%08lx:%zu",
|
|
(unsigned long)addr, len);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
if (!len) {
|
|
return 0;
|
|
}
|
|
|
|
SYNC_LOCK();
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
if (nrf_flash_sync_is_required()) {
|
|
ret = write_synchronously(addr, data, len);
|
|
} else
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
{
|
|
ret = write(addr, data, len);
|
|
}
|
|
|
|
SYNC_UNLOCK();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int flash_nrf_erase(const struct device *dev, off_t addr, size_t size)
|
|
{
|
|
uint32_t pg_size = nrfx_nvmc_flash_page_size_get();
|
|
uint32_t n_pages = size / pg_size;
|
|
int ret;
|
|
|
|
if (is_regular_addr_valid(addr, size)) {
|
|
/* Erase can only be done per page */
|
|
if (((addr % pg_size) != 0) || ((size % pg_size) != 0)) {
|
|
LOG_ERR("unaligned address: 0x%08lx:%zu",
|
|
(unsigned long)addr, size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!n_pages) {
|
|
return 0;
|
|
}
|
|
|
|
addr += DT_REG_ADDR(SOC_NV_FLASH_NODE);
|
|
#ifdef CONFIG_SOC_FLASH_NRF_UICR
|
|
} else if (addr != (off_t)NRF_UICR || size != sizeof(*NRF_UICR)) {
|
|
LOG_ERR("invalid address: 0x%08lx:%zu",
|
|
(unsigned long)addr, size);
|
|
return -EINVAL;
|
|
}
|
|
#else
|
|
} else {
|
|
LOG_ERR("invalid address: 0x%08lx:%zu",
|
|
(unsigned long)addr, size);
|
|
return -EINVAL;
|
|
}
|
|
#endif /* CONFIG_SOC_FLASH_NRF_UICR */
|
|
|
|
SYNC_LOCK();
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
if (nrf_flash_sync_is_required()) {
|
|
ret = erase_synchronously(addr, size);
|
|
} else
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
{
|
|
ret = erase(addr, size);
|
|
}
|
|
|
|
SYNC_UNLOCK();
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
static struct flash_pages_layout dev_layout;
|
|
|
|
static void flash_nrf_pages_layout(const struct device *dev,
|
|
const struct flash_pages_layout **layout,
|
|
size_t *layout_size)
|
|
{
|
|
*layout = &dev_layout;
|
|
*layout_size = 1;
|
|
}
|
|
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
|
|
|
|
static const struct flash_parameters *
|
|
flash_nrf_get_parameters(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return &flash_nrf_parameters;
|
|
}
|
|
|
|
static const struct flash_driver_api flash_nrf_api = {
|
|
.read = flash_nrf_read,
|
|
.write = flash_nrf_write,
|
|
.erase = flash_nrf_erase,
|
|
.get_parameters = flash_nrf_get_parameters,
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
.page_layout = flash_nrf_pages_layout,
|
|
#endif
|
|
};
|
|
|
|
static int nrf_flash_init(const struct device *dev)
|
|
{
|
|
SYNC_INIT();
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
nrf_flash_sync_init();
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
dev_layout.pages_count = nrfx_nvmc_flash_page_count_get();
|
|
dev_layout.pages_size = nrfx_nvmc_flash_page_size_get();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEVICE_DT_INST_DEFINE(0, nrf_flash_init, NULL,
|
|
NULL, NULL,
|
|
POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY,
|
|
&flash_nrf_api);
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
|
|
static int erase_synchronously(uint32_t addr, uint32_t size)
|
|
{
|
|
struct flash_context context = {
|
|
.flash_addr = addr,
|
|
.len = size,
|
|
.enable_time_limit = 1, /* enable time limit */
|
|
#if defined(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE)
|
|
.flash_addr_next = addr
|
|
#endif
|
|
};
|
|
|
|
struct flash_op_desc flash_op_desc = {
|
|
.handler = erase_op,
|
|
.context = &context
|
|
};
|
|
|
|
nrf_flash_sync_set_context(FLASH_SLOT_ERASE);
|
|
return nrf_flash_sync_exe(&flash_op_desc);
|
|
}
|
|
|
|
static int write_synchronously(off_t addr, const void *data, size_t len)
|
|
{
|
|
struct flash_context context = {
|
|
.data_addr = (uint32_t) data,
|
|
.flash_addr = addr,
|
|
.len = len,
|
|
.enable_time_limit = 1 /* enable time limit */
|
|
};
|
|
|
|
struct flash_op_desc flash_op_desc = {
|
|
.handler = write_op,
|
|
.context = &context
|
|
};
|
|
|
|
nrf_flash_sync_set_context(FLASH_SLOT_WRITE);
|
|
return nrf_flash_sync_exe(&flash_op_desc);
|
|
}
|
|
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
|
|
static int erase_op(void *context)
|
|
{
|
|
uint32_t pg_size = nrfx_nvmc_flash_page_size_get();
|
|
struct flash_context *e_ctx = context;
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
uint32_t i = 0U;
|
|
|
|
if (e_ctx->enable_time_limit) {
|
|
nrf_flash_sync_get_timestamp_begin();
|
|
}
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
|
|
#ifdef CONFIG_SOC_FLASH_NRF_UICR
|
|
if (e_ctx->flash_addr == (off_t)NRF_UICR) {
|
|
if (SUSPEND_POFWARN()) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
(void)nrfx_nvmc_uicr_erase();
|
|
RESUME_POFWARN();
|
|
return FLASH_OP_DONE;
|
|
}
|
|
#endif
|
|
|
|
do {
|
|
if (SUSPEND_POFWARN()) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE)
|
|
if (e_ctx->flash_addr == e_ctx->flash_addr_next) {
|
|
nrfx_nvmc_page_partial_erase_init(e_ctx->flash_addr,
|
|
CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE_MS);
|
|
e_ctx->flash_addr_next += pg_size;
|
|
}
|
|
|
|
if (nrfx_nvmc_page_partial_erase_continue()) {
|
|
e_ctx->len -= pg_size;
|
|
e_ctx->flash_addr += pg_size;
|
|
}
|
|
#else
|
|
(void)nrfx_nvmc_page_erase(e_ctx->flash_addr);
|
|
e_ctx->len -= pg_size;
|
|
e_ctx->flash_addr += pg_size;
|
|
#endif /* CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE */
|
|
|
|
RESUME_POFWARN();
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
i++;
|
|
|
|
if (e_ctx->enable_time_limit) {
|
|
if (nrf_flash_sync_check_time_limit(i)) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
|
|
} while (e_ctx->len > 0);
|
|
|
|
return (e_ctx->len > 0) ? FLASH_OP_ONGOING : FLASH_OP_DONE;
|
|
}
|
|
|
|
static void shift_write_context(uint32_t shift, struct flash_context *w_ctx)
|
|
{
|
|
w_ctx->flash_addr += shift;
|
|
w_ctx->data_addr += shift;
|
|
w_ctx->len -= shift;
|
|
}
|
|
|
|
static int write_op(void *context)
|
|
{
|
|
struct flash_context *w_ctx = context;
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
uint32_t i = 1U;
|
|
|
|
if (w_ctx->enable_time_limit) {
|
|
nrf_flash_sync_get_timestamp_begin();
|
|
}
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
#if defined(CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS)
|
|
/* If not aligned, write unaligned beginning */
|
|
if (!is_aligned_32(w_ctx->flash_addr)) {
|
|
uint32_t count = sizeof(uint32_t) - (w_ctx->flash_addr & 0x3);
|
|
|
|
if (count > w_ctx->len) {
|
|
count = w_ctx->len;
|
|
}
|
|
|
|
if (SUSPEND_POFWARN()) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
nrfx_nvmc_bytes_write(w_ctx->flash_addr,
|
|
(const void *)w_ctx->data_addr,
|
|
count);
|
|
|
|
RESUME_POFWARN();
|
|
shift_write_context(count, w_ctx);
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
if (w_ctx->enable_time_limit) {
|
|
if (nrf_flash_sync_check_time_limit(1)) {
|
|
nvmc_wait_ready();
|
|
return FLASH_OP_ONGOING;
|
|
}
|
|
}
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
}
|
|
#endif /* CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS */
|
|
/* Write all the 4-byte aligned data */
|
|
while (w_ctx->len >= sizeof(uint32_t)) {
|
|
if (SUSPEND_POFWARN()) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
nrfx_nvmc_word_write(w_ctx->flash_addr,
|
|
UNALIGNED_GET((uint32_t *)w_ctx->data_addr));
|
|
RESUME_POFWARN();
|
|
shift_write_context(sizeof(uint32_t), w_ctx);
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
i++;
|
|
|
|
if (w_ctx->enable_time_limit) {
|
|
if (nrf_flash_sync_check_time_limit(i)) {
|
|
nvmc_wait_ready();
|
|
return FLASH_OP_ONGOING;
|
|
}
|
|
}
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
}
|
|
#if defined(CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS)
|
|
/* Write remaining unaligned data */
|
|
if (w_ctx->len) {
|
|
if (SUSPEND_POFWARN()) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
nrfx_nvmc_bytes_write(w_ctx->flash_addr,
|
|
(const void *)w_ctx->data_addr,
|
|
w_ctx->len);
|
|
RESUME_POFWARN();
|
|
shift_write_context(w_ctx->len, w_ctx);
|
|
}
|
|
#endif /* CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS */
|
|
nvmc_wait_ready();
|
|
|
|
return FLASH_OP_DONE;
|
|
}
|
|
|
|
static int erase(uint32_t addr, uint32_t size)
|
|
{
|
|
struct flash_context context = {
|
|
.flash_addr = addr,
|
|
.len = size,
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
.enable_time_limit = 0, /* disable time limit */
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
#if defined(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE)
|
|
.flash_addr_next = addr
|
|
#endif
|
|
};
|
|
|
|
return erase_op(&context);
|
|
}
|
|
|
|
static int write(off_t addr, const void *data, size_t len)
|
|
{
|
|
struct flash_context context = {
|
|
.data_addr = (uint32_t) data,
|
|
.flash_addr = addr,
|
|
.len = len,
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
.enable_time_limit = 0 /* disable time limit */
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
};
|
|
|
|
return write_op(&context);
|
|
}
|
|
|
|
#if NRF52_ERRATA_242_PRESENT
|
|
/* Disable POFWARN by writing POFCON before a write or erase operation.
|
|
* Do not attempt to write or erase if EVENTS_POFWARN is already asserted.
|
|
*/
|
|
static bool pofcon_enabled;
|
|
|
|
static int suspend_pofwarn(void)
|
|
{
|
|
if (!nrf52_errata_242()) {
|
|
return 0;
|
|
}
|
|
|
|
bool enabled;
|
|
nrf_power_pof_thr_t pof_thr;
|
|
|
|
pof_thr = nrf_power_pofcon_get(NRF_POWER, &enabled);
|
|
|
|
if (enabled) {
|
|
nrf_power_pofcon_set(NRF_POWER, false, pof_thr);
|
|
|
|
/* This check need to be reworked once POFWARN event will be
|
|
* served by zephyr.
|
|
*/
|
|
if (nrf_power_event_check(NRF_POWER, NRF_POWER_EVENT_POFWARN)) {
|
|
nrf_power_pofcon_set(NRF_POWER, true, pof_thr);
|
|
return -ECANCELED;
|
|
}
|
|
|
|
pofcon_enabled = enabled;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void restore_pofwarn(void)
|
|
{
|
|
nrf_power_pof_thr_t pof_thr;
|
|
|
|
if (pofcon_enabled) {
|
|
pof_thr = nrf_power_pofcon_get(NRF_POWER, NULL);
|
|
|
|
nrf_power_pofcon_set(NRF_POWER, true, pof_thr);
|
|
pofcon_enabled = false;
|
|
}
|
|
}
|
|
#endif /* NRF52_ERRATA_242_PRESENT */
|