#include <EDK.h>
#include "General.h"
#include "CVIA6522.h"

#define Class CVIA6522
extern Class MSVC4Bug;


////////////////////////////////////////////////////////////////////////////////
// VIA port registers

/*
Written values are stored in an internal buffer, called a latch.

Open inputs are always high.

After switching a line from input to output, it will go low if the
corresponding bit in the latch is 0.

Accessing ORB clears the bits 3-4 in the IFR. Accessing ORA clears the
bits 0-1 in the IFR.

Accessing ORx can influence the Cx2 line, depending on the PCR.

  CPort PA;             // bidirectional ports with open collector outputs
  CPort PB;
  byte bPALatch;        // buffered output values
  byte bPBLatch;
  byte bDDRA;           // 8 bits: 0 = input, 1 = output
  byte bDDRB;
  CLine CA1;            // port control lines
  CLine CA2;
  CLine CB1;
  CLine CB2;
  byte bPCR;            // port control register
  byte abPortFill[3];
*/

void fn(OnCA1High)();
void fn(OnCA1Low)();
void fn(OnCA2High)();
void fn(OnCA2Low)();
void fn(OnCB1High)();
void fn(OnCB1Low)();
void fn(OnCB2High)();
void fn(OnCB2Low)();

proc(ResetPorts)

  xor EAX,EAX
  mov mvar(bPALatch),AL
  mov mvar(bPBLatch),AL
  mov mvar(bDDRA),AL
  mov mvar(bDDRB),AL
  mov mvar(bPCR),AL

  mov AL,0xFF
  SetPort(mvar(PA), 1)
  mov AL,0xFF
  SetPort(mvar(PB), 2)

  SetHigh(mvar(CA2), 1)
  SetHigh(mvar(CB2), 2)

  ret
endp


////////////////////////////////////////
// read port and direction registers

proc(ReadORA)
  push ESI
  mov ESI,ECX

  // reset IFR 0-1
  mov AL,mvar(bIFR)
  and AL,11111100b
  mov mvar(bIFR),AL
  test mvar(bIER),AL
  jne Ignore
  SetHigh(mvar(Int), 1)
Ignore:

  GetPort(mvar(PA))

  pop ESI
  ret
endp


proc(ReadORB)
  push ESI
  mov ESI,ECX


  // reset IFR 3-4
  mov AL,mvar(bIFR)
  and AL,11100111b
  mov mvar(bIFR),AL
  test mvar(bIER),AL
  jne Ignore
  SetHigh(mvar(Int), 1)
Ignore:

  GetPort(mvar(PB))


  pop ESI
  ret
endp


proc(ReadDDRA)
  push ESI
  mov ESI,ECX


  mov AL,mvar(bDDRA)


  pop ESI
  ret
endp


proc(ReadDDRB)
  push ESI
  mov ESI,ECX


  mov AL,mvar(bDDRB)


  pop ESI
  ret
endp


////////////////////////////////////////
// write port and direction registers

proc(WriteORA)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov mvar(bPALatch),AL

  // set input lines high
  mov AH,mvar(bDDRA)
  not AH
  or AL,AH
  SetPort(mvar(PA), 1)

  // reset IFR 0-1
  mov AL,mvar(bIFR)
  and AL,11111100b
  mov mvar(bIFR),AL
  test mvar(bIER),AL
  jne Ignore
  SetHigh(mvar(Int), 1)
Ignore:


  pop ESI
  pop EBX
  ret 4
endp


proc(WriteORB)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov mvar(bPBLatch),AL

  // set input lines high
  mov AH,mvar(bDDRB)
  not AH
  or AL,AH
  SetPort(mvar(PB), 1)

  // reset IFR 3-4
  mov AL,mvar(bIFR)
  and AL,11100111b
  mov mvar(bIFR),AL
  test mvar(bIER),AL
  jne Ignore
  SetHigh(mvar(Int), 1)
Ignore:


  pop ESI
  pop EBX
  ret 4
endp


proc(WriteDDRA)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov mvar(bDDRA),AL

  // set input lines high
  not AL
  or AL,mvar(bPALatch)
  SetPort(mvar(PA), 1)


  pop ESI
  pop EBX
  ret 4
