'******************************************************************************
'* 
'* DIRECT MEMORY ACCESS CONTROLLER i8237 (CHIP DEVICE)
'*
'* Used to provide fast CPU-independent 8-bit data transfer
'*
'* Supported platform/bus: X86
'* 
'* Version history:
'*  2008 - initial emulation (by Wadim)
'*
'******************************************************************************

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

//device interface support
public use object DEVICE

//device name
DeviceName="DMA i8237"
DebugName="DMA_I8237"

//Variables
dim i as integer

//Parameters
dim FlipFlop as boolean=false 'flag of 16-bit port part (false=low byte)
dim CanMemToMem as boolean=false 'memory-to-memory data transfer allowed //TODO
dim CanCh0FixedAddr as boolean=false 'channel 0 address fixation allowed //TODO
dim DMABlocked as boolean=false 'dma controller blocked //TODO
dim RotatedPriorities as boolean=false 'channel priority rotating

//DMA register values
dim STA_REG as byte=0 'dma state register
dim TMP_REG as byte=0 'dma temporary register (used as buffer when transferring)

//Extra ports (not used by DMA, but used by BIOS to store temporary values)
use object MEMORY_STREAM as extra_ports : extra_ports.size=16

//8-bit DMA channels
dim ch(4) as object
use object "i8237\channel" as ch0 : ch(0)=ch0 
use object "i8237\channel" as ch1 : ch(1)=ch1
use object "i8237\channel" as ch2 : ch(2)=ch2 
use object "i8237\channel" as ch3 : ch(3)=ch3 
for i=0 to 3 : ch(i).ChIndex=i : ch(i).Priority=i : ch(i).DebugPrefix=DebugPrefix
 ch(i).FlipFlopIndex=ch(i).CommonParams.Add(FlipFlop,"FlipFlip") 'flip-flop for all ch
 ch(i).TempRegIndex=ch(i).CommonParams.Add(TMP_REG,"TempReg") 'temp reg for all ch
next

'------------------------------- H/W Interface --------------------------------

//08H - Write dma controller command
public property CMD_PORT(Value as byte)
 'decode only important settings
 CanMemToMem=(Value and 1)<>0 : CanCh0FixedAddr=(Value and 2)<>0
 DMABlocked=(Value and 4)<>0 : RotatedPriorities=(Value and 0x10)<>0
 ?? DebugPrefix;">CMD_PORT=";Hex(Value,2);"h";
 ?? " M2M=";CanMemToMem;" Ch0FixA=";CanCh0FixedAddr;
 ?? " DMABlock=";DMABlocked;" RotPri=";RotatedPriorities
end

//08H - Read dma controller state (bits 0..3-ch. made transfer, 4..7-ch. DREQ)
public function STATE_PORT as byte 
 result=STA_REG and 0xF : STA_REG=0 : dim i as integer : for i=0 to 3 
 result=result or iif(ch(i).HWRequest,shl(0x10,i),0) : next
 ?? DebugPrefix;"<STATE_PORT=";Hex(result,2)
end

//09H - Write channel DREQ flag (bits 0..1-channel index, 2-DREQ set/clear)
public property CHDREQ_PORT(Value as byte) 'PORT 09h
 ch(Value and 3).SWRequest=(Value and 4)<>0 //TOCHECK - reset h/w DREQ too??
 ?? DebugPrefix;">CHDREQ_PORT=";Hex(Value,2);"h (ch=";Value and 3;
 ?? ", dreq=";(Value and 4)<>0;")"
end

//09H - Unreadable //TOCHECK
public function CHDREQ_PORT as byte 
 result=0xFF : ?? DebugPrefix;"<CHDREQ_PORT=";Hex(result,2)
end

//0AH - Write channel masked flag (bits 0..1-channel index, 2-masked set/clear)
public property CHMASK_PORT(Value as byte)
 ch(Value and 3).Masked=(Value and 4)<>0
 ?? DebugPrefix;">CHMASK_PORT=";Hex(Value,2);"h (ch=";Value and 3;
 ?? ", masked=";(Value and 4)<>0;")"
end

//0AH - Unreadable //TOCHECK
public function CHMASK_PORT as byte 
 result=0xFF : ?? DebugPrefix;"<CHMASK_PORT=";Hex(result,2)
