;
;                   IBM Confidential
;                   Copyright (C) IBM Corporation 1981-1992
;                   All Rights Reserved.
;------------------------------------------------------------------------------
;M074 - ptime.asm added, containing clock/time routines for power.asm.
;
;   09/11/91  SMR       M077: B#2669. Registered POWER's 2f channels in mult.inc
;
;   09/25/91  NSM       M090: B#2729. Try to update our time from CMOS under
;                       WIN ENH mode once in 1024 I1c ticks.
;                       (approx. once in a minute)
;                       This update happens only if DOS calls us for time and
;                       does not exactly happen once in a minute.
;
;                       (this is changed to 20 secs from 1 minute)
;
;  11/26/91   NSM       M101: We lose date sometimes under windows. To fix this
;                       do Int 1a's to get tick count instead of looking at 40:
;                       6ch and if we get rollover, then go & update date
;                       and time from CMOS.
;------------------------------------------------------------------------------
;
;
IF 1
VCB_OPT = 1     ; Space optimizations
ENDIF

        .xlist

        include version.inc     ; set build flags

IFDEF   POWER                   ; generate code only if power management
                                ; is enabled

IFNDEF  POWERALONE              ; segment declarations for resident version

        include biosseg.inc     ; establish bios segment structure

ELSE                            ; segment declarations for standalonde version

.SEQ
Bios_Code       segment word public 'Bios_Code'
Bios_Code       ends

Bios_Data       segment word public 'Bios_Data'
Bios_Data       ends

SysInitSeg      segment word public 'system_init'
SysInitSeg      ends

; following segment definitions used by transient part of code

ENDIF

        include msequ.inc
        include devsym.inc
        include bpb.inc
        include ioctl.inc
        include mult.inc                ; M077
        include power.inc

IFDEF INCL_APM
        include apmequ.inc              ; M001
ENDIF

break   macro
        endm

        include error.inc
        .list


        include msgroup.inc     ; define Bios_Data segment

IFNDEF VCB_OPT
; Delete unnecessary table month_table
        extrn   month_table:word
ENDIF

IFDEF   POWERALONE                      ; standalone device driver version
Bios_Res        dw      Bios_Code       ; Our code segment address
ELSE                                    ; resident BIOS version
        extrn   Bios_Res:word           ; Code segment address supplied externally
        extrn   ttticks:dword           ; far ptr to time_to_ticks routine

IFNDEF VCB_OPT
; Delete external reference to bin_to_bcd, always use inline
        extrn   bintobcd:dword          ; ptr to bin_to_bcd routine
ENDIF

IFDEF   INCL_APM
        extrn   Check_and_Init_APM_Ptr:dword    ; ptr to APM init routine
ENDIF   ;INCL_APM

        extrn   P_UpdFromCMOS_Ptr:dword ; ptr to CMOS clock read        ; M081

ENDIF   ;NOT POWERALONE

        extrn   daycnt:word     ; extrns for both resident and stand-alone
        extrn   daycnt2:word    ; versions
        extrn   base_century:byte
        extrn   base_year:byte
        extrn   month_tab:byte
        extrn   bin_date_time:byte
        extrn   CMOSUpdFlg:byte
        extrn   CMOSPollCount:word

        tocode
IFDEF   POWERALONE                      ; stand alone version

Bios_Data_Word  dw      Bios_Data               ; Our data segment

bintobcd        proc    near    ;for real time clock support

;convert a binary input in al (less than 63h or 99 decimal)
;into a bcd value in al.  ah destroyed.

IFDEF VCB_OPT
; Use AAD 16 (D5h 10h) - faster and smaller
        aam
        db      0d5h,10h
ELSE
        push    cx

        aam                             ; M048
        mov     cl, 4                   ; M048
        shl     ah, cl                  ; M048
        or      al, ah                  ; M048

        pop     cx
ENDIF

        ret

bintobcd        endp

ELSE                            ; resident version
        extrn   Bios_Data_word:word

ENDIF

        public  tim_read
        public  tim_writ
