mirror of https://github.com/wolfSSL/wolfBoot.git
613 lines
17 KiB
C
613 lines
17 KiB
C
/* nxp_s32k1xx.c
|
|
*
|
|
* Copyright (C) 2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfBoot.
|
|
*
|
|
* wolfBoot is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfBoot is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*
|
|
* HAL for NXP S32K1xx (S32K142, S32K144, S32K146, S32K148)
|
|
* Tested on S32K142: Cortex-M4F, 256KB Flash, 32KB SRAM
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include "image.h"
|
|
#include "hal.h"
|
|
#include "printf.h"
|
|
|
|
/* Override RAMFUNCTION for test-app: when RAM_CODE is set but not __WOLFBOOT,
|
|
* we still need flash functions to run from RAM for self-programming. */
|
|
#if defined(RAM_CODE) && !defined(__WOLFBOOT)
|
|
#undef RAMFUNCTION
|
|
#define RAMFUNCTION __attribute__((used,section(".ramcode"),long_call))
|
|
#endif
|
|
|
|
/* Assembly helpers */
|
|
#define DMB() __asm__ volatile ("dmb")
|
|
#define DSB() __asm__ volatile ("dsb")
|
|
#define ISB() __asm__ volatile ("isb")
|
|
|
|
/* PRIMASK helpers for critical sections */
|
|
#define __get_PRIMASK() ({ \
|
|
uint32_t primask; \
|
|
__asm__ volatile ("mrs %0, primask" : "=r" (primask)); \
|
|
primask; \
|
|
})
|
|
|
|
#define __set_PRIMASK(primask) \
|
|
__asm__ volatile ("msr primask, %0" :: "r" (primask) : "memory")
|
|
|
|
#define __disable_irq() \
|
|
__asm__ volatile ("cpsid i" ::: "memory")
|
|
|
|
#define __enable_irq() \
|
|
__asm__ volatile ("cpsie i" ::: "memory")
|
|
|
|
#include "s32k1xx.h"
|
|
|
|
/* ============== Flash Configuration Field (FCF) ============== */
|
|
/* Located at 0x400-0x40F in flash */
|
|
|
|
#ifdef __WOLFBOOT
|
|
|
|
/* Flash Option Byte - override with CFLAGS_EXTRA+=-DWOLFBOOT_FOPT=0xF7 */
|
|
#ifndef WOLFBOOT_FOPT
|
|
#define WOLFBOOT_FOPT 0xFF
|
|
#endif
|
|
|
|
#define FCF_LEN (16)
|
|
const uint8_t __attribute__((section(".flash_config"))) flash_config[FCF_LEN] = {
|
|
/* Backdoor comparison key (8 bytes) */
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
/* Program Flash Protection (4 bytes) - all unprotected */
|
|
0xFF, 0xFF, 0xFF, 0xFF,
|
|
/* Flash Security Byte */
|
|
0xFE, /* SEC=10 (unsecured), FSLACC=11, MEEN=11, KEYEN=11 */
|
|
/* Flash Option Byte */
|
|
WOLFBOOT_FOPT,
|
|
/* EEPROM Protection Byte */
|
|
0xFF,
|
|
/* Data Flash Protection Byte */
|
|
0xFF
|
|
};
|
|
#endif
|
|
|
|
/* ============== Watchdog Functions ============== */
|
|
|
|
/* Disable watchdog - must be called within 128 bus clock cycles after reset
|
|
* or after unlocking. The watchdog is enabled by default after reset.
|
|
*/
|
|
static void watchdog_disable(void)
|
|
{
|
|
/* Unlock watchdog by writing unlock key to CNT register */
|
|
WDOG_CNT = WDOG_CNT_UNLOCK;
|
|
|
|
/* Wait for unlock to complete (ULK bit set) */
|
|
while (!(WDOG_CS & WDOG_CS_ULK)) {}
|
|
|
|
/* Set timeout to max and disable watchdog */
|
|
WDOG_TOVAL = WDOG_TOVAL_DEFAULT;
|
|
WDOG_CS = WDOG_CS_DISABLE_CFG;
|
|
|
|
/* Wait for reconfiguration to complete (RCS bit set) */
|
|
while (!(WDOG_CS & WDOG_CS_RCS)) {}
|
|
}
|
|
|
|
#ifdef WATCHDOG
|
|
/* Enable watchdog with specified timeout value
|
|
* timeout_ms: timeout in milliseconds (max ~512ms with LPO clock)
|
|
* LPO clock is 128kHz, so each tick is ~7.8125us
|
|
* For longer timeouts, use PRES bit for 256x prescaler
|
|
*/
|
|
static void watchdog_enable(uint32_t timeout_ms)
|
|
{
|
|
uint32_t toval;
|
|
uint32_t cs_cfg = WDOG_CS_ENABLE_CFG;
|
|
|
|
/* Calculate TOVAL from timeout_ms
|
|
* LPO = 128kHz = 128 ticks/ms
|
|
* With PRES=0: max timeout = 65535/128 = 512ms
|
|
* With PRES=1: max timeout = 65535*256/128 = 131 seconds
|
|
*/
|
|
if (timeout_ms > 512) {
|
|
/* Use prescaler for longer timeouts */
|
|
cs_cfg |= WDOG_CS_PRES;
|
|
toval = (timeout_ms * 128) / 256;
|
|
} else {
|
|
toval = timeout_ms * 128;
|
|
}
|
|
|
|
/* Clamp to max value */
|
|
if (toval > 0xFFFF) {
|
|
toval = 0xFFFF;
|
|
}
|
|
|
|
/* Unlock watchdog */
|
|
WDOG_CNT = WDOG_CNT_UNLOCK;
|
|
while (!(WDOG_CS & WDOG_CS_ULK)) {}
|
|
|
|
/* Configure and enable */
|
|
WDOG_TOVAL = toval;
|
|
WDOG_CS = cs_cfg;
|
|
|
|
/* Wait for reconfiguration to complete */
|
|
while (!(WDOG_CS & WDOG_CS_RCS)) {}
|
|
}
|
|
|
|
#endif /* WATCHDOG */
|
|
|
|
/* ============== Clock Configuration ============== */
|
|
|
|
/* SIRC - Slow Internal RC (8 MHz) register fields */
|
|
#define SCG_SIRCCSR_SIRCEN (1UL << 0)
|
|
#define SCG_SIRCCSR_SIRCVLD (1UL << 24)
|
|
#define SCG_xCCR_SCS_SIRC (2UL << SCG_xCCR_SCS_SHIFT)
|
|
#define SCG_CSR_SCS_SIRC (2UL << SCG_CSR_SCS_SHIFT)
|
|
|
|
#ifdef WOLFBOOT_RESTORE_CLOCK
|
|
/* Restore clock to safe default (SIRC 8 MHz) before booting application.
|
|
* This allows the application to configure clocks from a known state.
|
|
*/
|
|
static void clock_restore_sirc(void)
|
|
{
|
|
/* Enable SIRC (8 MHz) if not already enabled */
|
|
SCG_SIRCDIV = (1UL << 8) | (1UL << 0); /* SIRCDIV1=/1, SIRCDIV2=/1 */
|
|
SCG_SIRCCFG = 0; /* Range 0: 2 MHz (default) - actually S32K uses 8MHz SIRC */
|
|
SCG_SIRCCSR = SCG_SIRCCSR_SIRCEN;
|
|
|
|
/* Wait for SIRC valid */
|
|
while (!(SCG_SIRCCSR & SCG_SIRCCSR_SIRCVLD)) {}
|
|
|
|
/* Switch to SIRC as system clock
|
|
* SCS = SIRC (2)
|
|
* DIVCORE = /1 (8 MHz)
|
|
* DIVBUS = /1 (8 MHz)
|
|
* DIVSLOW = /1 (8 MHz)
|
|
*/
|
|
SCG_RCCR = SCG_xCCR_SCS_SIRC |
|
|
(0UL << SCG_xCCR_DIVCORE_SHIFT) |
|
|
(0UL << SCG_xCCR_DIVBUS_SHIFT) |
|
|
(0UL << SCG_xCCR_DIVSLOW_SHIFT);
|
|
|
|
/* Wait for clock switch */
|
|
while ((SCG_CSR & SCG_CSR_SCS_MASK) != SCG_CSR_SCS_SIRC) {}
|
|
|
|
/* Disable FIRC to save power (application can re-enable if needed) */
|
|
SCG_FIRCCSR &= ~SCG_FIRCCSR_FIRCEN;
|
|
}
|
|
#endif /* WOLFBOOT_RESTORE_CLOCK */
|
|
|
|
static void clock_init_firc(void)
|
|
{
|
|
/* Enable FIRC (48 MHz) */
|
|
SCG_FIRCDIV = (1UL << 8) | (1UL << 0); /* FIRCDIV1=/1, FIRCDIV2=/1 */
|
|
SCG_FIRCCFG = 0; /* Range 0: 48 MHz */
|
|
SCG_FIRCCSR = SCG_FIRCCSR_FIRCEN;
|
|
|
|
/* Wait for FIRC valid */
|
|
while (!(SCG_FIRCCSR & SCG_FIRCCSR_FIRCVLD)) {}
|
|
}
|
|
|
|
static void clock_init_spll(void)
|
|
{
|
|
/* S32K1xx SPLL requires SOSC as source (FIRC cannot be used directly).
|
|
* For 112 MHz with 8 MHz SOSC: PREDIV=0, MULT=28 -> VCO=224 MHz, SPLL=112 MHz
|
|
* VCO range: 180-320 MHz, SPLL_CLK = VCO / 2
|
|
*
|
|
* Currently using FIRC 48 MHz directly. TODO: Add SOSC + SPLL for 112 MHz.
|
|
*/
|
|
SCG_SPLLCSR &= ~SCG_SPLLCSR_SPLLEN;
|
|
SCG_SPLLDIV = (2UL << 8) | (4UL << 0); /* SPLLDIV1=/2, SPLLDIV2=/4 */
|
|
}
|
|
|
|
static void clock_init(void)
|
|
{
|
|
/* Initialize FIRC to 48 MHz */
|
|
clock_init_firc();
|
|
|
|
/* Configure Run mode clock control:
|
|
* SCS = FIRC (3)
|
|
* DIVCORE = /1 (48 MHz)
|
|
* DIVBUS = /1 (48 MHz)
|
|
* DIVSLOW = /2 (24 MHz for flash)
|
|
*/
|
|
SCG_RCCR = SCG_xCCR_SCS_FIRC |
|
|
(0UL << SCG_xCCR_DIVCORE_SHIFT) |
|
|
(0UL << SCG_xCCR_DIVBUS_SHIFT) |
|
|
(1UL << SCG_xCCR_DIVSLOW_SHIFT);
|
|
|
|
/* Wait for clock switch */
|
|
while ((SCG_CSR & SCG_CSR_SCS_MASK) != SCG_CSR_SCS_FIRC) {}
|
|
|
|
#ifdef S32K1XX_CLOCK_HSRUN
|
|
/* HSRUN mode (112 MHz) - requires SOSC + SPLL (not fully implemented yet)
|
|
* TODO: Add SOSC initialization and SPLL configuration for true 112 MHz
|
|
* Currently this enters HSRUN mode but still uses FIRC at 48 MHz
|
|
*/
|
|
|
|
/* Enable HSRUN mode */
|
|
SMC_PMPROT = SMC_PMPROT_AHSRUN;
|
|
|
|
/* Configure HSRUN clock control (same as RUN for now with FIRC) */
|
|
SCG_HCCR = SCG_xCCR_SCS_FIRC |
|
|
(0UL << SCG_xCCR_DIVCORE_SHIFT) |
|
|
(0UL << SCG_xCCR_DIVBUS_SHIFT) |
|
|
(1UL << SCG_xCCR_DIVSLOW_SHIFT);
|
|
|
|
/* Enter HSRUN mode */
|
|
SMC_PMCTRL = (SMC_PMCTRL & ~(3UL << SMC_PMCTRL_RUNM_SHIFT)) | SMC_PMCTRL_RUNM_HSRUN;
|
|
|
|
/* Wait for HSRUN */
|
|
while ((SMC_PMSTAT & 0xFF) != SMC_PMSTAT_HSRUN) {}
|
|
#endif
|
|
}
|
|
|
|
/* ============== UART Functions ============== */
|
|
|
|
#ifdef DEBUG_UART
|
|
|
|
#ifndef UART_BAUDRATE
|
|
#define UART_BAUDRATE 115200
|
|
#endif
|
|
|
|
void uart_init(void)
|
|
{
|
|
uint32_t sbr;
|
|
uint32_t osr = 16; /* Oversampling ratio */
|
|
uint32_t uart_clock = 48000000UL; /* FIRC 48 MHz */
|
|
|
|
/* Enable clock to TX and RX port(s)
|
|
* Note: If TX and RX use different ports, both need clock enabled
|
|
*/
|
|
DEBUG_UART_TX_PCC_PORT |= PCC_CGC;
|
|
#if !DEBUG_UART_SAME_PORT
|
|
DEBUG_UART_RX_PCC_PORT |= PCC_CGC;
|
|
#endif
|
|
|
|
/* Configure pins for selected LPUART */
|
|
DEBUG_UART_RX_PCR = DEBUG_UART_RX_MUX;
|
|
DEBUG_UART_TX_PCR = DEBUG_UART_TX_MUX;
|
|
|
|
/* Enable clock to selected LPUART, source = FIRC (48 MHz) */
|
|
PCC_LPUART = 0; /* Disable before changing source */
|
|
PCC_LPUART = PCC_PCS_FIRC | PCC_CGC;
|
|
|
|
/* Calculate baud rate:
|
|
* SBR = UART_CLK / (BAUD * OSR)
|
|
*/
|
|
sbr = uart_clock / (UART_BAUDRATE * osr);
|
|
|
|
/* Disable TX/RX before configuration */
|
|
LPUART_CTRL = 0;
|
|
|
|
/* Configure baud rate */
|
|
LPUART_BAUD = ((osr - 1) << LPUART_BAUD_OSR_SHIFT) |
|
|
(sbr << LPUART_BAUD_SBR_SHIFT);
|
|
|
|
/* Enable transmitter and receiver */
|
|
LPUART_CTRL = LPUART_CTRL_TE | LPUART_CTRL_RE;
|
|
}
|
|
|
|
/* Transmit a single byte (raw, no conversion) - RAMFUNCTION for use during flash ops */
|
|
void RAMFUNCTION uart_tx(uint8_t byte)
|
|
{
|
|
while (!(LPUART1_STAT & LPUART_STAT_TDRE)) {}
|
|
LPUART1_DATA = byte;
|
|
while (!(LPUART1_STAT & LPUART_STAT_TC)) {}
|
|
}
|
|
|
|
/* Used for sending ASCII and CRLF conversions */
|
|
void uart_write(const char* buf, unsigned int sz)
|
|
{
|
|
unsigned int i;
|
|
for (i = 0; i < sz; i++) {
|
|
/* Handle newline -> CRLF conversion */
|
|
if (buf[i] == '\n') {
|
|
uart_tx('\r');
|
|
}
|
|
uart_tx(buf[i]);
|
|
}
|
|
|
|
/* Wait for transmission complete */
|
|
while (!(LPUART_STAT & LPUART_STAT_TC)) {}
|
|
}
|
|
|
|
/* Read a single character from UART (non-blocking) - RAMFUNCTION for use during flash ops
|
|
* Returns: 1 if character read, 0 if no data available
|
|
*/
|
|
int RAMFUNCTION uart_read(char* c)
|
|
{
|
|
uint32_t stat = LPUART1_STAT;
|
|
|
|
/* Clear any error flags first */
|
|
if (stat & (LPUART_STAT_OR | LPUART_STAT_NF | LPUART_STAT_FE | LPUART_STAT_PF)) {
|
|
LPUART1_STAT = stat; /* Write 1 to clear flags */
|
|
}
|
|
|
|
/* Check if data available - read even if there was an error */
|
|
if (stat & LPUART_STAT_RDRF) {
|
|
*c = (char)(LPUART1_DATA & 0xFF);
|
|
return 1;
|
|
}
|
|
|
|
return 0; /* No data available */
|
|
}
|
|
|
|
#endif /* DEBUG_UART */
|
|
|
|
/* ============== Flash Functions ============== */
|
|
|
|
static void RAMFUNCTION flash_wait_complete(void)
|
|
{
|
|
/* Wait for command complete */
|
|
while (!(FTFC_FSTAT & FTFC_FSTAT_CCIF)) {}
|
|
}
|
|
|
|
static void RAMFUNCTION flash_clear_errors(void)
|
|
{
|
|
/* Clear error flags by writing 1 */
|
|
if (FTFC_FSTAT & (FTFC_FSTAT_ACCERR | FTFC_FSTAT_FPVIOL)) {
|
|
FTFC_FSTAT = FTFC_FSTAT_ACCERR | FTFC_FSTAT_FPVIOL;
|
|
}
|
|
}
|
|
|
|
static int RAMFUNCTION flash_program_phrase(uint32_t address, const uint8_t *data)
|
|
{
|
|
/* Skip if phrase is all 0xFF (erased) */
|
|
if (data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF &&
|
|
data[4] == 0xFF && data[5] == 0xFF && data[6] == 0xFF && data[7] == 0xFF) {
|
|
return 0;
|
|
}
|
|
|
|
/* Wait for previous command to complete */
|
|
flash_wait_complete();
|
|
flash_clear_errors();
|
|
|
|
/* Set up Program Phrase command (0x07) */
|
|
/* Programs 8 bytes at the specified address */
|
|
FTFC_FCCOB0 = FTFC_CMD_PROGRAM_PHRASE;
|
|
FTFC_FCCOB1 = (uint8_t)(address >> 16);
|
|
FTFC_FCCOB2 = (uint8_t)(address >> 8);
|
|
FTFC_FCCOB3 = (uint8_t)(address);
|
|
|
|
/* Data bytes (big-endian order in FCCOB registers) */
|
|
FTFC_FCCOB4 = data[3];
|
|
FTFC_FCCOB5 = data[2];
|
|
FTFC_FCCOB6 = data[1];
|
|
FTFC_FCCOB7 = data[0];
|
|
FTFC_FCCOB8 = data[7];
|
|
FTFC_FCCOB9 = data[6];
|
|
FTFC_FCCOBA = data[5];
|
|
FTFC_FCCOBB = data[4];
|
|
|
|
/* Launch command */
|
|
DSB();
|
|
ISB();
|
|
FTFC_FSTAT = FTFC_FSTAT_CCIF;
|
|
|
|
/* Wait for completion */
|
|
flash_wait_complete();
|
|
|
|
#ifdef WATCHDOG
|
|
/* Refresh watchdog after flash operation */
|
|
WDOG_CNT = WDOG_CNT_REFRESH;
|
|
#endif
|
|
|
|
/* Check for errors */
|
|
if (FTFC_FSTAT & (FTFC_FSTAT_ACCERR | FTFC_FSTAT_FPVIOL | FTFC_FSTAT_MGSTAT0)) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int RAMFUNCTION flash_erase_sector_internal(uint32_t address)
|
|
{
|
|
uint32_t primask;
|
|
|
|
/* Wait for previous command to complete */
|
|
flash_wait_complete();
|
|
flash_clear_errors();
|
|
|
|
/* Set up Erase Sector command (0x09) */
|
|
FTFC_FCCOB0 = FTFC_CMD_ERASE_SECTOR;
|
|
FTFC_FCCOB1 = (uint8_t)(address >> 16);
|
|
FTFC_FCCOB2 = (uint8_t)(address >> 8);
|
|
FTFC_FCCOB3 = (uint8_t)(address);
|
|
|
|
/* Launch command */
|
|
DSB();
|
|
ISB();
|
|
|
|
/* Disable interrupts during flash operation to prevent code fetch from flash */
|
|
primask = __get_PRIMASK();
|
|
__disable_irq();
|
|
|
|
FTFC_FSTAT = FTFC_FSTAT_CCIF;
|
|
|
|
/* Wait for completion */
|
|
flash_wait_complete();
|
|
|
|
/* Re-enable interrupts */
|
|
__set_PRIMASK(primask);
|
|
|
|
#ifdef WATCHDOG
|
|
/* Refresh watchdog after potentially long flash operation */
|
|
WDOG_CNT = WDOG_CNT_REFRESH;
|
|
#endif
|
|
|
|
/* Check for errors */
|
|
if (FTFC_FSTAT & (FTFC_FSTAT_ACCERR | FTFC_FSTAT_FPVIOL | FTFC_FSTAT_MGSTAT0)) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ============== HAL Interface Functions ============== */
|
|
|
|
void hal_init(void)
|
|
{
|
|
/* Disable watchdog first - must be done early after reset */
|
|
watchdog_disable();
|
|
|
|
/* Initialize clocks */
|
|
clock_init();
|
|
|
|
/* Enable clock to flash controller */
|
|
PCC_FTFC |= PCC_CGC;
|
|
|
|
#ifdef DEBUG_UART
|
|
uart_init();
|
|
|
|
#ifdef __WOLFBOOT
|
|
#ifdef WOLFBOOT_REPRODUCIBLE_BUILD
|
|
wolfBoot_printf("wolfBoot Version: %s\n", LIBWOLFBOOT_VERSION_STRING);
|
|
#else
|
|
wolfBoot_printf("wolfBoot Version: %s (%s %s)\n",
|
|
LIBWOLFBOOT_VERSION_STRING,__DATE__, __TIME__);
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef WATCHDOG
|
|
watchdog_enable(WATCHDOG_TIMEOUT_MS);
|
|
#endif
|
|
}
|
|
|
|
void hal_prepare_boot(void)
|
|
{
|
|
#ifdef DEBUG_UART
|
|
/* Wait for any pending UART transmission to complete */
|
|
while (!(LPUART_STAT & LPUART_STAT_TC)) {}
|
|
|
|
/* Disable UART before jumping to application.
|
|
* This gives the application a clean UART state to initialize from.
|
|
* Without this, the application may have issues reinitializing the UART.
|
|
*/
|
|
LPUART_CTRL = 0;
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_RESTORE_CLOCK
|
|
/* Restore clock to SIRC (8 MHz) before booting application.
|
|
* This gives the application a known clock state to start from.
|
|
*/
|
|
clock_restore_sirc();
|
|
#endif
|
|
|
|
/* Re-enable watchdog before booting application.
|
|
* The watchdog is enabled by default after reset, so the application
|
|
* may expect it to be running. Use a generous timeout to give the
|
|
* application time to reconfigure or disable the watchdog.
|
|
*/
|
|
#ifndef WOLFBOOT_DISABLE_WATCHDOG_ON_BOOT
|
|
{
|
|
/* Unlock watchdog */
|
|
WDOG_CNT = WDOG_CNT_UNLOCK;
|
|
while (!(WDOG_CS & WDOG_CS_ULK)) {}
|
|
|
|
/* Enable watchdog with 65535 ticks at 128kHz LPO: ~512ms
|
|
* without the prescaler, ~131 seconds with the 1:256 prescaler
|
|
* (WDOG_CS_PRES). Application should either service or
|
|
* reconfigure the watchdog. */
|
|
WDOG_TOVAL = 0xFFFF;
|
|
WDOG_CS = WDOG_CS_EN | WDOG_CS_UPDATE | WDOG_CS_CMD32EN |
|
|
WDOG_CS_CLK_LPO | WDOG_CS_PRES;
|
|
|
|
/* Wait for reconfiguration to complete */
|
|
while (!(WDOG_CS & WDOG_CS_RCS)) {}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
|
|
{
|
|
int ret, i = 0;
|
|
uint8_t phrase_buf[FLASH_PHRASE_SIZE];
|
|
|
|
while (len > 0) {
|
|
if ((len < FLASH_PHRASE_SIZE) || (address & (FLASH_PHRASE_SIZE - 1))) {
|
|
/* Handle unaligned start or partial phrase */
|
|
uint32_t aligned_addr = address & ~(FLASH_PHRASE_SIZE - 1);
|
|
uint32_t offset = address - aligned_addr;
|
|
int bytes_to_copy = FLASH_PHRASE_SIZE - offset;
|
|
if (bytes_to_copy > len)
|
|
bytes_to_copy = len;
|
|
|
|
memcpy(phrase_buf, (void*)aligned_addr, FLASH_PHRASE_SIZE);
|
|
memcpy(phrase_buf + offset, data + i, bytes_to_copy);
|
|
|
|
ret = flash_program_phrase(aligned_addr, phrase_buf);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
address += bytes_to_copy;
|
|
i += bytes_to_copy;
|
|
len -= bytes_to_copy;
|
|
} else {
|
|
/* Program full phrases */
|
|
while (len >= FLASH_PHRASE_SIZE) {
|
|
ret = flash_program_phrase(address, data + i);
|
|
if (ret != 0)
|
|
return ret;
|
|
address += FLASH_PHRASE_SIZE;
|
|
i += FLASH_PHRASE_SIZE;
|
|
len -= FLASH_PHRASE_SIZE;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
|
|
{
|
|
int ret;
|
|
|
|
/* Align address to sector boundary */
|
|
if (address % FLASH_SECTOR_SIZE) {
|
|
address -= (address % FLASH_SECTOR_SIZE);
|
|
}
|
|
|
|
while (len > 0) {
|
|
ret = flash_erase_sector_internal(address);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
address += FLASH_SECTOR_SIZE;
|
|
len -= FLASH_SECTOR_SIZE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void RAMFUNCTION hal_flash_unlock(void)
|
|
{
|
|
/* Ensure flash controller clock is enabled */
|
|
PCC_FTFC |= PCC_CGC;
|
|
|
|
/* Clear any pending errors */
|
|
if (FTFC_FSTAT & (FTFC_FSTAT_ACCERR | FTFC_FSTAT_FPVIOL)) {
|
|
FTFC_FSTAT = FTFC_FSTAT_ACCERR | FTFC_FSTAT_FPVIOL;
|
|
}
|
|
}
|
|
|
|
void RAMFUNCTION hal_flash_lock(void)
|
|
{
|
|
/* No explicit lock needed */
|
|
}
|
|
|