/*
 * Copyright(C) Paul und Scherer (mct.de/mct.net)
 *
 * This example demonstrates how to...
 *
 *  ... use the IAP functions.
 *
 * Note: The IAP functions are Thumb code.
 * THIS PROGRAM FAILS IF NOT COMPILED WITH
 *  -mthumb or
 *  -mthumb-interwork
 */

#include <stdio.h>
#include <target.h>

/*
 * Choose sector and address
 *
 * The sector address depends on the processor type.
 * The LPC213x uses 4KB for the first 8 sectors, all
 * others use 8KB. If sector is <8, then the address
 * is sector*0x2000 (sector*0x1000 for the LPC213x).
 */
#define SECT	7
#ifdef lc2138
#define ADDR	0x7000
#else
#define ADDR	0xe000
#endif

#define	PREPARE_SECTOR_FOR_WRITE_OPERATION	50	// IAP command
#define	COPY_RAM_TO_FLASH			51
#define	ERASE_SECTOR				52
#define	BLANK_CHECK_SECTOR			53
#define READ_PART_ID				54
#define	READ_BOOT_CODE_VERSION			55
#define COMPARE					56	//  codes

static unsigned long iap_cmd[5];			// IAP command table
static unsigned long iap_res[3];			//     result  table

/*
 * Call IAP function.
 *
 * Note: Direct XTAL mode is required
 * for IAP functions (PLL turned off)!
 *
 * Return status.
 */
static long
iap(long code, long p1, long p2, long p3, long p4)
{
	iap_cmd[0] = code;				// set command code
	iap_cmd[1] = p1;				//     1st param
	iap_cmd[2] = p2;				//     2nd param
	iap_cmd[3] = p3;				//     3rd param
	iap_cmd[4] = p4;				//     4th param

	while (!(Intern_u0lsr&0x40)) ;			// transmit last char
	_pll_off();					// disconnect PLL
	((void (*)())0x7ffffff1)(iap_cmd, iap_res);	// IAP entry point
	_pll_on();					// re-connect PLL
	return *iap_res;				// return status
}

/*
 * Print IAP error message.
 */
static void
iap_err(char *s)
{
	static char *const msg[] =
	{
		"INVALID_COMMAND",
		"SRC_ADDR_ERROR",
		"DST_ADDR_ERROR",
		"SRC_ADDR_NOT_MAPPED",
		"DST_ADDR_NOT_MAPPED",
		"COUNT_ERROR",
		"INVALID_SECTOR",
		"SECTOR_NOT_BLANK",
		"SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION",
		"COMPARE_ERROR",
		"BUSY"
	};

	if (*iap_res-1 >= sizeof(msg)/sizeof(*msg)) printf("%lu: No such error code\n", *iap_res);
	else {
		int i;

		printf("--ERROR: %s failed (%s)\7\n\n", s, msg[*iap_res-1]);
		for (i = 0; i < 5; i++) printf("  iap_cmd[%d] 0x%08lx (%lu)\n", i, iap_cmd[i], iap_cmd[i]);
		for (i = 0; i < 3; i++) printf("  iap_res[%d] 0x%08lx (%lu)\n", i, iap_res[i], iap_res[i]);
	}
}

/*
 * Read part ID.
 */
static void
part_id(void)
{
	if (iap(READ_PART_ID, 0, 0, 0, 0)) {
		iap_err("Reading part ID");
		return;
	}
	printf("Part ID: %lu\n", iap_res[1]);
}

/*
 * Read boot code version.
 */
static void
version(void)
{
	if (iap(READ_BOOT_CODE_VERSION, 0, 0, 0, 0)) {
		iap_err("Reading boot code version");
		return;
	}
	printf("Boot code version: %d.%d\n",
		(unsigned char)(iap_res[1]>>8),
		(unsigned char)iap_res[1]
	);
}

/*
 * Write sector.
 */
static void
s_write(void)
{
	static char data[512];			// data buffer

	/*
	 * If not blank, dump sector data.
	 */
	if (iap(BLANK_CHECK_SECTOR, SECT, SECT, 0, 0) == 8) {
		unsigned char *p;

		puts("Sector not blank!\n"
		     "Sector data:\n"
		);
		for (p = (char *)ADDR; *p; p++) {
			if (*p > 0x1f && *p < 0x7f) putchar(*p);
			else printf("\\x%02x", *p);
		}
		putchar('\n');
		return;
	}

	/*
	 * If no data entered, skip write.
	 */
	puts("Enter data (single RETURN to abort):");
	if (*fgets(data, sizeof(data), stdin) == '\n') {
		puts("Aborted.");
		return;
	}

	if (iap(PREPARE_SECTOR_FOR_WRITE_OPERATION, SECT, SECT, 0, 0)
	 || iap(COPY_RAM_TO_FLASH, ADDR, (long)data, sizeof(data), _XTAL)
	 || iap(COMPARE, ADDR, (long)data, sizeof(data), 0)) {
		iap_err("Writing sector");
		return;
	}
	puts("Data successfully written.");
}

/*
 * Erase sector.
 */
static void
s_erase(void)
{
	/*
	 * If blank, skip erase.
	 */
	if (!iap(BLANK_CHECK_SECTOR, SECT, SECT, 0, 0)) {
		puts("Sector already blank!");
		return;
	}

	if (iap(PREPARE_SECTOR_FOR_WRITE_OPERATION, SECT, SECT, 0, 0)
	 || iap(ERASE_SECTOR, SECT, SECT, _XTAL, 0)
	 || iap(BLANK_CHECK_SECTOR, SECT, SECT, 0, 0)) {
		iap_err("Erasing sector");
		return;
	}
	puts("Sector successfully erased.");
}

/*
 * IAP functions menu
 *
 * Write sector only writes to a blank sector.
 * Erase sector only erases non-blank sectors.
 * A verify is done for each write/erase call.
 */
int
main(void)
{
	while (1) {
		int c;

		printf("\n"
		     " (1) Read part ID\n"
		     " (2) Read boot code version\n"
		     " (3) Write sector %d\n"
		     " (4) Erase sector %d\n\n"
		     "Select... ", SECT, SECT
		);
		c = getchar();
		puts("\n");
		switch (c) {
		case '1': part_id(); break;
		case '2': version(); break;
		case '3': s_write(); break;
		case '4': s_erase(); break;
		}
	}
}