;--------------------------------------------------------------------
;
; tim_writ sets the current time
;
; on entry es:[di] has the current time:
;
;       number of days since 1-1-80     (word)
;       minutes (0-59)                  (byte)
;       hours (0-23)                    (byte)
;       hundredths of seconds (0-99)    (byte)
;       seconds (0-59)                  (byte)
;
; each number has been checked for the correct range.
;
tim_writ proc   near
        assume  ds:Bios_Data
        mov     ax,word ptr es:[di]
        push    ax              ;daycnt. we need to set this at the very
                                ;  end to avoid tick windows.

;       Set hardware clock time.

        mov     al,es:[di+3]            ;get binary hours
        call    bintobcd                ;convert to bcd
        mov     ch,al                   ;ch = bcd hours
        mov     al,es:[di+2]            ;get binary minutes
        call    bintobcd                ;convert to bcd
        mov     cl,al                   ;cl = bcd minutes
        mov     al,es:[di+5]            ;get binary seconds
        call    bintobcd                ;convert to bcd
        mov     dh,al                   ;dh = bcd seconds
        mov     dl,0                    ;dl = 0 (st) or 1 (dst)
        cli                             ;turn off timer
        mov     ah,03h                  ;set rtc time
        int     1ah                     ;call rom bios clock routine
        sti

        mov     cx,word ptr es:[di+2]
        mov     dx,word ptr es:[di+4]
IFDEF POWERALONE
        call    time_to_ticks
ELSE
        call    ttticks
ENDIF
                                ;cx:dx now has time in ticks
        cli                     ; turn off timer
        mov     ah, 1           ; command is set time in clock
        int     1ah             ; call rom-bios clock routines
        pop     [daycnt]
        sti

        call    daycnttoday     ; convert to bcd format
        cli                     ; turn off timer
        mov     ah,05h          ; set rtc date
        int     1ah             ; call rom-bios clock routines
        sti

        clc
        ret
tim_writ endp

;
; gettime reads date and time
; and returns the following information:

;       es:[di]  =count of days since 1-1-80
;       es:[di+2]=hours
;       es:[di+3]=minutes
;       es:[di+4]=seconds
;       es:[di+5]=hundredths of seconds

tim_read proc   near

; M090 BEGIN - See if we have to update our time from CMOS before
; returning date and time to caller.

        cmp     [CMOSUpdFlg],0          ; M090 do we need to update from CMOS
        je      tr_NoCMOSUpdate         ;

tr_CMOSUpd:                             ; M101
IFDEF   POWERALONE                      ; M081
        call    far ptr P_UpdFromCMOS
ELSE
        call    P_UpdFromCMOS_ptr       ; M074 update our date and time
                                        ; from CMOS RTC
ENDIF
        mov     [CMOSPollCount],MAXCMOSPOLLCOUNT
        mov     [CMOSUpdFlg],0          ;
; M090 END

tr_NoCMOSUpdate:
; M101  - BEGIN - get tick count through 1a instead of looking at 40:6ch
; and check for rollover
        mov     ax,0                    ; get tick count
        int     1ah                     ; cx:dx = tick count
        or      al,al                   ; al != 0 if midnight passed
        jnz     tr_CMOSUpd              ; rollover; update date and time
; M101  - END
        mov     si,[daycnt]

; we now need to convert the time in tick to the time in 100th of
; seconds.  the relation between tick and seconds is:
;
;                65536 seconds
;              ----------------
;               1,193,180 tick
;
; to get to 100th of second we need to multiply by 100. the equation is:
;
;       ticks from clock  * 65536 * 100
;      ---------------------------------  = time in 100th of seconds
;               1,193,180
;
; fortunately this fromula simplifies to:
;
;       ticks from clock * 5 * 65,536
;      --------------------------------- = time in 100th of seconds
;               59,659
;
; the calculation is done by first multipling tick by 5. next we divide by
; 59,659.  in this division we multiply by 65,536 by shifting the dividend
; my 16 bits to the left.
;
; start with ticks in cx:dx
; multiply by 5

        mov     ax,cx
        mov     bx,dx
        shl     dx,1
        rcl     cx,1            ;times 2
        shl     dx,1
        rcl     cx,1            ;times 4
        add     dx,bx
        adc     ax,cx           ;times 5
        xchg    ax,dx


