- BLE peripheral applications - dr_piezo and bladder_patch projects Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
436 lines
13 KiB
C
436 lines
13 KiB
C
/**
|
|
* Copyright (c) 2019 - 2021, Nordic Semiconductor ASA
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form, except as embedded into a Nordic
|
|
* Semiconductor ASA integrated circuit in a product or a software update for
|
|
* such product, must reproduce the above copyright notice, this list of
|
|
* conditions and the following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of Nordic Semiconductor ASA nor the names of its
|
|
* contributors may be used to endorse or promote products derived from this
|
|
* software without specific prior written permission.
|
|
*
|
|
* 4. This software, with or without modification, must only be used with a
|
|
* Nordic Semiconductor ASA integrated circuit.
|
|
*
|
|
* 5. Any software provided in binary form under this license must not be reverse
|
|
* engineered, decompiled, modified and/or disassembled.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
|
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <nrfx.h>
|
|
|
|
#if NRFX_CHECK(NRFX_NVMC_ENABLED)
|
|
|
|
#include <nrfx_nvmc.h>
|
|
|
|
/**
|
|
* Value representing the number of bytes in a word.
|
|
*
|
|
* It is used in loops iterating over bytes contained in a word
|
|
* or in word-alignment checks.
|
|
*/
|
|
#define NVMC_BYTES_IN_WORD 4
|
|
|
|
/**
|
|
* Value representing non-volatile memory (NVM) page count.
|
|
*
|
|
* This symbol is needed to determine NVM page count for chips that cannot
|
|
* always access FICR for this information.
|
|
*/
|
|
#if defined(NRF9160_XXAA)
|
|
#define NVMC_FLASH_PAGE_COUNT 256
|
|
#endif
|
|
|
|
/**
|
|
* Value representing non-volatile memory (NVM) page size in bytes.
|
|
*
|
|
* This symbol is needed to determine NVM page size for chips that cannot
|
|
* always access FICR for this information.
|
|
*/
|
|
#if defined(NRF9160_XXAA)
|
|
#define NVMC_FLASH_PAGE_SIZE 0x1000 ///< 4 kB
|
|
#endif
|
|
|
|
#if defined(NRF_NVMC_PARTIAL_ERASE_PRESENT)
|
|
/**
|
|
* Value representing the page erase time.
|
|
*
|
|
* This value is used to determine whether the partial erase is still in progress.
|
|
*/
|
|
#if defined(NRF52810_XXAA) || defined(NRF52811_XXAA) || defined(NRF52840_XXAA)
|
|
#define NVMC_PAGE_ERASE_DURATION_MS 85
|
|
#elif defined(NRF52820_XXAA) || defined(NRF52833_XXAA) || defined(NRF9160_XXAA)
|
|
#define NVMC_PAGE_ERASE_DURATION_MS 87
|
|
#else
|
|
#error "Page partial erase present but could not determine its total duration for given SoC"
|
|
#endif
|
|
|
|
/**
|
|
* Value representing the invalid page partial erase address.
|
|
*
|
|
* This value is used for representing a NULL pointer for
|
|
* partial erase, as that address 0 can be a valid
|
|
* memory address in flash.
|
|
*/
|
|
#define NVMC_PARTIAL_ERASE_INVALID_ADDR 0xFFFFFFFF
|
|
|
|
/** Internal counter for page partial erase. */
|
|
static uint32_t m_partial_erase_time_elapsed;
|
|
|
|
/** Partial erase page address. */
|
|
static uint32_t m_partial_erase_page_addr = NVMC_PARTIAL_ERASE_INVALID_ADDR;
|
|
|
|
#endif // defined(NRF_NVMC_PARTIAL_ERASE_PRESENT)
|
|
|
|
static uint32_t flash_page_size_get(void)
|
|
{
|
|
uint32_t flash_page_size = 0;
|
|
|
|
#if defined(NRF51) || defined(NRF52_SERIES)
|
|
flash_page_size = nrf_ficr_codepagesize_get(NRF_FICR);
|
|
#elif defined(NVMC_FLASH_PAGE_SIZE)
|
|
flash_page_size = NVMC_FLASH_PAGE_SIZE;
|
|
#else
|
|
#error "Cannot determine flash page size for a given SoC."
|
|
#endif
|
|
|
|
return flash_page_size;
|
|
}
|
|
|
|
static uint32_t flash_page_count_get(void)
|
|
{
|
|
uint32_t page_count = 0;
|
|
|
|
#if defined(NRF51) || defined(NRF52_SERIES)
|
|
page_count = nrf_ficr_codesize_get(NRF_FICR);
|
|
#elif defined(NVMC_FLASH_PAGE_COUNT)
|
|
page_count = NVMC_FLASH_PAGE_COUNT;
|
|
#else
|
|
#error "Cannot determine flash page count for a given SoC."
|
|
#endif
|
|
|
|
return page_count;
|
|
}
|
|
|
|
static uint32_t flash_total_size_get(void)
|
|
{
|
|
return flash_page_size_get() * flash_page_count_get();
|
|
}
|
|
|
|
|
|
static bool is_page_aligned_check(uint32_t addr)
|
|
{
|
|
/* If the modulo operation returns '0', then the address is aligned. */
|
|
return !(addr % flash_page_size_get());
|
|
}
|
|
|
|
static uint32_t partial_word_create(uint32_t addr, uint8_t const * bytes, uint32_t bytes_count)
|
|
{
|
|
uint32_t value32;
|
|
uint32_t byte_shift;
|
|
|
|
byte_shift = addr % NVMC_BYTES_IN_WORD;
|
|
|
|
NRFX_ASSERT(bytes_count <= (NVMC_BYTES_IN_WORD - byte_shift));
|
|
|
|
value32 = 0xFFFFFFFF;
|
|
for (uint32_t i = 0; i < bytes_count; i++)
|
|
{
|
|
((uint8_t *)&value32)[byte_shift] = bytes[i];
|
|
byte_shift++;
|
|
}
|
|
|
|
return value32;
|
|
}
|
|
|
|
static void nvmc_readonly_mode_set(void)
|
|
{
|
|
#if defined(NRF_TRUSTZONE_NONSECURE)
|
|
nrf_nvmc_nonsecure_mode_set(NRF_NVMC, NRF_NVMC_NS_MODE_READONLY);
|
|
#else
|
|
nrf_nvmc_mode_set(NRF_NVMC, NRF_NVMC_MODE_READONLY);
|
|
#endif
|
|
}
|
|
|
|
static void nvmc_write_mode_set(void)
|
|
{
|
|
#if defined(NRF_TRUSTZONE_NONSECURE)
|
|
nrf_nvmc_nonsecure_mode_set(NRF_NVMC, NRF_NVMC_NS_MODE_WRITE);
|
|
#else
|
|
nrf_nvmc_mode_set(NRF_NVMC, NRF_NVMC_MODE_WRITE);
|
|
#endif
|
|
}
|
|
|
|
static void nvmc_erase_mode_set(void)
|
|
{
|
|
#if defined(NRF_TRUSTZONE_NONSECURE)
|
|
nrf_nvmc_nonsecure_mode_set(NRF_NVMC, NRF_NVMC_NS_MODE_ERASE);
|
|
#else
|
|
nrf_nvmc_mode_set(NRF_NVMC, NRF_NVMC_MODE_ERASE);
|
|
#endif
|
|
}
|
|
|
|
static void nvmc_word_write(uint32_t addr, uint32_t value)
|
|
{
|
|
#if defined(NRF9160_XXAA)
|
|
while (!nrf_nvmc_write_ready_check(NRF_NVMC))
|
|
{}
|
|
#else
|
|
while (!nrf_nvmc_ready_check(NRF_NVMC))
|
|
{}
|
|
#endif
|
|
|
|
*(volatile uint32_t *)addr = value;
|
|
__DMB();
|
|
}
|
|
|
|
static void nvmc_words_write(uint32_t addr, void const * src, uint32_t num_words)
|
|
{
|
|
for (uint32_t i = 0; i < num_words; i++)
|
|
{
|
|
nvmc_word_write(addr + (NVMC_BYTES_IN_WORD * i), ((uint32_t const *)src)[i]);
|
|
}
|
|
}
|
|
|
|
nrfx_err_t nrfx_nvmc_page_erase(uint32_t addr)
|
|
{
|
|
NRFX_ASSERT(addr < flash_total_size_get());
|
|
|
|
if (!is_page_aligned_check(addr))
|
|
{
|
|
return NRFX_ERROR_INVALID_ADDR;
|
|
}
|
|
|
|
nvmc_erase_mode_set();
|
|
nrf_nvmc_page_erase_start(NRF_NVMC, addr);
|
|
while (!nrf_nvmc_ready_check(NRF_NVMC))
|
|
{}
|
|
nvmc_readonly_mode_set();
|
|
|
|
return NRFX_SUCCESS;
|
|
}
|
|
|
|
nrfx_err_t nrfx_nvmc_uicr_erase(void)
|
|
{
|
|
#if defined(NVMC_ERASEUICR_ERASEUICR_Msk)
|
|
nvmc_erase_mode_set();
|
|
nrf_nvmc_uicr_erase_start(NRF_NVMC);
|
|
while (!nrf_nvmc_ready_check(NRF_NVMC))
|
|
{}
|
|
nvmc_readonly_mode_set();
|
|
return NRFX_SUCCESS;
|
|
#else
|
|
return NRFX_ERROR_NOT_SUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
void nrfx_nvmc_all_erase(void)
|
|
{
|
|
nvmc_erase_mode_set();
|
|
nrf_nvmc_erase_all_start(NRF_NVMC);
|
|
while (!nrf_nvmc_ready_check(NRF_NVMC))
|
|
{}
|
|
nvmc_readonly_mode_set();
|
|
}
|
|
|
|
#if defined(NRF_NVMC_PARTIAL_ERASE_PRESENT)
|
|
nrfx_err_t nrfx_nvmc_page_partial_erase_init(uint32_t addr, uint32_t duration_ms)
|
|
{
|
|
NRFX_ASSERT(addr < flash_total_size_get());
|
|
|
|
if (!is_page_aligned_check(addr))
|
|
{
|
|
return NRFX_ERROR_INVALID_ADDR;
|
|
}
|
|
|
|
m_partial_erase_time_elapsed = 0;
|
|
m_partial_erase_page_addr = addr;
|
|
nrf_nvmc_partial_erase_duration_set(NRF_NVMC, duration_ms);
|
|
|
|
return NRFX_SUCCESS;
|
|
}
|
|
|
|
bool nrfx_nvmc_page_partial_erase_continue(void)
|
|
{
|
|
NRFX_ASSERT(m_partial_erase_page_addr != NVMC_PARTIAL_ERASE_INVALID_ADDR);
|
|
|
|
uint32_t duration_ms = nrf_nvmc_partial_erase_duration_get(NRF_NVMC);
|
|
|
|
#if defined(NVMC_CONFIG_WEN_PEen)
|
|
nrf_nvmc_mode_set(NRF_NVMC, NRF_NVMC_MODE_PARTIAL_ERASE);
|
|
#else
|
|
nrf_nvmc_mode_set(NRF_NVMC, NRF_NVMC_MODE_ERASE);
|
|
#endif
|
|
|
|
nrf_nvmc_page_partial_erase_start(NRF_NVMC, m_partial_erase_page_addr);
|
|
while (!nrf_nvmc_ready_check(NRF_NVMC))
|
|
{}
|
|
nvmc_readonly_mode_set();
|
|
|
|
m_partial_erase_time_elapsed += duration_ms;
|
|
if (m_partial_erase_time_elapsed < NVMC_PAGE_ERASE_DURATION_MS)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
m_partial_erase_page_addr = NVMC_PARTIAL_ERASE_INVALID_ADDR;
|
|
return true;
|
|
}
|
|
}
|
|
#endif // defined(NRF_NVMC_PARTIAL_ERASE_PRESENT)
|
|
|
|
bool nrfx_nvmc_byte_writable_check(uint32_t addr, uint8_t val_to_check)
|
|
{
|
|
NRFX_ASSERT((addr < flash_total_size_get()) ||
|
|
((addr - (uint32_t)NRF_UICR) < sizeof(NRF_UICR_Type)));
|
|
|
|
uint8_t val_on_addr = *(uint8_t const *)addr;
|
|
return (val_to_check & val_on_addr) == val_to_check;
|
|
}
|
|
|
|
bool nrfx_nvmc_word_writable_check(uint32_t addr, uint32_t val_to_check)
|
|
{
|
|
NRFX_ASSERT((addr < flash_total_size_get()) ||
|
|
((addr - (uint32_t)NRF_UICR) < sizeof(NRF_UICR_Type)));
|
|
NRFX_ASSERT(nrfx_is_word_aligned((void const *)addr));
|
|
|
|
uint32_t val_on_addr = *(uint32_t const *)addr;
|
|
return (val_to_check & val_on_addr) == val_to_check;
|
|
}
|
|
|
|
void nrfx_nvmc_byte_write(uint32_t addr, uint8_t value)
|
|
{
|
|
uint32_t aligned_addr = addr & ~(0x03UL);
|
|
|
|
nrfx_nvmc_word_write(aligned_addr, partial_word_create(addr, &value, 1));
|
|
}
|
|
|
|
void nrfx_nvmc_word_write(uint32_t addr, uint32_t value)
|
|
{
|
|
NRFX_ASSERT((addr < flash_total_size_get()) ||
|
|
((addr - (uint32_t)NRF_UICR) < sizeof(NRF_UICR_Type)));
|
|
NRFX_ASSERT(nrfx_is_word_aligned((void const *)addr));
|
|
|
|
nvmc_write_mode_set();
|
|
|
|
nvmc_word_write(addr, value);
|
|
|
|
nvmc_readonly_mode_set();
|
|
}
|
|
|
|
void nrfx_nvmc_bytes_write(uint32_t addr, void const * src, uint32_t num_bytes)
|
|
{
|
|
NRFX_ASSERT((addr < flash_total_size_get()) ||
|
|
((addr - (uint32_t)NRF_UICR) < sizeof(NRF_UICR_Type)));
|
|
|
|
nvmc_write_mode_set();
|
|
|
|
uint8_t const * bytes_src = (uint8_t const *)src;
|
|
|
|
uint32_t unaligned_bytes = addr % NVMC_BYTES_IN_WORD;
|
|
if (unaligned_bytes != 0)
|
|
{
|
|
uint32_t leading_bytes = NVMC_BYTES_IN_WORD - unaligned_bytes;
|
|
if (leading_bytes > num_bytes)
|
|
{
|
|
leading_bytes = num_bytes;
|
|
}
|
|
|
|
nvmc_word_write(addr - unaligned_bytes,
|
|
partial_word_create(addr, bytes_src, leading_bytes));
|
|
num_bytes -= leading_bytes;
|
|
addr += leading_bytes;
|
|
bytes_src += leading_bytes;
|
|
}
|
|
|
|
#if defined(__CORTEX_M) && (__CORTEX_M == 0U)
|
|
if (!nrfx_is_word_aligned((void const *)bytes_src))
|
|
{
|
|
/* Cortex-M0 allows only word-aligned RAM access.
|
|
If source address is not word-aligned, bytes are combined
|
|
into words explicitly. */
|
|
for (uint32_t i = 0; i < num_bytes / NVMC_BYTES_IN_WORD; i++)
|
|
{
|
|
uint32_t word = (uint32_t)bytes_src[0]
|
|
| ((uint32_t)bytes_src[1]) << 8
|
|
| ((uint32_t)bytes_src[2]) << 16
|
|
| ((uint32_t)bytes_src[3]) << 24;
|
|
|
|
nvmc_word_write(addr, word);
|
|
bytes_src += NVMC_BYTES_IN_WORD;
|
|
addr += NVMC_BYTES_IN_WORD;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
uint32_t word_count = num_bytes / NVMC_BYTES_IN_WORD;
|
|
|
|
nvmc_words_write(addr, (uint32_t const *)bytes_src, word_count);
|
|
|
|
addr += word_count * NVMC_BYTES_IN_WORD;
|
|
bytes_src += word_count * NVMC_BYTES_IN_WORD;
|
|
}
|
|
|
|
uint32_t trailing_bytes = num_bytes % NVMC_BYTES_IN_WORD;
|
|
if (trailing_bytes != 0)
|
|
{
|
|
nvmc_word_write(addr, partial_word_create(addr, bytes_src, trailing_bytes));
|
|
}
|
|
|
|
nvmc_readonly_mode_set();
|
|
}
|
|
|
|
void nrfx_nvmc_words_write(uint32_t addr, void const * src, uint32_t num_words)
|
|
{
|
|
NRFX_ASSERT((addr < flash_total_size_get()) ||
|
|
((addr - (uint32_t)NRF_UICR) < sizeof(NRF_UICR_Type)));
|
|
NRFX_ASSERT(nrfx_is_word_aligned((void const *)addr));
|
|
NRFX_ASSERT(nrfx_is_word_aligned(src));
|
|
|
|
nvmc_write_mode_set();
|
|
|
|
nvmc_words_write(addr, src, num_words);
|
|
|
|
nvmc_readonly_mode_set();
|
|
}
|
|
|
|
uint32_t nrfx_nvmc_flash_size_get(void)
|
|
{
|
|
return flash_total_size_get();
|
|
}
|
|
|
|
uint32_t nrfx_nvmc_flash_page_size_get(void)
|
|
{
|
|
return flash_page_size_get();
|
|
}
|
|
|
|
uint32_t nrfx_nvmc_flash_page_count_get(void)
|
|
{
|
|
return flash_page_count_get();
|
|
}
|
|
|
|
#endif // NRFX_CHECK(NRFX_NVMC_ENABLED)
|