// The "bootloader" blob is (C) WCH.
// The rest of the code, Copyright 2023 Charles Lohr
// Freely licensable under the MIT/x11, NewBSD Licenses, or
// public domain where applicable. 

// TODO: Can we make a unified DMPROG for reading + writing?

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include "minichlink.h"
#include "../ch32v003fun/ch32v003fun.h"

#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
void Sleep(uint32_t dwMilliseconds);
#else
#include <unistd.h>
#endif

static int64_t StringToMemoryAddress( const char * number ) __attribute__((used));
static void StaticUpdatePROGBUFRegs( void * dev ) __attribute__((used));
int DefaultReadBinaryBlob( void * dev, uint32_t address_to_read_from, uint32_t read_size, uint8_t * blob );

void TestFunction(void * v );
struct MiniChlinkFunctions MCF;

void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO, const init_hints_t* init_hints )
{
	void * dev = 0;
	if( (dev = TryInit_WCHLinkE()) )
	{
		fprintf( stderr, "Found WCH Link\n" );
	}
	else if( (dev = TryInit_ESP32S2CHFUN()) )
	{
		fprintf( stderr, "Found ESP32S2 Programmer\n" );
	}
	else if ((dev = TryInit_NHCLink042()))
	{
		fprintf( stderr, "Found NHC-Link042 Programmer\n" );
	}
	else if ((dev = TryInit_B003Fun()))
	{
		fprintf( stderr, "Found B003Fun Bootloader\n" );
	}
	else if ((dev = TryInit_Ardulink(init_hints)))
	{
		fprintf( stderr, "Found Ardulink Programmer\n" );
	}
	else
	{
		fprintf( stderr, "Error: Could not initialize any supported programmers\n" );
		return 0;
	}

	SetupAutomaticHighLevelFunctions( dev );

	if( MCFO )
	{
		*MCFO = &MCF;
	}
	return dev;
}

void parse_possible_init_hints(int argc, char **argv, init_hints_t *hints)
{
	if (!hints)
		return;
	int c;
	opterr = 0;
	/* we're only interested in the value for the COM port, given in a -c parameter */
	/* the '-' is really important so that getopt does not permutate the argv array and messes up parsing later */
	while ((c = getopt(argc, argv, "-c:")) != -1)
	{
		switch (c)
		{
		case 'c':
			// we can use the pointer as-is because it points in our 
			// argv array and that is stable.
			hints->serial_port = optarg;
			break;
		}
	}
}