; now have ticks * 5 in dx:ax
; we now need to multiply by 65,536 and divide by 59659 d.

        mov     cx,59659        ; get divisor
        div     cx
                                ; dx now has remainder
                                ; ax has high word of final quotient
        mov     bx,ax           ; put high work if safe place
        xor     ax,ax           ; this is the multiply by 65536
        div     cx              ; bx:ax now has time in 100th of seconds


;rounding based on the remainder may be added here
;the result in bx:ax is time in 1/100 second.

        mov     dx,bx
        mov     cx,200          ;extract 1/100's

;division by 200 is necessary to ensure no overflow--max result
;is number of seconds in a day/2 = 43200.

        div     cx
        cmp     dl,100          ;remainder over 100?
        jb      noadj
        sub     dl,100          ;keep 1/100's less than 100
noadj:
        cmc                     ;if we subtracted 100, carry is now set
        mov     bl,dl           ;save 1/100's

;to compensate for dividing by 200 instead of 100, we now multiply
;by two, shifting a one in if the remainder had exceeded 100.

        rcl     ax,1
        mov     dl,0
        rcl     dx,1
        mov     cx,60           ;divide out seconds
        div     cx
        mov     bh,dl           ;save the seconds
        div     cl              ;break into hours and minutes
        xchg    al,ah

;time is now in ax:bx (hours, minutes, seconds, 1/100 sec)

        push    ax
        mov     ax,si           ; daycnt
        stosw
        pop     ax
        stosw
        mov     ax,bx
        stosw
        clc
        ret

tim_read endp

        assume  es:nothing

; the following routine is executed at resume time when the system
; powered on after suspension. it reads the real time clock and
; resets the system time and date
;
; This can be patched to be the INT6C vector so that this can be
; used by other people to update DOS time from CMOS (after taking
; care for IRET)

        public  P_UpdFromCMOS
P_UpdFromCMOS   proc    far

        assume  ds:nothing
        push    ds
        mov     ds,cs:Bios_Data_Word
        assume ds:Bios_Data

        call    read_real_date          ; get the date from the clock
PUFC_UpdDate:                           ; M101
        mov     ds:daycnt,si            ; update our copy of date
        call    read_real_time          ; get the time from the rtc
        cli
        mov     ah,01h                  ; command to set the time
        int     1ah                     ; call rom-bios time routine
        sti
; M101 BEGIN - check back to see if date changed when we were reading the
; time; if so, update date and time all over again ( Paranoid?)
        call    read_real_date          ; get the date from the clock

; BUGBUG- nagara - Store the ret.value of date from int 1a in this
; procedure read_real_date so that at the end we don't really have to
; call read_real_date again; we just need to call int 1a and compare the
; date registers against the stored values of these registers.

        cmp     si,ds:daycnt            ; is the date changed ?
        jne     PUFC_UpdDate            ; yes, go back and set the new date
; M101 END
        pop     ds
        ret

P_UpdFromCMOS   endp


;************************************************************************
;
;   read_real_date reads real-time clock for date and returns the number
;   of days elapsed since 1-1-80 in si
;
read_real_date proc near
        assume  ds:Bios_Data,es:nothing

        push    ax
        push    cx
        push    dx

        xor     ah,ah                   ; throw away clock roll over
        int     1ah

        pop     dx
        pop     cx
        pop     ax                      ; cas - bad code!

        push    ax
        push    bx
        push    cx
        push    dx

        mov     daycnt2,1       ; real time clock error flag (+1 day)
        mov     ah,4            ; read date function code
        int     1ah             ; read real-time clock
        jnc     read_ok         ; jmp success
        jmp     r_d_ret         ; jmp error

read_ok:                        ; ******* get bcd values in binary *****
        mov     word ptr bin_date_time+0,cx  ; store as hex value
        mov     word ptr bin_date_time+2,dx  ; ...

        mov     daycnt2,2       ; read of r-t clock successful
        call    bcd_verify      ; verify bcd values in range
        jc      r_d_ret         ; jmp some value out of range
        mov     daycnt2,3       ; read of r-t clock successful
        call    date_verify     ; verify date values in range
        jc      r_d_ret         ; jmp some value out of range
        mov     daycnt2,0       ; verify successful
        call    in_bin          ; convert date to binary
                                ; ******* years since 1-1-80 *********
        mov     al,byte ptr bin_date_time    ; get years into century
        cbw                                  ;
        cmp     byte ptr bin_date_time+1,20  ; 20th century?
        jnz     century_19                   ; jmp no
        add     ax,100          ; add in a century
