blob: 5004ca8d62927c25d7c151a52a3c7e3b889b5374 [file] [log] [blame]
/****************************************************************************
* arch/arm/src/samd5e5/sam_progmem.c
*
* 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <string.h>
#include <semaphore.h>
#include <sys/param.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>
#include <arch/samd5e5/chip.h>
#include "arm_internal.h"
#include "hardware/sam_memorymap.h"
#include "hardware/sam_nvmctrl.h"
#include "sam_progmem.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
#ifndef CONFIG_SAMD5E5_PROGMEM_NSECTORS
# error CONFIG_SAMD5E5_PROGMEM_NSECTORS is not defined
#endif
#define USE_WRITE_WQW
#define USE_UNLOCK
#define USE_LOCK
/* Chip dependencies */
#if defined(CONFIG_ARCH_CHIP_SAMD5X)
/* The page size is 512 bytes. However, the smallest thing that can be
* erased is 16 pages. We will refer to this as a "cluster".
*/
# define SAMD5E5_SECTOR_SHIFT (15) /* 2**15 = 32KB SAMD51J20 */
# define SAMD5E5_PAGE_SHIFT (9) /* 2**9 = 512B */
# define SAMD5E5_CLUSTER_SHIFT (13) /* 2**13 = 8*KB = 16 pages */
# define SAMD5E5_LOCK_REGION_SHIFT (15) /* 2**15 = 32KB SAMD51J20 */
#else
# error FLASH geometry for this SAMD5E5 chip not known
#endif
/* Sizes and masks */
#define SAMD5E5_SECTOR_SIZE (1 << SAMD5E5_SECTOR_SHIFT)
#define SAMD5E5_SECTOR_MASK (SAMD5E5_SECTOR_SIZE - 1)
#define SAMD5E5_PAGE_SIZE (1 << SAMD5E5_PAGE_SHIFT)
#define SAMD5E5_PAGE_WORDS (1 << (SAMD5E5_PAGE_SHIFT - 2))
#define SAMD5E5_PAGE_MASK (SAMD5E5_PAGE_SIZE - 1)
#define SAMD5E5_CLUSTER_SIZE (1 << SAMD5E5_CLUSTER_SHIFT)
#define SAMD5E5_CLUSTER_MASK (SAMD5E5_CLUSTER_SHIFT - 1)
/* Relationships */
#define SAMD5E5_PAGE2SEC_SHIFT (SAMD5E5_SECTOR_SHIFT - SAMD5E5_PAGE_SHIFT)
#define SAMD5E5_PAGE_PER_SEC (1 << SAMD5E5_PAGE2SEC_SHIFT)
#define SAMD5E5_PAGE2CLUST_SHIFT (SAMD5E5_CLUSTER_SHIFT - SAMD5E5_PAGE_SHIFT)
#define SAMD5E5_PAGE_PER_CLUSTER (1 << SAMD5E5_PAGE2CLUST_SHIFT)
#define SAMD5E5_CLUST2SECT_SHIFT (SAMD5E5_SECTOR_SHIFT - SAMD5E5_CLUSTER_SHIFT)
#define SAMD5E5_CLUSTER_PER_SEC (1 << SAMD5E5_CLUST2SECT_SHIFT)
/* Conversions */
#define SAMD5E5_BYTE2PAGE(o) ((o) >> SAMD5E5_PAGE_SHIFT)
#define SAMD5E5_BYTE2CLUST(o) ((o) >> SAMD5E5_CLUSTER_SHIFT)
#define SAMD5E5_BYTE2SECT(o) ((o) >> SAMD5E5_SECTOR_SHIFT)
#define SAMD5E5_PAGE2BYTE(p) ((p) << SAMD5E5_PAGE_SHIFT)
#define SAMD5E5_PAGE2CLUST(p) ((p) >> SAMD5E5_PAGE2CLUST_SHIFT)
#define SAMD5E5_PAGE2SEC(p) ((p) >> SAMD5E5_PAGE2SEC_SHIFT)
#define SAMD5E5_CLUST2BYTE(c) ((c) << SAMD5E5_CLUSTER_SHIFT)
#define SAMD5E5_CLUST2PAGE(c) ((c) << SAMD5E5_PAGE2CLUST_SHIFT)
#define SAMD5E5_CLUST2SEC(c) ((c) >> SAMD5E5_CLUST2SECT_SHIFT)
#define SAMD5E5_SEC2BYTE(s) ((s) << SAMD5E5_SECTOR_SHIFT)
#define SAMD5E5_SEC2PAGE(s) ((s) << SAMD5E5_PAGE2SEC_SHIFT)
#define SAMD5E5_SEC2CLUST(s) ((s) << SAMD5E5_CLUST2SECT_SHIFT)
/* Lock region */
#define SAMD5E5_LOCK_REGION_SIZE (1 << SAMD5E5_LOCK_REGION_SHIFT)
#define SAMD5E5_LOCK_REGION_MASK (SAMD5E5_LOCK_REGION_SIZE - 1)
/* Total FLASH sizes */
#define SAMD5E5_TOTAL_NSECTORS (SAMD5E5_FLASH_SIZE >> SAMD5E5_SECTOR_SHIFT)
#define SAMD5E5_TOTAL_NPAGES SAMD5E5_SEC2PAGE(SAMD5E5_TOTAL_NSECTORS)
#define SAMD5E5_TOTAL_NCLUSTERS SAMD5E5_SEC2CLUST(SAMD5E5_TOTAL_NSECTORS)
/* Start and size of the programmable region */
#define SAMD5E5_PROGMEM_NBYTES (CONFIG_SAMD5E5_PROGMEM_NSECTORS << SAMD5E5_SECTOR_SHIFT)
#define SAMD5E5_PROGMEM_END (SAM_FLASH_BASE + SAMD5E5_FLASH_SIZE)
#define SAMD5E5_PROGMEM_START (SAMD5E5_PROGMEM_END - SAMD5E5_PROGMEM_NBYTES)
#define SAMD5E5_PROGMEM_NPAGES SAMD5E5_SEC2PAGE(CONFIG_SAMD5E5_PROGMEM_NSECTORS)
#define SAMD5E5_PROGMEM_ENDPAGE (SAMD5E5_TOTAL_NPAGES)
#define SAMD5E5_PROGMEM_STARTPAGE (SAMD5E5_PROGMEM_ENDPAGE - SAMD5E5_PROGMEM_NPAGES)
#define SAMD5E5_PROGMEM_NCLUSTERS SAMD5E5_SEC2CLUST(CONFIG_SAMD5E5_PROGMEM_NSECTORS)
#define SAMD5E5_PROGMEM_ENDCLUST (SAMD5E5_TOTAL_NCLUSTERS)
#define SAMD5E5_PROGMEM_STARTCLUST (SAMD5E5_PROGMEM_ENDCLUST - SAMD5E5_PROGMEM_NCLUSTERS)
#define SAMD5E5_PROGMEM_NSECTORS (CONFIG_SAMD5E5_PROGMEM_NSECTORS)
#define SAMD5E5_PROGMEM_ENDSEC (SAMD5E5_TOTAL_NSECTORS)
#define SAMD5E5_PROGMEM_STARTSEC (SAMD5E5_PROGMEM_ENDSEC - CONFIG_SAMD5E5_PROGMEM_NSECTORS)
#define SAMD5E5_PROGMEM_ERASEDVAL (0xffu)
/****************************************************************************
* Private Functions
****************************************************************************/
static uint32_t g_page_buffer[SAMD5E5_PAGE_WORDS];
static mutex_t g_page_lock = NXMUTEX_INITIALIZER;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: nvm_command
*
* Description:
* Send a FLASH command
*
* Input Parameters:
* cmd - The FLASH command to be sent
* arg - The argument to accompany the command
*
* Returned Value:
* Zero is returned on success; a negated errno value is returned on any
* failure.
*
****************************************************************************/
static int nvm_command(uint16_t cmd, uint32_t arg)
{
uint16_t regval;
while (!(getreg16(SAM_NVMCTRL_STATUS) & NVMCTRL_STATUS_READY))
{
/* Wait until this module isn't busy */
}
/* Check for errors */
regval = getreg16(SAM_NVMCTRL_INTFLAG);
if ((regval & (NVMCTRL_INT_LOCKE |
NVMCTRL_INT_PROGE |
NVMCTRL_INT_ADDRE)) != 0)
{
ferr("ERROR: cmd=0x%x regval=0x%x\n", cmd, regval);
return -EIO;
}
putreg16(NVMCTRL_INT_DONE, SAM_NVMCTRL_INTFLAG);
/* Set address */
if (arg)
putreg32(arg, SAM_NVMCTRL_ADDR);
/* Write the command to the flash command register */
putreg16(cmd | NVMCTRL_CTRLB_CMDEX_KEY, SAM_NVMCTRL_CTRLB);
while (!(getreg16(SAM_NVMCTRL_STATUS) & NVMCTRL_STATUS_READY))
{
/* Wait until this module isn't busy */
}
return OK;
}
#ifdef USE_UNLOCK
/****************************************************************************
* Name: nvm_unlock
*
* Description:
* Make sure that the FLASH is unlocked
*
* Input Parameters:
* page - The first page to unlock
* npages - The number of consecutive pages to unlock
*
* Returned Value:
* Zero on success; a negated errno value on failure.
*
****************************************************************************/
static int nvm_unlock(size_t page, size_t npages)
{
size_t start_region;
size_t end_region;
size_t unlockregion;
int ret;
/* Align the page to the unlock region */
end_region = SAMD5E5_PAGE2BYTE(page + npages) &
~SAMD5E5_LOCK_REGION_MASK;
start_region = SAMD5E5_PAGE2BYTE(page) &
~SAMD5E5_LOCK_REGION_MASK;
unlockregion = start_region;
do
{
finfo("INFO: unlock region=%d address=0x%x\n",
unlockregion >> SAMD5E5_LOCK_REGION_SHIFT, unlockregion);
ret = nvm_command(NVMCTRL_CTRLB_CMD_UR, unlockregion);
if (ret < 0)
{
return ret;
}
unlockregion += SAMD5E5_LOCK_REGION_SIZE;
}
while (unlockregion < end_region);
return OK;
}
#endif
#ifdef USE_LOCK
/****************************************************************************
* Name: nvm_lock
*
* Description:
* Make sure that the FLASH is locked
*
* Input Parameters:
* page - The first page to lock
* npages - The number of consecutive pages to lock
*
* Returned Value:
* Zero on success; a negated errno value on failure.
*
****************************************************************************/
static int nvm_lock(size_t page, size_t npages)
{
size_t start_region;
size_t end_region;
size_t lockregion;
int ret;
/* Align the page to the unlock region */
end_region = SAMD5E5_PAGE2BYTE(page + npages) &
~SAMD5E5_LOCK_REGION_MASK;
start_region = SAMD5E5_PAGE2BYTE(page) &
~SAMD5E5_LOCK_REGION_MASK;
lockregion = start_region;
do
{
finfo("INFO: lock region=%d address=0x%x\n",
lockregion >> SAMD5E5_LOCK_REGION_SHIFT, lockregion);
ret = nvm_command(NVMCTRL_CTRLB_CMD_LR, lockregion);
if (ret < 0)
{
return ret;
}
lockregion += SAMD5E5_LOCK_REGION_SIZE;
}
while (lockregion < end_region);
return OK;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sam_progmem_initialize
*
* Description:
* Call to initialize FLASH programming memory access
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
void sam_progmem_initialize(void)
{
uint16_t ctrla;
ctrla = getreg16(SAM_NVMCTRL_CTRLA);
ctrla &= ~(NVMCTRL_CTRLA_AHBNS1 |
NVMCTRL_CTRLA_AHBNS0 |
NVMCTRL_CTRLA_PRM_MASK |
NVMCTRL_CTRLA_WMODE_MASK |
NVMCTRL_CTRLA_SUSPEN);
ctrla |= NVMCTRL_CTRLA_CACHEDIS1 |
NVMCTRL_CTRLA_CACHEDIS0 |
NVMCTRL_CTRLA_PRM_MANUAL |
NVMCTRL_CTRLA_WMODE_MAN |
NVMCTRL_CTRLA_AUTOWS;
putreg16(ctrla, SAM_NVMCTRL_CTRLA);
}
/****************************************************************************
* Name: up_progmem_neraseblocks
*
* Description:
* Return number of clusters in the available FLASH memory.
*
****************************************************************************/
size_t up_progmem_neraseblocks(void)
{
return SAMD5E5_PROGMEM_NCLUSTERS;
}
/****************************************************************************
* Name: up_progmem_isuniform
*
* Description:
* Cluster size is uniform? Say 'yes' even though that is not strictly
* true do to the odd organization of sector 0.
*
****************************************************************************/
bool up_progmem_isuniform(void)
{
return true;
}
/****************************************************************************
* Name: up_progmem_pagesize
*
* Description:
* Return page size
*
****************************************************************************/
size_t up_progmem_pagesize(size_t cluster)
{
return SAMD5E5_PAGE_SIZE;
}
/****************************************************************************
* Name: up_progmem_erasesize
*
* Description:
* Return cluster size
*
****************************************************************************/
size_t up_progmem_erasesize(size_t cluster)
{
return SAMD5E5_CLUSTER_SIZE;
}
/****************************************************************************
* Name: up_progmem_getpage
*
* Description:
* Address to page conversion
*
* Input Parameters:
* address - Address with or without flash offset
*
* Returned Value:
* Page or negative value on error. The following errors are reported
* (errno is not set!):
*
* -EFAULT: On invalid address
*
****************************************************************************/
ssize_t up_progmem_getpage(size_t address)
{
if (address >= SAMD5E5_PROGMEM_START)
{
address -= SAMD5E5_PROGMEM_START;
}
if (address >= SAMD5E5_PROGMEM_NBYTES)
{
return -EFAULT;
}
return address >> SAMD5E5_PAGE_SHIFT;
}
/****************************************************************************
* Name: up_progmem_getaddress
*
* Description:
* Cluster to address conversion
*
* Input Parameters:
* page - page index
*
* Returned Value:
* Base address of given page, maximum size if page index is not valid.
*
****************************************************************************/
size_t up_progmem_getaddress(size_t page)
{
if (page >= SAMD5E5_PROGMEM_NPAGES)
{
return SAMD5E5_PROGMEM_NBYTES;
}
return (page << SAMD5E5_PAGE_SHIFT) + SAMD5E5_PROGMEM_START;
}
/****************************************************************************
* Name: up_progmem_eraseblock
*
* Description:
* Erase selected cluster.
*
* Input Parameters:
* cluster - cluster index to be erased
*
* Returned Value:
* Page size or negative value on error. The following errors are reported
* (errno is not set!):
*
* -EFAULT: On invalid cluster
* -EIO: On unsuccessful erase
* -EROFS: On access to write protected area
* -EACCES: Insufficient permissions (read/write protected)
* -EPERM: If operation is not permitted due to some other constraints
* (i.e. some internal block is not running etc.)
*
****************************************************************************/
ssize_t up_progmem_eraseblock(size_t cluster)
{
uint32_t page;
int ret;
finfo("INFO: cluster=%d\n", cluster);
if (cluster >= SAMD5E5_PROGMEM_NCLUSTERS)
{
return -EFAULT;
}
/* Get the page number of the start of the cluster */
page = SAMD5E5_CLUST2PAGE((uint32_t)cluster) + SAMD5E5_PROGMEM_STARTPAGE;
/* Erase all pages in the cluster */
#ifdef USE_UNLOCK
nvm_unlock(page, SAMD5E5_PAGE_PER_CLUSTER);
#endif
finfo("INFO: erase block=%d address=0x%x\n",
page, SAMD5E5_PAGE2BYTE(page));
ret = nvm_command(NVMCTRL_CTRLB_CMD_EB, SAMD5E5_PAGE2BYTE(page));
#ifdef USE_LOCK
nvm_lock(page, SAMD5E5_PAGE_PER_CLUSTER);
#endif
if (ret < 0)
{
return ret;
}
/* Verify that the cluster of pages is really erased */
if (up_progmem_ispageerased(cluster) == 0)
{
return SAMD5E5_CLUSTER_SIZE; /* Success */
}
else
{
return -EIO; /* Failure */
}
}
/****************************************************************************
* Name: up_progmem_ispageerased
*
* Description:
* Checks whether cluster is erased
*
* Input Parameters:
* cluster - cluster to be checked
*
* Returned Value:
* Returns number of bytes erased or negative value on error. If it
* returns zero then complete cluster is empty (erased).
*
* The following errors are reported (errno is not set!)
* -EFAULT: On invalid cluster
*
****************************************************************************/
ssize_t up_progmem_ispageerased(size_t cluster)
{
size_t address;
size_t nwritten;
int nleft;
finfo("INFO: cluster=%d\n", cluster);
if (cluster >= SAMD5E5_PROGMEM_NCLUSTERS)
{
return -EFAULT;
}
/* Flush and invalidate D-Cache for this address range */
address = (cluster << SAMD5E5_CLUSTER_SHIFT) + SAMD5E5_PROGMEM_START;
up_flush_dcache(address, address + SAMD5E5_CLUSTER_SIZE);
/* Verify that the cluster is erased (i.e., all 0xff) */
for (nleft = SAMD5E5_CLUSTER_SIZE, nwritten = 0;
nleft > 0;
nleft--, address++)
{
if (getreg8(address) != SAMD5E5_PROGMEM_ERASEDVAL)
{
nwritten++;
}
}
if (nwritten)
fwarn("WARN: non written=%d\n", nwritten);
return nwritten;
}
/****************************************************************************
* Name: up_progmem_write
*
* Description:
* Program data at given address
*
* Input Parameters:
* address - Address with or without flash offset
* buffer - Pointer to buffer
* buflen - Number of bytes to write
*
* Returned Value:
* Bytes written or negative value on error. The following errors are
* reported (errno is not set!)
*
* EINVAL: If buflen is not aligned with the flash boundaries (i.e.
* some MCU's require per half-word or even word access)
* EFAULT: On invalid address
* EIO: On unsuccessful write
* EROFS: On access to write protected area
* EACCES: Insufficient permissions (read/write protected)
* EPERM: If operation is not permitted due to some other constraints
* (i.e. some internal block is not running etc.)
*
****************************************************************************/
ssize_t up_progmem_write(size_t address, const void *buffer, size_t buflen)
{
irqstate_t flags;
uint32_t *dest;
const uint32_t *src;
size_t written;
size_t xfrsize;
size_t offset;
size_t page;
size_t i;
int ret;
#ifdef USE_UNLOCK
size_t lock;
size_t locksize;
#endif
finfo("INFO: address=0x%x buflen=%d\n", address, buflen);
/* Convert the address into a FLASH byte offset, if necessary */
offset = address;
if (address >= SAMD5E5_PROGMEM_START)
{
/* Convert address to an offset relative to be beginning of the
* writable FLASH region.
*/
offset -= SAMD5E5_PROGMEM_START;
}
/* Check for valid address range */
if ((offset + buflen) > SAMD5E5_PROGMEM_NBYTES)
{
return -EFAULT;
}
/* Get exclusive access to the global page buffer */
nxmutex_lock(&g_page_lock);
/* Get the page number corresponding to the flash offset and the byte
* offset into the page.
*/
page = SAMD5E5_BYTE2PAGE((uint32_t)offset) + SAMD5E5_PROGMEM_STARTPAGE;
offset &= SAMD5E5_PAGE_MASK;
#ifdef USE_UNLOCK /* Make sure that the FLASH is unlocked */
lock = page;
locksize = SAMD5E5_BYTE2PAGE(buflen);
nvm_unlock(lock, locksize);
#endif
flags = enter_critical_section();
/* Loop until all of the data has been written */
dest = (uint32_t *)(address & ~SAMD5E5_PAGE_MASK);
written = 0;
while (buflen > 0)
{
/* How much can we write into this page? */
xfrsize = MIN((size_t)SAMD5E5_PAGE_SIZE - offset, buflen);
/* Do we need to use the intermediate buffer? */
if (offset == 0 && xfrsize == SAMD5E5_PAGE_SIZE)
{
/* No, we can take the data directly from the user buffer */
src = (const uint32_t *)buffer;
}
else
{
/* Yes, copy data into global page buffer */
if (offset > 0)
{
memcpy((uint8_t *)g_page_buffer, (uint8_t *)dest, offset);
}
memcpy((uint8_t *)g_page_buffer + offset,
(uint8_t *)buffer, xfrsize);
if (offset + xfrsize < SAMD5E5_PAGE_SIZE)
{
memcpy((uint8_t *)g_page_buffer + offset + xfrsize,
(const uint8_t *)dest + offset + xfrsize,
SAMD5E5_PAGE_SIZE - offset - xfrsize);
}
src = g_page_buffer;
}
#ifdef USE_WRITE_WQW
if (xfrsize <= (0x10 - (offset & 0xf)))
{
/* Write the page buffer */
dest += (offset & ~0xf) >> 2;
src += (offset & ~0xf) >> 2;
/* Dump flash data */
for (i = 0; i < 4; i++)
{
finfo("INFO: dest=%p write 0x%x over 0x%x\n",
dest + i, *(src + i), *(dest + i));
}
nvm_command(NVMCTRL_CTRLB_CMD_PBC, 0);
/* Write the page buffer */
for (i = 0; i < 4; i++)
{
*dest++ = *src++;
}
/* Flush the data cache to memory */
up_clean_dcache(address & ~SAMD5E5_PAGE_MASK,
(address & ~SAMD5E5_PAGE_MASK) + SAMD5E5_PAGE_SIZE);
/* Send the 4 words write command */
ret = nvm_command(NVMCTRL_CTRLB_CMD_WQW, 0);
if (ret >= 0)
{
written += xfrsize;
}
dest -= i;
src -= i;
/* Compare page data */
for (i = 0; i < 4; i++)
{
if (*dest != *src)
{
fwarn("WQW dest=%p (dest 0x%x != src 0x%x) address=0x%x",
dest, *dest, *src, address);
fwarn("offset=0x%x xfrsize=%d buflen=%d ECCERR=0x%x\n",
offset, xfrsize, buflen, getreg32(SAM_NVMCTRL_ECCERR));
}
dest++;
src++;
}
}
else
{
#endif
nvm_command(NVMCTRL_CTRLB_CMD_PBC, 0);
/* Write the page buffer */
for (i = 0; i < SAMD5E5_PAGE_WORDS; i++)
{
*dest++ = *src++;
}
/* Flush the data cache to memory */
up_clean_dcache(address & ~SAMD5E5_PAGE_MASK,
(address & ~SAMD5E5_PAGE_MASK) + SAMD5E5_PAGE_SIZE);
/* Send the write command */
finfo("INFO: WP address=0x%x\n", address & ~SAMD5E5_PAGE_MASK);
ret = nvm_command(NVMCTRL_CTRLB_CMD_WP, 0);
if (ret >= 0)
{
written += xfrsize;
}
dest -= i;
src -= i;
/* Compare page data */
for (i = 0; i < SAMD5E5_PAGE_WORDS; i++)
{
if (*dest != *src)
{
fwarn("WQW dest=%p (dest 0x%x != src 0x%x) address=0x%x",
dest, *dest, *src, address);
fwarn("offset=0x%x xfrsize=%d buflen=%d ECCERR=0x%x\n",
offset, xfrsize, buflen, getreg32(SAM_NVMCTRL_ECCERR));
}
dest++;
src++;
}
#ifdef USE_WRITE_WQW
}
#endif
/* Adjust pointers and counts for the next time through the loop */
address += xfrsize;
dest = (uint32_t *)address;
buffer = (void *)((uintptr_t)buffer + xfrsize);
buflen -= xfrsize;
offset = 0;
page++;
}
#ifdef USE_LOCK
nvm_lock(lock, locksize);
#endif
leave_critical_section(flags);
nxmutex_unlock(&g_page_lock);
return written;
}
/****************************************************************************
* Name: up_progmem_erasestate
*
* Description:
* Return value of erase state.
*
****************************************************************************/
uint8_t up_progmem_erasestate(void)
{
return SAMD5E5_PROGMEM_ERASEDVAL;
}
/****************************************************************************
* The NVM User Row contains calibration data that are
* automatically read at device power on.
* The NVM User Row can be read at address 0x804000.
****************************************************************************/
#ifndef _NVM_USER_ROW_BASE
#define _NVM_USER_ROW_BASE 0x804000
#endif
#define _NVM_USER_ROW_N_BITS 96
#define _NVM_USER_ROW_N_BYTES (_NVM_USER_ROW_N_BITS / 8)
#define _NVM_USER_ROW_END (((uint8_t *)_NVM_USER_ROW_BASE) + _NVM_USER_ROW_N_BYTES - 1)
#define _IS_NVM_USER_ROW(b) \
(((uint8_t *)(b) >= (uint8_t *)(_NVM_USER_ROW_BASE)) && ((uint8_t *)(b) <= (uint8_t *)(_NVM_USER_ROW_END)))
#define _IN_NVM_USER_ROW(b, o) (((uint8_t *)(b) + (o)) <= (uint8_t *)(_NVM_USER_ROW_END))
#define _NVM_USER_PAGE_SIZE 512
#define _NVM_USER_PAGE_OFFSET 32
#define _IS_NVM_USER_PAGE(b) \
(((uint8_t *)(b) >= (uint8_t *)(_NVM_USER_ROW_BASE)) && ((uint8_t *)(b) <= (((uint8_t *)_NVM_USER_ROW_BASE) + _NVM_USER_PAGE_SIZE - 1)))
/****************************************************************************
* The NVM Software Calibration Area can be read at address 0x00800080.
* The NVM Software Calibration Area can not be written.
****************************************************************************/
#ifndef _NVM_SW_CALIB_AREA_BASE
#define _NVM_SW_CALIB_AREA_BASE 0x00800080
#endif
#define _NVM_SW_CALIB_AREA_N_BITS 45
#define _NVM_SW_CALIB_AREA_N_BYTES (_NVM_SW_CALIB_AREA_N_BITS / 8)
#define _NVM_SW_CALIB_AREA_END (((uint8_t *)_NVM_SW_CALIB_AREA_BASE) + _NVM_SW_CALIB_AREA_N_BYTES - 1)
#define _IS_NVM_SW_CALIB_AREA(b) \
(((uint8_t *)(b) >= (uint8_t *)_NVM_SW_CALIB_AREA_BASE) && ((uint8_t *)(b) <= (uint8_t *)_NVM_SW_CALIB_AREA_END))
#define _IN_NVM_SW_CALIB_AREA(b, o) (((uint8_t *)(b) + (o)) <= (uint8_t *)(_NVM_SW_CALIB_AREA_END))
ssize_t up_progmem_writeuserpage(const uint32_t offset,
const uint8_t *buffer,
uint16_t count)
{
size_t i;
size_t written;
uint32_t *dest;
const uint32_t *src;
uint32_t userpage[128]; /* Copy of user page */
ASSERT(buffer);
/* Parameter check. */
if (!_IS_NVM_USER_PAGE(_NVM_USER_ROW_BASE + offset))
{
return -EFAULT;
}
/* Cut off if request too many bytes */
if (!_IS_NVM_USER_PAGE(_NVM_USER_ROW_BASE + offset + count - 1))
{
return -EFAULT;
}
/* Store previous data. */
memcpy((uint8_t *)userpage,
((uint8_t *)_NVM_USER_ROW_BASE),
_NVM_USER_PAGE_SIZE);
/* Modify with buffer data. */
memcpy((uint8_t *)userpage + offset, buffer, count);
/* Erase AUX page. */
nvm_command(NVMCTRL_CTRLB_CMD_EP, _NVM_USER_ROW_BASE);
dest = (uint32_t *)(_NVM_USER_ROW_BASE);
src = (const uint32_t *)userpage;
for (written = 0; written <
_NVM_USER_PAGE_SIZE; written += 4*sizeof(uint32_t))
{
/* Page buffer clear & write. */
nvm_command(NVMCTRL_CTRLB_CMD_PBC, 0);
/* Write the page buffer */
for (i = 0; i < 4; i++)
{
*dest++ = *src++;
}
/* Send the 4 words write command */
nvm_command(NVMCTRL_CTRLB_CMD_WQW, 0);
dest -= i;
src -= i;
/* Compare page data */
for (i = 0; i < 4; i++)
{
if (*dest != *src)
{
fwarn("WQW dest=%p (dest 0x%x != src 0x%x) ECCERR=0x%x\n",
dest, *dest, *src, getreg32(SAM_NVMCTRL_ECCERR));
}
dest++;
src++;
}
}
return OK;
}
ssize_t up_progmem_readuserpage(const uint32_t offset,
uint8_t *buffer,
uint16_t count)
{
ASSERT(buffer);
/* Parameter check. */
if (!_IS_NVM_USER_PAGE(_NVM_USER_ROW_BASE + offset))
{
return -EFAULT;
}
/* Cut off if request too many bytes */
if (!_IS_NVM_USER_PAGE(_NVM_USER_ROW_BASE + offset + count - 1))
{
return -EFAULT;
}
/* Copy data */
memcpy(buffer, ((uint8_t *)_NVM_USER_ROW_BASE) + offset, count);
return OK;
}