end

//0BH - Write channel mode (bits 0..1-channel index, 2..8 - mode bits)
public property CHMODE_PORT(Value as byte)
 ch(Value and 3).MODE_PORT=Value
 ?? DebugPrefix;">CHMODE_PORT=";Hex(Value,2);"h (ch=";Value and 3;")"
end

//0BH - Unreadable //TOCHECK
public function CHMODE_PORT as byte 
 result=0xFF : ?? DebugPrefix;"<CHMODE_PORT=";Hex(result,2)
end

//0CH - Write any value to reset flip-flop
public property FLIPFLOP_PORT(Value as byte)
 FlipFlop=false
 ?? DebugPrefix;">FLIPFLOP_PORT=";Hex(Value,2);"h (FlipFlop=";FlipFlop;")"
end

//0CH - Unreadable //TOCHECK
public function FLIPFLOP_PORT as byte 
 result=0xFF : ?? DebugPrefix;"<FLIPFLOP_PORT=";Hex(result,2)
end

//0DH - Write any value to reset ("master clear") dma controller
public property RESET_PORT(Value as byte)
 CMD_PORT=0 : TMP_REG=0 : STA_REG=0 : FlipFlop=false
 dim i as integer : for i=0 to 3 : ch(i).SWRequest=false 
 ch(i).HWRequest=false : ch(i).Masked=true : ch(i).Priority=i : next
 ?? DebugPrefix;">RESET_PORT=";Hex(Value,2);"h (master clear)"
end

//0DH - Read temporary register (used as buffer) //TOCHECK
public function TMP_PORT as byte 
 result=TMP_REG : ?? DebugPrefix;"<TMP_PORT=";Hex(result,2)
end

//0EH - Write any value to unmask all channels
public property UNMASKALL_PORT(Value as byte)
 dim i as integer : for i=0 to 3 : ch(i).Masked=false : next
 ?? DebugPrefix;">UNMASKALL_PORT=";Hex(Value,2);"h (unmask all channels)"
end

//0EH - Unreadable //TOCHECK
public function UNMASKALL_PORT as byte 
 result=0xFF : ?? DebugPrefix;"<UNMASKALL_PORT=";Hex(result,2)
end

//0FH - Write masked flags for all channels (bits 0..3 - channel masks)
public property MASK_PORT(Value as byte)
 dim i as integer : for i=0 to 3 : ch(i).Masked=(Value and shl(1,i))<>0 : next
 ?? DebugPrefix;">MASK_PORT=";Hex(Value,2);"h (mask/unmask channels)"
end

//0FH - Read temporary register (used as buffer) //TOCHECK
public function TMP_PORT2 as byte 
 result=TMP_REG : ?? DebugPrefix;"<TMP_PORT=";Hex(result,2)
end

'-------------------------- H/W DMA Channel Interfaces ------------------------

//channel 0
public property CH0_OFFSET_PORT alias ch0.OFFSET_PORT
public function CH0_OFFSET_PORT alias ch0.OFFSET_PORT
public property CH0_COUNT_PORT alias ch0.COUNT_PORT
public function CH0_COUNT_PORT alias ch0.COUNT_PORT
public property CH0_PAGE_PORT alias ch0.PAGE_PORT
public function CH0_PAGE_PORT alias ch0.PAGE_PORT

//channel 1
public property CH1_OFFSET_PORT alias ch1.OFFSET_PORT
public function CH1_OFFSET_PORT alias ch1.OFFSET_PORT
public property CH1_COUNT_PORT alias ch1.COUNT_PORT
public function CH1_COUNT_PORT alias ch1.COUNT_PORT
public property CH1_PAGE_PORT alias ch1.PAGE_PORT
public function CH1_PAGE_PORT alias ch1.PAGE_PORT

//channel 2
public property CH2_OFFSET_PORT alias ch2.OFFSET_PORT
public function CH2_OFFSET_PORT alias ch2.OFFSET_PORT
public property CH2_COUNT_PORT alias ch2.COUNT_PORT
public function CH2_COUNT_PORT alias ch2.COUNT_PORT
public property CH2_PAGE_PORT alias ch2.PAGE_PORT
public function CH2_PAGE_PORT alias ch2.PAGE_PORT

