; ; 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