
	.Z80	;TIME.COM 0000..FFFF
;
; Disassembly: John Elliott, 26-1-1999
;
; Storage: This program uses at least four different storage formats for 
; the year:
;
; * The RTC it talks to appears to store the year as BCD, 0-99, with 0 
;  meaning 1984.
; * The program stores the year as BCD, 84-99, with 84 meaning 1984;
; * and as binary, 84-99, with 84 meaning 1984;
; * and as the CP/M 3 day count.
;
; For year 2000 fixes, I will assume:
; * The RTC works correctly past 2000, so it returns 16 for 2000.
; * The internal year variables are 84-99 for 20th century, 0-83 for 21st.
;
; As provided, the source will assemble to the original TIME.COM. 
;Set Y2000 to YES for my tentative Year 2000 fixes.
;
;
NO	EQU	0
YES	EQU	NOT NO

Y2000	EQU	NO

FDOS	EQU	5
B0010	EQU	0010h
B0011	EQU	0011h
B0012	EQU	0012h
B0013	EQU	0013h
B0014	EQU	0014h
FCB	EQU	005Ch

	IF	Y2000		;[JCE] Might as well add in protection
				;from DOS, while we're here

	DEFB	0EBh,04h	;DOS protection... JMPS LABE
	EX	DE,HL
	JP	MAIN
	DEFB	0B4h,09h	;DOS protection... MOV AH,9
	DEFB	0BAh
	DEFW	BVMES		;DOS protection... MOV DX,OFFSET BVMES
	DEFB	0CDh,021h	;DOS protection... INT 21h.
	DEFB	0CDh,020h	;DOS protection... INT 20h.

	DB	0Dh, ' TIME (Year 2000 vers.) by RSDA/PJM/JCE-',13,10
	DB	' to provide CP/M+ with SCA MK2 date & time.'
T013F:	DB	1Ah

BVMES:	DB	'This is a CP/M program.',13,10,'$'

	ELSE		;Y2000

	JP	MAIN

	DB	' TIME by RSDA/PJM- to provide CPM+'
	DB	' with SCA MK2 date & time.'
T013F:	DB	1Ah

	ENDIF		;Y2000

MAIN:	LD	SP,T013F
	XOR	A
	OUT	(0E1h),A
	LD	A,18h
	OUT	(0E1h),A	;Reset the Z80-DART (Channel A)
	XOR	A
	OUT	(0E3h),A
	LD	A,18h
	OUT	(0E3h),A	;Reset the Z80-DART (Channel B)
	CALL	UPAUSE
	LD	A,(FCB+1)	;Get or set?
	CP	' '     	; 20h
	JP	Z,GETTM
SETTM:	CALL	INPDATE
	CALL	SENDTIME
	JP	L018C

GETTM:	LD	A,3		;Getting time?
	LD	(RETRIES),A
	CALL	ZAPTIME
L016D:	CALL	RDTIME
	JP	NZ,L017F	;Time has changed. Try again until it 
	CALL	YRPLUS84	;stabilises.
	CALL	BUF2BIN
	CALL	ISVALID		;Time is valid?
	JP	Z,L0189		;Valid. Check for 29 Feb.
L017F:	LD	HL,RETRIES	;Not got a valid time
	DEC	(HL)
	JP	NZ,L016D
	JP	SETTM		;Clock is not set. Complain bitterly.

L0189:	CALL	ROLL29		;Special check for 29 Feb
L018C:	CALL	SENDDATE
	CALL	SETTM3
	CALL	SETTM2
	CALL	PRDATE
	JP	0

RDTIME:	LD	HL,CHANGED
	LD	(HL),0		;No change detected
	CALL	STARTCMD
	LD	A,0D0h
	CALL	SENDBYT
	XOR	A		;Send D0, 0
	CALL	SENDBYT
	CALL	ENDCMD
	CALL	STARTCMD
	LD	A,0D1h		;Send D1
	CALL	SENDBYT
	INC	HL
	CALL	RDCK1		;Hours
	INC	HL
	CALL	RDCK1		;Mins
	INC	HL
	CALL	RDCK1		;Days
	INC	HL
	CALL	RDCHEK		;Months
	CALL	ENDCMD
	CALL	STARTCMD
	LD	A,0D0h		;Send D0, 5
	CALL	SENDBYT
	LD	A,5
	CALL	SENDBYT
	CALL	ENDCMD
	CALL	STARTCMD
	LD	A,0D1h
	CALL	SENDBYT
	INC	HL
	CALL	RDCK1		;Years
	INC	HL
	CALL	RDCK1		;?
	INC	HL
	CALL	RDCHEK		;?
	CALL	ENDCMD
	LD	A,(CHANGED)
	OR	A
	RET

ZAPTIME:
	LD	HL,CHANGED
	LD	A,8