#if !defined( MINICHLINK_AS_LIBRARY ) && !defined( MINICHLINK_IMPORT )
int main( int argc, char ** argv )
{
	if( argc > 1 && argv[1][0] == '-' && argv[1][1] == 'h' )
	{
		goto help;
	}
	init_hints_t hints;
	memset(&hints, 0, sizeof(hints));
	parse_possible_init_hints(argc, argv, &hints);
	void * dev = MiniCHLinkInitAsDLL( 0, &hints );
	if( !dev )
	{
		fprintf( stderr, "Error: Could not initialize any supported programmers\n" );
		return -32;
	}

	int status;
	int must_be_end = 0;

	int skip_startup = 
		(argc > 1 && argv[1][0] == '-' && argv[1][1] == 'u' ) |
		(argc > 1 && argv[1][0] == '-' && argv[1][1] == 'h' ) |
		(argc > 1 && argv[1][0] == '-' && argv[1][1] == 't' ) |
		(argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' ) |
		(argc > 1 && argv[1][0] == '-' && argv[1][1] == 'X' );

	if( !skip_startup && MCF.SetupInterface )
	{
		if( MCF.SetupInterface( dev ) < 0 )
		{
			fprintf( stderr, "Could not setup interface.\n" );
			return -33;
		}
		printf( "Interface Setup\n" );
	}

//	TestFunction( dev );

	int iarg = 1;
	const char * lastcommand = 0;
	for( ; iarg < argc; iarg++ )
	{
		char * argchar = argv[iarg];

		lastcommand = argchar;
		if( argchar[0] != '-' )
		{
			fprintf( stderr, "Error: Need prefixing - before commands\n" );
			goto help;
		}
		if( must_be_end )
		{
			fprintf( stderr, "Error: the command '%c' cannot be followed by other commands.\n", must_be_end );
			return -1;
		}
		
keep_going:
		switch( argchar[1] )
		{
			default:
				fprintf( stderr, "Error: Unknown command %c\n", argchar[1] );
			case 'h':
				goto help;
			case '3':
				if( MCF.Control3v3 )
					MCF.Control3v3( dev, 1 );
				else
					goto unimplemented;
				break;
			case '5':
				if( MCF.Control5v )
					MCF.Control5v( dev, 1 );
				else
					goto unimplemented;
				break;
			case 't':
				if( MCF.Control3v3 )
					MCF.Control3v3( dev, 0 );
				else
					goto unimplemented;
				break;
			case 'f':
				if( MCF.Control5v )
					MCF.Control5v( dev, 0 );
				else
					goto unimplemented;
				break;
			case 'c':
				// COM port argument already parsed previously
				// we still need to skip the next argument
				iarg+=1;
				if( iarg >= argc )
				{
					fprintf( stderr, "-c argument (COM port) required 2 arguments\n" );
					goto unimplemented;
				}
				break;
			case 'u':
				if( MCF.Unbrick )
					MCF.Unbrick( dev );
				else
					goto unimplemented;
				break;
			case 'U':
				// Unlock Bootloader
				if( InternalUnlockBootloader( dev ) )
					goto unimplemented;
				break;
			case 'b':  //reBoot
				if( !MCF.HaltMode || MCF.HaltMode( dev, HALT_MODE_REBOOT ) )
					goto unimplemented;
				break;
			case 'B':  //reBoot into Bootloader
				if( !MCF.HaltMode || MCF.HaltMode( dev, HALT_MODE_GO_TO_BOOTLOADER ) )
					goto unimplemented;
				break;
			case 'e':  //rEsume
				if( !MCF.HaltMode || MCF.HaltMode( dev, HALT_MODE_RESUME ) )
					goto unimplemented;
				break;
			case 'E':  //Erase whole chip.
				if( MCF.HaltMode ) MCF.HaltMode( dev, HALT_MODE_HALT_AND_RESET );
				if( !MCF.Erase || MCF.Erase( dev, 0, 0, 1 ) )
					goto unimplemented;
				break;
			case 'a':
				if( !MCF.HaltMode || MCF.HaltMode( dev, HALT_MODE_HALT_AND_RESET ) )
					goto unimplemented;
				break;
			case 'A':  // Halt without reboot
				if( !MCF.HaltMode || MCF.HaltMode( dev, HALT_MODE_HALT_BUT_NO_RESET ) )
					goto unimplemented;
				break;

			// disable NRST pin (turn it into a GPIO)
			case 'd':  // see "RSTMODE" in datasheet
				if( MCF.HaltMode ) MCF.HaltMode( dev, HALT_MODE_HALT_AND_RESET );
				if( MCF.ConfigureNRSTAsGPIO )
					MCF.ConfigureNRSTAsGPIO( dev, 0 );
				else
					goto unimplemented;
				break;
			case 'D': // see "RSTMODE" in datasheet
				if( MCF.HaltMode ) MCF.HaltMode( dev, HALT_MODE_HALT_AND_RESET );
				if( MCF.ConfigureNRSTAsGPIO )
					MCF.ConfigureNRSTAsGPIO( dev, 1 );
				else
					goto unimplemented;
				break;
			case 'p': 
				if( MCF.HaltMode ) MCF.HaltMode( dev, HALT_MODE_HALT_AND_RESET );
				if( MCF.ConfigureReadProtection )
					MCF.ConfigureReadProtection( dev, 0 );
				else
					goto unimplemented;
				break;
			case 'P':
				if( MCF.HaltMode ) MCF.HaltMode( dev, HALT_MODE_HALT_AND_RESET );
				if( MCF.ConfigureReadProtection )
					MCF.ConfigureReadProtection( dev, 1 );
				else
					goto unimplemented;
				break;
			case 'G':
			case 'T':
			{
				if( !MCF.PollTerminal )
					goto unimplemented;

				if( argchar[1] == 'G' && SetupGDBServer( dev ) )
				{
					printf( "Error: can't start GDB server\n" );
					return -1;
				}
				if( argchar[1] == 'G' )
				{
					printf( "GDBServer Running\n" );
				}
				else
				{
					// In case we aren't running already.
					//MCF.HaltMode( dev, 2 );
					//XXX TODO: Why do some programmers start automatically, and others don't? 
				}

				do
				{
					uint8_t buffer[256];
					if( !IsGDBServerInShadowHaltState( dev ) )
					{
						int r = MCF.PollTerminal( dev, buffer, sizeof( buffer ), 0, 0 );
						if( r < 0 )
						{
							fprintf( stderr, "Terminal dead.  code %d\n", r );
							return -32;
						}
						if( r > 0 )
						{
							fwrite( buffer, r, 1, stdout ); 
						}
					}

					if( argchar[1] == 'G' )
					{
						PollGDBServer( dev );
					}
				} while( 1 );

				// Currently unreachable - consider reachable-ing
				if( argchar[1] == 'G' )
					ExitGDBServer( dev );
				break;
			}
			case 's':
			{
				iarg+=2;
				if( iarg >= argc )
				{
					fprintf( stderr, "Debug set commands require 2 parameters, a register and a value.\n" );
					goto unimplemented;
				}

				uint32_t datareg = SimpleReadNumberInt( argv[iarg-1], DMDATA0 );
				uint32_t value = SimpleReadNumberInt( argv[iarg], 0 ); 

				if( MCF.WriteReg32 && MCF.FlushLLCommands )
				{
					MCF.FlushLLCommands( dev );	
					MCF.WriteReg32( dev, datareg, value );
					MCF.FlushLLCommands( dev );
				}
				else
					goto unimplemented;
				break;
			}
			case 'm':
			{
				iarg+=1;
				if( iarg >= argc )
				{
					fprintf( stderr, "Debug get commands require 1 parameter, a register.\n" );
					fprintf( stderr, "One of the following:\n"
						"	DMDATA0        0x04\n"
						"	DMDATA1        0x05\n"
						"	DMCONTROL      0x10\n"
						"	DMSTATUS       0x11\n"
						"	DMHARTINFO     0x12\n"
						"	DMABSTRACTCS   0x16\n"
						"	DMCOMMAND      0x17\n"
						"	DMABSTRACTAUTO 0x18\n"
						"	DMPROGBUF0     0x20\n"
						"	DMPROGBUF1     0x21\n"
						"	DMPROGBUF2     0x22\n"
						"	DMPROGBUF3     0x23\n"
						"	DMPROGBUF4     0x24\n"
						"	DMPROGBUF5     0x25\n"
						"	DMPROGBUF6     0x26\n"
						"	DMPROGBUF7     0x27\n"
						"	DMCPBR       0x7C\n"
						"	DMCFGR       0x7D\n"
						"	DMSHDWCFGR   0x7E\n" );

					goto unimplemented;
				}

				uint32_t datareg = SimpleReadNumberInt( argv[iarg], DMDATA0 );

				if( MCF.ReadReg32 && MCF.FlushLLCommands )
				{
					uint32_t value;
					int ret = MCF.ReadReg32( dev, datareg, &value );
					printf( "REGISTER %02x: %08x, %d\n", datareg, value, ret );
				}
				else
					goto unimplemented;
				break;
			}
			case 'i':
			{
				if( MCF.PrintChipInfo )
					MCF.PrintChipInfo( dev ); 
				else
					goto unimplemented;
				break;
			}
			case 'X':
			{
				iarg++;
				if( iarg >= argc )
				{
					fprintf( stderr, "Vendor command requires an actual command\n" );
					goto unimplemented;
				}
				if( MCF.VendorCommand )
					if( MCF.VendorCommand( dev, argv[iarg++] ) )
						goto unimplemented;
				break;
			}
			case 'r':
			{
				if( MCF.HaltMode ) MCF.HaltMode( dev, HALT_MODE_HALT_BUT_NO_RESET ); //No need to reboot.

				if( argchar[2] != 0 )
				{
					fprintf( stderr, "Error: can't have char after paramter field\n" ); 
					goto help;
				}
				iarg++;
				argchar = 0; // Stop advancing
				if( iarg + 2 >= argc )
				{
					fprintf( stderr, "Error: missing file for -o.\n" ); 
					goto help;
				}
				const char * fname = argv[iarg++];
				uint64_t offset = StringToMemoryAddress( argv[iarg++] );

				uint64_t amount = SimpleReadNumberInt( argv[iarg], -1 );
				if( offset > 0xffffffff || amount > 0xffffffff )
				{
					fprintf( stderr, "Error: memory value request out of range\n" );
					return -9;
				}

				FILE * f = 0;
				int hex = 0;
				if( strcmp( fname, "-" ) == 0 )
					f = stdout;
				else if( strcmp( fname, "+" ) == 0 )
					f = stdout, hex = 1;
				else
					f = fopen( fname, "wb" );
				if( !f )
				{
					fprintf( stderr, "Error: can't open write file \"%s\"\n", fname );
					return -9;
				}
				uint8_t * readbuff = malloc( amount );

				if( MCF.ReadBinaryBlob )
				{
					if( MCF.ReadBinaryBlob( dev, offset, amount, readbuff ) < 0 )
					{
						fprintf( stderr, "Fault reading device\n" );
						return -12;
					}
				}				
				else
				{
					goto unimplemented;
				}

				printf( "Read %d bytes\n", (int)amount );

				if( hex )
				{
					int i;
					for( i = 0; i < amount; i++ )
					{
						if( ( i & 0xf ) == 0 )
						{
							if( i != 0 ) printf( "\n" );
							printf( "%08x: ", (uint32_t)(offset + i) );
						}
						printf( "%02x ", readbuff[i] );
					}
					printf( "\n" );
				}
				else
					fwrite( readbuff, amount, 1, f );

				free( readbuff );

				if( f != stdout ) fclose( f );
				break;
			}
			case 'w':
			{
				if( argchar[2] != 0 ) goto help;
				iarg++;
				argchar = 0; // Stop advancing
				if( iarg + 1 >= argc ) goto help;

				// Write binary.
				int len = 0;
				uint8_t * image = 0;
				const char * fname = argv[iarg++];

				if( fname[0] == '-' )
				{
					len = strlen( fname + 1 );
					image = (uint8_t*)strdup( fname + 1 );
					status = 1;
				}
				else if( fname[0] == '+' )
				{
					int hl = strlen( fname+1 );
					if( hl & 1 )
					{
						fprintf( stderr, "Error: hex input doesn't align to chars correctly.\n" );
						return -32;
					}
					len = hl/2;
					image = malloc( len );
					int i;
					for( i = 0; i < len; i ++ )
					{
						char c1 = fname[i*2+1];
						char c2 = fname[i*2+2];
						int v1, v2;
						if( c1 >= '0' && c1 <= '9' ) v1 = c1 - '0';
						else if( c1 >= 'a' && c1 <= 'f' ) v1 = c1 - 'a' + 10;
						else if( c1 >= 'A' && c1 <= 'F' ) v1 = c1 - 'A' + 10;
						else
						{
							fprintf( stderr, "Error: Bad hex\n" );
							return -32;
						}

						if( c2 >= '0' && c2 <= '9' ) v2 = c2 - '0';
						else if( c2 >= 'a' && c2 <= 'f' ) v2 = c2 - 'a' + 10;
						else if( c2 >= 'A' && c2 <= 'F' ) v2 = c2 - 'A' + 10;
						else
						{
							fprintf( stderr, "Error: Bad hex\n" );
							return -32;
						}
						image[i] = (v1<<4) | v2;
					}
					status = 1;
				}
				else
				{
					FILE * f = fopen( fname, "rb" );
					if( !f )
					{
						fprintf( stderr, "Error: Could not open %s\n", fname );
						return -55;
					}
					fseek( f, 0, SEEK_END );
					len = ftell( f );
					fseek( f, 0, SEEK_SET );
					image = malloc( len );
					status = fread( image, len, 1, f );
					fclose( f );
				}

				uint64_t offset = StringToMemoryAddress( argv[iarg] );
				if( offset > 0xffffffff )
				{
					fprintf( stderr, "Error: Invalid offset (%s)\n", argv[iarg] );
					exit( -44 );
				}
				if( status != 1 )
				{
					fprintf( stderr, "Error: File I/O Fault.\n" );
					exit( -10 );
				}
				if( len > 16384 )
				{
					fprintf( stderr, "Error: Image for CH32V003 too large (%d)\n", len );
					exit( -9 );
				}

				int is_flash = IsAddressFlash( offset );
				if( MCF.HaltMode ) MCF.HaltMode( dev, is_flash ? HALT_MODE_HALT_AND_RESET : HALT_MODE_HALT_BUT_NO_RESET );

				if( MCF.WriteBinaryBlob )
				{
					if( MCF.WriteBinaryBlob( dev, offset, len, image ) )
					{
						fprintf( stderr, "Error: Fault writing image.\n" );
						return -13;
					}
				}
				else
				{
					goto unimplemented;
				}

				printf( "Image written.\n" );

				free( image );
				break;
			}
			
		}
		if( argchar && argchar[2] != 0 ) { argchar++; goto keep_going; }
	}

	if( MCF.FlushLLCommands )
		MCF.FlushLLCommands( dev );

	if( MCF.Exit )
		MCF.Exit( dev );

	return 0;

help:
	fprintf( stderr, "Usage: minichlink [args]\n" );
	fprintf( stderr, " single-letter args may be combined, i.e. -3r\n" );
	fprintf( stderr, " multi-part args cannot.\n" );
	fprintf( stderr, " -3 Enable 3.3V\n" );
	fprintf( stderr, " -5 Enable 5V\n" );
	fprintf( stderr, " -t Disable 3.3V\n" );
	fprintf( stderr, " -f Disable 5V\n" );
	fprintf( stderr, " -c [serial port for Ardulink, try /dev/ttyACM0 or COM11 etc]\n" );
	fprintf( stderr, " -u Clear all code flash - by power off (also can unbrick)\n" );
	fprintf( stderr, " -E Erase chip\n" );
	fprintf( stderr, " -b Reboot out of Halt\n" );
	fprintf( stderr, " -e Resume from halt\n" );
	fprintf( stderr, " -a Reboot into Halt\n" );
	fprintf( stderr, " -A Go into Halt without reboot\n" );
	fprintf( stderr, " -D Configure NRST as GPIO\n" );
	fprintf( stderr, " -d Configure NRST as NRST\n" );
	fprintf( stderr, " -i Show chip info\n" );
	fprintf( stderr, " -s [debug register] [value]\n" );
	fprintf( stderr, " -m [debug register]\n" );
	fprintf( stderr, " -T Terminal Only\n" );
	fprintf( stderr, " -G Terminal + GDB\n" );
	fprintf( stderr, " -P Enable Read Protection\n" );
	fprintf( stderr, " -p Disable Read Protection\n" );
	fprintf( stderr, " -w [binary image to write] [address, decimal or 0x, try0x08000000]\n" );
	fprintf( stderr, " -r [output binary image] [memory address, decimal or 0x, try 0x08000000] [size, decimal or 0x, try 16384]\n" );
	fprintf( stderr, "   Note: for memory addresses, you can use 'flash' 'launcher' 'bootloader' 'option' 'ram' and say \"ram+0x10\" for instance\n" );
	fprintf( stderr, "   For filename, you can use - for raw (terminal) or + for hex (inline).\n" );
	fprintf( stderr, " -T is a terminal. This MUST be the last argument. Also, will start a gdbserver.\n" );

	return -1;	

unimplemented:
	fprintf( stderr, "Error: Command '%s' unimplemented on this programmer.\n", lastcommand );
	return -1;
}
#endif

#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
#define strtoll _strtoi64
#endif

int64_t SimpleReadNumberInt( const char * number, int64_t defaultNumber )
{
	if( !number || !number[0] ) return defaultNumber;
	int radix = 10;
	if( number[0] == '0' )
	{
		char nc = number[1];
		number+=2;
		if( nc == 0 ) return 0;
		else if( nc == 'x' ) radix = 16;
		else if( nc == 'b' ) radix = 2;
		else { number--; radix = 8; }
	}
	char * endptr;
	uint64_t ret = strtoll( number, &endptr, radix );
	if( endptr == number )
	{
		return defaultNumber;
	}
	else
	{
		return ret;
	}
}

static int64_t StringToMemoryAddress( const char * number )
{
	uint32_t base = 0;

	if( strncmp( number, "flash", 5 ) == 0 )       base = 0x08000000, number += 5;
	if( strncmp( number, "launcher", 8 ) == 0 )    base = 0x1FFFF000, number += 8;
	if( strncmp( number, "bootloader", 10 ) == 0 ) base = 0x1FFFF000, number += 10;
	if( strncmp( number, "option", 6 ) == 0 )      base = 0x1FFFF800, number += 6;
	if( strncmp( number, "user", 4 ) == 0 )        base = 0x1FFFF800, number += 4;
	if( strncmp( number, "ram", 3 ) == 0 )         base = 0x20000000, number += 3;

	if( base )
	{
		if( *number != '+' )
			return base;
		number++;
		return base + SimpleReadNumberInt( number, 0 );
	}
	return SimpleReadNumberInt( number, -1 );
}

static int DefaultWaitForFlash( void * dev )
{
	uint32_t rw, timeout = 0;
	do
	{
		rw = 0;
		MCF.ReadWord( dev, (intptr_t)&FLASH->STATR, &rw ); // FLASH_STATR => 0x4002200C
		if( timeout++ > 100 ) return -1;
	} while(rw & 1);  // BSY flag.

	if( rw & FLASH_STATR_WRPRTERR )
	{
		fprintf( stderr, "Memory Protection Error\n" );
		return -44;
	}

	return 0;
}

static int DefaultWaitForDoneOp( void * dev, int ignore )
{
	int r;
	uint32_t rrv;

	do
	{
		r = MCF.ReadReg32( dev, DMABSTRACTCS, &rrv );
		if( r ) return r;
	}
	while( rrv & (1<<12) );

	if( (rrv >> 8 ) & 7 )
	{
		if( !ignore )
		{
			const char * errortext = 0;
			switch( (rrv>>8)&7 )
			{
			case 1: errortext = "Command in execution"; break;
			case 2: errortext = "Abstract Command Unsupported"; break;
			case 3: errortext = "Execption executing Abstract Command"; break;
			case 4: errortext = "Processor not halted."; break;
			case 5: errortext = "Bus Error"; break;
			case 6: errortext = "Parity Bit"; break;
			default: errortext = "Other Error"; break;
			}

			uint32_t temp;
			MCF.ReadReg32( dev, DMSTATUS, &temp );
			fprintf( stderr, "Fault writing memory (DMABSTRACTS = %08x) (%s) DMSTATUS: %08x\n", rrv, errortext, temp );
		}
		MCF.WriteReg32( dev, DMABSTRACTCS, 0x00000700 );
		return -9;
	}
	return 0;
}

int DefaultSetupInterface( void * dev )
{
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);

	if( MCF.Control3v3 ) MCF.Control3v3( dev, 1 );
	if( MCF.DelayUS ) MCF.DelayUS( dev, 16000 );
	MCF.WriteReg32( dev, DMSHDWCFGR, 0x5aa50000 | (1<<10) ); // Shadow Config Reg
	MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // CFGR (1<<10 == Allow output from slave)
	MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // Bug in silicon?  If coming out of cold boot, and we don't do our little "song and dance" this has to be called.

	// Read back chip status.  This is really basic.
	uint32_t reg = 0;
	int r = MCF.ReadReg32( dev, DMSTATUS, &reg );
	if( r >= 0 )
	{
		// Valid R.
		if( reg == 0x00000000 || reg == 0xffffffff )
		{
			fprintf( stderr, "Error: Setup chip failed. Got code %08x\n", reg );
			return -9;
		}
	}
	else
	{
		fprintf( stderr, "Error: Could not read chip code.\n" );
		return r;
	}

	iss->statetag = STTAG( "STRT" );
	return 0;
}

