'******************************************************************************
'* 
'* FLOPPY DISK CONTROLLER i8272(A) v.1.0 (CHIP DEVICE)
'*
'* Supported platform/bus: X86
'* 
'* Version history:
'*  - v.1.0 by WadiM (initial emulation)
'*
'****************************************************************************** 

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

//ISA device interface support
public use object DEVICE_ISA

//device name
DeviceName="FDC i8272(A)"
DebugName="FDC_I8272" 

//buffers
use object FIFO_STREAM as out_buffer 'data buffer to read from data port

//Controlled disks and their current states
const MAX_DISK_COUNT as byte=4
dim fdc_disks(MAX_DISK_COUNT) as object
dim motor(MAX_DISK_COUNT) as boolean 	'states of motors of disks (on/off)
dim cylinder(MAX_DISK_COUNT) as integer 'cylinder indexes
dim head(MAX_DISK_COUNT) as integer 	'head indexes
dim sector(MAX_DISK_COUNT) as integer 	'sector indexes

//Constants
const STATE_BUSY_FLAG as byte=0x10 'FDC busy
const STATE_DIO_FLAG as byte=0x40 'flag of i/o direction (0-CPU>FDC, 1-FDC>CPU)
const STATE_RQM_FLAG as byte=0x80 'ready to commands/answers (0-n/ready, 1-ready)

//Parameters
dim Disk as object 'current disk
dim DiskIndex as byte=0         	'index of current disk/drive
dim FDCEnabled as boolean=true  	'is controller enabled
dim IOEnabled as boolean=true   	'is DMA/IRQ modes enabled
dim DMAEnabled as boolean=false 	'is DMA enabled
dim IRQEnabled as boolean=true  	'is IRQ enabled
dim CMDIndex as integer=-1         	'index of current command (<0 - no)

//Values of controller registers
dim DATA_Reg as byte=0 			'data register
dim STATE_Reg as byte=0 		'main status register
dim STO0 as byte=0                      'status register

'------------------------------- Events ---------------------------------------

//Motor event (timed event of motor on/off)
dim motor_eventid as dword=0 'to discard deprecated events
function MotorEvent(freq as integer, eventid as dword) as boolean
 result=false 'no need to call it again
 if eventid=motor_eventid then //event is not deprecated
    STATE_Reg=STATE_Reg or STATE_RQM_FLAG 'ready
    if IRQEnabled then pc.SetIRQ(DEVPARAM_IRQ,true) 'IRQ on recalibration
 //TODO
end if : end

dim irq_eventid as dword=0 'to discard deprecated events
function IRQEvent(freq as integer, eventid as dword) as boolean
 result=false 'no need to call it again
 if eventid=irq_eventid then //event is not deprecated
    if IRQEnabled then pc.SetIRQ(DEVPARAM_IRQ,true) 'IRQ on recalibration
end if : end

'---------------------------------- Tools -------------------------------------

//Drive symbol by index (0=A,1=B,2=C,3=D)
function Symb(i as byte) as string
 result=iif(i=0,"A",iif(i=1,"B",iif(i=2,"C",iif(i=3,"D","?")))) : end

//On/off disk motor
procedure SetMotorState(index as byte, value as boolean)
 if (index>=MAX_DISK_COUNT) or (index>3) then exit 
 if Index<>DiskIndex then 'simply flag switch for non-current drives
    Motor(Index)=value
 else 'timed for current drive
    motor_eventid=motor_eventid+1
    if Motor(Index)<>value then
       STO0=DiskIndex or shl(Head(DiskIndex) and 1,2)
       pc.NewFreqEvent(4,motor_eventid)=MotorEvent '1/4 sek
    end if 
end if : end

//Switch drive
procedure SelectDrive(Index as byte)
 //TODO
 DiskIndex=Index : Disk=fdc_disks(DiskIndex) 'select disk/drive
end

//Reset controller
procedure ResetFDC
 dim i as integer
 DATA_Reg=0 : STATE_Reg=0 : CMDIndex=-1 : STO0=0
 out_buffer.size=0
 //TODO
 motor_eventid=motor_eventid+1 'disable active motor event
 DiskIndex=0 : FDCEnabled=false : IOEnabled=false
 DMAEnabled=false : IRQEnabled=false
 for i=0 to MAX_DISK_COUNT-1
  motor(i)=false : cylinder(i)=0
 next
end

'-------------------------------- S/W Interface -------------------------------

