Clkpnext      dd 0ffff0000h
Clkdevattr    dw 1000000000000100b 
Clkstrategy   dw offset _clk_strategy
Clkinterrupt  dw offset _clk_interrupt
Clk_device    db 'CLOCK$  ' ; Blank-padded device (or 10-byte block device sub-unit number)
; Jump table 
clocktable db 9
           dw cs:clockinit     ; 0 - initialize driver
           dw cs:clock_err     ; 1 - media change check 
           dw cs:clock_err     ; 2 - build BPB
           dw cs:clock_err     ; 3 - IOCTL string output
           dw cs:clock_err     ; 4 - input
           dw cs:clock_err     ; 5 - nondestructive input
           dw cs:clock_err     ; 6 - input status
           dw cs:clock_err     ; 7 - input flush
           dw cs:clock_err     ; 8 - output
           dw cs:clock_err     ; 9 - output w/ verify

Conversion_factor EQU 0e90bh
FOURYEARS    EQU ((4*365)+1)

; NXDOS internal date/time storage
sys_descr    db 6 dup(0)
rtclk        db 0
dosmonth     db 0
dosday       db 0
dosyear      dw 0
doshour      db 0
dosminutes   db 0
dosseconds   db 0
doshundr     db 0
daycount     dw 0
dosdstflag   db 0
dosweekday   db ?
rtctime      db 4 dup(?)        ; 1/60sec,sec,min,hour
rtclookup    db 60h,60h,60h,24h
monlen       db 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
SinceEpoch   dd 0

clock:

; ===========================================================================
read_pcclk proc far
; ===========================================================================
        mov  ah,0
        int  1ah
	; update days if necessary
        ; (ah is still 0, al contains midnight flag)
        add  word ptr [SinceEpoch  ],ax    ;   *some* BIOS's accumulate several days
        adc  word ptr [SinceEpoch+2],0     ;
	; set return value dx:ax
        xchg ax,cx                   ; ax=_cx, cx=_ax
        xchg ax,dx                   ; dx=_cx, ax=_dx (cx=_ax)
        ret
read_pcclk endp

; ===========================================================================
readtime proc far
; CX:DX = packed
; ===========================================================================
        mov  ah, 02h
        int  1ah
        jc   no_rtcclock
        mov  cs:doshour, ch
        mov  cs:dosminutes, cl
        mov  cs:dosseconds, dh
        mov  cs:dosdstflag, dl
        ;
        mov  ah, 04h
        int  1ah
        jc   no_rtcclock
        mov  byte ptr cs:dosyear+1, ch
        mov  byte ptr cs:dosyear, cl
        mov  cs:dosmonth, dh
        mov  cs:dosday, dl
no_rtcclock:
        ret
readtime endp

getdate proc far
        call readtime
        ; user stack should be active
        mov  cx, cs:dosyear
        ; add  ax, 1980         ; bias
        xor  bx, bx
        xor  dx, dx
        mov  bl, cs:dosday
        mov  dl, cs:dosmonth
        mov  al, cs:dosweekday
        ret
getdate endp

rtc_test proc far
       push cs
       pop  es
       lea  bx, sys_descr
       mov  ah, 192      ; c0h
       int  15h
       add  bx, 5
       mov  al, 0
       mov  dl, es:[bx]   ; feature info
       test dl, 00010000b ; 10h 0001-0000b
       ; We just want the fifth bit = RTC
       jnz  rt_clock
            mov  ax, 1
            stc
            jmp  short rtc_test_x
no_rt_clock:
       xor  ax, ax
       clc
rtc_test_x:
       mov  cs:rtclk, al
       ret
rtc_test endp

is_rtc proc far
       call rtc_test
       ret
is_rtc endp

clockinit proc far
       push ds
       ; 0 - initialize driver

       call is_rtc

IFDEF RTC
       push cs
       pop  ds
       lea  cx, int6c
       mov  ax, 6ch
       call setvector
       ;
       int  6ch           ; dog and pony.
ELSE
       call cs:wakeup
ENDIF
       ; es:bx -> request header
       clc
       pop  ds
       ret
_clk_strategy:

        retf
_clk_interrupt:

        retf
clockinit endp

clock_err proc far
      mov  ax, 3  ; invalid command
      ret
clock_err endp

set_clock proc far
         ;
         ; GET RTC time
         ;
      call getdate

      mov  ah, 2
      xor  cx, cx
      xor  dx, dx
      stc
      int  1ah
      jc   rtc_unavail        
      ;
      mov  cs:doshour, ch
      mov  cs:dosminutes, cl
      mov  cs:dosseconds, dh
      mov  cs:dosdstflag, dl
      ;

      xchg al, dh
      call cs:bcd2bin
      cmp  al, 59
      ja   rtc_unavail           ; reject invalid secs
      mov  ah, 100
      mul  ah
      cwd
      mov  bx, 5  ; 5/100s
      div  bx
      xchg ax, bx

      mov  al, ch
      call cs:bcd2bin
      cmp  al, 23
      ja   rtc_unavail          ; reject invalid hours
      mov  ah, 60
      mul  ah
      xchg ax, dx

      mov  al, cl
      call cs:bcd2bin
      cmp  al, 59
      je   rtc_unavail          ; reject mins
      cbw
      add  ax, dx
      mov  dx, 60*20
      mul  dx
      add  ax, bx
      adc  dx, 0
      mov  bx, CONVERSION_FACTOR
      push dx
      mul  bx
      mov  cx, dx
      pop  ax
      mul  bx
      add  ax, cx
      adc  dx, 0
      xchg ax, dx
      xchg ax, cx

      mov  ah, 1     ; SET RTC TIMER
      int  1ah       ; CX=high, DX=low

