*------------------------------------------------------------
* Clock module for CoCo 3 and TC9 OS9 Level 2 Version 02.00.01
*
* Includes support for several different RTC chips, GIME Toggle
* IRQ fix, numerous minor changes.
*
* Based on Microware/Tandy Clock Module for CC3/L2
*
         nam Clock
         ttl CoCo 3 & TC-9 Level 2 Version 02.00.01 OS-9

         ifp1
         use  defsfile
         endc

Edtn     equ 9
Vrsn     equ 3
*------------------------------------------------------------
*
* Conditional Assembly flags:
*   Only one of the below can be true.  If all are false,
* software clock will be used.
*
RTCElim  equ false    true = Eliminator RTC (MC146818/DS1287)
RTCDsto2 equ false    true = Disto 2-in-1 RTC
RTCDsto4 equ false    true = Disto 3-in-1/4-in-1 RTC (Oki M6242B)
RTCBB    equ false    true = Burke & Burke RTC (Dallas Semiconductor DS1215)
RTCSmart equ false    true = Smartwatch RTC (Must have <= 512k)
*                            This driver beats on the GIME, so won't work
*                            for > 512k with the Smartwatch, yet
*                            (Dallas Semiconductor DS1216E)
RTCHarrs equ false    true = Harris ICM7170
*
* These variables affect the operation of the RTC-reading code
*
EveryMin equ true    true = read RTC every minute; false = every second
MPIFlag  equ false   true = use MPI slot selection
*MPI.Slot equ 4       default slot is 4 (Slot numbers 1-4).

* If no hardware RTC was specified, then set RTCSoft to do software
* clock updating
*
         ifne RTCElim+RTCDsto2+RTCDsto4+RTCBB+RTCSmart+RTCHarrs
RTCSoft  equ  false   RTC was specified, set to false
         else
RTCSoft  equ  true    No hardware RTC, do software updating
         endc

*
* To add support for a new RTC....
* - Add new RTCxxx switch above, update RTCSoft condition
* - Add setup below (MUST define RTC.Base)
* - Add UpdTime routine
* - Add RTC set routine
* - Add initialization routine, if needed
*
* UpdTime and RTC Set routines should use M$Mem,pcr to get the
* base address, and get MPI slot value from SlotSlct,pcr
*

*
* regular equates
*
TkPerTS  equ 2 ticks per time slice
GI.Toggl equ %00000001 GIME CART* IRQ enable bit, for CC3

* TC9 needs to reset more interrupt sources
*GI.Toggl equ %00000111 GIME SERINT*, KEYINT*, CART* IRQ enable bits

*PIA0Base equ $FF00
*MPI.Slct equ $FF7F

         org $100F
Al.Time  equ .     Alarm variables
Al.Year  rmb 1
Al.Month rmb 1
Al.Day   rmb 1
Al.Hour  rmb 1
Al.Min   rmb 1
Al.Sec   rmb 1  Not used!
Al.PID   rmb 1
Al.Signl rmb 1
BellVct  rmb 2

*
* Setup for specific RTC chip
*
         ifne RTCElim
RTC.Sped equ $20 32.768 KHz, rate=0
RTC.Strt equ $06 binary, 24 Hour, DST disabled
RTC.Stop equ $86 bit 7 set stops clock to allow setting time
RTC.Base equ 0  I don't know base for this chip.
F$NVRAM  equ $FF    What is correct code?
         endc

         ifne RTCDsto2+RTCDsto4
RTC.Base equ $FF50  Base address of clock
         endc

         ifne RTCBB
RTC.Base equ $FF5C
RTC.Zero equ -4     Send zero bit by writing this offset
RTC.One  equ -3     Send one bit by writing this offset
RTC.Read equ 0      Read data from this offset
         endc

         ifne RTCSmart
RTC.Base equ $4004  We map the clock into this addr
RTC.Zero equ -4     Send zero bit by writing this offset
RTC.One  equ -3     Send one bit by writing this offset
RTC.Read equ 0      Read data from this offset
         endc

         ifne RTCHarrs
RTC.Base equ $FF60  Base address for clock
         endc

         ifne RTCSoft
*RTC.Base equ 0      Have to have one defined.
         endc

*------------------------------------------------------------
*
* Start of module
*
         mod len,name,Systm+Objct,ReEnt+Vrsn,Init,RTC.Base

name     fcs "Clock"
         fcb Edtn

         ifne MPIFlag
SlotSlct fcb MPI.Slot-1   Slot constant for MPI select code
         endc

