'******************************************************************************
'* 
'* PC/AT CMOS/RTC v.1.0 (GENERIC DEVICE)
'*
'* CMOS is battery-backed memory, used by BIOS and RTC
'*
'* Supported platform/bus: X86/ISA
'* 
'* Version history:
'*  - v.1.0 by WadiM (initial emulation)
'*
'* H/W Interface: 
'*  - INDEX_PORT - r/w byte (index of cmos cell, 0..6 bits of port 70h)
'*  - VALUE_PORT - r/w byte (value of cmos cell, )
'*
'* S/W Interface: 
'*  - Size - r/w word (size of cmos in bytes, typically 64 or 128)
'*  - Cell(index) - r/w byte (values of cmos cells)
'*
'****************************************************************************** 

//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="PC/AT CMOS"
DebugName="PCAT_CMOS"

'--------------------------------- Common data --------------------------------

//Constants
const MAX_SIZE as word=128

//Cmos cells
use object MEMORY_STREAM as cells : cells.Size=iif(256<=MAX_SIZE,256,MAX_SIZE)

//Event IDs (used to stop deprecated events)
dim time_eventid as dword=0 'of time update event
dim alarm_eventid as dword=0 'of alarm event
dim period_eventid as dword=0 'of periodic event

//Variables
dim IntAsked as boolean=false 'interrupt acknowledge flag

//RTC time update event 
function TIMEEvent(freq as integer,eventid as dword) as boolean
 if eventid<>time_eventid then : result=false : exit : end 'deprecated
 result=true 'need next event
 if (cells.Byte(0xB) and 0x80)=0 then 'updates allowed
  if (cells.Byte(0xA) and 0x80)=0 then 'update cycle started
    cells.Byte(0xA)=cells.Byte(0xA) or 0x80 : exit : else
    cells.Byte(0xA)=cells.Byte(0xA) and 0x7F : end if 'update cycle ended
  if (cells.Byte(0xB) and 4)<>0 then 'binary number format
    ?? DebugPrefix;"Time update event (binary date format)"
    cells.Byte(0)=cells.Byte(0)+1 'seconds increment
    if cells.Byte(0)>59 then 'seconds overlow
       cells.Byte(0)=0 'seconds restart
       cells.Byte(2)=cells.Byte(2)+1 'minutes increment
       if cells.Byte(2)>59 then 'minutes overlow
          cells.Byte(2)=0 'minutes restart
          cells.Byte(4)=cells.Byte(4)+1 'hours increment
          if cells.Byte(4)>23 then 'hours (24) overlow
             cells.Byte(4)=0 'hours restart
             //TODO - days, months, years (if it nesessary :)
          end if
       end if
    end if
  else 'BCD number format (default on PC compatible computers)
    ?? DebugPrefix;"Time update event (BCD date format)"
    dim b as byte=cells.Byte(0)+1 'seconds increment
    if (b and 0xF)>9 then : cells.Byte(0)=(cells.Byte(0) and 0xF0)+0x10
       b=shr(cells.Byte(0),4) : else : cells.Byte(0)=b : b=0 : end if
    if b>5 then 'seconds overlow >59
       cells.Byte(0)=0 'seconds restart
       //TODO - minutes, hours, days, months, years (if it nesessary :)
    end if
  end if
 end if 
 cells.Byte(0xC)=cells.Byte(0xC) or 0x10 'interrupt occured flag
 if not(IntAsked) then exit 'previous int was not acknowledged
 if (cells.Byte(0xB) and 0x10)<>0 then 'IRQ allowed
    cells.Byte(0xC)=cells.Byte(0xC) or 0x80 
    if pc.SetIRQ(DEVPARAM_IRQ,true) then IntAsked=false 'send IRQ
 end if
end

//RTC alarm event 
function ALARMEvent(freq as integer,eventid as dword) as boolean
 if eventid<>alarm_eventid then : result=false : exit : end 'deprecated
 result=true 'need next event
 if ((cells.Byte(0)=cells.Byte(1)) or (cells.Byte(1)=0xFF)) and _
    ((cells.Byte(2)=cells.Byte(3)) or (cells.Byte(3)=0xFF)) and _
    ((cells.Byte(4)=cells.Byte(5)) or (cells.Byte(5)=0xFF)) then
    cells.Byte(0xC)=cells.Byte(0xC) or 0x20 'interrupt occured flag
    if not(IntAsked) then exit 'previous int was not acknowledged
    if (cells.Byte(0xB) and 0x20)<>0 then 'IRQ allowed
       cells.Byte(0xC)=cells.Byte(0xC) or 0x80 
       if pc.SetIRQ(DEVPARAM_IRQ,true) then IntAsked=false 'send IRQ
    end if
 end if
