/*
|
* Copyright (c) 2019-2025 Beijing Hanwei Innovation Technology Ltd. Co. and
|
* its subsidiaries and affiliates (collectly called MKSEMI).
|
*
|
* 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 an MKSEMI
|
* 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 MKSEMI 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
|
* MKSEMI 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 MKSEMI "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 MKSEMI 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 "mk_nvm.h"
|
#include "mk_trace.h"
|
#include "crc.h"
|
|
/**************************************************************************************************
|
Macros
|
**************************************************************************************************/
|
|
/*! The size of flash space for NVM is 8K */
|
#define PAL_NVM_SIZE (0x2000)
|
|
/*! NVM start address is 0x0407E000, 126th sector */
|
#define PAL_NVM_ADDR (0x0407E000)
|
|
/*! defragmentation area size. */
|
#define NVM_DEFRAGMENT_SIZE (0x400)
|
|
/*! defragmentation area start address */
|
#define NVM_DEFRAGMENT_START_ADDR (PAL_NVM_SIZE - NVM_DEFRAGMENT_SIZE)
|
|
/*! NVM data offset. */
|
#define NVM_DATA_OFFSET 0x0000
|
|
/*! Reserved filecode. */
|
#define NVM_OBSOLETED_REC_TYPE ((uint16_t)0)
|
|
/* Unused (erased) filecode. */
|
#define NVM_UNUSED_REC_TYPE ((uint16_t)0xFFFF)
|
|
/*! Flash word size. */
|
#define NVM_FLASH_WORD_SIZE 1U
|
|
/*! Align value to word boundary. */
|
#define NVM_WORD_ALIGN(x) (((x) + (NVM_FLASH_WORD_SIZE - 1)) & ~(NVM_FLASH_WORD_SIZE - 1))
|
|
#define pal_flash_read(pBuf, size, srcAddr) flash_read(FLASH_ID0, PAL_NVM_ADDR + srcAddr, (uint8_t *)pBuf, size)
|
|
#if FLASH_WRITE_EN
|
#define pal_flash_write(pBuf, size, dstAddr) flash_write_nbytes(FLASH_ID0, PAL_NVM_ADDR + dstAddr, pBuf, size)
|
#else
|
#define pal_flash_write(pBuf, size, dstAddr) (void)0
|
#endif
|
|
/**************************************************************************************************
|
Data Types
|
**************************************************************************************************/
|
|
/*! \brief Header. */
|
typedef struct
|
{
|
uint16_t id; /*!< Stored data ID. */
|
uint16_t len; /*!< Stored data length. */
|
uint16_t headerCrc; /*!< CRC of this header. */
|
uint16_t dataCrc; /*!< CRC of subsequent data. */
|
} rec_hdr_t;
|
|
/*************************************************************************************************/
|
/*!
|
* \brief Erase sector.
|
*
|
* \param[in] numOfSectors Number of sectors to be erased.
|
* \param[in] startAddr Sector aligned start address.
|
*/
|
/*************************************************************************************************/
|
static void pal_flash_erase_sectors(uint32_t numOfSectors, uint32_t startAddr)
|
{
|
#if FLASH_WRITE_EN
|
uint32_t start_idx = (PAL_NVM_ADDR + startAddr - FLASH_BASE) / FLASH_SECTOR_SIZE;
|
|
ASSERT((PAL_NVM_ADDR + startAddr) % FLASH_SECTOR_SIZE == 0, "Address is not aligned to sector size: %d", PAL_NVM_ADDR + startAddr);
|
ASSERT(numOfSectors <= (PAL_NVM_SIZE / FLASH_SECTOR_SIZE), "Number of sectors exceeds limit: %d", numOfSectors);
|
ASSERT(start_idx + numOfSectors <= (FLASH_SIZE / FLASH_SECTOR_SIZE), "End index exceeds limit: %d", start_idx + numOfSectors);
|
|
for (uint32_t i = 0; i < numOfSectors; i++)
|
{
|
flash_sector_erase(FLASH_ID0, i + start_idx);
|
}
|
#endif
|
}
|
|
#define NVM_REC_FREE (1U)
|
#define NVM_REC_RUIN (2U)
|
#define NVM_REC_HITS (3U)
|
#define NVM_REC_ENDS (4U)
|
|
static uint16_t _seek_record(rec_hdr_t *header, uint32_t *storageAddr, uint16_t id)
|
{
|
do
|
{
|
pal_flash_read(header, sizeof(rec_hdr_t), *storageAddr);
|
if (header->id == NVM_UNUSED_REC_TYPE)
|
{
|
return NVM_REC_FREE;
|
}
|
else if (header->id == NVM_OBSOLETED_REC_TYPE)
|
{
|
if ((header->headerCrc != 0) || (header->dataCrc != 0))
|
{
|
return NVM_REC_RUIN;
|
}
|
}
|
else
|
{
|
uint16_t headerCrc = crc16((uint8_t *)header, sizeof(header->id) + sizeof(header->len));
|
if (headerCrc != header->headerCrc)
|
{
|
return NVM_REC_RUIN;
|
}
|
else if (header->id == id)
|
{
|
return NVM_REC_HITS;
|
}
|
}
|
|
*storageAddr += NVM_WORD_ALIGN(header->len) + sizeof(rec_hdr_t);
|
} while ((*storageAddr - NVM_DATA_OFFSET) < PAL_NVM_SIZE);
|
|
return NVM_REC_ENDS;
|
}
|
|
void mk_nvm_init(void)
|
{
|
flash_open(FLASH_ID0, NULL);
|
|
rec_hdr_t header;
|
uint32_t storageAddr = NVM_DATA_OFFSET;
|
uint16_t ret = _seek_record(&header, &storageAddr, NVM_UNUSED_REC_TYPE);
|
|
if (ret == NVM_REC_RUIN)
|
{
|
mk_nvm_erase_all();
|
}
|
}
|
|
bool mk_nvm_read(uint16_t id, uint8_t *pData, uint16_t len)
|
{
|
rec_hdr_t header;
|
uint16_t dataCrc;
|
uint32_t storageAddr = NVM_DATA_OFFSET;
|
|
ASSERT((id != NVM_UNUSED_REC_TYPE) && (id != NVM_OBSOLETED_REC_TYPE), "Invalid ID: %d", id);
|
|
uint16_t ret = _seek_record(&header, &storageAddr, id);
|
if (ret == NVM_REC_HITS)
|
{
|
if (header.len == len)
|
{
|
pal_flash_read(pData, header.len, storageAddr + sizeof(rec_hdr_t));
|
dataCrc = crc16(pData, header.len);
|
if (dataCrc == header.dataCrc)
|
{
|
return true;
|
}
|
}
|
}
|
|
return false;
|
}
|
|
static void _obsolete_record(rec_hdr_t *header, uint32_t storageAddr)
|
{
|
header->id = NVM_OBSOLETED_REC_TYPE;
|
header->headerCrc = 0;
|
header->dataCrc = 0;
|
pal_flash_write((const uint8_t *)header, sizeof(rec_hdr_t), storageAddr);
|
}
|
|
static void __copy_record(uint32_t srcAddr, uint32_t dstAddr, uint16_t len)
|
{
|
uint8_t buf[8];
|
uint16_t remain = len;
|
while (remain > 0)
|
{
|
uint16_t copyLen = (remain > sizeof(buf)) ? sizeof(buf) : remain;
|
pal_flash_read(buf, copyLen, srcAddr);
|
pal_flash_write(buf, copyLen, dstAddr);
|
srcAddr += copyLen;
|
dstAddr += copyLen;
|
remain -= copyLen;
|
}
|
}
|
|
static void __copy_valid_records(uint32_t srcAddr, uint32_t dstAddr, uint32_t endAddr)
|
{
|
rec_hdr_t header;
|
|
while (srcAddr < endAddr)
|
{
|
pal_flash_read(&header, sizeof(rec_hdr_t), srcAddr);
|
if (header.id == NVM_UNUSED_REC_TYPE)
|
{
|
break;
|
}
|
else if (header.id == NVM_OBSOLETED_REC_TYPE)
|
{
|
}
|
else
|
{
|
__copy_record(srcAddr, dstAddr, NVM_WORD_ALIGN(header.len) + sizeof(rec_hdr_t));
|
dstAddr += NVM_WORD_ALIGN(header.len) + sizeof(rec_hdr_t);
|
}
|
|
srcAddr += NVM_WORD_ALIGN(header.len) + sizeof(rec_hdr_t);
|
}
|
}
|
|
static void _defragment(void)
|
{
|
/* copy valid records to the defragmentation area */
|
__copy_valid_records(NVM_DATA_OFFSET, NVM_DEFRAGMENT_START_ADDR, NVM_DEFRAGMENT_START_ADDR);
|
|
/* erase the fist sector of the NVM */
|
pal_flash_erase_sectors(1, NVM_DATA_OFFSET);
|
|
/* copy valid records back to the fisrt sector of the NVM */
|
__copy_valid_records(NVM_DEFRAGMENT_START_ADDR, NVM_DATA_OFFSET, PAL_NVM_SIZE);
|
|
/* erase all sectors of the NVM except the first one */
|
pal_flash_erase_sectors(PAL_NVM_SIZE / FLASH_SECTOR_SIZE - 1, NVM_DATA_OFFSET + FLASH_SECTOR_SIZE);
|
}
|
|
bool mk_nvm_write(uint16_t id, const uint8_t *pData, uint16_t len)
|
{
|
rec_hdr_t header;
|
uint16_t dataCrc;
|
uint32_t storageAddr = NVM_DATA_OFFSET;
|
|
ASSERT((id != NVM_OBSOLETED_REC_TYPE) && (id != NVM_UNUSED_REC_TYPE), "Invalid ID: %d", id);
|
|
uint16_t ret = _seek_record(&header, &storageAddr, id);
|
if (ret == NVM_REC_HITS)
|
{
|
dataCrc = crc16(pData, len);
|
if (dataCrc == header.dataCrc)
|
{
|
return true;
|
}
|
else
|
{
|
_obsolete_record(&header, storageAddr);
|
}
|
}
|
|
ret = _seek_record(&header, &storageAddr, NVM_UNUSED_REC_TYPE);
|
if (storageAddr + NVM_DEFRAGMENT_SIZE + NVM_WORD_ALIGN(len) >= PAL_NVM_SIZE)
|
{
|
_defragment();
|
storageAddr = NVM_DATA_OFFSET;
|
ret = _seek_record(&header, &storageAddr, NVM_UNUSED_REC_TYPE);
|
}
|
|
if (ret == NVM_REC_FREE)
|
{
|
header.id = id;
|
header.len = len;
|
header.headerCrc = crc16((uint8_t *)&header, sizeof(header.id) + sizeof(header.len));
|
header.dataCrc = crc16(pData, len);
|
|
pal_flash_write((const uint8_t *)&header, sizeof(rec_hdr_t), storageAddr);
|
pal_flash_write(pData, len, storageAddr + sizeof(rec_hdr_t));
|
return true;
|
}
|
|
return false;
|
}
|
|
bool mk_nvm_erase(uint16_t id)
|
{
|
rec_hdr_t header;
|
uint32_t storageAddr = NVM_DATA_OFFSET;
|
|
ASSERT((id != NVM_OBSOLETED_REC_TYPE) && (id != NVM_UNUSED_REC_TYPE), "Invalid ID: %d", id);
|
|
uint16_t ret = _seek_record(&header, &storageAddr, id);
|
if (ret == NVM_REC_HITS)
|
{
|
_obsolete_record(&header, storageAddr);
|
return true;
|
}
|
|
return false;
|
}
|
|
void mk_nvm_erase_all(void)
|
{
|
pal_flash_erase_sectors(PAL_NVM_SIZE / FLASH_SECTOR_SIZE, NVM_DATA_OFFSET);
|
}
|