; File TIME.S includes the SIO2PC routines related to using
; and programming the timer chip, and timing routines in general
;
;
;  Includes:

;       ACC_DLY;  TIMER_0;  TIME_LAG;   PRG_T0
;       MYVEC8;  FIXV8;  COLSEV8;  ONE_TIK;  TIMING
;       SETUPV8;  DO_TIME

; The following notes copied from NEW_TIME.ASM

; Notes: The 8254 chip is clocked at 1.193 Mhz. The period is
; 840 nanoseconds. Timer 2 is gated by bit 0 of port 61h. Timer 2
; produces the sound through the speaker. Bit 1 of port 61h gates
; the timer's output to the speaker (1 = enable sound).

; Port #  dir:                  COMMENT

; 040h    R/W   Counter 0 data. Counter 0 is sys. timer; IRQ 0
; 041h    R/W   Counter 1 data. Used for refresh, 15 usec. period
; 042h    R/W   Counter 2 data. Gated by 61h, b0, used for speaker.
; 043h     W    Control word.

COMMENT\

 CONTROL WORD FORMAT:  (PORT 043h)

   BIT 7  BIT 6                                                    VALUE
    1       1           Read back command, see below                0Ch
    0       1           Select counter 1                            40h
    1       0           Select counter 2                            80h
    0       0           Select counter 0                            00H

   BIT 5  BIT 4
    0       0           Counter latch command                       00h
    0       1           Read/Write LSB only                         10h
    1       0           Read/Write MSB only                         20h
    1       1           Read/Write LSB, then MSB                    30h

Note: the remaining bits are don't care for Counter Latch command, and
have different meanings (see below) for Read Back command. Otherwise, they
are for mode programming as given below:

	BIT 3   BIT 2   BIT 1                                  VALUE
	 0       0       0      MODE 0                          00h
	 0       0       1      MODE 1                          02h
	 X       1       0      MODE 2                          04h
	 X       1       1      MODE 3                          06h
	 1       0       0      MODE 4                          08h
	 1       0       1      MODE 5                          09h

 BIT 0 is 0 for binary counter (normal) and 1 for BCD counter.

Direct speaker control via PORT 061h:

bit 0: set (1) for normal control by timer 2
bit 0: clear (0) for direct control
bit 1: set to turn speaker on (cone out) when bit 0 is 0
bit 1: clear to turn speaker off (cone in) when bit 0 is 0

Ref. Using Assembly Language page 386

ENDOFCOMMENT\

; A routine to waste just a few machine cycles, mainly for
; slow ports, between successive IN's and OUT's.

TIME_LAG:
        NOP
        NOP
        NOP
        NOP
        JMP >L1
L1:     RET


; A routine to delay until VARBL has changed value (one tick)
; or 55 msec.
ONE_TIK:
        PUSH AX
        MOV AX,VARBL
        CMP VARBL,0; It won't change if it's zero
        JNE >L1
        POP AX
        RET
L1:     CMP AX,VARBL
        JNE  L1
        POP AX
        RET

; the above routine has a resolution of 1 tick.  The one below
; has more resolution:
; A post rev 3.14 addition

ONETICK:
	PUSH AX, CX
	MOV CX, 4
L1:	MOV BX, 08000h
	CALL TIMER_0
	LOOP L1
	POP CX, AX
	RET

;
;       A routine to redirect the timer 0 interrupt to my routine

; This routine demonstrates the following:

; 1) Getting an interrupt vector
; 2) Re-directing an interrupt

; This routine is useful as a timer to time a certain number of timer
; ticks, each being 55 ms. After re-setting the interrupt, the user
; puts the number of ticks desired into WORD VARBL. The interrupt
; routine decrements the word until it is 0, and stops decrementing.
; The user can just monitor VARBL until it becomes zero. The normal
; functions of the timer 0 routine (INT 08) are still enabled. Some
; counts are:

;       10 SECONDS:     182 COUNTS
;        5               91
;        3               55
;        2               36
;        1               18
;       .5                9
;       110 mS            2

; My interrupt routine:

MYVEC8:
        STI
        PUSH DS
        PUSH CS
        POP DS

; *** NOTE TECHNIQUE FOR "CALLING" ORIGINAL INT ROUTINE AS
;     THOUGH IT WERE A SUBROUTINE...  ****

        PUSHF; See Bible, pg 247; simulate interrupt...
        CALL FAR [CONTIN]; let old handler go first then IRET here
        CMP VARBL,0;    If it's already 0, don't change it
        JE >L1
        DEC VARBL
