Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cpu/sam0_common: flashpage: clean up implementation #14502

Merged
merged 2 commits into from
Aug 10, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
288 changes: 153 additions & 135 deletions cpu/sam0_common/periph/flashpage.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* use in RIOT is actually the row size as specified in the datasheet.
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* @}
*/
Expand All @@ -30,10 +31,10 @@
#include "cpu.h"
#include "periph/flashpage.h"

#define NVMCTRL_PAC_BIT (0x00000002)
#define ENABLE_DEBUG (0)
#include "debug.h"

#define FLASH_MAIN 0
#define FLASH_RWWEE 1
#define MIN(x, y) (((x) < (y)) ? (x) : (y))

/**
* @brief NVMCTRL selection macros
Expand All @@ -44,10 +45,9 @@
#define _NVMCTRL NVMCTRL
#endif

static inline void wait_nvm_is_ready(void) __attribute__((always_inline));
static inline void wait_nvm_is_ready(void)
{
#if defined(CPU_SAML1X) || defined(CPU_SAMD5X)
#ifdef NVMCTRL_STATUS_READY
while (!_NVMCTRL->STATUS.bit.READY) {}
#else
while (!_NVMCTRL->INTFLAG.bit.READY) {}
Expand All @@ -60,189 +60,207 @@ static void _unlock(void)
#ifdef REG_PAC_WRCTRL
PAC->WRCTRL.reg = (PAC_WRCTRL_KEY_CLR | ID_NVMCTRL);
#else
if (PAC1->WPSET.reg & NVMCTRL_PAC_BIT) {
PAC1->WPCLR.reg = NVMCTRL_PAC_BIT;
}
PAC1->WPCLR.reg = PAC1_WPROT_DEFAULT_VAL;
#endif
}

static void _lock(void)
{
wait_nvm_is_ready();

/* put peripheral access lock for the NVMCTRL peripheral */
#ifdef REG_PAC_WRCTRL
PAC->WRCTRL.reg = (PAC_WRCTRL_KEY_SET | ID_NVMCTRL);
#else
if (PAC1->WPCLR.reg & NVMCTRL_PAC_BIT) {
PAC1->WPSET.reg = NVMCTRL_PAC_BIT;
}
PAC1->WPSET.reg = PAC1_WPROT_DEFAULT_VAL;
#endif
}

#ifdef FLASHPAGE_RWWEE_NUMOF
void flashpage_write_raw_internal(void *target_addr, const void *data, size_t len, int flash_type)
#else
void flashpage_write_raw(void *target_addr, const void *data, size_t len)
#endif
static void _cmd_clear_page_buffer(void)
{
/* The actual minimal block size for writing is 16B, thus we
* assert we write on multiples and no less of that length.
*/
assert(!(len % FLASHPAGE_RAW_BLOCKSIZE));

/* ensure 4 byte aligned writes */
assert(!(((unsigned)target_addr % FLASHPAGE_RAW_ALIGNMENT) ||
((unsigned)data % FLASHPAGE_RAW_ALIGNMENT)));

/* ensure the length doesn't exceed the actual flash size */
#ifdef FLASHPAGE_RWWEE_NUMOF
if (flash_type == FLASH_RWWEE) {
assert(((unsigned)target_addr + len) <=
(CPU_FLASH_RWWEE_BASE + (FLASHPAGE_SIZE * FLASHPAGE_RWWEE_NUMOF)));
} else {
#endif
assert(((unsigned)target_addr + len) <=
(CPU_FLASH_BASE + (FLASHPAGE_SIZE * FLASHPAGE_NUMOF)));
#ifdef FLASHPAGE_RWWEE_NUMOF
}
#endif

uint32_t *dst = (uint32_t *)target_addr;
const uint32_t *data_addr = data;

/* write 4 bytes in one go */
len /= 4;
wait_nvm_is_ready();

_unlock();
#ifdef NVMCTRL_CTRLB_CMDEX_KEY
_NVMCTRL->CTRLB.reg = (NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_PBC);
#else
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC);
#endif
}

static void _cmd_erase_row(void)
{
wait_nvm_is_ready();
for (unsigned i = 0; i < len; i++) {
*dst++ = *data_addr++;
}
#ifdef FLASHPAGE_RWWEE_NUMOF
if (flash_type == FLASH_RWWEE) {
#ifdef CPU_SAML1X
/* SAML1X use the same Write Page command for both flash memories */
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP);
#else
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_RWWEEWP);
#endif
} else {
#endif

/* send Row/Block erase command */
#ifdef NVMCTRL_CTRLB_CMDEX_KEY
_NVMCTRL->CTRLB.reg = (NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_WP);
_NVMCTRL->CTRLB.reg = (NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_EB);
#else
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP);
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER);
#endif
#ifdef FLASHPAGE_RWWEE_NUMOF
}
#endif
wait_nvm_is_ready();
_lock();
}