century_19:                     ;
        sub     ax,80           ; subtract off 1-1-80
        mov     cl,4            ; leap year every 4
        div     cl              ; al= # leap year blocks, ah= remainder
        mov     bl,ah           ; save odd years
        cbw                     ; zero ah
        mov     cx,366+3*365    ; # of days in leap year blocks
        mul     cx              ; dx:ax is result
        mov     daycnt2,ax      ; save count of days
        mov     al,bl           ; get odd years count
        cbw                     ;
        or      ax,ax           ; is ax= 0?
        jz      leap_year       ; jmp if none
        mov     cx,365          ; days in year
        mul     cx              ; dx:ax is result
        add     daycnt2,ax      ; add on days in odd years
        jmp     short leap_adjustment ; account for leap year
leap_year:                      ; possibly account for a leap day
        cmp     byte ptr bin_date_time+3,2 ; is month february
        jbe     no_leap_adjustment ; jan or feb. no leap day yet.
leap_adjustment:                ; account for leap day
        inc     daycnt2
no_leap_adjustment:             ; ******* get days of month *******
        mov     cl,byte ptr bin_date_time+2
        xor     ch,ch
        dec     cx              ; because of offset from day 1, not day 0
        add     daycnt2,cx       ; ******* get days in months preceeing *****
        mov     cl,byte ptr bin_date_time+3   ; get month
;       xor     ch,ch
        dec     cx              ; january starts at offset 0

IFDEF VCB_OPT
; Change code to use month_tab since larger month_table was deleted
        jz      r_d_ret                 ; Skip if 0 (January)
        mov     ah,0                    ; Zero AH
        mov     si,offset month_tab     ; Point to month_tab
r_d_loop:
        lodsb                           ; Get days in previous month
        add     daycnt2,ax              ; Add to total
        loop    r_d_loop                ; Iterate loop
ELSE
        shl     cx,1            ; word offset
        mov     si,offset month_table   ; beginning of month_table
        add     si,cx           ; point into month table
        mov     ax,word ptr [si]; get # days in previous months
        add     daycnt2,ax
ENDIF

r_d_ret:
        mov     si,daycnt2       ; result in si
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret

r_t_retj:
        xor     cx,cx
        xor     dx,dx
        jmp     short r_t_ret

read_real_date endp

;--------------------------------------------------------------------

; read_real_time reads the time from the rtc. on exit, it has the number of
; ticks (at 18.2 ticks per sec.) in cx:dx.

read_real_time proc near
        assume  ds:Bios_Data,es:nothing

        mov     ah,2
        int     1ah
        jc      r_t_retj

oktime:
        mov     word ptr bin_date_time,cx       ; hours + minutes
        mov     byte ptr bin_date_time+3,dh     ; seconds
        mov     byte ptr bin_date_time+2,0      ; unused for time

        call    bcd_verify
        jc      r_t_retj
        call    time_verify
        jc      r_t_retj

        call    in_bin
        mov     cx,word ptr bin_date_time
        mov     dx,word ptr bin_date_time+2

; get time in ticks in cx:dx

IFDEF POWERALONE
        call    time_to_ticks
ELSE
        call    ttticks
ENDIF
r_t_ret:
        ret

read_real_time endp

;--------------------------------------------------------------------

;   in_bin converts bin_date_time values from bcd to bin

in_bin  proc    near
        assume  ds:Bios_Data,es:nothing

        mov     al,byte ptr bin_date_time+0  ; years or minutes
        call    bcd_to_bin
        mov     byte ptr bin_date_time+0,al

        mov     al,byte ptr bin_date_time+1  ;century or hours
        call    bcd_to_bin
        mov     byte ptr bin_date_time+1,al

        mov     al,byte ptr bin_date_time+2  ; days (not used for time)
        call    bcd_to_bin
        mov     byte ptr bin_date_time+2,al

        mov     al,byte ptr bin_date_time+3  ; months or seconds
        call    bcd_to_bin
        mov     byte ptr bin_date_time+3,al

        ret

