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: add functions to write to user config area / AUX page #14512

Merged
merged 9 commits into from
Nov 10, 2020
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);
benpicco marked this conversation as resolved.
Show resolved Hide resolved

/**
* @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