'******************************************************************************
'* 
'* PC/AT ISA CHIPSET (CHIPSET DEVICE)
'*
'* Supported platform/bus: X86
'*
'* Chipset, used in original x86 PC/AT (model 5170) and clones
'*
'* 2 PIC i8259, PIT i8253, 2 DMA i8237, FDC i8272, KBC i8042
'* 
'* Version history:
'*  2007,2008 - initial emulation (by WadiM)
'*
'****************************************************************************** 

//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="PCAT_ISASET"

//chipset params
protected dim DEVPARAM_FDDCount as byte = 2 'count of supported FDDs (0-2)
protected dim DEVPARAM_HDDCount as byte = 2 'count of supported HDDs (0-2)
protected dim DEVPARAM_NMIEnabled as boolean = true 'NMI enabled/disabled

//variables
dim i as integer, j as integer

'---------------------- Interrupt controllers (PICs) --------------------------

//PICs (programmable interrupt controllers, master and slave)
public use object PICs_I8259AT as PICs : AddDevice(PICs)
pc.WritePort(0x20)=PICs.PICa.PORT0 : pc.ReadPort(0x20)=PICs.PICa.PORT0
pc.WritePort(0x21)=PICs.PICa.PORT1 : pc.ReadPort(0x21)=PICs.PICa.PORT1
pc.WritePort(0xA0)=PICs.PICb.PORT0 : pc.ReadPort(0xA0)=PICs.PICb.PORT0
pc.WritePort(0xA1)=PICs.PICb.PORT1 : pc.ReadPort(0xA1)=PICs.PICb.PORT1
pc.SetIRQ=PICs.SetIRQ 		   : pc.GetIRQ=PICs.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 controllers (direct memory access controllers)
public use object DMA_I8237 as DMA : AddDevice(DMA)
public use object DMA_I8237 as DMA2 : AddDevice(DMA2)
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
for i=0xC0 to 0xDF : pc.WritePort(i)=DMA2.PORTS : pc.ReadPort(i)=DMA2.PORTS : next
i=0x89 : pc.WritePort(i)=DMA2.PORTS : pc.ReadPort(i)=DMA2.PORTS
i=0x8b : pc.WritePort(i)=DMA2.PORTS : pc.ReadPort(i)=DMA2.PORTS
i=0x8a : pc.WritePort(i)=DMA2.PORTS : pc.ReadPort(i)=DMA2.PORTS

'------------------------------ CMOS (RTC, NVRAM) -----------------------------

//CMOS memory
public use object CMOS_AT as CMOS : AddISADevice(CMOS,8,-1) : CMOS.SIZE=128

//Write cell index (bits 0..6 - index, bit 7 - NMI enabled/disabled)
property PORT_70H(value as byte) 
 DEVPARAM_NMIEnabled=(value and 0x80)=0  : CMOS.INDEX_PORT=value and 0x7F 
end

//Read cell index (bits 0..6 - index, bit 7 - NMI enabled/disabled)
function PORT_70H as byte 
 Result=(CMOS.INDEX_PORT and 0x7F) or iif(DEVPARAM_NMIEnabled,0,0x80) 
end

//Attach port handlers
pc.WritePort(0x70)=PORT_70H : pc.ReadPort(0x70)=PORT_70H
pc.WritePort(0x71)=CMOS.VALUE_PORT : pc.ReadPort(0x71)=CMOS.VALUE_PORT

'------------------------ Floppy Drive Controller (FDC) -----------------------

//FDC (floppy disk controller) - unready yet, simply stub!
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

'-------------------------- Hard Drive Controllers (HDC) ----------------------

//HDC (first hard disk controller)
public use object HDC_ATA as HDC : AddISADevice(HDC,14,-1)
HDC.Disks(0)=emuDrives.HDD(0) : HDC.Disks(1)=emuDrives.HDD(1)
pc.WritePort(0x1F0)=HDC.DATA_PORT8           : pc.ReadPort(0x1F0)=HDC.DATA_PORT8
pc.WritePort16(0x1F0)=HDC.DATA_PORT16        : pc.ReadPort16(0x1F0)=HDC.DATA_PORT16
pc.WritePort32(0x1F0)=HDC.DATA_PORT32        : pc.ReadPort32(0x1F0)=HDC.DATA_PORT32
pc.WritePort(0x1F1)=HDC.PRECOMP_PORT         : pc.ReadPort(0x1F1)=HDC.ERROR_PORT
pc.WritePort(0x1F2)=HDC.SECTOR_COUNT_PORT    : pc.ReadPort(0x1F2)=HDC.SECTOR_COUNT_PORT
pc.WritePort(0x1F3)=HDC.SECTOR_INDEX_PORT    : pc.ReadPort(0x1F3)=HDC.SECTOR_INDEX_PORT
pc.WritePort(0x1F4)=HDC.CYLINDER_INDEXL_PORT : pc.ReadPort(0x1F4)=HDC.CYLINDER_INDEXL_PORT
pc.WritePort(0x1F5)=HDC.CYLINDER_INDEXH_PORT : pc.ReadPort(0x1F5)=HDC.CYLINDER_INDEXH_PORT
pc.WritePort(0x1F6)=HDC.DRIVE_HEAD_PORT      : pc.ReadPort(0x1F6)=HDC.DRIVE_HEAD_PORT
pc.WritePort(0x1F7)=HDC.CMD_PORT             : pc.ReadPort(0x1F7)=HDC.STATE_PORT
pc.WritePort(0x3F6)=HDC.CONTROL_PORT         : pc.ReadPort(0x3F6)=HDC.ALT_STATE_PORT
                                             : pc.ReadPort(0x3F7)=HDC.DRIVE_ADDRESS_PORT

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

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

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