static void StaticUpdatePROGBUFRegs( void * dev )
{
	uint32_t rr;
	if( MCF.ReadReg32( dev, DMHARTINFO, &rr ) )
	{
		fprintf( stderr, "Error: Could not get hart info.\n" );
		return;
	}
	uint32_t data0offset = 0xe0000000 | ( rr & 0x7ff );
	MCF.WriteReg32( dev, DMDATA0, data0offset );       // DATA0's location in memory.
	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100a );      // Copy data to x10
	MCF.WriteReg32( dev, DMDATA0, data0offset + 4 );   // DATA1's location in memory.
	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100b );      // Copy data to x11
	MCF.WriteReg32( dev, DMDATA0, 0x40022010 );        // FLASH->CTLR
	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100c );      // Copy data to x12
	MCF.WriteReg32( dev, DMDATA0, CR_PAGE_PG|CR_BUF_LOAD);
	MCF.WriteReg32( dev, DMCOMMAND, 0x0023100d );      // Copy data to x13
}

int InternalUnlockBootloader( void * dev )
{
	if( !MCF.WriteWord ) return -99;
	int ret = 0;
	uint32_t OBTKEYR;
	ret |= MCF.WriteWord( dev, 0x40022028, 0x45670123 ); //(FLASH_BOOT_MODEKEYP)
	ret |= MCF.WriteWord( dev, 0x40022028, 0xCDEF89AB ); //(FLASH_BOOT_MODEKEYP)
	ret |= MCF.ReadWord( dev, 0x40022008, &OBTKEYR ); //(FLASH_OBTKEYR)
	if( ret )
	{
		fprintf( stderr, "Error operating with OBTKEYR\n" );
		return -1;
	}
	if( OBTKEYR & (1<<15) )
	{
		fprintf( stderr, "Error: Could not unlock boot section (%08x)\n", OBTKEYR );
	}
	OBTKEYR |= (1<<14); // Configure for boot-to-bootload.
	ret |= MCF.WriteWord( dev, 0x40022008, OBTKEYR );
	ret |= MCF.ReadWord( dev, 0x40022008, &OBTKEYR ); //(FLASH_OBTKEYR)
	printf( "FLASH_OBTKEYR = %08x (%d)\n", OBTKEYR, ret );
	return ret;
}


