#include "snes.h"

//only need this file if connector is present on the device
#ifdef SNES_CONN 

//=================================================================================================
//
//	SNES operations
//	This file includes all the snes functions possible to be called from the snes dictionary.
//
//	See description of the commands contained here in shared/shared_dictionaries.h
//
//=================================================================================================

/* Desc:Function takes an opcode which was transmitted via USB
 * 	then decodes it to call designated function.
 * 	shared_dict_snes.h is used in both host and fw to ensure opcodes/names align
 * Pre: Macros must be defined in firmware pinport.h
 * 	opcode must be defined in shared_dict_snes.h
 * Post:function call complete.
 * Rtn: SUCCESS if opcode found and completed, error if opcode not present or other problem.
 */
uint8_t snes_call( uint8_t opcode, uint8_t miscdata, uint16_t operand, uint8_t *rdata )
{

#define	RD_LEN	0
#define	RD0	1
#define	RD1	2

#define	BYTE_LEN 1
#define	HWORD_LEN 2
	
	switch (opcode) { 
		//no return value:
		case SNES_SET_BANK:	
			HADDR_SET( operand );
			break;
		case SNES_ROM_WR:	
			snes_wr( operand, miscdata, 0 );	//last arg is romsel state
			break;
		case SNES_SYS_WR:	
			snes_wr( operand, miscdata, 1 );	//last arg is romsel state
			break;
		case FLASH_WR_5V:	
			snes_5v_flash_wr( operand, miscdata );
			break;
		case FLASH_WR_3V:	
			snes_3v_flash_wr( operand, miscdata );
			break;

		//8bit return values:
		case SNES_ROM_RD:
			rdata[RD_LEN] = BYTE_LEN;
			rdata[RD0] = snes_rd( operand, 0 );	//last arg is romsel state
			break;
		case SNES_SYS_RD:
			rdata[RD_LEN] = BYTE_LEN;
			rdata[RD0] = snes_rd( operand, 1 );	//last arg is romsel state
			break;
		default:
			 //macro doesn't exist
			 return ERR_UNKN_SNES_OPCODE;
	}
	
	return SUCCESS;

}

/* Desc:SNES ROM Read without changing high bank
 * 	/ROMSEL set based on romsel arg
 * 	EXP0/RESET not affected
 * 	NOTE: this will access addresses when /ROMSEL isn't low on the console
 * Pre: snes_init() setup of io pins
 * Post:address left on bus
 * 	data bus left clear
 * Rtn:	Byte read from ROM at addr
 */
uint8_t	snes_rd( uint16_t addr, uint8_t romsel )
{
	uint8_t	read;	//return value

	//set address bus
	ADDR_SET(addr);
	
	if (romsel==0)
		ROMSEL_LO();

	CSRD_LO();

	//couple more NOP's waiting for data
	//zero nop's returned previous databus value
	NOP();	//one nop got most of the bits right
	NOP();	//two nop got all the bits right
	NOP();	//add third nop for some extra
	NOP();	//one more can't hurt
	//might need to wait longer for some carts...
	//this was long enough for AVR
	
	//SNES v2.0p needed 6 more NOPs compared to v3.x & v1.x
	//seems like a crazy long time...
	NOP();	//v2.0p gets prod & density ID correct with addition of this NOP
	//not sure why manf ID and sector ID are so much slower on v2 board
	NOP();
	NOP();	//v2.0p gets most bits right after 3 NOPs
	NOP();
	NOP();	//more after 5 extra...
	NOP();	//all after 6 extra..
	//sounds like 1 AVR NOP needs to equal 2STM32
	//AVR running at 16Mhz, STM32 running at 48Mhz (3x as fast)
	NOP();	//4MB proto needed this to get manfID, sector still bad
	NOP();	//all good on 4MB proto
	NOP();	//swapped for OR gate and takes a little longer now..?

	//latch data
	DATA_RD(read);

	//return bus to default
	CSRD_HI();
	ROMSEL_HI();
	
	return read;
}

/* Desc:SNES ROM Write
 * 	/ROMSEL set based on romsel arg
 * 	EXP0/RESET unaffected
 * 	write value to currently selected bank
 * 	NOTE: this will access addresses when /ROMSEL isn't low on the console
 * Pre: snes_init() setup of io pins
 * Post:data latched by anything listening on the bus
 * 	address left on bus
 * Rtn:	None
 */
void	snes_wr( uint16_t addr, uint8_t data, uint8_t romsel )
{

	ADDR_SET(addr);

	//put data on bus
	DATA_OP();
	DATA_SET(data);

	//set /WR low first as this sets direction of 
	//level shifter on v3.0 boards
	CSWR_LO();
	//Then set romsel as this enables output of level shifter
	if (romsel==0)
		ROMSEL_LO();
	//Doing the other order creates bus conflict between ROMSEL low -> WR low

	//give some time
	NOP();
	NOP();
	NOP();		//3x total NOPs fails ~2Bytes per 2MByte on v3.0 proto and inl6
	//swaping /WR /ROMSEL order above helped greatly
	//but still had 2 byte fails adding NOPS
	NOP();		//4x total NOPs passed all bytes v3.0 SNES and inl6
	NOP();
	NOP();	//6x total NOPs passed all bytes
	NOP();
	NOP();
	

	//latch data to cart memory/mapper
	CSWR_HI();
	ROMSEL_HI();

	//Free data bus
	DATA_IP();
}

