| /* Copyright 2019 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* MAX32660 Flash Memory Module for Chrome EC */ |
| |
| #include "common.h" |
| #include "flash.h" |
| #include "flc_regs.h" |
| #include "icc_regs.h" |
| #include "registers.h" |
| #include "switch.h" |
| #include "system.h" |
| #include "timer.h" |
| #include "util.h" |
| #include "watchdog.h" |
| |
| #define CPUTS(outstr) cputs(CC_SYSTEM, outstr) |
| #define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ##args) |
| |
| /***** Definitions *****/ |
| |
| /// Bit mask that can be used to find the starting address of a page in flash |
| #define MXC_FLASH_PAGE_MASK ~(MXC_FLASH_PAGE_SIZE - 1) |
| |
| /// Calculate the address of a page in flash from the page number |
| #define MXC_FLASH_PAGE_ADDR(page) \ |
| (MXC_FLASH_MEM_BASE + ((unsigned long)page * MXC_FLASH_PAGE_SIZE)) |
| |
| void flash_operation(void) |
| { |
| volatile uint32_t *line_addr; |
| volatile uint32_t __attribute__((unused)) line; |
| |
| // Clear the cache |
| MXC_ICC->cache_ctrl ^= MXC_F_ICC_CACHE_CTRL_CACHE_EN; |
| MXC_ICC->cache_ctrl ^= MXC_F_ICC_CACHE_CTRL_CACHE_EN; |
| |
| // Clear the line fill buffer |
| line_addr = (uint32_t *)(MXC_FLASH_MEM_BASE); |
| line = *line_addr; |
| |
| line_addr = (uint32_t *)(MXC_FLASH_MEM_BASE + MXC_FLASH_PAGE_SIZE); |
| line = *line_addr; |
| } |
| |
| static int flash_busy(void) |
| { |
| return (MXC_FLC->cn & |
| (MXC_F_FLC_CN_WR | MXC_F_FLC_CN_ME | MXC_F_FLC_CN_PGE)); |
| } |
| |
| static int flash_init_controller(void) |
| { |
| // Set flash clock divider to generate a 1MHz clock from the APB clock |
| MXC_FLC->clkdiv = SystemCoreClock / 1000000; |
| |
| /* Check if the flash controller is busy */ |
| if (flash_busy()) { |
| return EC_ERROR_BUSY; |
| } |
| |
| /* Clear stale errors */ |
| if (MXC_FLC->intr & MXC_F_FLC_INTR_AF) { |
| MXC_FLC->intr &= ~MXC_F_FLC_INTR_AF; |
| } |
| |
| /* Unlock flash */ |
| MXC_FLC->cn = (MXC_FLC->cn & ~MXC_F_FLC_CN_UNLOCK) | |
| MXC_S_FLC_CN_UNLOCK_UNLOCKED; |
| |
| return EC_SUCCESS; |
| } |
| |
| static int flash_device_page_erase(uint32_t address) |
| { |
| int err; |
| |
| if ((err = flash_init_controller()) != EC_SUCCESS) |
| return err; |
| |
| // Align address on page boundary |
| address = address - (address % MXC_FLASH_PAGE_SIZE); |
| |
| /* Write paflash_init_controllerde */ |
| MXC_FLC->cn = (MXC_FLC->cn & ~MXC_F_FLC_CN_ERASE_CODE) | |
| MXC_S_FLC_CN_ERASE_CODE_ERASEPAGE; |
| /* Issue page erase command */ |
| MXC_FLC->addr = address; |
| MXC_FLC->cn |= MXC_F_FLC_CN_PGE; |
| |
| /* Wait until flash operation is complete */ |
| while (flash_busy()) |
| ; |
| |
| /* Lock flash */ |
| MXC_FLC->cn &= ~MXC_F_FLC_CN_UNLOCK; |
| |
| /* Check access violations */ |
| if (MXC_FLC->intr & MXC_F_FLC_INTR_AF) { |
| MXC_FLC->intr &= ~MXC_F_FLC_INTR_AF; |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| flash_operation(); |
| |
| return EC_SUCCESS; |
| } |
| |
| int crec_flash_physical_write(int offset, int size, const char *data) |
| { |
| int err; |
| uint32_t bytes_written; |
| uint8_t current_data[4]; |
| |
| if ((err = flash_init_controller()) != EC_SUCCESS) |
| return err; |
| |
| // write in 32-bit units until we are 128-bit aligned |
| MXC_FLC->cn &= ~MXC_F_FLC_CN_BRST; |
| MXC_FLC->cn |= MXC_F_FLC_CN_WDTH; |
| |
| // Align the address and read/write if we have to |
| if (offset & 0x3) { |
| // Figure out how many bytes we have to write to round up the |
| // address |
| bytes_written = 4 - (offset & 0x3); |
| |
| // Save the data currently in the flash |
| memcpy(current_data, (void *)(offset & (~0x3)), 4); |
| |
| // Modify current_data to insert the data from buffer |
| memcpy(¤t_data[4 - bytes_written], data, bytes_written); |
| |
| // Write the modified data |
| MXC_FLC->addr = offset - (offset % 4); |
| memcpy((void *)&MXC_FLC->data[0], ¤t_data, 4); |
| MXC_FLC->cn |= MXC_F_FLC_CN_WR; |
| |
| /* Wait until flash operation is complete */ |
| while (flash_busy()) |
| ; |
| |
| offset += bytes_written; |
| size -= bytes_written; |
| data += bytes_written; |
| } |
| |
| while ((size >= 4) && ((offset & 0x1F) != 0)) { |
| MXC_FLC->addr = offset; |
| memcpy((void *)&MXC_FLC->data[0], data, 4); |
| MXC_FLC->cn |= MXC_F_FLC_CN_WR; |
| |
| /* Wait until flash operation is complete */ |
| while (flash_busy()) |
| ; |
| |
| offset += 4; |
| size -= 4; |
| data += 4; |
| } |
| |
| if (size >= 16) { |
| // write in 128-bit bursts while we can |
| MXC_FLC->cn &= ~MXC_F_FLC_CN_WDTH; |
| |
| while (size >= 16) { |
| MXC_FLC->addr = offset; |
| memcpy((void *)&MXC_FLC->data[0], data, 16); |
| MXC_FLC->cn |= MXC_F_FLC_CN_WR; |
| |
| /* Wait until flash operation is complete */ |
| while (flash_busy()) |
| ; |
| |
| offset += 16; |
| size -= 16; |
| data += 16; |
| } |
| |
| // Return to 32-bit writes. |
| MXC_FLC->cn |= MXC_F_FLC_CN_WDTH; |
| } |
| |
| while (size >= 4) { |
| MXC_FLC->addr = offset; |
| memcpy((void *)&MXC_FLC->data[0], data, 4); |
| MXC_FLC->cn |= MXC_F_FLC_CN_WR; |
| |
| /* Wait until flash operation is complete */ |
| while (flash_busy()) |
| ; |
| |
| offset += 4; |
| size -= 4; |
| data += 4; |
| } |
| |
| if (size > 0) { |
| // Save the data currently in the flash |
| memcpy(current_data, (void *)(offset), 4); |
| |
| // Modify current_data to insert the data from data |
| memcpy(current_data, data, size); |
| |
| MXC_FLC->addr = offset; |
| memcpy((void *)&MXC_FLC->data[0], current_data, 4); |
| MXC_FLC->cn |= MXC_F_FLC_CN_WR; |
| |
| /* Wait until flash operation is complete */ |
| while (flash_busy()) |
| ; |
| } |
| |
| /* Lock flash */ |
| MXC_FLC->cn &= ~MXC_F_FLC_CN_UNLOCK; |
| |
| /* Check access violations */ |
| if (MXC_FLC->intr & MXC_F_FLC_INTR_AF) { |
| MXC_FLC->intr &= ~MXC_F_FLC_INTR_AF; |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| flash_operation(); |
| |
| return EC_SUCCESS; |
| } |
| |
| /*****************************************************************************/ |
| /* Physical layer APIs */ |
| |
| int crec_flash_physical_erase(int offset, int size) |
| { |
| int i; |
| int pages; |
| int error_status; |
| |
| /* |
| * erase 'size' number of bytes starting at address 'offset' |
| */ |
| /* calculate the number of pages */ |
| pages = size / CONFIG_FLASH_ERASE_SIZE; |
| /* iterate over the number of pages */ |
| for (i = 0; i < pages; i++) { |
| /* erase the page after calculating the start address */ |
| error_status = flash_device_page_erase( |
| offset + (i * CONFIG_FLASH_ERASE_SIZE)); |
| if (error_status != EC_SUCCESS) { |
| return error_status; |
| } |
| } |
| return EC_SUCCESS; |
| } |
| |
| int crec_flash_physical_get_protect(int bank) |
| { |
| /* Not protected */ |
| return 0; |
| } |
| |
| uint32_t crec_flash_physical_get_protect_flags(void) |
| { |
| /* no flags set */ |
| return 0; |
| } |
| |
| uint32_t crec_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 crec_flash_physical_get_writable_flags(uint32_t cur_flags) |
| { |
| /* no flags writable */ |
| return 0; |
| } |
| |
| int crec_flash_physical_protect_at_boot(uint32_t new_flags) |
| { |
| /* nothing to do here */ |
| return EC_SUCCESS; |
| } |
| |
| int crec_flash_physical_protect_now(bool all) |
| { |
| /* nothing to do here */ |
| return EC_SUCCESS; |
| } |
| |
| /*****************************************************************************/ |
| /* High-level APIs */ |
| |
| int crec_flash_pre_init(void) |
| { |
| return EC_SUCCESS; |
| } |
| |
| /*****************************************************************************/ |
| /* Test Commands */ |
| |
| /* |
| * Read, Write, and Erase a page of flash memory using chip routines |
| * NOTE: This is a DESTRUCTIVE test for the range of flash pages tested |
| * make sure that PAGE_START is beyond your flash code. |
| */ |
| static int command_flash_test1(int argc, const char **argv) |
| { |
| int i; |
| uint8_t *ptr; |
| const uint32_t PAGE_START = 9; |
| const uint32_t PAGE_END = 32; |
| uint32_t page; |
| int error_status; |
| uint32_t flash_address; |
| const int BUFFER_SIZE = 32; |
| uint8_t buffer[BUFFER_SIZE]; |
| |
| /* |
| * As a test, write unique data to each page in this for loop, later |
| * verify data in pages |
| */ |
| for (page = PAGE_START; page < PAGE_END; page++) { |
| flash_address = page * CONFIG_FLASH_ERASE_SIZE; |
| |
| /* |
| * erase page |
| */ |
| error_status = crec_flash_physical_erase( |
| flash_address, CONFIG_FLASH_ERASE_SIZE); |
| if (error_status != EC_SUCCESS) { |
| CPRINTS("Error with crec_flash_physical_erase"); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| /* |
| * verify page was erased |
| */ |
| // CPRINTS("read flash page %d, address %x, ", page, |
| // flash_address); |
| ptr = (uint8_t *)flash_address; |
| for (i = 0; i < CONFIG_FLASH_ERASE_SIZE; i++) { |
| if (*ptr++ != 0xff) { |
| CPRINTS("Error with verifying page erase"); |
| return EC_ERROR_UNKNOWN; |
| } |
| } |
| |
| /* |
| * write pattern to page, just write BUFFER_SIZE worth of data |
| */ |
| for (i = 0; i < BUFFER_SIZE; i++) { |
| buffer[i] = i + page; |
| } |
| error_status = crec_flash_physical_write(flash_address, |
| BUFFER_SIZE, buffer); |
| if (error_status != EC_SUCCESS) { |
| CPRINTS("Error with crec_flash_physical_write"); |
| return EC_ERROR_UNKNOWN; |
| } |
| } |
| |
| /* |
| * Verify data in pages |
| */ |
| for (page = PAGE_START; page < PAGE_END; page++) { |
| flash_address = page * CONFIG_FLASH_ERASE_SIZE; |
| |
| /* |
| * read a portion of flash memory |
| */ |
| ptr = (uint8_t *)flash_address; |
| for (i = 0; i < BUFFER_SIZE; i++) { |
| if (*ptr++ != (i + page)) { |
| CPRINTS("Error with verifing written test " |
| "data\n"); |
| return EC_ERROR_UNKNOWN; |
| } |
| } |
| CPRINTS("Verified Erase, Write, Read page %d", page); |
| } |
| |
| /* |
| * Clean up after tests |
| */ |
| for (page = PAGE_START; page <= PAGE_END; page++) { |
| flash_address = page * CONFIG_FLASH_ERASE_SIZE; |
| error_status = crec_flash_physical_erase( |
| flash_address, CONFIG_FLASH_ERASE_SIZE); |
| if (error_status != EC_SUCCESS) { |
| CPRINTS("Error with crec_flash_physical_erase"); |
| return EC_ERROR_UNKNOWN; |
| } |
| } |
| |
| CPRINTS("done command_flash_test1."); |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(flashtest1, command_flash_test1, "flashtest1", |
| "Flash chip routine tests"); |