int InternalIsMemoryErased( struct InternalState * iss, uint32_t address )
{
	if(( address & 0xff000000 ) != 0x08000000 ) return 0;
	int sector = (address & 0xffffff) / iss->sector_size;
	if( sector >= MAX_FLASH_SECTORS )
		return 0;
	else
		return iss->flash_sector_status[sector];
}

void InternalMarkMemoryNotErased( struct InternalState * iss, uint32_t address )
{
	if(( address & 0xff000000 ) != 0x08000000 ) return;
	int sector = (address & 0xffffff) / iss->sector_size;
	if( sector < MAX_FLASH_SECTORS )
		iss->flash_sector_status[sector] = 0;
}

static int DefaultWriteHalfWord( void * dev, uint32_t address_to_write, uint16_t data )
{
	int ret = 0;
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	if( MCF.VoidHighLevelState ) MCF.VoidHighLevelState( dev );
	iss->statetag = STTAG( "XXXX" );

	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.

	// Different address, so we don't need to re-write all the program regs.
	// sh x8,0(x9)  // Write to the address.
	MCF.WriteReg32( dev, DMPROGBUF0, 0x00849023 );
	MCF.WriteReg32( dev, DMPROGBUF1, 0x00100073 ); // c.ebreak

	MCF.WriteReg32( dev, DMDATA0, address_to_write );
	MCF.WriteReg32( dev, DMCOMMAND, 0x00231009 ); // Copy data to x9
	MCF.WriteReg32( dev, DMDATA0, data );
	MCF.WriteReg32( dev, DMCOMMAND, 0x00271008 ); // Copy data to x8, and execute program.

	ret |= MCF.WaitForDoneOp( dev, 0 );
	iss->currentstateval = -1;

	if( ret ) fprintf( stderr, "Fault on DefaultWriteHalfWord\n" );
	return ret;
}

static int DefaultReadHalfWord( void * dev, uint32_t address_to_write, uint16_t * data )
{
	int ret = 0;
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	if( MCF.VoidHighLevelState ) MCF.VoidHighLevelState( dev );
	iss->statetag = STTAG( "XXXX" );

	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.

	// Different address, so we don't need to re-write all the program regs.
	// lh x8,0(x9)  // Write to the address.
	MCF.WriteReg32( dev, DMPROGBUF0, 0x00049403 ); // lh x8, 0(x9)
	MCF.WriteReg32( dev, DMPROGBUF1, 0x00100073 ); // c.ebreak

	MCF.WriteReg32( dev, DMDATA0, address_to_write );
	MCF.WriteReg32( dev, DMCOMMAND, 0x00231009 ); // Copy data to x9
	MCF.WriteReg32( dev, DMCOMMAND, 0x00241000 ); // Only execute.
	MCF.WriteReg32( dev, DMCOMMAND, 0x00221008 ); // Read x8 into DATA0.

	ret |= MCF.WaitForDoneOp( dev, 0 );
	iss->currentstateval = -1;

	if( ret ) fprintf( stderr, "Fault on DefaultReadHalfWord\n" );

	uint32_t rr;
	ret |= MCF.ReadReg32( dev, DMDATA0, &rr );
	*data = rr;
	return ret;
}



static int DefaultWriteByte( void * dev, uint32_t address_to_write, uint8_t data )
{
	int ret = 0;
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	if( MCF.VoidHighLevelState ) MCF.VoidHighLevelState( dev );
	iss->statetag = STTAG( "XXXX" );

	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.

	// Different address, so we don't need to re-write all the program regs.
	// sh x8,0(x9)  // Write to the address.
	MCF.WriteReg32( dev, DMPROGBUF0, 0x00848023 ); // sb x8, 0(x9)
	MCF.WriteReg32( dev, DMPROGBUF1, 0x00100073 ); // c.ebreak

	MCF.WriteReg32( dev, DMDATA0, address_to_write );
	MCF.WriteReg32( dev, DMCOMMAND, 0x00231009 ); // Copy data to x9
	MCF.WriteReg32( dev, DMDATA0, data );
	MCF.WriteReg32( dev, DMCOMMAND, 0x00271008 ); // Copy data to x8, and execute program.

	ret |= MCF.WaitForDoneOp( dev, 0 );
	if( ret ) fprintf( stderr, "Fault on DefaultWriteByte\n" );
	iss->currentstateval = -1;
	return ret;
}

static int DefaultReadByte( void * dev, uint32_t address_to_write, uint8_t * data )
{
	int ret = 0;
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	if( MCF.VoidHighLevelState ) MCF.VoidHighLevelState( dev );
	iss->statetag = STTAG( "XXXX" );

	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.

	// Different address, so we don't need to re-write all the program regs.
	// lb x8,0(x9)  // Write to the address.
	MCF.WriteReg32( dev, DMPROGBUF0, 0x00048403 ); // lb x8, 0(x9)
	MCF.WriteReg32( dev, DMPROGBUF1, 0x00100073 ); // c.ebreak

	MCF.WriteReg32( dev, DMDATA0, address_to_write );
	MCF.WriteReg32( dev, DMCOMMAND, 0x00231009 ); // Copy data to x9
	MCF.WriteReg32( dev, DMCOMMAND, 0x00241000 ); // Only execute.
	MCF.WriteReg32( dev, DMCOMMAND, 0x00221008 ); // Read x8 into DATA0.

	ret |= MCF.WaitForDoneOp( dev, 0 );
	if( ret ) fprintf( stderr, "Fault on DefaultReadByte\n" );
	iss->currentstateval = -1;

	uint32_t rr;
	ret |= MCF.ReadReg32( dev, DMDATA0, &rr );
	*data = rr;
	return ret;
}


static int DefaultWriteWord( void * dev, uint32_t address_to_write, uint32_t data )
{
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	int ret = 0;

	int is_flash = IsAddressFlash( address_to_write );

	if( iss->statetag != STTAG( "WRSQ" ) || is_flash != iss->lastwriteflags )
	{
		int did_disable_req = 0;
		if( iss->statetag != STTAG( "WRSQ" ) )
		{
			MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
			did_disable_req = 1;
			// Different address, so we don't need to re-write all the program regs.
			// c.lw x9,0(x11) // Get the address to write to. 
			// c.sw x8,0(x9)  // Write to the address.
			MCF.WriteReg32( dev, DMPROGBUF0, 0xc0804184 );
			// c.addi x9, 4
			// c.sw x9,0(x11)
			MCF.WriteReg32( dev, DMPROGBUF1, 0xc1840491 );

			if( iss->statetag != STTAG( "RDSQ" ) )
			{
				StaticUpdatePROGBUFRegs( dev );
			}
		}

		if( iss->lastwriteflags != is_flash || iss->statetag != STTAG( "WRSQ" ) )
		{
			// If we are doing flash, we have to ack, otherwise we don't want to ack.
			if( is_flash )
			{
				// After writing to memory, also hit up page load flag.
				// c.sw x13,0(x12) // Acknowledge the page write.
				// c.ebreak
				MCF.WriteReg32( dev, DMPROGBUF2, 0x9002c214 );
			}
			else
			{
				MCF.WriteReg32( dev, DMPROGBUF2, 0x00019002 ); // c.ebreak
			}
		}

		MCF.WriteReg32( dev, DMDATA1, address_to_write );
		MCF.WriteReg32( dev, DMDATA0, data );

		if( did_disable_req )
		{
			MCF.WriteReg32( dev, DMCOMMAND, 0x00271008 ); // Copy data to x8, and execute program.
			MCF.WriteReg32( dev, DMABSTRACTAUTO, 1 ); // Enable Autoexec.
		}
		iss->lastwriteflags = is_flash;

		iss->statetag = STTAG( "WRSQ" );
		iss->currentstateval = address_to_write;

		if( is_flash )
		{
			ret |= MCF.WaitForDoneOp( dev, 0 );
			if( ret ) fprintf( stderr, "Fault on DefaultWriteWord Part 1\n" );
		}
	}
	else
	{
		if( address_to_write != iss->currentstateval )
		{
			MCF.WriteReg32( dev, DMABSTRACTAUTO, 0 ); // Disable Autoexec.
			MCF.WriteReg32( dev, DMDATA1, address_to_write );
			MCF.WriteReg32( dev, DMABSTRACTAUTO, 1 ); // Enable Autoexec.
		}
		MCF.WriteReg32( dev, DMDATA0, data );
		if( is_flash )
		{
			// XXX TODO: This likely can be a very short delay.
			// XXX POSSIBLE OPTIMIZATION REINVESTIGATE.
			ret |= MCF.WaitForDoneOp( dev, 0 );
			if( ret ) fprintf( stderr, "Fault on DefaultWriteWord Part 2\n" );
		}
		else
		{
			ret |= MCF.WaitForDoneOp( dev, 0 );
			if( ret ) fprintf( stderr, "Fault on DefaultWriteWord Part 3\n" );
		}
	}


	iss->currentstateval += 4;

	return ret;
}

