// CH32V003 SWIO / CH32Vxxx / CH32Xxxx bit banging minimal reference
// implementation for bit banged IO or primitive IO.
//
// You will need to implement some of the basic functions. Example
// implementations for the functions on the ESP32-S2 are included.
//
// Copyright 2023-2024 <>< Charles Lohr, May be licensed under the MIT/x11 or
// NewBSD licenses. You choose. (Can be included in commercial and copyleft work)
//
// This file is original work.
//
// Mostly tested, though, not may not be perfect. Expect to tweak some things.
// The 003 code is pretty hardened, the other code may not be.
//
// Originally the only resources used to produce this file were publicly available docs
// and playing with the chips.  The only leveraged tool was this:
//   https://github.com/perigoso/sigrok-rvswd

#ifndef _WCH_RVSWD_RVSWIO_H
#define _WCH_RVSWD_RVSWIO_H

#include <stdint.h>
#include <string.h>

// This is a hacky thing, but if you are laaaaazzzyyyy and don't want to add a 10k
// resistor, youcan do this.  It glitches the line high very, very briefly.
// Enable for when you don't have a 10k pull-upand are relying on the internal pull-up.
// WARNING: If you set this, you should set the drive current to 5mA.  This is
// only useful / needed if you are using the built-in functions.
//#define R_GLITCH_HIGH
//#define RUNNING_ON_ESP32S2
//#define BB_PRINTF_DEBUG uprintf

// You should interface to this file via these functions

enum RiscVChip {
	CHIP_UNKNOWN = 0x00,
	CHIP_CH32V10x = 0x01,
	CHIP_CH57x = 0x02,
	CHIP_CH56x = 0x03,
	CHIP_CH32V20x = 0x05,
	CHIP_CH32V30x = 0x06,
	CHIP_CH58x = 0x07,
	CHIP_CH32V003 = 0x09,
	CHIP_CH59x = 0x0b,
	CHIP_CH643 = 0x0c,
	CHIP_CH32X03x = 0x0d,
	CHIP_CH32L10x = 0x0e,
	CHIP_CH564 = 0x0f,

	CHIP_CH32V00x = 0x4e,

	CHIP_CH570 = 0x8b,
	CHIP_CH585 = 0x4b,
	CHIP_CH645 = 0x46,
	CHIP_CH641 = 0x49,
	CHIP_CH32V317 = 0x86,
	CHIP_CH32M030 = 0x8e,
};

enum MemoryArea {
	DEFAULT_AREA = 0,
	PROGRAM_AREA = 1,
	BOOTLOADER_AREA = 2,
	OPTIONS_AREA = 3,
	EEPROM_AREA = 4,
	RAM_AREA = 5,
};

struct SWIOState
{
	int opmode; // 0 for undefined, 1 for SWIO, 2 for SWD
	enum RiscVChip target_chip_type;
	int sectorsize;

	// Zero the rest of the structure.
	uint32_t statetag;
	uint32_t lastwriteflags;
	uint32_t currentstateval;
	uint32_t flash_unlocked;
	uint32_t autoincrement;
	uint32_t clock_set;
	uint32_t no_autoexec;
};

#define STTAG( x ) (*((uint32_t*)(x)))


// Provided Basic functions
static inline void ConfigureIOForRVSWD(void);
static inline void ConfigureIOForRVSWIO(void);
static void MCFWriteReg32( struct SWIOState * state, uint8_t command, uint32_t value );
static int MCFReadReg32( struct SWIOState * state, uint8_t command, uint32_t * value );

// More advanced functions built on lower level PHY.
static int InitializeSWDSWIO( struct SWIOState * state );
static int ReadWord( struct SWIOState * state, uint32_t word, uint32_t * ret );
static int WriteWord( struct SWIOState * state, uint32_t word, uint32_t val );
static int WaitForFlash( struct SWIOState * state );
static int WaitForDoneOp( struct SWIOState * state );
static int WriteBlock( struct SWIOState * iss, uint32_t address_to_write, uint8_t * data, uint8_t len, uint8_t erase );
static int UnlockFlash( struct SWIOState * iss );
static int EraseFlash( struct SWIOState * iss, uint32_t address, uint32_t length, int type );
static void ResetInternalProgrammingState( struct SWIOState * iss );
static int PollTerminal( struct SWIOState * iss, uint8_t * buffer, int maxlen, uint32_t leavevalA, uint32_t leavevalB );


#define BDMDATA0        0x04
#define BDMDATA1        0x05
#define DMCONTROL      0x10
#define DMSTATUS       0x11
#define DMHARTINFO     0x12
#define DMABSTRACTCS   0x16
#define DMCOMMAND      0x17
#define DMABSTRACTAUTO 0x18
#define DMPROGBUF0     0x20
#define DMPROGBUF1     0x21
#define DMPROGBUF2     0x22
#define DMPROGBUF3     0x23
#define DMPROGBUF4     0x24
#define DMPROGBUF5     0x25
#define DMPROGBUF6     0x26
#define DMPROGBUF7     0x27

#define DMCPBR       0x7C
#define DMCFGR       0x7D
#define DMSHDWCFGR   0x7E