L1:     POP DS
        IRET

SETUPV8:

        MOV AL,08; Get INT 08 address
        MOV AH, 035h; Get interrupt vector function
        INT 021h; Now ES:BX holds interrupt vector
        MOV H_CONTIN,ES
        MOV CONTIN,BX

; Now change the interrupt vector to point to my routine:

        MOV AL,08; Set INT 08 address
        MOV AH,025h; Set interrupt DOS function
        MOV DX, OFFSET MYVEC8
        PUSH CS
        POP DS
        INT 021h; Change the interrupt
        RET

; Now, to close, I'll restore the original vector:

; Note below:, I had to load DX first, because DS is the ref.
; segment for the MOV instruction!

FIXV8:  
        MOV AH,025h
        MOV AL,08
        MOV DX, CONTIN
        MOV BX, H_CONTIN
        MOV DS,BX
        INT 021h

        PUSH CS; Get DS back to program segment
        POP DS; just because I like it that way...
        RET

; CLOSEV8 will be used to fix INT 08 vector from the INT 024h
; handler. It does the job without DOS because you're not supposed
; to call high DOS functions while in INT 024h handler. I'm relying
; on having interrupts turned off to keep me out of trouble.  Note:
; don't try to use TICKS macro after turning off this function!
; NOTE: if there are problems, turn off timer interrupts directly at
; chip.

CLOSEV8:
        CLI
        PUSH ES
        PUSH BX
        PUSH AX
        XOR BX,BX; Put 0 into BX, int table is in segment 0000
        MOV ES,BX
        MOV BX,020h; Location of INT 8 vector
        MOV AX, CONTIN
        MOV ES:[BX],AX
        MOV AX,H_CONTIN
        MOV ES:[BX+2],AX
        STI
        POP AX
        POP BX
        POP ES
        RET


; *******************************************************************

; Timing routines for timer #2 imported from NEW_TIME.ASM as part of
; rev. 4.08.  Intent is to use timer 2 when timer 0 is busy with
; VARBL related timing.

; *******************************************************************

COMMENT\
; A subroutine to check timer 2 till it times out, then return
; Since reading the out pin didn't work, I'll try reading the counter
; till it's less than 256.

ONE_SHOT:
	MOV AL,0
	MOV ERROR,AL
	MOV WERROR,0; IF COUNT DOESN'T CHANGE
L1:     MOV DX,043h
	MOV AL,11101000b; Latch status of counter 2
	OUT DX,AL
	NOP
	INC ERROR; count loop iterations
	NOP
	MOV DX,042h
	IN AL,DX        ; Get status, b7 is the out pin status
;        IN AL,DX        ; Get latched count, low byte.
;        MOV AH,AL
;        IN AL,DX        ; now get high byte
;        XCHG AL,AH      ; Put in correct order...
;        CMP AH,0        ; High byte down to 0?
	AND AL,080h        ; b7 will be low until counter times out

        JZ L1
	RET
EOUT:   MOV ERROR,0     ; Return error level 0 for no count change.
	RET

; A routine to set timer #2 to mode 1 and load with count = 0FFFFh

MODE1:
	MOV DX,042h ; Clear out any latched junk:
	IN AL,DX
	NOP
	NOP
	NOP
        JMP >L1
L1:     IN AL,DX

	MOV DX,061h
	IN AL,DX
	AND AL,0FEh; Clear bit 1 of control port to turn off timer.
	NOP
	NOP
	OUT DX,AL
	MOV DX,043h
	MOV AL,10110010b; Counter 2, 16 bit, mode 1.
	OUT DX,AL
	MOV DX,042h
	MOV AL,0FFh
	OUT DX,AL
	NOP
        JMP >L1
L1:     OUT DX,AL

; Now, a loop using the one shot mode (1);

	MOV CX,182; 182 X 55 ms = 10 seconds
DO_IT:  MOV DX,061h
	IN AL,DX
	NOP
	NOP
        JMP >L1
L1:     OR AL,1
	OUT DX,AL; Set b1 on to trigger one-shot
	NOP
	NOP
	NOP
	NOP
	NOP
        JMP >L1
L1:     NOP
	CALL ONE_SHOT
	NOP
	NOP
	NOP
	NOP
	MOV DX,061h
	IN AL,DX
	AND AL,0FEh; Clear b1 to reset one-shot
	OUT DX,AL
	LOOP DO_IT
	MOV AL,ERROR

	MOV AH,4Ch    ; DOS end program function
	INT 021h

	END