int DefaultWriteBinaryBlob( void * dev, uint32_t address_to_write, uint32_t blob_size, uint8_t * blob )
{
	// NOTE IF YOU FIX SOMETHING IN THIS FUNCTION PLEASE ALSO UPDATE THE PROGRAMMERS.
	//  this is only fallback functionality for really realy basic programmers.

	uint32_t rw;
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);

	// We can't write into flash when mapped to 0x00000000
	if( address_to_write < 0x01000000 )
		address_to_write |= 0x08000000;

	int is_flash = IsAddressFlash( address_to_write );

	if( blob_size == 0 ) return 0;


	if( is_flash && !iss->flash_unlocked )
	{
		if( ( rw = InternalUnlockFlash( dev, iss ) ) )
			return rw;
	}

	if( is_flash && MCF.BlockWrite64 && ( address_to_write & 0x3f ) == 0 && ( blob_size & 0x3f ) == 0 )
	{
		int i;
		for( i = 0; i < blob_size; i+= 64 )
		{
			int r = MCF.BlockWrite64( dev, address_to_write + i, blob + i );
			if( r )
			{
				fprintf( stderr, "Error writing block at memory %08x / Error: %d\n", address_to_write, r );
				return r;
			}
		}
		return 0;
	}

	uint8_t tempblock[64];
	int sblock =  address_to_write >> 6;
	int eblock = ( address_to_write + blob_size + 0x3f) >> 6;
	int b;
	int rsofar = 0;

	for( b = sblock; b < eblock; b++ )
	{
		int offset_in_block = address_to_write - (b * 64);
		if( offset_in_block < 0 ) offset_in_block = 0;
		int end_o_plus_one_in_block = ( address_to_write + blob_size ) - (b*64);
		if( end_o_plus_one_in_block > 64 ) end_o_plus_one_in_block = 64;
		int	base = b * 64;

		if( offset_in_block == 0 && end_o_plus_one_in_block == 64 )
		{
			if( MCF.BlockWrite64 )
			{
				int r = MCF.BlockWrite64( dev, base, blob + rsofar );
				rsofar += 64;
				if( r )
				{
					fprintf( stderr, "Error writing block at memory %08x (error = %d)\n", base, r );
					return r;
				}
			}
			else 					// Block Write not avaialble
			{
				if( is_flash )
				{
					if( !InternalIsMemoryErased( iss, base ) )
						MCF.Erase( dev, base, 64, 0 );
					MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG ); // THIS IS REQUIRED, (intptr_t)&FLASH->CTLR = 0x40022010
					MCF.WriteWord( dev, 0x40022010, CR_BUF_RST | CR_PAGE_PG );  // (intptr_t)&FLASH->CTLR = 0x40022010
				}

				int j;
				for( j = 0; j < 16; j++ )
				{
					uint32_t writeword;
					memcpy( &writeword, blob + rsofar, 4 );
					MCF.WriteWord( dev, j*4+base, writeword );
					rsofar += 4;
				}

				if( is_flash )
				{
					MCF.WriteWord( dev, 0x40022014, base );  //0x40022014 -> FLASH->ADDR
					if( MCF.PrepForLongOp ) MCF.PrepForLongOp( dev );  // Give the programmer a headsup this next operation could take a while.
					MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG|CR_STRT_Set ); // 0x40022010 -> FLASH->CTLR
					if( MCF.WaitForFlash ) MCF.WaitForFlash( dev );
					InternalMarkMemoryNotErased( iss, base );
				}
			}
		}
		else
		{
			//Ok, we have to do something wacky.
			if( is_flash )
			{
				MCF.ReadBinaryBlob( dev, base, 64, tempblock );

				// Permute tempblock
				int tocopy = end_o_plus_one_in_block - offset_in_block;
				memcpy( tempblock + offset_in_block, blob + rsofar, tocopy );
				rsofar += tocopy;

				if( MCF.BlockWrite64 ) 
				{
					int r = MCF.BlockWrite64( dev, base, tempblock );
					if( r ) return r;
				}
				else
				{
					if( !InternalIsMemoryErased( iss, base ) )
						MCF.Erase( dev, base, 64, 0 );
					MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG ); // THIS IS REQUIRED, (intptr_t)&FLASH->CTLR = 0x40022010
					MCF.WriteWord( dev, 0x40022010, CR_BUF_RST | CR_PAGE_PG );  // (intptr_t)&FLASH->CTLR = 0x40022010

					int j;
					for( j = 0; j < 16; j++ )
					{
						MCF.WriteWord( dev, j*4+base, *(uint32_t*)(tempblock + j * 4) );
						rsofar += 4;
					}
					MCF.WriteWord( dev, 0x40022014, base );  //0x40022014 -> FLASH->ADDR
					MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG|CR_STRT_Set ); // 0x40022010 -> FLASH->CTLR
					InternalMarkMemoryNotErased( iss, base );
				}
				if( MCF.WaitForFlash && MCF.WaitForFlash( dev ) ) goto timedout;
			}
			else
			{
				// Accessing RAM.  Be careful to only do the needed operations.
				int j;
				for( j = 0; j < 16; j++ )
				{
					uint32_t taddy = j*4;
					if( offset_in_block <= taddy && end_o_plus_one_in_block >= taddy + 4 )
					{
						MCF.WriteWord( dev, taddy + base, *(uint32_t*)(blob + rsofar) );
						rsofar += 4;
					}
					else if( ( offset_in_block & 1 ) || ( end_o_plus_one_in_block & 1 ) )
					{
						// Bytes only.
						int j;
						for( j = 0; j < 4; j++ )
						{
							if( taddy >= offset_in_block && taddy < end_o_plus_one_in_block )
							{
								MCF.WriteByte( dev, taddy + base, *(uint32_t*)(blob + rsofar) );
								rsofar ++;
							}
							taddy++;
						}
					}
					else
					{
						// Half-words
						int j;
						for( j = 0; j < 4; j+=2 )
						{
							if( taddy >= offset_in_block && taddy < end_o_plus_one_in_block )
							{
								MCF.WriteHalfWord( dev, taddy + base, *(uint32_t*)(blob + rsofar) );
								rsofar +=2;
							}
							taddy+=2;
						}
					}
				}
			}
		}
	}

	MCF.FlushLLCommands( dev );

#if 0
	{
		uint8_t scratch[blob_size];
		int rrr = DefaultReadBinaryBlob( dev, address_to_write, blob_size, scratch );
		int i;
		printf( "Read op: %d\n", rrr );
		for( i = 0; i < blob_size; i++ )
		{
			if( scratch[i] != blob[i] )
			{
				printf( "DISAGREE: %04x\n", i );
				i = (i & ~0x3f) + 0x40-1;
			}
		}
	}
#endif

	if(MCF.DelayUS) MCF.DelayUS( dev, 100 ); // Why do we need this? (We seem to need this on the WCH programmers?)
	return 0;
timedout:
	fprintf( stderr, "Timed out\n" );
	return -5;
}