L01FB:	LD	(HL),0
	INC	HL
	DEC	A
	JP	NZ,L01FB
	RET


RDCK1:	CALL	RECV1		;Receive byte, acknowledge it, write with check
	JP	L020C

RDCHEK:	CALL	RECVBYTE	;Receive byte, write with check
L020C:	LD	B,(HL)
	CP	B
	RET	Z
	LD	(HL),A
	OR	1
	LD	(CHANGED),A
	RET

YRPLUS84:			;Year += 84
	LD	A,(BCDYR)
	CALL	BCD2BIN		;Convert packed BCD to binary
;;
;;Year 2000 <<
;;
	IF	Y2000

	CP	16
	JP	C,G0001		;Map 21st century into 1900-1983
	ADD	A,-100
G0001:

	ENDIF
;;
;;Year 2000 >>
;;
	ADD	A,84
	CALL	BIN2BCD		;Convert binary to packed BCD
	LD	(BCDYR),A
	RET

SENDTIME:
	CALL	STARTCMD
	LD	A,0D0h
	CALL	SENDBYT
	LD	A,20h
	CALL	SENDBYT
	CALL	ENDCMD
C0235:	CALL	STARTCMD
	LD	A,0D0h
	CALL	SENDBYT
	LD	A,0
	CALL	SENDBYT
	LD	HL,BCDHR	;Hours
	LD	A,(HL)
	CALL	SENDBYT
	INC	HL
	LD	A,(HL)		;Mins
	CALL	SENDBYT
	INC	HL
	LD	A,(HL)		;Days
	CALL	SENDBYT
	INC	HL
	LD	A,(HL)		;Months
	CALL	SENDBYT
	CALL	ENDCMD
	RET

SENDDATE:
	CALL	STARTCMD	;Send date. 
	LD	A,0D0h
	CALL	SENDBYT
	LD	A,5
	CALL	SENDBYT
	LD	HL,BCDYR	;Send Y, D, M of all the bizarre orderings
	LD	A,(HL)
	CALL	BCD2BIN	;Convert packed BCD to binary
;;
;;Year 2000 <<
;;
	IF	Y2000
	CP	84	;Assume 1900-1983 are in 21st century
	JP	NC,G0002	
	ADD	A,100	;Bring A back into the 21st century
G0002:

	ENDIF
;;
;;Year 2000 >>
;;
	SUB	84
	CALL	BIN2BCD	;Convert binary to packed BCD
	CALL	SENDBYT
	LD	HL,BCDDY
	LD	A,(HL)
	CALL	SENDBYT
	INC	HL
	LD	A,(HL)
	CALL	SENDBYT
	CALL	ENDCMD
	RET

DROP_DTR:
	LD	A,5
	OUT	(0E1h),A
	LD	A,68h
	OUT	(0E1h),A
	JP	UPAUSE

RAISE_DTR:
	LD	A,5
	OUT	(0E1h),A
	LD	A,0E8h
	OUT	(0E1h),A
	JP	UPAUSE

RESET_B:
	LD	A,18h
	OUT	(0E3h),A
	JP	UPAUSE

RAISE_RTS:	LD	A,5
	OUT	(0E3h),A
	LD	A,6Ah
	OUT	(0E3h),A
UPAUSE:	LD	A,40h
L02AF:	DEC	A
	JP	NZ,L02AF
	RET

GETBIT:	LD	A,10h
	OUT	(0E3h),A
	CALL	UPAUSE
	IN	A,(0E3h)
	PUSH	AF
	CALL	UPAUSE
	POP	AF
	XOR	0FFh
	AND	10h
	RRCA
	RRCA
	RRCA
	RRCA
	RET

STARTCMD:	CALL	DROP_DTR
	CALL	RESET_B
	CALL	RAISE_RTS
	CALL	RAISE_DTR
	RET

ENDCMD:	CALL	RAISE_RTS
	CALL	RAISE_DTR
	CALL	DROP_DTR
	CALL	RESET_B
	RET

SENDBYT:
	LD	C,8	;8 bits
	LD	B,A	;in B
	CALL	RAISE_DTR
L02EB:	LD	A,B
	RLCA
	LD	B,A
	JP	NC,L02F7
	CALL	RESET_B
	JP	L02FA

L02F7:	CALL	RAISE_RTS
L02FA:	CALL	DROP_DTR
	CALL	RAISE_DTR
	DEC	C
	JP	NZ,L02EB
	CALL	DROP_DTR
	CALL	RESET_B
	CALL	GETBIT
	CALL	RAISE_DTR
	RET

RECVBYTE:
	LD	C,8
	LD	B,0
	CALL	RAISE_DTR
	CALL	RESET_B