endp


proc(WriteDDRB)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov mvar(bDDRB),AL

  // set input lines high
  not AL
  or AL,mvar(bPBLatch)
  SetPort(mvar(PB), 1)


  pop ESI
  pop EBX
  ret 4
endp


////////////////////////////////////////
// port control lines CA1 and CB1

proc(OnCA1High)
  push ThisReg
  mov ThisReg,ECX

  // set IFR bit 1 if PCR bit 0 = 1
  test mvar(bPCR),00000001b
  je Ignore
  or mvar(bIFR),00000010b
  test mvar(bIER),00000010b
  je Ignore
  SetLow(mvar(Int), 1)
Ignore:

  pop ThisReg
  ret
endp


proc(OnCA1Low)
  push ThisReg
  mov ThisReg,ECX

  // set IFR bit 1 if PCR bit 0 = 0
  test mvar(bPCR),00000001b
  jne Ignore
  or mvar(bIFR),00000010b
  test mvar(bIER),00000010b
  je Ignore
  SetLow(mvar(Int), 1)
Ignore:

  pop ThisReg
  ret
endp


proc(OnCB1High)
  push ThisReg
  mov ThisReg,ECX

  // set IFR bit 4 if PCR bit 4 = 1
  test mvar(bPCR),00010000b
  je Ignore
  or mvar(bIFR),00010000b
  test mvar(bIER),00010000b
  je Ignore
  SetLow(mvar(Int), 1)
Ignore:

  pop ThisReg
  ret
endp


proc(OnCB1Low)
  push ThisReg
  mov ThisReg,ECX

  // set IFR bit 4 if PCR bit 4 = 0
  test mvar(bPCR),00010000b
  jne Ignore
  or mvar(bIFR),00010000b
  test mvar(bIER),00010000b
  je Ignore
  SetLow(mvar(Int), 1)
Ignore:

  pop ThisReg
  ret
endp


////////////////////////////////////////
// port control lines CA2 and CB2

proc(OnCA2High)
  push ThisReg
  mov ThisReg,ECX

  // set IFR bit 0 if PCR bit 2 = 1
  test mvar(bPCR),00000100b
  je Ignore
  or mvar(bIFR),00000001b
  test mvar(bIER),00000001b
  je Ignore
  SetLow(mvar(Int), 1)
Ignore:

  pop ThisReg
  ret
endp


proc(OnCA2Low)
  push ThisReg
  mov ThisReg,ECX

  // set IFR bit 0 if PCR bit 2 = 0
  test mvar(bPCR),00000100b
  jne Ignore
  or mvar(bIFR),00000001b
  test mvar(bIER),00000001b
  je Ignore
  SetLow(mvar(Int), 1)
Ignore:

  pop ThisReg
  ret
endp


proc(OnCB2High)
  push ThisReg
  mov ThisReg,ECX

  // set IFR bit 3 if PCR bit 6 = 1
  test mvar(bPCR),01000000b
  je Ignore
  or mvar(bIFR),00001000b
  test mvar(bIER),00001000b
  je Ignore
  SetLow(mvar(Int), 1)
Ignore:

  pop ThisReg
  ret
endp


proc(OnCB2Low)
  push ThisReg
  mov ThisReg,ECX

  // set IFR bit 3 if PCR bit 6 = 0
  test mvar(bPCR),01000000b
  jne Ignore
  or mvar(bIFR),00001000b
  test mvar(bIER),00001000b
  je Ignore
  SetLow(mvar(Int), 1)
Ignore:

  pop ThisReg
  ret
endp


////////////////////////////////////////
// port control register

proc(ReadPCR)
  push ESI
  mov ESI,ECX


  mov AL,mvar(bPCR)


  pop ESI
  ret
endp


proc(WritePCR)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov mvar(bPCR),AL

  // set CA2 high or low
  and AL,00001110b
  cmp AL,00001100b
  je CA2Low
  cmp AL,00001110b
  jne CA2Cont
  SetHigh(mvar(CA2), 1)
  jmp CA2Cont
CA2Low:
  SetLow(mvar(CA2), 1)