*
* Table to set up Service Calls:
*
NewSvc   fcb F$Time
         fdb F.Time-*-2
         fcb F$VIRQ
         fdb F.VIRQ-*-2
         fcb F$Alarm
         fdb F.Alarm-*-2
         fcb F$STime
         fdb F.STime-*-2

         ifne RTCElim
         fcb F$NVRAM    Eliminator adds one new service call
         fdb F.NVRAM-*-2
         endc

         fcb $80 end of service call installation table

*---------------------------------------------------------
* IRQ Handling starts here.
*
* Caveat: There may not be a stack at this point, so avoid using one.
*         Stack is set up by the kernel between here and SvcVIRQ.
*
SvcIRQ   lda  >IRQEnR Get GIME IRQ Status and save it.
         ora  <D.IRQS
         sta  <D.IRQS
         bita #$08    Check for clock interrupt
         beq  NoClock
         anda #^$08   Drop clock interrupt
         sta  <D.IRQS
         ldx  <D.VIRQ Set VIRQ routine to be executed
         bra  ContIRQ
NoClock  leax DoPoll,pcr  If not clock IRQ, just poll IRQ source
ContIRQ  stx  <D.SvcIRQ
         jmp  [D.XIRQ]  Chain through Kernel to continue IRQ handling

*------------------------------------------------------------
*
* IRQ handling re-enters here on VSYNC IRQ.
*
* - Count down VIRQ timers, mark ones that are done
* - Call DoPoll/DoToggle to service VIRQs and IRQs and reset GIME
* - Call Keyboard scan
* - Update time variables
* - At end of minute, check alarm
*
SvcVIRQ  clra            Flag if we find any VIRQs to service
         pshs  a
         ldy   <D.CLTb   Get address of VIRQ table
         bra   virqent

virqloop ldd   Vi.Cnt,x       Decrement tick count
         subd  #1
         bne   notzero    Is this one done?
         lda   Vi.Stat,x  Should we reset?
         bmi   doreset
         lbsr  DelVIRQ    No, delete this entry
doreset  ora   #$01       Mark this VIRQ as triggered.
         sta   Vi.Stat,x
         lda   #$80       Add VIRQ as interrupt source
         sta   ,s
         ldd   Vi.Rst,x   Reset from Reset count.
notzero  std   Vi.Cnt,x
virqent  ldx   ,y++
         bne   virqloop

         puls  a            Get VIRQ status flag: high bit set if VIRQ
         ora   <D.IRQS      Check to see if other hardware IRQ pending.
         bita  #%10110111   Any V/IRQ interrupts pending?
         beq   toggle
         bsr   DoPoll       Yes, go service them.
         bra   KbdCheck
toggle   bsr   DoToggle     No, toggle GIME anyway
KbdCheck jsr   [>D.AltIRQ]  Check Keyboard

         dec   <D.Tick   End of second?
         bne   VIRQend   No, skip time update and alarm check
         lda   #TkPerSec Reset tick count
         sta   <D.Tick

         ifne  RTCSoft+EveryMin Always update on minute if software clock
         inc   <D.Sec
         lda   <D.Sec
         cmpa  #60       End of minute?
         blo   VIRQend   No, skip time update and alarm check
         clr   <D.Sec    Reset second count
         endc

         lbsr  UpdTime   Call UpdTime once per minute (or second)

         ldd   >Al.PID
         ble   VIRQend   Quit if no Alarm set
         ldd   >Al.Hour  Does Hour/Minute agree?
         cmpd  <D.Hour
         bne   VIRQend
         ldd   >Al.Month Does Month/Day agree?
         cmpd  <D.Month
         bne   VIRQend
         ldb   >Al.Year  Does Year agree?
         cmpb  <D.Year
         bne   VIRQend
         ldd   >Al.PID
         cmpd  #1
         beq   checkbel
         os9   F$Send   
         bra   endalarm
checkbel ldb   <D.Sec    Sound bell for 15 seconds
         andb  #$F0
         beq   dobell
endalarm ldd   #$FFFF
         std   >Al.PID
         bra   VIRQend
dobell   ldx   >BellVct
         beq   VIRQend
         jsr   ,x
VIRQend  jmp   [>D.Clock] Jump to kernel's timeslice routine

*------------------------------------------------------------
* Interrupt polling and GIME reset code
*

*
* Call [D.Poll] until all interrupts have been handled
*
DoPoll   jsr   [>D.Poll]    Call poll routine
         bcc   DoPoll       Until error (error -> no interrupt found)