L031B:	CALL	DROP_DTR
	CALL	GETBIT
	ADD	A,B
	ADD	A,B
	LD	B,A
	CALL	RAISE_DTR
	DEC	C
	JP	NZ,L031B
	LD	A,B
	RET

RECV1:	CALL	RECVBYTE
	PUSH	AF
	CALL	RESET_B
	CALL	RAISE_RTS
	CALL	DROP_DTR
	CALL	RAISE_DTR
	CALL	RESET_B
	POP	AF
	RET

T0342:	DW	0,0,31,59,90,120,151,181,212,243,273,304,334

CPM3DY:	DW	0
CPM3HR:	DW	0

SETTM3:	XOR	A
	LD	(CPM3DY+1),A
	LD	A,(BINDY)
	LD	(CPM3DY),A
	LD	HL,T0342
	LD	A,(BINM)
	LD	C,A
	LD	B,0
	ADD	HL,BC
	ADD	HL,BC
	LD	C,(HL)		;Days from year start to start of month
	INC	HL
	LD	B,(HL)
	LD	HL,(CPM3DY)
	ADD	HL,BC		;Days from year start to today
	LD	A,(BINYR)
;;
;;Year 2000 <<
;;
	IF	Y2000

	CP	84
	JP	NC,G0003		;Bring A back into the 21st century
	ADD	A,100
G0003:

	ENDIF
;;
;;Year 2000 >>
;;
	LD	B,A
	LD	C,77		;1977 (CP/M clock starts on 1-1-1978)
	LD	DE,365
L0385:	LD	A,B		;Increase year until it matches
	INC	C
	CP	C
	JP	Z,L0396
	ADD	HL,DE
	LD	A,C		;Leap year fix
	AND	3
	JP	NZ,L0385
	INC	HL
	JP	L0385

L0396:	AND	3		;Current year is leap year
	JP	NZ,L03A4
	LD	A,(BINM)
	CP	3
	JP	M,L03A4
	INC	HL
L03A4:
	LD	(CPM3DY),HL
	LD	HL,(BCDHR)
	LD	(CPM3HR),HL
	LD	C,68h		;CP/M set-time call
	LD	DE,CPM3DY
	CALL	FDOS
	RET

SETTM2:	LD	A,(BCDM)
	LD	(B0010),A
	LD	A,(BCDDY)
	LD	(B0011),A
	LD	A,(BCDYR)
	LD	(B0012),A
	LD	A,(BCDHR)
	LD	(B0013),A
	LD	A,(BCDMN)
	LD	(B0014),A
	RET

INPDATE:
	LD	C,9
	LD	DE,ENTDT$
	CALL	FDOS
	CALL	INPLN
	LD	DE,BCDDY
	LD	HL,INPBUF+2
	LD	C,3
L03E8:	CALL	DEC2BCD
	INC	DE
	DEC	C
	JP	NZ,L03E8
	LD	C,9
	LD	DE,ENTTM$
	CALL	FDOS
	CALL	INPLN
	LD	DE,BCDHR
	LD	HL,INPBUF+2
	LD	C,2
L0403:	CALL	DEC2BCD
	INC	DE
	DEC	C
	JP	NZ,L0403
	CALL	BUF2BIN
	CALL	ISVALID
	RET	Z
	LD	C,9
	LD	DE,BADTM$
	CALL	FDOS
	JP	INPDATE

BADTM$:	DB	0Dh,0Ah,'Date or Time not valid!',0Dh,0Ah,'$'

DEC2BCD:
	LD	B,0
L043B:	LD	A,B
	RLCA
	RLCA
	RLCA
	RLCA
	AND	0F0h
	LD	B,A
	LD	A,(HL)
	INC	HL
	CP	'0'     ; 30h
	RET	M
	CP	':'     ; 3Ah
	RET	P
	AND	0Fh
	OR	B
	LD	B,A
	EX	DE,HL
	LD	(HL),B
	EX	DE,HL
	JP	L043B

INPLN:	LD	A,'*'
	LD	HL,INPBUF
	LD	B,(HL)	;Max len
	INC	HL
L045C:	INC	HL	;Fill buffer with *s
	LD	(HL),A
	DEC	B
	JP	NZ,L045C
	LD	C,0Ah
	LD	DE,INPBUF
	CALL	FDOS
	RET

ENTDT$:	DB	0Ah,0Dh,'Please enter date (DD/MM/YY format)   $'
ENTTM$:	DB	0Ah,0Dh,'Please enter time (HH:MM)   $'
INPBUF:	DB	8,0,'*********'

