Skip to content

Commit

Permalink
Merge pull request #14512 from benpicco/cpu/sam0_common/nvm_user_page
Browse files Browse the repository at this point in the history
cpu/sam0_common: flashpage: add functions to write to user config area / AUX page
  • Loading branch information
fjmolinas authored Nov 10, 2020
2 parents 9487779 + b9e61fd commit 6ed397b
Show file tree
Hide file tree
Showing 11 changed files with 458 additions and 30 deletions.
72 changes: 72 additions & 0 deletions cpu/sam0_common/include/periph_cpu_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,78 @@ int rtc_tamper_register(gpio_t pin, gpio_flank_t flank);
void rtc_tamper_enable(void);
/** @} */

/**
* @name sam0 User Configuration
*
* The MCUs of this family contain a region of memory that is used to store
* CPU configuration & calibration data.
* It can be used to set persistent settings and has some additional space
* to store user configuration data.
* @{
*/

/**
* @brief MCU configuration applied on start. The contents of this struct differ
* between families.
*/
typedef struct sam0_aux_cfg_mapping nvm_user_page_t;

/**
* @brief Size of the free to use auxiliary area in the user page
*/
#ifdef FLASH_USER_PAGE_SIZE
#define FLASH_USER_PAGE_AUX_SIZE (FLASH_USER_PAGE_SIZE - sizeof(nvm_user_page_t))
#else
#define FLASH_USER_PAGE_AUX_SIZE (AUX_PAGE_SIZE * AUX_NB_OF_PAGES - sizeof(nvm_user_page_t))
#endif

/**
* @brief Reset the configuration area, apply a new configuration.
*
*
* @param cfg New MCU configuration, may be NULL.
* If cfg is NULL, this will clear the configuration area
* and apply the current configuration again.
*/
void sam0_flashpage_aux_reset(const nvm_user_page_t *cfg);

/**
* @brief Write data to the user configuration area.
* This will write data to the remaining space after @see nvm_user_page_t
* The size of this area depends on the MCU family used.
*
* Will only write bits 1 -> 0. To reset bits to 1, call @see sam0_flashpage_aux_reset
* This will reset the whole user area configuration.
*
* Arbitrary data lengths and offsets are supported.
*
* @param offset Byte offset after @see nvm_user_page_t
* must be less than `FLASH_USER_PAGE_AUX_SIZE`
* @param data The data to write
* @param len Size of the data
*/
void sam0_flashpage_aux_write_raw(uint32_t offset, const void *data, size_t len);

/**
* @brief Get pointer to data in the user configuration area.
*
* @param offset Byte offset after @see nvm_user_page_t
* must be less than `FLASH_USER_PAGE_AUX_SIZE`
* @return Pointer to the data in the User Page
*/
#define sam0_flashpage_aux_get(offset) \
(const void*)((uint8_t*)NVMCTRL_USER + sizeof(nvm_user_page_t) + (offset))

/**
* @brief Get pointer to data in the CPU configuration struct
*
* @return Pointer to the @ref nvm_user_page_t structure
*/
#define sam0_flashpage_aux_cfg() \
((const nvm_user_page_t*)NVMCTRL_USER)

/** @} */

#ifdef __cplusplus
}
#endif
Expand Down
193 changes: 163 additions & 30 deletions cpu/sam0_common/periph/flashpage.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
*/

#include <assert.h>
#include <string.h>

#include "cpu.h"
#include "periph/flashpage.h"
Expand All @@ -36,6 +37,15 @@

#define MIN(x, y) (((x) < (y)) ? (x) : (y))

/* Write Quad Word is the only allowed operation on AUX pages */
#if defined(NVMCTRL_CTRLB_CMD_WQW)
#define AUX_CHUNK_SIZE (4 * sizeof(uint32_t))
#elif defined(AUX_PAGE_SIZE)
#define AUX_CHUNK_SIZE AUX_PAGE_SIZE
#else
#define AUX_CHUNK_SIZE FLASH_USER_PAGE_SIZE
#endif

/**
* @brief NVMCTRL selection macros
*/
Expand Down Expand Up @@ -74,6 +84,11 @@ static void _lock(void)
#else
PAC1->WPSET.reg = PAC1_WPROT_DEFAULT_VAL;
#endif

/* cached flash contents may have changed - invalidate cache */
#ifdef CMCC
CMCC->MAINT0.bit.INVALL = 1;
#endif
}

static void _cmd_clear_page_buffer(void)
Expand All @@ -87,6 +102,21 @@ static void _cmd_clear_page_buffer(void)
#endif
}

static void _cmd_erase_aux(void)
{
wait_nvm_is_ready();

/* send Erase Page/Auxiliary Row command */
#if defined(NVMCTRL_CTRLB_CMD_EP)
_NVMCTRL->CTRLB.reg = (NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_EP);
#elif defined(NVMCTRL_CTRLA_CMD_EAR)
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_EAR);
#else
/* SAML1x uses same command for all areas */
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER);
#endif
}

static void _cmd_erase_row(void)
{
wait_nvm_is_ready();
Expand All @@ -99,6 +129,21 @@ static void _cmd_erase_row(void)
#endif
}

static void _cmd_write_aux(void)
{
wait_nvm_is_ready();

/* write auxiliary page */
#if defined(NVMCTRL_CTRLA_CMD_WAP)
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WAP);
#elif defined(NVMCTRL_CTRLB_CMD_WQW)
_NVMCTRL->CTRLB.reg = (NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_WQW);
#else
/* SAML1x uses same command for all areas */
_NVMCTRL->CTRLA.reg = (NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP);
#endif
}

