| /* Copyright 2015 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. |
| */ |
| |
| /* |
| * The SoC's internal flash consists of two separate "banks" of 256K bytes each |
| * (sometimes called "macros" because of how they're implemented in Verilog). |
| * |
| * Each flash bank contains 128 "blocks" or "pages" of 2K bytes each. These |
| * blocks can be erased individually, or the entire bank can be erased at once. |
| * |
| * When the flash content is erased, all its bits are set to 1. |
| * |
| * The flash content can be read directly as bytes, halfwords, or words, just |
| * like any memory region. However, writes can only happen through special |
| * operations, in units of properly aligned 32-bit words. |
| * |
| * The flash controller has a 32-word write buffer. This allows up to 32 |
| * adjacent words (128 bytes) within a bank to be written in one operation. |
| * |
| * Multiple writes to the same flash word can be done without first erasing the |
| * block, however: |
| * |
| * A) writes can only change stored bits from 1 to 0, and |
| * |
| * B) the manufacturer recommends that no more than two writes be done between |
| * erase cycles for best results (in terms of reliability, longevity, etc.) |
| * |
| * All of this is fairly typical of most flash parts. This next thing is NOT |
| * typical: |
| * |
| * +--------------------------------------------------------------------------+ |
| * + While any write or erase operation is in progress, ALL other access to + |
| * + that entire bank is stalled. Data reads, instruction fetches, interrupt + |
| * + vector lookup -- every access blocks until the flash operation finishes. + |
| * +--------------------------------------------------------------------------+ |
| * |
| */ |
| |
| #include "common.h" |
| #include "console.h" |
| #include "cryptoc/util.h" |
| #include "flash.h" |
| #include "flash_config.h" |
| #include "flash_info.h" |
| #include "registers.h" |
| #include "shared_mem.h" |
| #include "timer.h" |
| #include "watchdog.h" |
| |
| #define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ## args) |
| |
| int flash_pre_init(void) |
| { |
| struct g_flash_region regions[4]; |
| int i, num_regions; |
| |
| num_regions = flash_regions_to_enable(regions, ARRAY_SIZE(regions)); |
| |
| for (i = 0; i < num_regions; i++) { |
| int reg_base; |
| |
| /* Region range */ |
| reg_base = GBASE(GLOBALSEC) + |
| GOFFSET(GLOBALSEC, FLASH_REGION2_BASE_ADDR) + |
| i * 8; |
| |
| REG32(reg_base) = regions[i].reg_base; |
| |
| /* |
| * The hardware requires a value which is 1 less than the |
| * actual region size. |
| */ |
| REG32(reg_base + 4) = regions[i].reg_size - 1; |
| |
| /* Region permissions. */ |
| reg_base = GBASE(GLOBALSEC) + |
| GOFFSET(GLOBALSEC, FLASH_REGION2_CTRL) + |
| i * 4; |
| REG32(reg_base) = regions[i].reg_perms; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| int flash_physical_get_protect(int bank) |
| { |
| return 0; /* Not protected */ |
| } |
| |
| uint32_t flash_physical_get_protect_flags(void) |
| { |
| return 0; /* no flags set */ |
| } |
| |
| uint32_t flash_physical_get_valid_flags(void) |
| { |
| /* These are the flags we're going to pay attention to */ |
| 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) |
| { |
| return 0; /* no flags writable */ |
| } |
| |
| int flash_physical_protect_at_boot(uint32_t new_flags) |
| { |
| return EC_SUCCESS; /* yeah, I did it. */ |
| } |
| |
| int flash_physical_protect_now(int all) |
| { |
| return EC_SUCCESS; /* yeah, I did it. */ |
| } |
| |
| |
| enum flash_op { |
| OP_ERASE_BLOCK, |
| OP_WRITE_BLOCK, |
| OP_READ_BLOCK, |
| }; |
| |
| static int do_flash_op(enum flash_op op, int is_info_bank, |
| int byte_offset, int words) |
| { |
| volatile uint32_t *fsh_pe_control; |
| uint32_t opcode, tmp, errors; |
| int retry_count, max_attempts, extra_prog_pulse, i; |
| int timedelay_us = 100; |
| uint32_t prev_error = 0; |
| |
| /* Make sure the smart program/erase algorithms are enabled. */ |
| if (!GREAD(FLASH, FSH_TIMING_PROG_SMART_ALGO_ON) || |
| !GREAD(FLASH, FSH_TIMING_ERASE_SMART_ALGO_ON)) { |
| CPRINTF("%s:%d\n", __func__, __LINE__); |
| return EC_ERROR_UNIMPLEMENTED; |
| } |
| |
| /* Error status is self-clearing. Read it until it does (we hope). */ |
| for (i = 0; i < 50; i++) { |
| tmp = GREAD(FLASH, FSH_ERROR); |
| if (!tmp) |
| break; |
| usleep(timedelay_us); |
| } |
| /* If we can't clear the error status register then something is wrong. |
| */ |
| if (tmp) { |
| CPRINTF("%s:%d\n", __func__, __LINE__); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| /* We have two flash banks. Adjust offset and registers accordingly. */ |
| if (is_info_bank) { |
| /* Only INFO bank operations are supported. */ |
| fsh_pe_control = GREG32_ADDR(FLASH, FSH_PE_CONTROL1); |
| } else if (byte_offset >= CFG_FLASH_HALF) { |
| byte_offset -= CFG_FLASH_HALF; |
| fsh_pe_control = GREG32_ADDR(FLASH, FSH_PE_CONTROL1); |
| } else { |
| fsh_pe_control = GREG32_ADDR(FLASH, FSH_PE_CONTROL0); |
| } |
| |
| /* What are we doing? */ |
| switch (op) { |
| case OP_ERASE_BLOCK: |
| #ifndef CR50_DEV |
| if (is_info_bank) |
| /* Erasing the INFO bank from the RW section is |
| * unsupported. */ |
| return EC_ERROR_INVAL; |
| #endif |
| opcode = 0x31415927; |
| words = 0; /* don't care, really */ |
| /* This number is based on the TSMC spec Nme=Terase/Tsme */ |
| max_attempts = 45; |
| break; |
| case OP_WRITE_BLOCK: |
| opcode = 0x27182818; |
| words--; /* count register is zero-based */ |
| /* This number is based on the TSMC spec Nmp=Tprog/Tsmp */ |
| max_attempts = 9; |
| break; |
| case OP_READ_BLOCK: |
| if (!is_info_bank) |
| /* This code path only supports reading from |
| * the INFO bank. |
| */ |
| return EC_ERROR_INVAL; |
| opcode = 0x16021765; |
| words = 1; |
| max_attempts = 9; |
| break; |
| default: |
| return EC_ERROR_INVAL; |
| } |
| |
| /* |
| * Set the parameters. For writes, we assume the write buffer is |
| * already filled before we call this function. |
| */ |
| GWRITE_FIELD(FLASH, FSH_TRANS, OFFSET, |
| byte_offset / 4); /* word offset */ |
| GWRITE_FIELD(FLASH, FSH_TRANS, MAINB, is_info_bank ? 1 : 0); |
| GWRITE_FIELD(FLASH, FSH_TRANS, SIZE, words); |
| |
| /* TODO: Make sure this function isn't getting called "too often" in |
| * between erases. |
| */ |
| extra_prog_pulse = 0; |
| for (retry_count = 0; retry_count < max_attempts; retry_count++) { |
| /* Kick it off */ |
| GWRITE(FLASH, FSH_PE_EN, 0xb11924e1); |
| *fsh_pe_control = opcode; |
| |
| /* Wait for completion. 150ms should be enough |
| * (crosbug.com/p/45366). |
| */ |
| for (i = 0; i < 1500; i++) { |
| tmp = *fsh_pe_control; |
| if (!tmp) |
| break; |
| usleep(timedelay_us); |
| } |
| |
| /* Timed out waiting for control register to clear */ |
| if (tmp) { |
| CPRINTF("%s:%d\n", __func__, __LINE__); |
| return EC_ERROR_UNKNOWN; |
| } |
| /* Check error status */ |
| errors = GREAD(FLASH, FSH_ERROR); |
| |
| if (errors && (errors != prev_error)) { |
| prev_error = errors; |
| CPRINTF("%s:%d errors %x fsh_pe_control %p\n", |
| __func__, __LINE__, errors, fsh_pe_control); |
| } |
| /* Error status is self-clearing. Read it until it does |
| * (we hope). |
| */ |
| for (i = 0; i < 50; i++) { |
| tmp = GREAD(FLASH, FSH_ERROR); |
| if (!tmp) |
| break; |
| usleep(timedelay_us); |
| } |
| /* If we can't clear the error status register then something |
| * is wrong. |
| */ |
| if (tmp) { |
| CPRINTF("%s:%d\n", __func__, __LINE__); |
| return EC_ERROR_UNKNOWN; |
| } |
| /* The operation was successful. */ |
| if (!errors) { |
| /* From the spec: |
| * "In addition, one more program pulse is needed after |
| * program verification is passed." |
| */ |
| if (op == OP_WRITE_BLOCK && !extra_prog_pulse) { |
| extra_prog_pulse = 1; |
| max_attempts++; |
| continue; |
| } |
| return EC_SUCCESS; |
| } |
| /* If there were errors after completion retry. */ |
| watchdog_reload(); |
| } |
| CPRINTF("%s:%d, retry count %d\n", __func__, __LINE__, retry_count); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| /* Write up to CONFIG_FLASH_WRITE_IDEAL_SIZE bytes at once */ |
| static int write_batch(int byte_offset, int is_info_bank, |
| int words, const uint8_t *data) |
| { |
| volatile uint32_t *fsh_wr_data = GREG32_ADDR(FLASH, FSH_WR_DATA0); |
| uint32_t val; |
| int i; |
| |
| /* Load the write buffer. */ |
| for (i = 0; i < words; i++) { |
| /* |
| * We have to write 32-bit values, but we can't guarantee |
| * alignment for the data. We'll just assemble the word |
| * manually to avoid alignment faults. Note that we're assuming |
| * little-endian order here. |
| */ |
| val = ((data[3] << 24) | (data[2] << 16) | |
| (data[1] << 8) | data[0]); |
| |
| *fsh_wr_data = val; |
| data += 4; |
| fsh_wr_data++; |
| } |
| |
| return do_flash_op(OP_WRITE_BLOCK, is_info_bank, byte_offset, words); |
| } |
| |
| static int flash_physical_write_internal(int byte_offset, int is_info_bank, |
| int num_bytes, const char *data) |
| { |
| int num, ret; |
| |
| /* The offset and size must be a multiple of CONFIG_FLASH_WRITE_SIZE */ |
| if (byte_offset % CONFIG_FLASH_WRITE_SIZE || |
| num_bytes % CONFIG_FLASH_WRITE_SIZE) |
| return EC_ERROR_INVAL; |
| |
| while (num_bytes) { |
| num = MIN(num_bytes, CONFIG_FLASH_WRITE_IDEAL_SIZE); |
| /* |
| * Make sure that the write operation will not go |
| * past a CONFIG_FLASH_ROW_SIZE boundary. |
| */ |
| num = MIN(num, CONFIG_FLASH_ROW_SIZE - |
| byte_offset % CONFIG_FLASH_ROW_SIZE); |
| ret = write_batch(byte_offset, |
| is_info_bank, |
| num / 4, /* word count */ |
| (const uint8_t *)data); |
| if (ret) |
| return ret; |
| |
| num_bytes -= num; |
| byte_offset += num; |
| data += num; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| int flash_physical_write(int byte_offset, int num_bytes, const char *data) |
| { |
| return flash_physical_write_internal(byte_offset, 0, num_bytes, data); |
| } |
| |
| int flash_physical_info_read_word(int byte_offset, uint32_t *dst) |
| { |
| int ret; |
| |
| if (byte_offset % CONFIG_FLASH_WRITE_SIZE) |
| return EC_ERROR_INVAL; |
| |
| ret = do_flash_op(OP_READ_BLOCK, 1, byte_offset, 1); |
| if (ret != EC_SUCCESS) |
| return ret; |
| |
| *dst = GREG32(FLASH, FSH_DOUT_VAL1); |
| return EC_SUCCESS; |
| } |
| |
| /* |
| * Verify that the range's size is power of 2, the range offset is aligned by |
| * size, and the range does not cross the INFO space boundary. |
| */ |
| static int valid_info_range(uint32_t offset, size_t size) |
| { |
| if (!size || (size & (size - 1))) |
| return 0; |
| |
| if (offset & (size - 1)) |
| return 0; |
| |
| if ((offset + size) > FLASH_INFO_SIZE) |
| return 0; |
| |
| return 1; |
| |
| } |
| |
| /* Write access is a superset of read access. */ |
| static int flash_info_configure_access(uint32_t offset, |
| size_t size, int write_mode) |
| { |
| int mask; |
| |
| if (!valid_info_range(offset, size)) |
| return EC_ERROR_INVAL; |
| |
| mask = GREG32(GLOBALSEC, FLASH_REGION6_CTRL); |
| mask |= GC_GLOBALSEC_FLASH_REGION6_CTRL_EN_MASK | |
| GC_GLOBALSEC_FLASH_REGION6_CTRL_RD_EN_MASK; |
| if (write_mode) |
| mask |= GC_GLOBALSEC_FLASH_REGION6_CTRL_WR_EN_MASK; |
| |
| GREG32(GLOBALSEC, FLASH_REGION6_BASE_ADDR) = |
| FLASH_INFO_MEMORY_BASE + offset; |
| |
| GREG32(GLOBALSEC, FLASH_REGION6_SIZE) = size - 1; |
| GREG32(GLOBALSEC, FLASH_REGION6_CTRL) = mask; |
| |
| return EC_SUCCESS; |
| } |
| |
| int flash_info_read_enable(uint32_t offset, size_t size) |
| { |
| return flash_info_configure_access(offset, size, 0); |
| } |
| |
| int flash_info_write_enable(uint32_t offset, size_t size) |
| { |
| return flash_info_configure_access(offset, size, 1); |
| } |
| |
| void flash_info_write_disable(void) |
| { |
| GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, WR_EN, 0); |
| } |
| |
| int flash_info_physical_write(int byte_offset, int num_bytes, const char *data) |
| { |
| if (byte_offset < 0 || num_bytes < 0 || |
| byte_offset + num_bytes > FLASH_INFO_SIZE || |
| (byte_offset | num_bytes) & (CONFIG_FLASH_WRITE_SIZE - 1)) |
| return EC_ERROR_INVAL; |
| |
| return flash_physical_write_internal(byte_offset, 1, num_bytes, data); |
| } |
| |
| int flash_physical_erase(int byte_offset, int num_bytes) |
| { |
| int ret; |
| |
| /* Offset and size must be a multiple of CONFIG_FLASH_ERASE_SIZE */ |
| if (byte_offset % CONFIG_FLASH_ERASE_SIZE || |
| num_bytes % CONFIG_FLASH_ERASE_SIZE) |
| return EC_ERROR_INVAL; |
| |
| while (num_bytes) { |
| /* We may be asked to erase multiple banks */ |
| ret = do_flash_op(OP_ERASE_BLOCK, |
| 0, /* not the INFO bank */ |
| byte_offset, |
| num_bytes / 4); /* word count */ |
| if (ret) { |
| CPRINTF("Failed to erase block at %x\n", byte_offset); |
| return ret; |
| } |
| |
| num_bytes -= CONFIG_FLASH_ERASE_SIZE; |
| byte_offset += CONFIG_FLASH_ERASE_SIZE; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| |
| /* Enable write access to the backup RO section. */ |
| void flash_open_ro_window(uint32_t offset, size_t size_b) |
| { |
| GREG32(GLOBALSEC, FLASH_REGION6_BASE_ADDR) = |
| offset + CONFIG_PROGRAM_MEMORY_BASE; |
| GREG32(GLOBALSEC, FLASH_REGION6_SIZE) = size_b - 1; |
| GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, EN, 1); |
| GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, RD_EN, 1); |
| GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, WR_EN, 1); |
| } |
| |
| #ifdef CR50_DEV |
| |
| static int command_erase_flash_info(int argc, char **argv) |
| { |
| uint32_t *preserved_manufacture_state; |
| const size_t manuf_word_count = FLASH_INFO_MANUFACTURE_STATE_SIZE / |
| sizeof(uint32_t); |
| int i; |
| int rv = EC_ERROR_BUSY; |
| |
| if (shared_mem_acquire(FLASH_INFO_MANUFACTURE_STATE_SIZE, |
| (char **)&preserved_manufacture_state) != |
| EC_SUCCESS) { |
| ccprintf("Failed to allocate memory for manufacture state!\n"); |
| return rv; |
| } |
| |
| flash_info_read_enable(0, 2048); |
| flash_info_write_enable(0, 2048); |
| |
| /* Preserve manufacturing information. */ |
| for (i = 0; i < manuf_word_count; i++) { |
| if (flash_physical_info_read_word |
| (FLASH_INFO_MANUFACTURE_STATE_OFFSET + |
| i * sizeof(uint32_t), |
| preserved_manufacture_state + i) != EC_SUCCESS) { |
| ccprintf("Failed to read word %d!\n", i); |
| goto exit; |
| } |
| } |
| |
| if (do_flash_op(OP_ERASE_BLOCK, 1, 0, 512) != EC_SUCCESS) { |
| ccprintf("Failed to erase info space!\n"); |
| goto exit; |
| } |
| |
| if (flash_info_physical_write |
| (FLASH_INFO_MANUFACTURE_STATE_OFFSET, |
| FLASH_INFO_MANUFACTURE_STATE_SIZE, |
| (char *)preserved_manufacture_state) != EC_SUCCESS) { |
| ccprintf("Failed to restore manufacture state!\n"); |
| goto exit; |
| } |
| |
| rv = EC_SUCCESS; |
| exit: |
| always_memset(preserved_manufacture_state, 0, |
| FLASH_INFO_MANUFACTURE_STATE_SIZE); |
| shared_mem_release(preserved_manufacture_state); |
| flash_info_write_disable(); |
| return rv; |
| } |
| DECLARE_CONSOLE_COMMAND(eraseflashinfo, command_erase_flash_info, |
| "", |
| "Erase INFO1 flash space"); |
| #endif |