*
* Reset GIME to avoid missed IRQs
*
DoToggle lda   #^GI.Toggl   Mask off CART* bit
         anda  <D.IRQS
         sta   <D.IRQS
         lda   <D.IRQER     Get current enable register status
         tfr   a,b
         anda  #^GI.Toggl   Mask off CART* bit
         sta   >IRQEnR      Disable CART
         stb   >IRQEnR      Enable CART
         clrb
         rts

*------------------------------------------------------------
*
* Update time subroutines
*
*   The subroutine UpdTime is called once per minute.  On systems
* with an RTC, UpdTime reads the RTC and sets the D.Time variables.
*

*
* Eliminator time update  (lacks MPI slot select ability)
*
         ifne RTCElim
UpdTime  ldx M$Mem,pcr get RTC base address from fake memory requirement
         ldb #$0A UIP status register address
         stb ,x generate address strobe
         lda 1,x get UIP status
         bpl NoUIP   Update In Progress, go shift next RTC read
         lda #TkPerSec/2 set up next RTC read attempt in 1/2 second
         sta <D.Tick save tick
         bra UpdTExit and return
NoUIP    decb year register address
         stb ,x generate address strobe
         lda 1,x get year
         sta <D.Year
         decb month register address
         stb ,x
         lda 1,x
         sta <D.Month
         decb day of month register address
         stb ,x
         lda 1,x
         sta <D.Day
         ldb #4 hour register address
         stb ,x
         lda 1,x
         sta <D.Hour
         ldb #2 minute register address
         stb ,x
         lda 1,x
         sta <D.Min
         clrb second register address
         stb ,x
         lda 1,x
SaveSec  sta <D.Sec
UpdTExit rts
         endc

*
* Disto 2-in-1 RTC time update
*
         ifne RTCDsto2
UpdTime  pshs a,cc       Save old interrupt status and mask IRQs
         bsr  RTCPre

         bsr  GetVal   Get Year
         bsr  GetVal   Get Month
         bsr  GetVal   Get Day
         decb          ldb #5
         stb  2,x
         decb
         lda  ,x
         anda #3
         bsr  GetVal1  Get Hour
         bsr  GetVal   Get Minute
         bsr  GetVal   Get Second

RTCPost  clr  >$FFD9   2 MHz  (Really should check $A0 first)
         puls cc,b

         ifne MPIFlag
         stb  >MPI.Slct  Restore saved "currently" selected MPak slot
         endc

         clrb
         rts

RTCPre   orcc #IntMasks

         ifne MPIFlag
         ldb  >MPI.Slct  Save currently selected MPak slot on stack
         stb  3,s
         andb #$F0
         orb  >SlotSlct,pcr Get slot to select
         stb  >MPI.Slct     Select MPak slot for clock
         endc

         ldy  #D.Time
         ldx  M$Mem,pcr
         clr  1,x
         ldb  #12
         clr  >$FFD8   1 MHz
         rts

GetVal   stb  2,x
         decb
         lda  ,x      read tens digit from clock
         anda #$0f
GetVal1  pshs b       save b
         ldb  #10
         mul          multiply by 10 to get value
         stb  ,y      save 10s value
         puls b       set up clock for ones digit
         stb  2,x
         decb
         lda  ,x      read ones digit from clock
         anda #$0f
         adda ,y      add ones + tens
         sta  ,y+     store clock value into time packet
         rts

         endc

*
* Disto 4-in-1 RTC time update
*
         ifne RTCDsto4
UpdTime  equ  *
         ifne MPIFlag
         pshs cc    Save old interrupt status and mask IRQs
         orcc #$50
         ldb  >MPI.Slct  Save currently selected MPak slot on stack
         pshs b
         andb #$F0
         orb  >SlotSlct,pcr Select MPak slot for clock
         stb  >MPI.Slct
         endc

         ldx M$Mem,pcr
         ldy #D.Time    Start with seconds
         
         ldb #11
         bsr GetVal   Get Year
         bsr GetVal   Get Month
         bsr GetVal   Get Day
         lda #3       Mask tens digit of hour to remove AM/PM bit
         bsr GetVal1  Get Hour
         bsr GetVal   Get Minute
         bsr GetVal   Get Second

         ifne MPIFlag
         puls b       Restore saved "currently" selected MPak slot
         stb >MPI.Slct
         puls cc,pc   Restore previous IRQ status
         else
         rts          No MPI, don't need to mess with slot, CC
         endc