static int DefaultReadWord( void * dev, uint32_t address_to_read, uint32_t * data )
{
	int r = 0;
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);

	int autoincrement = 1;
	if( address_to_read == 0x40022010 || address_to_read == 0x4002200C )  // Don't autoincrement when checking flash flag. 
		autoincrement = 0;

	if( iss->statetag != STTAG( "RDSQ" ) || address_to_read != iss->currentstateval || autoincrement != iss->autoincrement)
	{
		if( iss->statetag != STTAG( "RDSQ" ) )
		{
			MCF.WriteReg32( dev, DMABSTRACTAUTO, 0 ); // Disable Autoexec.

			// c.lw x8,0(x11) // Pull the address from DATA1
			// c.lw x9,0(x8)  // Read the data at that location.
			MCF.WriteReg32( dev, DMPROGBUF0, 0x40044180 );
			if( autoincrement )
			{
				// c.addi x8, 4
				// c.sw x9, 0(x10) // Write back to DATA0

				MCF.WriteReg32( dev, DMPROGBUF1, 0xc1040411 );
			}
			else
			{
				// c.nop
				// c.sw x9, 0(x10) // Write back to DATA0

				MCF.WriteReg32( dev, DMPROGBUF1, 0xc1040001 );
			}
			// c.sw x8, 0(x11) // Write addy to DATA1
			// c.ebreak
			MCF.WriteReg32( dev, DMPROGBUF2, 0x9002c180 );

			if( iss->statetag != STTAG( "WRSQ" ) )
			{
				StaticUpdatePROGBUFRegs( dev );
			}
			MCF.WriteReg32( dev, DMABSTRACTAUTO, 1 ); // Enable Autoexec (different kind of autoinc than outer autoinc)
			iss->autoincrement = autoincrement;
		}

		MCF.WriteReg32( dev, DMDATA1, address_to_read );
		MCF.WriteReg32( dev, DMCOMMAND, 0x00241000 ); 

		iss->statetag = STTAG( "RDSQ" );
		iss->currentstateval = address_to_read;

		r |= MCF.WaitForDoneOp( dev, 0 );
		if( r ) fprintf( stderr, "Fault on DefaultReadWord Part 1\n" );
	}

	if( iss->autoincrement )
		iss->currentstateval += 4;

	r |= MCF.ReadReg32( dev, DMDATA0, data );

	if( iss->currentstateval == iss->ram_base + iss->ram_size )
		MCF.WaitForDoneOp( dev, 1 ); // Ignore any post-errors. 
	return r;
}

int InternalUnlockFlash( void * dev, struct InternalState * iss )
{
	int ret = 0;
	uint32_t rw;
	ret = MCF.ReadWord( dev, 0x40022010, &rw );  // FLASH->CTLR = 0x40022010
	if( rw & 0x8080 ) 
	{
		ret = MCF.WriteWord( dev, 0x40022004, 0x45670123 ); // FLASH->KEYR = 0x40022004
		if( ret ) goto reterr;
		ret = MCF.WriteWord( dev, 0x40022004, 0xCDEF89AB );
		if( ret ) goto reterr;
		ret = MCF.WriteWord( dev, 0x40022008, 0x45670123 ); // OBKEYR = 0x40022008
		if( ret ) goto reterr;
		ret = MCF.WriteWord( dev, 0x40022008, 0xCDEF89AB );
		if( ret ) goto reterr;
		ret = MCF.WriteWord( dev, 0x40022024, 0x45670123 ); // MODEKEYR = 0x40022024
		if( ret ) goto reterr;
		ret = MCF.WriteWord( dev, 0x40022024, 0xCDEF89AB );
		if( ret ) goto reterr;

		ret = MCF.ReadWord( dev, 0x40022010, &rw ); // FLASH->CTLR = 0x40022010
		if( ret ) goto reterr;

		if( rw & 0x8080 ) 
		{
			fprintf( stderr, "Error: Flash is not unlocked (CTLR = %08x)\n", rw );
			return -9;
		}
	}
	iss->flash_unlocked = 1;
	return 0;
reterr:
	fprintf( stderr, "Error unlocking flash, got code %d from underlying system\n", ret );
	return ret;
}

int DefaultErase( void * dev, uint32_t address, uint32_t length, int type )
{
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	uint32_t rw;

	if( !iss->flash_unlocked )
	{
		if( ( rw = InternalUnlockFlash( dev, iss ) ) )
			return rw;
	}

	if( type == 1 )
	{
		// Whole-chip flash
		iss->statetag = STTAG( "XXXX" );
		printf( "Whole-chip erase\n" );
		if( MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, 0 ) ) goto flashoperr;
		if( MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, FLASH_CTLR_MER  ) ) goto flashoperr;
		if( MCF.PrepForLongOp ) MCF.PrepForLongOp( dev );  // Give the programmer a headsup this next operation could take a while.
		if( MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, CR_STRT_Set|FLASH_CTLR_MER ) ) goto flashoperr;
		rw = MCF.WaitForDoneOp( dev, 0 );
		if( MCF.WaitForFlash && MCF.WaitForFlash( dev ) ) { fprintf( stderr, "Error: Wait for flash error.\n" ); return -11; }
		MCF.VoidHighLevelState( dev );
		memset( iss->flash_sector_status, 1, sizeof( iss->flash_sector_status ) );
	}
	else
	{
		// 16.4.7, Step 3: Check the BSY bit of the FLASH_STATR register to confirm that there are no other programming operations in progress.
		// skip (we make sure at the end)
		int chunk_to_erase = address;
		while( chunk_to_erase < address + length )
		{
			if( ( chunk_to_erase & 0xff000000 ) == 0x08000000 )
			{
				int sector = ( chunk_to_erase & 0x00ffffff ) / iss->sector_size;
				if( sector < MAX_FLASH_SECTORS )
				{
					printf( "Check Sector: %08x\n", sector );
					iss->flash_sector_status[sector] = 1;
				}
			}

			// Step 4:  set PAGE_ER of FLASH_CTLR(0x40022010)
			if( MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, CR_PAGE_ER ) ) goto flashoperr; // Actually FTER
			// Step 5: Write the first address of the fast erase page to the FLASH_ADDR register.
			if( MCF.WriteWord( dev, (intptr_t)&FLASH->ADDR, chunk_to_erase ) ) goto flashoperr;
			// Step 6: Set the STAT bit of FLASH_CTLR register to '1' to initiate a fast page erase (64 bytes) action.
			if( MCF.PrepForLongOp ) MCF.PrepForLongOp( dev );  // Give the programmer a headsup this next operation could take a while.
			if( MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, CR_STRT_Set | CR_PAGE_ER ) ) goto flashoperr;
			if( MCF.WaitForFlash && MCF.WaitForFlash( dev ) ) return -99;
			chunk_to_erase+=64;
		}
	}

	return 0;
flashoperr:
	fprintf( stderr, "Error: Flash operation error\n" );
	return -93;
}

int DefaultReadBinaryBlob( void * dev, uint32_t address_to_read_from, uint32_t read_size, uint8_t * blob )
{
	uint32_t rpos = address_to_read_from;
	uint32_t rend = address_to_read_from + read_size;

	while( rpos < rend )
	{
		int r;
		int remain = rend - rpos;

		if( ( rpos & 3 ) == 0 && remain >= 4 )
		{
			uint32_t rw;
			r = MCF.ReadWord( dev, rpos, &rw );
			//printf( "RW: %d %08x %08x\n", r, rpos, rw );
			if( r ) return r;
			int rem = remain;
			if( rem > 4 ) rem = 4;
			memcpy( blob, &rw, rem);
			blob += 4;
			rpos += 4;
		}
		else
		{
			if( ( rpos & 1 ) )
			{
				uint8_t rw;
				r = MCF.ReadByte( dev, rpos, &rw );
				if( r ) return r;
				memcpy( blob, &rw, 1 );
				blob += 1;
				rpos += 1;
				remain -= 1;
			}
			if( ( rpos & 2 ) && remain >= 2 )
			{
				uint16_t rw;
				r = MCF.ReadHalfWord( dev, rpos, &rw );
				if( r ) return r;
				memcpy( blob, &rw, 2 );
				blob += 2;
				rpos += 2;
				remain -= 2;
			}
			if( remain >= 1 )
			{
				uint8_t rw;
				r = MCF.ReadByte( dev, rpos, &rw );
				if( r ) return r;
				memcpy( blob, &rw, 1 );
				blob += 1;
				rpos += 1;
				remain -= 1;
			}
		}
	}
	int r = MCF.WaitForDoneOp( dev, 0 );
	if( r ) fprintf( stderr, "Fault on DefaultReadBinaryBlob\n" );
	return r;
}

int DefaultReadCPURegister( void * dev, uint32_t regno, uint32_t * regret )
{
	if( !MCF.WriteReg32 || !MCF.ReadReg32 )
	{
		fprintf( stderr, "Error: Can't read CPU register on this programmer because it is missing read/writereg32\n" );
		return -5;
	}

	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
	iss->statetag = STTAG( "REGR" );

	MCF.WriteReg32( dev, DMCOMMAND, 0x00220000 | regno ); // Read xN into DATA0.
	int r = MCF.ReadReg32( dev, DMDATA0, regret );

	return r;
}

int DefaultReadAllCPURegisters( void * dev, uint32_t * regret )
{
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000001 ); // Disable Autoexec.
	iss->statetag = STTAG( "RER2" );
	int i;
	for( i = 0; i < 16; i++ )
	{
		MCF.WriteReg32( dev, DMCOMMAND, 0x00220000 | 0x1000 | i ); // Read xN into DATA0.
		if( MCF.ReadReg32( dev, DMDATA0, regret + i ) )
		{
			MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
			return -5;
		}
	}
	MCF.WriteReg32( dev, DMCOMMAND, 0x00220000 | 0x7b1 ); // Read xN into DATA0.
	int r = MCF.ReadReg32( dev, DMDATA0, regret + i );
	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
	return r;
}