#ifndef __CH32V00x_H
#define FLASH_STATR_WRPRTERR       ((uint8_t)0x10)
#define CR_PAGE_PG                 ((uint32_t)0x00010000)
#define CR_BUF_LOAD                ((uint32_t)0x00040000)
#ifndef FLASH_CTLR_MER
#define FLASH_CTLR_MER             ((uint16_t)0x0004)     /* Mass Erase */
#endif
#define CR_STRT_Set                ((uint32_t)0x00000040)
#define CR_PAGE_ER                 ((uint32_t)0x00020000)
#define CR_BUF_RST                 ((uint32_t)0x00080000)
#endif


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// High level functions
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static int InitializeSWDSWIO( struct SWIOState * state )
{
	for( int timeout = 20; timeout; timeout-- )
	{
		//printf( "CFG FOR RV\n" );
		ConfigureIOForRVSWIO();
		//printf( "DONE CFG FOR RV\n" );

		// Careful - don't halt the part, we might just want to attach for a debug printf or something.
		state->target_chip_type = CHIP_UNKNOWN;
		state->sectorsize = 64;

		state->opmode = 1; // Try SWIO first
		// First try to see if there is an 003.
		MCFWriteReg32( state, DMSHDWCFGR, 0x5aa50000 | (1<<10) ); // Shadow Config Reg
		MCFWriteReg32( state, DMCFGR, 0x5aa50000 | (1<<10) ); // CFGR (1<<10 == Allow output from slave)
		MCFWriteReg32( state, DMSHDWCFGR, 0x5aa50000 | (1<<10) ); // Try twice just in case
		MCFWriteReg32( state, DMCFGR, 0x5aa50000 | (1<<10) );

		MCFWriteReg32( state, DMCONTROL, 0x00000001 );
		MCFWriteReg32( state, DMCONTROL, 0x00000001 );

		// See if we can see a chip here...
		uint32_t value = 0;
		int readdm = MCFReadReg32( state, DMCFGR, &value );
		if( readdm == 0 && ( value & 0xffff0000 ) == ( 0x5aa50000 ) )
		{
			BB_PRINTF_DEBUG( "Found RVSWIO interface.\n" );
			return 0;
		}

		//printf( "CFG FOR SWD\n" );
		ConfigureIOForRVSWD();
		//printf( "DONE CFG FOR SWD\n" );

		//Otherwise Maybe it's SWD?
		state->opmode = 2;

		MCFWriteReg32( state, DMCONTROL, 0x00000001 );
		MCFWriteReg32( state, DMCONTROL, 0x00000001 );

		uint32_t dmstatus, dmcontrol;
		if( MCFReadReg32( state, DMSTATUS, &dmstatus ) != 0 ||
			MCFReadReg32( state, DMCONTROL, &dmcontrol ) != 0 )
		{
			BB_PRINTF_DEBUG( "Could not read from RVSWD connection\n" );
			state->opmode = 0;
			continue;
		}

		// See if we can see a chip here...
		if( ( ( ( dmstatus >> 8 ) & 0xf ) != 0x0c &&
			( ( dmstatus >> 8 ) & 0xf ) != 0x03 ) ||
			dmcontrol != 1 )
		{
			BB_PRINTF_DEBUG( "DMSTATUS invalid (Probably no RVSWD chip)\n" );
			state->opmode = 0;
			continue;
		}

		MCFWriteReg32( state, DMABSTRACTCS, 0x08000302 ); // Clear out abstractcs register.
		BB_PRINTF_DEBUG( "Found RVSWD interface\n" );
		return 0;
	}
	BB_PRINTF_DEBUG( "TIMEOUT\n" );
	return -55;
}

