%include	"zmacros.inc"

io8254		equ	040h		; Base I/O address of the 8254 timer
BIOS_TICKS	equ	00400h+0006Ch	; Address of BIOS timer in DOS memory

		[SECTION .data]

DosSelector	dw	0		; Save room for the Dos Selector
DosSelectorF	db	0		; Reset DOS selector valid flag

		[SECTION .text]
;-----------------------------------------------------------------------------
; void init_8254( int dos_selector)							     
;									     
; Initialize 8254 to mode 2 instead of mode 3.  This should only be	     
; called once, and can cause a one time error, in the real-time clock, of    
; no more than 55ms.  Except for the above single time error, this routine   
; does not effect the real-time clock.
;
; This routine also save the DOS selector needed to access the BIOS_TICK
; counter which is used as the overflow counter to the 8254.					     
;
; Overview:
;
; The 8254 is supplied by with a (14.31818mhz / 12 = 1193181.667hz) clock.   
;
; The BIOS uses mode 3 with a count of 0000h, when programming the the 8254
; which results in a square wave with a count of 65536.  This causes the
; 8254 to decrement by 2 each clock time, which is how the count is really
; the cylce time of the full square wave.
;
; This routine re-initializes the 8254 to mode 2 with a count of 0000h.
; This causes the 8254 to decrement by 1 each clock time, with a single
; pulse at the zero count to signal the 8259.  The time between pulses is
; the same as the time of the square wave, so nothing is changed in	     
; real-time clock accuracy.						     
;									     
; Since the 8254 is re-initialized, up to 55ms of time can be lost.	     
;-----------------------------------------------------------------------------
proc		_init_8254
%$dos_sel	arg	2

		mov	ax,[ebp+%$dos_sel]		; get DOS memory selector
		mov	[ds:DosSelector],ax		; save selector
		mov	[ds:DosSelectorF],Byte 1	; set flag indicating selector present

		; read the current timer mode

		mov	al,0E2h
		out	io8254+3,al
		IOWAIT
		in	al,io8254
		and	al,00Eh				; get only mode bits
		cmp	al,004h				; already in mode 2?
		je	iniSkip				; yes, then skip

		; if not mode 2, set to mode 2

		mov	al,034h				; set to mode 2
		out	io8254+3,al
		IOWAIT
		sub	al,al				; zero count
		out	io8254,al			; set counter
		IOWAIT
		out	io8254,al

iniSkip		endproc

;-----------------------------------------------------------------------------
; void restore_8254()
;
; Restores 8254 timer to MODE 3 (a few programs use this timer and expect it
; to be setup in MODE 3.)
;
; Since the 8254 is re-initialized, up to 55ms of time can be lost.
;-----------------------------------------------------------------------------
proc		_restore_8254

		mov	[ds:DosSelectorF],Byte 0	; Timer routines no longer work

		; read the current timer mode

		mov	al,0E2h
		out	io8254+3,al
		IOWAIT
		in	al,io8254
		and	al,00Eh				; get only mode bits
		cmp	al,006h				; already in mode 3?
		je	resSkip				; yes, then skip

		; if not already mode 3, set to mode 3

		mov	al,36h				; set to mode 3
		out	io8254+3,al
		IOWAIT
		sub	al,al				; zero count
		out	io8254,al			; set counter
		IOWAIT
		out	io8254,al

resSkip		endproc

;-----------------------------------------------------------------------------
; long readTimer()
;
; Read the extended 8254 bios timer.
; Returns the value of the 8254 counter and the BIOS_TICKS counts together as
; one extended count.  The BIOS_TICKS count is used as the overflow count to 
; the 8254 counter.
;-----------------------------------------------------------------------------
		proc	_readTimer

		; check for a valid DOS selector to prevent a GPF

		cmp	[ds:DosSelectorF],Byte 0	; is DOS selector present?
		je	rdExit				; no, do nothing

		mov	gs,[ds:DosSelector]		; get DOS selector
rdT1		mov	dx,[gs:BIOS_TICKS]		; get BIOS low timer tick
		sub	eax,eax				; zero AL
		out	io8254+3,al			; latch timer 0
		IOWAIT					; wait for I/O
		in	al,io8254			; get timer
		mov	ah,al				; save low byte
		in	al,io8254			; get high byte

		; check to see that the BIOS tick counter still matches.
		; If not, then an IRQ occured while reading the 8254 and
		; and incremented the tick counter, and we need to re-read.

		cmp	dx,[gs:BIOS_TICKS]		; has timer overflowed?
		jne	rdT1				; yes, try again
		xchg	al,ah				; else, fix counter
		neg	ax				; count upwards
		shl	edx,16				; move BIOS ticks to upper bits
		or	eax,edx				; get long timer value

rdExit		endproc
