/* * Copyright (C) 2019 Inria * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup cpu_kinetis * @ingroup drivers_periph_flashpage * @{ * * @file * @brief Low-level flashpage driver implementation * * @author Francisco Molina * @} */ #include "cpu.h" #include "assert.h" #include "periph/flashpage.h" #define ENABLE_DEBUG 0 #include "debug.h" /** * @brief Flash controller commands */ #define FTFx_VERIFY_SECTION (0x01U) /* RD1SEC */ #define FTFx_ERASE_SECTOR (0x09U) /* ERSSCR */ #define FTFx_PROGRAM_LONGWORD (0x06U) /* PGM4 */ #define FTFx_PROGRAM_PHRASE (0x07U) /* PGM8 */ /** * @brief Kinetis Flash Memory Module common registers */ #if defined(FTFA) #define FTFx FTFA #define FTFx_BASE FTFA_BASE #define FTFx_FSTAT_CCIF_MASK FTFA_FSTAT_CCIF_MASK #define FTFx_FSTAT_RDCOLERR_MASK FTFA_FSTAT_RDCOLERR_MASK #define FTFx_FSTAT_ACCERR_MASK FTFA_FSTAT_ACCERR_MASK #define FTFx_FSTAT_FPVIOL_MASK FTFA_FSTAT_FPVIOL_MASK #define FTFx_FSTAT_MGSTAT0_MASK FTFA_FSTAT_MGSTAT0_MASK #define FTFx_FSEC_SEC_MASK FTFA_FSEC_SEC_MASK #define FTFx_FSEC_KEYEN_MASK FTFA_FSEC_KEYEN_MASK #elif defined(FTFE) #define FTFx FTFE #define FTFx_BASE FTFE_BASE #define FTFx_FSTAT_CCIF_MASK FTFE_FSTAT_CCIF_MASK #define FTFx_FSTAT_RDCOLERR_MASK FTFE_FSTAT_RDCOLERR_MASK #define FTFx_FSTAT_ACCERR_MASK FTFE_FSTAT_ACCERR_MASK #define FTFx_FSTAT_FPVIOL_MASK FTFE_FSTAT_FPVIOL_MASK #define FTFx_FSTAT_MGSTAT0_MASK FTFE_FSTAT_MGSTAT0_MASK #define FTFx_FSEC_SEC_MASK FTFE_FSEC_SEC_MASK #define FTFx_FSEC_KEYEN_MASK FTFE_FSEC_KEYEN_MASK #elif defined(FTFL) #define FTFx FTFL #define FTFx_BASE FTFL_BASE #define FTFx_FSTAT_CCIF_MASK FTFL_FSTAT_CCIF_MASK #define FTFx_FSTAT_RDCOLERR_MASK FTFL_FSTAT_RDCOLERR_MASK #define FTFx_FSTAT_ACCERR_MASK FTFL_FSTAT_ACCERR_MASK #define FTFx_FSTAT_FPVIOL_MASK FTFL_FSTAT_FPVIOL_MASK #define FTFx_FSTAT_MGSTAT0_MASK FTFL_FSTAT_MGSTAT0_MASK #define FTFx_FSEC_SEC_MASK FTFL_FSEC_SEC_MASK #define FTFx_FSEC_KEYEN_MASK FTFL_FSEC_KEYEN_MASK #endif #define BYTES_JOIN_TO_WORD_1_3(x, y) ((((uint32_t)(x) & 0xFFU) << 24) | \ ((uint32_t)(y) & 0xFFFFFFU)) #define BYTES_JOIN_TO_WORD_2_1_1(x, y, z) \ ((((uint32_t)(x) & 0xFFFFU) << 16) | (((uint32_t)(y) & 0xFFU) << 8) | \ ((uint32_t)(z) & 0xFFU)) /** * @brief Flash errors */ enum { NO_ERROR = 0, PROTECTION_ERROR = -1, ALIGN_ERROR = -2, ACCESS_ERROR = -3, COLLISION_ERROR = -4, RUNTIME_ERROR = -5, ERASE_ERROR = -6, }; /* Pointer to FCCOB register. An intermediate cast is used to silence * -Wcast-align as the address of the h/w register is properly aligned * for 32 bit accesses */ static volatile uint32_t *const FCCOBx = (volatile uint32_t *)(uintptr_t)&FTFx->FCCOB3; /* Erasing/Programming flash is not allowed inside the same flash block where the program is being read, for _run_command to be executed in RAM to avoid this, more details in: https://www.nxp.com/docs/en/application-note/AN4695.pdf */ __attribute__ ((section (".ramfunc"))) static void _run_command(void) { /* Clear previous errors */ FTFx->FSTAT = FTFx_FSTAT_FPVIOL_MASK | FTFx_FSTAT_ACCERR_MASK | FTFx_FSTAT_RDCOLERR_MASK; /* Start Operation */ FTFx->FSTAT = FTFx_FSTAT_CCIF_MASK; /* Wait for CCIF to be set (operation finished) */ while (!(FTFx->FSTAT & FTFx_FSTAT_CCIF_MASK)) {} } static int _check_errors(void) { if (FTFx->FSTAT & FTFx_FSTAT_FPVIOL_MASK) { DEBUG("[flash_write]: flash protection violation\n"); return PROTECTION_ERROR; } if (FTFx->FSTAT & FTFx_FSTAT_ACCERR_MASK) { DEBUG("[flash_write]: flash access error\n"); return ACCESS_ERROR; } if (FTFx->FSTAT & FTFx_FSTAT_RDCOLERR_MASK) { DEBUG("[flash_write]: collision error\n"); return COLLISION_ERROR; } if (FTFx->FSTAT & FTFx_FSTAT_MGSTAT0_MASK) { DEBUG("[flash_write]: runtime error\\n"); return RUNTIME_ERROR; } DEBUG("[flash_write]: no error reported\n"); return 0; } static int _verify_erased(uint32_t address, size_t len) { /* Ensures verifies are aligned */ assert(!(address % FLASHPAGE_BLOCK_SECTION_ALIGNMENT)); /* We verify FLASHPAGE_BLOCK_SECTION_ALIGNMENT Bytes at a time, we round up before adjusting to FLASHPAGE_BLOCK_SECTION_ALIGNMENT */ uint32_t num = (len + (FLASHPAGE_BLOCK_SECTION_ALIGNMENT - 1)) / FLASHPAGE_BLOCK_SECTION_ALIGNMENT; /* Setup command and run */ FCCOBx[0] = BYTES_JOIN_TO_WORD_1_3(FTFx_VERIFY_SECTION, address); FCCOBx[1] = BYTES_JOIN_TO_WORD_2_1_1(num, 0x00, 0xFF); _run_command(); return _check_errors(); } static int _sector_erase(uint32_t address) { /* Ensures erases are aligned */ assert(!(address % FLASHPAGE_BLOCK_SECTION_ALIGNMENT)); /* Setup command and run */ FCCOBx[0] = BYTES_JOIN_TO_WORD_1_3(FTFx_ERASE_SECTOR, address); _run_command(); return _check_errors(); } static int _flash_write(uint32_t address, uint32_t *data) { /* Setup command and run */ FCCOBx[1] = *data++; if (FLASHPAGE_WRITE_BLOCK_SIZE == 8U) { FCCOBx[2] = *data; FCCOBx[0] = BYTES_JOIN_TO_WORD_1_3(FTFx_PROGRAM_PHRASE, address); } else { FCCOBx[0] = BYTES_JOIN_TO_WORD_1_3(FTFx_PROGRAM_LONGWORD, address); } _run_command(); return _check_errors(); } void flashpage_erase(unsigned page) { assert(page < (int)FLASHPAGE_NUMOF); /* ERASE sector */ _sector_erase((uint32_t)flashpage_addr(page)); } void flashpage_write(void *target_addr, const void *data, size_t len) { /* Assert multiples of FLASHPAGE_WRITE_BLOCK_SIZE are written and no less of that length. */ assert(!(len % FLASHPAGE_WRITE_BLOCK_SIZE)); /* Ensure writes are aligned */ assert(!(((unsigned)target_addr % FLASHPAGE_WRITE_BLOCK_ALIGNMENT) || ((unsigned)data % FLASHPAGE_WRITE_BLOCK_ALIGNMENT))); /* Ensure the length doesn't exceed the actual flash size */ assert(((unsigned)target_addr + len) < (CPU_FLASH_BASE + (FLASHPAGE_SIZE * FLASHPAGE_NUMOF)) + 1); #ifdef FLASHPAGE_BLOCK_PHRASE const uint64_t *data_addr = data; uint64_t *dst = target_addr; #else const uint32_t *data_addr = data; uint32_t *dst = target_addr; #endif /* Verify sector was previously erased */ if (_verify_erased((uint32_t)dst, len) != NO_ERROR) { DEBUG("[flash-write]: ERROR sector not in erased state\n"); return; } /* Write to flash FLASHPAGE_WRITE_BLOCK_SIZE bytes at a time */ for (size_t i = 0; i < (len / FLASHPAGE_WRITE_BLOCK_SIZE); i++) { _flash_write((uint32_t) dst++, (uint32_t *) data_addr++); } }