ENDOFCOMMENT\
;               TIMING ADJUST SUBROUTINE

; This subroutine lets the user input his own values for
; various serial bus timing loops. This is in hopes of curing
; 'ole Joe Woyak's timing problem once and for all. The calling
; program has cleared the screen and will restore it on return.

TIMING:
L2:	CALL SCURRENT; Put current values into strings
	CALL CLR_SCRN
        PRINTL TMENU
ABT:    CALL GET_TORK
	CALL TO_UPPER; 3.20
;	 CMP AL,'U'; "U" is a special: Update Menu
;	 JNE >L1
;L2:	 CALL SCURRENT
;	 CALL CLR_SCRN
;	 PRINTL TMENU
;	 JMP ABT
L1:     CMP AL, 'G' ; 4.09 - diagnostic function
        JNE > P2
        CALL DIAGS
        JMP > L7
P2:     CMP AL, 'D'
        JNE > L3
        CALL D1050
        JMP > L7
L3:     CMP AL, 'C' ; Change disk config: rev. 3.01
        JNE > L1
AEC:    CALL DISK_CONFG
	JMP L2; 3.20
L1:     CMP AL, 'T'   ; rev 3.06 addition
	JNE >L1
L6:	CALL T_RI_SENSE
	JMP L2
L1:     SUB AL,'1'
        CMP AL,9; REV 2.10, WAS 8 BEFORE
        JB >L1
	JMP >L7 ; Quit if other than 1 thru 8
L1:     XOR AH,AH
        MOV DI,AX
        SHL DI,1
        PUSH DI; DI now points to word 0 - 8

        PRINTL ENTER4
        CALL GET4H; Hex number is returned in BX
        POP DI
	JC >L7 ; Carry means ESC pressed
        MOV [T1+DI],BX; Good #, so put into timing database
;	 PRINTL ST8
	JMP L2

L7:    RET


DISK_CONFG:
	PRINTL CUR_DSTS
	MOV DI, 0
	MOV CX, 4; 4 DISKS TO CHECK

AEB:    PUSH CX
        CMP B[WRITTN+DI],' '; Does this disk exist?
	JE AED
        LEA DX, [S_DISK + DI]
	WRITE_SCREEN 8
	MOV AL, 'Y'
        TEST B[DSK_FLAGS+DI],2; B1 is "NO CONFG"
        JE >L1
	MOV AL,'N'
L1:     CALL PRINT1; one byte to screen
	CALL LINE_FEED
AED:    POP CX
	ADD DI, BLOCK_SIZE
	LOOP AEB
	PRINTL ENT_NCH
	CALL GET_TORK
	CALL SET_BP; Carry clear if disk exists
        JC >L1
        XOR B[DSK_FLAGS+BP],2; toggle configurability status
	CALL LINE_FEED
	JMP DISK_CONFG

L1:     RET


; This subroutine takes a hex # in AX and puts the 4 ASCII hex
; digits to the field "HEX_PLACE"

HEX_IT:
        HEXTEX HEX_PLACE
        RET

; This routine takes the 16 bit numbers found in T1 thru T7,
; converts them to ASCII equivalents, and puts the ASCII strings
; into the strings ST1 thru ST9, getting them ready for printing.

SCURRENT:
        MOV CX,8; counter, 8 items (BEFORE 2.10 WAS 7)
L1:     MOV BX,CX; BX will be index to specific WORD value
        DEC BX; do for 0 thru 6
        SHL BX,1; Multiply by 2 to point to WORD
        MOV AX,[T1+BX]; Get actual word in AX
        CALL HEX_IT; Convert to string @ HEX_PLACE
        PUSH CX
        MOV DI,[STROFS+BX]; Get string offset pointer into DI
        MOV CX,4; Number of bytes to move
        PUSH CS
        POP ES
        MOV SI,OFFSET HEX_PLACE
        CLD
        REP MOVSB

        POP CX
        LOOP L1
        RET


; A routine to program timer 0 to what it should already be:
; mode 3, count = 0 (2^16)
; Rev 3.18 change: was setting to MODE 2, now MODE 3 ...

PRG_T0:
        MOV DX,043h; Address control port
	MOV AL,00110110xB; timer 0, 16 bit, mode 3, binary 3.18
        OUT DX,AL
        MOV AL,0; now, output the count (0000)
        MOV DX,040h; Timer 0 port
        OUT DX,AL
        CALL TIME_LAG
        OUT DX,AL
        RET



