// ###########################################################################
// #
// # OSCAPIC - Implementation of OSCA PIC/EEPROM Module
// # Copyright (C) 2009-2011 Tony Friery [DialTone]
// #
// # ALL RIGHTS RESERVED
// #
// ###########################################################################

#pragma hdrstop

#include "OSCAPIC.h"
#include "FormDiag.h"

//---------------------------------------------------------------------------

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPIC::ByteReceived
//
//  PURPOSE:	This function gets called after the PIC device has
//					had 8 bits clocked to it from the FPGA
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAPIC::ByteReceived(void)
{
	switch(this->fCurrentMode)
	{
		// PIC is currently idle - a command preamble (0x88) is expected
		case PIC_IDLE:
			if (this->fCurrentByte == 0x88)
			{
				this->fCurrentMode  = PIC_CMDWAIT;

				if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
				{
					DiagForm->AddMessage(DIAG_EEPROM, L"Received Valid 0x88 Preamble");
				}
			}
			else
			{
				// TODO: Handle PIC Error Here Correctly!
				if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
				{
					DiagForm->AddMessage(DIAG_EEPROM, UnicodeString::Format(
						L"Received Invalid Preamble: 0x%02x",
						OPENARRAY(TVarRec, (this->fCurrentByte))
					));
				}
			}
			break;
		// PIC has already received a command preamble, it's expecting
		// an actual command byte now
		case PIC_CMDWAIT:
			// Store the received command byte
			this->fCurrentCmd = this->fCurrentByte;

			// Switch operating mode depending on the command byte received
			switch (this->fCurrentByte)
			{
				case 0xa1:	// Immediate Reconfigure
					this->fCurrentMode = PIC_IDLE;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
					{
						DiagForm->AddMessage(DIAG_EEPROM, L"Command: RECONFIGURE NOW!");
					}
					break;
				case 0xc9:	// Begin Databurst
					this->fCurrentMode = PIC_EXECUTE;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
					{
						DiagForm->AddMessage(DIAG_EEPROM, L"Command: BEGIN DATABURST!");
						DiagForm->AddMessage(DIAG_EEPROM, UnicodeString::Format(
							L"Databurst from: 0x%06x size(dec): %d bytes",
							OPENARRAY(TVarRec, (this->fBurstAddress, this->fBurstLength))
						));
					}

					this->fCurrentAddr = this->fBurstAddress;
					this->fBurstCount = this->fBurstLength;
					break;
				case 0x25:
				case 0x37:
					this->fCurrentMode = PIC_WAITX1;
					break;
				case 0xb8:
				case 0xd4:
				case 0xe2:
				case 0xf5:
				case 0x98:
					this->fCurrentAddr = 0x000000;
					this->fCurrentMode = PIC_WAITL;
					break;
				default:
					if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
					{
						DiagForm->AddMessage(DIAG_EEPROM, UnicodeString::Format(
							L"Received Invalid Command: 0x%02x",
							OPENARRAY(TVarRec, (this->fCurrentByte))
						));
					}

					this->fCurrentMode = PIC_IDLE;
					break;
			}
			break;
		case PIC_WAITX1:
			this->fCurrentMode = PIC_WAITX2;
			break;
		case PIC_WAITX2:
			this->fCurrentMode  = PIC_IDLE;

			if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
			{
				DiagForm->AddMessage(DIAG_EEPROM, L"Command: (4 byte command tbd)");
			}
			break;
		case PIC_WAITL:
			// Accept Low Byte and begin wait for Middle Byte
			this->fCurrentAddr |= this->fCurrentByte;
			this->fCurrentMode = PIC_WAITM;
			break;
		case PIC_WAITM:
			// Accept Middle Byte and begin wait for High Byte
			this->fCurrentAddr |= (this->fCurrentByte << 8);
			this->fCurrentMode = PIC_WAITH;
			break;
		case PIC_WAITH:
			// Accept High Byte and switch to "perform command" mode
			this->fCurrentAddr |= (this->fCurrentByte << 16);
			switch (this->fCurrentCmd)
			{
				case 0xf5:	// Erase 64k Block
					// TODO: Implement 64k Block Erase (PIC)
					this->fCurrentMode = PIC_IDLE;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
					{
						DiagForm->AddMessage(DIAG_EEPROM, L"Command: ERASE 64k BLOCK!");
					}
					break;
				case 0x98:	// Program 64 bytes
					// TODO: Implement PGM 64 Bytes (PIC)
					this->fCurrentMode = PIC_IDLE;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
					{
						DiagForm->AddMessage(DIAG_EEPROM, L"Command: ACCEPT PGM DATA!");
					}

					this->fProgAddress = this->fCurrentAddr;
					break;
				case 0xb8:	// set config address
					// Has no usefulness inside the emulator, but silently accept
					this->fCurrentMode = PIC_IDLE;
					this->fConfigBaseAddress = this->fCurrentAddr;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
					{
						DiagForm->AddMessage(DIAG_EEPROM, UnicodeString::Format(
							L"Command: SET CONFIG ADDR to 0x%06x",
							OPENARRAY(TVarRec, (this->fConfigBaseAddress))
						));
					}
					break;
				case 0xd4:	// burst address
					this->fCurrentMode = PIC_IDLE;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
					{
						DiagForm->AddMessage(DIAG_EEPROM, L"Command: SET BURST ADDR!");
					}

					this->fBurstAddress = fCurrentAddr;
					break;
				case 0xe2:	// burst length
					this->fCurrentMode = PIC_IDLE;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
					{
						DiagForm->AddMessage(DIAG_EEPROM, L"Command: SET BURST LEN!");
					}

					this->fBurstLength = this->fCurrentAddr;
					break;
				default:
					this->fCurrentMode = PIC_IDLE;
					break;
			}
			break;
		default:
			// Valid data now present
			if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
			{
				DiagForm->AddMessage(DIAG_EEPROM, UnicodeString::Format(
					L"Byte Received: 0x%02x",
					OPENARRAY(TVarRec, (this->fCurrentByte))
				));
			}
			break;
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPIC::LoadEEPROM
//
//  PURPOSE:	Load data from a disk file into the EEPROM memory
//
//  INPUTS:		UnicodeString		Filename of disk file to be loaded
//					unsigned int		Starting Address within the EEPROM
//											at which to load the disk file
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAPIC::LoadEEPROM(UnicodeString Name, unsigned int BaseAddr)
{
	TFileStream *RFS;

	// Load Boot ROM image
	RFS = new TFileStream(Name, fmOpenRead);
	RFS->Read(&this->EEPROM[BaseAddr], RFS->Size);
	RFS->Free();
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPIC::Reset
//
//  PURPOSE:	Perform a Power-On-Reset of the PIC/EEPROM Devices
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAPIC::Reset(void)
{
	this->fClock 			= false;
	this->fData 			= false;
	this->fBitCount		= 0;
	this->fCurrentByte	= 0x00;
	this->fCurrentCmd 	= 0x00;
	this->fCurrentMode	= PIC_IDLE;
	this->fBurstReady		= false;

	this->fConfigBaseAddress 	= 0x000000;
	this->fBurstAddress 			= 0x000000;
	this->fBurstLength 			= 0x000000;
	this->fProgAddress 			= 0x000000;
	this->fCurrentAddr			= 0x000000;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPIC::IsDataBurstReady
//
//  PURPOSE:	Signifies to the FPGA/CPU whether the next data byte
//					is available as part of a data burst operation
//
//  INPUTS:		void
//
//  OUTPUT:		bool					true if burst byte is ready for reading
//
///////////////////////////////////////////////////////////////////////
bool __fastcall TOSCAPIC::IsDataBurstReady(void)
{
	return this->fBurstReady;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPIC::GetDataBurstByte
//
//  PURPOSE:	Retrieve the next Data Burst byte made available
//					as part of a burst operation (byte was presented
//					because the clock was raised)
//
//  INPUTS:		void
//
//  OUTPUT:		byte					Next burst byte for the FPGA/CPU
//
///////////////////////////////////////////////////////////////////////
byte __fastcall TOSCAPIC::GetDataBurstByte(void)
{
	this->fBurstReady = false;

	return this->fBurstData;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPIC::Write
//
//  PURPOSE:	Handle a write from the FPGA
//
//  INPUTS:		bool					Value of the Clock line
//					bool					Value of the Data Line
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAPIC::Write(bool Clock, bool Data)
{
	char buf[100];

	// If the PIC is currently executing a command
	if (this->fCurrentMode == PIC_EXECUTE)
	{
		switch (this->fCurrentCmd)
		{
			case 0xc9:	// Databurst
				if ((Clock) && (!this->fClock))
				{
					// Handle a rising clock
					this->fBurstReady = true;
					this->fBurstData = this->EEPROM[this->fCurrentAddr & 0x7ffff];
					this->fBurstCount--;
					this->fCurrentAddr++;

					if (!fBurstCount)
					{
						this->fCurrentMode = PIC_IDLE;

						if ((DiagForm) && (DiagForm->IsActive(DIAG_EEPROM)))
						{
							DiagForm->AddMessage(DIAG_EEPROM, L"Databurst Complete");
						}
					}
				}
				break;
			default:
				break;
		}

		this->fClock = Clock;
		this->fData = Data;
	}
	else
	{
		// PIC is not currently handling a command, so clock in the new
		// command (or command parameter as appropriate)

		// Handle on rising clock
		if ((Clock) && (!this->fClock))
		{
			// Update current byte being clocked in
			this->fData = Data;
			this->fCurrentByte <<= 1;
			this->fBitCount++;
			this->fCurrentByte |= (this->fData) ? 0x01 : 0x00;

			// Once all 8 bits are received, handle the byte
			if (this->fBitCount == 8)
			{
				this->ByteReceived();
				this->fBitCount 		= 0;
				this->fCurrentByte 	= 0x00;
			}
		}

		// Remember this clock state for comparison with
		// the next write/clock state change
		this->fClock = Clock;
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPIC::~TOSCAPIC [CONSTRUCTOR]
//
//  PURPOSE:	Contructor for the PIC/EEPROM Handler Class
//
//  INPUTS:		none
//
//  OUTPUT:		none
//
///////////////////////////////////////////////////////////////////////
__fastcall TOSCAPIC::TOSCAPIC()
{
	// Perform initial Power-On-Reset
	this->Reset();
}

//---------------------------------------------------------------------------

#pragma package(smart_init)

