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