#ifdef FLASHPAGE_RWWEE_NUMOF
void flashpage_write_internal(int page, const void *data, int flash_type)
static void _cmd_write_page(void)
{
wait_nvm_is_ready();

/* write page */
#ifdef NVMCTRL_CTRLB_CMDEX_KEY
_NVMCTRL->CTRLB.reg = (NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_WP);
#else
void flashpage_write(int page, const void *data)
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP);
#endif
}

static void _write_page(void* dst, const void *data, size_t len, void (*cmd_write)(void))
{
uint32_t *page_addr;
uint32_t *dst32 = dst;

#ifdef FLASHPAGE_RWWEE_NUMOF
if (flash_type == FLASH_RWWEE) {
page_addr = (uint32_t *)flashpage_rwwee_addr(page);
} else {
#endif
page_addr = (uint32_t *)flashpage_addr(page);
#ifdef FLASHPAGE_RWWEE_NUMOF
_unlock();
_cmd_clear_page_buffer();

/* copy whole words */
const uint32_t *data32 = data;
while (len) {
*dst32++ = *data32++;
len -= sizeof(uint32_t);
}
#endif

cmd_write();
_lock();
}

static void _erase_page(void* page, void (*cmd_erase)(void))
{
uintptr_t page_addr = (uintptr_t)page;

/* erase given page (the ADDR register uses 16-bit addresses) */
_unlock();
#if defined(CPU_SAML1X) || defined(CPU_SAMD5X)
/* Ensure address alignment */
_NVMCTRL->ADDR.reg = (((uint32_t)page_addr) & 0xfffffffe);
#else
_NVMCTRL->ADDR.reg = (((uint32_t)page_addr) >> 1);
#endif
#ifdef FLASHPAGE_RWWEE_NUMOF
if (flash_type == FLASH_RWWEE) {
#ifdef CPU_SAML1X
/* SAML1X use the same Erase command for both flash memories */
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER);
#else
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_RWWEEER);
#endif
} else {
#endif
#ifdef NVMCTRL_CTRLB_CMDEX_KEY
_NVMCTRL->CTRLB.reg = (NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_EB);
#else
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER);
#endif
#ifdef FLASHPAGE_RWWEE_NUMOF
}

/* ADDR drives the hardware (16-bit) address to the NVM when a command is executed using CMDEX.
* 8-bit addresses must be shifted one bit to the right before writing to this register.
*/
#if defined(CPU_SAMD21) || defined(CPU_SAML21)
page_addr >>= 1;
#endif
wait_nvm_is_ready();

/* set Row/Block start address */
_NVMCTRL->ADDR.reg = page_addr;

cmd_erase();
_lock();
}

