;___XIO________________________________________________________________________________________________________________
;
; DIRECT SERIAL I/O
;
;   PROVIDES INTERFACE TO PLATFORM BASE SERIAL I/O DEVICE
;   ALLOWS USER MESSAGING/INTERACTION PRIOR TO AND DURING HBIOS INIT
;______________________________________________________________________________________________________________________
;
;
#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2))
;
UARTIOB		.EQU	$68
;
SIO_RBR		.EQU	UARTIOB + 0	; DLAB=0: RCVR BUFFER REG (READ ONLY)
SIO_THR		.EQU	UARTIOB + 0	; DLAB=0: XMIT HOLDING REG (WRITE ONLY)
SIO_IER		.EQU	UARTIOB + 1	; DLAB=0: INT ENABLE REG
SIO_IIR		.EQU	UARTIOB + 2	; INT IDENT REGISTER (READ ONLY)
SIO_FCR		.EQU	UARTIOB + 2	; FIFO CONTROL REG (WRITE ONLY)
SIO_LCR		.EQU	UARTIOB + 3	; LINE CONTROL REG
SIO_MCR		.EQU	UARTIOB + 4	; MODEM CONTROL REG
SIO_LSR		.EQU	UARTIOB + 5	; LINE STATUS REG
SIO_MSR		.EQU	UARTIOB + 6	; MODEM STATUS REG
SIO_SCR		.EQU	UARTIOB + 7	; SCRATCH REGISTER
SIO_DLL		.EQU	UARTIOB + 0	; DLAB=1: DIVISOR LATCH (LS)
SIO_DLM		.EQU	UARTIOB + 1	; DLAB=1: DIVISOR LATCH (MS)
;
;XIO_DIV	.EQU	(UARTOSC / (16 * CONBAUD))
;
#ENDIF

#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4))
#ENDIF

XIO_INIT:	; MINIMAL UART INIT

#IF (PLATFORM == PLT_UNA)
	; SHOULD UNA SERIAL I/O BE RESET HERE???
#ENDIF

#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4))

	; INIT ASCI0 WITH BASIC VALUES AND FAILSAFE DIVISOR
	LD	A,$66			; IGNORE CTS/DCD, NO BREAK DETECT
	OUT0	(Z180_ASEXT0),A		; -> ASEXT0
	LD	A,$64			; ENABLE XMT/RCV, 8 DATA, NO PARITY, 1 STOP
	OUT0	(Z180_CNTLA0),A		; -> CNTLA0
	;LD	A,$20			; FAILSAFE VALUE, 38400 BAUD AT 18.432 MHZ
	;LD	A,$22			; FAILSAFE VALUE, 9600 BAUD AT 18.432 MHZ
	;OUT0	(Z180_CNTLB0),A		; -> CNTLB0
	
	; TRY TO IMPLEMENT CONFIGURED BAUD RATE
	LD	HL,XIOCFG		; SERIAL CONFIG WORD
	LD	A,H			; BYTE W/ ENCODED BAUD RATE
	AND	$1F			; ISOLATE BITS
	LD	L,A			; MOVE TO L
	LD	H,0			; CLEAR MSB
	CALL	XIO_CNTLB		; DERIVE CNTLB VALUE
	JR	Z,XIO_INIT1		; SUCCESS, IMPLEMENT IT
	LD	C,$21 + Z180_CLKDIV	; FAILSAFE VALUE, 9600 BAUD IF OSC=18.432 MHZ

XIO_INIT1:
	LD	A,C			; MOVE VALUE TO ACCUM
	OUT0	(Z180_CNTLB0),A		; AND SET THE VALUE
#ENDIF

#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2))

	LD	DE,XIOCFG		; SERIAL CONFIG WORD
	CALL	XIO_COMPDIV		; COMPUTE DIVISOR TO BC

	LD	A,$80			; LCR := DLAB ON
	OUT	(SIO_LCR),A		; SET LCR
	;LD	A,XIO_DIV % $100	; BAUD RATE DIVISOR (LSB)
	LD	A,C			; LOW BYTE OF DIVISOR
	OUT	(SIO_DLL),A		; SET DIVISOR (LSB)
	;LD	A,XIO_DIV / $100	; BAUD RATE DIVISOR (MSB)
	LD	A,B			; HIGH BYTE OF DIVISOR
	OUT	(SIO_DLM),A		; SET DIVISOR (MSB)
	LD	A,03H			; VALUE FOR LCR AND MCR
	OUT	(SIO_LCR),A		; LCR := 3, DLAB OFF, 8 DATA, 1 STOP, NO PARITY
	OUT  	(SIO_MCR),A		; MCR := 3, DTR ON, RTS ON
	LD	A,6			; DISABLE & RESET FIFO'S
	OUT	(SIO_FCR),A		; DO IT