ISVALID:
	LD	HL,BINHR
	LD	A,23	;Validate hours
	CP	(HL)
	JP	M,L04EF
	INC	HL
	LD	A,59	;Validate minutes
	CP	(HL)
	JP	M,L04EF
	INC	HL
	XOR	A
	CP	(HL)
	JP	Z,L04EF
	LD	A,31	;Validate days
	CP	(HL)
	JP	M,L04EF
	INC	HL
	XOR	A
	CP	(HL)
	JP	Z,L04EF
	LD	A,12	;Validate months
	CP	(HL)
	JP	M,L04EF
	INC	HL
	LD	A,99	;Validate years
	CP	(HL)
	JP	M,L04EF
	XOR	A
	RET

L04EF:	OR	1
	RET

ROLL29:	LD	HL,BINM
	LD	A,(CANDIDATE)
	CP	(HL)
	INC	HL	;HL->year
	JP	Z,L0504
	JP	M,L0504
	INC	(HL)	;Next year
	JP	L0526

L0504:	LD	A,(HL)	;HL->year
	AND	3
	RET	NZ	;Leap year?
	DEC	HL	;HL->month
	LD	A,(HL)
	CP	3
	RET	NZ
	LD	HL,CANDIDATE
	CP	(HL)
	RET	M
	RET	Z
	DEC	HL	;HL->day
	LD	A,(HL)
	CP	29
	RET	Z
	LD	HL,BINDY
	DEC	(HL)
	LD	A,(HL)
	OR	(HL)
	JP	NZ,L0526
	LD	(HL),29		;Convert 0 March to 29 Feb
	INC	HL
	LD	(HL),2
L0526:	LD	DE,3		;
L0529:	LD	HL,BINMN
	ADD	HL,DE
	LD	A,(HL)
	CALL	BIN2BCD		;Convert binary to packed BCD
	LD	HL,BCDMN
	ADD	HL,DE
	LD	(HL),A
	DEC	E
	JP	NZ,L0529
	CALL	C0235
	RET

BUF2BIN:
	LD	HL,CHANGED
	LD	DE,BINBUF
	LD	B,8
L0546:	LD	A,(HL)
	CALL	BCD2BIN		;Convert packed BCD to binary
	LD	(DE),A
	INC	DE
	INC	HL
	DEC	B
	JP	NZ,L0546
	RET

PRDATE:	LD	A,(BCDDY)
	LD	HL,DECDAYS
	CALL	BCD2ASC
	LD	A,(BCDM)
	LD	HL,DECMONTH
	CALL	BCD2ASC
	LD	A,(BCDYR)	;Does not need fixing for 2000 as BCDYR
	LD	HL,DECYEAR	;is still 2 digits.
	CALL	BCD2ASC
	LD	A,(BCDHR)
	LD	HL,DECHOUR
	CALL	BCD2ASC
	LD	A,(BCDMN)
	LD	HL,DECMIN
	CALL	BCD2ASC
	LD	C,9
	LD	DE,DECDATE
	CALL	FDOS
	RET

BCD2BIN:
	PUSH	AF
	RRCA
	RRCA
	RRCA
	RRCA
	AND	0Fh	;Low nibble
	ADD	A,A
	LD	C,A	
	ADD	A,A	
	ADD	A,A	
	ADD	A,C	
	LD	C,A	;C := A * 10
	POP	AF
	AND	0Fh
	ADD	A,C
	RET

BIN2BCD:
	LD	C,0FFh
L059C:	INC	C
	SUB	0Ah
	JP	NC,L059C	;C := A / 10
	ADD	A,0Ah
	LD	B,A		;B := A mod 10
	LD	A,C
	RLCA
	RLCA
	RLCA
	RLCA
	ADD	A,B
	RET			;A := ((A / 10) << 4) | (A % 10)

BCD2ASC:
	LD	B,A	;Simplistic date writer, good for 0-99
	RRCA
	RRCA
	RRCA
	RRCA
	AND	0Fh
	ADD	A,'0'   ; 30h
	LD	(HL),A
	INC	HL
	LD	A,B
	AND	0Fh
	ADD	A,'0'   ; 30h
	LD	(HL),A
	RET

RETRIES:
	DB	0

CHANGED:
	DB	0	;Buffer changed?

BCDHR:	DB	0	;Time stored as BCD
BCDMN:	DB	0
BCDDY:	DB	0
BCDM:	DB	0
BCDYR:	DB	0
	DB	0
	DB	0

BINBUF:	DB	0	;Time stored as binary
BINHR:	DB	0
BINMN:	DB	0
BINDY:	DB	0
BINM:	DB	0
BINYR:	DB	0
	DB	0
CANDIDATE:
	DB	0

DECDATE:  DB	0Dh,0Ah,0Ah,'Date '
DECDAYS:  DB	'  /'
DECMONTH: DB	'  /'
DECYEAR:  DB	'       Time '
DECHOUR:  DB	'  :'
DECMIN:	  DB	'  ',0Dh,0Ah,'$'

	END
