;*DDK*************************************************************************/
;
; COPYRIGHT (C) Microsoft Corporation, 1989
; COPYRIGHT    Copyright (C) 1995 IBM Corporation
;
;    The following IBM OS/2 WARP source code is provided to you solely for
;    the purpose of assisting you in your development of OS/2 WARP device
;    drivers. You may use this code in accordance with the IBM License
;    Agreement provided in the IBM Device Driver Source Kit for OS/2. This
;    Copyright statement may not be removed.;
;*****************************************************************************/
;SCCSID = @(#)clock01.asm       6.22 91/10/22
title   clock device driver -- AT  [clock01.asm]

; ****************************************************************************
; *                                                                          *
; *                                                                          *
; *                                                                          *
; ****************************************************************************
;
; Change History
;
; 64012 03/16/93 BJH    Change to retrieve century byte location from the
;                       DevConfigTbl
; 71736 08/11/93 TPL    Don't change the periodic rate (register A) when
;                       updating the date and time

PAGE    60,132
;
.386p                                   ; assemble for iAPX 386
.xlist

; define MONITOR and/or MISCSTRICT to get help when debugging, these are
; NOT to be defined for a retail build.
;;MONITOR         EQU     1               ; builds in monitoring code
;;MISCSTRICT      EQU     1               ; turns on extra checking

INCL_ERRORS     EQU     1
        include bseerr.inc              ; error numbers
INCL_MI         EQU     1
        INCLUDE rtc.inc                 ; real time clock information
        INCLUDE mvdm.inc                ; equates and data for MVDM support
        INCLUDE devhlp.inc              ;  the devhlp function eqs
        INCLUDE infoseg.inc             ; definitions of Global Info Segment
        INCLUDE devsym.inc              ; definition of DD Request Packet
        INCLUDE clkdata.inc
        INCLUDE config.inc              ; device config table
        INCLUDE sas.inc                 ; System Anchor Segment (SAS)
        INCLUDE timer.inc               ; timer definitions
        INCLUDE clkseg.inc              ; segment definitions
        INCLUDE pvwxport.inc
        INCLUDE vtdptd.inc              ; vtimer/ptimer interface
        INCLUDE devhlpP.inc             ; 76711

.list
        extrn   GTDAYS:Far
        extrn   FIXDAYOFWEEK:Near
        extrn   ClkVDDProc:near         ; Clk router routine
        extrn   PTBeep:near             ; beep service entry point
        extrn   PTBeepOff:near          ; service to turn speaker off
        extrn   PTVDMProc:near          ; vtimer/ptimer interface entry point
        extrn   PTTimer0:near           ; timer 0 ownership arbitration serv.

ClkData SEGMENT
; data segment externals
        extrn   DevHlp:DWORD
        extrn   SchedClock:DWORD
        extrn   InfSeg:DWORD
        extrn   MonTab:BYTE
        extrn   Update_Flag:BYTE
        extrn   Post_Time:WORD
        extrn   Int_Nested:BYTE
        extrn   Int_In_Progress:BYTE
        extrn   CMOS_location:BYTE
        extrn   ATDataEnd:Near

        extrn   ReqRTCFreq:BYTE
        extrn   CurRTCFreq:BYTE
        extrn   Hundred_Whole:BYTE
        extrn   Hundred_Frac:BYTE
        extrn   Hundred_Inc:WORD
        extrn   MS_Fraction:WORD
        extrn   MS_Inc_Whole:DWORD
        extrn   MS_Inc_Frac:WORD
        extrn   MS_Delta:WORD

        extrn   Hz128To32:BYTE
        extrn   Accum8msTicks:WORD
        extrn   ExpectedMsCount:DWORD

ifdef   MONITOR
        extrn   LagCount:DWORD
        extrn   LagTotal:DWORD

        extrn   Accm:DWORD
        extrn   Accm8:WORD
        extrn   Accm8p:WORD
        extrn   Accm8mod4:WORD
        extrn   Ticks8:DWORD
        extrn   Ticks32:DWORD
endif

        extrn   szClkName:byte          ; device name for PCLK registration
        extrn   szTmrName:byte          ; device name for PTIMER registration
        extrn   fpfnVTProc:FWORD        ; vtimer entry point
        extrn   fsPTFlags:WORD          ; ptimer flags

        extrn   pqwTmrRollover:DWORD
        extrn   qwTmrRollover:DWORD
        extrn   pqwTmr:DWORD
        extrn   qwTmr:BYTE

ClkData ENDS

BREAK <Realtime Clock Write Routine>
;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  RTWRIT
;*
;* DESCRIPTIVE NAME:  Realtime Clock  Write routine
;*
;* FUNCTION:   Supports the Device Driver functions:
;*             - WRITE (function 8)
;*             - WRITE WITH VERIFY (function 9)
;*      by setting the Realtime Clock device with the date/time
;*      information and updating the Global InfoSeg date/time
;*      variables accordingly.
;*      Set the stack frame to reserve the clock information
;*      as follows:
;*                +---------------------+
;*                |  old  BP            |
;*                +---------------------+
;*         BP+16->| CURRENT CENTURY YEAR|
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+14->| YEAR/BCD | YEAR/BIN |
;*                +---------------------+
;*         BP+12->| MONTH/BCD| MON/BIN  |
;*                +---------------------+
;*         BP+10->| DAY OF MONTH        |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+8 ->| DAY OF WEEK         |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+6 ->| HOUR/BCD | HOUR/BIN |
;*                +---------------------+
;*         BP+4 ->| MIN/BCD  | MIN/BIN  |
;*                +---------------------+
;*         BP+2 ->| SEC/BCD  | SEC/BIN  |
;*                +---------------------+
;*         BP   ->| CENTURY  |HUNDREDTH |
;*                |  BINARY  |  BINARY  |
;*                +---------------------+
;*
;*      DevHlp_PhysToVirt converts the real address of the data buffer
;*      in the Device Driver Request Block (PktData) to a virtual
;*      address.  DevHlp_UnPysToVirt later restores the physical
;*      memory address.
;*
;* ENTRY POINT: RTWRIT
;*    LINKAGE:  NEAR from RTENTR
;*
;* USES:  AX, DX.  Preserves others.
;*
;* INPUT: (PARAMETERS)
;*      ES:BX = pointer to Request Block  (PktData)
;*      (SP+4):(SP+2) = pointer to Data Buffer
;*
;* OUTPUT: (RETURNED)
;*      AX = Status word to be stored in request packet status field.
;*
;* EXIT-NORMAL:
;*    RETURN CODE:      AX = BUSY and DONE
;*
;* EXIT-ERROR:
;*    RETURN CODE:      AX = ERROR and DONE and Write Fault
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  none
;*    ROUTINES:         cmos_write_clock
;*                      calc_year_sec
;*
;* EXTERNAL REFERENCES:
;*    STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:         DevHlp_PhysToVirt
;*                      DevHlp_UnPhysToVirt
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   *********************
;
; if caller does not request 6 bytes
;    goto write_fail
; call DevHlp_PhysToVirt to convert data buffer's physical addr to
;      virtual address in ES:DI
; if DevHlp_PhysToVirt fail
;    goto write_fail
; reserve 16 bytes for the clock information
; get days since 1-1-80 from user buffer
; day_count = no of days not in 4 year groups = days % 1461
; year_count = no of years in 4 year groups = ( days / 1461 ) * 4
; if day_count > 366
;    day_count -= 366
;    year_count++
;    while ( day_count > 365 )
;       day_count -= 365
;       year_count++
;    endwhile
; endif
;
; Normalize year_count ( year_count += 80 )
; if year_count in 20xx ( year_count >= 100 )
;    year_count -= 100
;    Century = 20x
; else Century = 19x
;
; if current date in leap year
;    set Feb = 29
; else
;    set Feb = 28
;
; for month = 1 to 12
;     if day_count >= MonTab[ month ]
;        break
;     else
;        day_count -= MonTab[ month ]
; endfor
;
; day_of_week = ( days + 2 ) % 7 + 1
; convert year, month, day_count, & day_of_week to BCD
;
; get seconds, minutes, & hours from user buffer and convert them to BCD
;     save both binary and BCD values on reserved stack
; get hundredths of seconds from user buffer and save on reserved stack
; call DevHlp_UnPhysToVirt to get original address mode
; if DevHlp_UnPhysToVirt successful
;    set return code  "DONE" & "BUSY"
; endif
; else goto write_fail
;
; disable interrupts
; initialize register A to 32.768kHz square wave output, and
;         31.25 millisecond periodic interrupt rate
; read register B value to get alarm interrupt status
; set register B to abort any update cycle in progress,
;     enable periodic interrupt, previous alarm interrupt status,
;     update-endedinterrupt enable, and 24 hour mode
; read register C & D values to initialize them
;
; call calc_year_sec to get seconds, minutes, hours, day_of_week, day_count,
;      month, year, & hundredths of seconds from reserved stack to put in
;      SysInfoSeg
; write seconds, minutes, hours, day_of_week, day_count,
;    month, & year to chip
;
; read register B value and reset its update cycle normally
; enable interrupts
;
; set return code = DONE+BUSY
; return
;
; write_fail:
;    set return code = DONE+ERROR+WRITE FAULT
;    return
;
;*********************** END OF PSEUDO-CODE    **********************

ClkSwap SEGMENT

century_year    equ     [bp+16]
year_bcd        equ     [bp+14]
year_bin        equ     [bp+15]
month_bcd       equ     [bp+12]
month_bin       equ     [bp+13]
day_month_bcd   equ     [bp+10]
day_month_bin   equ     [bp+11]
day_week_bcd    equ     [bp+8]
day_week_bin    equ     [bp+9]
hour_bcd        equ     [bp+6]
hour_bin        equ     [bp+7]
minute_bcd      equ     [bp+4]
minute_bin      equ     [bp+5]
second_bcd      equ     [bp+2]
second_bin      equ     [bp+3]
century_bin     equ     [bp]
hundredth_bin   equ     [bp+1]
;
; this extern is in the CS
;
        extrn   saveDS:WORD

    ASSUME      CS:ClkSwap,DS:ClkData,ES:NOTHING,SS:NOTHING
RTWRIT  PROC NEAR
        public  RTWRIT

        STI                             ; enable interrupts
        CMP     ES:[BX].IOcount,RW_BYTES        ; requesting 6 bytes ?
        JE      WRITE                           ; yes, continue....
WT_ERR:
        MOV     AX,(STDON+STERR+0ah)            ; Else, quit w/ error.
        RET

WRITE:  PUSH    BP
;       int     3
        sub     sp,18                           ; reserved 16 bytes for clock
        MOV     BP,SP
;; The addresses passed in the request packets are 32-bit physical addresses.
        MOV     CX,6
        MOV     AX,word ptr ES:[BX].IOpData+2   ; get hi word of address
        MOV     BX,word ptr ES:[BX].IOpData     ; get lo word of address
        MOV     DH,1                            ; result in ES:DI
        MOV     DL,DevHlp_PhysToVirt            ; call PhysToVirt
        CALL    [DevHlp]                        ; ES:DI points to buffer
        JNC     WRITE_OK

WRITE_FAIL:
        add     sp,18                   ; restore stack pointer
        POP     BP
        JMP     SHORT WT_ERR

;    get days since 1-1-80 from user buffer
WRITE_OK:
        MOV     AX,ES:[DI]              ; Get days since 1-1-80 from buffer

;    day_count = no of days not in 4 year groups = days % 1461
;    year_count = no of years in 4 year groups = ( days / 1461 ) * 4
        MOV     CX,DAYSIN4YR            ; No of days in four year group
        XOR     DX,DX                   ; Clear remainder word
        DIV     CX                      ; [AX] = No of four year groups
        SHL     AX,1
        SHL     AX,1                    ; [AX] = No of years in 4 year groups
        PUSH    AX                      ; Save (Note: 120 years max/[AH] = 0)

;    if day_count > 366
;       day_count -= 366
;       year_count++
;       while ( day_count > 365 )
;          day_count -= 365
;          year_count++
;       endwhile
;    endif
        XOR     CX,CX                   ; [CL] = 0 = extra year counter
        MOV     AX,DAYSINYR+1           ; Year zero is a leap year
YRCMP:  CMP     DX,AX                   ; Is date within year zero?
        JC      INYEAR                  ; Yes, don't subtract
        SUB     DX,AX                   ; No, sub days for this year
        INC     CL                      ; Increment counter
        MOV     AX,DAYSINYR             ; Days in years one and two
        JMP     YRCMP                   ; Loop until date in current year

;    Normalize year_count ( year_count += 80 )
;    if year_count in 20xx ( year_count >= 100 )
;       year_count -= 100
;       Century = 20x
;    else Century = 19x
INYEAR: POP     AX                      ; Restore partial year count
        MOV     ah,19H                  ; Set century = 19x
        ADD     AL,CL                   ; Add in 1 to 3 remainder years
        ADD     AL,80                   ; Normalize for 19xx
        CMP     AL,100                  ; Year in 19xx?
        JB      IS19XX                  ; Yes, AL = actual year (M003)
        SUB     AL,100                  ; No, normalize for 20xx
        MOV     ah,20H                  ; Set century = 20x

IS19XX: mov     century_bin,ah          ; save century byte
        mov     year_bin,al             ; save year in binary
        BN2BCD                          ; Convert year to BCD
        MOV     year_bcd,al             ; Year in BCD

;    if current date in leap year
;       set Feb = 29
;    else
;       set Feb = 28
        MOV     MonTab+1,28             ; Init Feb to 28 days
        OR      CL,CL                   ; Current date in year zero?
        JNZ     NOINC                   ; No, leave at 28
        INC     MonTab+1                ; Yes, add one day for leap year

;    for month = 1 to 12
;        if day_count >= MonTab[ month ]
;           break
;        else
;           day_count -= MonTab[ month ]
;    endfor
NOINC:  MOV     CL,1                    ; At least at first month in year
        PUSH    SI                      ; Save SI
        MOV     SI,OFFSET MonTab        ; Table of days in each month
        XOR     AH,AH
        CLD                             ; Insure increment
CHECK1: LODSB                           ; Get count of days this month
        CMP     DX,AX                   ; Date within this month?
        JC      FOUND                   ; Yes, month is found
        SUB     DX,AX                   ; No, sub this month's days and...
        INC     CX                      ; ...count one more month/year
        JMP     SHORT CHECK1            ; Loop until month is found

FOUND:  POP     SI                      ; Restore SI
        MOV     AL,CL                   ; Month number to AL
        mov     month_bin,al            ; save month/binary
        BN2BCD                          ; Convert to BCD
        mov     month_bcd,al            ; save month/BCD

        INC     DL                      ; Remainder is day of month (1 - 31)
        MOV     AL,DL
        mov     day_month_bin,al        ; save day of month/binary
        BN2BCD                          ; Convert DOM to BCD
        mov     day_month_bcd,al        ; save day of month/BCD

        MOV     AX,ES:[DI]              ; Reget total day count
        XOR     DX,DX                   ; Clear MSW
        MOV     CX,7                    ; Divide by days in week
        INC     AX
        INC     AX                      ; First day was Tuesday
        DIV     CX                      ; Compute day of week
        INC     DL                      ; Normalize
        MOV     AL,DL                   ; Remainder is day of week (1 - 7)
        mov     day_week_bin,al         ; save day of week/binary
        BN2BCD                          ; Convert DOW to BCD
        mov     day_week_bcd,al         ; save day of week/bcd

        MOV     AL,ES:[DI+5]            ; Get seconds value
        mov     second_bin,al           ; save second in binary
        BN2BCD                          ; Convert it
        mov     second_bcd,al           ; save second in bcd

        MOV     AL,ES:[DI+2]            ; Get minutes value
        mov     minute_bin,al           ; save minute in binary
        BN2BCD
        mov     minute_bcd,al           ; save minute in bcd

        MOV     AL,ES:[DI+3]            ; Get hours value
        mov     hour_bin,al             ; save hour in binary
        BN2BCD
        mov     hour_bcd,al             ; save hour in bcd


        MOV     AL,ES:[DI+4]            ; Get hundredths of seconds
        mov     hundredth_bin,al        ; save on reserved stack

        MOV     DL,DevHlp_UnPhysToVirt  ; call UnPhysToVirt
        CALL    [DevHlp]                ; original addr mode restored
        JNC     WRITE_clock
        JMP     WRITE_FAIL

WRITE_clock:
; prepare cmos clock to update clock
        cli
;71736  MOV     AX,(RABYTE SHL 8) OR 8AH        ; Register A
;71736  cmos_write_clock                ; Stuff init value
        MOV     AX,8B8BH                ; Register B (Init both for later set)
        cmos_read_clock                 ; Get current value
        AND     AL,00100000B            ; Preserve user alarm setting only
        OR      AL,RBWRIT               ; Init all others to correct values
        XCHG    AH,AL                   ; AL=register, AH=value
        cmos_write_clock                ; Stop clock updating
        MOV     AL,8CH                  ; Register C
        cmos_read_clock                 ; Read to initialize only
        MOV     AL,8DH                  ; Register D
        cmos_read_clock                 ; Read to initialize only

        cmos_reset                      ; cmos RAM=>read port w/NMI enable
        sti                             ; enable interrupts

        call    far ptr calc_year_sec   ; update sysinfoseg(interrupt disable)
                                        ; es:bx=>SysInfoSeg
        mov     al,hundredth_bin        ; get hundredth
        MOV     ES:[BX.SIS_HunTime],AL  ; Stuff it in InfoSeg field

; write cmos clock
        mov     al,CMOS_location        ; AL = 37h -PS/2 OR 32h -AT
        add     al,80h
        MOV     Ah,century_bin          ; write century
        cmos_write_clock

        MOV     AL,0+80H                ; set seconds
        mov     ah,second_bcd
        cmos_write_clock

        MOV     AL,2+80H                ; set minutes
        mov     ah,minute_bcd
        cmos_write_clock

        MOV     AL,4+80H                ; set hours
        mov     ah,hour_bcd
        cmos_write_clock

        MOV     AL,6+80H                ; set day_of_week
        mov     ah,day_week_bcd
        cmos_write_clock

        MOV     AL,7+80H                ; set day of month
        mov     ah,day_month_bcd
        cmos_write_clock

        MOV     AL,8+80H                ; set month
        mov     ah,month_bcd
        cmos_write_clock

        MOV     AL,9+80H                ; set year
        mov     ah,year_bcd
        cmos_write_clock

; restore clock state
        MOV     AX,8B8BH                ; Register B (Init both for later set)
        cmos_read_clock                 ; Get current value
        AND     AL,7FH                  ; Reset update disable bit
        XCHG    AH,AL                   ; AH=value, AL=register
        cmos_write_clock
        cmos_reset                      ; cmos RAM=>read port w/NMI enable
        sti                             ; enable interrupts

        MOV     AX,(STDON OR STBUI)     ; No errors
        add     sp,18                   ; restore stack pointer
        POP     BP
        RET

RTWRIT  ENDP

ClkSwap ENDS


;***LP  CLKFreq(ulFreq) - Set RTC Periodic Frequency service
;
;       CLKFreq is called by the kernel to request a new frequency at
;       which real time clock periodic "ticks" are generated. (See DCR 1345)
;       The actual reprogramming of the device is delayed until such time
;       as it can be done safely: interrupts disabled, pre-EOI, and on the
;       next 32ms tick boundary
;
;       ENTRY
;           (TOS) = ulFreq  : desired frequency in Hz.
;                             Only 32 and 128 supported.
;       EXIT
;           SUCCESS
;               (eax) = 0
;           FAILURE
;               (eax) = ERROR_INVALID_FREQUENCY
;
;       CONTEXT
;           Any (called by TomTick; see task\tom.c)
;
;       PSEUDOCODE
;           if ulFreq is 32 or 128
;              record new frequency desired; the actual change
;                will occur on the next 32ms boundary
;              return rc = no errors;
;           else
;              return rc = invalid frequency
;

ClkCode SEGMENT

        ASSUME  CS:ClkCode,DS:NOTHING,ES:NOTHING,SS:NOTHING
Procedure CLKFreq,far

        .386p

        ?abase  = 8 + 2
        ArgVar  ulFreq,ULONG

        EnterProc
        SaveReg <ds>

        mov     ds,cs:[selClkData]
        ASSUME  DS:ClkData

; check if requested frequency is allowed
        cmp     WORD PTR [ulFreq],128   ; is frequency 128Hz?
        je      clkfreq_valid           ; .. yes,  its valid
        cmp     WORD PTR [ulFreq],32    ; is frequency 32Hz?
        jne     clkfreq_invalid         ; ...... no, so fail

clkfreq_valid:
        mov     ax,WORD PTR [ulFreq]
        mov     ReqRTCFreq,al           ; record requested new frequency
        xor     eax,eax                 ; (eax)=ERROR_NONE

clkfreq_exit:
        RestoreReg <ds>
        ASSUME  DS:NOTHING

        LeaveProc
        retfd   ?aframe

clkfreq_invalid:
        mov     eax,ERROR_INVALID_FREQUENCY  ;  errors
        jmp     clkfreq_exit

EndProc CLKFreq


BREAK <Realtime Clock Interrupt Service Routine>
;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  RTCINT
;*
;* DESCRIPTIVE NAME:  Interrupt Service routine for RT/CMOS Clock
;*
;* FUNCTION:
;*      This routine services RT/CMOS Clock interrupts, giving
;*      control to CP/DOS.  The entry point is associated with IRQ 8
;*      using the DevHlp SetIRQ function in RTINIT.  A periodic timer
;*      interrupt causes it to be invoked and the routine uses the
;*      DevHelp_SchedClock function first (before EOI) to inform
;*      CP/DOS of the timer tick and again later (after EOI) to allow
;*      SchedClock to perform any lengthy periodic operations.
;*
;*      On every update-ended interrupt, the clock device is read
;*      and the Global InfoSeg is updated with interrupts disabled.
;*
;* NOTES:
;*      Following issue of DevHlp_EOI to the Interrupt Manager (before
;*      the second SchedClock call) it is possible for this routine
;*      to be reentered.  Stack usage is kept to a minimum. Re-
;*      entrance should not cause problems, since no real processing
;*      is done by this routine after that point.
;*
;* ENTRY POINT: RTCINT
;*    LINKAGE:  FAR call by Interrupt Manager for IRQ Level = 8
;*
;* USES:  All registers.
;*
;* INPUT:  DS, Cs set up by Intmgr on call to RTCINT
;*
;* EXIT-NORMAL:         RET FAR
;*    RETURN CODE:      CF = 0 if my-interrupt
;*                      CF = 1 if not-my-interrupt
;*
;* EXIT-ERROR:
;*    RETURN CODE:
;*    ERROR MESSAGE:
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  none
;*    ROUTINES:         FIXISEG, GTDAYS
;*
;* EXTERNAL REFERENCES:
;*    STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:         DevHlp_SchedClock,
;*                      DevHlp_EOI
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; enable interrupts
; call GetRegC to get interrupt flags
; enable interrupts
; if update-ended interrupt flag set
;    update_flag = 1
; if periodic interrupt flag not set
;    goto EOI_RET
; endif
;
; disable interrupts to keep hundredths and milliseconds in sync
; Accum8msTicks++;
; increment hundredths by time (in hundredths) since last tick
; increment millseconds by time (in milliseconds) since last tick
; if Hz128To32 (first 32hz tick since RTC programmed from 128->32hz)
;    set increments to 1/32 sec and clear Hz128To32 flag
; if pending request to reprogram RTC frequency
;    if requested RTC frequency is 128hz
;        change rtc frequency to 128hz
;        change interval to 1/128
;        clear accumulated 8ms tick counter (Accum8msTicks)
;        clear pending request to reprogram RTC frequency
;    else (requested RTC frequency is 32hz)
;        if clock is on a 32ms boundary
;            change rtc frequency to 32hz
;            change interval to 2/128ths
;            set Hz128To32 to indicate transition
;            clear pending request to reprogram RTC frequency
; enable interrupts
;
; update_flag = 0
; if previous update_flag != 0
;    call FIXISEG to read clock to update SysInfoSeg
; endif
;
; call SchedClock to do pre-EOI with AX = interval
; if we are nested (i.e. Int_In_Progress != 0 )
;    increment nest_count
;    goto EOI_RET
; endif
; Int_In_Progress = 1
; call DevHlp to issue EOI
;
; nest_count = 0
; call chkINT15 to process int 15 with previous nest_count value
;
; call SchedClock to do post-EOI with AX = post_time
; Int_In_Progress = 0
;
; post_time = 0
; call timer ticker routine with previous post_time value
;
; clear carry flag
; return
;
; EOI_RET:
;    call DevHlp to issue EOI
;    clear carry flag
;    return
;
;*********************** END OF PSEUDO-CODE    **********************

    ASSUME      CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING
RTCINT  PROC    FAR
        public  RTCINT
        STI                             ; Run with interrupts enabled

                                        ; The interrupt managers does a pusha
        CALL    GetRegC                 ; Get register C,(AX) = interrupt flags

; IS THIS AN UPDATE INTERRUPT ?
        TEST    AL,UIMASK               ; Update interrupt?
        JZ      GotPeriodic             ; NO, go process the Periodic Int.
        MOV     Update_Flag, 1          ; Set the Update flag
        TEST    AL,PIMASK               ; Also a periodic interrupt?
        jz      EOI_RET                 ; .. No, EOI and return...
                                        ; .. Yes, process it...

; disable interrupts to keep hundredths and milliseconds in sync
; Accum8msTicks++;
; increment hundredths by time (in hundredths) since last tick
; increment millseconds by time (in milliseconds) since last tick
; if Hz128To32 (first 32hz tick since RTC programmed from 128->32hz)
;    set increments to 1/32 sec and clear Hz128To32 flag
; if pending request to reprogram RTC frequency
;    if requested RTC frequency is 128hz
;        change rtc frequency to 128hz
;        change interval to 1/128
;        clear accumulated 8ms tick counter (Accum8msTicks)
;        clear pending request to reprogram RTC frequency
;    else (requested RTC frequency is 32hz)
;        if clock is on a 32ms boundary
;            change rtc frequency to 32hz
;            change interval to 2/128ths
;            set Hz128To32 to indicate transition
;            clear pending request to reprogram RTC frequency
; enable interrupts

GotPeriodic:

        cli     ; disable ints during update; keep everything synchronous
        inc     Accum8msTicks;  ; only needed if at 128hz, but do always

; increment hundredths by time (in hundredths) since last tick
        les     BX,[InfSeg]                     ; ES:BX -> Global InfoSeg
        mov     ah,BYTE PTR ES:[BX.SIS_HunTime] ; Get current hundredths
        mov     al,Hundred_Frac                 ; align fraction part we keep
        add     ax,Hundred_Inc                  ; add in hundredths tick inc.
        mov     Hundred_Frac,al                 ; save new fractional part

;** the following is a      to account for the case where a user sets the
;  hundredths field ahead, such that ensuing ticks will cause the
;  hundredths to exceed 100.  We prevent this condidtion.  Note that
;  this causes the 100ths to "freeze" at 99 until the next 1 second update
;  interrupt advances the seconds.  Ref: PTM 6670

        cmp     ah,100                          ; over 100?
        jb      NoOverflow                      ; ..no,
        mov     ah,99                           ; ..yes, DO NOT allow it > 100
NoOverflow :
        mov     BYTE PTR ES:[BX.SIS_HunTime],ah ; update Hundredths

; increment millseconds by time (in milliseconds) since last tick
        mov     ax,MS_Inc_Frac          ; get fraction of millisecond increment
        add     MS_Fraction,ax          ; add to running millisecond fraction
        mov     eax,ES:[BX.SIS_MsCount] ; get system's ms count (whole)
        mov     ecx,eax                 ; save to determine actual ms delta
        adc     eax,MS_Inc_Whole        ; add in the whole increment part
        mov     ES:[BX.SIS_MsCount],eax ; update the system's ms count
        sub     eax,ecx                 ; compute actual ms delta
        mov     MS_Delta,ax             ; save delta in word

        cmp     Hz128To32,0             ; First 32hz tick after reprogramming?
        je      NoAdjust                ; .. no, increment is ok as is.
        mov     Hundred_Inc,((100 SHL 8) / 32); .. yes, set increments to 1/32
        mov     MS_Inc_Whole,(1000 / 32)
        mov     MS_Inc_Frac,(((1000 SHL 16) / 32) AND 0FFFFh)
        mov     Hz128To32,0             ; reset flag

NoAdjust:
        cmp     ReqRTCFreq,0    ; pending request to change the rtc frequency?
        je      rtcdone         ; .. no, done with the rtc
        mov     al,ReqRTCFreq   ; Get requested rtc frequency
        cmp     al,CurRTCFreq   ; Are we requesting the current frequency?
        je      hzchanged       ; .. yes, don't reprogram, but mark change
        cmp     al,128          ; request to change to 128hz?
        je      hz128           ; .. yes, safe to do it now
                                ; .. no, change to 32hz only on 32ms border
        mov     ax,Accum8msTicks  ; get #accumulated 8ms ticks
        and     ax,3            ; are we on 1/32nd tick border?
        jnz     rtcdone         ; .. no, not safe to reprogram clock yet

hz32:

; program new periodic interrupt rate to 32hz
; set frequency to indicate 1/32nd interval
; set hundreths, milliseconds, frequency to zero
;   Increment 2/128ths the next tick, rather than a full 1/32 interval,
;   or the rest of the 1/32 current interval remaining.
;   Experiment has shown that incrementing time
;   in this manner keeps MsCount adding up correctly
;   while changing the RTC's frequency.
;   Time will increment at 1/32 starting the tick after next
; clear frequency change request (ReqRTCFreq)

ifdef   MONITOR
; This shows how 8ms ticks accumulate.
ACCM8SIZE EQU 64
        mov     bx,Accum8msTicks        ; get #accumulated 8ms ticks
        cmp     bx,ACCM8SIZE
        jl      inceach
        inc     Accm8p
        jmp     incedone
inceach:
        shl     bx,1
        inc     Accm8[bx]
        shr     bx,1
incedone:
        and     bx,3                    ; ..last 1/32nd tick  (0,1,2 or 3)
        shl     bx,1
        inc     Accm8mod4[bx]
        shr     bx,1
endif


        MOV     AX,((DV32K OR IN032) SHL 8) OR 0AH  ; rtc to 32hz
        cmos_write_clock                       ; do it
        mov     CurRTCFreq,32   ; indicate new 32hz frequency
        mov     Hundred_Inc,((100 SHL 8) / 64) ; set increment to 2/128
        mov     MS_Inc_Whole,(1000 / 64)       ; .. for the next tick only
        mov     MS_Inc_Frac,(((1000 SHL 16) / 64) AND 0FFFFh)
        mov     Hz128To32,1     ; set flag to indicate 128->32hz transition
        jmp     hzchanged       ; 

hz128:
; program new periodic interrupt rate to 128hz
; set hundreths, milliseconds, frequency to indicate 1/128nd interval

        MOV     AX,((DV32K OR IN128) SHL 8) OR 0AH  ; rtc to 128hz
        cmos_write_clock                       ; do it
        mov     CurRTCFreq,128                 ; indicate new 128hz frequency
        mov     Hundred_Inc,((100 SHL 8) / 128); set increments to 1/128
        mov     MS_Inc_Whole,(1000 / 128)
        mov     MS_Inc_Frac,(((1000 SHL 16) / 128) AND 0FFFFh)
        mov     Accum8msTicks,0                ; start counting 7.8125ms ticks

hzchanged:
        mov     ReqRTCFreq,0    ; clear frequency change request indicator

rtcdone:
        sti                     ; enable, all timer counts updated

;*************** Do not change FIXISEG and post_time sequence *********
; Determine if there was an Update Int that occured previously
;          and needs handling now.

        xor     al,al
        xchg    al,Update_Flag          ; set update_flag = 0
        or      al,al                   ; Was there an update interrupt?
        je      Continue                ; No.

; This driver makes every effort to program the clock to 128hz ONLY within
; the first 7.8125ms after a full 32hz tick.  This is required in order for
; increments of the running millisecond counter to stay accurate.  In
; (hopefully rare) cases, higher priority interrupt activity may prevent
; the clock from being reprogrammed to 128hz within the 7.8125ms window.
;
; When the clock is programmed from 32hz to 128hz, system time will
; increment at 7.8125ms starting the next tick.  In the worst case, up to
; 31.25ms of real time may have passed just prior to the reprogramming.
; This can result in up to 24ms of time being "lost".  We attempt to limit
; the accumulation of this "lost time" within a one second period to no
; more than 32ms.  We predict what the MSCount should be one second from
; now.  If it is more than 32ms less, we make a correction.  This also
; helps keep the MsCount accurate in the event of missed periodic
; interrupts.
;
; It is possible that the independent one-second interrupt arrived just
; before the last 32hz periodic interrupt.  In that case, the time will be
; 32ms short of one second.  That is allowed.  But if the time accumulation
; is more than 32ms short, we assume either periodic interrupt(s) were missed
; or several reprogrammings of the clock frequency resulted in more than
; 32ms of "lost time".  In this case, we adjust the running millisecond
; count to our previously predicted value.  This limits the loss to 32ms.

        mov     eax,ExpectedMsCount     ; recall expected millisecond count
        mov     ecx,ES:[BX.SIS_MsCount] ; get the real MsCount
        sub     eax,ecx                 ; Did the real MsCount fall behind?
        jle     NotLagging              ; .. No, continue
        add     ecx,eax                 ; .. Yes, MsCount should be this
        mov     ES:[BX.SIS_MsCount],ecx ; bring it up to speed
        add     MS_Delta,ax             ; This contributes to the delta
ifdef   MONITOR
        add     LagTotal,eax            ; it is nice to know these values..
        inc     LagCount                ; .. to observe timing dynamics.
endif

NotLagging:
        add     ecx,968                 ; We expect at least 1000-32ms to pass
        mov     ExpectedMsCount,ecx     ; save for next time

        push    cx
        mov     cx,1                    ; only read clock once
        call    FIXISEG                 ; Read clock and update Infoseg
        pop     cx

Continue:
        mov     ax,MS_Delta             ; get number of milliseconds transpired
        add     Post_Time,ax            ; Post = n * Pre
;*************** end of sequence problem ************************

        push    DS
        les     BX,SchedClock
        xor     DH,DH                   ; DH=0 to indicate pre-EOI
        call    DWORD PTR ES:[BX]       ; call timer tick routine
        pop     DS

;here we test for nested int or not.  if we are already nested, we issue
;the EOI disabled and return without doing any Post_EOI processing

        MOV     AL,1
        XCHG    Int_In_Progress,AL      ;  set to nested
        OR      AL,AL                   ;  are we nested?
        jz      Not_Nest                ;  if ZR, we are not
        inc     Int_Nested              ;  bump nested counter

EOI_RET:
        cli                             ; b720746
;       MOV     AL,RTCIRQ               ;  interrupt level = 8   remove 76711
;       MOV     DL,DevHlp_EOI           ;                        remove 76711
;       CALL    [DevHlp]                ;  call the EOI devhlp   remove 76711
        DevEOI  <RTCIRQ>,DevHlp         ;                        add    76711
        clc                             ;  always clear CY
        ret                             ;  leave

;we are here if we are in a first level Int handler, we have to account
;for any nesting that may have occured after the EOI

Not_Nest:                               ;  we are first level
;       MOV     AL,RTCIRQ               ;  interrupt level = 8   remove 76711
;       MOV     DL,DevHlp_EOI           ;                        remove 76711
;       CALL    [DevHlp]                ;  call the EOI devhlp   remove 76711
        DevEOI  <RTCIRQ>,DevHlp         ;                        add    76711

; call SchedClock to do post-EOI with AX = post_time
        MOV     DH,01H                  ; DH=1 to indicate post-EOI

        PUSH    DS                      ;  moved this
        LES     BX,SchedClock
        xor     ax,ax
        xchg    ax,Post_Time            ; zero out for possible nests

; Input: AX = # of ms to catch up on, same as Pre if no nesting
        CALL    DWORD PTR ES:[BX]       ; call timer tick routine
        POP     DS                      ;  and this

; reset all variables used to check for nesting
        cli                             ; b720746
        mov     Int_In_Progress,0       ;  clear nested flag
        CLC
        RET
RTCINT  ENDP


BREAK <Routine to Update the Global InfoSeg>
;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  FIXISEG
;*
;* DESCRIPTIVE NAME: Routine to read the RT/CMOS clock and then
;*      update the Global InfoSeg date/time variables.
;*
;* FUNCTION:
;*      Performs a read request to the Realtime Clock device
;*      and then updates the global InfoSeg time/date information.
;*
;* NOTES:
;*      This routine is called by three routines of the clock
;*      device driver.  RTINIT (initialization) and RTCINT (interrupt
;*      handler) use this routine to initialize and update InfoSeg
;*      respectively.
;*
;*      Time is reduced to seconds and preserved for later adding
;*      into secs since 1-1-70.
;*
;* ENTRY POINT: FIXISEG
;*    LINKAGE:  Near (from RTINIT, RTCINT)
;*
;* USES:  Preserves all registers except AX
;*
;* INPUT: CX = # OF RETRY COUNT
;*
;* OUTPUT: (RETURNED)
;*      No interrupt flag modified
;*      Global InfoSeg time/date data is filled in.
;*      AX = status flag is set, CARRY FLAG indicates success
;*
;* EXIT-NORMAL:
;*      CF is cleared
;*
;* EXIT-ERROR:
;*      AX = STATUS word:  DONE, ERROR + DEVICE NOT READY bits set
;*      CF is set
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  none
;*    ROUTINES:  cmos_read_clock, calc_year_sec
;*
;* EXTERNAL REFERENCES:
;*    DATA STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:  none
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; for try_count = 1 to 300
;     read register A
;     if register A shows update is done
;        break
; endfor
;
; if try_count > 300
;    set return code DONE+ERROR+DEVICE NOT READY
;    set carry flag on
;    return
; endif
;
; save interrupt flag
; disable interrupts
; call cmos_read_clockto read seconds, minutes, hours, years,
;      day of month, & months from chip
; restore interrupt flag
; convert seconds, minutes, hours, years, day of month, & months to BCD
;
; call calc_year_sec to update sysinfoseg
; hundredth_count_timer = 0
; HunTime = 0
; restore interrupt flag
; return
;
;*********************** END OF PSEUDO-CODE    **********************

    ASSUME      CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING
FIXISEG PROC    NEAR
        public  FIXISEG
        SaveReg <AX,BX,ES>
                                        ; (begin)
; for try_count = 1 to 300
;     read register A
;     if register A shows update is done
;        break
; endfor
;
; if try_count > 300
;    set return code DONE+ERROR+DEVICE NOT READY
;    set carry flag on
;    return
; endif
;
CHKAGN: MOV     AL,0AH                  ; Address of 'A' register

        pushf                           ; save interrupt flag
        cli                             ; disable interrupts
        cmos_read_clock                 ; Read register 'A'
        push    cs
        call    cmos_popf               ; restore interrupt flag

        TEST    AL,80H                  ; If bit 7 set, cannot read clock
        JZ      FIS2                    ; Okay, go read it
        LOOP    CHKAGN                  ; Try 300 times

        MOV     AX,(STDON+STERR+ERRDNR)
        STC
        JMP     GOBACK                  ; Return Done and Device Not Ready

FIS2:
        push    bp
        sub     sp,18                   ; reserved for clock information
        mov     bp,sp

; call cmos_read_clock to read seconds, minutes, hours, years,
;      day of month, & months from chip
;*                +---------------------+
;*                |  old  BP            |
;*                +---------------------+
;*         BP+16->| ???                 |
;*                +---------------------+
;*         BP+14->| YEAR/BCD |          |
;*                +---------------------+
;*         BP+12->| MONTH/BCD|          |
;*                +---------------------+
;*         BP+10->| DAY OF MONTH        |
;*                |  BCD     |          |
;*                +---------------------+
;*         BP+8 ->|                     |
;*                +---------------------+
;*         BP+6 ->| HOUR/BCD |          |
;*                +---------------------+
;*         BP+4 ->| MIN/BCD  |          |
;*                +---------------------+
;*         BP+2 ->| SEC/BCD  |          |
;*                +---------------------+

        pushf                           ; save interrupt flag
        CLI                             ; DISABLE INTERRUPTS
        MOV     AL,0+80H                ; get seconds
        cmos_read_clock
        mov     second_bcd,al

        MOV     AL,2+80H                ; get minutes
        cmos_read_clock
        mov     minute_bcd,al

        MOV     AL,4+80H                ; get hours
        cmos_read_clock
        mov     hour_bcd,al

        MOV     AL,7+80H                ; get day of month
        cmos_read_clock
        mov     day_month_bcd,al

        MOV     AL,8+80H                ; get month
        cmos_read_clock
        mov     month_bcd,al

        MOV     AL,9+80H                ; get year
        cmos_read_clock
        mov     year_bcd,al

        cmos_reset                      ; cmos RAM=>read port w/NMI enable
        push    cs
        call    cmos_popf               ; restore interrupt flag

; convert seconds, minutes, hours, years, day of month, & months to BCD

        mov     al,second_bcd           ; Get seconds
        BCD2BN                          ; Convert it
        MOV     second_bin,al           ; Stuff it in table

        mov     al,minute_bcd           ; get minutes
        BCD2BN
        MOV     minute_bin,al           ; Stuff it in table

        MOV     AL,hour_bcd             ; Same for hours
        BCD2BN
        MOV     hour_bin,al             ; Stuff it in table

        MOV     AL,day_month_bcd        ; Same for day of month
        BCD2BN
        MOV     day_month_bin,al

        MOV     al,month_bcd            ; get month
        BCD2BN
        MOV     month_bin,al

        MOV     al,year_bcd             ; get month
        BCD2BN
        MOV     year_bin,al

        pushf                           ; save interrupt flag
        call    far ptr calc_year_sec   ; AX:DX=seconds from 1-1-1970
        mov     ES:[BX.SIS_HunTime],00H
        push    cs
        call    cmos_popf               ; restore interrupt flag
        add     sp,18                   ; restore local stack
        pop     bp
        CLC

GOBACK:
        RestoreReg <ES,BX,AX>
        RET                             ; Return AX
FIXISEG ENDP


;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  calc_year_sec
;*
;* DESCRIPTIVE NAME: Calculate total seconds and current year
;*
;* FUNCTION:
;*      Calculate the total seconds from 1-1-1970, and get the
;*      current year in century year, e.g., 1987.
;*
;* NOTES:
;*      This routine is called by three routines of the clock
;*      device driver.  FIXISEG initializes and updates InfoSeg.
;*      The RTWRIT sub-module uses it to update
;*      InfoSeg following a resetting of the Realtime Clock device.
;*
;*      Time is reduced to seconds and preserved for later adding
;*      into secs since 1-1-70.
;*
;* ENTRY POINT: calc_year_sec
;*    LINKAGE:  Far (from FIXISEG, RTWRIT)
;*
;* USES:  Preserves all registers except AX
;*
;* INPUT:
;*                +---------------------+
;*                |  old  BP            |
;*                +---------------------+
;*         BP+16->|     ???             |
;*                |                     |
;*                +---------------------+
;*         BP+14->| YEAR/BCD | YEAR/BIN |
;*                +---------------------+
;*         BP+12->| MONTH/BCD| MON/BIN  |
;*                +---------------------+
;*         BP+10->| DAY OF MONTH        |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+8 ->| DAY OF WEEK         |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+6 ->| HOUR/BCD | HOUR/BIN |
;*                +---------------------+
;*         BP+4 ->| MIN/BCD  | MIN/BIN  |
;*                +---------------------+
;*         BP+2 ->| SEC/BCD  | SEC/BIN  |
;*                +---------------------+
;*         BP   ->| CENTURY  |HUNDREDTH |
;*                |  BINARY  |  BINARY  |
;*                +---------------------+
;*
;* OUTPUT: (RETURNED)
;*      ES:BX = address of SysInfoSeg
;*      Global InfoSeg time/date data is filled in.
;*      Interrupt Disabled.
;*                +---------------------+
;*                |  old  BP            |
;*                +---------------------+
;*         BP+16->| CURRENT CENTURY YEAR|
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+14->| YEAR/BCD | YEAR/BIN |
;*                +---------------------+
;*         BP+12->| MONTH/BCD| MON/BIN  |
;*                +---------------------+
;*         BP+10->| DAY OF MONTH        |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+8 ->| DAY OF WEEK         |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+6 ->| HOUR/BCD | HOUR/BIN |
;*                +---------------------+
;*         BP+4 ->| MIN/BCD  | MIN/BIN  |
;*                +---------------------+
;*         BP+2 ->| SEC/BCD  | SEC/BIN  |
;*                +---------------------+
;*         BP   ->| CENTURY  |HUNDREDTH |
;*                |  BINARY  |  BINARY  |
;*                +---------------------+
;*
;* EXIT-NORMAL:
;*
;* EXIT-ERROR:
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  none
;*    ROUTINES:  FixDayOfWeek, GTDAYS
;*
;* EXTERNAL REFERENCES:
;*    DATA STRUCTURES:
;*    ROUTINES:  none
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; if hours > 12
;    factor = 1
;    hours -= 12
; else
;    factor = 0
; total_seconds = seconds + minutes * 60 + hours * 3600
;
; if year > 99 or month > 12 or month = 0 or Day_of_Month = 0 or
;    Day_of_Month > 31
;    set year = 80
;    set date = 1-1
;    set Day_of_Week = 2
; endif
; if year is over 20 centry ( year < 80 )
;    year += 100
; year += 1900
; save year in century_year stack
; call GTDAYS to get DAYS since 1-1-80
; total_time from 1-1-70 =
;      ( (DAYS+# of days from 1-1-70 to 12-31-79) * # of 12 hours in a day +
;       factor ) * seconds in 12 hours + total_seconds
; disable interrupts
; save all the information in SysInfoSeg
; return
;
;*********************** END OF PSEUDO-CODE    **********************

calc_year_sec PROC    FAR
    ASSUME      CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING

        SaveReg <CX,DX,SI,DI>
                                        ; (begin)
; if hours > 12
;    factor = 1
;    hours -= 12
; else
;    factor = 0
; total_seconds = seconds + minutes * 60 + hours * 3600
; read Day_of_Month, month, & year from chip
        mov     al,second_bin           ; get seconds
        XOR     AH,AH
        MOV     SI,AX                   ; Save sec's

        mov     al,minute_bin           ; get minutes
        MOV     CL,60                   ; secs/min
        MUL     CL
        ADD     SI,AX                   ; SI = secs in mins and secs

        xOR     AH,AH
        mov     AL,hour_bin             ; get hours
        XOR     CX,CX                   ; ch=day of week; cl=factor
        CMP     AL,12                   ; >12 hours?
        JBE     calc10                  ; (M003)
        SUB     AL,12
        INC     cl                      ; add factor = 1

calc10: PUSH    CX
        MOV     CX,3600
        MUL     CX                      ; AX = secs in hrs remainder
        POP     CX
        ADD     SI,AX                   ; SI = secs in 12 hr time

; Here we do a VERY rough bounds check on the Year, Month, Day and DOW
;
; if year > 99 or month > 12 or month = 0 or Day_of_Month = 0 or
;    Day_of_Month > 31
;    set year = 80
;    set date = 1-1
;    set Day_of_Week = 2
;
        mov     dh,month_bin
        mov     dl,day_month_bin
        MOV     AL,year_bin             ; get year

        CMP     AL,99                   ; Year > 99?
        JA      DATERR
        OR      DH,DH                   ; Month = 0?
        JZ      DATERR
        CMP     DH,12                   ; Month > 12?
        JA      DATERR
        OR      DL,DL                   ; Day = 0?
        JZ      DATERR
        CMP     DL,31                   ; Day > 31?
        JLE     DATEOK

DATERR: MOV     AL,80
        MOV     DH,1
        MOV     DL,1                    ; Setup for 1-1-80 if     date in RTC
        MOV     CH,2                    ; 1-1-80 was a Tuesday (M003)
        mov     month_bin,dh
        mov     day_month_bin,dl
        mov     year_bin,al

DATEOK:
; if year is over 20 centry ( year < 80 )
;    year += 100
        XOR     AH,AH                   ; AX = LOWER PART OF YEAR
        CMP     AL,80                   ; Is it 20xx?
        JGE     calc20                  ; No, Add only 1900
        ADD     AX,100                  ; Yes, add extra 100

; year += 1900
; save on stack
calc20: ADD     AX,1900                 ; AX = Year and century
        MOV     century_year,ax         ; 
        SUB     AX,1980                 ; Get years since 1980

; call GTDAYS to get DAYS since 1-1-80
;* INPUT:     AX = Years since 1-1-80
;*            DH = Current month
;*            DL = Current day
        CALL    GTDAYS                  ; AX=Get days since 1-1-80

; if Day_of_Week != 2
;    call FixDayOfWeek to get Day_of_Week
;
        OR      CH,CH                   ; Is day_of_week correct?
        JNZ     CENT20                  ; Yes, jump
        CALL    FixDayOfWeek
        mov     day_week_bin,ch         ; save on stack

CENT20:
; total_time from 1-1-70 =
;      ( (DAYS+# of days from 1-1-70 to 12-31-79) * # of 12 hours in a day +
;       factor ) * seconds in 12 hours + total_seconds
        XOR     BX,BX                   ; Clear it
        ADD     AX,DAYS1                ; Add in days 1-1-70 thru 12-31-79
        SHL     AX,1                    ; AX = # of 12 hour segments
        rcl     bl,1                    ; Save overflow (carry) in bl
        xor     ch,ch                   ; CX = add factor (0/1)
        add     ax,cx                   ; AX=Low 16 bits of 12 hour segments
        MOV     CX,SECP12               ; CX = Seconds in 12 hours
        MUL     CX                      ; [DXAX] = Secs in Low 16 bits of segs
        shr     bl,1                    ; Any beyond Low 16 bits?
        jnc     calc30                  ; No, [DXAX] = Total seconds

        add     dx,cx                   ; Yes, fudge bit 17 multiply
calc30: add     ax,si                   ; Add in secs in current time
        adc     dx,0                    ; ...and any carry

        LES     BX,[InfSeg]             ; ES:BX -> InfoSeg
        cli                             ; disable interrupts
        MOV     WORD PTR ES:[BX.SIS_BigTime],AX
        MOV     WORD PTR ES:[BX.SIS_BigTime+2],DX
        mov     al,second_bin           ; get seconds
        MOV     ES:[BX.SIS_SecTime],AL  ; Stuff it in table
        mov     al,minute_bin           ; get minutes
        MOV     ES:[BX.SIS_MinTime],AL
        mov     al,hour_bin             ; get hour
        MOV     ES:[BX.SIS_HrsTime],AL  ; InfoSeg time is complete
        mov     al,day_month_bin        ; get day of month
        MOV     ES:[BX.SIS_DayDate],al
        mov     al,month_bin            ; get month
        MOV     ES:[BX.SIS_MonDate],al
        mov     al,day_week_bin         ; get day of week
        MOV     ES:[BX.SIS_DOWDate],al  ; save the day (DOW)
        mov     ax,century_year
        MOV     ES:[BX.SIS_YrsDate],AX  ; InfoSeg date is complete

        RestoreReg <DI,SI,DX,CX>
        RET
calc_year_sec ENDP

; restore interrupt flag
CMOS_POPF       PROC
                IRET                    ; RESTORE FLAGS
CMOS_POPF       ENDP

;********************** START OF SPECIFICATIONS *********************
;**     GetRegC - Get interrupt status from register C
;*
;*      This reads the C register (interrupt ID) TWICE to make
;*      sure that the IRQF will be clear.  The IRQF directly corresponds
;*      to the hardware IRQ line to the 8259.  Another interrpt can become
;*      pending while we are reading the first Interrupt.  If this happens
;*      then the IRQF line will REMAIN set and then it needs to be read
;*      again.  This routine is to assure that the IRQ line will drop.
;*
;*      NOTE: on the XT286 the clock is different than the AT clock.
;*      The XT clock requires that an OUT is done before each IN to a port.
;*      This is because of the Stand-by mode of the XT286.
;*
;*      ENTRY   none
;*
;*      EXIT    [AL] = Byte recieved from register C
;*
;*      USES    Preserves all registers except AX
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; save interrupt flag
; disable interrupts
; select register C to get register C 1st value
; select register C to get register C 2nd value
; restore interrupt flag
; combine register C 1st & 2nd values
; if register C combine value = 0
;    set zero flag
; else
;    clear zero flag
; return
;
;*********************** END OF PSEUDO-CODE    **********************

GetRegC PROC    NEAR

        pushf                           ; save interrupt flag
        MOV     ax,0C0ch                ; register C
        cli                             ; disable interrupts
        cmos_read_clock                 ; Get register value
        xchg    AH,AL                   ; Save this value
        cmos_read_clock                 ; Get register c value One more time
        push    cs
        call    cmos_popf               ; restore interrupt flag

        OR      AL,AH                   ; combine 2 register C values
        RET

GetRegC ENDP


;***LP  PTInt - Timer 0 Interrupt Handler
;
;       ENTRY
;           ds -> ClkData (interrupt manager guarantee this)
;
;       EXIT
;           carry flag clear
;
;       USES
;           All
;
;       CONTEXT
;           Interrupt
;
;       PSEUDOCODE
;           issue EOI to PIC;

        ASSUME  CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING
Procedure PTInt,far

;TMR -- Update Rollover count and Tmr value.
        SaveReg <ds>
        mov     dx,WORD PTR pqwTmr
        lds     bx,pqwTmrRollover
        ASSUME DS:NOTHING
.386p
        mov     eax,DWORD PTR [bx].qw_ulLo
        mov     ecx,eax                         ; (ECX) = current rollover count.
        mov     eax,DWORD PTR [bx].qw_ulHi      ; Set Current rollover count =
        mov     DWORD PTR [bx].qw_ulLo,eax      ; Next rollover count.

        mov     bx,dx                           ; (DS:BX) -> qwTmr.
                                                ; ASSUMES SAME DS.
        add     DWORD PTR [bx].qw_ulLo,ecx
        adc     DWORD PTR [bx].qw_ulHi,0
        jo      short pti40
CPUMode Reset

pti20:  RestoreReg <ds>
        ASSUME DS:ClkData

        test    fsPTFlags,PTF_OWNT0     ; ptimer owns T0?
        jnz     pti30                   ; YES, skip the rest

        test    fsPTFlags,PTF_TICKENABLE; vtimer enables tick?
        jz      pti30                   ; NO, skip the rest

        push    WORD PTR 0
        push    VTDCMD_TICKEVENT        ; (TOS+8)=VTDCMD_TICKEVENT

        push    WORD PTR 0
        push    WORD PTR 0              ; (TOS+4)=f16p1=0

        push    WORD PTR 0
        push    WORD PTR 0              ; (TOS)=f16p2=0


        .386p
        call    FWORD PTR fpfnVTProc
        CPUMode Reset

pti30: ; mov     al,TIMERIRQ             ; issue EOI to PIC      remove 76711
       ; mov     dl,DevHlp_EOI                                   remove 76711
       ; call    [DevHlp]                                        remove 76711
        DevEOI   <TIMERIRQ>,DevHlp      ;                        add    76711

        clc
        ret

.386p
pti40:  mov     DWORD PTR [bx].qw_ulLo,QW_MAX_LO
        mov     DWORD PTR [bx].qw_ulHi,QW_MAX_HI
        jmp     short pti20
CPUMode Reset

EndProc PTInt

;***LP  RTINIT2 - 2nd stage init
;
;      FUNCTION
;          Because RIPL with ETHERNET/PC-NET uses IRQ0, we must delay
;          hooking IRQ0 until after loadable device drivers/IFS are
;          installed.  The kernel will call with command 0 (loadable
;          device driver init) when it's OK to hook IRQ 0, and this
;          routine will be invoked.
;
;      ENTRY
;          ds -> ClkData
;
;      EXIT
;          carry flag clear
;
;      USES
;          All
;
;      CONTEXT
;          Interrupt
;
;      PSEUDOCODE
;          clear IRQ 0 latch;
;          issue EOI to PIC;
    ASSUME      CS:ClkSwap,DS:ClkData,ES:NOTHING,SS:NOTHING
RTINIT2 PROC FAR
        public  RTINIT2

        SaveReg <bx,cx,dx,di>

;Initialize physical timer 0 to proper mode running at the rate of 18.2Hz

        mov     al,SC_CNT0+RW_LSBMSB+CM_MODE2
        out     PORT_CW,al              ;program count mode of timer 0
        IODelay
        xor     al,al
        out     PORT_CNT0,al            ; program timer 0 count
        IODelay                         ; count == 0h actually means 10000h
        out     PORT_CNT0,al            ; => approx. 18.2Hz
        IODelay

;DevHlp(DevHlp_SetIRQ,TIMERIRQ,pfnPTInt,NO_INTSHARING)

        mov     ax,offset ClkCode:PTInt ; (ax)=interrupt handler offset
        mov     bx,TIMERIRQ             ; (bx)=IRQ number
        mov     dx,DevHlp_SetIRQ        ; (dh)=No interrupt sharing
        call    [DevHlp]                ; hook timer 0 interrupt

        RestoreReg <di,dx,cx,bx>
        mov     AX,STDON                ; Done
        ret

RTINIT2  ENDP

ClkSwap SEGMENT

public  endswapcode
endswapcode     label   byte

ClkSwap ENDS


BREAK <Realtime Clock Initialization Routine>
;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  RTINIT
;*
;* DESCRIPTIVE NAME:  RTC Initialization Function 0
;*
;* FUNCTION:    Initializes data structures and the Realtime Clock
;*              device.
;*
;* ENTRY POINT: RTINIT
;*    LINKAGE:  FAR from RTENTR
;*
;* NOTES:  Interrupt flag is modified
;*
;* USES:  AX, DX.  Preserves others.
;*
;* INPUT: (PARAMETERS)
;*      ES:BX = pointer to Request Packet
;*
;* OUTPUT: (RETURNED)
;*      No interrupt flag modified
;*      Request Packet filled in.
;*
;* EXIT-NORMAL:
;*      AX = Status flag is "DONE"
;*
;* EXIT-ERROR:
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  none
;*    ROUTINES:   FIXISEG
;*
;* EXTERNAL REFERENCES:
;*    STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:  DevHlp_GetDOSVar, DevHlp_SetIRQ
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; set the pointer, DevHlp, to the DevHelp Router
; call DevHlp to get the address of SysInfoSeg in ES:BX
; if DevHlp failed
;    goto abort
; save the address of SysInfoSeg in InfSeg
;
; get pointer to DevConfigTbl from SystemAnchorSegment(SAS)
; get the TYPE byte from DevConfigTbl
; based on TYPE byte, store correct CMOS location in
;
; call DevHlp to get the address of SchedClock in ES:BX
; save the address of SchedClock
; initialize register B to periodic interrupt enable,
;            update-endedinterrupt enable, and 24 hour mode
; initialize register A to 32.768kHz square wave output, and
;            31.25 millisecond periodic interrupt rate
; read register C to initialize it
; read register D to initialize it
; call DevHlp to set non-sharing IRQ 8 to routine RTCINT
; if DevHlp failed
;    goto abort
;
; call GetRegC to read register C
; if zero flag not set
;    goto abort
; set up end of code & data segment for SysInit
;
; call FIXISEG to initialize SysInfoSeg from the data on the clock
; if carry flag set
;    goto abort
;
; call DevHlp to set int 1A to point to routine int1Artn
; if DevHlp failed
;    goto abort
; save old int 1A vector in Int1AChain
;
; call DevHlp to set int 15 to point to routine int15rtn
; if DevHlp failed
;    goto abort
; save old int 15 vector in Int15Chain
; set return code = DONE
; return
;
; abort:
;       set return code = DONE+ERROR+GENERAL FAIL
;       set end of code & data segment to zero
;       return
;
;*********************** END OF PSEUDO-CODE    **********************

    ASSUME      CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING
RTINIT  PROC FAR
        public  RTINIT

        MOV     AX,WORD PTR ES:[BX].InitDevHlp   ;  Save away the pointer to
        MOV     WORD PTR DevHlp,AX               ;  the DevHelp Router.
        MOV     AX,WORD PTR ES:[BX].InitDevHlp+2
        MOV     WORD PTR DevHlp+2,AX

;%    Temporary      to fix loader problem for which the data selector
;%    does not get fixed up.

ES_DI   EQU     1                       ;equate for PhysToVirt DevHlp service

        extrn   selClkData:word

        SaveReg <bx,es>
        SaveReg <ds>
        push    ds
        pop     es
        ASSUME  es:ClkData
        push    cs
        pop     ds
        ASSUME  ds:NOTHING

        mov     si,offset selClkData    ;(ds:si)->selClkData
        mov     dl,DevHlp_VirtToPhys
        call    es:[DevHlp]             ;(ax:bx)=physical address of selClkData

        mov     cx,2                    ;length of word we need to write into
        mov     dx,DevHlp_PhysToVirt + ES_DI*100h
        call    es:[DevHlp]             ;(es:di)->selClkData (writeable!)

        ASSUME  es:NOTHING
        RestoreReg <ds>
        ASSUME  ds:ClkData
        mov     es:[di],ds              ;save ds into code segment

        RestoreReg <es,bx>
        ASSUME  es:NOTHING

        mov     dl,DevHlp_UnPhysToVirt  ;free selector
        call    [DevHlp]

;% END hack

        PUSH    ES
        PUSH    BX

        MOV     AL,1                     ; Variable number of SysInfoSeg
        MOV     DL,DevHlp_GetDOSVar
        CALL    [DevHlp]
        jnc     rtok
        jmp     rtabort2                ; if carry flag set, abort clock

rtok:   MOV     ES,AX                    ; ES:BX -> SysInfoSeg
        MOV     AX,ES:[BX]               ; AX = InfoSeg segment selector
        MOV     WORD PTR InfSeg+2,AX     ; Stuff for later

        mov     ax,SAS_selector          ; AX = SAS selector = DevConfigTbl Sel
        mov     es,ax                    ; ES = DevConfigTbl selector
        mov     bx,es:[SAS_config_data]  ; BX = @ of DevConfigTbl off.
        mov     bx,es:[bx]               ; BX = DevConfigTbl off.

;;;     Decide wether to use the AT or the PS/2 CMOS address.
;;;     Refer to DCR 1085.
        mov     al,es:[bx].Config_century_location ;AL = century byte location  64012

        mov    CMOS_location,al                  ; store CMOS LOCATION in var.

        MOV     DL,DevHlp_SchedClock     ; ***** FOR DCR 487 *****
        CALL    [DevHlp]                 ; ES:BX = address of SchedClock pointer
        MOV     WORD PTR SchedClock,BX   ; Save away the SchedClock ptr address
        MOV     WORD PTR SchedClock+2,ES ; **

                                        ; (begin)
        pushf                           ; save interrupt flag
        CLI                             ; DISABLE INTERRUPTS FOR CMOS ACCESS

; Set hundredths and milliseconds increments to initial values, based on
; initial periodic interrupt frequency of 32hz.

        mov     Hundred_Inc,((100 SHL 8) / RTCHZINIT)
        mov     MS_Inc_Whole,(1000 / RTCHZINIT)
        mov     MS_Inc_Frac,(((1000 SHL 16) / RTCHZINIT) AND 0FFFFh)

        MOV     AX,(RBINIT SHL 8) OR 0BH        ; Register B
        cmos_write_clock                ; Initialize it
        MOV     AX,(RABYTE SHL 8) OR 0AH        ; Register A
        cmos_write_clock                ; Initialize it
        MOV     AL,0CH                  ; Register C
        cmos_read_clock                 ; Read to initialize
        MOV     AL,0DH                  ; Register D
        cmos_read_clock                 ; Read to initialize
        cmos_reset                      ; cmos RAM=>read port w/NMI enable
        push    cs
        call    cmos_popf               ; restore interrupt flag

        MOV     BX,RTCIRQ               ; IRQ Number
        XOR     DH,DH                   ; Not Sharing this IRQ !
        MOV     AX,OFFSET ClkCode:RTCINT ; Get the handler address
        MOV     DL,DevHlp_SetIRQ        ; Dev_Hlp Set interrupt function
        CALL    [DevHlp]                ; Install interrupt handler
        JNC     Get_RegC                ; if carry flag set, abort clock

rtabort2:
        POP     BX
        POP     ES
rtabort3:
        MOV     AX,(STDON+STERR+ERRGFAIL) ; set error code for general failure
        MOV     es:[bx].InitEcode,0     ; Set up end of Code Segment
        MOV     es:[bx].InitEdata,0     ; Set up end of Data Segment
        RET

Get_RegC:
;       Clear out any pending interrupts.  This is here for a     fix.
;       After the clock is initialized and BEFORE the 8259 slave is told
;       about this IRQ line, the clock can interrupt.  If this happens
;       the clock will be waiting for this interrupt to be serviced and
;       no one will know about it.  Thus hanging the system since things
;       like the floppy driver depend on it.
        CALL    GetRegC                 ; Get register C (interrupt flags)

        POP     BX
        POP     ES
        MOV     AX,offset RTINIT        ;  Set up end of Code Segment
        MOV     es:[bx].InitEcode,AX
        MOV     AX,offset ATDataEnd     ;  Set up end of Data Segment
        MOV     es:[bx].InitEdata,AX
                                        ; (end)

        push    cx
        mov     cx,200                  ; read 200 times clock
        CALL    FIXISEG                 ; Initialize InfoSeg from Clock
        pop     cx
        JC      rtabort3                ; fixiseg could have failed

;% Clk/Tmr registration and Initialization

ClkInit label near
        public ClkInit

;DevHlp(DevHlp_RegisterPDD,pszClkName,fpfnClkVDDProc)
        SaveReg <es,bx>
        mov     dl,DevHlp_RegisterPDD   ;(dl)=DevHlp_RegisterPDD
        mov     si,offset ClkData:szClkName;(ds:si)->szClkName
        mov     di,cs
        mov     es,di
        mov     di,offset ClkCode:ClkVDDProc;(es:di)->ClkVDDProc
        call    [DevHlp]                ;(ax)=error code
        RestoreReg <bx,es>
        or      ax,ax                   ;error?
        jnz     rtabort3                ;YES, goto error exit

;DevHlp(DevHlp_RegisterPDD,pszTmrName,fpfnPTmrProc)
        SaveReg <es,bx>
        mov     dl,DevHlp_RegisterPDD   ;(dl)=DevHlp_RegisterPDD
        mov     si,offset ClkData:szTmrName;(ds:si)->szTmrName
        mov     di,cs
        mov     es,di
        mov     di,offset ClkCode:PTVDMProc;(es:di)->PTVDMProc
        call    [DevHlp]                ;(ax)=error code
        RestoreReg <bx,es>
        or      ax,ax                   ;error?
        jnz     rtabort3                ;YES, goto error exit

;DevHlp(DevHlp_RegisterBeep,fpfnPTBeep)
        mov     dl,DevHlp_RegisterBeep  ;(dl)=DevHlp_RegisterBeep
        mov     cx,cs
        mov     di,offset ClkCode:PTBeep;(cx:di)->PTBeep
        call    [DevHlp]
        or      ax,ax                   ;error?
        jnz     rtabort3                ;YES, goto error exit

        call    PTBeepOff               ;disable speaker

;DevHlp(DevHlp_RegisterFreq,fpfnCLKFreq)
        mov     dl,DevHlp_RegisterFreq  ;(dl)=DevHlp_RegisterFreq
        mov     cx,cs
        mov     di,offset ClkCode:CLKFreq;(cx:di)->CLKFreq
        call    [DevHlp]
        or      ax,ax                   ;error?
        jnz     rtabort3                ;YES, goto error exit


;Initialize TMR device (PIT Ctr 0).

        SaveReg <bx,cx,dx,di>

        mov     cx,cs
        mov     di,offset ClkCode:PTTimer0;(cx:di)->PTTimer0
        mov     dl,DevHlp_RegisterTmrDD
        call    [DevHlp]

;       di:bx -> qwTmrRollover (16:16 ptr).
;       di:cx -> qwTmr (16:16 ptr).

        or      di,di                   ;Valid Selector?
        jnz     cinit2                  ; Yes.

cinit1: mov     di,ds                   ;Invalid, so store valid ptr.
        mov     bx,offset qwTmrRollover
        mov     cx,offset qwTmr
cinit2:                                 ;Store (16:16) pointers.
        mov     WORD PTR pqwTmrRollover,bx      ;Store offset.
        mov     WORD PTR pqwTmrRollover+2,di    ;Store segment.
        mov     WORD PTR pqwTmr,cx      ;Store offset.
        mov     WORD PTR pqwTmr+2,di    ;Store segment.

;Initialize current rollover count = next rollover count = 10000h.

        SaveReg <ds>
        mov     ds,di
.386p
        mov     DWORD PTR [bx].qw_ulLo,10000h
        mov     DWORD PTR [bx].qw_ulHi,10000h
CPUMode Reset
        RestoreReg <ds>

        RestoreReg <di,dx,cx,bx>

        mov     AX,STDON                ; Done
        ret

RTINIT  ENDP

ClkCode ENDS

        END