; A routine to delay the number of 420 ns cycles as are stored
; in WORD @ T7. Used for delay between accesses to UART

ACC_DLY:
        CMP T7,0 ; Just return if zero
        JE >L1
        PUSH BX
        MOV BX,T7
        CALL TIMER_0
        POP BX
L1:     RET


;       TIMER_0

; rev 4.10 is converting TIMER_0 to actually use timer #2 ...
;
; A routine which waits until timer 0 has decremented the number
; of counts found in register BX. Each count takes about 420 nSec.
; Note: don't try this with BX = 0FFFFh; the CX < BX check is bound
; to always be true.  In fact, I recommend that you don't even get
; close to 0FFFF, say within 1000, unless your computer is pretty
; fast, because "rollunder" will cause you to miss the event.
; Note that prior to 3.18, I thought each count was 840 nSec, but I've
; found out that the counter is counting by TWOS!

; 3.18 Changed this routine so it always set the timer up with a count
; of 0 and restarted.  For some reason, this caused problems with PUTSEC
; (SIO2PC receiving sectors from the Atari).  So 3.19 restores the old
; TIMER_0 method.
;SET	   DB 1


TIMER_0:
        PUSH AX
        PUSH DX
        PUSH CX
COMMENT\
        MOV DX,043h
        MOV AL,00000000xB; Latch counter 0
        OUT DX,AL
        MOV DX,040h
        NOP
        IN AL,DX
        NOP
        NOP
        MOV CL,AL
        IN AL,DX
        NOP
        NOP
        MOV CH,AL       ; CX now holds the initial count
L1:     MOV DX,043h
        MOV AL,0
        OUT DX,AL
        MOV DX,040h
        NOP
        NOP
        IN AL,DX        ; Get LSB
        MOV AH,AL
        NOP
        NOP
        IN AL,DX        ; Get MSB
        XCHG AL,AH      ; Correct order of LSB, MSB
        PUSH CX
        SUB CX,AX       ; How many counts have passed?
        CMP CX,BX
        POP CX
        JB L1           ; Loop if CX less than BX

ENDCOMMENT\
;COMMENT\   rev 3.18 stuff taken out below, then fixed & put back 3.19

; Note: it's necessary to turn bit 1 of PORT 61 on to allow input to
; timer 2.  This isn't just for the speaker, it's needed for the counting
; function.  It's done in the INIT part of SIO2PC.

	MOV DX, 043h
        MOV AL, 10110110xB; program timer 2 to mode 3 (b7 was 0)
	OUT DX, AL
	MOV AL, 0
        MOV DX, 042h ; was 040h
	OUT DX, AL  ; programmed count = 0 (65535)
	NOP
	NOP
	JMP >B1
B1:	OUT DX, AL
	MOV CX, 0
	SUB CX, BX	; CX now holds target count

L1:	MOV DX, 043h	; send counter latch command
        MOV AL, 080h ; was 0
	OUT DX, AL

; now read the count (low, then high)

        MOV DX, 042h
	JMP >B2
B2:	IN AL, DX
	MOV AH, AL
	NOP
	JMP >B3
B3:	IN AL, DX
	XCHG AL, AH
	AND AX, AX
	JE L1
;	 CMP CMND, 'W'
;	 JE > B6
;	 CMP CMND, 'P'
;	 JNE > B5
;B6:	 CMP SET,0
;	 JE > B5; first time thru?? (3.19, debugging)
;	 PUSH SI  ; this part for debugging
;	 MOV SI, T_COUNT
;	 MOV KEEP[SI],AX
;	 MOV KEEPBX[SI],BX
;	 MOV SET, 0
;	 POP SI
B5:	CMP CX, AX

; 3.19 The BIG problem with 3.18 was testing the logic backwards ...
; I first changed from JG to JA to no avail, but finally realized
; that it needed to be JB, meaning branch if CX < AX and continue
; looping and timing ...

	JB L1; 3.19--was JG, but JG is for signed, JA for unsigned ...
;ENDCOMMENT\
        POP CX
        POP DX
        POP AX
;	 CMP BX, 10
;	 JB BREAK
;	 CMP CMND, 'W'
;	 JE > B7
;	 CMP CMND, 'P'
;	 JNE > B4
;B7:	 INC T_COUNT
;	 INC T_COUNT
;	 CMP T_COUNT, 20
;	 JMP >B4
;BREAK:  NOP
;B4:	 MOV SET,0
	RET


