summaryrefslogtreecommitdiff
path: root/chip/stm32/flash-stm32h7.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/stm32/flash-stm32h7.c')
-rw-r--r--chip/stm32/flash-stm32h7.c468
1 files changed, 468 insertions, 0 deletions
diff --git a/chip/stm32/flash-stm32h7.c b/chip/stm32/flash-stm32h7.c
new file mode 100644
index 0000000000..d697faf4dd
--- /dev/null
+++ b/chip/stm32/flash-stm32h7.c
@@ -0,0 +1,468 @@
+/* Copyright 2018 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 STM32H7 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 2
+
+/* Flash 256-bit word programming timeout. */
+#define FLASH_TIMEOUT_US 600
+
+/*
+ * Flash 128-KB block erase timeout.
+ * Datasheet says maximum is about 4 seconds in x8.
+ * Real delay seems to be: < 1 second in x64, < 2 seconds in x8.
+ */
+#define FLASH_ERASE_TIMEOUT_US (4200 * MSEC)
+
+/*
+ * Option bytes programming timeout.
+ * No specification, real delay seems to be around 300ms.
+ */
+#define FLASH_OPT_PRG_TIMEOUT_US (1000 * MSEC)
+
+/*
+ * All variants have 2 banks (as in parallel hardware / controllers)
+ * not what is called 'bank' in the common code (ie Write-Protect sectors)
+ * both have the same number of 128KB blocks.
+ */
+#define HWBANK_SIZE (CONFIG_FLASH_SIZE / 2)
+#define BLOCKS_PER_HWBANK (HWBANK_SIZE / CONFIG_FLASH_ERASE_SIZE)
+#define BLOCKS_HWBANK_MASK ((1 << BLOCKS_PER_HWBANK) - 1)
+
+/*
+ * We can tune the power consumption vs erase/write speed
+ * by default, go fast (and consume current)
+ */
+#define DEFAULT_PSIZE FLASH_CR_PSIZE_DWORD
+
+/* Can no longer write/erase flash until next reboot */
+static int access_disabled;
+/* Can no longer modify write-protection in option bytes until next reboot */
+static int option_disabled;
+/* Is physical flash stuck protected? (avoid reboot loop) */
+static int stuck_locked;
+
+static inline int calculate_flash_timeout(void)
+{
+ return (FLASH_TIMEOUT_US *
+ (clock_get_freq() / SECOND) / CYCLE_PER_FLASH_LOOP);
+}
+
+static int unlock(int bank)
+{
+ /* unlock CR only if needed */
+ if (STM32_FLASH_CR(bank) & FLASH_CR_LOCK) {
+ /*
+ * 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);
+
+ STM32_FLASH_KEYR(bank) = FLASH_KEYR_KEY1;
+ STM32_FLASH_KEYR(bank) = FLASH_KEYR_KEY2;
+ ignore_bus_fault(0);
+ }
+
+ return (STM32_FLASH_CR(bank) & FLASH_CR_LOCK) ? EC_ERROR_UNKNOWN
+ : EC_SUCCESS;
+}
+
+static void lock(int bank)
+{
+ STM32_FLASH_CR(bank) |= FLASH_CR_LOCK;
+}
+
+static int unlock_optb(void)
+{
+ if (option_disabled)
+ return EC_ERROR_ACCESS_DENIED;
+
+ if (unlock(0))
+ return EC_ERROR_UNKNOWN;
+
+ /*
+ * Always use bank 0 flash controller as there is only one option bytes
+ * set for both banks.
+ */
+ if (STM32_FLASH_OPTCR(0) & FLASH_OPTCR_OPTLOCK) {
+ /*
+ * 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);
+
+ STM32_FLASH_OPTKEYR(0) = FLASH_OPTKEYR_KEY1;
+ STM32_FLASH_OPTKEYR(0) = FLASH_OPTKEYR_KEY2;
+ ignore_bus_fault(0);
+ }
+
+ return STM32_FLASH_OPTCR(0) & FLASH_OPTCR_OPTLOCK ? EC_ERROR_UNKNOWN
+ : EC_SUCCESS;
+}
+
+static int commit_optb(void)
+{
+ /* might use this before timer_init, cannot use get_time/usleep */
+ int timeout = (FLASH_OPT_PRG_TIMEOUT_US *
+ (clock_get_freq() / SECOND) / CYCLE_PER_FLASH_LOOP);
+
+ STM32_FLASH_OPTCR(0) |= FLASH_OPTCR_OPTSTART;
+
+ while (STM32_FLASH_OPTSR_CUR(0) & FLASH_OPTSR_BUSY && timeout-- > 0)
+ ;
+
+ STM32_FLASH_OPTCR(0) |= FLASH_OPTCR_OPTLOCK;
+ lock(0);
+
+ return (timeout > 0) ? EC_SUCCESS : EC_ERROR_TIMEOUT;
+}
+
+static void protect_blocks(uint32_t blocks)
+{
+ if (unlock_optb())
+ return;
+ STM32_FLASH_WPSN_PRG(0) |= blocks & BLOCKS_HWBANK_MASK;
+ STM32_FLASH_WPSN_PRG(1) |= (blocks >> BLOCKS_PER_HWBANK)
+ & BLOCKS_HWBANK_MASK;
+ commit_optb();
+}
+
+/* use the option bytes RSS1 bit as 'Write Protect enabled' flag. */
+static int is_wp_enabled(void)
+{
+ return !!(STM32_FLASH_OPTSR_CUR(0) & FLASH_OPTSR_RSS1);
+}
+
+static int set_wp(int enabled)
+{
+ int rv;
+
+ rv = unlock_optb();
+ if (rv)
+ return rv;
+ if (enabled)
+ STM32_FLASH_OPTSR_PRG(0) |= FLASH_OPTSR_RSS1;
+ else
+ STM32_FLASH_OPTSR_PRG(0) &= ~FLASH_OPTSR_RSS1;
+
+ return commit_optb();
+}
+
+/*****************************************************************************/
+/* Physical layer APIs */
+
+int flash_physical_write(int offset, int size, const char *data)
+{
+ int res = EC_SUCCESS;
+ int bank = offset / HWBANK_SIZE;
+ uint32_t *address = (void *)(CONFIG_PROGRAM_MEMORY_BASE + offset);
+ int timeout = calculate_flash_timeout();
+ int i;
+ int unaligned = (uint32_t)data & (CONFIG_FLASH_WRITE_SIZE - 1);
+ uint32_t *data32 = (void *)data;
+
+ if (access_disabled)
+ return EC_ERROR_ACCESS_DENIED;
+
+ /* work on a single hardware bank at a time */
+ if ((offset + size) / HWBANK_SIZE != bank)
+ return EC_ERROR_INVAL;
+
+ if (unlock(bank) != EC_SUCCESS)
+ return EC_ERROR_UNKNOWN;
+
+ /* Clear previous error status */
+ STM32_FLASH_CCR(bank) = FLASH_CCR_ERR_MASK;
+
+ /* select write parallelism */
+ STM32_FLASH_CR(bank) = (STM32_FLASH_CR(bank) & ~FLASH_CR_PSIZE_MASK)
+ | DEFAULT_PSIZE;
+
+ /* set PG bit */
+ STM32_FLASH_CR(bank) |= 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();
+
+ /* write a 256-bit flash word */
+ if (unaligned) {
+ for (i = 0; i < CONFIG_FLASH_WRITE_SIZE / 4; i++,
+ data += 4)
+ *address++ = (uint32_t)data[0] | (data[1] << 8)
+ | (data[2] << 16) | (data[3] << 24);
+ } else {
+ for (i = 0; i < CONFIG_FLASH_WRITE_SIZE / 4; i++)
+ *address++ = *data32++;
+ }
+
+ /* Wait for writes to complete */
+ for (i = 0; (STM32_FLASH_SR(bank) &
+ (FLASH_SR_WBNE | FLASH_SR_QW)) && (i < timeout); i++)
+ ;
+
+ if (STM32_FLASH_SR(bank) & (FLASH_SR_WBNE | FLASH_SR_QW)) {
+ res = EC_ERROR_TIMEOUT;
+ goto exit_wr;
+ }
+
+ if (STM32_FLASH_SR(bank) & FLASH_CCR_ERR_MASK) {
+ res = EC_ERROR_UNKNOWN;
+ goto exit_wr;
+ }
+ }
+
+exit_wr:
+ /* Disable PG bit */
+ STM32_FLASH_CR(bank) &= ~FLASH_CR_PG;
+
+ lock(bank);
+
+ return res;
+}
+
+int flash_physical_erase(int offset, int size)
+{
+ int res = EC_SUCCESS;
+ int bank = offset / HWBANK_SIZE;
+ int last = (offset + size) / CONFIG_FLASH_ERASE_SIZE;
+ int sect;
+
+ if (access_disabled)
+ return EC_ERROR_ACCESS_DENIED;
+
+ /* work on a single hardware bank at a time */
+ if ((offset + size) / HWBANK_SIZE != bank)
+ return EC_ERROR_INVAL;
+
+ if (unlock(bank) != EC_SUCCESS)
+ return EC_ERROR_UNKNOWN;
+
+ /* Clear previous error status */
+ STM32_FLASH_CCR(bank) = FLASH_CCR_ERR_MASK;
+
+ /* select erase parallelism */
+ STM32_FLASH_CR(bank) = (STM32_FLASH_CR(bank) & ~FLASH_CR_PSIZE_MASK)
+ | DEFAULT_PSIZE;
+
+ for (sect = offset / CONFIG_FLASH_ERASE_SIZE; sect < last; sect++) {
+ timestamp_t deadline;
+
+ /* select page to erase and PER bit */
+ STM32_FLASH_CR(bank) = (STM32_FLASH_CR(bank)
+ & ~FLASH_CR_SNB_MASK)
+ | FLASH_CR_SER | FLASH_CR_SNB(sect);
+
+ /* set STRT bit : start erase */
+ STM32_FLASH_CR(bank) |= FLASH_CR_STRT;
+
+ /*
+ * Reload the watchdog timer to avoid watchdog reset during a
+ * long erase operation.
+ */
+ watchdog_reload();
+
+ deadline.val = get_time().val + FLASH_ERASE_TIMEOUT_US;
+ /* Wait for erase to complete */
+ while ((STM32_FLASH_SR(bank) & FLASH_SR_BUSY) &&
+ (get_time().val < deadline.val)) {
+ usleep(5000);
+ }
+ if (STM32_FLASH_SR(bank) & FLASH_SR_BUSY) {
+ res = EC_ERROR_TIMEOUT;
+ goto exit_er;
+ }
+
+ /*
+ * Check for error conditions - erase failed, voltage error,
+ * protection error
+ */
+ if (STM32_FLASH_SR(bank) & FLASH_CCR_ERR_MASK) {
+ res = EC_ERROR_UNKNOWN;
+ goto exit_er;
+ }
+ }
+
+exit_er:
+ /* reset SER bit */
+ STM32_FLASH_CR(bank) &= ~(FLASH_CR_SER | FLASH_CR_SNB_MASK);
+
+ lock(bank);
+
+ return res;
+}
+
+int flash_physical_get_protect(int block)
+{
+ int bank = block / BLOCKS_PER_HWBANK;
+ int index = block % BLOCKS_PER_HWBANK;
+
+ return !(STM32_FLASH_WPSN_CUR(bank) & (1 << index));
+}
+
+/*
+ * Note: This does not need to update _NOW flags, as flash_get_protect
+ * in common code already does so.
+ */
+uint32_t flash_physical_get_protect_flags(void)
+{
+ uint32_t flags = 0;
+
+ if (access_disabled)
+ flags |= EC_FLASH_PROTECT_ALL_NOW;
+
+ if (is_wp_enabled())
+ flags |= EC_FLASH_PROTECT_RO_AT_BOOT;
+
+ /* Check if blocks were stuck locked at pre-init */
+ if (stuck_locked)
+ flags |= EC_FLASH_PROTECT_ERROR_STUCK;
+
+ return flags;
+}
+
+#define WP_RANGE(start, count) (((1 << (count)) - 1) << (start))
+#define RO_WP_RANGE WP_RANGE(WP_BANK_OFFSET, WP_BANK_COUNT)
+
+int flash_physical_protect_now(int all)
+{
+ protect_blocks(RO_WP_RANGE);
+
+ /*
+ * Lock the option bytes or the full access by writing a wrong
+ * key to FLASH_*KEYR. This triggers a bus fault, so we need to
+ * disable bus fault handler while doing this.
+ *
+ * This incorrect key fault causes the flash to become
+ * permanently locked until reset, a correct keyring write
+ * will not unlock it.
+ */
+ ignore_bus_fault(1);
+
+ if (all) {
+ /* cannot do any write/erase access until next reboot */
+ STM32_FLASH_KEYR(0) = 0xffffffff;
+ STM32_FLASH_KEYR(1) = 0xffffffff;
+ access_disabled = 1;
+ }
+ /* cannot modify the WP bits in the option bytes until reboot */
+ STM32_FLASH_OPTKEYR(0) = 0xffffffff;
+ option_disabled = 1;
+ ignore_bus_fault(0);
+
+ return EC_SUCCESS;
+}
+
+int flash_physical_protect_at_boot(uint32_t new_flags)
+{
+ int new_wp_enable = !!(new_flags & EC_FLASH_PROTECT_RO_AT_BOOT);
+
+ if (is_wp_enabled() != new_wp_enable)
+ return set_wp(new_wp_enable);
+
+ return EC_SUCCESS;
+}
+
+uint32_t flash_physical_get_valid_flags(void)
+{
+ return EC_FLASH_PROTECT_RO_AT_BOOT |
+ EC_FLASH_PROTECT_RO_NOW |
+ 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;
+
+ /*
+ * If entire flash isn't protected at this boot, it can be enabled if
+ * the WP GPIO is asserted.
+ */
+ if (!(cur_flags & EC_FLASH_PROTECT_ALL_NOW) &&
+ (cur_flags & EC_FLASH_PROTECT_GPIO_ASSERTED))
+ ret |= EC_FLASH_PROTECT_ALL_NOW;
+
+ return ret;
+}
+
+int flash_pre_init(void)
+{
+ uint32_t reset_flags = system_get_reset_flags();
+ uint32_t prot_flags = flash_get_protect();
+ uint32_t unwanted_prot_flags = EC_FLASH_PROTECT_ALL_NOW |
+ EC_FLASH_PROTECT_ERROR_INCONSISTENT;
+
+ /*
+ * 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) {
+ /*
+ * Write protect is asserted. If we want RO flash protected,
+ * protect it now.
+ */
+ if ((prot_flags & EC_FLASH_PROTECT_RO_AT_BOOT) &&
+ !(prot_flags & EC_FLASH_PROTECT_RO_NOW)) {
+ int rv = flash_set_protect(EC_FLASH_PROTECT_RO_NOW,
+ EC_FLASH_PROTECT_RO_NOW);
+ if (rv)
+ return rv;
+
+ /* Re-read flags */
+ prot_flags = flash_get_protect();
+ }
+ } else {
+ /* Don't want RO flash protected */
+ unwanted_prot_flags |= EC_FLASH_PROTECT_RO_NOW;
+ }
+
+ /* If there are no unwanted flags, done */
+ if (!(prot_flags & unwanted_prot_flags))
+ return EC_SUCCESS;
+
+ /*
+ * If the last reboot was a power-on reset, it should have cleared
+ * write-protect. If it didn't, then the flash write protect registers
+ * have been permanently committed and we can't fix that.
+ */
+ if (reset_flags & RESET_FLAG_POWER_ON) {
+ stuck_locked = 1;
+ return EC_ERROR_ACCESS_DENIED;
+ }
+
+ /* Otherwise, do a hard boot to clear the flash protection registers */
+ system_reset(SYSTEM_RESET_HARD | SYSTEM_RESET_PRESERVE_FLAGS);
+
+ /* That doesn't return, so if we're still here that's an error */
+ return EC_ERROR_UNKNOWN;
+}