VersaMCU/bootloader/src/flash_samd21.c
cjjohn de52b57041 Add UF2 bootloader for USB firmware flashing
Vendored and trimmed from microsoft/uf2-samdx1, adapted to the actual
ATSAMD21G17D (128 KiB flash / 16 KiB RAM), fixing the flash/RAM-size
mismatch that the previously commented-out SAM-BA bootloader target
had (it assumed a 256 KiB SAMD21G18A). Builds as a standalone
PlatformIO environment (bootloader/platformio.ini), no python2/make
dependency. Compiles clean, fits in the 8 KiB bootloader region
(7292/8192 bytes). Not yet flashed/verified on real hardware.
2026-08-03 20:56:58 +02:00

115 lines
3.2 KiB
C

#include "uf2.h"
// this actually generates less code than a function
#define wait_ready() \
while (NVMCTRL->INTFLAG.bit.READY == 0) \
;
void flash_erase_row(uint32_t *dst) {
wait_ready();
NVMCTRL->STATUS.reg = NVMCTRL_STATUS_MASK;
// Execute "ER" Erase Row
NVMCTRL->ADDR.reg = (uint32_t)dst / 2;
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER;
wait_ready();
}
void flash_erase_to_end(uint32_t *start_address) {
// Note: the flash memory is erased in ROWS, that is in
// block of 4 pages.
// Even if the starting address is the last byte
// of a ROW the entire
// ROW is erased anyway.
uint32_t dst_addr = (uint32_t) start_address; // starting address
while (dst_addr < FLASH_SIZE) {
flash_erase_row((void *)dst_addr);
dst_addr += FLASH_ROW_SIZE;
}
}
void copy_words(uint32_t *dst, uint32_t *src, uint32_t n_words) {
while (n_words--)
*dst++ = *src++;
}
void flash_write_words(uint32_t *dst, uint32_t *src, uint32_t n_words) {
// Set automatic page write
NVMCTRL->CTRLB.bit.MANW = 0;
while (n_words > 0) {
uint32_t len = (FLASH_PAGE_SIZE >> 2) < n_words ? (FLASH_PAGE_SIZE >> 2) : n_words;
n_words -= len;
// Execute "PBC" Page Buffer Clear
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC;
wait_ready();
// make sure there are no other memory writes here
// otherwise we get lock-ups
while (len--)
*dst++ = *src++;
// Execute "WP" Write Page
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP;
wait_ready();
}
}
void flash_write(void) {
uint32_t *src = (void *)0x20006000;
uint32_t *dst = (void *)*src++;
uint32_t n_rows = *src++;
NVMCTRL->CTRLB.bit.MANW = 1;
while (n_rows--) {
wait_ready();
NVMCTRL->STATUS.reg = NVMCTRL_STATUS_MASK;
// Execute "ER" Erase Row
NVMCTRL->ADDR.reg = (uint32_t)dst / 2;
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER;
wait_ready();
// there are 4 pages to a row
for (int i = 0; i < 4; ++i) {
// Execute "PBC" Page Buffer Clear
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC;
wait_ready();
uint32_t len = FLASH_PAGE_SIZE >> 2;
while (len--)
*dst++ = *src++;
// Execute "WP" Write Page
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP;
wait_ready();
}
}
}
// Skip writing blocks that are identical to the existing block.
// only disable for debugging/timing
#define QUICK_FLASH 1
void flash_write_row(uint32_t *dst, uint32_t *src) {
#if QUICK_FLASH
bool src_different = false;
for (int i = 0; i < FLASH_ROW_SIZE / 4; ++i) {
if (src[i] != dst[i]) {
src_different = true;
break;
}
}
if (!src_different) {
return;
}
#endif
flash_erase_row(dst);
flash_write_words(dst, src, FLASH_ROW_SIZE / 4);
}