'******************************************************************************
'* 
'* PC ISA CHIPSET v.1.0 (CHIPSET DEVICE)
'*
'* Supported platform/bus: X86
'*
'* Chipset, used in original x86 PC (model 5150) and clones
'*
'* PIC i8259, PIT i8253, DMA i8237, FDC i8272, KBC i8048
'* 
'* Version history:
'*  - v.1.0 by WadiM (initial emulation)
'*
'****************************************************************************** 

//DEBUG.ON 'uncomment to enable debug messages (can be slow for hi-freq events)

//parent ISA bus implementation
public use object BUS_ISA

//chipset name
DeviceName="PC ISA Chipset"
DebugName="PC_ISASET"

//chipset params
protected dim DEVPARAM_FDDCOUNT as byte = 2 'count of supported FDDs (1-4)

//variables
dim i as integer

'----------------------- Interrupt controller (PIC) ---------------------------

//PIC (programmable interrupt controller)
public use object PIC_I8259 as PIC : AddDevice(PIC)
pc.WritePort(0x20)=PIC.PORT0 : pc.ReadPort(0x20)=PIC.PORT0
pc.WritePort(0x21)=PIC.PORT1 : pc.ReadPort(0x21)=PIC.PORT1
pc.SetIRQ=PIC.SetIRQ : pc.GetIRQ=PIC.GetIRQ

'--------------------------- Interval timer (PIT) -----------------------------

//PIT (programmable interval timer)
public use object PIT_I8253 as PIT : AddDevice(PIT)
AddISADevice(PIT.Channel0, 0,-1) //system timer (IRQ0, no DMA)
   PIT.Channel0.Freq=1190000     //1.19Mhz
AddISADevice(PIT.Channel1,-1,-1) //not assigned (no IRQ, no DMA)
   PIT.Channel1.Freq=1190000     //1.19Mhz
AddISADevice(PIT.Channel2,-1,-1) //speaker (no IRQ, no DMA)
   PIT.Channel2.Freq=1190000     //1.19Mhz
pc.WritePort(0x40)=PIT.CH0_DATA_PORT : pc.ReadPort(0x40)=PIT.CH0_DATA_PORT
pc.WritePort(0x41)=PIT.CH1_DATA_PORT : pc.ReadPort(0x41)=PIT.CH1_DATA_PORT
pc.WritePort(0x42)=PIT.CH2_DATA_PORT : pc.ReadPort(0x42)=PIT.CH2_DATA_PORT
pc.WritePort(0x43)=PIT.MODE_PORT     : pc.ReadPort(0x43)=PIT.MODE_PORT

'------------------------- Serial interfaces (COM) ----------------------------

//COM1 (as MS Mouse)
public use object MS_MOUSE as COM1 : COM1.DeviceIndex=1
AddISADevice(COM1,4,-1,0x3F8) //IRQ4, no DMA, base port 3F8h
for i=0x3F8 to 0x3FF : pc.WritePort(i)=COM1.PORTS : pc.ReadPort(i)=COM1.PORTS 
next
pc.HandleMouse=COM1.HandleMouse

//COM2
public use object COM_I8250 as COM2 : COM2.DeviceIndex=2
AddISADevice(COM2,3,-1,0x2F8) //IRQ3, no DMA, base port 2F8h
for i=0x2F8 to 0x2FF : pc.WritePort(i)=COM2.PORTS : pc.ReadPort(i)=COM2.PORTS 
next

'------------------- Direct memory access controllers (DMAs) ------------------

//DMA controller (direct memory access controller)
public use object DMA_I8237 as DMA : AddDevice(DMA)
for i=0 to 0xF : pc.WritePort(i)=DMA.PORTS : pc.ReadPort(i)=DMA.PORTS : next
for i=0x80 to 0x8F : pc.WritePort(i)=DMA.PORTS : pc.ReadPort(i)=DMA.PORTS : next

'----------------------- Floppy disk controller (FDC) -------------------------

//FDC (floppy disk controller) 
public use object FDC_I8272 as FDC : AddISADevice(FDC,6,-1)
pc.WritePort(0x3F2)=FDC.MODE_PORT : pc.ReadPort(0x3F4)=FDC.STATE_PORT
pc.WritePort(0x3F5)=FDC.DATA_PORT : pc.ReadPort(0x3F5)=FDC.DATA_PORT

'------------------- Keyboard controller and switches (KBC) -------------------

//KBC controller (keyboard controller)
public use object KBC_I8048 as KBC : AddISADevice(KBC,1,-1)
pc.HandleKey=KBC.HandleKey 

//Ports 60h..61h handling
public dim p60h as byte=0, p61h as byte=0

property PORT_60H(value as byte)
 p60h=value 'TODO
