This is an automated email from Gerrit. "Jérôme Pouiller <[email protected]>" just uploaded a new patch set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/9398
-- gerrit commit fd3f8d869bfbd06d382ce92b545b04f126a40139 Author: Jérôme Pouiller <[email protected]> Date: Fri Jan 23 14:56:27 2026 +0100 flash/efr32-series2: Add support for Silabs Series 2 Add support for flashing Silicon Labs Series 2 targets. These targets are usually branded EFR32xG2x or EFM32PG2x (less common). Regarding the naming, the existing driver efm32.c supports in fact Silicon Labs Series 0 and 1. These chip are branded with various names beginning by either EFR32 or EFM32. Silabs prefers to identify the series with "Series 0", "Series 1" and "Series 2" excluding "EFR" or "EFM" in the name. However, since efm32.c was already here, I trying to keep a bit uniformity. Also note that support for Series 0, 1 and 2 may be merged with a bit of effort. However, since I didn't have the opportunity to test on Series 0/1, I didn't do this effort. Change-Id: Ia93aee45e11ed8eb4b43c3f410388cc5f4e22783 Co-developed-by: Peter Johanson <[email protected]> Signed-off-by: Peter Johanson <[email protected]> Signed-off-by: Jérôme Pouiller <[email protected]> diff --git a/jimtcl b/jimtcl index f160866171..a77ef1a621 160000 --- a/jimtcl +++ b/jimtcl @@ -1 +1 @@ -Subproject commit f160866171457474f7c4d6ccda70f9b77524407e +Subproject commit a77ef1a6218fad4c928ddbdc03c1aedc41007e70 diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index d95249c2ef..1065e62911 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -30,6 +30,7 @@ NOR_DRIVERS = \ %D%/dsp5680xx_flash.c \ %D%/dw-spi.c \ %D%/efm32.c \ + %D%/efr32-series2.c \ %D%/em357.c \ %D%/eneispif.c \ %D%/esirisc_flash.c \ diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index 19015554bd..221aa3c9a6 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -10,6 +10,7 @@ #include <helper/types.h> #include "imp.h" +extern const struct flash_driver efr32_series2_flash; /** * The list of built-in flash drivers. @@ -38,6 +39,7 @@ static const struct flash_driver * const flash_drivers[] = { &dsp5680xx_flash, &dw_spi_flash, &efm32_flash, + &efr32_series2_flash, &em357_flash, &eneispif_flash, &esirisc_flash, diff --git a/src/flash/nor/efr32-series2.c b/src/flash/nor/efr32-series2.c new file mode 100644 index 0000000000..7af5f46dd8 --- /dev/null +++ b/src/flash/nor/efr32-series2.c @@ -0,0 +1,1322 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/*************************************************************************** + * Copyright (C) 2005 by Dominic Rath * + * [email protected] * + * * + * Copyright (C) 2008 by Spencer Oliver * + * [email protected] * + * * + * Copyright (C) 2011 by Andreas Fritiofson * + * [email protected] * + * * + * Copyright (C) 2013 by Roman Dmitrienko * + * [email protected] * + * * + * Copyright (C) 2014 Nemui Trinomius * + * [email protected] * + * * + * Copyright (C) 2021 Michael Teichgräber * + * [email protected] * + * * + * Copyright (C) 2021 Doug Brunner * + * [email protected] * + * * + * Copyright (C) 2022 Mikrodust AB * + * [email protected] * + * * + * Copyright (c) 2026 Silicon Laboratories Inc. * + * [email protected] * + ***************************************************************************/ + +/* This driver manage Flash fond on Silicon Labs Series 2. Series 0 and 1 are + * managed by efm32.c. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" +#include <helper/binarybuffer.h> +#include <target/algorithm.h> +#include <target/armv7m.h> +#include <target/cortex_m.h> + +#define EFR32_FLASH_ERASE_TMO 100 +#define EFR32_FLASH_WDATAREADY_TMO 100 +#define EFR32_FLASH_WRITE_TMO 100 + +#define EFR32_FLASH_BASE_V1 0x00000000 +#define EFR32_FLASH_BASE_V2 0x08000000 + +#define LOCKWORDS_SZ 512 + +#define EFR32_MSC_INFO_BASE 0x0fe00000 + +#define EFR32_MSC_USER_DATA EFR32_MSC_INFO_BASE +#define EFR32_MSC_LOCK_BITS (EFR32_MSC_INFO_BASE + 0x4000) +#define EFR32_MSC_LOCK_BITS_EXTRA (EFR32_MSC_LOCK_BITS + LOCKWORDS_SZ) +#define EFR32_MSC_DEV_INFO (EFR32_MSC_INFO_BASE + 0x8000) + +#define EFR32_MSC_DI_PAGE_SIZE (EFR32_MSC_DEV_INFO + 0x008) +#define EFR32_MSC_DI_FLASH_SZ (EFR32_MSC_DEV_INFO + 0x00c) +#define EFR32_MSC_DI_RAM_SZ (EFR32_MSC_DEV_INFO + 0x00e) +#define EFR32_MSC_DI_PART_FAMILY (EFR32_MSC_DEV_INFO + 0x004) +#define EFR32_MSC_DI_LEGACY_FAMILY (EFR32_MSC_DEV_INFO + 0x1fe) +#define EFR32_MSC_DI_PROD_REV (EFR32_MSC_DEV_INFO + 0x002) + +#define EFR32_MSC_REGBASE 0x40030000 +#define EFR32_MSC_REGBASE_NS 0x50030000 + +#define EFR32_MSC_REG_WRITECTRL 0x00c +#define EFR32_MSC_WRITECTRL_WREN_MASK 0x1 +#define EFR32_MSC_REG_WRITECMD 0x010 +#define EFR32_MSC_WRITECMD_LADDRIM_MASK 0x1 +#define EFR32_MSC_WRITECMD_ERASEPAGE_MASK 0x2 +#define EFR32_MSC_WRITECMD_WRITEONCE_MASK 0x8 +#define EFR32_MSC_REG_ADDRB 0x014 +#define EFR32_MSC_REG_WDATA 0x018 +#define EFR32_MSC_REG_STATUS 0x01c +#define EFR32_MSC_STATUS_BUSY_MASK 0x1 +#define EFR32_MSC_STATUS_LOCKED_MASK 0x2 +#define EFR32_MSC_STATUS_INVADDR_MASK 0x4 +#define EFR32_MSC_STATUS_WDATAREADY_MASK 0x8 +#define EFR32_MSC_STATUS_ERASEABORTED_MASK 0x10 +#define EFR32_MSC_STATUS_PENDING_MASK 0x20 +#define EFR32_MSC_STATUS_WORDTIMEOUT_MASK 0x40 +#define EFR32_MSC_STATUS_RANGEPARTIAL_MASK 0x80 + +#define EFR32_MSC_REG_LOCK 0x03c +#define EFR32_MSC_LOCK_LOCKKEY 0x1b71 + +#define EFR32_CMU_REGBASE 0x40008000 +#define EFR32_CMU_REGBASE_NS 0x50008000 + +#define EFR32_CMU_REG_CLKEN1_SET 0x1068 + +#define EFR32_CMU_REG_CLKEN1_MSC_MSK_17 BIT(17) +#define EFR32_CMU_REG_CLKEN1_MSC_MSK_16 BIT(16) + +enum efr32_bank_index { + EFR32_BANK_INDEX_MAIN, + EFR32_BANK_INDEX_USER_DATA, + EFR32_BANK_INDEX_LOCK_BITS, + EFR32_N_BANKS +}; + +static int efr32_get_bank_index(target_addr_t base) +{ + switch (base) { + case EFR32_FLASH_BASE_V1: + case EFR32_FLASH_BASE_V2: + return EFR32_BANK_INDEX_MAIN; + case EFR32_MSC_USER_DATA: + return EFR32_BANK_INDEX_USER_DATA; + case EFR32_MSC_LOCK_BITS: + return EFR32_BANK_INDEX_LOCK_BITS; + default: + return ERROR_FAIL; + } +} + +struct efr32_family_data { + int family_id; + const char *name; + int series; + + /* Page size in bytes, or 0 to read from EFR32_MSC_DI_PAGE_SIZE */ + int page_size; + + /* MSC register base address, or 0 to use default */ + uint32_t msc_regbase; +}; + +struct efr32_info { + const struct efr32_family_data *family_data; + uint16_t flash_sz_kib; + uint16_t ram_sz_kib; + uint8_t legacy_family; + char part_family; + uint16_t dev_num_digits; + char dev_num_letter; + uint8_t part_family_num; + uint8_t prod_rev; + uint16_t page_size; +}; + +struct efr32_flash_chip { + struct efr32_info info; + bool probed[EFR32_N_BANKS]; + uint32_t lb_page[LOCKWORDS_SZ / 4]; + uint32_t reg_base; + uint32_t reg_lock; + uint32_t refcount; +}; + +static const struct efr32_family_data efr32_families[] = { + { 16, "EFR32MG1P Mighty", .series = 1 }, + { 17, "EFR32MG1B Mighty", .series = 1 }, + { 18, "EFR32MG1V Mighty", .series = 1 }, + { 19, "EFR32BG1P Blue", .series = 1 }, + { 20, "EFR32BG1B Blue", .series = 1 }, + { 21, "EFR32BG1V Blue", .series = 1 }, + { 25, "EFR32FG1P Flex", .series = 1 }, + { 26, "EFR32FG1B Flex", .series = 1 }, + { 27, "EFR32FG1V Flex", .series = 1 }, + { 28, "EFR32MG2P Mighty", .series = 1 }, + { 29, "EFR32MG2B Mighty", .series = 1 }, + { 30, "EFR32MG2V Mighty", .series = 1 }, + { 31, "EFR32BG12P Blue", .series = 1 }, + { 32, "EFR32BG12B Blue", .series = 1 }, + { 33, "EFR32BG12V Blue", .series = 1 }, + { 37, "EFR32FG12P Flex", .series = 1 }, + { 38, "EFR32FG12B Flex", .series = 1 }, + { 39, "EFR32FG12V Flex", .series = 1 }, + { 40, "EFR32MG13P Mighty", .series = 1 }, + { 41, "EFR32MG13B Mighty", .series = 1 }, + { 42, "EFR32MG13V Mighty", .series = 1 }, + { 43, "EFR32BG13P Blue", .series = 1 }, + { 44, "EFR32BG13B Blue", .series = 1 }, + { 45, "EFR32BG13V Blue", .series = 1 }, + { 46, "EFR32ZG13P Zen", .series = 1 }, + { 49, "EFR32FG13P Flex", .series = 1 }, + { 50, "EFR32FG13B Flex", .series = 1 }, + { 51, "EFR32FG13V Flex", .series = 1 }, + { 52, "EFR32MG14P Mighty", .series = 1 }, + { 53, "EFR32MG14B Mighty", .series = 1 }, + { 54, "EFR32MG14V Mighty", .series = 1 }, + { 55, "EFR32BG14P Blue", .series = 1 }, + { 56, "EFR32BG14B Blue", .series = 1 }, + { 57, "EFR32BG14V Blue", .series = 1 }, + { 58, "EFR32ZG14P Zen", .series = 1 }, + { 61, "EFR32FG14P Flex", .series = 1 }, + { 62, "EFR32FG14B Flex", .series = 1 }, + { 63, "EFR32FG14V Flex", .series = 1 }, + { 71, "EFR32G", .series = 0, .page_size = 512 }, + { 72, "EFR32GG Giant", .series = 0 }, + { 73, "EFR32TG Tiny", .series = 0, .page_size = 512 }, + { 74, "EFR32LG Leopard", .series = 0 }, + { 75, "EFR32WG Wonder", .series = 0 }, + { 76, "EFR32ZG Zero", .series = 0, .page_size = 1024 }, + { 77, "EFR32HG Happy", .series = 0, .page_size = 1024 }, + { 81, "EFR32PG1B Pearl", .series = 1 }, + { 83, "EFR32JG1B Jade", .series = 1 }, + { 85, "EFR32PG12B Pearl", .series = 1 }, + { 87, "EFR32JG12B Jade", .series = 1 }, + { 89, "EFR32PG13B Pearl", .series = 1 }, + { 91, "EFR32JG13B Jade", .series = 1 }, + { 100, "EFR32GG11B Giant", .series = 1, .msc_regbase = 0x40000000 }, + { 103, "EFR32TG11B Tiny", .series = 1, .msc_regbase = 0x40000000 }, + { 106, "EFR32GG12B Giant", .series = 1, .msc_regbase = 0x40000000 }, + { 120, "EZR32WG Wonder", .series = 0 }, + { 121, "EZR32LG Leopard", .series = 0 }, + { 122, "EZR32HG Happy", .series = 0, .page_size = 1024 }, + { 128, "EFR32/EFM32 Series-2", .series = 2 }, +}; + +const struct flash_driver efr32_series2_flash; + +static int efr32_priv_write(struct flash_bank *bank, const uint8_t *buffer, + uint32_t addr, uint32_t count); + +static int efr32_write_only_lockbits(struct flash_bank *bank); + +static int efr32_get_flash_size(struct flash_bank *bank, uint16_t *flash_sz) +{ + return target_read_u16(bank->target, EFR32_MSC_DI_FLASH_SZ, flash_sz); +} + +static int efr32_get_ram_size(struct flash_bank *bank, uint16_t *ram_sz) +{ + return target_read_u16(bank->target, EFR32_MSC_DI_RAM_SZ, ram_sz); +} + +static int efr32_get_legacy_family(struct flash_bank *bank, uint8_t *pfamily) +{ + return target_read_u8(bank->target, EFR32_MSC_DI_LEGACY_FAMILY, pfamily); +} + +static int efr32_get_part_info(struct flash_bank *bank, struct efr32_info *pinfo) +{ + int ret; + uint32_t part_info; + uint8_t fam; + uint16_t dev_num; + + ret = target_read_u32(bank->target, EFR32_MSC_DI_PART_FAMILY, &part_info); + if (ret != ERROR_OK) + return ret; + fam = (part_info >> 24) & 0x3F; + switch (fam) { + case 0: + pinfo->part_family = 'F'; + break; + case 1: + pinfo->part_family = 'M'; + break; + case 2: + pinfo->part_family = 'B'; + break; + case 3: + pinfo->part_family = 'Z'; + break; + case 5: + pinfo->part_family = 'P'; + break; + default: + LOG_ERROR("Unknown MCU family %d", fam); + return ERROR_FAIL; + } + + pinfo->part_family_num = (part_info >> 16) & 0xff; + + dev_num = part_info & 0xffff; + pinfo->dev_num_letter = 'A' + (dev_num / 1000); + pinfo->dev_num_digits = dev_num % 1000; + + return ERROR_OK; +} + +static int efr32_get_prod_rev(struct flash_bank *bank, uint8_t *prev) +{ + return target_read_u8(bank->target, EFR32_MSC_DI_PROD_REV, prev); +} + +static int efr32_read_reg_u32(struct flash_bank *bank, target_addr_t offset, + uint32_t *value) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + uint32_t base = efr32_info->reg_base; + + return target_read_u32(bank->target, base + offset, value); +} + +static int efr32_write_reg_u32(struct flash_bank *bank, target_addr_t offset, + uint32_t value) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + uint32_t base = efr32_info->reg_base; + + return target_write_u32(bank->target, base + offset, value); +} + +static int efr32_read_info(struct flash_bank *bank) +{ + int ret; + struct efr32_flash_chip *efr32_info = bank->driver_priv; + struct efr32_info *efr32_mcu_info = &efr32_info->info; + + memset(efr32_mcu_info, 0, sizeof(struct efr32_info)); + + ret = efr32_get_flash_size(bank, &efr32_mcu_info->flash_sz_kib); + if (ret != ERROR_OK) + return ret; + + ret = efr32_get_ram_size(bank, &efr32_mcu_info->ram_sz_kib); + if (ret != ERROR_OK) + return ret; + + ret = efr32_get_legacy_family(bank, &efr32_mcu_info->legacy_family); + if (ret != ERROR_OK) + return ret; + + ret = efr32_get_prod_rev(bank, &efr32_mcu_info->prod_rev); + if (ret != ERROR_OK) + return ret; + + for (size_t i = 0; i < ARRAY_SIZE(efr32_families); i++) { + if (efr32_families[i].family_id == efr32_mcu_info->legacy_family) + efr32_mcu_info->family_data = &efr32_families[i]; + } + + if (!efr32_mcu_info->family_data) { + LOG_ERROR("Unknown MCU family %d", efr32_mcu_info->legacy_family); + return ERROR_FAIL; + } + + switch (efr32_mcu_info->family_data->series) { + case 0: + LOG_ERROR("Series 0 MCU detected; use efm32 driver, not efr32-series2"); + break; + case 1: + LOG_ERROR("Series 1 MCU detected; use efm32 driver, not efr32-series2"); + break; + case 2: + efr32_info->reg_base = EFR32_MSC_REGBASE_NS; + efr32_info->reg_lock = EFR32_MSC_REG_LOCK; + ret = efr32_get_part_info(bank, efr32_mcu_info); + if (ret != ERROR_OK) + return ret; + break; + } + + if (efr32_mcu_info->family_data->msc_regbase != 0) + efr32_info->reg_base = efr32_mcu_info->family_data->msc_regbase; + + if (efr32_mcu_info->family_data->page_size != 0) { + efr32_mcu_info->page_size = efr32_mcu_info->family_data->page_size; + } else { + uint8_t pg_size = 0; + ret = target_read_u8(bank->target, EFR32_MSC_DI_PAGE_SIZE, + &pg_size); + if (ret != ERROR_OK) + return ret; + + efr32_mcu_info->page_size = BIT((pg_size + 10) & 0xff); + + if (efr32_mcu_info->page_size != 2048 && + efr32_mcu_info->page_size != 4096 && + efr32_mcu_info->page_size != 8192) { + LOG_ERROR("Invalid page size %u", efr32_mcu_info->page_size); + return ERROR_FAIL; + } + } + + return ERROR_OK; +} + +/* flash bank efr32-series2 <base> <size> 0 0 <target#> */ +FLASH_BANK_COMMAND_HANDLER(efr32_flash_bank_command) +{ + struct efr32_flash_chip *efr32_info = NULL; + struct flash_bank *bank_iter; + int bank_index; + + if (CMD_ARGC < 6) + return ERROR_COMMAND_SYNTAX_ERROR; + + bank_index = efr32_get_bank_index(bank->base); + if (bank_index < 0) { + LOG_ERROR("Flash bank with base address %" PRIx32 " is not supported", + (uint32_t)bank->base); + return ERROR_FAIL; + } + + /* look for an existing flash structure matching target */ + for (bank_iter = flash_bank_list(); bank_iter; bank_iter = bank_iter->next) { + if (bank_iter->driver == &efr32_series2_flash + && bank_iter->target == bank->target + && bank->driver_priv) { + efr32_info = bank->driver_priv; + break; + } + } + + if (!efr32_info) { + /* target not matched, make a new one */ + efr32_info = calloc(1, sizeof(struct efr32_flash_chip)); + + memset(efr32_info->lb_page, 0xff, LOCKWORDS_SZ); + } + + ++efr32_info->refcount; + bank->driver_priv = efr32_info; + + return ERROR_OK; +} + +/** + * Remove flash structure corresponding to this bank, if and only if it's not + * used by any others + */ +static void efr32_free_driver_priv(struct flash_bank *bank) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + + if (efr32_info) { + /* Use ref count to determine if it can be freed; scanning bank + * list doesn't work, because this function can be called after + * some banks in the list have been already destroyed. + */ + --efr32_info->refcount; + if (efr32_info->refcount == 0) { + free(efr32_info); + bank->driver_priv = NULL; + } + } +} + +/* set or reset given bits in a register */ +static int efr32_set_reg_bits(struct flash_bank *bank, uint32_t reg, + uint32_t bitmask, int set) +{ + int ret = 0; + uint32_t reg_val = 0; + + ret = efr32_read_reg_u32(bank, reg, ®_val); + if (ret != ERROR_OK) + return ret; + + if (set) + reg_val |= bitmask; + else + reg_val &= ~bitmask; + + return efr32_write_reg_u32(bank, reg, reg_val); +} + +static int efr32_set_wren(struct flash_bank *bank, int write_enable) +{ + return efr32_set_reg_bits(bank, EFR32_MSC_REG_WRITECTRL, + EFR32_MSC_WRITECTRL_WREN_MASK, write_enable); +} + +static int efr32_msc_lock(struct flash_bank *bank, int lock) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + return efr32_write_reg_u32(bank, efr32_info->reg_lock, + (lock ? 0 : EFR32_MSC_LOCK_LOCKKEY)); +} + +static int efr32_wait_status(struct flash_bank *bank, int timeout, + uint32_t wait_mask, int wait_for_set) +{ + int ret = 0; + uint32_t status = 0; + + while (1) { + ret = efr32_read_reg_u32(bank, EFR32_MSC_REG_STATUS, &status); + if (ret != ERROR_OK) + break; + + LOG_DEBUG("status: 0x%" PRIx32 "", status); + + if ((status & wait_mask) == 0 && wait_for_set == 0) + break; + else if ((status & wait_mask) != 0 && wait_for_set) + break; + + if (timeout-- <= 0) { + LOG_ERROR("timed out waiting for MSC status"); + return ERROR_FAIL; + } + + alive_sleep(1); + } + + if (status & EFR32_MSC_STATUS_ERASEABORTED_MASK) + LOG_WARNING("page erase was aborted"); + + return ret; +} + +static int efr32_erase_page(struct flash_bank *bank, uint32_t addr) +{ + /* this function DOES NOT set WREN; must be set already */ + /* 1. write address to ADDRB + 2. write LADDRIM + 3. check status (INVADDR, LOCKED) + 4. write ERASEPAGE + 5. wait until !STATUS_BUSY + */ + uint32_t status = 0; + int ret; + + LOG_DEBUG("erasing flash page at 0x%08" PRIx32, addr); + + ret = efr32_write_reg_u32(bank, EFR32_MSC_REG_ADDRB, addr); + if (ret != ERROR_OK) + return ret; + + ret = efr32_set_reg_bits(bank, EFR32_MSC_REG_WRITECMD, + EFR32_MSC_WRITECMD_LADDRIM_MASK, 1); + if (ret != ERROR_OK) + return ret; + + ret = efr32_read_reg_u32(bank, EFR32_MSC_REG_STATUS, &status); + if (ret != ERROR_OK) + return ret; + + LOG_DEBUG("status 0x%" PRIx32, status); + + if (status & EFR32_MSC_STATUS_LOCKED_MASK) { + LOG_ERROR("Page is locked"); + return ERROR_FAIL; + } else if (status & EFR32_MSC_STATUS_INVADDR_MASK) { + LOG_ERROR("Invalid address 0x%" PRIx32, addr); + return ERROR_FAIL; + } + + ret = efr32_set_reg_bits(bank, EFR32_MSC_REG_WRITECMD, + EFR32_MSC_WRITECMD_ERASEPAGE_MASK, 1); + if (ret != ERROR_OK) + return ret; + + return efr32_wait_status(bank, EFR32_FLASH_ERASE_TMO, + EFR32_MSC_STATUS_BUSY_MASK, 0); +} + +static int efr32_erase(struct flash_bank *bank, unsigned int first, + unsigned int last) +{ + struct target *target = bank->target; + int ret = 0; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + efr32_msc_lock(bank, 0); + ret = efr32_set_wren(bank, 1); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to enable MSC write"); + return ret; + } + + for (unsigned int i = first; i <= last; i++) { + ret = efr32_erase_page(bank, bank->base + bank->sectors[i].offset); + if (ret != ERROR_OK) + LOG_ERROR("Failed to erase page %d", i); + } + + ret = efr32_set_wren(bank, 0); + efr32_msc_lock(bank, 1); + if (ret != ERROR_OK) + return ret; + + if (bank->base == EFR32_MSC_LOCK_BITS) { + ret = efr32_write_only_lockbits(bank); + if (ret != ERROR_OK) + LOG_ERROR("Failed to restore lockbits after erase"); + } + + return ret; +} + +static int efr32_read_lock_data(struct flash_bank *bank) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + struct target *target = bank->target; + int data_size = 0; + uint32_t *ptr = NULL; + int ret = 0; + + assert(bank->num_sectors > 0); + + /* calculate the number of 32-bit words to read (one lock bit per sector) */ + data_size = (bank->num_sectors + 31) / 32; + + ptr = efr32_info->lb_page; + + for (int i = 0; i < data_size; i++, ptr++) { + ret = target_read_u32(target, EFR32_MSC_LOCK_BITS + i * 4, ptr); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read PLW %d", i); + return ret; + } + } + + /* also, read ULW, DLW, MLW, ALW and CLW words */ + + /* ULW, word 126 */ + ptr = efr32_info->lb_page + 126; + ret = target_read_u32(target, EFR32_MSC_LOCK_BITS + 126 * 4, ptr); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read ULW"); + return ret; + } + + /* DLW, word 127 */ + ptr = efr32_info->lb_page + 127; + ret = target_read_u32(target, EFR32_MSC_LOCK_BITS + 127 * 4, ptr); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read DLW"); + return ret; + } + + /* MLW, word 125, present in GG, LG, PG, JG, EFR32 */ + ptr = efr32_info->lb_page + 125; + ret = target_read_u32(target, EFR32_MSC_LOCK_BITS + 125 * 4, ptr); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read MLW"); + return ret; + } + + /* ALW, word 124, present in GG, LG, PG, JG, EFR32 */ + ptr = efr32_info->lb_page + 124; + ret = target_read_u32(target, EFR32_MSC_LOCK_BITS + 124 * 4, ptr); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read ALW"); + return ret; + } + + /* CLW1, word 123, present in EFR32 */ + ptr = efr32_info->lb_page + 123; + ret = target_read_u32(target, EFR32_MSC_LOCK_BITS + 123 * 4, ptr); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read CLW1"); + return ret; + } + + /* CLW0, word 122, present in GG, LG, PG, JG, EFR32 */ + ptr = efr32_info->lb_page + 122; + ret = target_read_u32(target, EFR32_MSC_LOCK_BITS + 122 * 4, ptr); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read CLW0"); + return ret; + } + + return ERROR_OK; +} + +static int efr32_write_only_lockbits(struct flash_bank *bank) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + + return efr32_priv_write(bank, (uint8_t *)efr32_info->lb_page, + EFR32_MSC_LOCK_BITS, LOCKWORDS_SZ); +} + +static int efr32_write_lock_data(struct flash_bank *bank) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + uint32_t extra_bytes = efr32_info->info.page_size - LOCKWORDS_SZ; + uint8_t *extra_data = NULL; + int ret = 0; + + /* Preserve any data written to the high portion of the lockbits page */ + assert(efr32_info->info.page_size >= LOCKWORDS_SZ); + if (extra_bytes) { + extra_data = malloc(extra_bytes); + ret = target_read_buffer(bank->target, + EFR32_MSC_LOCK_BITS_EXTRA, + extra_bytes, extra_data); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read extra contents of LB page"); + free(extra_data); + return ret; + } + } + + ret = efr32_erase_page(bank, EFR32_MSC_LOCK_BITS); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to erase LB page"); + if (extra_data) + free(extra_data); + return ret; + } + + if (extra_data) { + ret = efr32_priv_write(bank, extra_data, + EFR32_MSC_LOCK_BITS_EXTRA, + extra_bytes); + free(extra_data); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to restore extra contents of LB page"); + return ret; + } + } + + return efr32_write_only_lockbits(bank); +} + +static int efr32_get_page_lock(struct flash_bank *bank, size_t page) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + uint32_t dw = 0; + uint32_t mask = 0; + + switch (bank->base) { + case EFR32_FLASH_BASE_V1: + case EFR32_FLASH_BASE_V2: + dw = efr32_info->lb_page[page >> 5]; + mask = BIT(page & 0x1f); + break; + case EFR32_MSC_USER_DATA: + dw = efr32_info->lb_page[126]; + mask = BIT(0); + break; + case EFR32_MSC_LOCK_BITS: + dw = efr32_info->lb_page[126]; + mask = BIT(1); + break; + } + + return (dw & mask) ? 0 : 1; +} + +static int efr32_set_page_lock(struct flash_bank *bank, size_t page, int set) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + uint32_t *dw = &efr32_info->lb_page[page >> 5]; + uint32_t mask = 1 << (page & 0x1f); + + if (bank->base != EFR32_FLASH_BASE_V1 && bank->base != EFR32_FLASH_BASE_V2) { + LOG_ERROR("Locking user and lockbits pages is not supported yet"); + return ERROR_FAIL; + } + + if (!set) + *dw |= mask; + else + *dw &= ~mask; + + return ERROR_OK; +} + +static int efr32_protect(struct flash_bank *bank, int set, unsigned int first, + unsigned int last) +{ + struct target *target = bank->target; + int ret = 0; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + for (unsigned int i = first; i <= last; i++) { + ret = efr32_set_page_lock(bank, i, set); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to set lock on page %d", i); + return ret; + } + } + + ret = efr32_write_lock_data(bank); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to write LB page"); + return ret; + } + + return ERROR_OK; +} + +static int efr32_write_block(struct flash_bank *bank, const uint8_t *buf, + uint32_t address, uint32_t count) +{ + struct target *target = bank->target; + uint32_t buffer_size = 16384; + struct working_area *write_algorithm; + struct working_area *source; + struct reg_param reg_params[5]; + struct armv7m_algorithm armv7m_info; + struct efr32_flash_chip *efr32_info = bank->driver_priv; + int ret = ERROR_OK; + + /* see contrib/loaders/flash/efr32.S for src */ + static const uint8_t efr32_flash_write_code[] = { + /* #define EFR32_MSC_WRITECTRL_OFFSET 0x00c */ + /* #define EFR32_MSC_WRITECMD_OFFSET 0x010 */ + /* #define EFR32_MSC_ADDRB_OFFSET 0x014 */ + /* #define EFR32_MSC_WDATA_OFFSET 0x018 */ + /* #define EFR32_MSC_STATUS_OFFSET 0x01c */ + + 0x01, 0x26, /* movs r6, #1 */ + 0xc6, 0x60, /* str r6, [r0, #EFR32_MSC_WRITECTRL_OFFSET] */ + + /* wait_fifo: */ + 0x16, 0x68, /* ldr r6, [r2, #0] */ + 0x00, 0x2e, /* cmp r6, #0 */ + 0x22, 0xd0, /* beq.n 50 <exit> */ + 0x55, 0x68, /* ldr r5, [r2, #4] */ + 0xb5, 0x42, /* cmp r5, r6 */ + 0xf9, 0xd0, /* beq.n 4 <wait_fifo> */ + 0x44, 0x61, /* str r4, [r0, #EFR32_MSC_ADDRB_OFFSET] */ + 0x01, 0x26, /* movs r6, #1 */ + 0x06, 0x61, /* str r6, [r0, #EFR32_MSC_WRITECMD_OFFSET] */ + 0xc6, 0x69, /* ldr r6, [r0, #EFR32_MSC_STATUS_OFFSET] */ + 0x06, 0x27, /* movs r7, #6 */ + 0x3e, 0x42, /* tst r6, r7 */ + 0x16, 0xd1, /* bne.n 4c <error> */ + + /* wait_wdataready: */ + 0xc6, 0x69, /* ldr r6, [r0, #EFR32_MSC_STATUS_OFFSET] */ + 0x08, 0x27, /* movs r7, #8 */ + 0x3e, 0x42, /* tst r6, r7 */ + 0xfb, 0xd0, /* beq.n 1e <wait_wdataready> */ + 0x2e, 0x68, /* ldr r6, [r5, #0] */ + 0x86, 0x61, /* str r6, [r0, #EFR32_MSC_WDATA_OFFSET] */ + 0x08, 0x26, /* movs r6, #8 */ + 0x06, 0x61, /* str r6, [r0, #EFR32_MSC_WRITECMD_OFFSET] */ + 0x04, 0x35, /* adds r5, #4 */ + 0x04, 0x34, /* adds r4, #4 */ + + /* busy: */ + 0xc6, 0x69, /* ldr r6, [r0, #28] */ + 0x01, 0x27, /* movs r7, #1 */ + 0x3e, 0x42, /* tst r6, r7 */ + 0xfb, 0xd1, /* bne.n 32 <busy> */ + 0x9d, 0x42, /* cmp r5, r3 */ + 0x01, 0xd3, /* bcc.n 42 <no_wrap> */ + 0x15, 0x1c, /* adds r5, r2, #0 */ + 0x08, 0x35, /* adds r5, #8 */ + + /* no_wrap: */ + 0x55, 0x60, /* str r5, [r2, #4] */ + 0x01, 0x39, /* subs r1, #1 */ + 0x00, 0x29, /* cmp r1, #0 */ + 0x02, 0xd0, /* beq.n 50 <exit> */ + 0xdb, 0xe7, /* b.n 4 <wait_fifo> */ + + /* error: */ + 0x00, 0x20, /* movs r0, #0 */ + 0x50, 0x60, /* str r0, [r2, #4] */ + + /* exit: */ + 0x30, 0x1c, /* adds r0, r6, #0 */ + 0x00, 0xbe, /* bkpt 0x0000 */ + }; + + /* flash write code */ + if (target_alloc_working_area(target, sizeof(efr32_flash_write_code), + &write_algorithm) != ERROR_OK) { + LOG_WARNING("no working area available, can't do block memory writes"); + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + } + + ret = target_write_buffer(target, write_algorithm->address, + sizeof(efr32_flash_write_code), + efr32_flash_write_code); + if (ret != ERROR_OK) + return ret; + + /* memory buffer */ + while (target_alloc_working_area_try(target, buffer_size, &source) != ERROR_OK) { + buffer_size /= 2; + buffer_size &= ~3UL; /* Make sure it's 4 byte aligned */ + if (buffer_size <= 256) { + /* we already allocated the writing code, but failed to get a + * buffer, free the algorithm */ + target_free_working_area(target, write_algorithm); + + LOG_WARNING("no large enough working area available, can't do block memory writes"); + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + } + } + + init_reg_param(®_params[0], "r0", 32, PARAM_IN_OUT); /* flash base (in), status (out) */ + init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* count (word-32bit) */ + init_reg_param(®_params[2], "r2", 32, PARAM_OUT); /* buffer start */ + init_reg_param(®_params[3], "r3", 32, PARAM_OUT); /* buffer end */ + init_reg_param(®_params[4], "r4", 32, PARAM_IN_OUT); /* target address */ + + buf_set_u32(reg_params[0].value, 0, 32, efr32_info->reg_base); + buf_set_u32(reg_params[1].value, 0, 32, count); + buf_set_u32(reg_params[2].value, 0, 32, source->address); + buf_set_u32(reg_params[3].value, 0, 32, source->address + source->size); + buf_set_u32(reg_params[4].value, 0, 32, address); + + armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; + armv7m_info.core_mode = ARM_MODE_THREAD; + + ret = target_run_flash_async_algorithm(target, buf, count, 4, + 0, NULL, + 5, reg_params, + source->address, source->size, + write_algorithm->address, 0, + &armv7m_info); + + if (ret == ERROR_FLASH_OPERATION_FAILED) { + LOG_ERROR("flash write failed at address 0x%" PRIx32, + buf_get_u32(reg_params[4].value, 0, 32)); + + if (buf_get_u32(reg_params[0].value, 0, 32) & + EFR32_MSC_STATUS_LOCKED_MASK) { + LOG_ERROR("flash memory write protected"); + } + + if (buf_get_u32(reg_params[0].value, 0, 32) & + EFR32_MSC_STATUS_INVADDR_MASK) { + LOG_ERROR("invalid flash memory write address"); + } + } + + target_free_working_area(target, source); + target_free_working_area(target, write_algorithm); + + destroy_reg_param(®_params[0]); + destroy_reg_param(®_params[1]); + destroy_reg_param(®_params[2]); + destroy_reg_param(®_params[3]); + destroy_reg_param(®_params[4]); + + return ret; +} + +static int efr32_write_word(struct flash_bank *bank, uint32_t addr, + uint32_t val) +{ + /* this function DOES NOT set WREN; must be set already */ + /* 1. write address to ADDRB + 2. write LADDRIM + 3. check status (INVADDR, LOCKED) + 4. wait for WDATAREADY + 5. write data to WDATA + 6. write WRITECMD_WRITEONCE to WRITECMD + 7. wait until !STATUS_BUSY + */ + + /* FIXME: efr32G ref states (7.3.2) that writes should be + * performed twice per dword */ + + int ret = 0; + uint32_t status = 0; + + /* if not called, GDB errors will be reported during large writes */ + keep_alive(); + + ret = efr32_write_reg_u32(bank, EFR32_MSC_REG_ADDRB, addr); + if (ret != ERROR_OK) + return ret; + + ret = efr32_set_reg_bits(bank, EFR32_MSC_REG_WRITECMD, + EFR32_MSC_WRITECMD_LADDRIM_MASK, 1); + if (ret != ERROR_OK) + return ret; + + ret = efr32_read_reg_u32(bank, EFR32_MSC_REG_STATUS, &status); + if (ret != ERROR_OK) + return ret; + + LOG_DEBUG("status 0x%" PRIx32, status); + + if (status & EFR32_MSC_STATUS_LOCKED_MASK) { + LOG_ERROR("Page is locked"); + return ERROR_FAIL; + } else if (status & EFR32_MSC_STATUS_INVADDR_MASK) { + LOG_ERROR("Invalid address 0x%" PRIx32, addr); + return ERROR_FAIL; + } + + ret = efr32_wait_status(bank, EFR32_FLASH_WDATAREADY_TMO, + EFR32_MSC_STATUS_WDATAREADY_MASK, 1); + if (ret != ERROR_OK) { + LOG_ERROR("Wait for WDATAREADY failed"); + return ret; + } + + ret = efr32_write_reg_u32(bank, EFR32_MSC_REG_WDATA, val); + if (ret != ERROR_OK) { + LOG_ERROR("WDATA write failed"); + return ret; + } + + ret = efr32_write_reg_u32(bank, EFR32_MSC_REG_WRITECMD, + EFR32_MSC_WRITECMD_WRITEONCE_MASK); + if (ret != ERROR_OK) { + LOG_ERROR("WRITECMD write failed"); + return ret; + } + + ret = efr32_wait_status(bank, EFR32_FLASH_WRITE_TMO, + EFR32_MSC_STATUS_BUSY_MASK, 0); + if (ret != ERROR_OK) { + LOG_ERROR("Wait for BUSY failed"); + return ret; + } + + return ERROR_OK; +} + +static int efr32_priv_write(struct flash_bank *bank, const uint8_t *buffer, + uint32_t addr, uint32_t count) +{ + struct target *target = bank->target; + uint8_t *new_buffer = NULL; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (addr & 0x3) { + LOG_ERROR("addr 0x%" PRIx32 " breaks required 4-byte alignment", addr); + return ERROR_FLASH_DST_BREAKS_ALIGNMENT; + } + + if (count & 0x3) { + uint32_t old_count = count; + count = (old_count | 3) + 1; + new_buffer = malloc(count); + if (!new_buffer) { + LOG_ERROR("odd number of bytes to write and no memory for padding buffer"); + return ERROR_FAIL; + } + LOG_INFO("odd number of bytes to write (%" PRIu32 "), extending to %" + PRIu32 " and padding with 0xff", old_count, count); + memset(new_buffer, 0xff, count); + buffer = memcpy(new_buffer, buffer, old_count); + } + + uint32_t words_remaining = count / 4; + int retval, retval2; + + /* unlock flash registers */ + efr32_msc_lock(bank, 0); + retval = efr32_set_wren(bank, 1); + if (retval != ERROR_OK) + goto cleanup; + + /* try using a block write */ + retval = efr32_write_block(bank, buffer, addr, words_remaining); + + if (retval == ERROR_TARGET_RESOURCE_NOT_AVAILABLE) { + /* if block write failed (no sufficient working area), + * we use normal (slow) single word accesses */ + LOG_WARNING("couldn't use block writes, falling back to single memory accesses"); + + while (words_remaining > 0) { + uint32_t value; + memcpy(&value, buffer, sizeof(uint32_t)); + + retval = efr32_write_word(bank, addr, value); + if (retval != ERROR_OK) + goto reset_pg_and_lock; + + words_remaining--; + buffer += 4; + addr += 4; + } + } + +reset_pg_and_lock: + retval2 = efr32_set_wren(bank, 0); + efr32_msc_lock(bank, 1); + if (retval == ERROR_OK) + retval = retval2; + +cleanup: + free(new_buffer); + return retval; +} + +static int efr32_write(struct flash_bank *bank, const uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + if (bank->base == EFR32_MSC_LOCK_BITS && offset < LOCKWORDS_SZ) { + LOG_ERROR("Cannot write to lock words"); + return ERROR_FAIL; + } + return efr32_priv_write(bank, buffer, bank->base + offset, count); +} + +static int efr32_probe(struct flash_bank *bank) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + struct efr32_info *efr32_mcu_info = &efr32_info->info; + int ret; + uint32_t base_address = EFR32_FLASH_BASE_V2; + int bank_index = efr32_get_bank_index(bank->base); + + assert(bank_index >= 0); + + efr32_info->probed[bank_index] = false; + memset(efr32_info->lb_page, 0xff, LOCKWORDS_SZ); + + ret = efr32_read_info(bank); + if (ret != ERROR_OK) + return ret; + + if (efr32_mcu_info->part_family_num == 21 || + efr32_mcu_info->part_family_num == 22) + base_address = EFR32_FLASH_BASE_V1; + + LOG_INFO("detected part: %cG%d%c%03d, rev %d", + efr32_mcu_info->part_family, + efr32_mcu_info->part_family_num, + efr32_mcu_info->dev_num_letter, + efr32_mcu_info->dev_num_digits, + efr32_mcu_info->prod_rev); + LOG_INFO("flash size = %d KiB", efr32_mcu_info->flash_sz_kib); + LOG_INFO("flash page size = %d B", efr32_mcu_info->page_size); + + assert(efr32_mcu_info->page_size != 0); + + free(bank->sectors); + bank->sectors = NULL; + + /* enable MSC clock */ + uint32_t msc_clken; + + ret = target_read_u32(bank->target, + EFR32_CMU_REGBASE_NS + EFR32_CMU_REG_CLKEN1_SET, + &msc_clken); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read CMU register"); + return ret; + } + if (efr32_mcu_info->part_family_num == 21) { + msc_clken = 0; + } else if (efr32_mcu_info->part_family_num == 22 || + efr32_mcu_info->part_family_num == 27 || + efr32_mcu_info->part_family_num == 29) { + msc_clken = EFR32_CMU_REG_CLKEN1_MSC_MSK_17; + } else if (efr32_mcu_info->part_family_num == 23 || + efr32_mcu_info->part_family_num == 24 || + efr32_mcu_info->part_family_num == 25 || + efr32_mcu_info->part_family_num == 26 || + efr32_mcu_info->part_family_num == 28) { + msc_clken = EFR32_CMU_REG_CLKEN1_MSC_MSK_16; + } else { + msc_clken = EFR32_CMU_REG_CLKEN1_MSC_MSK_16; + LOG_WARNING("Don't know EFR/EFM Gx family number, can't set MSC register. Use default values.."); + } + ret = target_write_u32(bank->target, + EFR32_CMU_REGBASE_NS + EFR32_CMU_REG_CLKEN1_SET, + msc_clken); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to enable MSC clock"); + return ret; + } + + if (bank->base == base_address) { + bank->num_sectors = efr32_mcu_info->flash_sz_kib * 1024 / + efr32_mcu_info->page_size; + assert(bank->num_sectors > 0); + + ret = efr32_read_lock_data(bank); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read LB data"); + return ret; + } + } else { + bank->num_sectors = 1; + } + bank->size = bank->num_sectors * efr32_mcu_info->page_size; + bank->sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors); + + for (uint32_t i = 0; i < bank->num_sectors; i++) { + bank->sectors[i].offset = i * efr32_mcu_info->page_size; + bank->sectors[i].size = efr32_mcu_info->page_size; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = 1; + } + + efr32_info->probed[bank_index] = true; + + return ERROR_OK; +} + +static int efr32_auto_probe(struct flash_bank *bank) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + int bank_index = efr32_get_bank_index(bank->base); + + assert(bank_index >= 0); + + if (efr32_info->probed[bank_index]) + return ERROR_OK; + return efr32_probe(bank); +} + +static int efr32_protect_check(struct flash_bank *bank) +{ + struct target *target = bank->target; + int ret = 0; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + ret = efr32_read_lock_data(bank); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read LB data"); + return ret; + } + + assert(bank->sectors); + + for (unsigned int i = 0; i < bank->num_sectors; i++) + bank->sectors[i].is_protected = efr32_get_page_lock(bank, i); + + return ERROR_OK; +} + +static int efr32_get_info(struct flash_bank *bank, struct command_invocation *cmd) +{ + struct efr32_flash_chip *efr32_info = bank->driver_priv; + int ret; + + ret = efr32_read_info(bank); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to read efr32 info"); + return ret; + } + + command_print_sameline(cmd, "%cG%d%c%03d, rev %d", + efr32_info->info.part_family, + efr32_info->info.part_family_num, + efr32_info->info.dev_num_letter, + efr32_info->info.dev_num_digits, + efr32_info->info.prod_rev); + return ERROR_OK; +} + +COMMAND_HANDLER(efr32_handle_debuglock_command) +{ + struct efr32_flash_chip *efr32_info; + struct flash_bank *bank; + struct target *target; + uint32_t *ptr; + int ret; + + if (CMD_ARGC < 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + ret = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (ret != ERROR_OK) + return ret; + + efr32_info = bank->driver_priv; + target = bank->target; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + ptr = efr32_info->lb_page + 127; + *ptr = 0; + + ret = efr32_write_lock_data(bank); + if (ret != ERROR_OK) { + LOG_ERROR("Failed to write LB page"); + return ret; + } + + command_print(CMD, "efr32 debug interface locked, reset the device to apply"); + + return ERROR_OK; +} + +static const struct command_registration efr32_exec_command_handlers[] = { + { + .name = "debuglock", + .handler = efr32_handle_debuglock_command, + .mode = COMMAND_EXEC, + .usage = "bank_id", + .help = "Lock the debug interface of the device.", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration efr32_command_handlers[] = { + { + .name = "efr32-series2", + .mode = COMMAND_ANY, + .help = "Silicon Labs Series 2 (EFM32PG2x and EFR32xG2x) flash command group", + .usage = "", + .chain = efr32_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +const struct flash_driver efr32_series2_flash = { + .name = "efr32-series2", + .commands = efr32_command_handlers, + .flash_bank_command = efr32_flash_bank_command, + .erase = efr32_erase, + .protect = efr32_protect, + .write = efr32_write, + .read = default_flash_read, + .probe = efr32_probe, + .auto_probe = efr32_auto_probe, + .erase_check = default_flash_blank_check, + .protect_check = efr32_protect_check, + .info = efr32_get_info, + .free_driver_priv = efr32_free_driver_priv, +}; diff --git a/tcl/target/efr32-series2.cfg b/tcl/target/efr32-series2.cfg new file mode 100644 index 0000000000..59bcfcb0ad --- /dev/null +++ b/tcl/target/efr32-series2.cfg @@ -0,0 +1,277 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026 Silicon Laboratories Inc. +# +# Silicon Labs Series 2 targets. These target are usually branded as EFM32PG2x +# or EFR32xG2x (eg. EFM32PG28, EFR32MG27, ...). +# +# Silicon Labs Series 0 and 1 are managed by efm32.cfg + +source [find target/swj-dp.tcl] + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME efr32 +} + +# Work-area is a space in RAM used for flash programming. By default use 2kB +if { [info exists WORKAREASIZE] } { + set _WORKAREASIZE $WORKAREASIZE +} else { + set _WORKAREASIZE 0x800 +} + +if { [info exists CPUTAPID] } { + set _CPUTAPID $CPUTAPID +} else { + if { [using_jtag] } { + set _CPUTAPID 0x4ba00477 + } { + set _CPUTAPID 0x2ba01477 + } +} + +# Family group xg21 and xg22 has flash base address 0x00000000. The others has +# flash base address 0x08000000 +if { [info exists FLASHBASE] } { + set _FLASHBASE $FLASHBASE +} else { + set _FLASHBASE 0x08000000 +} + +swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID +dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu + +adapter speed 1000 + +set _TARGETNAME $_CHIPNAME.cpu +target create $_TARGETNAME cortex_m -dap $_CHIPNAME.dap + +$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 + +set _FLASHNAME $_CHIPNAME.flash + +flash bank $_FLASHNAME efr32-series2 $_FLASHBASE 0 0 0 $_TARGETNAME +flash bank userdata.flash efr32-series2 0x0FE00000 0 0 0 $_TARGETNAME + +if {![using_hla]} { + # if srst is not fitted use SYSRESETREQ to perform a soft reset + cortex_m reset_config sysresetreq + + $_TARGETNAME configure -event examine-fail efr32s2_dci_read_se_status +} + +proc efr32s2_dci_connect {} { + set target [target current] + set dap [$target cget -dap] + + # Read DP_IDCODE + set dp_id [$dap dpreg 0] + + if {$dp_id != 0x6BA02477} { + echo "Error: invalid DP ID for Silabs Series 2" + return + } + + poll off + + # Clear error and sticky flag conditions + # DP_ABORT = ORUNERRCLR | WDERRCLR STKERRCLR | STKCMPCLR + $dap dpreg 0 0x1E + + # DP_CTRL = CSYSPWRUPREQ | CDBGPWRUPREQ + $dap dpreg 0x04 0x50000000 + + # DP_SELECT for DCP AP register bank 0 + $dap dpreg 0x08 0x01000000 + + # Set CSW + $dap apreg 1 0x00 0x22000002 + + # Write AP_TAR = DCIID + $dap apreg 1 0x04 0x10FC + + # Read DP DRW + $dap dpreg 0x0C + + # Read AP RDBUFF + set DCIID [$dap apreg 1 0x0C] + if {$DCIID != 0xdc11d} { + echo "Failed to read correct DCIID" + } else { + echo "Successfully connected to DCI" + } +} + +proc efr32s2_dci_read_status {} { + set target [target current] + set dap [$target cget -dap] + + # AP_TAR = DCISTATUS + $dap apreg 1 0x04 0x1008 + + set dcistatus [$dap apreg 1 0x0C] + set dcistatus [$dap dpreg 0x0C] + + return $dcistatus +} + +proc efr32s2_dci_write_cmd { dci_write_word } { + set target [target current] + set dap [$target cget -dap] + + for {set i 0} {1} {incr i} { + if {$i >= 100} { + echo "$target DCI write timeout, unable to write command" + return + } + + set dcistatus [efr32s2_dci_read_status] + if {$dcistatus & 1} { + echo "DCI WPENDING bit set, retrying" + sleep 10 + continue + } + + if {$dcistatus & 0x100} { + echo "DCI RDATAVALID is set, can't write to DCIWRITE" + return + } + + # AP_TAR = DCIWDATA + $dap apreg 1 0x04 0x1000 + + # Write word + $dap apreg 1 0x0C $dci_write_word + return + } +} + +proc efr32s2_dci_read_response {} { + set target [target current] + set dap [$target cget -dap] + + for {set i 0} {1} {incr i} { + if {$i >= 100} { + echo "$target DCI read timeout" + return + } + + set dcistatus [efr32s2_dci_read_status] + + if {$dcistatus & 0x100} { + # AP_TAR = DCIRDATA + $dap apreg 1 0x04 0x1004 + + set dciread [$dap apreg 1 0x0C] + set dciread [$dap dpreg 0x0C] + return $dciread + } else { + echo "DCI RDATAVALID is not set, retrying" + sleep 10 + continue + } + } +} + +proc efr32s2_dci_device_erase {} { + efr32s2_dci_connect + efr32s2_dci_write_cmd 8 + efr32s2_dci_write_cmd 0x430F0000 + + sleep 2000 + echo "Device erase command sent. Device should now be erased and debug should be available again after a reset" + poll on +} + +proc efr32s2_dci_device_lock {} { + echo "Attempting to activate debug lock..." + efr32s2_dci_connect + efr32s2_dci_write_cmd 8 + efr32s2_dci_write_cmd 0x430C0000 + + set recvlen [efr32s2_dci_read_response] + + if {$recvlen & 0xFFFF0000} { + echo "command response was not OK, got $recvlen as command response. Can't continue." + poll on + return + } + + sleep 100 + + efr32s2_dci_read_se_status + poll on +} + +proc efr32s2_dci_read_se_status {} { + efr32s2_dci_connect + + # Write len + efr32s2_dci_write_cmd 8 + + # Write cmd + efr32s2_dci_write_cmd 0xFE010000 + + set recvlen [efr32s2_dci_read_response] + + if {$recvlen & 0xFFFF0000} { + echo "command response was not OK, got $recvlen as command response. Can't continue." + poll on + return + } + + set debuglock_idx 3 + + if {$recvlen == 0x28} { + set debuglock_idx 7 + } + + # decrement length word + set recvlen [expr {$recvlen - 4}] + + for {set i 0} {1} {incr i} { + if {$recvlen <= 0} { + break + } + + set recvlen [expr {$recvlen - 4}] + set sestatusarray($i) [efr32s2_dci_read_response] + } + poll on + + echo "DCI SESTATUS response:" + set sestatusarray + + set debuglock $sestatusarray($debuglock_idx) + + if {$debuglock & 0x01} { + echo "Debug lock (config): Enabled" + } else { + echo "Debug lock (config): Disabled" + } + + if {$debuglock & 0x02} { + echo "Device erase: Enabled" + } else { + echo "Device erase: Disabled" + } + + if {$debuglock & 0x04} { + echo "Secure debug: Enabled" + } else { + echo "Secure debug: Disabled" + } + + if {$debuglock & 0x20} { + echo "Debug lock (hw status): Enabled" + echo "\n" + echo " * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *" + echo " You will not be able to communicate with this device" + echo " unless you perform a device erase (if available, indicated above)!" + echo " Try efr32s2_dci_device_erase to attempt erase." + echo " * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n\n" + } else { + echo "Debug lock (hw status): Disabled" + } +} --
