diff options
author | Vic Yang <victoryang@chromium.org> | 2014-10-09 14:04:32 -0700 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2014-10-15 20:49:35 +0000 |
commit | 4a9cabc3f9e8cfbeee857180c8a0b400ef7c8092 (patch) | |
tree | 6cdc15de6e9995be04351870030ef334fee9459d /chip/stm32/flash-f.c | |
parent | b8f73a451d9f0e62aba43dab1184bc1352ee968f (diff) | |
download | chrome-ec-4a9cabc3f9e8cfbeee857180c8a0b400ef7c8092.tar.gz |
Factor out common flash code for STM32F and STM32F0
This is a preparatory work for the following change for write protection
support on STM32F0.
BUG=chrome-os-partner:32745
TEST=make buildall
BRANCH=samus
Change-Id: Ic4deea06e26c4a6ac024a5388e1a5783b40e9876
Signed-off-by: Vic Yang <victoryang@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/222660
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Diffstat (limited to 'chip/stm32/flash-f.c')
-rw-r--r-- | chip/stm32/flash-f.c | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/chip/stm32/flash-f.c b/chip/stm32/flash-f.c new file mode 100644 index 0000000000..29e2c062d7 --- /dev/null +++ b/chip/stm32/flash-f.c @@ -0,0 +1,448 @@ +/* Copyright 2014 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. + */ + +/* Common flash memory module for STM32F and STM32F0 */ + +#include "battery.h" +#include "console.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 16000 +#define FLASH_TIMEOUT_LOOP \ + (FLASH_TIMEOUT_US * (CPU_CLOCK / SECOND) / CYCLE_PER_FLASH_LOOP) + +/* Flash unlocking keys */ +#define KEY1 0x45670123 +#define KEY2 0xCDEF89AB + +/* Lock bits for FLASH_CR register */ +#define PG (1<<0) +#define PER (1<<1) +#define OPTPG (1<<4) +#define OPTER (1<<5) +#define STRT (1<<6) +#define CR_LOCK (1<<7) +#define PRG_LOCK 0 +#define OPT_LOCK (1<<9) + +static int write_optb(int byte, uint8_t value); + +static int wait_busy(void) +{ + int timeout = FLASH_TIMEOUT_LOOP; + while (STM32_FLASH_SR & (1 << 0) && timeout-- > 0) + udelay(CYCLE_PER_FLASH_LOOP); + 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 & CR_LOCK) { + STM32_FLASH_KEYR = KEY1; + STM32_FLASH_KEYR = KEY2; + } + /* unlock option memory if required */ + if ((locks & OPT_LOCK) && !(STM32_FLASH_CR & OPT_LOCK)) { + STM32_FLASH_OPTKEYR = KEY1; + STM32_FLASH_OPTKEYR = KEY2; + } + + /* Re-enable bus fault handler */ + ignore_bus_fault(0); + + return ((STM32_FLASH_CR ^ OPT_LOCK) & (locks | CR_LOCK)) ? + EC_ERROR_UNKNOWN : EC_SUCCESS; +} + +static void lock(void) +{ + STM32_FLASH_CR = CR_LOCK; +} + +/* + * Option byte organization + * + * [31:24] [23:16] [15:8] [7:0] + * + * 0x1FFF_F800 nUSER USER nRDP RDP + * + * 0x1FFF_F804 nData1 Data1 nData0 Data0 + * + * 0x1FFF_F808 nWRP1 WRP1 nWRP0 WRP0 + * + * 0x1FFF_F80C nWRP3 WRP2 nWRP2 WRP2 + * + * Note that the variable with n prefix means the complement. + */ +static uint8_t read_optb(int byte) +{ + return *(uint8_t *)(STM32_OPTB_BASE + byte); +} + +static int erase_optb(void) +{ + int rv; + + rv = wait_busy(); + if (rv) + return rv; + + rv = unlock(OPT_LOCK); + if (rv) + return rv; + + /* Must be set in 2 separate lines. */ + STM32_FLASH_CR |= OPTER; + STM32_FLASH_CR |= STRT; + + rv = wait_busy(); + if (rv) + return rv; + lock(); + + return EC_SUCCESS; +} + +/* + * Since the option byte erase is WHOLE erase, this function is to keep + * rest of bytes, but make this byte 0xff. + * Note that this could make a recursive call to write_optb(). + */ +static int preserve_optb(int byte) +{ + int i, rv; + uint8_t optb[8]; + + /* The byte has been reset, no need to run preserve. */ + if (*(uint16_t *)(STM32_OPTB_BASE + byte) == 0xffff) + return EC_SUCCESS; + + for (i = 0; i < ARRAY_SIZE(optb); ++i) + optb[i] = read_optb(i * 2); + + optb[byte / 2] = 0xff; + + rv = erase_optb(); + if (rv) + return rv; + for (i = 0; i < ARRAY_SIZE(optb); ++i) { + rv = write_optb(i * 2, optb[i]); + if (rv) + return rv; + } + + return EC_SUCCESS; +} + +static int write_optb(int byte, uint8_t value) +{ + volatile int16_t *hword = (uint16_t *)(STM32_OPTB_BASE + byte); + int rv; + + rv = wait_busy(); + if (rv) + return rv; + + /* The target byte is the value we want to write. */ + if (*(uint8_t *)hword == value) + return EC_SUCCESS; + + /* Try to erase that byte back to 0xff. */ + rv = preserve_optb(byte); + if (rv) + return rv; + + /* The value is 0xff after erase. No need to write 0xff again. */ + if (value == 0xff) + return EC_SUCCESS; + + rv = unlock(OPT_LOCK); + if (rv) + return rv; + + /* set OPTPG bit */ + STM32_FLASH_CR |= OPTPG; + + *hword = ((~value) << STM32_OPTB_COMPL_SHIFT) | value; + + /* reset OPTPG bit */ + STM32_FLASH_CR &= ~OPTPG; + + rv = wait_busy(); + if (rv) + return rv; + lock(); + + return EC_SUCCESS; +} + +/*****************************************************************************/ +/* Physical layer APIs */ + +int flash_physical_write(int offset, int size, const char *data) +{ + uint16_t *address = (uint16_t *)(CONFIG_FLASH_BASE + offset); + int res = EC_SUCCESS; + int i; + + if (unlock(PRG_LOCK) != EC_SUCCESS) { + res = EC_ERROR_UNKNOWN; + goto exit_wr; + } + + /* Clear previous error status */ + STM32_FLASH_SR = 0x34; + + /* set PG bit */ + STM32_FLASH_CR |= PG; + + for (; size > 0; size -= sizeof(uint16_t)) { + /* + * Reload the watchdog timer to avoid watchdog reset when doing + * long writing with interrupt disabled. + */ + watchdog_reload(); + + /* wait to be ready */ + for (i = 0; (STM32_FLASH_SR & 1) && (i < FLASH_TIMEOUT_LOOP); + i++) + ; + + /* write the half word */ + *address++ = data[0] + (data[1] << 8); + data += 2; + + /* Wait for writes to complete */ + for (i = 0; (STM32_FLASH_SR & 1) && (i < FLASH_TIMEOUT_LOOP); + i++) + ; + + if (STM32_FLASH_SR & 1) { + res = EC_ERROR_TIMEOUT; + goto exit_wr; + } + + /* Check for error conditions - erase failed, voltage error, + * protection error */ + if (STM32_FLASH_SR & 0x14) { + res = EC_ERROR_UNKNOWN; + goto exit_wr; + } + } + +exit_wr: + /* Disable PG bit */ + STM32_FLASH_CR &= ~PG; + + lock(); + + return res; +} + +int flash_physical_erase(int offset, int size) +{ + int res = EC_SUCCESS; + + if (unlock(PRG_LOCK) != EC_SUCCESS) + return EC_ERROR_UNKNOWN; + + /* Clear previous error status */ + STM32_FLASH_SR = 0x34; + + /* set PER bit */ + STM32_FLASH_CR |= PER; + + for (; size > 0; size -= CONFIG_FLASH_ERASE_SIZE, + offset += CONFIG_FLASH_ERASE_SIZE) { + timestamp_t deadline; + + /* Do nothing if already erased */ + if (flash_is_erased(offset, CONFIG_FLASH_ERASE_SIZE)) + continue; + + /* select page to erase */ + STM32_FLASH_AR = CONFIG_FLASH_BASE + offset; + + /* set STRT bit : start erase */ + STM32_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 & 1) && + (get_time().val < deadline.val)) { + usleep(300); + } + if (STM32_FLASH_SR & 1) { + res = EC_ERROR_TIMEOUT; + goto exit_er; + } + + /* + * Check for error conditions - erase failed, voltage error, + * protection error + */ + if (STM32_FLASH_SR & 0x14) { + res = EC_ERROR_UNKNOWN; + goto exit_er; + } + } + +exit_er: + /* reset PER bit */ + STM32_FLASH_CR &= ~PER; + + lock(); + + return res; +} + +static int flash_physical_get_protect_at_boot(int block) +{ + uint8_t val = read_optb(STM32_OPTB_WRP_OFF(block/8)); + return (!(val & (1 << (block % 8)))) ? 1 : 0; +} + +int flash_physical_protect_at_boot(enum flash_wp_range range) +{ + int block; + int i; + int original_val[4], val[4]; + enum flash_wp_range cur_range; + + for (i = 0; i < 4; ++i) + original_val[i] = val[i] = read_optb(i * 2 + 8); + + for (block = RO_BANK_OFFSET; + block < RO_BANK_OFFSET + PHYSICAL_BANKS; + block++) { + int byte_off = STM32_OPTB_WRP_OFF(block/8) / 2 - 4; + + if (block >= RO_BANK_OFFSET + RO_BANK_COUNT + PSTATE_BANK_COUNT) + cur_range = FLASH_WP_ALL; + else + cur_range = FLASH_WP_RO; + + if (cur_range <= range) + val[byte_off] = val[byte_off] & (~(1 << (block % 8))); + else + val[byte_off] = val[byte_off] | (1 << (block % 8)); + } + + for (i = 0; i < 4; ++i) + if (original_val[i] != val[i]) + write_optb(i * 2 + 8, val[i]); + + 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 i; + int ro_at_boot = (flags & EC_FLASH_PROTECT_RO_AT_BOOT) ? 1 : 0; + int ro_wp_region_start = RO_BANK_OFFSET; + int ro_wp_region_end = + RO_BANK_OFFSET + RO_BANK_COUNT + PSTATE_BANK_COUNT; + + for (i = ro_wp_region_start; i < ro_wp_region_end; i++) + if (flash_physical_get_protect_at_boot(i) != ro_at_boot) + return 1; + return 0; +} + +static void unprotect_all_blocks(void) +{ + int i; + for (i = 4; i < 8; ++i) + write_optb(i * 2, 0xff); +} + +/*****************************************************************************/ +/* High-level APIs */ + +int flash_pre_init(void) +{ + uint32_t prot_flags = flash_get_protect(); + int need_reset = 0; + + if (flash_physical_restore_state()) + 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(FLASH_WP_RO); + 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_ro_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 (need_reset) + system_reset(SYSTEM_RESET_HARD | SYSTEM_RESET_PRESERVE_FLAGS); + + return EC_SUCCESS; +} |