/*
 * (Generic) 6522 VIA emulation
 */

#include <stdio.h>
#include "VIA6522.h"

void VIA6522::Reset (void)
{
	// PinA and PinB start random
	IRA = IRB = 0;
	ORB = ORA = 0;
	DDRB = DDRA = 0;
	ACR = 0;
	PCR = 0;
	IER = 0;
	IFR = 0;
}

byte VIA6522::MMIO_Read (int addr)
{
	/* Info on the VIA:
	Reding from the port register (A or B) returns either the
	current outputs or the current inputs in each bit depending on
	the value of the data direction register for that bit.
	
	Set to 1 to make it return the corresponding output bit.
	Set to 0 to make it return EITHER:-
		the corresponding input bit [pin level] (in non-latching mode)
		the corresponding bit of the corresponding IR
			(in latching mode [only with handshaking])
	*/
	
	switch (addr)
	{
		case VIA_ORB:
		ClearPortBints ();
		if (ACR & Latch_B)
			return ((ORB & DDRB) | (IRB & ~DDRB));
		else
			return ((ORB & DDRB) | (PinB & ~DDRB));
		
		case VIA_ORA:
		ClearPortAints ();
		if (ACR & Latch_A)
			return ((ORA & DDRA) | (IRA & ~DDRA));
		else
			return ((ORA & DDRA) | (PinA & ~DDRA));
		
		case VIA_ORA_nh:
		ClearPortAints ();
		return ((ORA & DDRA) | (PinA & ~DDRA));
		
		case VIA_DDRB: return DDRB;
		case VIA_DDRA: return DDRA;
		
		case VIA_T1CL: ClearInts (INT_T1); return Timer1 & 0xff;
		case VIA_T1CH: return (Timer1 >> 8) & 0xff;
		
		case VIA_T1LL: return T1L.b.lo;
		case VIA_T1LH: return T1L.b.hi;
		
		case VIA_T2CL:
		Timer2_running = 1;
		ClearInts (INT_T2);
		return Timer2 & 0xff;
		
		case VIA_T2CH: return (Timer2 >> 8) & 0xff;
		
		case VIA_SR:
		return 0;	// Not implemented
		
		case VIA_PCR: return PCR;
		case VIA_ACR: return ACR;
		
		case VIA_IER:
		return IER | 0x80;	// bit 7 is always high
		
		case VIA_IFR:
		return IFR;
	}
	return 0;
}

void VIA6522::MMIO_Write (int addr, byte val)
{
	byte v;
	
	switch (addr)
	{
		case VIA_ORB:
		ORB = val;
		v = (PinB & ~DDRB) | (ORB & DDRB);
		ClearPortBints ();
		if (v != PinB)
		{
			PinB = v;
			PinBchange ();
		}
		break;
		
		case VIA_ORA:
		case VIA_ORA_nh:
		ORA = val;
		v = (PinA & ~DDRA) | (ORA & DDRA);
		ClearPortAints ();
		if (v != PinA)
		{
			PinA = v;
			PinAchange ();
		}
		break;
		
		case VIA_DDRB:
		DDRB = val;
		v = (PinB & ~DDRB) | (ORB & DDRB);
		if (v != PinB)
		{
			PinB = v;
			PinBchange ();
		}
		break;
		
		case VIA_DDRA:
		DDRA = val;
		v = (PinA & ~DDRA) | (ORA & DDRA);
		if (v != PinA)
		{
			PinA = v;
			PinAchange ();
		}
		break;
		
		case VIA_T1CL:
		// Written to T1CL when T1CH is written
		case VIA_T1LL: T1L.b.lo = val; break;
		
		case VIA_T1LH: T1L.b.hi = val; break;
		
		case VIA_T1CH:
		T1L.b.hi = val; Timer1 = T1L.w;
		ClearInts (INT_T1);
		if (ACR & T1_SetPB7)
			MMIO_Write (VIA_ORB, MMIO_Read (VIA_ORB) & 0x7f);
		break;
		
		// Written to T2CL when T2CH is written
		case VIA_T2CL: T2L.b.lo = val; break;
		
		case VIA_T2CH:
		T2L.b.hi = val; Timer2 = T2L.w;
		Timer2_running = 1;
		ClearInts (INT_T2);
		break;
		
		case VIA_SR:
		// Not implemented
		break;
		
		case VIA_ACR: ACR = val; break;
		case VIA_PCR: PCR = val; break;
		
		case VIA_IER:
		/* Bit 7 set --> other bits SET bits in IER
		   Bit 7 not set --> other bits CLEAR bits in IER */
		if (val & 0x80)
			IER |= (val & 0x7f);
		else
			IER &= ~(val & 0x7f);
		//SetInts (IFR);
		if (IFR & IER)
		{
			IFR |= INT_ANY;
			SignalInt ();
		}
		else
		{
			IFR &= ~INT_ANY;
			ClearInt ();
		}
		break;
		
		case VIA_IFR:
		ClearInts (val);
		break;
	}
}