#ENDIF

	RET
;
XIO_SYNC:	; WAIT FOR FOR PENDING DATA IN FIFO TO CLEAR
;
#IF (PLATFORM == PLT_UNA)
	; NOT SURE ANYTHING IS POSSIBLE HERE...
#ENDIF

#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4))
	; IMPLEMENT THIS... OR MAYBE NOT.
#ENDIF

#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2))
	LD	DE,CPUMHZ * 25		; FAILSAFE TIMEOUT COUNTER
XIO_SYNC1:
	IN	A,(SIO_LSR)		; GET LINE STATUS REGISTER
	BIT	6,A			; TEST BIT 6 (TRANSMITTER EMPTY)
	JR	NZ,XIO_SYNC2		; EMPTY, MOVE ON
 	DEC	DE			; DECREMENT TIMEOUT COUNTER
	LD	A,D			; TEST TIMEOUT COUNTER
	OR	E			; ... FOR ZERO
	JR	NZ,XIO_SYNC1		; LOOP UNTIL TIMEOUT
XIO_SYNC2:
#ENDIF

	RET
;
XIO_CRLF2:	; OUTPUT 2 NEWLINES
	CALL	XIO_CRLF		; SEND CRLF, FALL THRU FOR ANOTHER
XIO_CRLF:	; OUTPUT A NEWLINE
	LD	A,13			; A = CR
	CALL	XIO_OUTC		; WRITE IT
	LD	A,10			; A = LF
	JR	XIO_OUTC		; WRITE IT AND RETURN
;
XIO_SPACE:	; OUTPUT A SPACE CHARACTER
	LD	A,' '
	JR	XIO_OUTC
;
XIO_DOT:	; OUTPUT A DOT (MARK PROGRESS)
	LD	A,'.'
;
XIO_OUTC:	; OUTPUT BYTE IN A

#IF (PLATFORM == PLT_UNA)
	PUSH	BC			; PRESERVE BC
	PUSH	DE			; PRESERVE DE
	LD	BC,$0012		; UNA UNIT = 0, FUNC = WRITE CHAR
	LD	E,A			; CHAR TO E
	CALL	$FFFD			; DO IT (RST 08 NOT SETUP YET)
	POP	DE			; RESTORE DE
	POP	BC			; RESTORE BC
	RET				; DONE
#ENDIF

#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4))
	PUSH	AF			; SAVE INCOMING BYTE
XIO_OUTC1:
	IN0	A,(Z180_STAT0)		; GET LINE STATUS
	AND	$02			; ISOLATE TDRE
	JR	Z,XIO_OUTC1		; LOOP TILL READY (EMPTY)
	POP	AF			; RECOVER INCOMING BYTE TO OUTPUT
	OUT0	(Z180_TDR0),A		; WRITE THE CHAR TO ASCI
	RET
#ENDIF

#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2))
	PUSH	AF			; SAVE INCOMING BYTE
XIO_OUTC1:
	IN	A,(SIO_LSR)		; READ LINE STATUS REGISTER
	AND	$20			; ISOLATE THRE
	JR	Z,XIO_OUTC1		; LOOP TILL READY (EMPTY)
	POP	AF			; RECOVER BYTE TO WRITE
	OUT	(SIO_THR),A		; WRITE THE CHAR TO UART
	RET
#ENDIF
;
XIO_INC:	; INPUT BYTE TO A

#IF (PLATFORM == PLT_UNA)
	PUSH	BC			; PRESERVE BC
	PUSH	DE			; PRESERVE DE
	LD	BC,$0011		; UNA UNIT = 0, FUNC = READ CHAR
	CALL	$FFFD			; DO IT (RST 08 NOT SETUP YET)
	LD	A,E			; CHAR TO A
	POP	DE			; RESTORE DE
	POP	BC			; RESTORE BC
	RET				; DONE
#ENDIF

#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4))
XIO_INC1:
	IN0	A,(Z180_STAT0)		; READ LINE STATUS
	AND	$80			; ISOLATE RDRF
	JR	Z,XIO_INC1		; LOOP TILL CHAR AVAILABLE
	IN0	A,(Z180_RDR0)		; READ THE CHAR
	RET
#ENDIF

#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2))
XIO_INC1:
	IN	A,(SIO_LSR)		; READ LINE STATUS REGISTER
	AND	$01			; ISOLATE RDR
	JR	Z,XIO_INC1		; LOOP TILL CHAR AVAILABLE
	IN	A,(SIO_RBR)		; READ THE CHAR
	RET
#ENDIF
;
XIO_IST:	; INPUT STATUS TO A (NUM CHARS WAITING)