end

//RTC periodic event 
function PERIODEvent(freq as integer,eventid as dword) as boolean
 if eventid<>period_eventid then : result=false : exit : end 'deprecated
 result=true 'need next event
 cells.Byte(0xC)=cells.Byte(0xC) or 0x40 'interrupt occured flag
 if not(IntAsked) then exit 'previous int was not acknowledged
 if (cells.Byte(0xB) and 0x40)<>0 then 'IRQ allowed
    cells.Byte(0xC)=cells.Byte(0xC) or 0x80 
    if pc.SetIRQ(DEVPARAM_IRQ,true) then IntAsked=false 'send IRQ
 end if
end

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

//Index of cmos cell (read/write) 
//public dim INDEX_PORT as byte

dim INDEX_PORT2 as byte=0

public property INDEX_PORT(value as byte)
 INDEX_PORT2=value 
 ?? DebugPrefix;">INDEX_PORT=";Hex(Value)
end

public function INDEX_PORT as byte
 result=INDEX_PORT2
 ?? DebugPrefix;"<INDEX_PORT=";Hex(INDEX_PORT2)
end


//Value of cmos cell (write)
public property VALUE_PORT(value as byte)
 select case INDEX_PORT2 'analize cells to do actions
 case 0xA 'state port A
   value=(value and 0x7F) or (cells.Byte(0xA) and 0x80) 'preserve read-only bit
   //TODO - bits 6-4 is a divider of 32.768 KHz frequency (default 010)
   //periodic interrupt rate (bits 3-0, default 0110)
   if (cells.Byte(0xA) and 0x0F)<>(value and 0x0F) then 'periodic interrupt changed
      period_eventid=period_eventid+1 'remove deprecated interrupt
      dim w as word : select case value and 0x0F
      case 0 : w=0
      case 1 : w=256
      case 2 : w=128
      case 3 : w=8192
      case 4 : w=4096
      case 5 : w=2048
      case 6 : w=1024
      case 7 : w=512
      case 8 : w=256
      case 9 : w=128
      case 10 : w=64
      case 11 : w=32
      case 12 : w=16
      case 13 : w=8
      case 14 : w=4
      case 15 : w=2
      end select
      if w<>0 then pc.NewFreqEvent(w,period_eventid)=PERIODEvent 'set event
   end if
   IntAsked=true 'TOCHECK???
 case 0xB 'state port B
   if (value and 0x80)<>0 then 'abort updates 
      cells.Byte(0xA)=cells.Byte(0xA) and 0x7F 'clear updates flag
      value=value and not (0x10) 'clear update interrupt flag
   end if
//     value=value and 0x7F //TOCHECK
   IntAsked=true 'TOCHECK???
 end select
 if INDEX_PORT2<cells.Size then cells.Byte(INDEX_PORT2)=value
 ?? ">[CMOS] CELL[";Hex(INDEX_PORT2,2);"]=";Hex(value,2)
end

//Value of cmos cell (read)
public function VALUE_PORT as byte
 result=iif(INDEX_PORT2<cells.Size,cells.Byte(INDEX_PORT2),0xFF)
 select case INDEX_PORT2 'analize cells to do actions
 case 0xC 'state port C
  result=cells.Byte(0xC) : cells.Byte(0xC)=0 : IntAsked=true 'clear after read
 case 0xD 'state port D
  result=0x80 'power was not lost for CMOS
 end select
 ?? "<[CMOS] CELL[";Hex(INDEX_PORT2,2);"]=";Hex(result,2)
end

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

//Size of cmos in bytes (write)
public property Size(value as word)
 cells.Size=iif(value<=MAX_SIZE,value,MAX_SIZE)
end

//Size of cmos in bytes (read) 
public function Size as word
 result=cells.Size
end

//Value of cmos cell (write)
public property Cell(index as byte, value as byte)
 if index<cells.Size then cells.Byte(index)=value
end

//Value of cmos cell (read)
public function Cell(index as byte) as byte
 result=iif(index<cells.Size,cells.Byte(index),0xFF)
end

'---------------------------- 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 'basically enought
 
 'clear cells (must be generated/loaded externally)
 dim i : for i=0 to cells.Size-1 : cells.Byte(i)=0 : next : result=true

 'events (time update and alarm, periodic event programmed externally)
 pc.NewFreqEvent(2,time_eventid)=TIMEEvent 'time update event
 pc.NewFreqEvent(1,alarm_eventid)=ALARMEvent 'set event

 'success
 result=true

end
