blob: 75d9b0bc0f7af887296fdfb90444146e2201d76b [file] [log] [blame]
/****************************************************************************
* arch/arm/src/samv7/sam_progmem.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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <string.h>
#include <sys/param.h>
#include <errno.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>
#include <arch/barriers.h>
#include <arch/samv7/chip.h>
#include "arm_internal.h"
#include "hardware/sam_memorymap.h"
#include "sam_eefc.h"
#include "sam_progmem.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
#ifndef CONFIG_SAMV7_PROGMEM_NSECTORS
# error CONFIG_SAMV7_PROGMEM_NSECTORS is not defined
#endif
#ifndef CONFIG_ARCH_RAMFUNCS
# error "Flashing function should executed in ram"
#endif
/* Chip dependencies */
#if defined(CONFIG_ARCH_CHIP_SAMV71) || defined(CONFIG_ARCH_CHIP_SAME70) || \
defined(CONFIG_ARCH_CHIP_PIC32CZCA70)
/* All sectors are 128KB and are uniform in size.
* The only exception is sector 0 which is subdivided into two small sectors
* of 8KB and one larger sector of 112KB.
* The page size is 512 bytes. However, the smallest thing that can be
* erased is four pages. We will refer to this as a "cluster".
*/
# define SAMV7_SECTOR_SHIFT (17) /* 2**17 = 128KB */
# define SAMV7_PAGE_SHIFT (9) /* 2**9 = 512B */
# define SAMV7_CLUSTER_SHIFT (13) /* 2**13 = 8*KB = 16 pages */
# define SAMV7_LOCK_REGION_SHIFT (13) /* 2**13 = 8*KB = 16 pages */
#else
# error FLASH geometry for this SAMV7 chip not known
#endif
/* Sizes and masks */
#define SAMV7_SECTOR_SIZE (1 << SAMV7_SECTOR_SHIFT)
#define SAMV7_SECTOR_MASK (SAMV7_SECTOR_SIZE - 1)
#define SAMV7_PAGE_SIZE (1 << SAMV7_PAGE_SHIFT)
#define SAMV7_PAGE_WORDS (1 << (SAMV7_PAGE_SHIFT - 2))
#define SAMV7_PAGE_MASK (SAMV7_PAGE_SIZE - 1)
#define SAMV7_CLUSTER_SIZE (1 << SAMV7_CLUSTER_SHIFT)
#define SAMV7_CLUSTER_MASK (SAMV7_CLUSTER_SHIFT - 1)
/* Relationships */
#define SAMV7_PAGE2SEC_SHIFT (SAMV7_SECTOR_SHIFT - SAMV7_PAGE_SHIFT)
#define SAMV7_PAGE_PER_SEC (1 << SAMV7_PAGE2SEC_SHIFT)
#define SAMV7_PAGE2CLUST_SHIFT (SAMV7_CLUSTER_SHIFT - SAMV7_PAGE_SHIFT)
#define SAMV7_PAGE_PER_CLUSTER (1 << SAMV7_PAGE2CLUST_SHIFT)
#define SAMV7_CLUST2SECT_SHIFT (SAMV7_SECTOR_SHIFT - SAMV7_CLUSTER_SHIFT)
#define SAMV7_CLUSTER_PER_SEC (1 << SAMV7_CLUST2SECT_SHIFT)
/* Conversions */
#define SAMV7_BYTE2PAGE(o) ((o) >> SAMV7_PAGE_SHIFT)
#define SAMV7_BYTE2CLUST(o) ((o) >> SAMV7_CLUSTER_SHIFT)
#define SAMV7_BYTE2SECT(o) ((o) >> SAMV7_SECTOR_SHIFT)
#define SAMV7_PAGE2BYTE(p) ((p) << SAMV7_PAGE_SHIFT)
#define SAMV7_PAGE2CLUST(p) ((p) >> SAMV7_PAGE2CLUST_SHIFT)
#define SAMV7_PAGE2SEC(p) ((p) >> SAMV7_PAGE2SEC_SHIFT)
#define SAMV7_CLUST2BYTE(c) ((c) << SAMV7_CLUSTER_SHIFT)
#define SAMV7_CLUST2PAGE(c) ((c) << SAMV7_PAGE2CLUST_SHIFT)
#define SAMV7_CLUST2SEC(c) ((c) >> SAMV7_CLUST2SECT_SHIFT)
#define SAMV7_SEC2BYTE(s) ((s) << SAMV7_SECTOR_SHIFT)
#define SAMV7_SEC2PAGE(s) ((s) << SAMV7_PAGE2SEC_SHIFT)
#define SAMV7_SEC2CLUST(s) ((s) << SAMV7_CLUST2SECT_SHIFT)
/* Total FLASH sizes */
#define SAMV7_TOTAL_NSECTORS (SAMV7_FLASH_SIZE >> SAMV7_SECTOR_SHIFT)
#define SAMV7_TOTAL_NPAGES SAMV7_SEC2PAGE(SAMV7_TOTAL_NSECTORS)
#define SAMV7_TOTAL_NCLUSTERS SAMV7_SEC2CLUST(SAMV7_TOTAL_NSECTORS)
/* Start and size of the programmable region */
#define SAMV7_PROGMEM_NBYTES (CONFIG_SAMV7_PROGMEM_NSECTORS << SAMV7_SECTOR_SHIFT)
#define SAMV7_PROGMEM_END (SAM_INTFLASH_BASE + SAMV7_FLASH_SIZE)
#define SAMV7_PROGMEM_START (SAMV7_PROGMEM_END - SAMV7_PROGMEM_NBYTES)
#define SAMV7_PROGMEM_NPAGES SAMV7_SEC2PAGE(CONFIG_SAMV7_PROGMEM_NSECTORS)
#define SAMV7_PROGMEM_ENDPAGE (SAMV7_TOTAL_NPAGES)
#define SAMV7_PROGMEM_STARTPAGE (SAMV7_PROGMEM_ENDPAGE - SAMV7_PROGMEM_NPAGES)
#define SAMV7_PROGMEM_NCLUSTERS SAMV7_SEC2CLUST(CONFIG_SAMV7_PROGMEM_NSECTORS)
#define SAMV7_PROGMEM_ENDCLUST (SAMV7_TOTAL_NCLUSTERS)
#define SAMV7_PROGMEM_STARTCLUST (SAMV7_PROGMEM_ENDCLUST - SAMV7_PROGMEM_NCLUSTERS)
#define SAMV7_PROGMEM_NSECTORS (CONFIG_SAMV7_PROGMEM_NSECTORS)
#define SAMV7_PROGMEM_ENDSEC (SAMV7_TOTAL_NSECTORS)
#define SAMV7_PROGMEM_STARTSEC (SAMV7_PROGMEM_ENDSEC - CONFIG_SAMV7_PROGMEM_NSECTORS)
#define SAMV7_PROGMEM_ERASEDVAL (0xffu)
/****************************************************************************
* Private Functions
****************************************************************************/
static uint32_t g_page_buffer[SAMV7_PAGE_WORDS];
static mutex_t g_page_lock = NXMUTEX_INITIALIZER;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* 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)
{
uint32_t regval;
/* Set flash access mode to 128bit and wait status to 4 */
sam_eefc_initaccess(SAM_EFC_ACCESS_MODE_128, 4);
/* Make sure that the read interrupt is disabled */
regval = getreg32(SAM_EEFC_FMR);
regval &= ~EEFC_FMR_FRDY;
sam_eefc_writefmr(regval);
}
/****************************************************************************
* Name: up_progmem_neraseblocks
*
* Description:
* Return number of clusters in the available FLASH memory.
*
****************************************************************************/
size_t up_progmem_neraseblocks(void)
{
return SAMV7_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 cluster size
*
****************************************************************************/
size_t up_progmem_pagesize(size_t cluster)
{
return SAMV7_CLUSTER_SIZE;
}
/****************************************************************************
* Name: up_progmem_erasesize
*
* Description:
* Return cluster size
*
****************************************************************************/
size_t up_progmem_erasesize(size_t cluster)
{
return SAMV7_CLUSTER_SIZE;
}
/****************************************************************************
* Name: up_progmem_getpage
*
* Description:
* Address to cluster 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 >= SAMV7_PROGMEM_START)
{
address -= SAMV7_PROGMEM_START;
}
if (address >= SAMV7_PROGMEM_NBYTES)
{
return -EFAULT;
}
return address >> SAMV7_CLUSTER_SHIFT;
}
/****************************************************************************
* Name: up_progmem_getaddress
*
* Description:
* Cluster to address conversion
*
* Input Parameters:
* cluster - cluster index
*
* Returned Value:
* Base address of given cluster, maximum size if cluster index is not
* valid.
*
****************************************************************************/
size_t up_progmem_getaddress(size_t cluster)
{
if (cluster >= SAMV7_PROGMEM_NCLUSTERS)
{
return SAMV7_PROGMEM_NBYTES;
}
return (cluster << SAMV7_CLUSTER_SHIFT) + SAMV7_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;
uint32_t arg;
int ret;
if (cluster >= SAMV7_PROGMEM_NCLUSTERS)
{
return -EFAULT;
}
/* Get the page number of the start of the cluster */
page = SAMV7_CLUST2PAGE((uint32_t)cluster) + SAMV7_PROGMEM_STARTPAGE;
/* Erase all pages in the cluster */
sam_eefc_unlock(page, SAMV7_PAGE_PER_CLUSTER);
/* Get FARG field for EPA command:
*
* The first page to be erased is specified in the FARG[15:2] field of
* the EEFC_FCR register. The first page number must be modulo 4, 8,16 or
* 32 according to the number of pages to erase at the same time.
*
* The 2 lowest bits of the FARG field define the number of pages to
* be erased (FARG[1:0]).
*/
#if SAMV7_PAGE_PER_CLUSTER == 32
arg = page | 3; /* Not valid for small 8 KB sectors */
#elif SAMV7_PAGE_PER_CLUSTER == 16
arg = page | 2;
#elif SAMV7_PAGE_PER_CLUSTER == 8
# error Cluster size of 8 not supported
arg = page | 1; /* Only valid for small 8 KB sectors */
#elif SAMV7_PAGE_PER_CLUSTER == 4
# error Cluster size of 4 not supported
arg = page | 0; /* Only valid for small 8 KB sectors */
#else
# error Unsupported/undefined pages-per-cluster size
#endif
ret = sam_eefc_command(FCMD_EPA, arg);
if (ret < 0)
{
return ret;
}
/* Verify that the cluster of pages is really erased */
if (up_progmem_ispageerased(cluster) == 0)
{
return SAMV7_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;
if (cluster >= SAMV7_PROGMEM_NCLUSTERS)
{
return -EFAULT;
}
/* Flush and invalidate D-Cache for this address range */
address = (cluster << SAMV7_CLUSTER_SHIFT) + SAMV7_PROGMEM_START;
up_flush_dcache(address, address + SAMV7_CLUSTER_SIZE);
/* Verify that the cluster is erased (i.e., all SAMV7_PROGMEM_ERASEDVAL) */
for (nleft = SAMV7_CLUSTER_SIZE, nwritten = 0;
nleft > 0;
nleft--, address++)
{
if (getreg8(address) != SAMV7_PROGMEM_ERASEDVAL)
{
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)
{
uint32_t *dest;
const uint32_t *src;
size_t written;
size_t xfrsize;
size_t offset;
size_t page;
size_t i;
int ret;
/* Convert the address into a FLASH byte offset, if necessary */
offset = address;
if (address >= SAMV7_PROGMEM_START)
{
/* Convert address to an offset relative to be beginning of the
* writable FLASH region.
*/
offset -= SAMV7_PROGMEM_START;
}
/* Check for valid address range */
if ((offset + buflen) > SAMV7_PROGMEM_NBYTES)
{
return -EFAULT;
}
/* Get the page number corresponding to the flash offset and the byte
* offset into the page.
*/
page = SAMV7_BYTE2PAGE((uint32_t)offset) + SAMV7_PROGMEM_STARTPAGE;
offset &= SAMV7_PAGE_MASK;
/* Get exclusive access to the global page buffer */
ret = nxmutex_lock(&g_page_lock);
if (ret < 0)
{
return (ssize_t)ret;
}
/* Make sure that the FLASH is unlocked */
sam_eefc_unlock(page, SAMV7_BYTE2PAGE(buflen + SAMV7_PAGE_MASK));
/* Loop until all of the data has been written */
dest = (uint32_t *)(address & ~SAMV7_PAGE_MASK);
written = 0;
while (buflen > 0)
{
/* How much can we write into this page? */
xfrsize = MIN((size_t)SAMV7_PAGE_SIZE - offset, buflen);
/* Do we need to use the intermediate buffer? */
if (offset == 0 && xfrsize == SAMV7_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(g_page_buffer, dest, offset);
}
memcpy((uint8_t *)g_page_buffer + offset, buffer, xfrsize);
if (offset + xfrsize < SAMV7_PAGE_SIZE)
{
memcpy((uint8_t *)g_page_buffer + offset + xfrsize,
(const uint8_t *)dest + offset + xfrsize,
SAMV7_PAGE_SIZE - offset - xfrsize);
}
src = g_page_buffer;
}
/* Write the page */
for (i = 0; i < SAMV7_PAGE_WORDS; i++)
{
*dest++ = *src++;
#ifdef CONFIG_ARMV7M_DCACHE_WRITETHROUGH
UP_DMB();
#endif
}
/* Flush the data cache to memory */
up_clean_dcache(address & ~SAMV7_PAGE_MASK,
(address & ~SAMV7_PAGE_MASK) + SAMV7_PAGE_SIZE);
/* Send the write command */
ret = sam_eefc_command(FCMD_WP, page);
if (ret >= 0)
{
written += xfrsize;
}
/* 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++;
}
nxmutex_unlock(&g_page_lock);
return written;
}
/****************************************************************************
* Name: up_progmem_erasestate
*
* Description:
* Return a byte that represents flash erased value state
*
****************************************************************************/
uint8_t up_progmem_erasestate(void)
{
return SAMV7_PROGMEM_ERASEDVAL;
}