int DefaultWriteAllCPURegisters( void * dev, uint32_t * regret )
{
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000001 ); // Disable Autoexec.
	iss->statetag = STTAG( "WER2" );
	int i;
	for( i = 0; i < 16; i++ )
	{
		MCF.WriteReg32( dev, DMCOMMAND, 0x00230000 | 0x1000 | i ); // Read xN into DATA0.
		if( MCF.WriteReg32( dev, DMDATA0, regret[i] ) )
		{
			MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
			return -5;
		}
	}
	MCF.WriteReg32( dev, DMCOMMAND, 0x00230000 | 0x7b1 ); // Read xN into DATA0.
	int r = MCF.WriteReg32( dev, DMDATA0, regret[i] );
	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
	return r;
}


int DefaultWriteCPURegister( void * dev, uint32_t regno, uint32_t value )
{
	if( !MCF.WriteReg32 || !MCF.ReadReg32 )
	{
		fprintf( stderr, "Error: Can't read CPU register on this programmer because it is missing read/writereg32\n" );
		return -5;
	}

	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
	iss->statetag = STTAG( "REGW" );
	MCF.WriteReg32( dev, DMDATA0, value );
	return MCF.WriteReg32( dev, DMCOMMAND, 0x00230000 | regno ); // Write xN from DATA0.
}

int DefaultSetEnableBreakpoints( void * dev, int is_enabled, int single_step )
{
	if( !MCF.ReadCPURegister || !MCF.WriteCPURegister )
	{
		fprintf( stderr, "Error: Can't set breakpoints on this programmer because it is missing read/writereg32\n" );
		return -5;
	}
	uint32_t DCSR;
	if( MCF.ReadCPURegister( dev, 0x7b0, &DCSR ) )
		fprintf( stderr, "Error: DCSR could not be read\n" );
	DCSR |= 0xb600;
	if( single_step )
		DCSR |= 4;
	else
		DCSR &=~4;

	//printf( "Setting DCSR: %08x\n", DCSR );
	if( MCF.WriteCPURegister( dev, 0x7b0, DCSR ) )
		fprintf( stderr, "Error: DCSR could not be read\n" );

	return 0;
}


static int DefaultHaltMode( void * dev, int mode )
{
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	switch ( mode )
	{
	case HALT_MODE_HALT_BUT_NO_RESET: // Don't reboot.
	case HALT_MODE_HALT_AND_RESET:
		MCF.WriteReg32( dev, DMSHDWCFGR, 0x5aa50000 | (1<<10) ); // Shadow Config Reg
		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // CFGR (1<<10 == Allow output from slave)
		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // Bug in silicon?  If coming out of cold boot, and we don't do our little "song and dance" this has to be called.
		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
		if( mode == 0 ) MCF.WriteReg32( dev, DMCONTROL, 0x80000003 ); // Reboot.
		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Re-initiate a halt request.
//		MCF.WriteReg32( dev, DMCONTROL, 0x00000001 ); // Clear Halt Request.  This is recommended, but not doing it seems more stable.
		// Sometimes, even if the processor is halted but the MSB is clear, it will spuriously start?
		MCF.FlushLLCommands( dev );
		break;
	case HALT_MODE_REBOOT:
		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Initiate a halt request.
		MCF.WriteReg32( dev, DMCONTROL, 0x80000003 ); // Reboot.
		MCF.WriteReg32( dev, DMCONTROL, 0x40000001 ); // resumereq
		MCF.FlushLLCommands( dev );
		break;
	case HALT_MODE_RESUME:
		MCF.WriteReg32( dev, DMSHDWCFGR, 0x5aa50000 | (1<<10) ); // Shadow Config Reg
		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // CFGR (1<<10 == Allow output from slave)
		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // Bug in silicon?  If coming out of cold boot, and we don't do our little "song and dance" this has to be called.

		MCF.WriteReg32( dev, DMCONTROL, 0x40000001 ); // resumereq
		MCF.FlushLLCommands( dev );
		break;
	case HALT_MODE_GO_TO_BOOTLOADER:
		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Initiate a halt request.

		MCF.WriteWord( dev, (intptr_t)&FLASH->KEYR, FLASH_KEY1 );
		MCF.WriteWord( dev, (intptr_t)&FLASH->KEYR, FLASH_KEY2 );
		MCF.WriteWord( dev, (intptr_t)&FLASH->BOOT_MODEKEYR, FLASH_KEY1 );
		MCF.WriteWord( dev, (intptr_t)&FLASH->BOOT_MODEKEYR, FLASH_KEY2 );
		MCF.WriteWord( dev, (intptr_t)&FLASH->STATR, 1<<14 );
		MCF.WriteWord( dev, (intptr_t)&FLASH->CTLR, CR_LOCK_Set );

		MCF.WriteReg32( dev, DMCONTROL, 0x80000003 ); // Reboot.
		MCF.WriteReg32( dev, DMCONTROL, 0x40000001 ); // resumereq
		MCF.FlushLLCommands( dev );
		break;
	default:
		fprintf( stderr, "Error: Unknown halt mode %d\n", mode );
	}

	// pull reset line back to 0 again (NO RESET)  ((XXX TODO: Move this to unbrick))
	if (MCF.TargetReset) {
		MCF.TargetReset(dev, 0);
	}

	iss->flash_unlocked = 0;

	iss->processor_in_mode = mode;
	return 0;
}

// Returns positive if received text.
// Returns negative if error.
// Returns 0 if no text waiting.
// maxlen MUST be at least 8 characters.  We null terminate.
int DefaultPollTerminal( void * dev, uint8_t * buffer, int maxlen, uint32_t leaveflagA, int leaveflagB )
{
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);

	int r;
	uint32_t rr;
	if( iss->statetag != STTAG( "TERM" ) )
	{
		MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
		iss->statetag = STTAG( "TERM" );
	}
	r = MCF.ReadReg32( dev, DMDATA0, &rr );
	if( r < 0 ) return r;

	if( maxlen < 8 ) return -9;

	// DMDATA1:
	//  bit  7 = host-acknowledge.
	if( rr & 0x80 )
	{
		int ret = 0;
		int num_printf_chars = (rr & 0xf)-4;

		if( num_printf_chars > 0 && num_printf_chars <= 7)
		{
			if( num_printf_chars > 3 )
			{
				uint32_t r2;
				r = MCF.ReadReg32( dev, DMDATA1, &r2 );
				memcpy( buffer+3, &r2, num_printf_chars - 3 );
			}
			int firstrem = num_printf_chars;
			if( firstrem > 3 ) firstrem = 3;
			memcpy( buffer, ((uint8_t*)&rr)+1, firstrem );
			buffer[num_printf_chars] = 0;
			ret = num_printf_chars;
		}
		if( leaveflagA ) MCF.WriteReg32( dev, DMDATA1, leaveflagB );
		MCF.WriteReg32( dev, DMDATA0, leaveflagA ); // Write that we acknowledge the data.
		return ret;
	}
	else
	{
		return 0;
	}
}

int DefaultUnbrick( void * dev )
{
	printf( "Entering Unbrick Mode\n" );
	MCF.Control3v3( dev, 0 );
	MCF.DelayUS( dev, 60000 );
	MCF.DelayUS( dev, 60000 );
	MCF.DelayUS( dev, 60000 );
	MCF.DelayUS( dev, 60000 );
	MCF.Control3v3( dev, 1 );
	MCF.DelayUS( dev, 100 );
	MCF.FlushLLCommands( dev );
	printf( "Connection starting\n" );
	int timeout = 0;
	int max_timeout = 500;
	uint32_t ds = 0;
	for( timeout = 0; timeout < max_timeout; timeout++ )
	{
		MCF.DelayUS( dev, 10 );
		MCF.WriteReg32( dev, DMSHDWCFGR, 0x5aa50000 | (1<<10) ); // Shadow Config Reg
		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // CFGR (1<<10 == Allow output from slave)
		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // Bug in silicon?  If coming out of cold boot, and we don't do our little "song and dance" this has to be called.
		MCF.FlushLLCommands( dev );
		int r = MCF.ReadReg32( dev, DMSTATUS, &ds );
		printf( "/%d/%08x\n", r, ds );
		MCF.FlushLLCommands( dev );
		if( ds != 0xffffffff && ds != 0x00000000 ) break;
	}

	// Make sure we are in halt.
	MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
	MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Initiate a halt request.
	MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // No, really make sure.
	MCF.WriteReg32( dev, DMCONTROL, 0x80000001 );

//  Many times we would clear the halt request, but in this case, we want to just leave it here, to prevent it from booting.
//  TODO: Experiment and see if this is needed/wanted in cases.  NOTE: If you don't clear halt request, progarmmers can get stuck.
//	MCF.WriteReg32( dev, DMCONTROL, 0x00000001 ); // Clear Halt Request.

	// After more experimentation, it appaers to work best by not clearing the halt request.

	MCF.FlushLLCommands( dev );
	if( MCF.DelayUS )
		MCF.DelayUS( dev, 20000 );
	else
	{
#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
		Sleep(20);
#else
		usleep(20000);
#endif
	}

	if( timeout == max_timeout ) 
	{
		fprintf( stderr, "Timed out trying to unbrick\n" );
		return -5;
	}
	MCF.Erase( dev, 0, 0, 1);
	MCF.FlushLLCommands( dev );
	return -5;
}