GetVal   lda  #$0f     Mask to apply to tens digit
GetVal1  stb  1,x
         decb
         anda  ,x       read ones digit from clock
         pshs b        save b
         ldb  #10
         mul           multiply by 10 to get value
         stb  ,y       Add to ones digit
         puls b
         stb  1,x
         decb
         lda  ,x       read tens digit from clock and mask it
         anda #$0f
         adda ,y
         sta  ,y+
         rts

         endc


*
* Update time from B&B RTC
*
         ifne RTCBB
UpdTime  pshs  u,y,cc
         leay  ReadBCD,pcr   Read bytes of clock

TfrTime  orcc  #$50       turn off interrupts
         ldu   M$Mem,pcr  Get base address

         ifne  MPIFlag
         ldb   >MPI.Slct  Select slot
         pshs  b
         andb  #$F0
         orb   SlotSlct,pcr
         stb   >MPI.Slct
         endc

         lbsr  SendMsg   Initialize clock
         ldx   #D.Sec
         ldb   #8        Tfr 8 bytes

tfrloop  jsr   ,y        Tfr 1 byte

         bitb  #$03
         beq   skipstuf  Skip over day-of-week, etc.
         leax  -1,x
skipstuf decb
         bne   tfrloop

         ifne  MPIFlag
         puls  b
         stb   >MPI.Slct     restore MPAK slot
         endc

         puls  u,y,cc,pc

*ClkMsg   fcb   $C5,$3A,$A3,$5C,$C5,$3A,$A3,$5C
* Enable clock with message $C53AA35CC53AA35C
SendMsg  lda   RTC.Read,u     Send Initialization message to clock
         leax  <ClkMsg,pcr
         ldb   #8
msgloop  lda   ,x+
         bsr   SendByte
         decb
         bne   msgloop
         rts

SendBCD  pshs  b          Send byte to clock, first converting to BCD
         bitb  #$03
         bne   BCDskip    Send zero for day-of-week, etc.
         lda   #0
         bra   SndBCDGo
BCDskip  lda   ,x
SndBCDGo tfr   a,b
         bra   binenter
binloop  adda  #6
binenter subb  #10
         bhs   binloop
         puls  b
SendByte coma             Send one byte to clock
         rora
         bcc   sendone
sendzero tst   RTC.Zero,u
         lsra
         bcc   sendone
         bne   sendzero
         rts
sendone  tst   RTC.One,u
         lsra
         bcc   sendone
         bne   sendzero
         rts


ReadBCD  pshs  b
         ldb   #$80    High bit will rotate out after we read 8 bits
readbit  lda   RTC.Read,u  Read a bit
         lsra  
         rorb          Shift it into B
         bcc   readbit Stop when marker bit appears
         tfr   b,a
         bra   BCDEnter  Convert BCD number to Binary
BCDLoop  subb  #6       by subtracting 6 for each $10
BCDEnter suba  #$10
         bhs   BCDLoop
         stb   ,x
         puls  b,pc

         endc


*
* Update time from Smartwatch RTC
*
         ifne RTCSmart
UpdTime  pshs  cc
         orcc  #IntMasks     Disable interrupts
         lda   >MPI.Slct     Get MPI slot
         ldb   <$90          Get GIME shadow of $FF90
         pshs  b,a
         anda  #$F0
         ora   >SlotSlct,pcr Get new slot to select
         anda  #$03          *** TEST ***
         sta   >MPI.Slct     And select it
         andb  #$FC
         stb   >$FF90        ROM mapping = 16k internal, 16k external
         ldb   >$FFA2        Read GIME for $4000-$5fff
         pshs  b
         lda   #$3E
         sta   >$FFA2        Put block $3E at $4000-$5fff
         clr   >$FFDE        Map RAM/ROM, to map in external ROM
         lbsr  SendMsg       Initialize clock
         ldx   #D.Sec        Start with seconds
         lda   #$08
         sta   ,-s           Set up loop counter = 8
L021E    ldb   #$08
L0220    lda   >RTC.Read+RTC.Base     Read one bit
         lsra  
         ror   ,x            Put bit into time
         decb                End of bit loop
         bne   L0220
         lda   ,s            Check loop counter
         cmpa  #$08
         beq   L023D         Fill "seconds" twice (ignore 1st value)
         cmpa  #$04
         bne   L0239
         ldb   ,x            Save 4th value read at $34 (day of week?)
         stb   $0A,x
         bra   L023D         And overwrite "day" with day
L0239    leax  -$01,x        Go to next time to read
         bsr   BCD2Dec       Convert 1,x from BCD to decimal