//channel 3
public property CH3_OFFSET_PORT alias ch3.OFFSET_PORT
public function CH3_OFFSET_PORT alias ch3.OFFSET_PORT
public property CH3_COUNT_PORT alias ch3.COUNT_PORT
public function CH3_COUNT_PORT alias ch3.COUNT_PORT
public property CH3_PAGE_PORT alias ch3.PAGE_PORT
public function CH3_PAGE_PORT alias ch3.PAGE_PORT

'---------------------------- H/W All I/O Ports -------------------------------

'Write all standard ports
public property PORTS(Index as word, Value as byte)
 select case Index
  //DMA-1 (channels 0..3)
  case 0x00 : CH0_OFFSET_PORT=Value
  case 0x01 : CH0_COUNT_PORT=Value
  case 0x02 : CH1_OFFSET_PORT=Value
  case 0x03 : CH1_COUNT_PORT=Value
  case 0x04 : CH2_OFFSET_PORT=Value
  case 0x05 : CH2_COUNT_PORT=Value
  case 0x06 : CH3_OFFSET_PORT=Value
  case 0x07 : CH3_COUNT_PORT=Value
  case 0x08 : CMD_PORT=Value
  case 0x09 : CHDREQ_PORT=Value
  case 0x0A : CHMASK_PORT=Value
  case 0x0B : CHMODE_PORT=Value
  case 0x0C : FLIPFLOP_PORT=Value
  case 0x0D : RESET_PORT=Value
  case 0x0E : UNMASKALL_PORT=Value
  case 0x0F : MASK_PORT=Value
  case 0x87 : CH0_PAGE_PORT=Value
  case 0x83 : CH1_PAGE_PORT=Value
  case 0x81 : CH2_PAGE_PORT=Value
  case 0x82 : CH3_PAGE_PORT=Value
  //DMA-2 (channels 4..7)
  case 0xC0 : CH0_OFFSET_PORT=Value
  case 0xC2 : CH0_COUNT_PORT=Value
  case 0xC4 : CH1_OFFSET_PORT=Value
  case 0xC6 : CH1_COUNT_PORT=Value
  case 0xC8 : CH2_OFFSET_PORT=Value
  case 0xCA : CH2_COUNT_PORT=Value
  case 0xCC : CH3_OFFSET_PORT=Value
  case 0xCE : CH3_COUNT_PORT=Value
  case 0xD0 : CMD_PORT=Value
  case 0xD2 : CHDREQ_PORT=Value
  case 0xD4 : CHMASK_PORT=Value
  case 0xD6 : CHMODE_PORT=Value
  case 0xD8 : FLIPFLOP_PORT=Value
  case 0xDA : RESET_PORT=Value
  case 0xDC : UNMASKALL_PORT=Value
  case 0xDE : MASK_PORT=Value
  case 0x8B : CH1_PAGE_PORT=Value
  case 0x89 : CH2_PAGE_PORT=Value
  case 0x8A : CH3_PAGE_PORT=Value
  //EXTRA
  case 0x80 to 0x8F : extra_ports.Byte(Index-0x80)=Value
       ?? DebugPrefix;">EXTRA_PORT_";Hex(Index-0x80,4);"H=";Hex(Value,2);"h"
 end select
end