in_bin  endp

;--------------------------------------------------------------------

;   bcd_to_bin converts two bcd nibbles in al (value <= 99.) to
;   a binary representation in al
;   ah is destroyed

bcd_to_bin proc near
        assume  ds:nothing,es:nothing

        mov     ah, al          ; M048
        and     al, 0fh         ; M048
        mov     cl, 4           ; M048
        shr     ah, cl          ; M048
        aad                     ; M048
        ret

bcd_to_bin endp

;--------------------------------------------------------------------

;   date_verify loosely checks bcd date values to be in range in bin_date_time

date_verify proc near
        assume  ds:Bios_Data,es:nothing

        cmp     byte ptr bin_date_time+1,20h  ; century check
        ja      date_error                    ; error
        jz      century_20                    ; jmp in 20th century

        cmp     byte ptr bin_date_time+1,19h  ; century check
        jb      date_error                    ;  error
        cmp     byte ptr bin_date_time+0,80h  ; year check
        jb      date_error                    ;  error

century_20:
        cmp     byte ptr bin_date_time+0,99h  ; year check
        ja      date_error                    ;  error
        cmp     byte ptr bin_date_time+3,12h  ; month check
        ja      date_error                    ;  error
        cmp     byte ptr bin_date_time+3,00h  ; month check
        jbe     date_error                    ;  error
        cmp     byte ptr bin_date_time+2,31h  ; day check
        ja      date_error                    ;  error
        cmp     byte ptr bin_date_time+2,00h  ; day check
        jbe     date_error                    ;  error
        clc                                   ; set success flag
        ret
date_error:
        stc                                   ; set error flag
        ret

date_verify endp

;--------------------------------------------------------------------

; time_verify very loosely checks bcd date values to be in range in bin_date_time

time_verify proc near
        assume  ds:Bios_Data,es:nothing

        cmp     byte ptr bin_date_time+1,24h
        ja      time_error
        cmp     byte ptr bin_date_time+0,59h
        ja      time_error
        cmp     byte ptr bin_date_time+3,59h
        ja      time_error

        clc
        ret

time_error:
        stc
        ret

time_verify endp

;--------------------------------------------------------------------

;   bcd_verify checks values in bin_date_time to be valid
;   bcd numerals.  carry set if any nibble out of range

bcd_verify proc near
        assume  ds:Bios_Data,es:nothing

        mov     cx,4            ; 4 bytes to check
        mov     bx,offset bin_date_time
bv_loop:
        mov     al,[bx]         ; get a bcd number (0..99)
        mov     ah,al
        and     ax,0f00fh       ; 10's place in high ah, 1's in al
        cmp     al,10           ; is 1's place in range?
        ja      bv_error        ; jmp out of range

        shr     ah,1            ; swap nibbles
        shr     ah,1
        shr     ah,1
        shr     ah,1
        and     ah,0fh          ; get rid of any erroneous bits
        cmp     ah,10           ; is 10's place in range
        ja      bv_error        ; jmp out of range

        inc     bx              ; next byte
        dec     cx
        jnz     bv_loop

        clc                     ; set success flag
        ret

bv_error:
        stc                     ; set error flag
        ret

bcd_verify endp

;--------------------------------------------------------------------
daycnttoday     proc    near    ; for real time clock support

;entry: [daycnt] = number of days since 1-1-80
;
;return: ch - centry in bcd
;        cl - year in bcd
;        dh - month in bcd
;        dl - day in bcd

        push    di
        mov     di,daycnt
        cmp     di,(365*20+(20/4))      ;# of days from 1-1-1980 to 1-1-2000
        jae     century20

IFDEF VCB_OPT
; Since base_century and base_year are contiguous, save some bytes
; by setting them together
        mov     word ptr base_century,19+(80 shl 8)
ELSE
        mov     base_century,19
        mov     base_year,80
ENDIF
        jmp     short years

century20:                              ;21st century
;       mov     base_century,20
;       mov     base_year,0
        mov     word ptr base_century,20
        sub     di,(365*20+(20/4))      ;adjust daycnt