#IF (PLATFORM == PLT_UNA)
	PUSH	BC			; PRESERVE BC
	PUSH	DE			; PRESERVE DE
	LD	BC,$0013		; UNA UNIT = 0, FUNC = READ CHAR
	CALL	$FFFD			; DO IT (RST 08 NOT SETUP YET)
	LD	A,E			; CHAR TO A
	OR	A			; UPDATE ZF
	POP	DE			; RESTORE DE
	POP	BC			; RESTORE BC
	RET				; DONE
#ENDIF

#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4))
	IN0	A,(Z180_STAT0)		; READ LINE STATUS
	AND	$80			; ISOLATE RDRF
	RET	Z			; NO CHARS WAITING, A=0, Z SET
	LD	A,1			; SIGNAL 1 CHAR WAITING
	OR	A			; UPDATE ZF
	RET
#ENDIF

#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2))
	IN	A,(SIO_LSR)		; READ LINE STATUS REGISTER
	AND	$01			; ISOLATE RDR
	RET	Z			; NO CHARS WAITING, A=0, Z SET
	LD	A,1			; SIGNAL 1 CHAR WAITING
	OR	A			; UPDATE ZF
	RET
#ENDIF
;
XIO_OUTS:	; OUTPUT '$' TERMINATED STRING AT ADDRESS IN HL
	LD	A,(HL)			; GET NEXT BYTE
	CP	'$'			; END OF STRING?
	RET	Z			; YES, GET OUT
	CALL	XIO_OUTC		; OTHERWISE, WRITE IT
	INC	HL			; POINT TO NEXT BYTE
	JR	XIO_OUTS		; AND LOOP

#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2))
;
; COMPUTE DIVISOR TO BC
;
XIO_COMPDIV:
	; WE WANT TO DETERMINE A DIVISOR FOR THE UART CLOCK
	; THAT RESULTS IN THE DESIRED BAUD RATE.
	; BAUD RATE = UART CLK / DIVISOR, OR TO SOLVE FOR DIVISOR
	; DIVISOR = UART CLK / BAUDRATE.
	; THE UART CLOCK IS THE UART OSC PRESCALED BY 16.  ALSO, WE CAN
	; TAKE ADVANTAGE OF ENCODED BAUD RATES ALWAYS BEING A FACTOR OF 75.
	; SO, WE CAN USE (UART OSC / 16 / 75) / (BAUDRATE / 75)
;
	; FIRST WE DECODE THE BAUDRATE, BUT WE USE A CONSTANT OF 1 INSTEAD
	; OF THE NORMAL 75.  THIS PRODUCES (BAUDRATE / 75).
;
	LD	A,D			; GET CONFIG MSB
	AND	$1F			; ISOLATE ENCODED BAUD RATE
	LD	L,A			; PUT IN L
	LD	H,0			; H IS ALWAYS ZERO
	LD	DE,1			; USE 1 FOR ENCODING CONSTANT
	CALL	DECODE			; DE:HL := BAUD RATE, ERRORS IGNORED
	EX	DE,HL			; DE := (BAUDRATE / 75), DISCARD HL
	LD	HL,UARTOSC / 16 / 75	; HL := (UART OSC / 16 / 75)
	JP	XIO_DIV16		; BC := HL/DE == DIVISOR AND RETURN
;
#ENDIF	
;
;
;
#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4))
;
; DERIVE A CNTLB VALUE BASED ON AN ENCODED BAUD RATE AND CURRENT CPU SPEED
; ENTRY: HL = ENCODED BAUD RATE
; EXIT: C = CNTLB VALUE, A=0/Z IFF SUCCESS
;
; DESIRED DIVISOR == CPUHZ / BAUD
; DUE TO ENCODING BAUD IS ALWAYS DIVISIBLE BY 75
; Z180 DIVISOR IS ALWAYS A FACTOR OF 160
;
; X = (CPU_HZ / 160) / 75 ==> SIMPLIFIED ==> X = CPU_KHZ / 12
; X = X / (BAUD / 75)
; IF X % 3 == 0, THEN (PS=1, X := X / 3) ELSE PS=0
; IF X % 4 == 0, THEN (DR=1, X := X / 4) ELSE DR=0
; SS := LOG2(X)
;
XIO_CNTLB:
	LD	DE,1			; USE DECODE CONSTANT OF 1 TO GET BAUD RATE ALREADY DIVIDED BY 75
	CALL	DECODE			; DECODE THE BAUDATE INTO DE:HL, DE IS DISCARDED
	;CALL	TSTPT
	RET	NZ			; ABORT ON ERROR
	PUSH	HL			; HL HAS (BAUD / 75), SAVE IT
	;LD	HL,(HCB + HCB_CPUKHZ)	; GET CPU CLK IN KHZ
	LD	HL,CPUKHZ		; CPU CLK IN KHZ
	;LD	HL,9216			; *DEBUG*
	
	; DUE TO THE LIMITED DIVISORS POSSIBLE WITH CNTLB, YOU PRETTY MUCH
	; NEED TO USE A CPU SPEED THAT IS A MULTIPLE OF 128KHZ.  BELOW, WE
	; ATTEMPT TO ROUND THE CPU SPEED DETECTED TO A MULTIPLE OF 128KHZ
	; WITH ROUNDING.  THIS JUST MAXIMIZES OUR CHANCES OF SUCCESS COMPUTING
	; THE DIVISOR.
	LD	DE,$0040		; HALF OF 128 IS 64
	ADD	HL,DE			; ADD FOR ROUNDING
	LD	A,L			; MOVE TO ACCUM
	AND	$80			; STRIP LOW ORDER 7 BITS
	LD	L,A			; ... AND PUT IT BACK
	
	LD	DE,12			; PREPARE TO DIVIDE BY 12
	CALL	XIO_DIV16		; BC := (CPU_KHZ / 12), REM IN HL, ZF
	;CALL	TSTPT
	POP	DE			; RESTORE (BAUD / 75)
	RET	NZ			; ABORT IF REMAINDER
	PUSH	BC			; MOVE WORKING VALUE
	POP	HL			; ... BACK TO HL
	CALL	XIO_DIV16		; BC := X / (BAUD / 75)
	;CALL	TSTPT
	RET	NZ			; ABORT IF REMAINDER