void VIA6522::SetInts (byte ints)
{
	/* Presumably IFR gets set, then the IER decides whether to interrupt
	   the CPU */
	IFR |= ints;
	if (IFR & IER)
		IFR |= INT_ANY;
	else
	{
		IFR &= ~INT_ANY;
		//ClearInt ();		// Optimization
	}
 	if ((IER & ints))
		SignalInt ();
}

void VIA6522::ClearInts (byte ints)
{
	// Bit 7 is set if any other interrupts are active
	IFR &= ~(ints | INT_ANY);
	if (IFR & IER)
		IFR |= INT_ANY;
	else
		ClearInt ();
}

void VIA6522::SetCA2 (byte val)
{
	switch (PCR_CA2Control ())
	{
		case HS2_NEG:
		case HS2_NEG_IND:
		if (!val)
			SetInts (INT_CA2);
		break;
		
		case HS2_POS:
		case HS2_POS_IND:
		if (val)
			SetInts (INT_CA2);
		break;
		
		// Other values of CA2Control configure CA2 as an output
	}
	
	CA2 = val;
}

void VIA6522::SetCB2 (byte val)
{
	switch (PCR_CB2Control ())
	{
		case HS2_NEG:
		case HS2_NEG_IND:
		if (!val)
			SetInts (INT_CB2);
		break;
		
		case HS2_POS:
		case HS2_POS_IND:
		if (val)
			SetInts (INT_CB2);
		break;
		
		// Other values of CA2Control configure CA2 as an output
	}
	
	CB2 = val;
}

void VIA6522::ClearPortAints (void)
{
	// Always clear CA1
	// If CA2 is in independent mode, clear it too.
	switch (PCR_CA2Control ())
	{
		case HS2_NEG_IND:
		case HS2_POS_IND:
		ClearInts (INT_CA2 | INT_CA1);
		break;
		
		default:
		ClearInts (INT_CA1);
		break;
	}
}

void VIA6522::ClearPortBints (void)
{
	// Always clear CB1
	// If CB2 is in independent mode, clear it too.
	switch (PCR_CB2Control ())
	{
		case HS2_NEG_IND:
		case HS2_POS_IND:
		ClearInts (INT_CB2 | INT_CB1);
		break;
		
		default:
		ClearInts (INT_CB1);
		break;
	}
}

void VIA6522::DoTicks (int ticks)
{
	Timer1 -= ticks;
	Timer2 -= ticks;
	if (Timer1 <= 0 || Timer2 <= 0)
		DoTimers ();
}

void VIA6522::DoTimers (void)
{
	// Update timers
	if (Timer1 <= 0)
	{
		if (ACR & T1_SetPB7)
		{
			if (ACR & T1_Continuous)
			{
				//SetPinB (PinB ^ 0x80);
				PinB ^= 0x80;
				PinBchange ();
			}
			else	// One-shot mode
			{
				//SetPinB (PinB | 0x80);
				if (!(PinB & 0x80))
				{
					PinB |= 0x80;
					PinBchange ();
				}
				ACR &= ~T1_SetPB7;
			}
		}
		// In continuous, reload counter from latches
		if (ACR & T1_Continuous)
			Timer1 += T1L.w;
		else
			Timer1 &= 0xffff;	// Roll-over
		SetInts (INT_T1);
	}
	
	// Timer 2 - only if it's actually in Timing mode
	if (!(ACR & T2_PulseCount))
	{
		if ((Timer2 <= 0) && Timer2_running)
		{
			// Its a one-shot
			Timer2_running = 0;
			SetInts (INT_T2);
		}
		// Roll it over
		Timer2 &= 0xffff;
	}
}

/* End of file. */