int DefaultConfigureNRSTAsGPIO( void * dev, int one_if_yes_gpio  )
{
	fprintf( stderr, "Error: DefaultConfigureNRSTAsGPIO does not work via the programmer here.  Please see the demo \"optionbytes\"\n" );
	return -5;
}

int DefaultConfigureReadProtection( void * dev, int one_if_yes_protect  )
{
	fprintf( stderr, "Error: DefaultConfigureReadProtection does not work via the programmer here.  Please see the demo \"optionbytes\"\n" );
	return -5;
}

int DefaultPrintChipInfo( void * dev )
{
	uint32_t reg;
	MCF.HaltMode( dev, HALT_MODE_HALT_BUT_NO_RESET );
	
	if( MCF.ReadWord( dev, 0x1FFFF800, &reg ) ) goto fail;	
	printf( "USER/RDPR  : %04x/%04x\n", reg>>16, reg&0xFFFF );
	if( MCF.ReadWord( dev, 0x1FFFF804, &reg ) ) goto fail;	
	printf( "DATA1/DATA0: %04x/%04x\n", reg>>16, reg&0xFFFF );
	if( MCF.ReadWord( dev, 0x1FFFF808, &reg ) ) goto fail;	
	printf( "WRPR1/WRPR0: %04x/%04x\n", reg>>16, reg&0xFFFF );
	if( MCF.ReadWord( dev, 0x1FFFF80c, &reg ) ) goto fail;	
	printf( "WRPR3/WRPR2: %04x/%04x\n", reg>>16, reg&0xFFFF );
	if( MCF.ReadWord( dev, 0x1FFFF7E0, &reg ) ) goto fail;
	printf( "Flash Size: %d kB\n", (reg&0xffff) );
	if( MCF.ReadWord( dev, 0x1FFFF7E8, &reg ) ) goto fail;	
	printf( "R32_ESIG_UNIID1: %08x\n", reg );
	if( MCF.ReadWord( dev, 0x1FFFF7EC, &reg ) ) goto fail;	
	printf( "R32_ESIG_UNIID2: %08x\n", reg );
	if( MCF.ReadWord( dev, 0x1FFFF7F0, &reg ) ) goto fail;	
	printf( "R32_ESIG_UNIID3: %08x\n", reg );
	return 0;
fail:
	fprintf( stderr, "Error: Failed to get chip details\n" );
	return -11;
}

int DefaultVoidHighLevelState( void * dev )
{
	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
	iss->statetag = STTAG( "VOID" );
	return 0;
}

int SetupAutomaticHighLevelFunctions( void * dev )
{
	// Will populate high-level functions from low-level functions.
	if( MCF.WriteReg32 == 0 && MCF.ReadReg32 == 0 && MCF.WriteWord == 0 ) return -5;

	// Else, TODO: Build the high level functions from low level functions.
	// If a high-level function alrady exists, don't override.
	
	if( !MCF.SetupInterface )
		MCF.SetupInterface = DefaultSetupInterface;
	if( !MCF.WriteBinaryBlob )
		MCF.WriteBinaryBlob = DefaultWriteBinaryBlob;
	if( !MCF.ReadBinaryBlob )
		MCF.ReadBinaryBlob = DefaultReadBinaryBlob;
	if( !MCF.WriteWord )
		MCF.WriteWord = DefaultWriteWord;
	if( !MCF.WriteHalfWord )
		MCF.WriteHalfWord = DefaultWriteHalfWord;
	if( !MCF.WriteByte )
		MCF.WriteByte = DefaultWriteByte;
	if( !MCF.ReadCPURegister )
		MCF.ReadCPURegister = DefaultReadCPURegister;
	if( !MCF.WriteCPURegister )
		MCF.WriteCPURegister = DefaultWriteCPURegister;
	if( !MCF.WriteAllCPURegisters )
		MCF.WriteAllCPURegisters = DefaultWriteAllCPURegisters;
	if( !MCF.ReadAllCPURegisters )
		MCF.ReadAllCPURegisters = DefaultReadAllCPURegisters;
	if( !MCF.SetEnableBreakpoints )
		MCF.SetEnableBreakpoints = DefaultSetEnableBreakpoints;
	if( !MCF.ReadWord )
		MCF.ReadWord = DefaultReadWord;
	if( !MCF.ReadHalfWord )
		MCF.ReadHalfWord = DefaultReadHalfWord;
	if( !MCF.ReadByte )
		MCF.ReadByte = DefaultReadByte;
	if( !MCF.Erase )
		MCF.Erase = DefaultErase;
	if( !MCF.HaltMode )
		MCF.HaltMode = DefaultHaltMode;
	if( !MCF.PollTerminal )
		MCF.PollTerminal = DefaultPollTerminal;
	if( !MCF.WaitForFlash )
		MCF.WaitForFlash = DefaultWaitForFlash;
	if( !MCF.WaitForDoneOp )
		MCF.WaitForDoneOp = DefaultWaitForDoneOp;
	if( !MCF.PrintChipInfo )
		MCF.PrintChipInfo = DefaultPrintChipInfo;
	if( !MCF.Unbrick )
		MCF.Unbrick = DefaultUnbrick;
	if( !MCF.ConfigureNRSTAsGPIO )
		MCF.ConfigureNRSTAsGPIO = DefaultConfigureNRSTAsGPIO;
	if( !MCF.VoidHighLevelState )
		MCF.VoidHighLevelState = DefaultVoidHighLevelState;

	struct InternalState * iss = calloc( 1, sizeof( struct InternalState ) );
	iss->ram_base = 0x20000000;
	iss->ram_size = 2048;
	iss->sector_size = 64;

	((struct ProgrammerStructBase*)dev)->internal = iss;
	return 0;
}




void TestFunction(void * dev )
{
	uint32_t rv;
	int r;
	MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
	MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Initiate a halt request.
	MCF.WriteReg32( dev, DMCONTROL, 0x00000001 ); // Clear Halt Request.

	r = MCF.WriteWord( dev, 0x20000100, 0xdeadbeef );
	r = MCF.WriteWord( dev, 0x20000104, 0xcafed0de );
	r = MCF.WriteWord( dev, 0x20000108, 0x12345678 );
	r = MCF.WriteWord( dev, 0x20000108, 0x00b00d00 );
	r = MCF.WriteWord( dev, 0x20000104, 0x33334444 );

	r = MCF.ReadWord( dev, 0x20000100, &rv );
	printf( "**>>> %d %08x\n", r, rv );
	r = MCF.ReadWord( dev, 0x20000104, &rv );
	printf( "**>>> %d %08x\n", r, rv );
	r = MCF.ReadWord( dev, 0x20000108, &rv );
	printf( "**>>> %d %08x\n", r, rv );


	r = MCF.ReadWord( dev, 0x00000300, &rv );
	printf( "F %d %08x\n", r, rv );
	r = MCF.ReadWord( dev, 0x00000304, &rv );
	printf( "F %d %08x\n", r, rv );
	r = MCF.ReadWord( dev, 0x00000308, &rv );
	printf( "F %d %08x\n", r, rv );

	uint8_t buffer[256];
	int i;
	for( i = 0; i < 256; i++ ) buffer[i] = 0;
	MCF.WriteBinaryBlob( dev, 0x08000300, 256, buffer );
	MCF.ReadBinaryBlob( dev, 0x08000300, 256, buffer );
	for( i = 0; i < 256; i++ )
	{
		printf( "%02x ", buffer[i] );
		if( (i & 0xf) == 0xf ) printf( "\n" );
	}

	for( i = 0; i < 256; i++ ) buffer[i] = i;
	MCF.WriteBinaryBlob( dev, 0x08000300, 256, buffer );
	MCF.ReadBinaryBlob( dev, 0x08000300, 256, buffer );
	for( i = 0; i < 256; i++ )
	{
		printf( "%02x ", buffer[i] );
		if( (i & 0xf) == 0xf ) printf( "\n" );
	}
}