L023D    dec   ,s
         bne   L021E         End of loop for reading time
         leas  $01,s         Done with loop counter
         clr   >$FFDF        Map all RAM
         puls  b
         stb   >$FFA2        Put back original memory block
         puls  b,a
         sta   >MPI.Slct
         stb   >$FF90        Restore original ROM mapping
         puls  cc,pc         Re-enable interrupts

* Convert BCD to a normal number

BCD2Dec  lda   $01,x
         clrb  
B2DLoop  cmpa  #$10
         bcs   B2DDone
         suba  #$10
         addb  #$0A
         bra   B2DLoop
B2DDone  pshs  a
         addb  ,s+
         stb   $01,x
         rts   

ClkMsg   fcb   $C5,$3A,$A3,$5C,$C5,$3A,$A3,$5C

* Send above "string" to smartwatch, one bit at a time

SendMsg  leax  <ClkMsg,pcr
         lda   >RTC.Read+RTC.Base  Tell clock we're going to start???
         lda   #$08
         sta   ,-s         Store counter = 8
L006B    ldb   #$08        Start of outer loop, 8 bytes to send
         lda   ,x+           Get byte to send
L006F    lsra              Start of inner loop, 8 bits to send
         bcs   L0077
         tst   >RTC.Zero+RTC.Base   Send a "zero" bit
         bra   L007A
L0077    tst   >RTC.One+RTC.Base    Send a "one" bit
L007A    decb  
         bne   L006F       End of inner loop
         dec   ,s          End of outer loop
         bne   L006B
         puls  pc,a

         endc

*
* Update time from Harris RTC 
*
         ifne  RTCHarrs
UpdTime  pshs  cc
         orcc  #IntMasks     Disable interrupts

         ldu   M$Mem,pcr     Get base address
         ldy   #D.Time       Pointer to time in system map

         lda   #%00001100    Init command register (Normal,Int. Disabled,
         sta   $11,u            Run,24-hour mode, 32kHz)

         lda   ,u            Read base address to set-up clock regs for read
         lda   6,u           Get year
         sta   ,y+
         lda   4,u           Get month
         sta   ,y+
         lda   5,u           Get day
         sta   ,y+
         lda   1,u           Get hour
         sta   ,y+
         lda   2,u           Get minute
         sta   ,y+
         lda   3,u           Get second
         sta   ,y+

         puls  cc,pc         Re-enable interrupts
         endc

*
*
* Software time update---used if no RTC hardware
*
*
         ifne RTCSoft
UpdTime  lda  <D.Min
         inca          minute+1
         cmpa #60      End of hour?
         blo  UpdMin    Set start of minute
         ldd  <D.Day get day, hour
         incb hour+1
         cmpb #24      End of Day?
         blo  UpdHour ..no
         inca day+1
         leax months,pcr point to months table
         ldb  <D.Month this month
         cmpa b,x end of month?
         bls  UpdDay ..no
         cmpb #2 yes, is it Feb?
         bne  NoLeap ..no, ok
         ldb  <D.Year else get year
         andb #$03 check for leap year
         cmpd #$1D00 29th on leap year?
         beq  UpdDay ..yes, skip it
NoLeap   ldd  <D.Year else month+1
         incb 
         cmpb #13   end of year?
         blo  UpdMonth ..no
         inca       year+1
         ldb #$01   set month to jan
UpdMonth std <D.Year save year, month
         lda #$01   day=1st
UpdDay   clrb       hour=midnite
UpdHour  std <D.Day save day,hour
         clra       minute=00
UpdMin   clrb       seconds=00
         std <D.Min save min,secs
UpdTExit rts

months   fcb $00,31,28,31,30,31,30,31,31,30,31,30,31 Days in each month
         endc

*------------------------------------------------------------
*
* Handle F$VIRQ system call
*
F.VIRQ   pshs  cc
         orcc  #$50       Disable interrupts
         ldy   <D.CLTb    Address of VIRQ table
         ldx   <D.Init    Address of INIT
         ldb   PollCnt,x  Number of polling table entries from INIT
         ldx   R$X,u      Zero means delete entry
         beq   RemVIRQ

FindVIRQ ldx   ,y++       Is VIRQ entry null?
         beq   AddVIRQ    If yes, add entry here
         decb
         bne   FindVIRQ
         puls  cc
         comb  
         ldb   #E$Poll
         rts   

AddVIRQ  leay  -2,y       Point to first null VIRQ entry
         ldx   R$Y,u
         stx   ,y
         ldy   R$D,u
         sty   ,x
         bra   virqexit