static int DetermineChipTypeAndSectorInfo( struct SWIOState * iss, uint8_t * reply)
{
	struct SWIOState * dev = iss;
	if( iss->target_chip_type == CHIP_UNKNOWN || reply != NULL )
	{
		if( iss->opmode == 0 )
		{
			int r = InitializeSWDSWIO( iss );
			if( r ) return 0x11;
		}
		uint32_t rr;
		if( MCFReadReg32( dev, DMHARTINFO, &rr ) )
		{
			BB_PRINTF_DEBUG( "Error: Could not get hart info.\n" );
			return 0x12;
		}

		MCFWriteReg32( dev, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
		MCFWriteReg32( dev, DMCONTROL, 0x80000001 ); // Initiate halt request.

		// Tricky, this function needs to clean everything up because it may be used entering debugger.
		uint32_t old_data0;
		MCFReadReg32( dev, BDMDATA0, &old_data0 );
		MCFWriteReg32( dev, DMCOMMAND, 0x00221008 );		// Copy data from x8.
		uint32_t old_x8;
		MCFReadReg32( dev, BDMDATA0, &old_x8 );

		uint32_t marchid = 0;

		MCFWriteReg32( dev, DMABSTRACTCS, 0x08000700 ); // Clear out any dmabstractcs errors.

		MCFWriteReg32( dev, DMABSTRACTAUTO, 0x00000000 );
		MCFWriteReg32( dev, DMCOMMAND, 0x00220000 | 0xf12 );
		MCFWriteReg32( dev, DMCOMMAND, 0x00220000 | 0xf12 );  // Need to double-read, not sure why.
		MCFReadReg32( dev, BDMDATA0, &marchid );
		MCFWriteReg32( dev, DMPROGBUF0, 0x90024000 );		// c.ebreak <<== c.lw x8, 0(x8)

		uint32_t chip_id = 0;
		uint32_t vendor_bytes = 0;
		uint32_t sevenf_id = 0;
		int read_protection = 0;
		uint8_t ch5xx = 0;
		MCFReadReg32( dev, 0x7f, &sevenf_id );

		if( sevenf_id == 0 )
		{
			// Need to load new progbuf because we're reading 1 byte now
			MCFWriteReg32( dev, DMPROGBUF0, 0x00040403 ); // lb x8, 0(x8)
			MCFWriteReg32( dev, DMPROGBUF1, 0x00100073 ); // c.ebreak
			MCFWriteReg32( dev, BDMDATA0, 0x40001041 );			// Special chip ID location.
			MCFWriteReg32( dev, DMCOMMAND, 0x00271008 );		// Copy data to x8, and execute.
			WaitForDoneOp( dev );
			MCFWriteReg32( dev, DMCOMMAND, 0x00221008 );		// Copy data from x8.
			MCFReadReg32( dev, BDMDATA0, &chip_id );
			chip_id = chip_id & 0xff;

			// Looks like a CH32V103 or a CH56x
			if( chip_id == 0 )
			{
				// First check for CH56x
				MCFWriteReg32( dev, BDMDATA0, 0x40001001 );
				MCFWriteReg32( dev, DMCOMMAND, 0x00271008 );		// Copy data to x8, and execute.
				WaitForDoneOp( dev );
				MCFWriteReg32( dev, DMCOMMAND, 0x00221008 );		// Copy data from x8.
				MCFReadReg32( dev, BDMDATA0, &chip_id );
				MCFWriteReg32( dev, BDMDATA0, 0x40001002 );			// Special chip ID location.
				MCFWriteReg32( dev, DMCOMMAND, 0x00271008 );		// Copy data to x8, and execute.
				WaitForDoneOp( dev );
				MCFWriteReg32( dev, DMCOMMAND, 0x00221008 );		// Copy data from x8.
				MCFReadReg32( dev, BDMDATA0, &vendor_bytes );

				if( (vendor_bytes & 0xff) == 2 && ((chip_id & 0xff) == 65 || (chip_id & 0xff) == 69) )
				{
					iss->target_chip_type = CHIP_CH56x;
					chip_id = 0x500 | (chip_id & 0xff);
					goto chip_identified;
				}

				// Now actually check for CH32V103
				MCFWriteReg32( dev, DMPROGBUF0, 0x90024000 );	// c.ebreak <<== c.lw x8, 0(x8)
				MCFWriteReg32( dev, BDMDATA0, 0x1ffff880 );			// Special chip ID location.
				MCFWriteReg32( dev, DMCOMMAND, 0x00271008 );		// Copy data to x8, and execute.
				WaitForDoneOp( dev );
				MCFWriteReg32( dev, DMCOMMAND, 0x00221008 );		// Copy data from x8.
				MCFReadReg32( dev, BDMDATA0, &chip_id );
				MCFWriteReg32( dev, BDMDATA0, 0x1ffff884 );			// Special chip ID location.
				MCFWriteReg32( dev, DMCOMMAND, 0x00271008 );		// Copy data to x8, and execute.
				WaitForDoneOp( dev );
				MCFWriteReg32( dev, DMCOMMAND, 0x00221008 );		// Copy data from x8.
				MCFReadReg32( dev, BDMDATA0, &vendor_bytes );

				if( ((((vendor_bytes >> 16) & 0xff00) != 0x2500) && (((vendor_bytes >> 16) & 0xdf00) != 0x1500)) || chip_id != 0xdc78fe34 )
				{
					uint32_t flash_obr = 0;
					MCFWriteReg32( dev, BDMDATA0, 0x4002201c );			// Special chip ID location.
					MCFWriteReg32( dev, DMCOMMAND, 0x00271008 );		// Copy data to x8, and execute.
					WaitForDoneOp( dev );
					MCFWriteReg32( dev, DMCOMMAND, 0x00221008 );		// Copy data from x8.
					MCFReadReg32( dev, BDMDATA0, &flash_obr );

					if( (flash_obr & 3) == 2 )
					{
						iss->target_chip_type = CHIP_CH32V10x;
						iss->no_autoexec = 1;
						iss->sectorsize = 128;
						read_protection = 1;
						chip_id = (3 << 12) | 0x103;
					}
				}
				else
				{
					iss->target_chip_type = CHIP_CH32V10x;
					iss->no_autoexec = 1;
					iss->sectorsize = 128;
					read_protection = -1;
					chip_id = (3 << 12) | 0x103;
				}
			}
			else
			{
				// Check for CH5xx
				read_protection = -1;
				if( (chip_id & 0xf0) == 0x90 )
				{
					uint32_t sevenc = 0;
					MCFReadReg32( dev, DMCPBR, &sevenc );
					if((sevenc & 0x30000) == 0)
					{
						iss->target_chip_type = CHIP_CH59x;
						iss->sectorsize = 256;
						ch5xx = 1;
						chip_id = 0x500 | chip_id;
					}
					else
					{
						iss->target_chip_type = CHIP_CH585;
						iss->no_autoexec = 1;
						iss->sectorsize = 256;
						ch5xx = 1;
						chip_id = 0x500 | 0x85;
					}
				}
				else
				{
					if( chip_id == 0x70 || chip_id == 0x72 )
					{
						iss->target_chip_type = CHIP_CH570;
						iss->sectorsize = 4096;
						ch5xx = 1;
						chip_id = 0x500 | chip_id;
					}
					else if( chip_id == 0x71 || chip_id == 0x73 )
					{
						iss->target_chip_type = CHIP_CH57x;
						iss->no_autoexec = 1;
						iss->sectorsize = 256;
						ch5xx = 1;
						chip_id = 0x500 | chip_id;
					}
					else if( chip_id == 0x82 || chip_id == 0x83 )
					{
						iss->target_chip_type = CHIP_CH58x;
						iss->no_autoexec = 1;
						iss->sectorsize = 256;
						ch5xx = 1;
						chip_id = 0x500 | chip_id;
					}
				}
			}
		}
		else
		{
			uint32_t masked_id = sevenf_id & 0xfff00000;
			uint32_t masked_id2 = sevenf_id & 0xfff00f00;
			if( masked_id == 0x3b00000 )
			{
				iss->target_chip_type = CHIP_CH32M030;
				iss->sectorsize = 256;
				chip_id = (2 << 12) | 0x30;
			}
			else if( masked_id == 0x56400000 )
			{
				iss->target_chip_type = CHIP_CH564;
				iss->sectorsize = 256;
				chip_id = 0x564;
			}
			else if( masked_id == 0x31700000 )
			{
				iss->target_chip_type = CHIP_CH32V317;
				iss->sectorsize = 256;
				chip_id = (3 << 12) | 0x317;
			}
			else if( masked_id == 0x00200000 || masked_id == 0x00400000 || masked_id == 0x00600000 || masked_id == 0x00700000 )
			{
				iss->target_chip_type = CHIP_CH32V00x;
				iss->sectorsize = 256;
				chip_id = (3 << 12) | (masked_id >> 20);
			}

			if( masked_id2 == 0x64100500 )
			{
				iss->target_chip_type = CHIP_CH641;
				iss->sectorsize = 64;
				chip_id = 0x641;
			}
			else if( masked_id2 == 0x64300600 )
			{
				iss->target_chip_type = CHIP_CH643;
				iss->sectorsize = 256;
				chip_id = 0x643;
			}
			else if( masked_id == 0x64500000 )
			{
				iss->target_chip_type = CHIP_CH645;
				iss->sectorsize = 256;
				chip_id = 0x645;
			}
			else if( masked_id2 == 0x3500600 )
			{
				iss->target_chip_type = CHIP_CH32X03x;
				iss->sectorsize = 256;
				chip_id = (4 << 12) | 0x35;
			}
			else if( masked_id2 == 0x10300700 )
			{
				iss->target_chip_type = CHIP_CH32L10x;
				iss->sectorsize = 256;
				chip_id = (1 << 12) | 0x103;
			}
			else if( masked_id2 == 0x300500 )
			{
				iss->target_chip_type = CHIP_CH32V003;
				iss->sectorsize = 64;
				chip_id = (3 << 12) | 0x3;
			}

			if( (sevenf_id & 0x20000500) == 0x20000500 || (sevenf_id & 0x30000500) == 0x30000500 )
			{
				switch ((sevenf_id & 0xfff00000) >> 20)
				{
				case 0x203:
				case 0x208:
					iss->target_chip_type = CHIP_CH32V20x;
					iss->sectorsize = 256;
					break;
				case 0x303:
				case 0x305:
				case 0x307:
					iss->target_chip_type = CHIP_CH32V30x;
					iss->sectorsize = 256;
					break;
				}
				chip_id = (3 << 12) | (masked_id >> 20);
			}
		}

chip_identified:

		if( read_protection == 0 )
		{
			uint32_t one;
			int two;
			MCFWriteReg32( dev, BDMDATA0, 0x4002201c );
			MCFWriteReg32( dev, DMCOMMAND, 0x00271008 );		// Copy data to x8, and execute.
			WaitForDoneOp( dev );
			MCFWriteReg32( dev, DMCOMMAND, 0x00221008 );		// Copy data from x8.
			MCFReadReg32( dev, BDMDATA0, &one );
			MCFWriteReg32( dev, BDMDATA0, 0x40022020 );
			MCFWriteReg32( dev, DMCOMMAND, 0x00271008 );		// Copy data to x8, and execute.
			WaitForDoneOp( dev );
			MCFWriteReg32( dev, DMCOMMAND, 0x00221008 );		// Copy data from x8.
			MCFReadReg32( dev, BDMDATA0, (uint32_t*)&two );

			if( (one & 2) || two != -1 ) read_protection = 1;
		}

		if( reply != NULL )
		{
			reply[3] = sevenf_id >> 24;
			reply[2] = sevenf_id >> 16;
			reply[1] = sevenf_id >> 8;
			reply[0] = sevenf_id;
			reply[5] = chip_id >> 8;
			reply[4] = chip_id;
			if( read_protection == 1 ) reply[5] |= 0x80;
		}

		// Cleanup
		MCFWriteReg32( dev, BDMDATA0, old_x8 );
		MCFWriteReg32( dev, DMCOMMAND, 0x00231008 );		// Copy data to x8
		MCFWriteReg32( dev, BDMDATA0, old_data0 );
		iss->statetag = STTAG( "XXXX" );
#if CH5xx_SUPPORT
		if( ch5xx ) ch5xx_set_clock( dev, 0 );
#endif
	}
	return 0;
}

static int WaitForFlash( struct SWIOState * iss )
{
	struct SWIOState * dev = iss;

	if( DetermineChipTypeAndSectorInfo( iss, NULL ) ) return -9;

	uint32_t rw, timeout = 0;
	do
	{
		rw = 0;
		ReadWord( dev, 0x4002200C, &rw ); // FLASH_STATR => 0x4002200C
	} while( (rw & 1) && timeout++ < 2000);  // BSY flag.

	WriteWord( dev, 0x4002200C, 0 );

	if( rw & FLASH_STATR_WRPRTERR )
		return -44;

	if( rw & 1 )
		return -5;

	return 0;
}


static int WaitForDoneOp( struct SWIOState * iss )
{
	int r;
	uint32_t rrv;
	int ret = 0;
	int timeout = 1000;
	struct SWIOState * dev = iss;
	do
	{
		r = MCFReadReg32( dev, DMABSTRACTCS, &rrv );
		if( r ) return r;
		if( timeout-- == 0 ) return -8;
	}
	while( rrv & (1<<12) );
	if( (rrv >> 8 ) & 7 )
	{
		MCFWriteReg32( dev, DMABSTRACTCS, 0x00000700 );
		ret = -33;
	}
	return ret;
}

static int StaticUpdatePROGBUFRegs( struct SWIOState * dev )
{
	if( DetermineChipTypeAndSectorInfo( dev, NULL ) ) return -9;

	MCFWriteReg32( dev, DMABSTRACTAUTO, 0 ); // Disable Autoexec.
	uint32_t rr;
	MCFReadReg32( dev, DMHARTINFO, &rr );
	uint32_t data0offset = 0xe0000000 | ( rr & 0x7ff );
	MCFWriteReg32( dev, BDMDATA0, data0offset );   // DATA0's location in memory. (hard code to 0xe00000f4 if only working on the 003)
	MCFWriteReg32( dev, DMCOMMAND, 0x0023100a ); // Copy data to x10
	MCFWriteReg32( dev, BDMDATA0, data0offset + 4 );   // DATA1's location in memory. (hard code to 0xe00000f8 if only working on the 003)
	MCFWriteReg32( dev, DMCOMMAND, 0x0023100b ); // Copy data to x11
	MCFWriteReg32( dev, BDMDATA0, 0x4002200c ); //FLASH->STATR (note add 4 to FLASH->CTLR)
	MCFWriteReg32( dev, DMCOMMAND, 0x0023100c ); // Copy data to x12

	// This is not even needed on the v20x/v30x chips.  But it won't harm us to set the register for simplicity.
	// v003 requires bufload every word.
	// x035 requires bufload every word in spite of what the datasheet says.
	// CR_PAGE_PG = FTPG = 0x00010000 | CR_BUF_LOAD = 0x00040000
	MCFWriteReg32( dev, BDMDATA0, 0x00010000|0x00040000 );

	MCFWriteReg32( dev, DMCOMMAND, 0x0023100d ); // Copy data to x13
	return 0;
}

static void ResetInternalProgrammingState( struct SWIOState * iss )
{
	iss->statetag = 0;
	iss->lastwriteflags = 0;
	iss->currentstateval = 0;
	iss->flash_unlocked = 0;
	iss->autoincrement = 0;
	iss->clock_set = 0;
}

static int ReadByte( struct SWIOState * iss, uint32_t address_to_read, uint8_t * data )
{
	struct SWIOState * dev = iss;
	int ret = 0;
	iss->statetag = STTAG( "XXXX" );

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

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

	MCFWriteReg32( dev, BDMDATA0, address_to_read );
	MCFWriteReg32( dev, DMCOMMAND, 0x00231009 ); // Copy data to x9
	MCFWriteReg32( dev, DMCOMMAND, 0x00241000 ); // Only execute.
	MCFWriteReg32( dev, DMCOMMAND, 0x00221008 ); // Read x8 into DATA0.

	ret |= WaitForDoneOp( dev );
	iss->currentstateval = -1;

	uint32_t rr;
	ret |= MCFReadReg32( dev, BDMDATA0, &rr );
	*data = rr;
	return ret;
}

static int ReadWord( struct SWIOState * iss, uint32_t address_to_read, uint32_t * data )
{
	struct SWIOState * dev = iss;
	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" ) || autoincrement != iss->autoincrement )
		{
			if( iss->statetag != STTAG( "WRSQ" ) )
			{
				int r = StaticUpdatePROGBUFRegs( dev );
				if( r ) return r;
			}

			MCFWriteReg32( 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.
			MCFWriteReg32( dev, DMPROGBUF0, 0x40044180 );
			if( autoincrement )
			{
				// c.addi x8, 4
				// c.sw x9, 0(x10) // Write back to DATA0

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

				MCFWriteReg32( dev, DMPROGBUF1, 0xc1040001 );
			}
			// c.sw x8, 0(x11) // Write addy to DATA1
			// c.ebreak
			MCFWriteReg32( dev, DMPROGBUF2, 0x9002c180 );
			if( !iss->no_autoexec )
				MCFWriteReg32( dev, DMABSTRACTAUTO, 1 ); // Enable Autoexec (not autoincrement)
			iss->autoincrement = autoincrement;
		}

		MCFWriteReg32( dev, BDMDATA1, address_to_read );
		if( !iss->no_autoexec )
			MCFWriteReg32( dev, DMCOMMAND, 0x00240000 ); // Execute.

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

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

	if( iss->no_autoexec ) {
		MCFWriteReg32( dev, DMCOMMAND, 0x00240000 );
	}

	// Only an issue if we are curising along very fast.
	int r = WaitForDoneOp( dev );
	if( r ) return r;

	r = MCFReadReg32( dev, BDMDATA0, data );
	return r;
}

static int WriteByte( struct SWIOState * iss, uint32_t address_to_write, uint8_t data )
{
	struct SWIOState * dev = iss;
	int ret = 0;
	iss->statetag = STTAG( "XXXX" );

	MCFWriteReg32( 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.
	MCFWriteReg32( dev, DMPROGBUF0, 0x00848023 ); // sb x8, 0(x9)
	MCFWriteReg32( dev, DMPROGBUF1, 0x00100073 ); // c.ebreak

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

	ret |= WaitForDoneOp( dev );
	iss->currentstateval = -1;
	return ret;
}

static int WriteWord( struct SWIOState * iss, uint32_t address_to_write, uint32_t data )
{
	struct SWIOState * dev = iss;

	int ret = 0;

	int is_flash = 0;
	if( ( address_to_write & 0xff000000 ) == 0x08000000 || ( address_to_write & 0x1FFF0000 ) == 0x1FFF0000 )
	{
		// Is flash.
		is_flash = 1;
	}

	if( iss->statetag != STTAG( "WRSQ" ) || is_flash != iss->lastwriteflags )
	{
		int did_disable_req = 0;

		if( iss->statetag != STTAG( "WRSQ" ) )
		{
			MCFWriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
			did_disable_req = 1;

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

			// Different address, so we don't need to re-write all the program regs.
			// c.lw x8,0(x10) // Get the value to write.
			// c.lw x9,0(x11) // Get the address to write to.
			MCFWriteReg32( dev, DMPROGBUF0, 0x41844100 );
			// c.sw x8,0(x9)  // Write to the address.
			// c.addi x9, 4
			MCFWriteReg32( dev, DMPROGBUF1, 0x0491c080 );
		}

		if( is_flash && iss->target_chip_type == CHIP_CH32V10x)
		{
			// Special 16 bytes buffer write sequence for CH32V103
			MCFWriteReg32( dev, DMPROGBUF2, 0x0001c184 ); // c.sw x9,0(x11); c.nop;
			MCFWriteReg32( dev, DMPROGBUF3, 0x9002e391 ); // c.bnez x15, 4; c.ebreak;
			MCFWriteReg32( dev, DMPROGBUF4, 0x4200c254 ); // c.sw x13,4(x12); c.lw x8,0(x12);
			MCFWriteReg32( dev, DMPROGBUF5, 0xfc758805 ); // c.andi x8, 1; c.bnez x8, -4;
			MCFWriteReg32( dev, DMPROGBUF6, 0x90024781 ); // c.li x15, 0; c.ebreak;
		}
		else if( is_flash )
		{
			// A little weird - we need to wait until the buf load is done here to continue.
			// x12 = 0x4002200C (FLASH_STATR)
			//
			// c254 c.sw x13,4(x12) // Acknowledge the page write.  (BUT ONLY ON x035 / v003)
			//  otherwise c.nop
			// 4200 c.lw x8,0(x12)  // Start checking to see when buf load is done.
			// 8809 c.andi x8, 2    // Only look at WR_BSY (seems to be rather undocumented)
			//  8805 c.andi x8, 1    // Only look at BSY if we're not on a v30x / v20x
			// fc75 c.bnez x8, -4
			// c.ebreak
			MCFWriteReg32( dev, DMPROGBUF2, 0x0001c184 );
			MCFWriteReg32( dev, DMPROGBUF3,
				(iss->target_chip_type == CHIP_CH32V003 || iss->target_chip_type == CHIP_CH32V00x
				 || iss->target_chip_type == CHIP_CH32X03x || iss->target_chip_type == CHIP_CH32L10x
				 || iss->target_chip_type == CHIP_CH641 || iss->target_chip_type == CHIP_CH643) ?
				0x4200c254 : 0x42000001  );

			MCFWriteReg32( dev, DMPROGBUF4,
				(iss->target_chip_type == CHIP_CH32V20x || iss->target_chip_type == CHIP_CH32V30x ) ?
				0xfc758809 : 0xfc758805 );

			MCFWriteReg32( dev, DMPROGBUF5, 0x90029002 );
		}
		else
		{
			MCFWriteReg32( dev, DMPROGBUF2, 0x9002c184 ); // c.sw x9,0(x11); c.ebreak;
		}

		MCFWriteReg32( dev, BDMDATA1, address_to_write );
		MCFWriteReg32( dev, BDMDATA0, data );

		if( iss->no_autoexec )
		{
			MCFWriteReg32( dev, DMCOMMAND, 0x00240000 ); // Execute.
		}
		else if( did_disable_req )
		{
			MCFWriteReg32( dev, DMCOMMAND, 0x00240000 ); // Execute.
			MCFWriteReg32( dev, DMABSTRACTAUTO, 1 ); // Enable Autoexec.
		}

		iss->lastwriteflags = is_flash;

		iss->statetag = STTAG( "WRSQ" );
		iss->currentstateval = address_to_write;
	}
	else
	{
		if( address_to_write != iss->currentstateval )
		{
			MCFWriteReg32( dev, BDMDATA1, address_to_write );
		}
		MCFWriteReg32( dev, BDMDATA0, data );
		if( iss->no_autoexec )
		{
			MCFWriteReg32( dev, DMCOMMAND, 0x00240000 ); // Execute.
		}
	}
	if( is_flash )
		ret |= WaitForDoneOp( dev );

	iss->currentstateval += 4;

	return ret;
}

static int UnlockFlash( struct SWIOState * iss )
{
	struct SWIOState * dev = iss;

	if( DetermineChipTypeAndSectorInfo( iss, NULL ) ) return -9;

	uint32_t rw;
	ReadWord( dev, 0x40022010, &rw );  // FLASH->CTLR = 0x40022010
	if( rw & 0x8080 )
	{

		WriteWord( dev, 0x40022004, 0x45670123 ); // FLASH->KEYR = 0x40022004
		WriteWord( dev, 0x40022004, 0xCDEF89AB );
		WriteWord( dev, 0x40022008, 0x45670123 ); // OBKEYR = 0x40022008
		WriteWord( dev, 0x40022008, 0xCDEF89AB );
		WriteWord( dev, 0x40022024, 0x45670123 ); // MODEKEYR = 0x40022024
		WriteWord( dev, 0x40022024, 0xCDEF89AB );

		ReadWord( dev, 0x40022010, &rw ); // FLASH->CTLR = 0x40022010
		if( rw & 0x8080 )
		{
			return -9;
		}
	}
	iss->statetag = STTAG( "XXXX" );
	iss->flash_unlocked = 1;
	return 0;
}

static int EraseFlash( struct SWIOState * iss, uint32_t address, uint32_t length, int type )
{
	struct SWIOState * dev = iss;

	uint32_t rw;

	if( !iss->flash_unlocked )
	{
		if( ( rw = UnlockFlash( iss ) ) )
			return rw;
	}

	if( type == 1 )
	{
		// Whole-chip flash
		iss->statetag = STTAG( "XXXX" );
		BB_PRINTF_DEBUG( "Whole-chip erase\n" );
		WriteWord( dev, 0x40022010, 0 ); //  FLASH->CTLR = 0x40022010
		WriteWord( dev, 0x40022010, FLASH_CTLR_MER  );
		WriteWord( dev, 0x40022010, CR_STRT_Set|FLASH_CTLR_MER );
		if( WaitForFlash( dev ) ) return -13;
		WriteWord( dev, 0x40022010, 0 ); //  FLASH->CTLR = 0x40022010
	}
	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( WaitForFlash( dev ) ) return -14;

			// Step 4:  set PAGE_ER of FLASH_CTLR(0x40022010)
			WriteWord( dev, 0x40022010, CR_PAGE_ER ); // Actually FTER //  FLASH->CTLR = 0x40022010

			// Step 5: Write the first address of the fast erase page to the FLASH_ADDR register.
			WriteWord( dev, 0x40022014, chunk_to_erase  ); // FLASH->ADDR = 0x40022014

			// Step 6: Set the STAT bit of FLASH_CTLR register to '1' to initiate a fast page erase (64 bytes) action.
			WriteWord( dev, 0x40022010, CR_STRT_Set|CR_PAGE_ER );  // FLASH->CTLR = 0x40022010
			if( WaitForFlash( dev ) ) return -15;

			WriteWord( dev, 0x40022010, 0 ); //  FLASH->CTLR = 0x40022010 (Disable any pending ops)
			chunk_to_erase+=64;
		}
	}
	return 0;
}

static int WriteBlock( struct SWIOState * iss, uint32_t address_to_write, uint8_t * blob, uint8_t len, uint8_t erase )
{
	struct SWIOState * dev = iss;

	if( DetermineChipTypeAndSectorInfo( iss, NULL ) ) return -9;

	const int blob_size = len;
	uint32_t wp = address_to_write;
	uint32_t ew = wp + blob_size;
	int group = -1;
	int is_flash = 0;
	int rw = 0;

	if( (address_to_write & 0xff000000) == 0x08000000 || (address_to_write & 0xff000000) == 0x00000000  || (address_to_write & 0x1FFF0000) == 0x1FFF0000  )
	{
		// Need to unlock flash.
		// Flash reg base = 0x40022000,
		// FLASH_MODEKEYR => 0x40022024
		// FLASH_KEYR => 0x40022004

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

		is_flash = 1;

		if( erase )
		{
			// Only erase on first block in sector.
			int block_in_sector = (wp & ( iss->sectorsize - 1 )) / blob_size;
			int is_first_block = block_in_sector == 0;

			if( is_first_block )
			{
				rw = EraseFlash( dev, address_to_write, blob_size, 0 );
				if( rw ) return rw;
				// 16.4.6 Main memory fast programming, Step 5
				//if( WaitForFlash( dev ) ) return -11;
				//WriteWord( dev, 0x40022010, FLASH_CTLR_BUF_RST );
				//if( WaitForFlash( dev ) ) return -11;
			}
		}
	}

	/* General Note:
		Most flash operations take about 3ms to complete :(
	*/

	while( wp < ew )
	{
		if( is_flash )
		{
			group = (wp & 0xffffffc0);

			int block_in_sector = (group & ( iss->sectorsize - 1 )) / blob_size;
			int is_first_block = block_in_sector == 0;
			int is_last_block = block_in_sector == (iss->sectorsize / blob_size - 1 );

			if( is_first_block )
			{
				if( dev->target_chip_type == CHIP_CH32V20x || dev->target_chip_type == CHIP_CH32V30x )
				{
					// No bufrst on v20x, v30x
					WaitForFlash( dev );
					WriteWord( dev, 0x40022010, CR_PAGE_PG ); // THIS IS REQUIRED, (intptr_t)&FLASH->CTLR = 0x40022010
					//FTPG ==  CR_PAGE_PG   == ((uint32_t)0x00010000)
				}
				else
				{
					// V003, x035, maybe more.
					WriteWord( dev, 0x40022010, CR_PAGE_PG ); // THIS IS REQUIRED, (intptr_t)&FLASH->CTLR = 0x40022010
					WriteWord( dev, 0x40022010, CR_BUF_RST | CR_PAGE_PG );  // (intptr_t)&FLASH->CTLR = 0x40022010
				}
				WaitForFlash( dev );
			}

			int j;
			for( j = 0; j < blob_size/4; j++ )
			{
				int index = (wp-address_to_write);
				uint32_t data = 0xffffffff;
				if( index + 3 < blob_size )
				{
					memcpy( &data, &blob[index], 4 );
				}
				else if( (int32_t)(blob_size - index) > 0 )
				{
					memcpy( &data, &blob[index], blob_size - index );
				}
				if( iss->target_chip_type == CHIP_CH32V10x && !((j+1)&3) )
				{
					// Signal to WriteWord that we need to do a buffer load
					MCFWriteReg32( dev, BDMDATA0, 1 );
					MCFWriteReg32( dev, DMCOMMAND, 0x0023100f );
				}
				WriteWord( dev, wp, data );
				wp += 4;
			}


			if( ( iss->target_chip_type == CHIP_CH32V20x || iss->target_chip_type == CHIP_CH32V30x ) )
			{
				if( is_last_block )
				{
					WriteWord( dev, 0x40022010, CR_PAGE_PG | (1<<21) ); // Page Start
					if( WaitForFlash( dev ) ) return -13;
				}
			}
			else
			{
				// Datasheet says the x03x needs to have this called every group-of-16, but that's not true, it should be every 16-words.
				WriteWord( dev, 0x40022014, group );  //0x40022014 -> FLASH->ADDR
				WriteWord( dev, 0x40022010, CR_PAGE_PG|CR_STRT_Set ); // 0x40022010 -> FLASH->CTLR
				if( WaitForFlash( dev ) ) return -16;
			}
		}
		else
		{
			int index = (wp-address_to_write);
			uint32_t data = 0xffffffff;
			if( index + 3 < blob_size )
			{
				memcpy( &data, &blob[index], 4 );
			}
			else if( (int32_t)(blob_size - index) > 0 )
			{
				memcpy( &data, &blob[index], blob_size - index );
			}
			WriteWord( dev, wp, data );
			wp += 4;
		}
	}

	return 0;
}

// Polls up to 7 bytes of printf, and can leave a 7-bit flag for the CH32V003.
static int PollTerminal( struct SWIOState * iss, uint8_t * buffer, int maxlen, uint32_t leavevalA, uint32_t leavevalB )
{
	struct SWIOState * dev = iss;

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

	if( maxlen < 8 ) return -9;

	// BDMDATA1:
	//  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 = MCFReadReg32( dev, BDMDATA1, &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( leavevalA )
		{
			MCFWriteReg32( dev, BDMDATA1, leavevalB );
		}
		MCFWriteReg32( dev, BDMDATA0, leavevalA ); // Write that we acknowledge the data.
		return ret;
	}
	else
	{
		return 0;
	}
}

#endif // _CH32V003_SWIO_H