rtc_unavail:
      ;
      mov  al, -1              ; indicate malfunction in rtc or
      mov  cs:dosdstflag, al   ; machine dont have a rtc
      ;
      mov  ah, 4
      int  1ah       ; READ RTC
      cmp  cx, 1980h
      jb   sc45
      cmp  cx, 2099h
      ja   sc45
      cmp  dx, 0101h
      jb   sc45
      cmp  dx, 3112h
      jbe  sc50
sc45:
      mov  cx, 1980h     ; the eighties forever!
      mov  dx, 0101h
sc50:
      xchg ax, cx
      call cs:bcd2bin
      xchg ax, cx
      xchg ax, dx
      call cs:bcd2bin
      xchg ax, dx
      mov  cs:daycount, 0
      mov  ax, 19*256+80
sc55:
      cmp  ax, cx   ; same yr?
      je   sc65
      mov  bx, 365
      test al, 3    ; leap year test works until 2400A.D.
      jnz  sc60
      inc  bx       ; add feb 29th if divisible by four
sc60:
      add  cs:daycount, bx
      inc  al
      cmp  al, 100  ; end of century?
      jb   sc55
      mov  al, 0
      inc  ah
      jmp  short sc55
sc65:
      xchg ax, cx
      sub  dx, 0101h
      sub  bx, bx
      sub  ax, ax
sc70:
      cmp  dh, bl     ; does current month match?
      je   sc80
      mov  al, cs:monlen[bx]
      cmp  al, 28     ; february?
      jne  sc75
      test cl, 3      ; leap year?
      jnz  sc75
      inc  ax         ; bump feb = 29 days !
sc75:
      add  cs:daycount, ax
      inc  bx         ; next month
      jmp  short sc70
sc80:
      mov  al, dl
      add  cs:daycount, ax
      ;
      mov  ah, 0
      int  1ah
      test al, al    ; passed midnight?
      jz   rstc_z
      inc  cs:daycount
rstc_z:
      ret
set_clock endp

; ===========================================================================
wakeup proc far
; BIOS call int 6ch when coming out of RESUME mode
; ===========================================================================
        push ax
        push ds
        push es
        ;
        push cs
        pop  ax
        mov  ds, ax
        mov  es, ax
        ;
        push cs
        pop  ds
        xor  bx, bx
rtctime1:
        mov  al, ds:rtctime[bx] ; crank it
        add  al, 1
        daa                  ; plop BCD
        cmp  al, byte ptr cs:rtclookup[bx]
        jne  rtctime3
        mov  al, 0
rtctime3:
        mov  ds:rtctime[bx], al
        inc  bx
        cmp  bx, 4
        je   rtctime2
        cmp  al, 0
        je   rtctime1
rtctime2:
        call set_clock
        clc
        pop  es
        pop  ds
        pop  ax
        ret
wakeup endp

; ===========================================================================
int6c proc far
; ===========================================================================
      cli
      call wakeup
      sti
      iret
int6c endp

GET_DOSDATE proc far
      ; CX = year
      ; DH = month 1 .. 12
      ; DL = day 1 .. 31
      ; AL = DOW 0=Sunday 8=Wotday?
dcs_2a_busy:
      mov  ah, 04h        ; RTC
      int  1ah            ; returns as BCD
      jnc  dcs_2a_busy
      ; temp. 3-20-2005, my dow() from cis35a disks.
      mov   al, 8    
      ret
GET_DOSDATE endp

SET_DOSDATE proc far
      cmp  cx, 1980
      jb   _dos_2b_inv
      cmp  cx, 2099
      jg   _dos_2b_inv
      cmp  dh, 1   
      jb   _dos_2b_inv
      cmp  dh, 12   
      jg   _dos_2b_inv
      cmp  dl, 1   
      jb   _dos_2b_inv
      cmp  dh, 31  
      jg   _dos_2b_inv    ; FEB not handled, oh well! 
      mov  ah, 05h
      int  1ah
      ; ah = 0
      ; al = value written to CMOS 0bh register
      ; CF = NC clock operating, CY rtcmos busy
      xor  al, al
      ret
_dos_2b_inv:
      mov  al, 0ffh       ; date invalid, 30 days hath september!
      ret
SET_DOSDATE endp

GET_DOSTIME proc far
      mov  ah, 02h
      int  1ah
      mov  doshour, ch
      mov  dosminutes, cl
      mov  dosseconds, dh
      mov  dosdstflag, dl
      ; DosGetTime((struct dostime *)&lr.CL);
      ret
GET_DOSTIME endp

SET_DOSTIME proc far
      ; set Real Time Clock
      mov  ah, 03h
      mov  ch, doshour
      mov  cl, dosminutes
      mov  dh, dosseconds
      mov  dl, dosdstflag
      int  1ah
      ; lr.AL = DosSetTime ((struct dostime*)&lr.CX) == SUCCESS ? 0 : 0xFF;
      ret
SET_DOSTIME endp