years:
        xor     dx,dx
        mov     ax,di
        mov     bx,(366+365*3)          ;# of days in a leap year block
        div     bx                      ;ax = # of leap block, dx = daycnt
        mov     di,dx                   ;save daycnt left

        mov     bl,4
        mul     bl                      ;ax = # of years. less than 100 years!
        add     base_year,al            ;so, ah = 0. adjust year accordingly.
        inc     di                      ;set daycnt to 1 base
        cmp     di,366                  ;the daycnt here is the remainder of the leap year block.
        jbe     leapyear                ;so, it should within 366+355+355+355 days.
        inc     base_year               ;first if daycnt <= 366, then leap year
        sub     di,366                  ;else daycnt--, base_year++;
                                        ;and the next three years are regular years.
        mov     cx,3
regularyear:
        cmp     di,365                  ;for(i=1; i>3 or daycnt <=365;i++)
        jbe     yeardone                ;{if (daycnt > 365)
        inc     base_year               ;  { daycnt -= 365
        sub     di,365                  ;  }
        loop    regularyear             ;}

;       should never fall through loop

leapyear:
        mov     byte ptr month_tab+1,29 ;leap year. change the month table.
yeardone:
        xor     bx,bx
        xor     dx,dx
        mov     ax,di
        mov     si,offset month_tab
        mov     cx,12
months:
        inc     bl
        mov     dl,byte ptr ds:[si]     ;compare daycnt for each month until fits
        cmp     ax,dx                   ;dh=0.
        jbe     month_done

        inc     si                      ;next month
        sub     ax,dx                   ;adjust daycnt
        loop    months

;       should never fall through loop

month_done:
        mov     byte ptr month_tab+1,28 ;restore month table value
        mov     dl,bl
        mov     dh,base_year
        mov     cl,base_century         ;now, al=day, dl=month,dh=year,cl=century
        call    bintobcd                ;oh my!!! to save 15 bytes, bin_to_bcd proc
                                        ;was relocated seperately from daycnt_to_day proc.
        xchg    dl,al                   ;dl = bcd day, al = month
        call    bintobcd

        xchg    dh,al                   ;dh = bcd month, al = year
        call    bintobcd

        xchg    cl,al                   ;cl = bcd year, al = century
        call    bintobcd

        mov     ch,al                   ;ch = bcd century
        pop     di                      ;restore original value
        ret
daycnttoday     endp

IFDEF   POWERALONE              ; needed only for standalone version
                                ; for resident version use the one in
                                ; mschar.asm
;--------------------------------------------------------------------
; convert time to ticks
; input : time in cx and dx
; ticks returned in cx:dx

time_to_ticks proc near

; first convert from hour,min,sec,hund. to
; total number of 100th of seconds

        mov     al,60
        mul     ch              ;hours to minutes
        mov     ch,0
        add     ax,cx           ;total minutes
        mov     cx,6000         ;60*100
        mov     bx,dx           ;get out of the way of the multiply
        mul     cx              ;convert to 1/100 sec
        mov     cx,ax
        mov     al,100
        mul     bh              ;convert seconds to 1/100 sec
        add     cx,ax           ;combine seconds with hours and min.
        adc     dx,0            ;ripple carry
        mov     bh,0
        add     cx,bx           ;combine 1/100 sec
        adc     dx,0

;       dx:cx is time in 1/100 sec

        xchg    ax,dx
        xchg    ax,cx           ;now time is in cx:ax
        mov     bx,59659
        mul     bx              ;multiply low half
        xchg    dx,cx
        xchg    ax,dx           ;cx->ax, ax->dx, dx->cx
        mul     bx              ;multiply high half
        add     ax,cx           ;combine overlapping products
        adc     dx,0
        xchg    ax,dx           ;ax:dx=time*59659
        mov     bx,5
        div     bl              ;divide high half by 5
        mov     cl,al
        mov     ch,0
        mov     al,ah           ;remainder of divide-by-5
        cbw
        xchg    ax,dx           ;use it to extend low half
        div     bx              ;divde low half by 5
        mov     dx,ax
                                ; cx:dx is now number of ticks in time
        ret
time_to_ticks endp
ENDIF
Bios_Code       ends
ENDIF
        END