CA2Cont:

  // set CB2 high or low
  mov AL,mvar(bPCR)
  and AL,11100000b
  cmp AL,11000000b
  je CB2Low
  cmp AL,11100000b
  jne CB2Cont
  SetHigh(mvar(CB2), 2)
  jmp CB2Cont
CB2Low:
  SetLow(mvar(CB2), 2)
CB2Cont:


  pop ESI
  pop EBX
  ret 4
endp


////////////////////////////////////////////////////////////////////////////////
// VIA timers T1 and T2

/*
  CEvent T1;            // functions for timer underflows
  CEvent T2;
  int iT1End;           // iT1End - GetClocks = timer values for reads
  int iT2End;
  int iT1Latch;         // start values
  int iT2Latch;
  byte bACR;            // auxiliary control register
  byte abTimerFill[3];
*/

proc(ResetTimers)

  mov EAX,65535
  mov mvar(iT1Latch),EAX
  mov mvar(iT2Latch),EAX

  }
  static CVIA6522* p;
  __asm mov p,ThisReg
  __asm push ThisReg
  p->T1.StartCounter(65536);
  extern Clock* gpClock;
  p->iT1End = gpClock->GetClocks() + 65536;
  p->T2.StartCounter(65536);
  p->iT2End = gpClock->GetClocks() + 65536;
  __asm pop ThisReg
  __asm {

  ret
endp

                                        
////////////////////////////////////////
// read timer 1 latch

proc(ReadT1LL)
  push ESI
  mov ESI,ECX


  mov AL,byte ptr mvar(iT1Latch+0)


  pop ESI
  ret
endp


proc(ReadT1LH)
  push ESI
  mov ESI,ECX


  mov AL,byte ptr mvar(iT1Latch+1)


  pop ESI
  ret
endp


////////////////////////////////////////
// write timer 1 latch

proc(WriteT1LL)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov byte ptr mvar(iT1Latch+0),AL


  pop ESI
  pop EBX
  ret 4
endp


proc(WriteT1LH)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov byte ptr mvar(iT1Latch+1),AL


  pop ESI
  pop EBX
  ret 4
endp


////////////////////////////////////////
// read timer counter low

static void AddEvent() {
  static Timer* p;
  static int i;
  __asm mov p,EAX
  __asm mov i,EBX
  p->StartCounter(i);
  extern Clock* gpClock;
  i = gpClock->GetClocks();
  __asm mov EAX,i
}
#define GetClocks } static int i; extern Clock* gpClock; i = gpClock->GetClocks(); __asm mov EAX,i __asm {

proc(ReadT1CL)
  push EBX
  push ESI
  mov ESI,ECX


  // clear bit 6 in IFR
  mov AL,mvar(bIFR)
  test AL,01000000b
  je NoClear
  and AL,10111111b
  mov mvar(bIFR),AL

  // clear Int line
  test mvar(bIER),AL
  jne NoClear
  SetHigh(mvar(Int), 1)

NoClear:

  GetClocks
  neg EAX
  add EAX,mvar(iT1End)


  pop ESI
  pop EBX
  ret
endp


proc(ReadT2CL)
  push EBX
  push ESI
  mov ESI,ECX


  // clear bit 5 in IFR
  mov AL,mvar(bIFR)
  test AL,00100000b
  je NoClear
  and AL,11011111b
  mov mvar(bIFR),AL

  // clear Int line
  test mvar(bIER),AL
  jne NoClear
  SetHigh(mvar(Int), 1)

NoClear:

  GetClocks
  neg EAX
  add EAX,mvar(iT2End)


  pop ESI
  pop EBX
  ret
endp


////////////////////////////////////////
// read timer counter high

proc(ReadT1CH)
  push EBX
  push ESI
  mov ESI,ECX


  GetClocks
  neg EAX
  add EAX,mvar(iT1End)

  mov AL,AH


  pop ESI
  pop EBX
  ret
endp


proc(ReadT2CH)
  push EBX
  push ESI
  mov ESI,ECX


  GetClocks
  neg EAX
  add EAX,mvar(iT2End)

  mov AL,AH


  pop ESI
  pop EBX
  ret
endp


////////////////////////////////////////
// write timer counter low

proc(WriteT1CL)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov byte ptr mvar(iT1Latch+0),AL


  pop ESI
  pop EBX
  ret 4
endp


proc(WriteT2CL)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov byte ptr mvar(iT2Latch+0),AL


  pop ESI
  pop EBX
  ret 4
endp


////////////////////////////////////////
// write timer counter high

proc(WriteT1CH)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov byte ptr mvar(iT1Latch+1),AL

  lea EAX,mvar(T1)
  mov EBX,mvar(iT1Latch)
  inc EBX
  call AddEvent
  add EAX,mvar(iT1Latch)
  mov mvar(iT1End),EAX

  // clear bit 6 in IFR
  mov AL,mvar(bIFR)
  test AL,01000000b
  je NoClear
  and AL,10111111b
  mov mvar(bIFR),AL

  // clear Int line
  test mvar(bIER),AL
  jne NoClear
  SetHigh(mvar(Int), 1)

NoClear:


  pop ESI
  pop EBX
  ret 4
endp


proc(WriteT2CH)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov byte ptr mvar(iT2Latch+1),AL

  lea EAX,mvar(T2)
  mov EBX,mvar(iT2Latch)
  inc EBX
  call AddEvent
  add EAX,mvar(iT2Latch)
  mov mvar(iT2End),EAX

  // clear bit 5 in IFR
  mov AL,mvar(bIFR)
  test AL,00100000b
  je NoClear
  and AL,11011111b
  mov mvar(bIFR),AL

  // clear Int line
  test mvar(bIER),AL
  jne NoClear
  SetHigh(mvar(Int), 1)

NoClear:


  pop ESI
  pop EBX
  ret 4
endp

                                        
////////////////////////////////////////
// on timer underflow

void CVIA6522::OnT1Underflow() {
  __asm {
  push ThisReg
  mov ThisReg,ECX

  // set IFR and fire Int
  or mvar(bIFR),01000000b
  test mvar(bIER),01000000b
  je NoInt
  SetLow(mvar(Int), 1)
NoInt:

  // reload value
  lea EAX,mvar(T1)
  mov EBX,mvar(iT1Latch)
  inc EBX
  call AddEvent
  add EAX,mvar(iT1Latch)
  mov mvar(iT1End),EAX

  pop ThisReg
  }
}


void CVIA6522::OnT2Underflow() {
  __asm {
  push ThisReg
  mov ThisReg,ECX

  // set IFR and fire Int
  or mvar(bIFR),00100000b
  test mvar(bIER),00100000b
  je NoInt
  SetLow(mvar(Int), 1)
NoInt:

  // reload value
  lea EAX,mvar(T2)
  mov EBX,mvar(iT2Latch)
  inc EBX
  call AddEvent
  add EAX,mvar(iT2Latch)
  mov mvar(iT2End),EAX

  pop ThisReg
  }
}


////////////////////////////////////////
// auxiliary control

proc(ReadACR)
  push ESI
  mov ESI,ECX


  mov AL,mvar(bACR)


  pop ESI
  ret
endp


proc(WriteACR)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  mov mvar(bACR),AL


  pop ESI
  pop EBX
  ret 4
endp


////////////////////////////////////////////////////////////////////////////////
// VIA interrupts

/*
  CLine Int;
  byte bIFR;            // interrupt flag register
  byte bIER;            // interrupt enable register
  byte abIntFill[2];
*/

proc(ResetInt)

  xor EAX,EAX
  mov mvar(bIFR),AL
  mov mvar(bIER),AL

  SetHigh(mvar(Int), 1)

  ret
endp


////////////////////////////////////////
// read interrupt registers

proc(ReadIFR)
  push ESI
  mov ESI,ECX


  mov AL,mvar(bIFR)

  test AL,mvar(bIER)
  je NoInt
  or AL,10000000b
NoInt:


  pop ESI
  ret
endp

proc(ReadIER)
  push ESI
  mov ESI,ECX


  mov AL,mvar(bIER)


  pop ESI
  ret
endp


////////////////////////////////////////
// write interrupt registers

proc(WriteIFR)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  not AL
  and AL,mvar(bIFR)
  mov mvar(bIFR),AL

  test AL,mvar(bIER)
  jne DontClear
  SetHigh(mvar(Int), 1)
DontClear:


  pop ESI
  pop EBX
  ret 4
endp


proc(WriteIER)
  mov EAX,[ESP+4]
  push EBX
  push ESI
  mov ESI,ECX


  and AL,AL
  js Set

  not AL
  and AL,mvar(bIER)
  mov mvar(bIER),AL

  test AL,mvar(bIFR)
  jne DontClear
  SetHigh(mvar(Int), 1)
DontClear:


  pop ESI
  pop EBX
  ret 4

Set:
  or AL,mvar(bIER)
  and AL,01111111b
  mov mvar(bIER),AL

  test AL,mvar(bIFR)
  je DontSet
  SetLow(mvar(Int), 1)
DontSet:


  pop ESI
  pop EBX
  ret 4
endp


////////////////////////////////////////////////////////////////////////////////
// VIA initialisation

/*
  CLine Reset;
*/

void fn(OnResetHigh)();
void fn(OnResetLow)();

proc(Reset)

  call fn(ResetPorts)
  call fn(ResetTimers)
  call fn(ResetInt)

  ret
endp


proc(OnResetHigh)
  push ThisReg
  mov ThisReg,ECX

  call fn(Reset)

  pop ThisReg
  ret
endp


proc(OnResetLow)
  push ThisReg
  mov ThisReg,ECX

  call fn(Reset)

  pop ThisReg
  ret
endp


////////////////////////////////////////
// constructor

static pfnv gapfnRegs[16 * 3] = {
  fn(ReadORB), fn(WriteORB), NULL,
  fn(ReadORA), fn(WriteORA), NULL,
  fn(ReadDDRB), fn(WriteDDRB), NULL,
  fn(ReadDDRA), fn(WriteDDRA), NULL,
  fn(ReadT1CL), fn(WriteT1CL), NULL,
  fn(ReadT1CH), fn(WriteT1CH), NULL,
  fn(ReadT1LL), fn(WriteT1LL), NULL,
  fn(ReadT1LH), fn(WriteT1LH), NULL,
  fn(ReadT2CL), fn(WriteT2CL), NULL,
  fn(ReadT2CH), fn(WriteT2CH), NULL,
  NULL, NULL, NULL,
  fn(ReadACR), fn(WriteACR), NULL,
  fn(ReadPCR), fn(WritePCR), NULL,
  fn(ReadIFR), fn(WriteIFR), NULL,
  fn(ReadIER), fn(WriteIER), NULL,
  fn(ReadORA), fn(WriteORA), NULL,
};


global CVIA6522::CVIA6522() {

  pRegisters = (Register*)gapfnRegs;
  iRegisterCount = 16;

  Reset.Init("Reset", this);
  Reset.SetOnHigh(make_pfn(fn(OnResetHigh)));
  Reset.SetOnLow(make_pfn(fn(OnResetLow)));
  Int.Init("Int", this);
  PA.Init("PA", this);
  PB.Init("PB", this);
  T1.Init("T1", this);
  T1.SetOnFire((pfn)OnT1Underflow);
  T2.Init("T2", this);
  T2.SetOnFire((pfn)OnT2Underflow);
  CA1.Init("CA1", this);
  CA1.SetOnHigh(make_pfn(fn(OnCA1High)));
  CA1.SetOnLow(make_pfn(fn(OnCA1Low)));
  CA2.Init("CA2", this);
  CA2.SetOnHigh(make_pfn(fn(OnCA2High)));
  CA2.SetOnLow(make_pfn(fn(OnCA2Low)));
  CB1.Init("CB1", this);
  CB1.SetOnHigh(make_pfn(fn(OnCB1High)));
  CB1.SetOnLow(make_pfn(fn(OnCB1Low)));
  CB2.Init("CB2", this);
  CB2.SetOnHigh(make_pfn(fn(OnCB2High)));
  CB2.SetOnLow(make_pfn(fn(OnCB2Low)));

  __asm {
    push ThisReg
    mov ThisReg,this
    call fn(Reset)
    pop ThisReg
  }

}


CVIA6522::~CVIA6522() {
}