/* write data to page */
if (data != NULL) {
/* One RIOT page is FLASHPAGE_PAGES_PER_ROW SAM0 flash pages (a row) as
* defined in the file cpu/sam0_common/include/cpu_conf.h, therefore we
* have to split the write into FLASHPAGE_PAGES_PER_ROW raw calls
* underneath, each writing a physical page in chunks of 4 bytes (see
* flashpage_write_raw)
* The erasing is done once as a full row is always reased.
*/
for (unsigned curpage = 0; curpage < FLASHPAGE_PAGES_PER_ROW; curpage++) {
#ifdef FLASHPAGE_RWWEE_NUMOF
flashpage_write_raw_internal(page_addr + (curpage * NVMCTRL_PAGE_SIZE / 4),
(void *) ((uint32_t *) data + (curpage * NVMCTRL_PAGE_SIZE / 4)),
NVMCTRL_PAGE_SIZE, flash_type);
#else
flashpage_write_raw(page_addr + (curpage * NVMCTRL_PAGE_SIZE / 4),
(void *) ((uint32_t *) data + (curpage * NVMCTRL_PAGE_SIZE / 4)),
NVMCTRL_PAGE_SIZE);
#endif
}
/* dst must be row-aligned */
static void _write_row(uint8_t *dst, const void *_data, size_t len, size_t chunk_size,
void (*cmd_write)(void))
{
const uint8_t *data = _data;

/* One RIOT page is FLASHPAGE_PAGES_PER_ROW SAM0 flash pages (a row) as
* defined in the file cpu/sam0_common/include/cpu_conf.h, therefore we
* have to split the write into FLASHPAGE_PAGES_PER_ROW raw calls
* underneath, each writing a physical page in chunks of 4 bytes (see
* flashpage_write_raw)
* The erasing is done once as a full row is always erased.
*/
while (len) {
size_t chunk = MIN(len, chunk_size);
_write_page(dst, data, chunk, cmd_write);
data += chunk;
dst += chunk;
len -= chunk;
}
}

void flashpage_write(int page, const void *data)
{
assert((unsigned)page < FLASHPAGE_NUMOF);

_erase_page(flashpage_addr(page), _cmd_erase_row);

if (data == NULL) {
return;
}

_write_row(flashpage_addr(page), data, FLASHPAGE_SIZE, NVMCTRL_PAGE_SIZE,
_cmd_write_page);
}

#ifdef FLASHPAGE_RWWEE_NUMOF
/*
* If RWWEE flash is present then we create an additional layer for the write functions
* so we can specify the type (either MAIN or RWWEE) we want to access, keeping the
* standard API unchanged and code for systems without RWWEE at a minimum at the cost
* of some more #defines in the code
*/
void flashpage_write_raw(void *target_addr, const void *data, size_t len)
{
flashpage_write_raw_internal(target_addr, data, len, FLASH_MAIN);
/* The actual minimal block size for writing is 16B, thus we
* assert we write on multiples and no less of that length.
*/
assert(!(len % FLASHPAGE_RAW_BLOCKSIZE));

/* ensure 4 byte aligned writes */
assert(!(((unsigned)target_addr % FLASHPAGE_RAW_ALIGNMENT) ||
((unsigned)data % FLASHPAGE_RAW_ALIGNMENT)));

/* ensure the length doesn't exceed the actual flash size */
assert(((unsigned)target_addr + len) <=
(CPU_FLASH_BASE + (FLASHPAGE_SIZE * FLASHPAGE_NUMOF)));

_write_page(target_addr, data, len, _cmd_write_page);

}

#ifdef FLASHPAGE_RWWEE_NUMOF

static void _cmd_erase_row_rwwee(void)
{
wait_nvm_is_ready();

/* send erase row command */
#ifdef NVMCTRL_CTRLA_CMD_RWWEEER
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_RWWEEER);
#else
/* SAML1X use the same Erase command for both flash memories */
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER);
#endif
}

void flashpage_write(int page, const void *data)
static void _cmd_write_page_rwwee(void)
{
assert((uint32_t)page < FLASHPAGE_NUMOF);
wait_nvm_is_ready();

flashpage_write_internal(page, data, FLASH_MAIN);
/* write page */
#ifdef NVMCTRL_CTRLA_CMD_RWWEEWP
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_RWWEEWP);
#else
/* SAML1X use the same Write Page command for both flash memories */
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP);
#endif
}

void flashpage_rwwee_write_raw(void *target_addr, const void *data, size_t len)
{
flashpage_write_raw_internal(target_addr, data, len, FLASH_RWWEE);
/* The actual minimal block size for writing is 16B, thus we
* assert we write on multiples and no less of that length.
*/
assert(!(len % FLASHPAGE_RAW_BLOCKSIZE));

/* ensure 4 byte aligned writes */
assert(!(((unsigned)target_addr % FLASHPAGE_RAW_ALIGNMENT) ||
((unsigned)data % FLASHPAGE_RAW_ALIGNMENT)));

assert(((unsigned)target_addr + len) <=
(CPU_FLASH_RWWEE_BASE + (FLASHPAGE_SIZE * FLASHPAGE_RWWEE_NUMOF)));

_write_page(target_addr, data, len, _cmd_write_page_rwwee);
}

void flashpage_rwwee_write(int page, const void *data)
{
assert((uint32_t)page < FLASHPAGE_RWWEE_NUMOF);
assert((unsigned)page < FLASHPAGE_RWWEE_NUMOF);

flashpage_write_internal(page, data, FLASH_RWWEE);
_erase_page(flashpage_rwwee_addr(page), _cmd_erase_row_rwwee);

if (data == NULL) {
return;
}

_write_row(flashpage_rwwee_addr(page), data, FLASHPAGE_SIZE, NVMCTRL_PAGE_SIZE,
_cmd_write_page_rwwee);
}
#endif
#endif /* FLASHPAGE_RWWEE_NUMOF */