mirror of
https://github.com/stevenhowes/wbios.git
synced 2026-05-27 00:03:32 +01:00
d410ad0acb
Original source https://www.pcengines.ch/file/wbios111s.zip
500 lines
11 KiB
Plaintext
500 lines
11 KiB
Plaintext
;
|
|
; Timer / RTC functions
|
|
;
|
|
; (C)1997-2001 Pascal Dornier / PC Engines; All rights reserved.
|
|
; This file is licensed pursuant to the COMMON PUBLIC LICENSE 0.5.
|
|
|
|
;
|
|
; Limitations:
|
|
;
|
|
; - Wait function not supported.
|
|
; - Reinitialization doesn't set SET bit.
|
|
; - We don't use CMOS RAM for configuration data
|
|
; (exception: extended memory size).
|
|
; - There is no CMOS checksum.
|
|
;
|
|
|
|
; Year 2000 issue:
|
|
;
|
|
; - Years below 1980 are considered century roll-over, replaced
|
|
; by year 2000.
|
|
; - Please note that DOS will force default date for anything
|
|
; before 1980.
|
|
|
|
; pd 000817 add TICK_RATE option to support AMD Elan SC520
|
|
; (needs modified tick rate)
|
|
; pd 990211 move tests from cm_shut to cm_test
|
|
; pd 980416 fix alarm function (interrupt mask, CX saved)
|
|
|
|
;
|
|
; INT1A timer BIOS
|
|
;
|
|
int1a:
|
|
#if def PCI ;transfer to PCI BIOS if necessary
|
|
cmp ah,0b1h ;PCI
|
|
jnz int1a1
|
|
jmp pci_i1a ;go to PCI BIOS
|
|
int1a1:
|
|
#endif
|
|
sti ;enable interrupts
|
|
push ds
|
|
push bx
|
|
xor bx,bx ;BIOS segment
|
|
mov ds,bx
|
|
cmp ah,7
|
|
ja int1a_err ;:bad command code
|
|
mov bl,ah
|
|
shl bx,1
|
|
cli ;disable interrupts
|
|
jmp [cs:bx+int1atab] ;dispatch function
|
|
;
|
|
; AH=2: get RTC time
|
|
;
|
|
rtc_get: call rtc_uip ;check for RTC update
|
|
jb int1a_err ;:error
|
|
mov ah,cm_ss ;read seconds -> DH
|
|
call rtc_read
|
|
mov dh,al
|
|
mov ah,cm_mm ;read minutes -> CL
|
|
call rtc_read
|
|
mov cl,al
|
|
mov ah,cm_hh ;read hours -> CH
|
|
call rtc_read
|
|
mov ch,al
|
|
mov ah,cm_b ;read daylight savings bit -> DL
|
|
call rtc_read
|
|
and al,1
|
|
mov dl,al
|
|
jmp short int1a_ok
|
|
;
|
|
; AH=3: set RTC time
|
|
;
|
|
rtc_set: push cx
|
|
call rtc_uip ;check for RTC update
|
|
pop cx
|
|
mov ah,cm_ss ;DH -> seconds
|
|
mov al,dh
|
|
call rtc_write
|
|
mov ah,cm_mm ;CL -> minutes
|
|
mov al,cl
|
|
call rtc_write
|
|
mov ah,cm_hh ;CH -> hours
|
|
mov al,ch
|
|
call rtc_write
|
|
mov ah,cm_b ;read status register B
|
|
call rtc_read
|
|
and al,01100010xb ;mask off set, update interrupt,
|
|
;square wave, daylight savings
|
|
or al,2 ;set 24 hour mode
|
|
or al,dl ;add daylight savings bit from DL
|
|
call rtc_write ;update status register B
|
|
int1a_ok: clc
|
|
int1a_ret: sti
|
|
pop bx
|
|
pop ds
|
|
retf 2
|
|
|
|
int1a_err: stc
|
|
jmp short int1a_ret
|
|
;
|
|
; AH=4: get RTC date
|
|
;
|
|
rtc_date: call rtc_uip ;check for RTC update
|
|
jb int1a_err
|
|
mov ah,cm_dd ;day -> DL
|
|
call rtc_read
|
|
mov dl,al
|
|
mov ah,cm_mo ;month -> DH
|
|
call rtc_read
|
|
mov dh,al
|
|
mov ah,cm_yy ;year -> CL
|
|
call rtc_read
|
|
mov cl,al
|
|
mov ah,cm_cent ;century -> CH
|
|
call rtc_read
|
|
mov ch,al
|
|
cmp cx,1980h ;century roll-over ?
|
|
jae rtc_date9
|
|
mov ax,cm_cent*256+20h ;update century register
|
|
call rtc_write
|
|
mov ch,20h ;force 2000
|
|
rtc_date9: jmp int1a_ok
|
|
;
|
|
; AH=5: set RTC date
|
|
;
|
|
rtc_sdat: push cx
|
|
call rtc_uip ;check for RTC update
|
|
pop cx
|
|
mov ax,cm_day*256 ;0 -> day of week (not used)
|
|
call rtc_write
|
|
mov ah,cm_dd ;DL -> day
|
|
mov al,dl
|
|
call rtc_write
|
|
mov ah,cm_mo ;DH -> month
|
|
mov al,dh
|
|
call rtc_write
|
|
mov ah,cm_yy ;CL -> year
|
|
mov al,cl
|
|
call rtc_write
|
|
mov ah,cm_cent ;CH -> century
|
|
mov al,ch
|
|
call rtc_write
|
|
jmp int1a_ok
|
|
;
|
|
; AH=6: set RTC alarm
|
|
;
|
|
rtc_alrm: mov ah,cm_b ;read status B
|
|
call rtc_read
|
|
and al,20h ;alarm enabled ?
|
|
jnz int1a_err ;:error
|
|
push cx ;save CX !
|
|
call rtc_uip ;check for RTC update
|
|
pop cx ;restore...
|
|
mov ah,cm_ssa ;DH -> alarm second
|
|
mov al,dh
|
|
call rtc_write
|
|
mov ah,cm_mma ;CL -> alarm minute
|
|
mov al,cl
|
|
call rtc_write
|
|
mov ah,cm_hha ;CH -> alarm hour
|
|
mov al,ch
|
|
call rtc_write
|
|
|
|
in al,pic1+1 ;read mask register
|
|
and al,0feh ;enable RTC interrupt (8)
|
|
out pic1+1,al
|
|
|
|
mov ah,cm_b ;read status B
|
|
call rtc_read
|
|
or al,20h ;enable alarm
|
|
call rtc_write
|
|
jmp int1a_ok
|
|
;
|
|
; AH=7: clear RTC alarm
|
|
;
|
|
rtc_snz: mov ah,cm_b ;read status B
|
|
call rtc_read
|
|
and al,5fh ;disable alarm interrupt
|
|
call rtc_write
|
|
jmp int1a_ok
|
|
;
|
|
; AH=0: get time
|
|
;
|
|
tm_get: mov dx,[m_timer] ;DX = timer low
|
|
mov cx,[m_timer+2] ;CX = timer high
|
|
mov al,0
|
|
xchg al,[m_timofl] ;AL = timer overflow, reset flag
|
|
jmp int1a_ok
|
|
;
|
|
; AH=1: set time
|
|
;
|
|
tm_set: mov byte [m_timofl],0 ;clear timer overflow flag
|
|
mov [m_timer],dx ;DX = timer low
|
|
mov [m_timer+2],cx ;CX = timer high
|
|
jmp int1a_ok
|
|
;
|
|
; INT1A dispatch table
|
|
;
|
|
even
|
|
int1atab: dw tm_get ;AH=0: get time
|
|
dw tm_set ;AH=1: set time
|
|
dw rtc_get ;AH=2: get RTC time
|
|
dw rtc_set ;AH=3: set RTC time
|
|
dw rtc_date ;AH=4: get RTC date
|
|
dw rtc_sdat ;AH=5: set RTC date
|
|
dw rtc_alrm ;AH=6: set RTC alarm
|
|
dw rtc_snz ;AH=7: clear RTC alarm
|
|
;
|
|
; Clear RTC interrupt, test shutdown byte
|
|
;
|
|
; Be sure to leave cm_test non-zero, as this is used by the
|
|
; master reset code in cs_clr to determine whether to reset
|
|
; the bus.
|
|
;
|
|
rtc_test: mov al,cm_nmi+cm_c ;clear pending interrupt
|
|
out cm_idx,al
|
|
out iowait,al
|
|
in al,cm_dat
|
|
|
|
mov bx,8000h+cm_nmi+cm_test ;bit to test, test register
|
|
rtc_test1: mov al,bl ;write pattern
|
|
out cm_idx,al
|
|
out iowait,al
|
|
mov al,bh
|
|
out cm_dat,al
|
|
out iowait,al
|
|
|
|
mov al,bl ;read back
|
|
out cm_idx,al
|
|
out iowait,al
|
|
in al,cm_dat
|
|
out iowait,al
|
|
cmp al,bh
|
|
jnz rtc_test8 ;:error - clc -> inverted -> error
|
|
shr bh,1 ;shift pattern right
|
|
jnb rtc_test1 ;:another bit
|
|
rtc_test8: cmc ;last bit inverted -> no carry if ok
|
|
ret
|
|
;
|
|
; Wait for RTC UIP bit cleared
|
|
;
|
|
; This bit is set about 250æs before the next update. If clear,
|
|
; we have at least 250æs to read or write the RTC without
|
|
; updates coming in between.
|
|
;
|
|
rtc_uip: mov cx,1000
|
|
mov ah,cm_a
|
|
rtc_uip1: cli
|
|
call rtc_read
|
|
and al,80h ;UIP ?
|
|
jz rtc_uip9 ;:ok, carry clear
|
|
sti ;give interrupts a chance
|
|
loop rtc_uip1 ;:try again
|
|
mov ax,cm_a*256+26h ;set 32768 Hz oscillator, 1 ms int
|
|
call rtc_write ;to restart...
|
|
stc
|
|
rtc_uip9: ret
|
|
;
|
|
; read RTC register [AH] -> AL
|
|
;
|
|
rtc_read: mov al,ah
|
|
out cm_idx,al
|
|
out iowait,al
|
|
in al,cm_dat
|
|
out iowait,al
|
|
ret
|
|
;
|
|
; write RTC register AL -> [AH]
|
|
;
|
|
rtc_write: xchg al,ah
|
|
out cm_idx,al
|
|
out iowait,al
|
|
xchg al,ah
|
|
out cm_dat,al
|
|
ret
|
|
;
|
|
; clock tick (IRQ0)
|
|
;
|
|
irq0: sti ;enable interrupts
|
|
push ax
|
|
push dx
|
|
push ds
|
|
|
|
#if def debug
|
|
call diag_csip ;debug: display CS:IP on MDA
|
|
#endif
|
|
xor ax,ax ;access BIOS segment
|
|
mov ds,ax
|
|
|
|
mov ax,[m_timer] ;update timer
|
|
mov dx,[m_timer+2]
|
|
add ax,1
|
|
adc dx,0
|
|
cmp ax,00b2h ;24 hours ?
|
|
jnz irq0_1
|
|
cmp dx,0018h
|
|
jnz irq0_1
|
|
xor ax,ax ;timer overflow - back to 0
|
|
xor dx,dx
|
|
mov byte [m_timofl],1
|
|
irq0_1: mov [m_timer],ax
|
|
mov [m_timer+2],dx
|
|
|
|
dec byte [m_fdcnt] ;floppy motor timer
|
|
jnz irq0_2 ;:not yet
|
|
mov al,0ch
|
|
mov dx,fdc_ctrl
|
|
out dx,al ;turn off motor
|
|
and byte [m_fdmot],0f0h ;turn off motor bits
|
|
|
|
irq0_2: int 1ch ;call user hook
|
|
|
|
pop ds
|
|
pop dx
|
|
mov al,eoi ;signal end of interrupt
|
|
cli
|
|
out pic0,al
|
|
pop ax
|
|
iret
|
|
;
|
|
; RTC interrupt (IRQ8)
|
|
;
|
|
irq8: push ax
|
|
mov al,cm_c ;check alarm interrupt bit
|
|
out cm_idx,al
|
|
out iowait,al
|
|
in al,cm_dat
|
|
test al,20h
|
|
jz irq8_1
|
|
push ax
|
|
int 4ah ;call user hook
|
|
pop ax
|
|
irq8_1: mov al,eoi ;signal end of interrupt
|
|
out pic1,al
|
|
out pic0,al
|
|
pop ax
|
|
iret
|
|
;
|
|
; Timer initialization -> 18.2 Hz tick
|
|
; Unmask timer and keyboard interrupts
|
|
;
|
|
tim_init: mov al,36h ;square wave generator
|
|
out timer+3,al
|
|
#if def TICK_RATE
|
|
mov al,low(TICK_RATE) ;LSB
|
|
out timer,al
|
|
mov al,high(TICK_RATE) ;MSB
|
|
out timer,al
|
|
#else
|
|
mov al,0
|
|
out timer,al
|
|
out timer,al
|
|
#endif
|
|
|
|
in al,pic0+1 ;enable timer, keyboard interrupt
|
|
and al,11111100xb
|
|
out iowait,al
|
|
out pic0+1,al
|
|
out iowait,al
|
|
mov al,eoi
|
|
out pic0,al
|
|
ret
|
|
;
|
|
; RTC init
|
|
;
|
|
rtc_ini: mov ah,cm_d ;read status register D
|
|
call rtc_read
|
|
and al,80h ;battery low ?
|
|
jz rtc_ini0 ;:yes
|
|
|
|
; battery ok - validate the time / date
|
|
|
|
mov ah,2 ;get RTC time
|
|
int 1ah
|
|
jb rtc_ini0 ;:error
|
|
mov al,dh ;validate seconds
|
|
mov ah,60h
|
|
call rtc_val
|
|
jb rtc_ini0 ;:bad
|
|
mov al,cl ;validate minutes
|
|
mov ah,60h
|
|
call rtc_val
|
|
jb rtc_ini0 ;:bad
|
|
mov al,ch ;validate hours
|
|
mov ah,24h
|
|
call rtc_val
|
|
jb rtc_ini0 ;:bad
|
|
|
|
mov ah,4 ;get RTC date
|
|
int 1ah
|
|
mov al,dl ;day
|
|
mov ah,31h
|
|
call rtc_val
|
|
jb rtc_ini0 ;:bad
|
|
mov al,dh ;month
|
|
mov ah,12h
|
|
call rtc_val
|
|
jb rtc_ini0 ;:bad
|
|
mov ax,cx
|
|
cmp ax,1980h ;minimum 1980
|
|
jb rtc_ini0
|
|
cmp ax,2099h ;maximum 2099
|
|
ja rtc_ini0
|
|
mov ah,99h ;maximum year
|
|
call rtc_val
|
|
jb rtc_ini0 ;:bad
|
|
|
|
mov ax,cm_dia*256 ;clear diag register
|
|
call rtc_write
|
|
jmp short rtc_ini2
|
|
|
|
; battery was low or invalid time - initialize the RTC
|
|
|
|
rtc_ini0: inc byte [tmp_rtc] ;set RTC failure flag
|
|
mov si,offset rtc_tab
|
|
rtc_ini1: cs: lodsw ;get value from table
|
|
call rtc_write ;write to RTC
|
|
cmp si,offset rtc_tab9 ;end of table ?
|
|
jb rtc_ini1 ;:no
|
|
|
|
; Set timer tick value from RTC time
|
|
;
|
|
; Please note that there are different algorithms with varying
|
|
; accuracy for doing this, there can be slight time discrepancies
|
|
; depending on what algorithm is used by the OS.
|
|
|
|
rtc_ini2: mov ah,2 ;get RTC time
|
|
int 1ah
|
|
jb rtc_ini9 ;:error
|
|
mov [tmp_ss],dh ;save second for run check
|
|
mov [tmp_mm],cl ;save minute for run check
|
|
push dx
|
|
xor ebx,ebx
|
|
mov al,ch ;hour
|
|
mov edx,01000755h ;18.206 * 3600 * 256
|
|
call rtc_mul
|
|
mov al,cl ;minute
|
|
mov edx,00044464h ;18.206 * 60 * 256
|
|
call rtc_mul
|
|
pop ax ;second
|
|
mov al,ah
|
|
mov dx,4661 ;18.206 * 256
|
|
call rtc_mul
|
|
shr ebx,8 ;timer / 256
|
|
mov dword [m_timer],ebx ;set timer
|
|
mov [tmp_timer],bx ;backup for run check
|
|
rtc_ini9: ret
|
|
;
|
|
; convert number in AL from BCD -> binary, * EDX -> add to EBX
|
|
;
|
|
rtc_mul: mov ah,al ;high digit
|
|
and al,15 ;mask low digit
|
|
shr ah,4 ;high -> low nibble
|
|
aad ;convert to binary
|
|
db 066h
|
|
cbw ;clear top of eax
|
|
mul edx
|
|
add ebx,eax ;add ebx,eax
|
|
ret
|
|
;
|
|
; validate a BCD number in AL, AH = limit
|
|
;
|
|
rtc_val: cmp al,ah ;exceed limit ?
|
|
ja rtc_val9 ;(no carry -> cmc -> error)
|
|
and al,15 ;high digit is ok, now check low digit
|
|
cmp al,10 ;(less than 10 -> carry -> cmc -> ok)
|
|
rtc_val9: cmc ;return error status
|
|
ret
|
|
;
|
|
; Timer & RTC test
|
|
;
|
|
tim_test: mov ax,[m_timer] ;did we get at least one timer tick ?
|
|
cmp ax,[tmp_timer]
|
|
jnz tim_test0 ;:ok
|
|
|
|
; Could fail if floppy and IDE init were super fast. Give it
|
|
; another chance.
|
|
|
|
mov bx,60 ;wait 60 ms
|
|
call cs_waitbx
|
|
mov ax,[m_timer] ;did we get at least one timer tick ?
|
|
cmp ax,[tmp_timer]
|
|
jz tim_test8 ;no: error
|
|
|
|
tim_test0: add ax,20 ;wait max. of one second
|
|
xchg ax,bx
|
|
tim_test1: mov ah,2 ;read RTC
|
|
int 1ah
|
|
jb tim_test8 ;:error
|
|
cmp dh,[tmp_ss] ;compare second
|
|
jnz tim_test9 ;:ok
|
|
cmp cl,[tmp_mm] ;compare minute
|
|
jnz tim_test9 ;:ok
|
|
cmp bx,[m_timer] ;time-out ?
|
|
js tim_test8 ;:yes
|
|
hlt ;wait for next interrupt
|
|
jmp tim_test1 ;look again
|
|
|
|
tim_test8: inc byte [tmp_tim] ;set error flag
|
|
tim_test9: ret
|