;	
	; DETERMINE PS BIT BY ATTEMPTING DIVIDE BY 3
	PUSH	BC			; SAVE WORKING VALUE ON STACK
	PUSH	BC			; MOVE WORKING VALUE
	POP	HL			; ... TO HL
	LD	DE,3			; SETUP TO DIVIDE BY 3
	CALL	XIO_DIV16		; BC := X / 3, REM IN HL, ZF
	;CALL	TSTPT
	POP	HL			; HL := PRIOR WORKING VALUE
	LD	E,0			; INIT E := 0 AS WORKING CNTLB VALUE
	JR	NZ,XIO_CNTLB1		; DID NOT WORK, LEAVE PS==0, SKIP AHEAD
	SET	5,E			; SET PS BIT
	PUSH	BC			; MOVE NEW WORKING
	POP	HL			; ... VALUE TO HL
;
XIO_CNTLB1:
	;CALL	TSTPT
	; DETERMINE DR BIT BY ATTEMPTING DIVIDE BY 4
	LD	A,L			; LOAD LSB OF WORKING VALUE
	AND	$03			; ISOLATE LOW ORDER BITS
	JR	NZ,XIO_CNTLB2		; NOT DIVISIBLE BY 4, SKIP AHEAD
	SET	3,E			; SET PS BIT
	SRL	H			; DIVIDE HL BY 4
	RR	L			; ...
	SRL	H			; ...
	RR	L			; ...
;
XIO_CNTLB2:
	;CALL	TSTPT
	; DETERMINE SS BITS BY RIGHT SHIFTING AND INCREMENTING
	LD	B,7			; LOOP COUNTER, MAX VALUE OF SS IS 7
	LD	C,E			; MOVE WORKING CNTLB VALUE TO C
XIO_CNTLB3:
	BIT	0,L			; CAN WE SHIFT AGAIN?
	JR	NZ,XIO_CNTLB4		; NOPE, DONE
	SRL	H			; IMPLEMENT THE
	RR	L			; ... SHIFT OPERATION
	INC	C			; INCREMENT SS BITS
	DJNZ	XIO_CNTLB3		; LOOP IF MORE SHIFTING POSSIBLE
;
	; AT THIS POINT HL MUST BE EQUAL TO 1 OR WE FAILED!
	DEC	HL			; IF HL == 1, SHOULD BECOME ZERO
	LD	A,H			; TEST HL
	OR	L			; ... FOR ZERO
	RET	NZ			; ABORT IF NOT ZERO
;
XIO_CNTLB4:
	;CALL	TSTPT
	XOR	A
	RET
;
#ENDIF
;
; COMPUTE HL / DE = BC W/ REMAINDER IN HL & ZF
;
XIO_DIV16:
	LD	A,H			; HL -> AC
	LD	C,L			; ...
	LD	HL,0			; INIT HL
	LD	B,16			; INIT LOOP COUNT
XIO_DIV16A:	
	SCF
	RL	C
	RLA	
	ADC	HL,HL	
	SBC	HL,DE	
	JR	NC,XIO_DIV16B	
	ADD	HL,DE	
	DEC	C	
XIO_DIV16B:	
	DJNZ	XIO_DIV16A		; LOOP AS NEEDED
	LD	B,A			; AC -> BC
	LD	A,H			; SET ZF
	OR	L			; ... BASED ON REMAINDER
	RET				; DONE