//Set disk objects
public property Disks(Index as byte, Value as object) 
 if Index>=MAX_DISK_COUNT then Error(DeviceName+" is not support more then "+ _
    Str(MAX_DISK_COUNT)+" disks") 
 fdc_disks(Index)=Value : if DiskIndex=Index then Disk=Value
 ?? DebugPrefix;"Disk ";Symb(Index);"=";iif(IsEmpty(Value),"0",Value.Size);" Kb"
end

//Get disk objects
public function Disks(Index as byte) as object
 if Index>=MAX_DISK_COUNT then Error(DeviceName+"is not support more then 4 disks")
 result=fdc_disks(Index)
end

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

//Mode port (DOR, write-only)
public property MODE_PORT(Value as byte) 
 dim i as integer
 //drive selection
 i=Value and 3 : if DiskIndex<>i then SelectDrive(i)
 //controller enabled
 FDCEnabled=(Value and 4)<>0 
 //i/o operations enabling
 IOEnabled=(Value and 8)<>0 
 DMAEnabled=(IOEnabled) and (DEVPARAM_DMA>=0)
 IRQEnabled=(IOEnabled) and (DEVPARAM_IRQ>=0)
 'motor states
 SetMotorState(0,(Value and 0x10)<>0) : SetMotorState(1,(Value and 0x20)<>0)  
 SetMotorState(2,(Value and 0x40)<>0) : SetMotorState(3,(Value and 0x80)<>0) 
 'debug messages
 ?? DebugPrefix;">MODE_PORT=";Hex(Value,2);"h: disk=";Symb(DiskIndex);
 ?? ",IRQ/DMA=";iif((Value and 8)<>0,"yes","no");
 ?? ",motors=";Hex(shr(Value and 0xF0,4),1);
 'reset controller if asked, or activate it 
 if not FDCEnabled then : ResetFDC : ?? ",reset and off" : else ?? ",activate"
    SetMotorState(DiskIndex,true) //TOCHECK - start drive???
 end if
end

//Data rate port (write-only)
public property RATE_PORT(Value as byte) 
 ?? DebugPrefix;">RATE_PORT=";Hex(Value,2);"h"
 //TODO
end

//Read controller state (read-only)
public function STATE_PORT as byte 
 result=STATE_Reg 
 ?? DebugPrefix;"<STATE_PORT=";Hex(result,2);"h"
// dbg.Break
end

//Write controller command or data
public property DATA_PORT(Value as byte)
 //new command detection
 if CMDIndex<0 then 
   out_buffer.size=0 'clear output buffer
   ?? DebugPrefix;">CMD_PORT=";Hex(Value,2);"h ";
   select case Value
   case 8: ?? "Sence Interrupt Cmd (result=STO0,Cyl)"
     out_buffer.PushByte(STO0) : out_buffer.PushByte(cylinder(DiskIndex))
     STATE_Reg=(STATE_Reg and 0xF) or STATE_DIO_FLAG or STATE_RQM_FLAG or STATE_BUSY_FLAG
   case else : ?? "(unknown command)"
   end select
 else
   ?? DebugPrefix;">DATA_PORT=";Hex(Value,2);"h"
 end


//    ASWSize=0 : STATE=STATE or STATE_DIO_FLAG or STATE_RQM_FLAG
//    if CMD(0)=0x08 then CMD_InterruptSence
end

//Read controller command or data
public function DATA_PORT as byte 
//    STATE=(STATE and (not(STATE_DIO_FLAG))) or STATE_RQM_FLAG
 if out_buffer.size>0 then 
    DATA_Reg=out_buffer.PopByte
    ?? "DATA_Reg=";DATA_Reg
    if out_buffer.size=0 then 
       STATE_Reg=(STATE_Reg and (not(STATE_DIO_FLAG or STATE_BUSY_FLAG))) or STATE_RQM_FLAG
    end
 end
 result=DATA_Reg
 //dbg.Break
 ?? DebugPrefix;"<DATA_PORT=";Hex(result,2);"h"
end

//    'if all ops completed, update state
//   if OPCount<=0 then STATE=STATE and (not 0x1F) or 0x80

'---------------------------- 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)
 if EventFreq<2000 then EventFreq=2000

 //initial power up and reset controller
 ResetFDC
 //StateValue=StateValue or FLAG_STATE_BUSY
 //pc.NewFreqEvent(2000)=CMD_Reset '0.5 ms

 //success
 result=true

end 


//Device finalization
public procedure DEV_DONE(stream as object)

 //parent call
 DEV_DONE(stream)

end
 