end

function PORT_60H as byte
 if (p61h and 0x80)=0 then 'keyboard data port
     result=KBC.DATA_PORT
 else 'configuration switches
     result=0x4D or 2 //2FDD,FPU
     if DEV_EXISTS("","cga") then result=result or 0x20 //color CGA 80x25 - need by ES-1841
end : end

property PORT_61H(value as byte)
 p61h=value 
 if (value and 0x80)<>0 then KBC.KeyProcessed
 //TODO - sound
end

function PORT_61H as byte
 result=p61h and (not 0x80) 'TODO - sound
end

function PORT_62H as byte
 if (p61h and 0x8)=0 then //low switches
    result=1 		  'loop on POST (bit 0 set = no) 
    result=result or 0x02 'FPU installed (bit 1 = yes)
    result=result or 0x0C 'RAM on board (bit 2..3 = 0-64,1-128,2-192,3-256Kb)
 else  //high switches
    result=2              'display at start (bit 0..1 = 0-res,1-40,2-80,3-M80)
    result=result or shl((DEVPARAM_FDDCOUNT-1) and 0x3,2) 'FDD count (bit 2..3)
 end if
end

function PORT_63H as byte
 result=0
end

pc.WritePort(0x60)=PORT_60H : pc.ReadPort(0x60)=PORT_60H
pc.WritePort(0x61)=PORT_61H : pc.ReadPort(0x61)=PORT_61H
pc.ReadPort(0x62)=PORT_62H
pc.ReadPort(0x63)=PORT_63H

'-------------------------------- Debug stuff ---------------------------------

//Port 80H  used by BIOS to send and read debug information (POST codes etc)
dim Port80Value as byte=0
property PORT_80H(value as byte)
 Port80Value=Value
 ?? "[BIOS] >Port80=";Hex(Value,2);"h" : //if Value=0xFF then dbg.break
end
function PORT_80H as byte : Result=Port80Value : end
pc.WritePort(0x80)=PORT_80H : pc.ReadPort(0x80)=PORT_80H

//DOS INT 21h interrupt filter (can be used to analize emulation bugs)
public function INT_21H as boolean 
 result=false 'to call real int 21h handler
 if cpu.AH=0x4B then 'DOS EXEC
  dim i as integer, b as byte, s as string="" : mem.Pos=shl(cpu.ES,4)+cpu.DX 
  for i=0 to 79 : b=mem.Byte : if b=0 then exit for : s=s+Chr(b) : next
  ?? "[DOS_EXEC] CmdLn=""";s;""""
  //if s="" then dbg.Break
 end
 // ?? "[INT_21H] AX=";Hex(cpu.AX,4);"h"
 //if cpu.AH=0xFF then dbg.Break
end

'------------------------------ Other stuff -----------------------------------

//HACK: Capture cpu's "int 13" to emulate FDD/HDD (because lack of controller emu)
//Bootstap loader int19h used to detect change int13 handler to emulated one, 
//because BIOS already set interrupt vectors and OS not captured them yet

use module BIOS_SPCX86_PATH+"eint_13h" as INT13 : pc.CallEmuInt(0x13)=INT13.EINT_13H

public function EINT_19H as boolean
 dim pos as dword=shl(mem.Word(0x13*4+2),4)+mem.Word(0x13*4) //handler address
 if pos+5<mem.Size then : mem.pos=pos 'trying to change handler
  dim i=pos/1024 'remove posible write protetion (in case of ROM)
  dim a as byte=mem.KBAccess(i) : mem.KBAccess(i)=3 : dim b as byte
  if (i+1)*1024<mem.Size then : b=mem.KBAccess(i+1) : mem.KBAccess(i+1)=3 : end
  mem.Bytes=Array(0xF1,0x13)                        'EINT 13h
  mem.Bytes=Array(0xFB)                             'STI
  mem.Bytes=Array(0xCA,0x02,0x00)                   'RETF 0002h
  mem.KBAccess(i)=a : if (i+1)*1024<mem.Size then mem.KBAccess(i+1)=b
 end if
 result=false 'need real int19 processing
end

pc.CallInt(0x19)=EINT_19H

//stubs
function ReadPort69 as byte : result=0x11 : end : pc.ReadPort(0x69)=ReadPort69
function ReadPort6A as byte : result=0xFF : end : pc.ReadPort(0x6A)=ReadPort6A

'---------------------------- DEVICE Interface --------------------------------

//Device initialization
public function DEV_INIT(stream as object,byref EventFreq as integer) as boolean
 
 'parent call
 if not DEV_INIT(stream,EventFreq) then exit(false)

 'capture INT 21h of DOS to trace calls
 //pc.CallInt(0x21)=INT_21H

 'success
 result=true

end

