This is an automated email from Gerrit. Michael Jung (mij...@gmx.net) just uploaded a new patch set to Gerrit, which you can find at http://openocd.zylin.com/5344
-- gerrit commit b480bac4d0ee819f1dd1044e9fd4b95a77632d04 Author: Michael Jung <mij...@gmx.net> Date: Fri Nov 15 19:33:25 2019 +0100 stm32l5x: added support for STM32L5 devices - Based on the stm32l4x code. - Tested with flash programming via gdb 'load' and single stepping Change-Id: I706c51fe73734475a44be9c77515dbfb58ed04d7 Signed-off-by: Michael Jung <mij...@gmx.net> diff --git a/contrib/loaders/flash/stm32/Makefile b/contrib/loaders/flash/stm32/Makefile index b58b412..46d265a 100644 --- a/contrib/loaders/flash/stm32/Makefile +++ b/contrib/loaders/flash/stm32/Makefile @@ -8,7 +8,7 @@ OBJDUMP=$(CROSS_COMPILE)objdump CFLAGS = -static -nostartfiles -mlittle-endian -Wa,-EL -all: stm32f1x.inc stm32f2x.inc stm32h7x.inc stm32l4x.inc stm32lx.inc +all: stm32f1x.inc stm32f2x.inc stm32h7x.inc stm32l4x.inc stm32l5x.inc stm32lx.inc .PHONY: clean diff --git a/contrib/loaders/flash/stm32/stm32l5x.S b/contrib/loaders/flash/stm32/stm32l5x.S new file mode 100644 index 0000000..d0c2f9d --- /dev/null +++ b/contrib/loaders/flash/stm32/stm32l5x.S @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (C) 2010 by Spencer Oliver * + * s...@spen-soft.co.uk * + * * + * Copyright (C) 2011 Øyvind Harboe * + * oyvind.har...@zylin.com * + * * + * Copyright (C) 2015 Uwe Bonnes * + * b...@elektron.ikp.physik.tu-darmstadt.de * + * * + * Copyright (C) 2019 Michael Jung * + * mij...@gmx.net * + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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. * + ***************************************************************************/ + + .text + .syntax unified + .cpu cortex-m33 + .thumb + +/* + * Params : + * r0 = workarea start, status (out) + * r1 = workarea end + * r2 = target address + * r3 = count (64bit words) + * r4 = flash base + * + * Clobbered: + * r5 - rp + * r6/7 - temp (64-bit) + * r8 - wp, tmp + */ + +#define STM32_FLASH_NSCR_OFFSET 0x28 /* offset of NSCR register in FLASH struct */ +#define STM32_FLASH_NSSR_OFFSET 0x20 /* offset of NSSR register in FLASH struct */ + +#define STM32_PROG 0x1 /* PG */ + + .thumb_func + .global _start +_start: +wait_fifo: + ldr r8, [r0, #0] /* read wp */ + cmp r8, #0 /* abort if wp == 0 */ + beq exit + ldr r5, [r0, #4] /* read rp */ + subs r6, r8, r5 /* number of bytes available for read in r6*/ + itt mi /* if wrapped around*/ + addmi r6, r1 /* add size of buffer */ + submi r6, r0 + cmp r6, #8 /* wait until 8 bytes are available */ + bcc wait_fifo + + ldr r6, =STM32_PROG + str r6, [r4, #STM32_FLASH_NSCR_OFFSET] + ldrd r6, [r5], #0x08 /* read one word from src, increment ptr */ + strd r6, [r2], #0x08 /* write one word to dst, increment ptr */ + dsb +busy: + ldr r6, [r4, #STM32_FLASH_NSSR_OFFSET] + tst r6, #0x10000 /* BSY (bit16) == 1 => operation in progress */ + bne busy /* wait more... */ + tst r6, #0xfa /* PGSERR | PGPERR | PGAERR | WRPERR | PROGERR*/ + bne error /* fail... */ + + cmp r5, r1 /* wrap rp at end of buffer */ + it cs + addcs r5, r0, #8 /* skip loader args */ + str r5, [r0, #4] /* store rp */ + subs r3, r3, #1 /* decrement dword count */ + cbz r3, exit /* loop if not done */ + b wait_fifo +error: + movs r1, #0 + str r1, [r0, #4] /* set rp = 0 on error */ +exit: + mov r0, r6 /* return status in r0 */ + bkpt #0x00 + + .pool diff --git a/contrib/loaders/flash/stm32/stm32l5x.inc b/contrib/loaders/flash/stm32/stm32l5x.inc new file mode 100644 index 0000000..a200565 --- /dev/null +++ b/contrib/loaders/flash/stm32/stm32l5x.inc @@ -0,0 +1,7 @@ +/* Autogenerated with ../../../../src/helper/bin2char.sh */ +0xd0,0xf8,0x00,0x80,0xb8,0xf1,0x00,0x0f,0x21,0xd0,0x45,0x68,0xb8,0xeb,0x05,0x06, +0x44,0xbf,0x76,0x18,0x36,0x1a,0x08,0x2e,0xf2,0xd3,0x4f,0xf0,0x01,0x06,0xa6,0x62, +0xf5,0xe8,0x02,0x67,0xe2,0xe8,0x02,0x67,0xbf,0xf3,0x4f,0x8f,0x26,0x6a,0x16,0xf4, +0x80,0x3f,0xfb,0xd1,0x16,0xf0,0xfa,0x0f,0x07,0xd1,0x8d,0x42,0x28,0xbf,0x00,0xf1, +0x08,0x05,0x45,0x60,0x01,0x3b,0x13,0xb1,0xda,0xe7,0x00,0x21,0x41,0x60,0x30,0x46, +0x00,0xbe, diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index 34f91ce..d264552 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -59,6 +59,7 @@ NOR_DRIVERS = \ %D%/stm32f2x.c \ %D%/stm32lx.c \ %D%/stm32l4x.c \ + %D%/stm32l5x.c \ %D%/stm32h7x.c \ %D%/str7x.c \ %D%/str9x.c \ diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index 551f389..35a3f15 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -72,6 +72,7 @@ extern const struct flash_driver stm32f1x_flash; extern const struct flash_driver stm32f2x_flash; extern const struct flash_driver stm32lx_flash; extern const struct flash_driver stm32l4x_flash; +extern const struct flash_driver stm32l5x_flash; extern const struct flash_driver stm32h7x_flash; extern const struct flash_driver stmsmi_flash; extern const struct flash_driver str7x_flash; @@ -142,6 +143,7 @@ static const struct flash_driver * const flash_drivers[] = { &stm32f2x_flash, &stm32lx_flash, &stm32l4x_flash, + &stm32l5x_flash, &stm32h7x_flash, &stmsmi_flash, &str7x_flash, diff --git a/src/flash/nor/stm32l5x.c b/src/flash/nor/stm32l5x.c new file mode 100644 index 0000000..923c887 --- /dev/null +++ b/src/flash/nor/stm32l5x.c @@ -0,0 +1,1087 @@ +/*************************************************************************** + * Copyright (C) 2015 by Uwe Bonnes * + * b...@elektron.ikp.physik.tu-darmstadt.de * + * * + * Copyright (C) 2019 by Michael Jung * + * mij...@gmx.net * + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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, see <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" +#include <helper/binarybuffer.h> +#include <target/algorithm.h> +#include <target/armv7m.h> + +/* STM32L562 series for reference. + * + * RM0428 (STM32L552xx/STM32L562xx) + * http://www.st.com/resource/en/reference_manual/dm00346336.pdf + */ + +/* Erase time can be as high as 25ms, 10x this and assume it's toast... */ + +#define FLASH_ERASE_TIMEOUT 250 + +#define STM32_FLASH_BASE 0x40022000 +#define STM32_FLASH_ACR 0x40022000 +#define STM32_FLASH_NSKEYR 0x40022008 +#define STM32_FLASH_OPTKEYR 0x40022010 +#define STM32_FLASH_NSSR 0x40022020 +#define STM32_FLASH_NSCR 0x40022028 +#define STM32_FLASH_OPTR 0x40022040 +#define STM32_FLASH_WRP1AR 0x40022058 +#define STM32_FLASH_WRP1BR 0x4002205c +#define STM32_FLASH_WRP2AR 0x40022068 +#define STM32_FLASH_WRP2BR 0x4002206c + +/* FLASH_NSCR register bits */ + +#define FLASH_NSPG (1 << 0) +#define FLASH_NSPER (1 << 1) +#define FLASH_NSMER1 (1 << 2) +#define FLASH_PAGE_SHIFT 3 +#define FLASH_CR_NSBKER (1 << 11) +#define FLASH_NSMER2 (1 << 15) +#define FLASH_NSSTRT (1 << 16) +#define FLASH_NSOPTSTRT (1 << 17) +#define FLASH_NSEOPIE (1 << 24) +#define FLASH_NSERRIE (1 << 25) +#define FLASH_OBL_LAUNCH (1 << 27) +#define FLASH_OPTLOCK (1 << 30) +#define FLASH_NSLOCK (1 << 31) + +/* FLASH_NSSR register bits */ + +#define FLASH_NSBSY (1 << 16) +/* Fast programming not used => related errors not used*/ +#define FLASH_NSPGSERR (1 << 7) /* Programming sequence error */ +#define FLASH_NSSIZERR (1 << 6) /* Size error */ +#define FLASH_NSPGAERR (1 << 5) /* Programming alignment error */ +#define FLASH_NSWRPERR (1 << 4) /* Write protection error */ +#define FLASH_NSPROGERR (1 << 3) /* Programming error */ +#define FLASH_NSOPERR (1 << 1) /* Operation error */ +#define FLASH_NSEOP (1 << 0) /* End of operation */ + +#define FLASH_ERROR (FLASH_NSPGSERR | FLASH_NSSIZERR | FLASH_NSPGAERR | FLASH_NSWRPERR | FLASH_NSOPERR) + +/* STM32_FLASH_OBR bit definitions (reading) */ + +#define OPT_DBANK_C_256K (1 << 21) /* dual bank for devices with 256 KiB flash */ +#define OPT_DBANK_E_512K (1 << 22) /* dual bank for devices with 512 KiB flash */ + +/* register unlock keys */ + +#define KEY1 0x45670123 +#define KEY2 0xCDEF89AB + +/* option register unlock key */ +#define OPTKEY1 0x08192A3B +#define OPTKEY2 0x4C5D6E7F + +#define RDP_LEVEL_0 0xAA +#define RDP_LEVEL_1 0xBB +#define RDP_LEVEL_2 0xCC + + +/* other registers */ +#define DBGMCU_IDCODE 0xE0044000 +#define FLASH_SIZE_REG 0x0BFA05E0 + +/* Power control macro (PWR) */ + +#define STM32_PWR_BASE 0x40007000 +#define STM32_PWR_CR1 0x40007000 +#define STM32_PWR_SR2 0x40007014 + +/* PWR_CR1 register bits */ + +#define PWR_CR1_VOS_MASK 0x00000600 +#define PWR_CR1_VOS_RANGE_0 0x00000000 +#define PWR_CR1_VOS_RANGE_1 0x00000200 +#define PWR_CR1_VOS_RANGE_2 0x00000400 + +#define PWR_SR2_VOSF 0x00000400 + +/* Reset and Clock Control macro (RCC) */ + +#define STM32_RCC_BASE 0x40021000 +#define STM32_RCC_APB1ENR1 0x40021058 + +/* RCC_APB1ENR1 register bits */ + +#define RCC_APB1ENR1_PWREN 0x10000000 + +struct stm32l5_flash_bank { + uint16_t bank2_start; + int probed; +}; + +/* flash bank stm32l5x <base> <size> 0 0 <target#> + */ +FLASH_BANK_COMMAND_HANDLER(stm32l5_flash_bank_command) +{ + struct stm32l5_flash_bank *stm32l5_info; + + if (CMD_ARGC < 6) + return ERROR_COMMAND_SYNTAX_ERROR; + + stm32l5_info = malloc(sizeof(struct stm32l5_flash_bank)); + if (!stm32l5_info) + return ERROR_FAIL; /* Checkme: What better error to use?*/ + bank->driver_priv = stm32l5_info; + + stm32l5_info->probed = 0; + + return ERROR_OK; +} + +static inline int stm32l5_get_flash_reg(struct flash_bank *bank, uint32_t reg) +{ + return reg; +} + +static inline int stm32l5_get_flash_status(struct flash_bank *bank, uint32_t *status) +{ + struct target *target = bank->target; + return target_read_u32( + target, stm32l5_get_flash_reg(bank, STM32_FLASH_NSSR), status); +} + +static int stm32l5_wait_status_busy(struct flash_bank *bank, int timeout) +{ + struct target *target = bank->target; + uint32_t status; + int retval = ERROR_OK; + + /* wait for busy to clear */ + for (;;) { + retval = stm32l5_get_flash_status(bank, &status); + if (retval != ERROR_OK) + return retval; + LOG_DEBUG("status: 0x%" PRIx32 "", status); + if ((status & FLASH_NSBSY) == 0) + break; + if (timeout-- <= 0) { + LOG_ERROR("timed out waiting for flash"); + return ERROR_FAIL; + } + alive_sleep(1); + } + + + if (status & FLASH_NSWRPERR) { + LOG_ERROR("stm32x device protected"); + retval = ERROR_FAIL; + } + + /* Clear but report errors */ + if (status & FLASH_ERROR) { + if (retval == ERROR_OK) + retval = ERROR_FAIL; + /* If this operation fails, we ignore it and report the original + * retval + */ + target_write_u32(target, stm32l5_get_flash_reg(bank, STM32_FLASH_NSSR), + status & FLASH_ERROR); + } + return retval; +} + +static int stm32l5_unlock_reg(struct target *target) +{ + uint32_t ctrl; + + /* first check if not already unlocked + * otherwise writing on STM32_FLASH_NSKEYR will fail + */ + int retval = target_read_u32(target, STM32_FLASH_NSCR, &ctrl); + if (retval != ERROR_OK) + return retval; + + if ((ctrl & FLASH_NSLOCK) == 0) + return ERROR_OK; + + /* Flash write and erase operations are possible only in medium or + * high performance voltage ranges (range 1 or 0, respectively). Per + * default, devices come up in range 2 (low-power range). Thus, ensure + * we are running in an appropriate voltage range. + */ + + retval = target_read_u32(target, STM32_PWR_CR1, &ctrl); + if (retval != ERROR_OK) + return retval; + + if (((ctrl & PWR_CR1_VOS_MASK) != PWR_CR1_VOS_RANGE_0) && + ((ctrl & PWR_CR1_VOS_MASK) != PWR_CR1_VOS_RANGE_1)) { + + uint32_t enable; + uint32_t status; + int i; + + /* The device has to be reconfigured to run in voltage range 1. + * To be able to do this, the peripheral clock of the PWR macro + * has to be enabled first. + */ + + retval = target_read_u32(target, STM32_RCC_APB1ENR1, &enable); + if (retval != ERROR_OK) + return retval; + + enable |= RCC_APB1ENR1_PWREN; + + retval = target_write_u32(target, STM32_RCC_APB1ENR1, enable); + if (retval != ERROR_OK) + return retval; + + /* Now switch to voltage range 1 ... */ + + ctrl = (ctrl & ~PWR_CR1_VOS_MASK) | PWR_CR1_VOS_RANGE_1; + + retval = target_write_u32(target, STM32_PWR_CR1, ctrl); + if (retval != ERROR_OK) + return retval; + + /* ... and wait for the power controller to complete */ + + for (i = 0; i < 100; i++) { + retval = target_read_u32(target, STM32_PWR_SR2, + &status); + if (retval != ERROR_OK) + return retval; + + if (!(status & PWR_SR2_VOSF)) + break; + } + + if (status & PWR_SR2_VOSF) { + LOG_ERROR("Dynamic regulator failed to enter selected " + "voltage range. PWR_SR2: 0x%08" PRIx32, + status); + return ERROR_TARGET_FAILURE; + } + + retval = target_read_u32(target, STM32_PWR_CR1, &ctrl); + if (retval != ERROR_OK) + return retval; + + if ((ctrl & PWR_CR1_VOS_MASK) != PWR_CR1_VOS_RANGE_1) { + LOG_ERROR("Dynamic regulator still not in voltage " + "range 1. PWR_CR1: 0x%08" PRIx32, ctrl); + return ERROR_TARGET_FAILURE; + } + } + + /* unlock flash registers */ + retval = target_write_u32(target, STM32_FLASH_NSKEYR, KEY1); + if (retval != ERROR_OK) + return retval; + + retval = target_write_u32(target, STM32_FLASH_NSKEYR, KEY2); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u32(target, STM32_FLASH_NSCR, &ctrl); + if (retval != ERROR_OK) + return retval; + + if (ctrl & FLASH_NSLOCK) { + LOG_ERROR("flash not unlocked STM32_FLASH_CR: %" PRIx32, ctrl); + return ERROR_TARGET_FAILURE; + } + + return ERROR_OK; +} + +static int stm32l5_unlock_option_reg(struct target *target) +{ + uint32_t ctrl; + + int retval = target_read_u32(target, STM32_FLASH_NSCR, &ctrl); + if (retval != ERROR_OK) + return retval; + + if ((ctrl & FLASH_OPTLOCK) == 0) + return ERROR_OK; + + /* unlock option registers */ + retval = target_write_u32(target, STM32_FLASH_OPTKEYR, OPTKEY1); + if (retval != ERROR_OK) + return retval; + + retval = target_write_u32(target, STM32_FLASH_OPTKEYR, OPTKEY2); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u32(target, STM32_FLASH_NSCR, &ctrl); + if (retval != ERROR_OK) + return retval; + + if (ctrl & FLASH_OPTLOCK) { + LOG_ERROR("options not unlocked STM32_FLASH_CR: %" PRIx32, ctrl); + return ERROR_TARGET_FAILURE; + } + + return ERROR_OK; +} + +static int stm32l5_read_option(struct flash_bank *bank, uint32_t address, uint32_t* value) +{ + struct target *target = bank->target; + return target_read_u32(target, address, value); +} + +static int stm32l5_write_option(struct flash_bank *bank, uint32_t address, uint32_t value, uint32_t mask) +{ + struct target *target = bank->target; + uint32_t optiondata; + + int retval = target_read_u32(target, address, &optiondata); + if (retval != ERROR_OK) + return retval; + + retval = stm32l5_unlock_reg(target); + if (retval != ERROR_OK) + return retval; + + retval = stm32l5_unlock_option_reg(target); + if (retval != ERROR_OK) + return retval; + + optiondata = (optiondata & ~mask) | (value & mask); + + retval = target_write_u32(target, address, optiondata); + if (retval != ERROR_OK) + return retval; + + retval = target_write_u32(target, stm32l5_get_flash_reg(bank, STM32_FLASH_NSCR), FLASH_NSOPTSTRT); + if (retval != ERROR_OK) + return retval; + + retval = stm32l5_wait_status_busy(bank, FLASH_ERASE_TIMEOUT); + if (retval != ERROR_OK) + return retval; + + return retval; +} + +static int stm32l5_protect_check(struct flash_bank *bank) +{ + struct stm32l5_flash_bank *stm32l5_info = bank->driver_priv; + uint32_t wrp1ar, wrp1br, wrp2ar, wrp2br; + stm32l5_read_option(bank, STM32_FLASH_WRP1AR, &wrp1ar); + stm32l5_read_option(bank, STM32_FLASH_WRP1BR, &wrp1br); + stm32l5_read_option(bank, STM32_FLASH_WRP2AR, &wrp2ar); + stm32l5_read_option(bank, STM32_FLASH_WRP2BR, &wrp2br); + + const uint8_t wrp1a_start = wrp1ar & 0xFF; + const uint8_t wrp1a_end = (wrp1ar >> 16) & 0xFF; + const uint8_t wrp1b_start = wrp1br & 0xFF; + const uint8_t wrp1b_end = (wrp1br >> 16) & 0xFF; + const uint8_t wrp2a_start = wrp2ar & 0xFF; + const uint8_t wrp2a_end = (wrp2ar >> 16) & 0xFF; + const uint8_t wrp2b_start = wrp2br & 0xFF; + const uint8_t wrp2b_end = (wrp2br >> 16) & 0xFF; + + for (int i = 0; i < bank->num_sectors; i++) { + if (i < stm32l5_info->bank2_start) { + if (((i >= wrp1a_start) && + (i <= wrp1a_end)) || + ((i >= wrp1b_start) && + (i <= wrp1b_end))) + bank->sectors[i].is_protected = 1; + else + bank->sectors[i].is_protected = 0; + } else { + uint8_t snb; + snb = i - stm32l5_info->bank2_start; + if (((snb >= wrp2a_start) && + (snb <= wrp2a_end)) || + ((snb >= wrp2b_start) && + (snb <= wrp2b_end))) + bank->sectors[i].is_protected = 1; + else + bank->sectors[i].is_protected = 0; + } + } + return ERROR_OK; +} + +static int stm32l5_erase(struct flash_bank *bank, int first, int last) +{ + struct target *target = bank->target; + int i; + + assert(first < bank->num_sectors); + assert(last < bank->num_sectors); + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + int retval; + retval = stm32l5_unlock_reg(target); + if (retval != ERROR_OK) + return retval; + + /* + Sector Erase + To erase a sector, follow the procedure below: + 1. Check that no Flash memory operation is ongoing by + checking the BSY bit in the FLASH_SR register + 2. Set the PER bit and select the page and bank + you wish to erase in the FLASH_CR register + 3. Set the STRT bit in the FLASH_CR register + 4. Wait for the BSY bit to be cleared + */ + struct stm32l5_flash_bank *stm32l5_info = bank->driver_priv; + + for (i = first; i <= last; i++) { + uint32_t erase_flags; + erase_flags = FLASH_NSPER | FLASH_NSSTRT; + + if (i >= stm32l5_info->bank2_start) { + uint8_t snb; + snb = i - stm32l5_info->bank2_start; + erase_flags |= snb << FLASH_PAGE_SHIFT | FLASH_CR_NSBKER; + } else + erase_flags |= i << FLASH_PAGE_SHIFT; + retval = target_write_u32(target, + stm32l5_get_flash_reg(bank, STM32_FLASH_NSCR), erase_flags); + if (retval != ERROR_OK) + return retval; + + retval = stm32l5_wait_status_busy(bank, FLASH_ERASE_TIMEOUT); + if (retval != ERROR_OK) + return retval; + + bank->sectors[i].is_erased = 1; + } + + retval = target_write_u32( + target, stm32l5_get_flash_reg(bank, STM32_FLASH_NSCR), FLASH_NSLOCK); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int stm32l5_protect(struct flash_bank *bank, int set, int first, int last) +{ + struct target *target = bank->target; + struct stm32l5_flash_bank *stm32l5_info = bank->driver_priv; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + int ret = ERROR_OK; + /* Bank 2 */ + uint32_t reg_value = 0xFF; /* Default to bank un-protected */ + if (last >= stm32l5_info->bank2_start) { + if (set == 1) { + uint8_t begin = first > stm32l5_info->bank2_start ? first : 0x00; + reg_value = ((last & 0xFF) << 16) | begin; + } + + ret = stm32l5_write_option(bank, STM32_FLASH_WRP2AR, reg_value, 0xffffffff); + } + /* Bank 1 */ + reg_value = 0xFF; /* Default to bank un-protected */ + if (first < stm32l5_info->bank2_start) { + if (set == 1) { + uint8_t end = last >= stm32l5_info->bank2_start ? 0xFF : last; + reg_value = (end << 16) | (first & 0xFF); + } + + ret = stm32l5_write_option(bank, STM32_FLASH_WRP1AR, reg_value, 0xffffffff); + } + + return ret; +} + +/* Count is in halfwords */ +static int stm32l5_write_block(struct flash_bank *bank, const uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + struct target *target = bank->target; + uint32_t buffer_size = 16384; + struct working_area *write_algorithm; + struct working_area *source; + uint32_t address = bank->base + offset; + struct reg_param reg_params[5]; + struct armv7m_algorithm armv7m_info; + int retval = ERROR_OK; + + static const uint8_t stm32l5_flash_write_code[] = { +#include "../../../contrib/loaders/flash/stm32/stm32l5x.inc" + }; + + if (target_alloc_working_area(target, sizeof(stm32l5_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; + } + + retval = target_write_buffer(target, write_algorithm->address, + sizeof(stm32l5_flash_write_code), + stm32l5_flash_write_code); + if (retval != ERROR_OK) { + target_free_working_area(target, write_algorithm); + return retval; + } + + /* memory buffer */ + while (target_alloc_working_area_try(target, buffer_size, &source) != + ERROR_OK) { + buffer_size /= 2; + 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("large enough working area not available, can't do block memory writes"); + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + } + } + + armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; + armv7m_info.core_mode = ARM_MODE_THREAD; + + init_reg_param(®_params[0], "r0", 32, PARAM_IN_OUT); /* buffer start, status (out) */ + init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* buffer end */ + init_reg_param(®_params[2], "r2", 32, PARAM_OUT); /* target address */ + init_reg_param(®_params[3], "r3", 32, PARAM_OUT); /* count (double word-64bit) */ + init_reg_param(®_params[4], "r4", 32, PARAM_OUT); /* flash base */ + + buf_set_u32(reg_params[0].value, 0, 32, source->address); + buf_set_u32(reg_params[1].value, 0, 32, source->address + source->size); + buf_set_u32(reg_params[2].value, 0, 32, address); + buf_set_u32(reg_params[3].value, 0, 32, count / 4); + buf_set_u32(reg_params[4].value, 0, 32, STM32_FLASH_BASE); + + retval = target_run_flash_async_algorithm(target, buffer, count, 2, + 0, NULL, + 5, reg_params, + source->address, source->size, + write_algorithm->address, 0, + &armv7m_info); + + if (retval == ERROR_FLASH_OPERATION_FAILED) { + LOG_ERROR("error executing stm32l5 flash write algorithm"); + + uint32_t error = buf_get_u32(reg_params[0].value, 0, 32) & FLASH_ERROR; + + if (error & FLASH_NSWRPERR) + LOG_ERROR("flash memory write protected"); + + if (error != 0) { + LOG_ERROR("flash write failed = %08" PRIx32, error); + /* Clear but report errors */ + target_write_u32(target, STM32_FLASH_NSSR, error); + retval = ERROR_FAIL; + } + } + + 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 retval; +} + +static int stm32l5_write(struct flash_bank *bank, const uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + struct target *target = bank->target; + int retval; + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (offset & 0x7) { + LOG_WARNING("offset 0x%" PRIx32 " breaks required 8-byte alignment", + offset); + return ERROR_FLASH_DST_BREAKS_ALIGNMENT; + } + + if (count & 0x7) { + LOG_WARNING("Padding %d bytes to keep 8-byte write size", + count & 7); + count = (count + 7) & ~7; + /* This pads the write chunk with random bytes by overrunning the + * write buffer. Padding with the erased pattern 0xff is purely + * cosmetical, as 8-byte flash words are ECC secured and the first + * write will program the ECC bits. A second write would need + * to reprogramm these ECC bits. + * But this can only be done after erase! + */ + } + + retval = stm32l5_unlock_reg(target); + if (retval != ERROR_OK) + return retval; + + /* Only full double words (8-byte) can be programmed*/ + retval = stm32l5_write_block(bank, buffer, offset, count / 2); + if (retval != ERROR_OK) { + LOG_WARNING("block write failed"); + return retval; + } + + LOG_WARNING("block write succeeded"); + return target_write_u32(target, STM32_FLASH_NSCR, FLASH_NSLOCK); +} + +static int stm32l5_probe(struct flash_bank *bank) +{ + struct target *target = bank->target; + struct stm32l5_flash_bank *stm32l5_info = bank->driver_priv; + int i; + uint16_t flash_size_in_kb = 0xffff; + uint16_t max_flash_size_in_kb; + uint32_t device_id; + uint32_t options; + uint32_t base_address = 0x08000000; + + stm32l5_info->probed = 0; + + /* read stm32 device id register */ + int retval = target_read_u32(target, DBGMCU_IDCODE, &device_id); + if (retval != ERROR_OK) + return retval; + LOG_INFO("device id = 0x%08" PRIx32 "", device_id); + + /* set max flash size depending on family */ + switch (device_id & 0xfff) { + case 0x472: + max_flash_size_in_kb = 512; + break; + default: + LOG_WARNING("Cannot identify target as an STM32L5 family device."); + return ERROR_FAIL; + } + + /* get flash size from target. */ + retval = target_read_u16(target, FLASH_SIZE_REG, &flash_size_in_kb); + + /* failed reading flash size or flash size invalid (early silicon), + * default to max target family */ + if (retval != ERROR_OK || flash_size_in_kb == 0xffff || flash_size_in_kb == 0) { + LOG_WARNING("STM32 flash size failed, probe inaccurate - assuming %dk flash", + max_flash_size_in_kb); + flash_size_in_kb = max_flash_size_in_kb; + } + + LOG_INFO("flash size = %dkbytes", flash_size_in_kb); + + /* did we assign a flash size? */ + assert((flash_size_in_kb != 0xffff) && flash_size_in_kb); + + /* get options for DUAL BANK. */ + retval = target_read_u32(target, STM32_FLASH_OPTR, &options); + + if (retval != ERROR_OK) + return retval; + + int num_pages = 0; + int page_size = 0; + + switch (device_id & 0xfff) { + case 0x472: + /* L552/L562 have 512 KiB FLASH and dual/single bank mode. + * Page size is 2K or 4K.*/ + if (flash_size_in_kb == 512) { + stm32l5_info->bank2_start = 128; + if (options & OPT_DBANK_E_512K) { + page_size = 2048; + num_pages = 256; + } else { + page_size = 4096; + num_pages = 128; + } + break; + } + /* Invalid FLASH size for this device. */ + LOG_WARNING("Invalid flash size for STM32L5+ family device."); + return ERROR_FAIL; + default: + /* Invalid FLASH size for this device. */ + LOG_WARNING("Invalid flash size for STM32L5+ family device."); + return ERROR_FAIL; + } + + /* Release sector table if allocated. */ + if (bank->sectors) { + free(bank->sectors); + bank->sectors = NULL; + } + + /* Set bank configuration and construct sector table. */ + bank->base = base_address; + bank->size = num_pages * page_size; + bank->num_sectors = num_pages; + bank->sectors = malloc(sizeof(struct flash_sector) * num_pages); + if (!bank->sectors) + return ERROR_FAIL; /* Checkme: What better error to use?*/ + + for (i = 0; i < num_pages; i++) { + bank->sectors[i].offset = i * page_size; + bank->sectors[i].size = page_size; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = 1; + } + + stm32l5_info->probed = 1; + + return ERROR_OK; +} + +static int stm32l5_auto_probe(struct flash_bank *bank) +{ + struct stm32l5_flash_bank *stm32l5_info = bank->driver_priv; + if (stm32l5_info->probed) + return ERROR_OK; + return stm32l5_probe(bank); +} + +static int get_stm32l5_info(struct flash_bank *bank, char *buf, int buf_size) +{ + struct target *target = bank->target; + uint32_t dbgmcu_idcode; + + /* read stm32 device id register */ + int retval = target_read_u32(target, DBGMCU_IDCODE, &dbgmcu_idcode); + if (retval != ERROR_OK) + return retval; + + uint16_t device_id = dbgmcu_idcode & 0xfff; + uint8_t rev_id = dbgmcu_idcode >> 28; + uint8_t rev_minor = 0; + int i; + + for (i = 16; i < 28; i++) { + if (dbgmcu_idcode & (1 << i)) + rev_minor++; + else + break; + } + + const char *device_str; + + switch (device_id) { + case 0x472: + device_str = "STM32L552/562"; + break; + + default: + snprintf(buf, buf_size, "Cannot identify target as a STM32L5\n"); + return ERROR_FAIL; + } + + snprintf(buf, buf_size, "%s - Rev: %1d.%02d", + device_str, rev_id, rev_minor); + + return ERROR_OK; +} + +static int stm32l5_mass_erase(struct flash_bank *bank, uint32_t action) +{ + int retval; + struct target *target = bank->target; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + retval = stm32l5_unlock_reg(target); + if (retval != ERROR_OK) + return retval; + + /* mass erase flash memory */ + retval = target_write_u32( + target, stm32l5_get_flash_reg(bank, STM32_FLASH_NSCR), action); + if (retval != ERROR_OK) + return retval; + retval = target_write_u32( + target, stm32l5_get_flash_reg(bank, STM32_FLASH_NSCR), + action | FLASH_NSSTRT); + if (retval != ERROR_OK) + return retval; + + retval = stm32l5_wait_status_busy(bank, FLASH_ERASE_TIMEOUT); + if (retval != ERROR_OK) + return retval; + + retval = target_write_u32( + target, stm32l5_get_flash_reg(bank, STM32_FLASH_NSCR), FLASH_NSLOCK); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +COMMAND_HANDLER(stm32l5_handle_mass_erase_command) +{ + int i; + uint32_t action; + + if (CMD_ARGC < 1) { + command_print(CMD, "stm32l5x mass_erase <STM32L5 bank>"); + return ERROR_COMMAND_SYNTAX_ERROR; + } + + struct flash_bank *bank; + int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (ERROR_OK != retval) + return retval; + + action = FLASH_NSMER1 | FLASH_NSMER2; + retval = stm32l5_mass_erase(bank, action); + if (retval == ERROR_OK) { + /* set all sectors as erased */ + for (i = 0; i < bank->num_sectors; i++) + bank->sectors[i].is_erased = 1; + + command_print(CMD, "stm32l5x mass erase complete"); + } else { + command_print(CMD, "stm32l5x mass erase failed"); + } + + return retval; +} + +COMMAND_HANDLER(stm32l5_handle_option_read_command) +{ + if (CMD_ARGC < 2) { + command_print(CMD, "stm32l5x option_read <STM32L5 bank> <option_reg offset>"); + return ERROR_COMMAND_SYNTAX_ERROR; + } + + struct flash_bank *bank; + int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (ERROR_OK != retval) + return retval; + + uint32_t reg_addr = STM32_FLASH_BASE; + uint32_t value = 0; + + reg_addr += strtoul(CMD_ARGV[1], NULL, 16); + + retval = stm32l5_read_option(bank, reg_addr, &value); + if (ERROR_OK != retval) + return retval; + + command_print(CMD, "Option Register: <0x%" PRIx32 "> = 0x%" PRIx32 "", reg_addr, value); + + return retval; +} + +COMMAND_HANDLER(stm32l5_handle_option_write_command) +{ + if (CMD_ARGC < 3) { + command_print(CMD, "stm32l5x option_write <STM32L5 bank> <option_reg offset> <value> [mask]"); + return ERROR_COMMAND_SYNTAX_ERROR; + } + + struct flash_bank *bank; + int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (ERROR_OK != retval) + return retval; + + uint32_t reg_addr = STM32_FLASH_BASE; + uint32_t value = 0; + uint32_t mask = 0xFFFFFFFF; + + reg_addr += strtoul(CMD_ARGV[1], NULL, 16); + value = strtoul(CMD_ARGV[2], NULL, 16); + if (CMD_ARGC > 3) + mask = strtoul(CMD_ARGV[3], NULL, 16); + + command_print(CMD, "%s Option written.\n" + "INFO: a reset or power cycle is required " + "for the new settings to take effect.", bank->driver->name); + + retval = stm32l5_write_option(bank, reg_addr, value, mask); + return retval; +} + +COMMAND_HANDLER(stm32l5_handle_option_load_command) +{ + if (CMD_ARGC < 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + struct flash_bank *bank; + int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (ERROR_OK != retval) + return retval; + + struct target *target = bank->target; + + retval = stm32l5_unlock_reg(target); + if (ERROR_OK != retval) + return retval; + + retval = stm32l5_unlock_option_reg(target); + if (ERROR_OK != retval) + return retval; + + /* Write the OBLLAUNCH bit in CR -> Cause device "POR" and option bytes reload */ + retval = target_write_u32(target, stm32l5_get_flash_reg(bank, STM32_FLASH_NSCR), FLASH_OBL_LAUNCH); + + command_print(CMD, "stm32l5x option load (POR) completed."); + return retval; +} + +COMMAND_HANDLER(stm32l5_handle_lock_command) +{ + struct target *target = NULL; + + if (CMD_ARGC < 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + struct flash_bank *bank; + int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (ERROR_OK != retval) + return retval; + + target = bank->target; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + /* set readout protection level 1 by erasing the RDP option byte */ + if (stm32l5_write_option(bank, STM32_FLASH_OPTR, 0, 0x000000FF) != ERROR_OK) { + command_print(CMD, "%s failed to lock device", bank->driver->name); + return ERROR_OK; + } + + return ERROR_OK; +} + +COMMAND_HANDLER(stm32l5_handle_unlock_command) +{ + struct target *target = NULL; + + if (CMD_ARGC < 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + struct flash_bank *bank; + int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (ERROR_OK != retval) + return retval; + + target = bank->target; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (stm32l5_write_option(bank, STM32_FLASH_OPTR, RDP_LEVEL_0, 0x000000FF) != ERROR_OK) { + command_print(CMD, "%s failed to unlock device", bank->driver->name); + return ERROR_OK; + } + + return ERROR_OK; +} + +static const struct command_registration stm32l5_exec_command_handlers[] = { + { + .name = "lock", + .handler = stm32l5_handle_lock_command, + .mode = COMMAND_EXEC, + .usage = "bank_id", + .help = "Lock entire flash device.", + }, + { + .name = "unlock", + .handler = stm32l5_handle_unlock_command, + .mode = COMMAND_EXEC, + .usage = "bank_id", + .help = "Unlock entire protected flash device.", + }, + { + .name = "mass_erase", + .handler = stm32l5_handle_mass_erase_command, + .mode = COMMAND_EXEC, + .usage = "bank_id", + .help = "Erase entire flash device.", + }, + { + .name = "option_read", + .handler = stm32l5_handle_option_read_command, + .mode = COMMAND_EXEC, + .usage = "bank_id reg_offset", + .help = "Read & Display device option bytes.", + }, + { + .name = "option_write", + .handler = stm32l5_handle_option_write_command, + .mode = COMMAND_EXEC, + .usage = "bank_id reg_offset value mask", + .help = "Write device option bit fields with provided value.", + }, + { + .name = "option_load", + .handler = stm32l5_handle_option_load_command, + .mode = COMMAND_EXEC, + .usage = "bank_id", + .help = "Force re-load of device options (will cause device reset).", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration stm32l5_command_handlers[] = { + { + .name = "stm32l5x", + .mode = COMMAND_ANY, + .help = "stm32l5x flash command group", + .usage = "", + .chain = stm32l5_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +const struct flash_driver stm32l5x_flash = { + .name = "stm32l5x", + .commands = stm32l5_command_handlers, + .flash_bank_command = stm32l5_flash_bank_command, + .erase = stm32l5_erase, + .protect = stm32l5_protect, + .write = stm32l5_write, + .read = default_flash_read, + .probe = stm32l5_probe, + .auto_probe = stm32l5_auto_probe, + .erase_check = default_flash_blank_check, + .protect_check = stm32l5_protect_check, + .info = get_stm32l5_info, + .free_driver_priv = default_flash_free_driver_priv, +}; diff --git a/tcl/target/stm32l5x.cfg b/tcl/target/stm32l5x.cfg new file mode 100644 index 0000000..8ee602c --- /dev/null +++ b/tcl/target/stm32l5x.cfg @@ -0,0 +1,103 @@ +# script for stm32l5x family + +# +# stm32l5 devices support both JTAG and SWD transports. +# +source [find target/swj-dp.tcl] +source [find mem_helper.tcl] + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME stm32l5x +} + +set _ENDIAN little + +# Work-area is a space in RAM used for flash programming +# Smallest current target has 256kB ram, use 32kB by default to avoid surprises +if { [info exists WORKAREASIZE] } { + set _WORKAREASIZE $WORKAREASIZE +} else { + set _WORKAREASIZE 0x8000 +} + +#jtag scan chain +if { [info exists CPUTAPID] } { + set _CPUTAPID $CPUTAPID +} else { + if { [using_jtag] } { + # See STM Document RM0438 + # Section 51.2.10 Debug port registers - corresponds to Cortex-M33 r0p0 + set _CPUTAPID 0x0be11477 + } { + set _CPUTAPID 0x0be12477 + } +} + +swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID +dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu + +if {[using_jtag]} { + jtag newtap $_CHIPNAME bs -irlen 5 +} + +set _TARGETNAME $_CHIPNAME.cpu +target create $_TARGETNAME cortex_m -endian $_ENDIAN -dap $_CHIPNAME.dap + +$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 + +set _FLASHNAME $_CHIPNAME.flash +flash bank $_FLASHNAME stm32l5x 0 0 0 0 $_TARGETNAME + +# Common knowledges tells JTAG speed should be <= F_CPU/6. +# F_CPU after reset is MSI 4MHz, so use F_JTAG = 500 kHz to stay on +# the safe side. +# +# Note that there is a pretty wide band where things are +# more or less stable, see http://openocd.zylin.com/#/c/3366/ +adapter_khz 500 + +adapter_nsrst_delay 100 +if {[using_jtag]} { + jtag_ntrst_delay 100 +} + +reset_config srst_nogate + +if {![using_hla]} { + # if srst is not fitted use SYSRESETREQ to + # perform a soft reset + cortex_m reset_config sysresetreq +} + +$_TARGETNAME configure -event reset-init { + # CPU comes out of reset with MSI_ON | MSI_RDY | MSI Range 6 (4 MHz). + # Use MSI 24 MHz clock, compliant even with VOS == 2. + # 3 WS compliant with VOS == 2 and 24 MHz. + mww 0x40022000 0x00000103 ;# FLASH_ACR = PRFTBE | 3(Latency) + mww 0x40021000 0x00000099 ;# RCC_CR = MSI_ON | MSIRGSEL | MSI Range 9 + # Boost JTAG frequency + adapter_khz 4000 +} + +$_TARGETNAME configure -event reset-start { + # Reset clock is MSI (4 MHz) + adapter_khz 500 +} + +$_TARGETNAME configure -event examine-end { + # DBGMCU_CR |= DBG_STANDBY | DBG_STOP | DBG_SLEEP + mmw 0xE0042004 0x00000007 0 + + # Stop watchdog counters during halt + # DBGMCU_APB1_FZ |= DBG_IWDG_STOP | DBG_WWDG_STOP + mmw 0xE0042008 0x00001800 0 +} + +$_TARGETNAME configure -event trace-config { + # Set TRACE_IOEN; TRACE_MODE is set to async; when using sync + # change this value accordingly to configure trace pins + # assignment + mmw 0xE0042004 0x00000020 0 +} -- _______________________________________________ OpenOCD-devel mailing list OpenOCD-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openocd-devel