property PORT_60H(value as byte)
 p60h=value 'TODO
 KBC.DATA_PORT=value
 ?? "[KBC] >Port60=";Hex(Value,2);"h"
end

function PORT_60H as byte
 if (p61h and 0x80)=0 then 'keyboard data port
     result=KBC.DATA_PORT
     ?? "READ PORT 60=";Hex(result,2)
 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
     ?? "[KBC] <Port60=";Hex(result,2);"h"
end : end

property PORT_61H(value as byte)
 p61h=value 
// KBC.KeyProcessed
 ?? "[KBC] >Port61=";Hex(Value,2);"h"
 //TODO - sound
end

dim refresh_counter as byte=0 'temporary hack to emulate refresh

function PORT_61H as byte
// if (p61h and 0x10)=0 then p61h=p61h or 0x10 else p61h=p61h and (not 0x10) 'refresh
// if (p61h and 0x20)=0 then p61h=p61h or 0x20 else p61h=p61h and (not 0x20) 'refresh
 p61h=(p61h and (not 0x30)) or (refresh_counter and 0x30) 
 refresh_counter=refresh_counter+0x08
 result=p61h and (not 0x80) 'not parity error
 result=result and (not 0x40) 'not channel error
 ?? "[KBC] <Port61=";Hex(result,2);"h"
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 0x04 'FDD count (bit 2..3 = 1 to 4)
 end if
 ?? "[KBC] <Port62=";Hex(result,2);"h"
end

function PORT_63H as byte
 result=0
 ?? "[KBC] <Port63=";Hex(result,2);"h"
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
pc.WritePort(0x64)=KBC.CMD_PORT : pc.ReadPort(0x64)=KBC.STATE_PORT


'-------------------------------- 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=0x0B 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 -----------------------------------

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

//Video BIOS interrupt filter
public function INT_10H as boolean 
 result=false 'to call real int 10h handler
 'fix ET4000 BIOS bug (by R. Brown's Int. List) - system crush (try Heretic :)
 if (cpu.AH=0x12) and (cpu.BL=0x80) then result=true
 'disable VESA support (which is not emulated yet)
 if cpu.AH=0x4F then : cpu.AX=0x100 : result=true : end
 ?? "[INT_10H] AX=";Hex(cpu.AX,4);"h   BX=";Hex(cpu.BX,4);"h (";Chr(cpu.AL);")" //BMHACK
// if cpu.AX=0x0E4B then dbg.Break
end

//CMOS checksum calculation (cells [10h..2Dh]) and writing to [2E..2F])
protected procedure UpdateCMOSChecksum
 dim w as word=0 : for i=0x10 to 0x2D : w=w+CMOS.Cell(i) : next 
 CMOS.Cell(0x2E)=shr(w,8) : CMOS.Cell(0x2F)=w
end

//Handler of non-phisycal memory reading (address bigger than memory size)
function ExternalMemoryReader(Addr as dword, byref Value as byte) as boolean
 if Addr>=0xFFFC0000 then //BIOS at top of address space
    dim pos as int64=mem.Pos : Value=mem.Byte(Addr-0xFFF00000)
    mem.pos=pos : result=true
 else : result=false : end if
end

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

//Device initialization
public function DEV_INIT(stream as object,byref EventFreq as integer) as boolean
 
 dim i as integer, j as integer 

 'parent call
 if not DEV_INIT(stream,EventFreq) then exit(false)

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

 'generate initial CMOS data
 exec("PCAT\cmos_gen")
 if not DEV_EXISTS("","CGA") then 
    CMOS.Cell(0x14)=CMOS.Cell(0x14) and (not 0x30) 'EGA/VGA
 end if
 UpdateCMOSChecksum

 'capture INT 10h of Video BIOS to disable VESA stuff
 pc.CallInt(0x10)=INT_10H

 'top of address space remapping
 mem.ExternalReader=ExternalMemoryReader

 'success
 result=true

end