/* Desc:SNES ROM Write to current address
 * 	/ROMSEL set based on romsel arg
 * 	EXP0/RESET unaffected
 * 	write value to currently selected bank, and current address
 * 	Mostly used when address is don't care
 * Pre: snes_init() setup of io pins
 * Post:data latched by anything listening on the bus
 * 	address left on bus
 * Rtn:	None
 */
void	snes_wr_cur_addr( uint8_t data, uint8_t romsel)
{

//	ADDR_SET(addr);

	//put data on bus
	DATA_OP();
	DATA_SET(data);

	//set /WR low first as this sets direction of 
	//level shifter on v3.0 boards
	CSWR_LO();
	//Then set romsel as this enables output of level shifter
	if (romsel==0)
		ROMSEL_LO();
	//Doing the other order creates bus conflict between ROMSEL low -> WR low

	//give some time
	NOP();
	NOP();
	NOP();		//3x total NOPs fails ~2Bytes per 2MByte on v3.0 proto and inl6
	//swaping /WR /ROMSEL order above helped greatly
	//but still had 2 byte fails adding NOPS
	NOP();		//4x total NOPs passed all bytes v3.0 SNES and inl6
	//NOP();
	//NOP();	//6x total NOPs passed all bytes

	//latch data to cart memory/mapper
	CSWR_HI();
	ROMSEL_HI();

	//Free data bus
	DATA_IP();
}


/* Desc:SNES ROM Page Read with optional USB polling
 * 	/ROMSEL based on romsel arg, EXP0/RESET unaffected
 *	if poll is true calls usbdrv.h usbPoll fuction
 *	this is needed to keep from timing out when double buffering usb data
 * Pre: snes_init() setup of io pins
 *	num_bytes can't exceed 256B page boundary
 * Post:address left on bus
 * 	data bus left clear
 *	data buffer filled starting at first to last
 * Rtn:	Index of last byte read
 */
uint8_t snes_page_rd_poll( uint8_t *data, uint8_t addrH, uint8_t romsel, uint8_t first, uint8_t len, uint8_t poll )
{
	uint8_t i;

	//set address bus
	ADDRH(addrH);
	
	//set /ROMSEL and /RD
	CSRD_LO();

	if (romsel==0) {
		ROMSEL_LO();
	}

	//set lower address bits
	ADDRL(first);		//doing this prior to entry and right after latching
				//gives longest delay between address out and latching data
	for( i=0; i<=len; i++ ) {
		//testing shows that having this if statement doesn't affect overall dumping speed
		if ( poll == FALSE ) {
			NOP();	//couple more NOP's waiting for data
			NOP();	//one prob good enough considering the if/else
			NOP();
			NOP();
		} else {
			usbPoll();	//Call usbdrv.h usb polling while waiting for data
			NOP();
			NOP();
			NOP();
		}

		//gameboy needed some extra NOPS
		NOP();
		NOP();
		NOP();
		NOP();
		NOP();
		NOP();
		//latch data
		DATA_RD(data[i]);

		//set lower address bits
		//ADDRL(++first);	THIS broke things, on stm adapter because macro expands it twice!
		first++;
		ADDRL(first);
	}

	//return bus to default
	CSRD_HI();
	ROMSEL_HI();
	
	//return index of last byte read
	return i;
}


/* Desc:SNES 5v ROM FLASH Write
 * 	NOTE: /ROMSEL is always taken low
 * 	NOTE: if the byte isn't erased it will stop over current value
 * 	NOTE: doesn't hang if write fails, just returns, goal is to be fast
 * Pre: snes_init() setup of io pins
 * 	desired bank must already be selected
 * Post:Byte written and ready for another write
 * Rtn:	None
 */
void snes_5v_flash_wr( uint16_t addr, uint8_t data )
{

	uint8_t rv;

	//unlock and write data
	snes_wr(0x5555, 0xAA, 0);
	snes_wr(0x2AAA, 0x55, 0);
	snes_wr(0x5555, 0xA0, 0);
	snes_wr(addr, data, 0);

	do {
		rv = snes_rd(addr, 0);
		usbPoll();	//orignal kazzo needs this frequently to slurp up incoming data
	} while (rv != snes_rd(addr, 0));

	return;
}

/* Desc:SNES 3v ROM FLASH Write
 * 	NOTE: /ROMSEL is always taken low
 * 	NOTE: if the byte isn't erased it will stop over current value
 * 	NOTE: doesn't hang if write fails, just returns, goal is to be fast
 * Pre: snes_init() setup of io pins
 * 	desired bank must already be selected
 * Post:Byte written and ready for another write
 * Rtn:	None
 */
void snes_3v_flash_wr( uint16_t addr, uint8_t data )
{

	uint8_t rv;

	//unlock and write data
	snes_wr(0x8AAA, 0xAA, 0);
	snes_wr(0x8555, 0x55, 0);
	snes_wr(0x8AAA, 0xA0, 0);
	snes_wr(addr, data, 0);

	do {
		rv = snes_rd(addr, 0);
		usbPoll();	//orignal kazzo needs this frequently to slurp up incoming data
	} while (rv != snes_rd(addr, 0));

	return;
}


#endif //SNES_CONN
