From d8cbf0dc402619cc7e027534c196abd9059ab98a Mon Sep 17 00:00:00 2001 From: Vincent Palatin Date: Tue, 21 Mar 2017 15:29:11 +0100 Subject: stm32: add internal flash support for STM32L4 family Add a flash driver for the STM32L4 family. For write and erase, the code is very similar to other variants excepted the 'normal' writes need to be perform 2 aligned 32-bit words at a time. Option bytes are a sligthly easier business since the hardware deals with the option bytes page preserving and erasing for us. For the write-protection, the STM32L4 is slightly different from the other variants. The write-protection granularity is still a 2-kB block (2kB here) but instead of having a 'bitmap' of the protected blocks, it defines 2 write-protection ranges (WRP1AR and WRP1BR). For the EC code base, we are using WRP1AR to protect the Read-Only regions and WRP1BR to protect the Rollback and RW regions (if they exist). Signed-off-by: Vincent Palatin BRANCH=none BUG=b:35648258 TEST=On Eve, run 'flashrom -p ec:type=fp -w /tmp/ec.bin' and 'flashrom -p ec:type=fp --wp-enable --wp-range 0x0 0x20000' Change-Id: Iaa98c1b4d3b07de2923ac076624bd4601c31a600 Reviewed-on: https://chromium-review.googlesource.com/456711 Commit-Ready: Vincent Palatin Tested-by: Vincent Palatin Reviewed-by: Randall Spangler Reviewed-by: Nicolas Boichat --- chip/stm32/flash-stm32l4.c | 531 +++++++++++++++++++++++++++++++++++++++++++++ chip/stm32/registers.h | 23 +- chip/stm32/system.c | 6 + 3 files changed, 554 insertions(+), 6 deletions(-) create mode 100644 chip/stm32/flash-stm32l4.c (limited to 'chip/stm32') diff --git a/chip/stm32/flash-stm32l4.c b/chip/stm32/flash-stm32l4.c new file mode 100644 index 0000000000..821d277b7a --- /dev/null +++ b/chip/stm32/flash-stm32l4.c @@ -0,0 +1,531 @@ +/* Copyright 2017 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +/* Flash memory module for STM32L4 family */ + +#include "common.h" +#include "clock.h" +#include "flash.h" +#include "hooks.h" +#include "registers.h" +#include "panic.h" +#include "system.h" +#include "task.h" +#include "timer.h" +#include "util.h" +#include "watchdog.h" + +/* + * Approximate number of CPU cycles per iteration of the loop when polling + * the flash status + */ +#define CYCLE_PER_FLASH_LOOP 10 + +/* Flash page programming timeout. This is 2x the datasheet max. */ +#define FLASH_TIMEOUT_US 48000 + +static inline int calculate_flash_timeout(void) +{ + return (FLASH_TIMEOUT_US * + (clock_get_freq() / SECOND) / CYCLE_PER_FLASH_LOOP); +} + +static int wait_while_busy(void) +{ + int timeout = calculate_flash_timeout(); + + while (STM32_FLASH_SR & FLASH_SR_BUSY && timeout-- > 0) + ; + return (timeout > 0) ? EC_SUCCESS : EC_ERROR_TIMEOUT; +} + +static int unlock(int locks) +{ + /* + * We may have already locked the flash module and get a bus fault + * in the attempt to unlock. Need to disable bus fault handler now. + */ + ignore_bus_fault(1); + + /* unlock CR if needed */ + if (STM32_FLASH_CR & FLASH_CR_LOCK) { + STM32_FLASH_KEYR = FLASH_KEYR_KEY1; + STM32_FLASH_KEYR = FLASH_KEYR_KEY2; + } + /* unlock option memory if required */ + if ((locks & FLASH_CR_OPTLOCK) && + (STM32_FLASH_CR & FLASH_CR_OPTLOCK)) { + STM32_FLASH_OPTKEYR = FLASH_OPTKEYR_KEY1; + STM32_FLASH_OPTKEYR = FLASH_OPTKEYR_KEY2; + } + + /* Re-enable bus fault handler */ + ignore_bus_fault(0); + + return (STM32_FLASH_CR & (locks | FLASH_CR_LOCK)) ? EC_ERROR_UNKNOWN + : EC_SUCCESS; +} + +static void lock(void) +{ + STM32_FLASH_CR = FLASH_CR_LOCK; +} + +/* + * Option byte organization + * + * [63:56][55:48][47:40][39:32] [31:24][23:16][15: 8][ 7: 0] + * +--------------+-------------------+------+ +-------------------+------+ + * | 0x1FFF7800 | nUSER | nRDP | | USER | RDP | + * +--------------+------------+------+------+ +------------+------+------+ + * | 0x1FFF7808 | | nPCROP1_STRT| | | PCROP1_STRT | + * +--------------+------------+-------------+ +------------+-------------+ + * | 0x1FFF7810 | | nPCROP1_END | | | PCROP1_END | + * +--------------+------------+-------------+ +------------+-------------+ + * | 0x1FFF7818 | |nWRP1A| |nWRP1A| | | WRP1A| | WRP1A| + * | | |_END | |_STRT | | | _END | | _STRT| + * +--------------+------------+-------------+ +------------+-------------+ + * | 0x1FFF7820 | |nWRP1B| |nWRP1B| | | WRP1B| | WRP1B| + * | | |_END | |_STRT | | | _END | | _STRT| + * +--------------+------------+-------------+ +------------+-------------+ + * + * Note that the variable with n prefix means the complement. + */ +static int unlock_optb(void) +{ + int rv; + + rv = wait_while_busy(); + if (rv) + return rv; + + rv = unlock(FLASH_CR_OPTLOCK); + if (rv) + return rv; + + return EC_SUCCESS; +} + +static int commit_optb(void) +{ + int rv; + + STM32_FLASH_CR |= FLASH_CR_OPTSTRT; + + rv = wait_while_busy(); + if (rv) + return rv; + lock(); + + return EC_SUCCESS; +} + +static void unprotect_all_blocks(void) +{ + unlock_optb(); + STM32_FLASH_WRP1AR = FLASH_WRP_RANGE_DISABLED; + STM32_FLASH_WRP1BR = FLASH_WRP_RANGE_DISABLED; + commit_optb(); +} + +int flash_physical_protect_at_boot(uint32_t new_flags) +{ + uint32_t ro_range = FLASH_WRP_RANGE_DISABLED; + uint32_t rb_rw_range = FLASH_WRP_RANGE_DISABLED; + /* + * WRP1AR is storing the write-protection range for the RO region. + * WRP1BR is storing the write-protection range for the + * rollback and RW regions. + */ + if (new_flags & (EC_FLASH_PROTECT_ALL_AT_BOOT | + EC_FLASH_PROTECT_RO_AT_BOOT)) + ro_range = FLASH_WRP_RANGE(WP_BANK_OFFSET, + WP_BANK_OFFSET + WP_BANK_COUNT); + + if (new_flags & EC_FLASH_PROTECT_ALL_AT_BOOT) { + rb_rw_range = FLASH_WRP_RANGE(WP_BANK_OFFSET + WP_BANK_COUNT, + PHYSICAL_BANKS); + } else { + uint8_t strt = WP_BANK_OFFSET + WP_BANK_COUNT; + uint8_t end = FLASH_WRP_END(FLASH_WRP_RANGE_DISABLED); +#ifdef CONFIG_ROLLBACK + if (new_flags & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT) { + strt = ROLLBACK_BANK_OFFSET; + end = ROLLBACK_BANK_OFFSET + ROLLBACK_BANK_COUNT; + } else { + strt = ROLLBACK_BANK_OFFSET + ROLLBACK_BANK_COUNT; + } +#endif /* !CONFIG_ROLLBACK */ +#ifdef CONFIG_FLASH_PROTECT_RW + if (new_flags & EC_FLASH_PROTECT_RW_AT_BOOT) + end = PHYSICAL_BANKS; +#endif /* CONFIG_FLASH_PROTECT_RW */ + + if (end != FLASH_WRP_END(FLASH_WRP_RANGE_DISABLED)) + rb_rw_range = FLASH_WRP_RANGE(strt, end); + } + + unlock_optb(); +#ifdef CONFIG_WP_ALWAYS + /* + * Set a permanent protection by increasing RDP to level 1, + * trying to unprotected the flash will trigger a full erase. + */ + STM32_FLASH_OPTR = (STM32_FLASH_OPTR & ~0xff) | 0x11; +#endif + STM32_FLASH_WRP1AR = ro_range; + STM32_FLASH_WRP1BR = rb_rw_range; + commit_optb(); + + return EC_SUCCESS; +} + +/** + * Check if write protect register state is inconsistent with RO_AT_BOOT and + * ALL_AT_BOOT state. + * + * @return zero if consistent, non-zero if inconsistent. + */ +static int registers_need_reset(void) +{ + uint32_t flags = flash_get_protect(); + int ro_at_boot = (flags & EC_FLASH_PROTECT_RO_AT_BOOT) ? 1 : 0; + /* + * The RO region is write-protected by the WRP1AR range, + * it starts at page WP_BANK_OFFSET for WP_BANK_COUNT pages. + */ + uint32_t wrp1ar = STM32_OPTB_WRP1AR; + uint32_t ro_range = ro_at_boot ? + FLASH_WRP_RANGE(WP_BANK_OFFSET, WP_BANK_OFFSET + WP_BANK_COUNT) + : FLASH_WRP_RANGE_DISABLED; + + return ro_range != (wrp1ar & FLASH_WRP_MASK); +} + +/*****************************************************************************/ +/* Physical layer APIs */ + +int flash_physical_write(int offset, int size, const char *data) +{ + uint32_t *address = (void *)(CONFIG_PROGRAM_MEMORY_BASE + offset); + int res = EC_SUCCESS; + int timeout = calculate_flash_timeout(); + int i; + int unaligned = (uint32_t)data & (CONFIG_FLASH_WRITE_SIZE - 1); + uint32_t *data32 = (void *)data; + + if (unlock(FLASH_CR_LOCK) != EC_SUCCESS) + return EC_ERROR_UNKNOWN; + + /* Clear previous error status */ + STM32_FLASH_SR = FLASH_SR_ERR_MASK; + + /* set PG bit */ + STM32_FLASH_CR |= FLASH_CR_PG; + + for (; size > 0; size -= CONFIG_FLASH_WRITE_SIZE) { + /* + * Reload the watchdog timer to avoid watchdog reset when doing + * long writing. + */ + watchdog_reload(); + + /* wait to be ready */ + for (i = 0; (STM32_FLASH_SR & FLASH_SR_BUSY) && (i < timeout); + i++) + ; + if (STM32_FLASH_SR & FLASH_SR_BUSY) { + res = EC_ERROR_TIMEOUT; + goto exit_wr; + } + + /* write the 2 words */ + if (unaligned) { + *address++ = (uint32_t)data[0] | (data[1] << 8) + | (data[2] << 16) | (data[3] << 24); + *address++ = (uint32_t)data[4] | (data[5] << 8) + | (data[6] << 16) | (data[7] << 24); + data += CONFIG_FLASH_WRITE_SIZE; + } else { + *address++ = *data32++; + *address++ = *data32++; + } + + /* Wait for writes to complete */ + for (i = 0; (STM32_FLASH_SR & FLASH_SR_BUSY) && (i < timeout); + i++) + ; + + if (STM32_FLASH_SR & FLASH_SR_BUSY) { + res = EC_ERROR_TIMEOUT; + goto exit_wr; + } + + /* + * Check for error conditions - erase failed, voltage error, + * protection error. + */ + if (STM32_FLASH_SR & FLASH_SR_ERR_MASK) { + res = EC_ERROR_UNKNOWN; + goto exit_wr; + } + } + +exit_wr: + /* Disable PG bit */ + STM32_FLASH_CR &= ~FLASH_CR_PG; + + lock(); + + return res; +} + +int flash_physical_erase(int offset, int size) +{ + int res = EC_SUCCESS; + int pg; + int last; + + if (unlock(FLASH_CR_LOCK) != EC_SUCCESS) + return EC_ERROR_UNKNOWN; + + /* Clear previous error status */ + STM32_FLASH_SR = FLASH_SR_ERR_MASK; + + last = (offset + size) / CONFIG_FLASH_ERASE_SIZE; + for (pg = offset / CONFIG_FLASH_ERASE_SIZE; pg < last; pg++) { + timestamp_t deadline; + + /* select page to erase and PER bit */ + STM32_FLASH_CR = (STM32_FLASH_CR & ~FLASH_CR_PNB_MASK) + | FLASH_CR_PER | FLASH_CR_PNB(pg); + + /* set STRT bit : start erase */ + STM32_FLASH_CR |= FLASH_CR_STRT; + + /* + * Reload the watchdog timer to avoid watchdog reset during a + * long erase operation. + */ + watchdog_reload(); + + deadline.val = get_time().val + FLASH_TIMEOUT_US; + /* Wait for erase to complete */ + while ((STM32_FLASH_SR & FLASH_SR_BUSY) && + (get_time().val < deadline.val)) { + usleep(300); + } + if (STM32_FLASH_SR & FLASH_SR_BUSY) { + res = EC_ERROR_TIMEOUT; + goto exit_er; + } + + /* + * Check for error conditions - erase failed, voltage error, + * protection error + */ + if (STM32_FLASH_SR & FLASH_SR_ERR_MASK) { + res = EC_ERROR_UNKNOWN; + goto exit_er; + } + } + +exit_er: + /* reset PER bit */ + STM32_FLASH_CR &= ~(FLASH_CR_PER | FLASH_CR_PNB_MASK); + + lock(); + + return res; +} + +int flash_physical_get_protect(int block) +{ + uint32_t wrp1ar = STM32_FLASH_WRP1AR; + uint32_t wrp1br = STM32_FLASH_WRP1BR; + + return ((block >= FLASH_WRP_START(wrp1ar)) && + (block < FLASH_WRP_END(wrp1ar))) || + ((block >= FLASH_WRP_START(wrp1br)) && + (block < FLASH_WRP_END(wrp1br))); +} + +/* + * Note: This does not need to update _NOW flags, as get_protect_flags + * in common code already does so. + */ +uint32_t flash_physical_get_protect_flags(void) +{ + uint32_t flags = 0; + uint32_t wrp1ar = STM32_OPTB_WRP1AR; + uint32_t wrp1br = STM32_OPTB_WRP1BR; + + /* RO region protection range is in WRP1AR range */ + if (wrp1ar == FLASH_WRP_RANGE(WP_BANK_OFFSET, + WP_BANK_OFFSET + WP_BANK_COUNT)) + flags |= EC_FLASH_PROTECT_RO_AT_BOOT; + /* Rollback and RW regions protection range is in WRP1BR range */ + if (wrp1br != FLASH_WRP_RANGE_DISABLED) { + int end = FLASH_WRP_END(wrp1br); + int strt = FLASH_WRP_START(wrp1br); + +#ifdef CONFIG_ROLLBACK + if (strt <= ROLLBACK_BANK_OFFSET && + end >= ROLLBACK_BANK_OFFSET + ROLLBACK_BANK_COUNT) + flags |= EC_FLASH_PROTECT_ROLLBACK_AT_BOOT; +#endif /* CONFIG_ROLLBACK */ +#ifdef CONFIG_FLASH_PROTECT_RW + if (end == PHYSICAL_BANKS) + flags |= EC_FLASH_PROTECT_RW_AT_BOOT; +#endif /* CONFIG_FLASH_PROTECT_RW */ + if (end == PHYSICAL_BANKS && + strt == WP_BANK_OFFSET + WP_BANK_COUNT && + flags & EC_FLASH_PROTECT_RO_AT_BOOT) + flags |= EC_FLASH_PROTECT_ALL_AT_BOOT; + } + + return flags; +} + +int flash_physical_protect_now(int all) +{ + return EC_ERROR_INVAL; +} + +uint32_t flash_physical_get_valid_flags(void) +{ + return EC_FLASH_PROTECT_RO_AT_BOOT | + EC_FLASH_PROTECT_RO_NOW | +#ifdef CONFIG_FLASH_PROTECT_RW + EC_FLASH_PROTECT_RW_AT_BOOT | + EC_FLASH_PROTECT_RW_NOW | +#endif +#ifdef CONFIG_ROLLBACK + EC_FLASH_PROTECT_ROLLBACK_AT_BOOT | + EC_FLASH_PROTECT_ROLLBACK_NOW | +#endif + EC_FLASH_PROTECT_ALL_AT_BOOT | + EC_FLASH_PROTECT_ALL_NOW; +} + +uint32_t flash_physical_get_writable_flags(uint32_t cur_flags) +{ + uint32_t ret = 0; + + /* If RO protection isn't enabled, its at-boot state can be changed. */ + if (!(cur_flags & EC_FLASH_PROTECT_RO_NOW)) + ret |= EC_FLASH_PROTECT_RO_AT_BOOT; + + /* + * ALL/RW at-boot state can be set if WP GPIO is asserted and can always + * be cleared. + */ + if (cur_flags & (EC_FLASH_PROTECT_ALL_AT_BOOT | + EC_FLASH_PROTECT_GPIO_ASSERTED)) + ret |= EC_FLASH_PROTECT_ALL_AT_BOOT; + +#ifdef CONFIG_FLASH_PROTECT_RW + if (cur_flags & (EC_FLASH_PROTECT_RW_AT_BOOT | + EC_FLASH_PROTECT_GPIO_ASSERTED)) + ret |= EC_FLASH_PROTECT_RW_AT_BOOT; +#endif + +#ifdef CONFIG_ROLLBACK + if (cur_flags & (EC_FLASH_PROTECT_ROLLBACK_AT_BOOT | + EC_FLASH_PROTECT_GPIO_ASSERTED)) + ret |= EC_FLASH_PROTECT_ROLLBACK_AT_BOOT; +#endif + + return ret; +} + +int flash_pre_init(void) +{ + uint32_t reset_flags = system_get_reset_flags(); + uint32_t prot_flags = flash_get_protect(); + int need_reset = 0; + + /* + * If we have already jumped between images, an earlier image could + * have applied write protection. Nothing additional needs to be done. + */ + if (reset_flags & RESET_FLAG_SYSJUMP) + return EC_SUCCESS; + + if (prot_flags & EC_FLASH_PROTECT_GPIO_ASSERTED) { + if ((prot_flags & EC_FLASH_PROTECT_RO_AT_BOOT) && + !(prot_flags & EC_FLASH_PROTECT_RO_NOW)) { + /* + * Pstate wants RO protected at boot, but the write + * protect register wasn't set to protect it. Force an + * update to the write protect register and reboot so + * it takes effect. + */ + flash_physical_protect_at_boot( + EC_FLASH_PROTECT_RO_AT_BOOT); + need_reset = 1; + } + + if (registers_need_reset()) { + /* + * Write protect register was in an inconsistent state. + * Set it back to a good state and reboot. + * + * TODO(crosbug.com/p/23798): this seems really similar + * to the check above. One of them should be able to + * go away. + */ + flash_protect_at_boot( + prot_flags & EC_FLASH_PROTECT_RO_AT_BOOT); + need_reset = 1; + } + } else { + if (prot_flags & EC_FLASH_PROTECT_RO_NOW) { + /* + * Write protect pin unasserted but some section is + * protected. Drop it and reboot. + */ + unprotect_all_blocks(); + need_reset = 1; + } + } + + if ((flash_physical_get_valid_flags() & EC_FLASH_PROTECT_ALL_AT_BOOT) && + (!!(prot_flags & EC_FLASH_PROTECT_ALL_AT_BOOT) != + !!(prot_flags & EC_FLASH_PROTECT_ALL_NOW))) { + /* + * ALL_AT_BOOT and ALL_NOW should be both set or both unset + * at boot. If they are not, it must be that the chip requires + * OBL_LAUNCH to be set to reload option bytes. Let's reset + * the system with OBL_LAUNCH set. + * This assumes OBL_LAUNCH is used for hard reset in + * chip/stm32/system.c. + */ + need_reset = 1; + } + +#ifdef CONFIG_FLASH_PROTECT_RW + if ((flash_physical_get_valid_flags() & EC_FLASH_PROTECT_RW_AT_BOOT) && + (!!(prot_flags & EC_FLASH_PROTECT_RW_AT_BOOT) != + !!(prot_flags & EC_FLASH_PROTECT_RW_NOW))) { + /* RW_AT_BOOT and RW_NOW do not match. */ + need_reset = 1; + } +#endif + +#ifdef CONFIG_ROLLBACK + if ((flash_physical_get_valid_flags() & + EC_FLASH_PROTECT_ROLLBACK_AT_BOOT) && + (!!(prot_flags & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT) != + !!(prot_flags & EC_FLASH_PROTECT_ROLLBACK_NOW))) { + /* ROLLBACK_AT_BOOT and ROLLBACK_NOW do not match. */ + need_reset = 1; + } +#endif + + if (need_reset) + system_reset(SYSTEM_RESET_HARD | SYSTEM_RESET_PRESERVE_FLAGS); + + return EC_SUCCESS; +} diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h index a8104114cf..f0bd1cf861 100644 --- a/chip/stm32/registers.h +++ b/chip/stm32/registers.h @@ -1323,30 +1323,41 @@ typedef volatile struct stm32_spi_regs stm32_spi_regs_t; #define STM32_FLASH_ACR_DCEN (1 << 10) #define STM32_FLASH_PDKEYR REG32(STM32_FLASH_REGS_BASE + 0x04) #define STM32_FLASH_KEYR REG32(STM32_FLASH_REGS_BASE + 0x08) +#define FLASH_KEYR_KEY1 0x45670123 +#define FLASH_KEYR_KEY2 0xCDEF89AB #define STM32_FLASH_OPTKEYR REG32(STM32_FLASH_REGS_BASE + 0x0c) +#define FLASH_OPTKEYR_KEY1 0x08192A3B +#define FLASH_OPTKEYR_KEY2 0x4C5D6E7F #define STM32_FLASH_SR REG32(STM32_FLASH_REGS_BASE + 0x10) #define FLASH_SR_BUSY (1 << 16) -#define FLASH_SR_ERR_MASK (0xc3fb) +#define FLASH_SR_ERR_MASK (0xc3fa) #define STM32_FLASH_CR REG32(STM32_FLASH_REGS_BASE + 0x14) #define FLASH_CR_PG (1 << 0) #define FLASH_CR_PER (1 << 1) #define FLASH_CR_STRT (1 << 16) +#define FLASH_CR_OPTSTRT (1 << 17) +#define FLASH_CR_OBL_LAUNCH (1 << 27) +#define FLASH_CR_OPTLOCK (1 << 30) #define FLASH_CR_LOCK (1 << 31) #define FLASH_CR_PNB(sec) (((sec) & 0xff) << 3) -#define FLASH_CR_PNB_MASK FLASH_CR_SNB(0xff) +#define FLASH_CR_PNB_MASK FLASH_CR_PNB(0xff) #define STM32_FLASH_ECCR REG32(STM32_FLASH_REGS_BASE + 0x18) #define STM32_FLASH_OPTR REG32(STM32_FLASH_REGS_BASE + 0x20) #define STM32_FLASH_PCROP1SR REG32(STM32_FLASH_REGS_BASE + 0x24) #define STM32_FLASH_PCROP1ER REG32(STM32_FLASH_REGS_BASE + 0x28) #define STM32_FLASH_WRP1AR REG32(STM32_FLASH_REGS_BASE + 0x2C) #define STM32_FLASH_WRP1BR REG32(STM32_FLASH_REGS_BASE + 0x30) +#define FLASH_WRP_START(val) ((val) & 0xff) +#define FLASH_WRP_END(val) (((val) >> 16) & 0xff) +#define FLASH_WRP_RANGE(strt, end) (((end) << 16) | (strt)) +#define FLASH_WRP_RANGE_DISABLED FLASH_WRP_RANGE(0xFF, 0x00) +#define FLASH_WRP_MASK FLASH_WRP_RANGE(0xFF, 0xFF) #define STM32_OPTB_BASE 0x1FFF7800 -#define STM32_OPTB_USER_RDP_OFF 0x00 -#define STM32_OPTB_WRP1A 0x18 -#define STM32_OPTB_WRP1B 0x20 -#define STM32_OPTB_COMPL_OFF 4 +#define STM32_OPTB_USER_RDP REG32(STM32_OPTB_BASE + 0x00) +#define STM32_OPTB_WRP1AR REG32(STM32_OPTB_BASE + 0x18) +#define STM32_OPTB_WRP1BR REG32(STM32_OPTB_BASE + 0x20) #elif defined(CHIP_FAMILY_STM32F4) #define STM32_FLASH_REGS_BASE 0x40023c00 diff --git a/chip/stm32/system.c b/chip/stm32/system.c index 8acdc7a729..b81dc0055c 100644 --- a/chip/stm32/system.c +++ b/chip/stm32/system.c @@ -292,6 +292,12 @@ void system_reset(int flags) * use this for hard reset. */ STM32_FLASH_CR |= STM32_FLASH_CR_OBL_LAUNCH; +#elif defined(CHIP_FAMILY_STM32L4) + STM32_FLASH_KEYR = FLASH_KEYR_KEY1; + STM32_FLASH_KEYR = FLASH_KEYR_KEY2; + STM32_FLASH_OPTKEYR = FLASH_OPTKEYR_KEY1; + STM32_FLASH_OPTKEYR = FLASH_OPTKEYR_KEY2; + STM32_FLASH_CR |= FLASH_CR_OBL_LAUNCH; #else /* Ask the watchdog to trigger a hard reboot */ STM32_IWDG_KR = 0x5555; -- cgit v1.2.1