;               DO_TIME

; This is a crude timer used to guard against getting stuck in a loop.
; Typically, at entry into the loop, you will put 0 into the word
; variable COUNT_TIME. Then, each time through, call this routine. If
; it returns with zero flag true, break out of the loop. Also, at entry
; into the loop, put the loop error ID character to variable B[ERR_CHAR].
; If this routine times out, it will put the character to field 
; STAT_EROR on the DEBUG status line.
; The time it takes is 65000*cycle time*# of cycles and is at least a
; few milliseconds, and could be several seconds in a big loop with a
; slow clock.

COMMENT\
THIS ROUTINE HAS CAUSED ME PROBLEMS WITH FAST COMPUTERS
(PENTIUMS), SO I'M GETTING RID OF IT!!!
DO_TIME:
        DEC W[COUNT_TIME]; yes, DEC does condition ZF.
        JNZ >L1
        CALL PUT_ERCH           
        CMP AX,AX; Make sure ZF is still set
L1:     RET
ENDOFCOMMENT\

; BEEP gives a beep over the console speaker

BEEP:
        CMP SOUND, 0
        JE > L3
        PUSH DX, AX
        MOV DX, 061h     ; TTL control port
        IN AL, DX

; Now, program counter 2 for speaker.

	AND AL,0FEh; Set off bit 0
	OUT DX,AL
	MOV DX,043h
        MOV AL,10110110xb; #2, 16 bit count, mode 3, binary
        OUT DX, AL
        MOV DX, 042h; Address counter #2
        MOV AX, BEEPCNT  ; 16 bit count
        OUT DX, AL
        XCHG AL, AH
        OUT DX, AL
        MOV DX, 061h; Now, turn on the gate
        IN AL, DX
        OR AL, 3; bits 0 and 1 on
        OUT DX, AL ; Hear anything?
        TICKS BEEPTIME
	MOV DX,061h
        IN AL, DX
        AND AL,0FFh - 2 ; Turn it off!!!!! (speaker output)
        OR AL, 1 ; Turn it on!! (gating input to counter #2)
        OUT DX, AL
        POP AX, DX
L3:     RET

; CLICK energizes the speaker coil for about 1 ms, then turns it off

CLICK:
        CMP SOUND, 0
        JE > L1 ;
        PUSH AX, BX, DX
;        MOV DX, 063h
;        IN AL, DX
;        OR AL, 2 ; Make port 61h "input"
;        OUT DX, AL
        MOV DX, 061h
        IN AL, DX
;        AND AL, 0FFh - 1 ; clear bit 0
        OR AL, 2; set bit 1
;        PUSH AX
;        MOV DX, 063h
;        IN AL, DX
;        AND AL, 0FFh - 2 ; Make port 61h "output"
;        OUT DX, AL
;        POP AX
;        MOV DX, 061h
        OUT DX, AL
        MOV BX, 2300
        CALL TIMER_0
        AND AL, 0FFh - 2; clear bit 1
        OUT DX, AL
        POP DX, BX, AX
L1:     RET


; CLICK1 is a click meant to sound different from CLICK

CLICK1:
        CMP SOUND, 0
        JE > L2
        PUSH AX, BX, DX
        MOV DX, 061h
        IN AL, DX
        OR AL, 2; set bit 1
        OUT DX, AL
        MOV BX, 1000 ; time with cone out
        CALL TIMER_0
        AND AL, 0FFh - 2; clear bit 1
        OUT DX, AL  ; cone in

        MOV BX, 500  ; dead time
        CALL TIMER_0

        MOV DX, 061h
        IN AL, DX
        OR AL, 2; set bit 1
        OUT DX, AL

        MOV BX, 1000 ; time with cone out
        CALL TIMER_0

        AND AL, 0FFh - 2; clear bit 1
        OUT DX, AL   ; cone in
        POP DX, BX, AX
L2:     RET

; SNDONOFF turns the sound effects ON and OFF

SNDONOFF:
        CMP SOUND, 0 ; sound OFF??
        JE > L1
        PSTATUS SOUNDOFF, ATTR9
        MOV SOUND, 0
        JMP > L2
L1:     MOV SOUND, 0FFh
        PSTATUS SOUNDON, ATTR9
L2:     TICKS 22
        RET