RemVIRQ  ldx   ,y++
         beq   virqexit
         cmpx  R$Y,u
         bne   RemVIRQ
         bsr   DelVIRQ
virqexit puls  cc
         clrb  
         rts   

DelVIRQ  pshs x,y
DelVLup  ldx ,y++ move entries up in table
         stx -4,y
         bne DelVLup
         puls x,y
         leay -2,y reset table pointer
         rts

*------------------------------------------------------------
*
* Handle F$Alarm call
*
F.Alarm  ldx   #Al.Time
         ldd   R$D,u
         bne   DoAlarm
         std   Al.PID-Al.Time,x  Erase F$Alarm PID, Signal.
         rts   

DoAlarm  tsta             If PID != 0, set alarm for this process
         bne   SetAlarm
         cmpd  #1         1 -> Set system-wide alarm
         bne   GetAlarm
SetAlarm std   Al.PID-Al.Time,x
         ldy   <D.Proc
         lda   P$Task,y   Move from process task
         ldb   <D.SysTsk  To system task
         ldx   R$X,u      From address given in X
         ldu   #Al.Time
         ldy   #5         Move 5 bytes
         bra   FMove

GetAlarm cmpd  #2
         bne   AlarmErr
         ldd   Al.PID-Al.Time,x
         std   R$D,u
         bra   RetTime
AlarmErr comb
         ldb   #E$IllArg
         rts   

*------------------------------------------------------------
*
* Handle F$Time System call
*
F.Time   ldx   #D.Time    Address of system time packet
RetTime  ldy   <D.Proc    Get pointer to current proc descriptor
         ldb   P$Task,y      Process Task number
         lda   <D.SysTsk  From System Task
         ldu   R$X,u
STime.Mv ldy   #6         Move 6 bytes
FMove    os9   F$Move   
         rts   

*------------------------------------------------------------
*
* Handle F$STime system call
*
* First, copy time packet from user address space to system time
* variables, then fall through to code to update RTC.
*
F.STime  ldx <D.Proc   Caller's process descriptor
         lda P$Task,x  Source is in user map
         ldx R$X,u     Address of caller's time packet
         ldu #D.Time   Destination address
         ldb <D.SysTsk Destination is in system map
         bsr STime.Mv  Get time packet (ignore errors)
         lda #TkPerSec Reset to start of second
         sta <D.Tick

*
* No RTC, just end  (Also for SmartWatch, temporarily)
*
         ifne RTCSoft+RTCSmart
         rts
         endc