'Read all standard ports
public function PORTS(Index as word) as byte
 select case Index
  //DMA-1 (channels 0..3)
  case 0x00 : result=CH0_OFFSET_PORT
  case 0x01 : result=CH0_COUNT_PORT
  case 0x02 : result=CH1_OFFSET_PORT
  case 0x03 : result=CH1_COUNT_PORT
  case 0x04 : result=CH2_OFFSET_PORT
  case 0x05 : result=CH2_COUNT_PORT
  case 0x06 : result=CH3_OFFSET_PORT
  case 0x07 : result=CH3_COUNT_PORT
  case 0x08 : result=STATE_PORT
  case 0x09 : result=CHDREQ_PORT
  case 0x0A : result=CHMASK_PORT
  case 0x0B : result=CHMODE_PORT
  case 0x0C : result=FLIPFLOP_PORT
  case 0x0D : result=TMP_PORT
  case 0x0E : result=UNMASKALL_PORT
  case 0x0F : result=TMP_PORT
  case 0x87 : result=CH0_PAGE_PORT
  case 0x83 : result=CH1_PAGE_PORT
  case 0x81 : result=CH2_PAGE_PORT
  case 0x82 : result=CH3_PAGE_PORT
  //DMA-2 (channels 4..7)
  case 0xC0 : result=CH0_OFFSET_PORT
  case 0xC2 : result=CH0_COUNT_PORT
  case 0xC4 : result=CH1_OFFSET_PORT
  case 0xC6 : result=CH1_COUNT_PORT
  case 0xC8 : result=CH2_OFFSET_PORT
  case 0xCA : result=CH2_COUNT_PORT
  case 0xCC : result=CH3_OFFSET_PORT
  case 0xCE : result=CH3_COUNT_PORT
  case 0xD0 : result=STATE_PORT
  case 0xDA : result=TMP_PORT
  case 0x8B : result=CH1_PAGE_PORT
  case 0x89 : result=CH2_PAGE_PORT
  case 0x8A : result=CH3_PAGE_PORT
  //EXTRA
  case 0x80 to 0x8F : result=extra_ports.Byte(Index-0x80)
       ?? DebugPrefix;"<EXTRA_PORT_";Hex(Index-0x80,4);"H=";Hex(result,2);"h"
  case else : result=0xFF
 end select
end

'---------------------------- Local Functions ---------------------------------

//Get channel index to transfer data ( <0  - none )
function FindChannel(byref priority as byte) as integer
 result=-1 : priority=255

 //search requested unmasked channel with highest priority
 //channel masking only disable H/W DREQ, but S/W DREQ non-maskable
 if ((ch0.HWRequest and not ch0.Masked) or (ch0.SWRequest)) and _
    (ch0.Priority<priority) then : result=0 : priority=ch0.Priority : end if
 if ((ch1.HWRequest and not ch1.Masked) or (ch1.SWRequest)) and _
    (ch1.Priority<priority) then : result=1 : priority=ch1.Priority : end if
 if ((ch2.HWRequest and not ch2.Masked) or (ch2.SWRequest)) and _
    (ch2.Priority<priority) then : result=2 : priority=ch2.Priority : end if
 if ((ch3.HWRequest and not ch3.Masked) or (ch3.SWRequest)) and _
    (ch3.Priority<priority) then : result=3 : priority=ch3.Priority : end if
end 

//Rotate priorities (if needed)
procedure RotatePriorities(priority as byte)
 if RotatedPriorities then
    if ch0.Priority=priority then : ch0.Priority=3 : elseif _
       ch0.Priority>priority then : ch0.Priority=ch0.Priority-1 : end if
    if ch1.Priority=priority then : ch1.Priority=3 : elseif _
       ch1.Priority>priority then : ch1.Priority=ch1.Priority-1 : end if
    if ch2.Priority=priority then : ch2.Priority=3 : elseif _
       ch2.Priority>priority then : ch2.Priority=ch2.Priority-1 : end if
    if ch3.Priority=priority then : ch3.Priority=3 : elseif _
       ch3.Priority>priority then : ch3.Priority=ch3.Priority-1 : end if
end if : end

//Transfer event handler
function TransferEvent(freq as integer,eventid as dword) as boolean
 dim priority as byte

 result=true 'need next event call
 

 //TODO  CanMemToMem?  CanCh0FixedAddr? DMABlocked?

 dim ch as integer = FindChannel(priority)  : if ch>=0 then

 //transferring
 select case ch
 case 0: if ch0.Transfer then : STA_REG=STA_REG or 1 
         RotatePriorities(priority) : end if
 case 1: if ch1.Transfer then : STA_REG=STA_REG or 2
         RotatePriorities(priority) : end if
 case 2: if ch2.Transfer then : STA_REG=STA_REG or 4
         RotatePriorities(priority) : end if
 case 3: if ch3.Transfer then : STA_REG=STA_REG or 8
         RotatePriorities(priority) : end if
 end select

end if : end

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

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

 'parent call
 if not DEV_INIT(stream,EventFreq) then exit(false)
 if EventFreq<2000 then EventFreq=2000 
 //fill extra-ports registers
 for i=0 to extra_ports.size-1 : extra_ports.Byte(i)=0xFF : next
 //register transfer event
 pc.NewFreqEvent(2000)=TransferEvent //2000Hz

 //success
 result=true
end

