#include "general.h"
#include "target.h"
#include "target_internal.h"
#include "cortex.h"
#include "lpc_common.h"
#include "adiv5.h"
#define IAP_PGM_CHUNKSIZE 4096U
#define MIN_RAM_SIZE 8192U
#define RAM_USAGE_FOR_IAP_ROUTINES 32U
#define IAP_ENTRYPOINT 0x1fff1ff1U
#define IAP_RAM_BASE 0x10000000U
#define LPC40xx_MEMMAP UINT32_C(0x400fc040)
#define LPC40xx_MPU_BASE UINT32_C(0xe000ed90)
#define LPC40xx_MPU_CTRL (LPC40xx_MPU_BASE + 0x04U)
#define FLASH_NUM_SECTOR 30U
typedef struct iap_config {
uint32_t command;
uint32_t params[4];
} iap_config_s;
typedef struct __attribute__((aligned(4))) iap_frame {
uint16_t opcode;
iap_config_s config;
} iap_frame_s;
typedef struct lpc40xx_priv {
uint32_t mpu_ctrl_state;
uint32_t memmap_state;
} lpc40xx_priv_s;
static void lpc40xx_extended_reset(target_s *target);
static bool lpc40xx_enter_flash_mode(target_s *target);
static bool lpc40xx_exit_flash_mode(target_s *target);
static bool lpc40xx_mass_erase(target_s *target);
iap_status_e lpc40xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e cmd, ...);
static void lpc40xx_add_flash(target_s *target, uint32_t addr, size_t len, size_t erasesize, uint8_t base_sector)
{
lpc_flash_s *flash = lpc_add_flash(target, addr, len, IAP_PGM_CHUNKSIZE);
flash->f.blocksize = erasesize;
flash->base_sector = base_sector;
flash->f.write = lpc_flash_write_magic_vect;
flash->iap_entry = IAP_ENTRYPOINT;
flash->iap_ram = IAP_RAM_BASE;
flash->iap_msp = IAP_RAM_BASE + MIN_RAM_SIZE - RAM_USAGE_FOR_IAP_ROUTINES;
}
bool lpc40xx_probe(target_s *target)
{
if ((target->cpuid & CORTEX_CPUID_PARTNO_MASK) != CORTEX_M4)
return false;
target_halt_request(target);
lpc40xx_priv_s *priv = calloc(1, sizeof(*priv));
if (!priv) {
DEBUG_ERROR("calloc: failed in %s\n", __func__);
return false;
}
target->target_storage = priv;
lpc40xx_enter_flash_mode(target);
iap_result_s result;
lpc40xx_iap_call(target, &result, IAP_CMD_PARTID);
lpc40xx_exit_flash_mode(target);
target_halt_resume(target, false);
if (result.return_code) {
free(priv);
target->target_storage = NULL;
return false;
}
switch (result.values[0]) {
case 0x481d3f47U:
case 0x47193f47U:
case 0x47191f43U:
case 0x47011132U:
target->driver = "LPC40xx";
target->extended_reset = lpc40xx_extended_reset;
target->mass_erase = lpc40xx_mass_erase;
target->enter_flash_mode = lpc40xx_enter_flash_mode;
target->exit_flash_mode = lpc40xx_exit_flash_mode;
target_add_ram(target, 0x10000000U, 0x10000U);
target_add_ram(target, 0x2007c000U, 0x4000U);
target_add_ram(target, 0x20080000U, 0x4000U);
lpc40xx_add_flash(target, 0x00000000U, 0x10000U, 0x1000U, 0);
lpc40xx_add_flash(target, 0x00010000U, 0x70000U, 0x8000U, 16);
return true;
}
return false;
}
static bool lpc40xx_enter_flash_mode(target_s *const target)
{
lpc40xx_priv_s *const priv = (lpc40xx_priv_s *)target->target_storage;
priv->mpu_ctrl_state = target_mem_read32(target, LPC40xx_MPU_CTRL);
target_mem_write32(target, LPC40xx_MPU_CTRL, 0);
priv->memmap_state = target_mem_read32(target, LPC40xx_MEMMAP);
return true;
}
static bool lpc40xx_exit_flash_mode(target_s *const target)
{
const lpc40xx_priv_s *const priv = (lpc40xx_priv_s *)target->target_storage;
target_mem_write32(target, LPC40xx_MEMMAP, priv->memmap_state);
target_mem_write32(target, LPC40xx_MPU_CTRL, priv->mpu_ctrl_state);
return true;
}
static bool lpc40xx_mass_erase(target_s *target)
{
iap_result_s result;
lpc40xx_enter_flash_mode(target);
if (lpc40xx_iap_call(target, &result, IAP_CMD_PREPARE, 0, FLASH_NUM_SECTOR - 1U)) {
lpc40xx_exit_flash_mode(target);
DEBUG_ERROR("lpc40xx_cmd_erase: prepare failed %" PRIu32 "\n", result.return_code);
return false;
}
if (lpc40xx_iap_call(target, &result, IAP_CMD_ERASE, 0, FLASH_NUM_SECTOR - 1U, CPU_CLK_KHZ)) {
lpc40xx_exit_flash_mode(target);
DEBUG_ERROR("lpc40xx_cmd_erase: erase failed %" PRIu32 "\n", result.return_code);
return false;
}
if (lpc40xx_iap_call(target, &result, IAP_CMD_BLANKCHECK, 0, FLASH_NUM_SECTOR - 1U)) {
lpc40xx_exit_flash_mode(target);
DEBUG_ERROR("lpc40xx_cmd_erase: blankcheck failed %" PRIu32 "\n", result.return_code);
return false;
}
lpc40xx_exit_flash_mode(target);
tc_printf(target, "Erase OK.\n");
return true;
}
static void lpc40xx_extended_reset(target_s *target)
{
target_mem_write32(target, LPC40xx_MEMMAP, 1);
}
static size_t lpc40xx_iap_params(const iap_cmd_e cmd)
{
switch (cmd) {
case IAP_CMD_PREPARE:
case IAP_CMD_BLANKCHECK:
return 2U;
case IAP_CMD_ERASE:
return 3U;
default:
return 0U;
}
}
iap_status_e lpc40xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e cmd, ...)
{
iap_frame_s frame = {
.opcode = CORTEX_THUMB_BREAKPOINT,
{.command = cmd},
};
const size_t params_count = lpc40xx_iap_params(cmd);
va_list params;
va_start(params, cmd);
for (size_t i = 0; i < params_count; ++i)
frame.config.params[i] = va_arg(params, uint32_t);
va_end(params);
for (size_t i = params_count; i < 4; ++i)
frame.config.params[i] = 0U;
target_mem_write(target, IAP_RAM_BASE, &frame, sizeof(iap_frame_s));
const uint32_t iap_params_addr = IAP_RAM_BASE + offsetof(iap_frame_s, config);
uint32_t regs[CORTEXM_GENERAL_REG_COUNT];
target_regs_read(target, regs);
regs[0] = iap_params_addr;
regs[1] = iap_params_addr;
regs[CORTEX_REG_MSP] = IAP_RAM_BASE + MIN_RAM_SIZE;
regs[CORTEX_REG_LR] = IAP_RAM_BASE | 1;
regs[CORTEX_REG_PC] = IAP_ENTRYPOINT;
target_regs_write(target, regs);
platform_timeout_s timeout;
platform_timeout_set(&timeout, 500);
target_halt_resume(target, false);
while (!target_halt_poll(target, NULL)) {
if (cmd == IAP_CMD_ERASE)
target_print_progress(&timeout);
else if (cmd == IAP_CMD_PARTID && platform_timeout_is_expired(&timeout)) {
target_halt_request(target);
return IAP_STATUS_INVALID_COMMAND;
}
}
target_mem_read(target, result, iap_params_addr, sizeof(iap_result_s));
return result->return_code;
}