// ###########################################################################
// #
// # OSCAPS2Devices - Implementation of OSCA PS2 Device Emulation (Mouse/KB)
// # Copyright (C) 2009-2011 Tony Friery [DialTone]
// #
// # ALL RIGHTS RESERVED
// #
// ###########################################################################

#pragma hdrstop

#include "OSCAPS2Devices.h"
#include "FormDiag.h"

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

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAPS2Device::WriteToDevice
//
//  PURPOSE:	Handle incoming Clock/Data line writes from the CPU
//
//  INPUTS:		bool					Clock line value to be asserted
//					bool					Data line value to be asserted
//
//  OUTPUT:		void
//
//	 NOTES:		The Clock and Data lines for a PS2 device are
//					logically inverted (open-collector with pullup). The
//					states are stored in their inverted state inside the
//					class to make further processing simpler
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAPS2Device::WriteToDevice(bool Clock, bool Data)
{
	// Invert values to compensate for logical inversion
	bool InvClock = !Clock;
	bool InvData = !Data;

//	if ((DiagForm) && (DiagForm->IsActive(DIAG_KBMOUSE)))
//	{
//		DiagForm->AddMessage(DIAG_KBMOUSE, UnicodeString::Format(
//			L"Write: %03s %03s",
//			OPENARRAY(TVarRec, ((InvClock ? L"CLK" : L"---"), (InvData ? L"DTA" : L"---")))
//		));
//	}

	// Check for clock state change
	if ((this->fClockIn) && (!InvClock))
	{
		// Clock Lowered - Communication Inhibited
		if ((DiagForm) && (DiagForm->IsActive(DIAG_KBMOUSE)))
		{
			DiagForm->AddMessage(DIAG_KBMOUSE, L"Comms Inhibited by host");
		}
	}
	else if ((!this->fClockIn) && (InvClock))
	{
		// Clock Raised
		if (!InvData)
		{
			// Host RTS
			if ((DiagForm) && (DiagForm->IsActive(DIAG_KBMOUSE)))
			{
				DiagForm->AddMessage(DIAG_KBMOUSE, L"Host is RTS");
			}
			this->fCurrentState = HOST_RTS;
			this->fClockPrescaler = 0;
			this->fRecvData = 0;
			this->fBitCount = 0;
			this->fRecvParity = true;
			this->fParityCount = 0;
		}
	}

	// Store new states
	this->fClockIn = InvClock;
	this->fDataIn = InvData;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAPS2Device::Tick
//
//  PURPOSE:	Method called periodically to enable synching the
//					device clock against the CPU clock
//
//  INPUTS:		unsigned int		Number of cycles elapsed since last
//											time this method was called
//
//  OUTPUT:		bool					IRQ Status following tick being
//											processed
//
///////////////////////////////////////////////////////////////////////
bool __fastcall TOSCAPS2Device::Tick(unsigned int cycles)
{
	switch (this->fCurrentState)
	{
		// Host is Ready to Send (or sending!)
		case HOST_RTS:
			this->fClockPrescaler += cycles;

			// Process as many sub-states as possible
			while (this->fClockPrescaler > 1150)
			{
				this->fClockPrescaler -= 1150;

				// Wiggle the clock line
				this->fClockOut = !this->fClockOut;

//				if ((DiagForm) && (DiagForm->IsActive(DIAG_KBMOUSE)))
//				{
//					DiagForm->AddMessage(DIAG_KBMOUSE, UnicodeString::Format(
//						L"Output Clock: %03s for bit %d",
//						OPENARRAY(TVarRec, ((fClockOut ? L"CLK" : L"---"), fBitCount))
//					));
//				}

				// If the clock line is high (on rising edge)

				if (this->fClockOut)

				{

					// Advance to next bit

					this->fBitCount++;

					if (this->fBitCount < 9)
					{
						// First 8 bits contain the data packet, so
						// shift the received byte and combine the new
						// data bit received
						this->fRecvData <<= 1;
						this->fRecvData |= (this->fClockIn ? 0x01 : 0x00);

						// Work out the parity bit as we're going
						if (this->fClockIn)
						{
							this->fParityCount++;
						}
					}
					else if (this->fBitCount == 9)
					{
						// 9th bit is the Parity Bit
						this->fRecvParity = this->fClockIn;

						if ((DiagForm) && (DiagForm->IsActive(DIAG_KBMOUSE)))
						{
							DiagForm->AddMessage(DIAG_KBMOUSE, UnicodeString::Format(
								L"Received Data: %02x with parity %03s",
								OPENARRAY(TVarRec, (this->fRecvData,
								(this->fRecvParity ? L"SET" : L"CLR")))
							));
						}
						this->fDataOut = false;
					}
					else if (this->fBitCount == 12)
					{
						// All done
						this->fCurrentState = DEVICE_IDLE;
						this->fDataOut = true;
						this->fClockOut = true;
						this->GotByte(this->fRecvData);
					}
				}
			}
			break;
	}

	return GetIRQState();
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPS2Device::PushByte
//
//  PURPOSE:	Callback from the emulation UI to indicate that a
//					key was pressed, or mouse moved, so data needs to be
//					stuffed into the buffer for later retrieval
//
//  INPUTS:		void
//
//  OUTPUT:		bool					Current state of the Clock line
//											(as output to the host cpu)
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAPS2Device::PushByte(byte Value)
{
	// This means scancode value(s) or mouse movement data
	//	need to be queued for sending to the host later
	if (this->fWritePointer == (this->fReadPointer - 1))
	{
		// Buffer is full - this would cause a clash
	}
	else
	{
		// Add new data to the internal buffer
		this->fDeviceBuffer[this->fWritePointer++] = Value;

		// Handle write pointer wrapping
		if (this->fWritePointer > 31)
		{
			this->fWritePointer = 0;
		}
	}

	// Flag that an IRQ has been triggered
	this->fIRQState = true;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPS2Device::GetDataByte
//
//  PURPOSE:	Retrieve the value of the next data byte in the
//					buffer (from device to host).
//
//  INPUTS:		void
//
//  OUTPUT:		byte					Next byte from the buffer
//											(or 0x00 if there's no byte available)
//
///////////////////////////////////////////////////////////////////////
byte __fastcall TOSCAPS2Device::GetDataByte(void)
{
	byte	rv = 0x00;

	// Only do something if there's data in the internal buffer
	if (this->fReadPointer != this->fWritePointer)
	{
		// Obtain next byte from the internal buffer
		rv = this->fDeviceBuffer[this->fReadPointer++];

		// Handle read pointer wrap-around
		if (this->fReadPointer > 31)
		{
			this->fReadPointer = 0;
		}

		// Assert IRQ line
		this->fIRQState = true;
	}

	return rv;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPS2Device::GetClock
//
//  PURPOSE:	Retrieve the value of the Clock line as it is returned
//					to the host CPU
//
//  INPUTS:		void
//
//  OUTPUT:		bool					Current state of the Clock line
//											(as output to the host cpu)
//
///////////////////////////////////////////////////////////////////////
bool __fastcall TOSCAPS2Device::GetClock(void)
{
	return this->fClockOut;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPS2Device::GetData
//
//  PURPOSE:	Retrieve the value of the Data line as it is returned
//					to the host CPU
//
//  INPUTS:		void
//
//  OUTPUT:		bool					Current state of the Data line
//											(as output to the host cpu)
//
///////////////////////////////////////////////////////////////////////
bool __fastcall TOSCAPS2Device::GetData(void)
{
	return this->fDataOut;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPS2Device::GetIRQState
//
//  PURPOSE:	Clear down the internal IRQ flag. The next time that the
//					flag is queried, however, it may end up being set high
//					again if there is data pending
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
bool __fastcall TOSCAPS2Device::GetIRQState(void)
{
	// If there is data pending, IRQ is asserted, else it's clear
	return (!(this->fReadPointer == this->fWritePointer));
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPS2Device::ClearIRQ
//
//  PURPOSE:	Clear down the internal IRQ flag. The next time that the
//					flag is queried, however, it may end up being set high
//					again if there is data pending
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAPS2Device::ClearIRQ(void)
{
	if (this->fIRQState)
	{
		if ((DiagForm) && (DiagForm->IsActive(DIAG_KBMOUSE)))
		{
			DiagForm->AddMessage(DIAG_KBMOUSE, L"IRQ Flag Cleared");
		}

		this->fIRQState = false;
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPS2Device::Reset
//
//  PURPOSE:	Perform a Power-On Reset of the PS2 Device Subsystem
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAPS2Device::Reset(void)
{
	if ((DiagForm) && (DiagForm->IsActive(DIAG_KBMOUSE)))
	{
		DiagForm->AddMessage(DIAG_KBMOUSE, L"PS2 Reset");
	}

	// Clear internal buffer
	memset(&this->fDeviceBuffer[0], 0, 32);

	// Reset flags/variables
	this->fReadPointer	= 0;
	this->fWritePointer	= 0;
	this->fIRQState 		= false;
	this->fClockIn 		= true;
	this->fDataIn 			= true;
	this->fClockOut		= false;		// Default is Clk/Data Low
	this->fDataOut			= false;		// (Device is Ready)

	// Begin in IDLE mode initially
	this->fCurrentState  = DEVICE_IDLE;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAPS2Device::TOSCAPS2Device [CONSTRUCTOR]
//
//  PURPOSE:	Constructor for the base PS2 Handler Class
//
//  INPUTS:		none
//
//  OUTPUT:		none
//
///////////////////////////////////////////////////////////////////////
__fastcall TOSCAPS2Device::TOSCAPS2Device()
{
	// Perform initial Power-On Reset
	this->Reset();
}

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

#pragma package(smart_init)

