From 75e64206eb34220ed6ee2f799eddcf207e408abb Mon Sep 17 00:00:00 2001 From: Alexander Potashev <aspotashev@emcraft.com> Date: Wed, 30 Nov 2011 19:45:03 +0400 Subject: [PATCH] RT73025. ea-lpc1788: self-upgrade We use IAP commands for writing into eNVM. All code and pre-initialized data that are used during self-upgrade are forced to reside in `.ramcode` and `.data` respectively. Uninitialized data go into `.bss` automatically. --- cpu/arm_cortexm3/lpc178x/clock.c | 11 +- cpu/arm_cortexm3/lpc178x/envm.c | 383 ++++++++++++++++++++++++- include/asm-arm/arch-lpc178x/lpc178x.h | 15 +- include/configs/ea-lpc1788.h | 4 +- 4 files changed, 389 insertions(+), 24 deletions(-) diff --git a/cpu/arm_cortexm3/lpc178x/clock.c b/cpu/arm_cortexm3/lpc178x/clock.c index 1d015258..1436eed3 100644 --- a/cpu/arm_cortexm3/lpc178x/clock.c +++ b/cpu/arm_cortexm3/lpc178x/clock.c @@ -421,14 +421,15 @@ void clock_init(void) /* * Return a clock value for the specified clock. - * Note that we need this function in RAM because it will be used - * during self-upgrade of U-boot into eNMV. + * + * Note that we do not need this function in RAM (.ramcode) because it will + * be used during self-upgrade of U-boot into eNMV only once, before the data + * in the eNMV will be actually changed. + * * @param clck id of the clock * @returns frequency of the clock */ -unsigned long __attribute__((section(".ramcode"))) - __attribute__ ((long_call)) - clock_get(enum clock clck) +unsigned long clock_get(enum clock clck) { return clock_val[clck]; } diff --git a/cpu/arm_cortexm3/lpc178x/envm.c b/cpu/arm_cortexm3/lpc178x/envm.c index 41ac08fe..a2fc7ec7 100644 --- a/cpu/arm_cortexm3/lpc178x/envm.c +++ b/cpu/arm_cortexm3/lpc178x/envm.c @@ -20,19 +20,363 @@ */ #include <common.h> - +#include <errno.h> #include "envm.h" +/* + * IAP library for editing eNVM + */ + +/* IAP function pointer */ +#define IAP_LOCATION 0x1FFF1FF1 +typedef void (*lpc178x_iap) (u32 *, u32 *); + +/* IAP commands */ +#define IAP_CMD_PREP_SECTORS 50 +#define IAP_CMD_RAM_TO_FLASH 51 +#define IAP_CMD_ERASE_SECTORS 52 +#define IAP_CMD_BLANK_CHECK_SECTORS 53 +#define IAP_CMD_READ_PART_ID 54 + +/* IAP statuses */ +#define IAP_STATUS_SUCCESS 0 + +/* + * Block size used for the "Copy RAM to Flash" operation + * + * Can be 256, 512, 1024 or 4096 + */ +#define IAP_BLOCK_SIZE 256 +#define IAP_BLOCK_MASK (IAP_BLOCK_SIZE - 1) + +static u32 iap_num_sectors; /* Number of eNVM sectors */ +static u32 iap_flash_size; /* Size of eNVM in bytes */ +static u32 iap_clkrate; /* CPU clock in KHz */ + +/* + * List of block addresses and sizes + * + * IMPORTANT: We force this data into the `.data` section, because otherwise + * it will be in eNVM and will be overwritten on self-upgrade. + */ +static lpc178x_iap lpc178x_iap_entry __attribute__((section(".data"))) = + (lpc178x_iap) IAP_LOCATION; +static u32 flash_bsize[] __attribute__((section(".data"))) = { + [0 ... 15] = 4 * 1024, + [16 ... 29] = 32 * 1024 +}; + +static u32 iap_commands[5]; +static u32 iap_results[5]; + +/* + * A temporary buffer in SRAM (section `.bss`) used for the "Copy RAM to Flash" + * operation. We need this buffer, because direct transfer from the external RAM + * does not work. + * + * This buffer should be aligned on a 4 byte boundary. + */ +static u8 iap_sram_buf[IAP_BLOCK_SIZE] __attribute__((aligned(4))); + +/* + * Prepare sectors for erase or write + */ +int __attribute__((section(".ramcode"))) + __attribute__((long_call)) +lpc178x_iap_prepare_sectors(u32 start, u32 end) +{ + iap_commands[0] = IAP_CMD_PREP_SECTORS; + iap_commands[1] = start; + iap_commands[2] = end; + + lpc178x_iap_entry(iap_commands, iap_results); + + return iap_results[0]; +} + +/* + * Copy RAM to FLASH + */ +int __attribute__((section(".ramcode"))) + __attribute__((long_call)) +lpc178x_iap_ram_to_flash(u32 dst, u32 src, u32 bytes) +{ + iap_commands[0] = IAP_CMD_RAM_TO_FLASH; + iap_commands[1] = dst; + iap_commands[2] = src; + iap_commands[3] = bytes; + iap_commands[4] = iap_clkrate; + + lpc178x_iap_entry(iap_commands, iap_results); + + return iap_results[0]; +} + +/* + * Erase sectors + */ +int __attribute__((section(".ramcode"))) + __attribute__((long_call)) +lpc178x_iap_erase_sectors(u32 start, u32 end) +{ + iap_commands[0] = IAP_CMD_ERASE_SECTORS; + iap_commands[1] = start; + iap_commands[2] = end; + iap_commands[3] = iap_clkrate; + + lpc178x_iap_entry(iap_commands, iap_results); + + return iap_results[0]; +} + +/* + * Blank check sectors + */ +int __attribute__((section(".ramcode"))) + __attribute__((long_call)) +lpc178x_iap_blank_check_sectors( + u32 start, u32 end, u32 *bad_addr, u32 *bad_data) +{ + iap_commands[0] = IAP_CMD_BLANK_CHECK_SECTORS; + iap_commands[1] = start; + iap_commands[2] = end; + + lpc178x_iap_entry(iap_commands, iap_results); + + *bad_addr = iap_results[1]; + *bad_data = iap_results[2]; + + return iap_results[0]; +} + +/* + * Read part identification number + * + * This function should not be in .ramcode, because it will be called only once + * before self-upgrade. + */ +u32 lpc178x_iap_read_part_id_num(void) +{ + iap_commands[0] = IAP_CMD_READ_PART_ID; + + lpc178x_iap_entry(iap_commands, iap_results); + + return iap_results[1]; +} + +/* + * Initialize IAP library - call this first + * + * This function should not be in .ramcode, because it will be called only once + * before self-upgrade. + */ +void lpc178x_iap_init(void) +{ + u32 val; + + /* Read part ID first to determine size of FLASH */ + val = lpc178x_iap_read_part_id_num(); + + /* Decode part ID to get number of sectors and size */ + if ((val & 0x08000000) != 0) { + /* LPC178x */ + if ((val & 0x00000004) != 0) { + /* LPC1787, LPC1788 */ + iap_flash_size = 512 * 1024; + iap_num_sectors = 30; + } else { + /* LPC1785, LPC1786 */ + iap_flash_size = 256 * 1024; + iap_num_sectors = 22; + } + } else { + /* LPC177x */ + if ((val & 0x00000004) != 0) { + /* LPC1777, LPC1778 */ + iap_flash_size = 512 * 1024; + iap_num_sectors = 30; + } else if ((val & 0x00000002) != 0) { + /* LPC1776 */ + iap_flash_size = 256 * 1024; + iap_num_sectors = 22; + } else { + /* LPC1774 */ + iap_flash_size = 128 * 1024; + iap_num_sectors = 18; + } + } +} + /* * Initialize internal Flash interface + * + * This function should not be in .ramcode, because it will be called only once + * before self-upgrade. */ void envm_init(void) { + lpc178x_iap_init(); +} + +/* + * `addr` is the offset from the beginning of eNVM + */ +static u32 __attribute__((section(".ramcode"))) + __attribute__((long_call)) +find_sector(u32 addr) { + u32 i; + u32 sect_base = 0; + + for (i = 0; i < iap_num_sectors; i ++) { + if (addr >= sect_base && addr < sect_base + flash_bsize[i]) + goto out; + + sect_base += flash_bsize[i]; + } + + i = -1; +out: + return i; +} + +/* + * Erase FLASH sectors + */ +int __attribute__((section(".ramcode"))) + __attribute__((long_call)) +lpc178x_flash_erase(u32 offset, u32 size) +{ + int rv; + u32 badaddr, baddata; + u32 first, last; + + /* + * Convert (offset, size) to (first, last) + */ + first = find_sector(offset); + last = find_sector(offset + size - 1); + + if (first < 0 || last < first || last >= iap_num_sectors) { + rv = -EINVAL; + goto out; + } + + if (lpc178x_iap_prepare_sectors(first, last) != IAP_STATUS_SUCCESS) { + rv = -EIO; + goto out; + } + + if (lpc178x_iap_erase_sectors(first, last) != IAP_STATUS_SUCCESS) { + rv = -EIO; + goto out; + } + + if (lpc178x_iap_blank_check_sectors(first, last, &badaddr, &baddata) != + IAP_STATUS_SUCCESS) { + rv = -EIO; + goto out; + } + + rv = 0; +out: + return rv; +} + +/* + * Copy memory buffer to FLASH + */ +int __attribute__((section(".ramcode"))) + __attribute__((long_call)) +lpc178x_flash_program(u32 dest_addr, u8 *src, u32 size) +{ + int rv; + u32 offset; /* Offset of the current block being written */ + u32 i; + u32 sect; + + u8 *dest = (u8 *)(CONFIG_MEM_NVM_BASE + dest_addr); + /* - * TBD + * Write size must be on a block boundary */ + if (size & IAP_BLOCK_MASK) { + rv = -EINVAL; + goto out; + } - return; + /* + * Write address must be on a 256 byte boundary + * (even if IAP_BLOCK_SIZE is not 256) + */ + if (dest_addr & 0xFF) { + rv = -EINVAL; + goto out; + } + + /* + * Write range should not exceed the end of FLASH + */ + if (dest_addr + size > iap_flash_size) { + rv = -EINVAL; + goto out; + } + + /* + * Check that the destination area is erased + */ + for (i = 0; i < size; i ++) { + if (dest[i] != 0xFF) { + /* FLASH is not blank */ + rv = -EEXIST; + goto out; + } + } + + /* + * Write data in blocks of size IAP_BLOCK_SIZE + */ + for (offset = 0; offset < size; offset += IAP_BLOCK_SIZE) { + sect = find_sector(dest_addr + offset); + + if (lpc178x_iap_prepare_sectors(sect, sect) != + IAP_STATUS_SUCCESS) { + rv = -EACCES; + goto out; + } + + /* + * Copy block into the second SRAM region + * ("Peripheral RAM"). Cannot use `memcpy()` here, because it + * resides in eNVM, not in `.ramcode` section. + */ + for (i = 0; i < IAP_BLOCK_SIZE; i ++) + iap_sram_buf[i] = src[offset + i]; + + /* + * Copy block from RAM to FLASH + */ + if (lpc178x_iap_ram_to_flash( + dest_addr + offset, (u32)iap_sram_buf, IAP_BLOCK_SIZE) != + IAP_STATUS_SUCCESS) { + rv = -EACCES; + goto out; + } + + } + + /* + * Verify + */ + for (i = 0; i < size; i ++) { + if (dest[i] != src[i]) { + rv = -EAGAIN; + goto out; + } + } + + rv = 0; +out: + return rv; } /* @@ -40,13 +384,36 @@ void envm_init(void) * Note that we need for this function to reside in RAM since it * will be used to self-upgrade U-boot in internal Flash. */ -unsigned int __attribute__((section(".ramcode"))) - __attribute__ ((long_call)) - envm_write(unsigned int offset, void * buf, unsigned int size) +u32 __attribute__((section(".ramcode"))) + __attribute__ ((long_call)) +envm_write(u32 offset, void *buf, u32 size) { + u32 rv = 0; + /* - * TBD + * Clock in is CPU in KHz + * + * This initialization code cannot be put into `envm_init()`, because + * at the time when `envm_init()` is called `clock_get()` is not yet + * initialized. */ + iap_clkrate = clock_get(CLOCK_SYSTICK) / 1000; + + if (offset < CONFIG_MEM_NVM_BASE || + offset + size > CONFIG_MEM_NVM_BASE + iap_flash_size) { + printf("%s: Address %#x is not in flash or " + "size 0x%x is too big\n", + __func__, offset, size); + goto out; + } + + if (lpc178x_flash_erase(offset, size) < 0 || + lpc178x_flash_program(offset, buf, + (size + IAP_BLOCK_MASK) & + ~IAP_BLOCK_MASK) < 0) + goto out; - return 0; + rv = size; +out: + return rv; } diff --git a/include/asm-arm/arch-lpc178x/lpc178x.h b/include/asm-arm/arch-lpc178x/lpc178x.h index 36fb465b..b6344477 100644 --- a/include/asm-arm/arch-lpc178x/lpc178x.h +++ b/include/asm-arm/arch-lpc178x/lpc178x.h @@ -147,19 +147,16 @@ enum clock { CLOCK_END /* for internal usage */ }; -/****************************************************************************** - * FIXME: get rid of this - ******************************************************************************/ - /* * Return a clock value for the specified clock. - * Note that we need this function in RAM because it will be used - * during self-upgrade of U-boot into eNMV. + * + * Note that we do not need this function in RAM (.ramcode) because it will + * be used during self-upgrade of U-boot into eNMV only once, before the data + * in the eNMV will be actually changed. + * * @param clck id of the clock * @returns frequency of the clock */ -unsigned long __attribute__((section(".ramcode"))) - __attribute__ ((long_call)) - clock_get(enum clock clck); +unsigned long clock_get(enum clock clck); #endif /* _MACH_LPC178X_H_ */ diff --git a/include/configs/ea-lpc1788.h b/include/configs/ea-lpc1788.h index b3a254e0..fe886a63 100644 --- a/include/configs/ea-lpc1788.h +++ b/include/configs/ea-lpc1788.h @@ -153,8 +153,8 @@ #define CONFIG_MEM_NVM_LEN (512 * 1024) /* 64, 128, 256 or 512 kB */ #define CONFIG_MEM_RAM_BASE 0x10000000 -#define CONFIG_MEM_RAM_LEN (16 * 1024) -#define CONFIG_MEM_RAM_BUF_LEN (32 * 1024) +#define CONFIG_MEM_RAM_LEN (32 * 1024) +#define CONFIG_MEM_RAM_BUF_LEN (16 * 1024) #define CONFIG_MEM_MALLOC_LEN (12 * 1024) #define CONFIG_MEM_STACK_LEN (4 * 1024) -- GitLab