static void _cmd_write_page(void)
{
wait_nvm_is_ready();
Expand All @@ -111,20 +156,90 @@ static void _cmd_write_page(void)
#endif
}

/* We have to write whole words, but writing 0xFF is basically a no-op
* so fill the unaligned bytes with 0xFF to get a whole extra word.
*/
static uint32_t unaligned_pad_start(const void *_data, uint8_t len)
{
const uint8_t *data = _data;
union {
uint32_t u32;
uint8_t u8[4];
} buffer = {.u32 = ~0};

switch (len) {
case 3:
buffer.u8[1] = *data++;
/* fall-through */
case 2:
buffer.u8[2] = *data++;
/* fall-through */
case 1:
buffer.u8[3] = *data++;
}

return buffer.u32;
}

/* We have to write whole words, but writing 0xFF is basically a no-op
* so fill the unaligned bytes with 0xFF to get a whole extra word.
*/
static uint32_t unaligned_pad_end(const void *_data, uint8_t len)
{
const uint8_t *data = _data;
union {
uint32_t u32;
uint8_t u8[4];
} buffer = {.u32 = ~0};

switch (len) {
case 3:
buffer.u8[2] = data[2];
/* fall-through */
case 2:
buffer.u8[1] = data[1];
/* fall-through */
case 1:
buffer.u8[0] = data[0];
}

return buffer.u32;
}

static void _write_page(void* dst, const void *data, size_t len, void (*cmd_write)(void))
{
uint32_t *dst32 = dst;
/* set bytes in the first, unaligned word */
uint8_t unaligned_start = (4 - ((uintptr_t)dst & 0x3)) & 0x3;
len -= unaligned_start;

/* set bytes in the last, unaligned word */
uint8_t unaligned_end = len & 0x3;
len -= unaligned_end;

/* word align destination address */
uint32_t *dst32 = (void*)((uintptr_t)dst & ~0x3);

_unlock();
_cmd_clear_page_buffer();

/* write the first, unaligned bytes */
if (unaligned_start) {
*dst32++ = unaligned_pad_start(data, unaligned_start);
data = (uint8_t*)data + unaligned_start;
}

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

/* write the last, unaligned bytes */
if (unaligned_end) {
*dst32 = unaligned_pad_end(data32, unaligned_end);
}

cmd_write();
_lock();
}
Expand All @@ -150,21 +265,18 @@ static void _erase_page(void* page, void (*cmd_erase)(void))
_lock();
}

/* 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.
*/
size_t next_chunk = chunk_size - ((uintptr_t)dst & (chunk_size - 1));
next_chunk = next_chunk ? next_chunk : chunk_size;

while (len) {
size_t chunk = MIN(len, chunk_size);
size_t chunk = MIN(len, next_chunk);
next_chunk = chunk_size;

_write_page(dst, data, chunk, cmd_write);
data += chunk;
dst += chunk;
Expand All @@ -182,27 +294,50 @@ void flashpage_write(int page, const void *data)
return;
}

/* 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.
*/
_write_row(flashpage_addr(page), data, FLASHPAGE_SIZE, NVMCTRL_PAGE_SIZE,
_cmd_write_page);
}

void flashpage_write_raw(void *target_addr, const void *data, size_t len)
{
/* 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);
_write_row(target_addr, data, len, NVMCTRL_PAGE_SIZE, _cmd_write_page);
}

void sam0_flashpage_aux_write_raw(uint32_t offset, const void *data, size_t len)
{
uintptr_t dst = NVMCTRL_USER + sizeof(nvm_user_page_t) + offset;

#ifdef FLASH_USER_PAGE_SIZE
assert(dst + len <= NVMCTRL_USER + FLASH_USER_PAGE_SIZE);
#else
assert(dst + len <= NVMCTRL_USER + AUX_PAGE_SIZE * AUX_NB_OF_PAGES);
#endif

_write_row((void*)dst, data, len, AUX_CHUNK_SIZE, _cmd_write_aux);
}

void sam0_flashpage_aux_reset(const nvm_user_page_t *cfg)
{
nvm_user_page_t old_cfg;

if (cfg == NULL) {
cfg = &old_cfg;
memcpy(&old_cfg, (void*)NVMCTRL_USER, sizeof(*cfg));
}

_erase_page((void*)NVMCTRL_USER, _cmd_erase_aux);
_write_row((void*)NVMCTRL_USER, cfg, sizeof(*cfg), AUX_CHUNK_SIZE, _cmd_write_aux);
}

#ifdef FLASHPAGE_RWWEE_NUMOF
Expand Down Expand Up @@ -235,19 +370,10 @@ static void _cmd_write_page_rwwee(void)

void flashpage_rwwee_write_raw(void *target_addr, const void *data, size_t len)
{
/* 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);
_write_row(target_addr, data, len, NVMCTRL_PAGE_SIZE, _cmd_write_page_rwwee);
}

void flashpage_rwwee_write(int page, const void *data)
Expand All @@ -260,6 +386,13 @@ void flashpage_rwwee_write(int page, const void *data)
return;
}

/* 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.
*/
_write_row(flashpage_rwwee_addr(page), data, FLASHPAGE_SIZE, NVMCTRL_PAGE_SIZE,
_cmd_write_page_rwwee);
}
Expand Down
Loading

0 comments on commit 6ed397b

Please sign in to comment.