*
* Set Eliminator RTC from D.Time
*
         ifne RTCElim
         pshs cc save interrupt status
         orcc #IntMasks disable IRQs
         ldx M$Mem,pcr get RTC base address from fake memory requirement
         ldy #D.Time point [Y] to time variables in DP
         ldd #$0B*256+RTC.Stop
         bsr UpdatCk0 stop clock before setting it
         ldb #RTC.Sped
         bsr UpdatCk0 set crystal speed, output rate
         bsr UpdatClk go set year
         bsr UpdatClk go set month
         bsr UpdatClk go set day of month
         bsr UpdatCk0 go set day of week (value doesn't matter)
         bsr UpdatCk0 go set hours alarm (value doesn't matter)
         bsr UpdatClk go set hour
         bsr UpdatCk0 go set minutes alarm (value doesn't matter)
         bsr UpdatClk go set minute
         bsr UpdatCk0 go set seconds alarm (value doesn't matter)
         bsr UpdatClk go set second
         ldd #$0B*256+RTC.Strt
         bsr UpdatCk0 go start clock
         puls cc Recover IRQ status
         clrb
         rts

UpdatClk ldb ,y+ get data from D.Time variables in DP
UpdatCk0 std ,x generate address strobe, save data
         deca set [A] to next register down
         rts
         endc

*
* Set Disto 2-in-1 RTC from Time variables
*
         ifne RTCDsto2
         pshs a,cc
         lbsr RTCPre    Initialize

         bsr SetVal  Set Year
         bsr SetVal  Set Month
         bsr SetVal  Set Day
         ldd #$0805  $08 in A, $05 in B
         bsr SetVal1 Set Hour   (OR value in A ($08) with hour)
         bsr SetVal  Set Minute
         bsr SetVal  Set Second

         lbra RTCPost   Clean up + return

SetVal   clra
SetVal1  stb  2,x    Set Clock address
         decb
         pshs b
         ldb  ,y+    Get current value
DvLoop   subb #10    Get Tens digit in A, ones digit in B
         bcs  DvDone
         inca
         bra  DvLoop
DvDone   addb #10
         sta  ,x     Store tens digit
         tfr  b,a
         puls b      Get back original clock address
         stb  2,x
         decb
         sta  ,x     Store ones digit
         rts
         endc

*
* Set Disto 4-in-1 RTC from Time variables
*
         ifne RTCDsto4
         pshs cc
         orcc #IntMasks

         ifne MPIFlag
         ldb  >MPI.Slct   Save currently selected MPak slot
         pshs b
         andb #$F0
         orb  >SlotSlct,pcr Get slot to select
         stb  >MPI.Slct     Select MPak slot for clock
         endc

         ldy #D.Time+6
         ldx M$Mem,pcr
         clrb
         bsr SetVal  Set Second
         bsr SetVal  Set Minute
         bsr SetVal  Set Hour
         bsr SetVal  Set Day
         bsr SetVal  Set Month
         bsr SetVal  Set Year

         ifne MPIFlag
         puls b       Restore old MPAK slot
         stb >MPI.Slct
         endc

         puls cc
         clrb          No error
         rts

SetVal   clr  ,-s    Create variable for tens digit
         lda  ,-y     Get current value
DvLoop   suba #10    Get Tens digit on stack, ones digit in A
         bcs  DvDone
         inc  ,s
         bra  DvLoop
DvDone   adda #10
         stb  1,x     Set Clock address
         incb
         sta  ,x      Store ones digit
         stb  1,x
         incb
         puls a
         sta  ,x      Store tens digit
         rts
         endc

*
* Set B&B RTC from Time variables
*
         ifne  RTCBB
         pshs  u,y,cc
         leay  SendBCD,pcr   Send bytes of clock
         lbra  TfrTime
         endc
 
*------------------------------------------------------------
* read/write RTC Non Volatile RAM (NVRAM)
*
* INPUT:  [U] = pointer to caller's register stack
*         R$A = access mode (1 = read, 2 = write, other = error)
*         R$B = byte count (1 through 50 here, but in other implementations
*               may be 1 through 256 where 0 implies 256)
*         R$X = address of buffer in user map
*         R$Y = start address in NVRAM
*
* OUTPUT:  RTC NVRAM read/written
*
* ERROR OUTPUT:  [CC] = Carry set
*                [B] = error code
         ifne RTCElim
F.NVRAM  tfr u,y        copy caller's register stack pointer
         ldd #$0100     ask for one page
         os9 F$SRqMem
         bcs NVR.Exit   go report error...
         pshs y,u       save caller's stack and data buffer pointers
         ldx R$Y,y      get NVRAM start address
         cmpx #50       too high?
         bhs Arg.Err    yes, go return error...
         ldb R$B,y      get NVRAM byte count
         beq Arg.Err
         abx            check end address
         cmpx #50       too high?
         bhi Arg.Err    yes, go return error...
         lda R$A,y      get direction flag
         cmpa #WRITE.   put caller's data into NVRAM?
         bne ChkRead    no, go check if read...
         clra           [D]=byte count
         pshs d         save it...
         ldx <D.Proc    get caller's process descriptor address
         lda P$Task,x   caller is source task
         ldb <D.SysTsk  system is destination task
         ldx R$X,y      get caller's source pointer
         puls y         recover byte count
         os9 F$Move     go MOVE data
         bcs NVR.Err
         ldy ,s         get caller's register stack pointer from stack
         lda R$Y+1,y    get NVRAM start address
         adda #$0E      add offset to first RTC NVRAM address
         ldb R$B,y      get byte count
         ldx M$Mem,pcr  get clock base address from fake memory requirement
         pshs cc,b      save IRQ enable status and byte counter
         orcc #IntMasks disable IRQs
WrNVR.Lp ldb ,u+        get caller's data
         std ,x         generate RTC address strobe and save data to NVRAM
         inca           next NVRAM address
         dec 1,s        done yet?
         bne WrNVR.Lp   no, go save another byte
         puls cc,b      recover IRQ enable status and clean up stack
NVR.RtM  puls y,u       recover caller's register stack & data buffer pointers
         ldd #$0100     return one page
         os9 F$SRtMem
NVR.Exit rts

Arg.Err  ldb #E$IllArg Illegal Argument error
         bra NVR.Err

ChkRead  cmpa #READ.    return NVRAM data to caller?
         bne Arg.Err    illegal access mode, go return error...
         lda R$Y+1,y    get NVRAM start address
         adda #$0E      add offset to first RTC NVRAM address
         ldx M$Mem,pcr  get clock base address from fake memory requirement
         pshs cc,b      save IRQ enable status and byte counter
         orcc #IntMasks disable IRQs
RdNVR.Lp sta ,x         generate RTC address strobe
         ldb 1,x        get NVRAM data
         stb ,u+        save it to buffer
         inca           next NVRAM address
         dec 1,s        done yet?
         bne RdNVR.Lp   no, go get another byte
         puls cc,a      recover IRQ enable status, clean up stack ([A]=0)
         ldb R$B,y      [D]=byte count
         pshs d         save it...
         ldx <D.Proc    get caller's process descriptor address
         ldb P$Task,x   caller is source task
         lda <D.SysTsk  system is destination task
         ldu R$X,y      get caller's source pointer
         puls y         recover byte count
         ldx 2,s        get data buffer (source) pointer
         os9 F$Move     go MOVE data
         bcc NVR.RtM
NVR.Err  puls y,u       recover caller's stack and data pointers
         pshs b         save error code
         ldd #$0100     return one page
         os9 F$SRtMem
         comb set       Carry for error
         puls b,pc      recover error code, return...
         endc
*
* Set Harris 1770 RTC from Time variables
*
         ifne  RTCHarrs
         pshs  cc
         orcc  #IntMasks     Disable interrupts

         ldu   M$Mem,pcr     Get base address
         ldy   #D.Time       Pointer to time in system map

         lda   #%00000100    Init command register (Normal,Int. Disabled,
         sta   $11,u            STOP clock,24-hour mode, 32kHz)

         lda   ,y+           Get year
         sta   6,u
         lda   ,y+           Get month
         sta   4,u
         lda   ,y+           Get day
         sta   5,u
         lda   ,y+           Get hour
         sta   1,u
         lda   ,y+           Get minute
         sta   2,u
         lda   ,y            Get second
         sta   3,u

         lda   #%00001100    Init command register (Normal,Int. Disabled,
         sta   $11,u            START clock,24-hour mode, 32kHz)

         puls  cc,pc         Re-enable interrupts
         endc

*--------------------------------------------------
*
* Clock Initialization
*
* This vector is called by the kernel to service the first F$STime
* call.  F$STime is usually called by CC3Go (with a dummy argument)
* in order to initialize the clock.  F$STime is re-vectored to the
* service code above to handle future F$STime calls.
*
*
Init     ldx #PIA0Base point to PIA0
         clra          no error for return...
         pshs cc       save IRQ enable status (and Carry clear)
         orcc #IntMasks stop interrupts

         ifeq TkPerSec-50
         ldb <D.VIDMD    get video mode register copy
         orb #$08        set 50 Hz VSYNC bit
         stb <D.VIDMD    save video mode register copy
         stb >$FF98      set 50 Hz VSYNC
         endc

         sta 1,x enable DDRA
         sta ,x set port A all inputs
         sta 3,x enable DDRB
         coma
         sta 2,x set port B all outputs
         ldd #$343C [A]=PIA0 CRA contents, [B]=PIA0 CRB contents
         sta 1,x CA2 (MUX0) out low, port A, disable HBORD high-to-low IRQs
         stb 3,x CB2 (MUX1) out low, port B, disable VBORD low-to-high IRQs
         lda ,x clear possible pending PIA0 HBORD IRQ
         lda 2,x clear possible pending PIA0 VBORD IRQ

* Don't need to explicitly read RTC during initialization
         ldd #59*256+TkPerTS last second and time slice in minute
         std <D.Sec      Will prompt RTC read at next time slice

         stb <D.TSlice set ticks per time slice
         stb <D.Slice set first time slice
         leax SvcIRQ,pcr set IRQ handler
         stx <D.IRQ
         leax SvcVIRQ,pcr set VIRQ handler
         stx <D.VIRQ
         leay NewSvc,pcr insert syscalls
         os9 F$SSvc
         lda <D.IRQER get shadow GIME IRQ enable register
         ora #$08 set VBORD bit
         sta <D.IRQER save shadow register
         sta >IRQEnR enable GIME VBORD IRQs

*
* RTC-specific initializations here
*
         ifne RTCDsto4
         ldx  M$Mem,pcr
         ldd  #$010F    Set mode for RTC chip
         stb  1,x
         sta  ,x
         ldd  #$0504
         sta  ,x
         stb  ,x
         endc

         puls cc,pc recover IRQ enable status and return


         emod
len      equ *
         end
*--------------------------------------------------------------------
