;
;==================================================================================================
; DUART DRIVER (SERIAL PORT)
;==================================================================================================
;
;  SETUP PARAMETER WORD:
;  +-------+---+-------------------+ +---+---+-----------+---+-------+
;  |       |RTS| ENCODED BAUD RATE | |DTR|XON|  PARITY   |STP| 8/7/6 |
;  +-------+---+---+---------------+ ----+---+-----------+---+-------+
;    F   E   D   C   B   A   9   8     7   6   5   4   3   2   1   0
;       -- MSB (D REGISTER) --           -- LSB (E REGISTER) --
;
;  MODE REGISTER 1
;
;    D7      D6      D5      D4      D3      D2      D1      D0
;  +-------+-------+-------+-------+-------+-------+-------+-------+
;  | RXRTS | RXINT | EMODE |  PARITY MODE  |  SEL  |   BITS/CHAR   |
;  +-------+-------+-------+-------+-------+-------+-------+-------+
;
;	RXRTS:	AUTOMATIC CONTROL OF /RTS BY RECEIVER
;		0 = NO
;		1 = YES
;	RXINT:	RECEIVE INTERRUPT SELECT
;		0 = RXRDY
;		1 = FFULL
;	EMODE:	ERROR MODE
;		0 = BY CHARACTER
;		1 = BY BLOCK
;	PARITY MODE:
;		00 = WITH PARITY
;		01 = FORCE PARITY
;		10 = NO PARITY
;		11 = MULTIDROP MODE
;	SEL:	PARITY TYPE
;		0 = EVEN / SPACE
;		1 = ODD / MARK
;	BITS/CHAR:
;		00 = 5
;		01 = 6
;		10 = 7
;		11 = 8
;
;  MODE REGISTER 2
;
;    D7      D6      D5      D4      D3      D2      D1      D0
;  +-------+-------+-------+-------+-------+-------+-------+-------+
;  | CHANNEL MODE  | TXRTS | TXCTS |        STOP BIT LENGTH        |
;  +-------+-------+-------+-------+-------+-------+-------+-------+
;
;	CHANNEL MODE:
;		00 = NORMAL
;		01 = AUTO-ECHO
;		10 = LOCAL LOOP
;		11 = REMOTE LOOP
;	TXRTS:	AUTOMATIC CONTROL OF /RTS BY TRANSMITTER
;		0 = NO
;		1 = YES
;	TXCTS:	AUTOMATIC CONTROL OF TRANSMITTER BY /CTS
;		0 = NO
;		1 = YES
;	STOP BIT LENGTH:
;		0 =  9/16
;		1 = 10/16 = 5/8
;		2 = 11/16
;		3 = 12/16 = 3/4
;		4 = 13/16
;		5 = 14/16 = 7/8
;		6 = 15/16
;		7 = 16/16 =   1
;		8-F = LENGTHS OF 0-7 PLUS ONE
;		IF BITS/CHAR = 5 THEN ADD AN ADDITIONAL HALF BIT
;
DUART_DEBUG		.EQU	FALSE
;
DUART_NONE		.EQU	0		; UNKNOWN OR NOT PRESENT
DUART_2681		.EQU	1		; OLD '681 WITHOUT IVR/GPR
DUART_26C92		.EQU	2		; '92 WITH MR0
DUART_XR88C681		.EQU	3		; EXAR/MAXLINEAR CHIP WITH Z-MODE
;
DUART_BAUD_INV		.EQU	$FF		; INVALID BAUD RATE
DUART_BAUD_ACR7		.EQU	%10000000	; ACR BIT 7 = 1
DUART_BAUD_X1		.EQU	%01000000	; BRG EXTEND BIT = 1 ('681)
DUART_BAUD_EXT1		.EQU	%00100000	; EXTENDED TABLE 1 ('92)
DUART_BAUD_EXT2		.EQU	%00010000	; EXTENDED TABLE 2 ('92)
;
; PER CHANNEL REGISTERS (CHANNEL A AT OFFSET 0, CHANNEL B AT OFFSET 8)
;
DUART_MR		.EQU	$00		; MODE REGISTER (R/W)
DUART_SR		.EQU	$01		; STATUS REGISTER (READ)
DUART_CSR		.EQU	$01		; CLOCK SELECT REGISTER (WRITE)
DUART_CR		.EQU	$02		; COMMAND REGISTER (WRITE)
DUART_RX		.EQU	$03		; RECEIVER HOLDING REGISTER (READ)
DUART_TX		.EQU	$03		; TRANSMITTER HOLDING REGISTER (WRITE)
;
; PER CHIP REGISTERS
;
DUART_IPCR		.EQU	$04		; INPUT PORT CHANGE REGISTER (READ)
DUART_ACR		.EQU	$04		; AUXILLIARY CONTROL REGISTER (WRITE)
DUART_ISR		.EQU	$05		; INTERRUPT STATUS REGISTER (READ)
DUART_IMR		.EQU	$05		; INTERRUPT MASK REGISTER (WRITE)
DUART_CTU		.EQU	$06		; COUNTER/TIMER UPPER BYTE REGISTER (R/W)
DUART_CTL		.EQU	$07		; COUNTER/TIMER LOWER BYTE REGISTER (R/W)
DUART_GPR		.EQU	$0C		; GENERAL PURPOSE REGISTER (R/W)
DUART_IVR		.EQU	$0C		; INTERRUPT VECTOR REGISTER (R/W)
DUART_IPR		.EQU	$0D		; INPUT PORT REGISTER (READ)
DUART_OPCR		.EQU	$0D		; OUTPUT PORT CONFIGURATION REGISTER (WRITE)
DUART_STCR		.EQU	$0E		; START COUNTER/TIMER COMMAND (READ)
DUART_SOPR		.EQU	$0E		; SET OUTPUT PORT REGISTER (WRITE)
DUART_SPCR		.EQU	$0F		; STOP COUNTER/TIMER COMMAND (READ)
DUART_ROPR		.EQU	$0F		; RESET OUTPUT PORT REGISTER (WRITE)
;
; COMMAND REGISTER
;
DUART_CR_ENA_RX		.EQU	%00000100	; ENABLE RECEIVER
DUART_CR_DIS_RX		.EQU	%00001000	; DISABLE RECEIVER
DUART_CR_ENA_TX		.EQU	%00000001	; ENABLE TRANSMITTER
DUART_CR_DIS_TX		.EQU	%00000010	; DISABLE TRANSMITTER
DUART_CR_NOP		.EQU	$00		; NULL COMMAND
DUART_CR_MR1		.EQU	$10		; RESET MR POINTER TO MR1
DUART_CR_RESET_RX	.EQU	$20		; RESET RECEIVER
DUART_CR_RESET_TX	.EQU	$30		; RESET TRANSMITTER
DUART_CR_RESET_ERR	.EQU	$40		; RESET ERROR STATUS
DUART_CR_RESET_BRK	.EQU	$50		; RESET BREAK STATUS
DUART_CR_START_BRK	.EQU	$60		; START BREAK
DUART_CR_STOP_BRK	.EQU	$70		; STOP BREAK
DUART_CR_SET_RX_X	.EQU	$80		; SET RECEIVER BRG EXTEND BIT (X=1)
DUART_CR_CLR_RX_X	.EQU	$90		; CLEAR RECEIVER BRG EXTEND BIT (X=0)
DUART_CR_SET_TX_X	.EQU	$A0		; SET TRANSMITTER BRG EXTEND BIT (X=1)
DUART_CR_CLR_TX_X	.EQU	$B0		; CLEAR TRANSMITTER BRG EXTEND BIT (X=0)
DUART_CR_MR0		.EQU	$B0		; RESET MR POINTER TO MR0 (26C92 ONLY)
DUART_CR_STANDBY	.EQU	$C0		; SET STANDBY MODE (CHANNEL A ONLY)
DUART_CR_RESET_IUS	.EQU	$C0		; RESET IUS LATCH (CHANNEL B ONLY)
DUART_CR_ACTIVE		.EQU	$D0		; SET ACTIVE MODE (CHANNEL A ONLY)
DUART_CR_ZMODE		.EQU	$D0		; SET Z-MODE (CHANNEL B ONLY)
;
; DUART STATUS REGISTER
;
DUART_SR_RXRDY		.EQU	%00000001	; RECEIVER READY
DUART_SR_RXFULL		.EQU	%00000010	; RECEIVE FIFO FULL
DUART_SR_TXRDY		.EQU	%00000100	; TRANSMITTER READY
DUART_SR_TXEMPTY	.EQU	%00001000	; TRANSMITTER FIFO EMPTY
DUART_SR_OVERRUN	.EQU	%00010000	; OVERRUN ERROR
DUART_SR_PARITY		.EQU	%00100000	; PARITY ERROR
DUART_SR_FRAMING	.EQU	%01000000	; FRAMING ERROR
DUART_SR_BREAK		.EQU	%10000000	; RECEIVED BREAK
;
; DUART MODE REGISTER 0
;
DUART_MR0_NORMAL	.EQU	%00000000	; NORMAL BAUD RATE TABLE
DUART_MR0_EXT1		.EQU	%00000001	; EXTENDED BAUD RATE TABLE 1
DUART_MR0_EXT2		.EQU	%00000100	; EXTENDED BAUD RATE TABLE 2
;
; DUART MODE REGISTER 1
;
DUART_MR1_RXRTS		.EQU	%10000000	; RECEIVER CONTROLS RTS
DUART_MR1_PARNONE	.EQU	%00010000	; NO PARITY
DUART_MR1_PARODD	.EQU	%00000100	; ODD PARITY
DUART_MR1_PAREVEN	.EQU	%00000000	; EVEN PARITY
DUART_MR1_PARMARK	.EQU	%00001100	; MARK PARITY
DUART_MR1_PARSPACE	.EQU	%00001000	; SPACE PARITY
;
; DUART MODE REGISTER 2
;
DUART_MR2_TXCTS		.EQU	%00010000	; CTS CONTROLS TRANSMITTER
DUART_MR2_STOP1		.EQU	%00000111	; 1 STOP BIT (1.5 IF 5 BITS/CHAR)
DUART_MR2_STOP2		.EQU	%00001111	; 2 STOP BITS (2.5 IF 5 BITS/CHAR)
;
;
#DEFINE	DUART_INP(RID)	CALL DUART_INP_IMP \ .DB RID
#DEFINE	DUART_OUTP(RID)	CALL DUART_OUTP_IMP \ .DB RID
;
;
;
DUART_PREINIT:
;
; SETUP THE DISPATCH TABLE ENTRIES
;
	LD	B,DUART_CFGCNT		; LOOP CONTROL
	XOR	A			; ZERO TO ACCUM
	LD	(DUART_DEV),A		; CURRENT DEVICE NUMBER
	LD	IY,DUART_CFG		; POINT TO START OF CFG TABLE
DUART_PREINIT0:
	PUSH	BC			; SAVE LOOP CONTROL
	CALL	DUART_DETECT		; DETERMINE DUART TYPE
	POP	BC			; RESTORE LOOP CONTROL
	LD	(IY + 1),A		; SAVE TYPE IN CONFIG TABLE
	OR	A			; SET FLAGS
	JR	Z,DUART_PREINIT1	; SKIP IT IF NOTHING FOUND
;	
	PUSH	BC			; SAVE LOOP CONTROL
	PUSH	IY
	POP	DE			; DE := UNIT INSTANCE TABLE ADDRESS
	LD	BC,DUART_FNTBL		; BC := FUNCTION TABLE ADDRESS
	CALL	NZ,CIO_ADDENT		; ADD ENTRY IF DUART FOUND, BC:DE
	POP	BC			; RESTORE LOOP CONTROL
;
DUART_PREINIT1:
	LD	DE,DUART_CFGSIZ		; SIZE OF CFG ENTRY
	ADD	IY,DE			; BUMP IY TO NEXT ENTRY
	DJNZ	DUART_PREINIT0		; LOOP UNTIL DONE
;
	LD	B,DUART_CFGCNT		; LOOP CONTROL
	LD	IY,DUART_CFG		; POINT TO START OF CFG TABLE
DUART_PREINIT2:
	PUSH	BC			; SAVE LOOP CONTROL
	CALL	DUART_INITUNIT		; INITIALIZE UNIT
	POP	BC			; RESTORE LOOP CONTROL
	LD	DE,DUART_CFGSIZ		; SIZE OF CFG ENTRY
	ADD	IY,DE			; BUMP IY TO NEXT ENTRY
	DJNZ	DUART_PREINIT2		; LOOP UNTIL DONE
;
	XOR	A			; SIGNAL SUCCESS
	RET				; AND RETURN
;
; DUART INITIALIZATION ROUTINE
;
DUART_INITUNIT:
	; CHECK IF PORT IS PRESENT
	LD	A,(IY + 1)		; GET TYPE FROM CONFIG TABLE
	OR	A			; SET FLAGS
	RET	Z			; ABORT IF NOTHING THERE
	
	; UPDATE WORKING DUART DEVICE NUM
	LD	HL,DUART_DEV		; POINT TO CURRENT DUART DEVICE NUM
	LD	A,(HL)			; PUT IN ACCUM
	INC	(HL)			; INCREMENT IT (FOR NEXT LOOP)
	LD	(IY),A			; UDPATE UNIT NUM
	
	; SET DEFAULT CONFIG
	LD	DE,-1			; LEAVE CONFIG ALONE
	JP	DUART_INITDEV		; IMPLEMENT IT AND RETURN
;
;
;
DUART_INIT:
	LD	B,DUART_CFGCNT		; COUNT OF POSSIBLE DUART UNITS
	LD	IY,DUART_CFG		; POINT TO START OF CFG TABLE
DUART_INIT1:
	PUSH	BC			; SAVE LOOP CONTROL
	LD	A,(IY + 1)		; GET DUART TYPE
	OR	A			; SET FLAGS
	CALL	NZ,DUART_PRTCFG		; PRINT IF NOT ZERO
	
	POP	BC			; RESTORE LOOP CONTROL
	LD	DE,DUART_CFGSIZ		; SIZE OF CFG ENTRY
	ADD	IY,DE			; BUMP IY TO NEXT ENTRY
	DJNZ	DUART_INIT1		; LOOP TILL DONE
;
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
; DRIVER FUNCTION TABLE
;
DUART_FNTBL:
	.DW	DUART_IN
	.DW	DUART_OUT
	.DW	DUART_IST
	.DW	DUART_OST
	.DW	DUART_INITDEV
	.DW	DUART_QUERY
	.DW	DUART_DEVICE
#IF (($ - DUART_FNTBL) != (CIO_FNCNT * 2))
	.ECHO	"*** INVALID DUART FUNCTION TABLE ***\n"
#ENDIF
;
;
;
DUART_IN:
	CALL	DUART_IST		; RECEIVED CHAR READY?
	JR	Z,DUART_IN		; LOOP IF NOT
	DUART_INP(DUART_RX)		; GET CHAR READ IN A
	LD	E,A			; CHAR READ TO E
	XOR	A			; SIGNAL SUCCESS
	RET				; AND DONE
;
;
;
DUART_OUT:
	CALL	DUART_OST		; READY FOR CHAR?
	JR	Z,DUART_OUT		; LOOP IF NOT
	LD	A,E			; GET CHAR TO SEND IN A
	DUART_OUTP(DUART_TX)		; SEND CHAR FROM A
	XOR	A			; SIGNAL SUCCESS
	RET
;
;
;
DUART_IST:
	DUART_INP(DUART_SR)		; GET CHANNEL STATUS REGISTER IN A
	AND	DUART_SR_RXRDY		; ISOLATE RXRDY BIT
	JP	Z,CIO_IDLE		; NOT READY, RETURN VIA IDLE PROCESSING
	XOR	A			; ZERO ACCUM
	INC	A			; ACCUM := 1 TO SIGNAL 1 CHAR WAITING
	RET				; DONE
;
;
;
DUART_OST:
	DUART_INP(DUART_SR)		; GET CHANNEL STATUS REGISTER IN A
	AND	DUART_SR_TXRDY		; ISOLATE TXRDY BIT
	JP	Z,CIO_IDLE		; NOT READY, RETURN VIA IDLE PROCESSING
	XOR	A			; ZERO ACCUM
	INC	A			; ACCUM := 1 TO SIGNAL 1 BUFFER POSITION
	RET				; DONE
;
;
;
DUART_INITDEV:
	; TEST FOR -1 WHICH MEANS USE CURRENT CONFIG (JUST REINIT)
	LD	A,D			; TEST DE FOR
	AND	E			; ... VALUE OF -1
	INC	A			; ... SO Z SET IF -1
	JR	NZ,DUART_INITDEV1	; IF DE == -1, REINIT CURRENT CONFIG
;
	; LOAD EXISTING CONFIG TO REINIT
	LD	E,(IY + 8)		; LOW BYTE
	LD	D,(IY + 9)		; HIGH BYTE
;
DUART_INITDEV1:
	; GET CLOCK SELECT FROM TABLE
	LD	HL,DUART_BAUDTBL_681	; GET START OF XR88C681 TABLE IN HL
	LD	A,(IY + 1) 		; GET DUART TYPE
	CP	DUART_26C92		; IS IT A 26C92?
	JR	NZ,DUART_INITDEV1A	; NO, SKIP NEXT INSTRUCTION
	LD	HL,DUART_BAUDTBL_92	; GET START OF SC26C92 TABLE IN HL
;
DUART_INITDEV1A:
	LD	A,D			; GET CONFIG MSB
	AND	$1F			; ISOLATE ENCODED BAUD RATE
	CALL	ADDHLA			; HL -> ENTRY
	LD	A,(HL)			; A = ENTRY
	INC	A			; A = $FF?
	JP	Z,DUART_INITDEVZ	; INVALID RATE, ERROR OUT
	DEC	A			; GET ORIGINAL VALUE BACK
;
	; GOT A VALID RATE, COMMIT NEW CONFIG
	LD	(IY + 8),E		; SAVE LOW WORD
	LD	(IY + 9),D		; SAVE HI WORD
;
	; START OF ACTUAL DUART CHANNEL CONFIGURATION
	LD	L,A			; SAVE BAUD TABLE ENTRY IN L
	LD	A,DUART_CR_DIS_RX | DUART_CR_DIS_TX
	DUART_OUTP(DUART_CR)		; DISABLE RECEIVER AND TRANSMITTER
	LD	A,DUART_CR_RESET_RX
	DUART_OUTP(DUART_CR)		; RESET RECEIVER
	LD	A,DUART_CR_RESET_TX
	DUART_OUTP(DUART_CR)		; RESET TRANSMITTER
	LD	A,DUART_CR_RESET_ERR
	DUART_OUTP(DUART_CR)		; RESET ERROR STATUS
	LD	A,(IY + 1)		; GET DUART TYPE
	CP	DUART_26C92		; IS IT A 26C92?
	JR	Z,DUART_INITDEV1B	; YES
	CALL	DUART_SETBAUD_681	; NO, CALL '681 BRG SETUP
	JR	DUART_INITDEV2
;
DUART_INITDEV1B:
	CALL	DUART_SETBAUD_92	; CALL '92 BRG SETUP
;
DUART_INITDEV2:
;
	; SET PARITY AND WORD SIZE
	LD	A,DUART_CR_MR1
	DUART_OUTP(DUART_CR)		; SET MR POINTER TO MR1
	LD	A,E			; GET LOW WORD OF CONFIG IN A
	AND	%00111000		; KEEP ONLY PARITY BITS
	RRA
	RRA
	RRA				; SHIFT PARITY BITS INTO AN INDEX
	LD	HL,DUART_PARTBL		; GET START OF TABLE IN HL
	CALL	ADDHLA			; HL -> ENTRY
	LD	B,(HL)			; BUILD MR1 IN B
	LD	A,E			; GET LOW WORD OF CONFIG IN A
	AND	%00000011		; WORD LENGTH BITS ARE THE SAME
	OR	B			; MERGE PARITY BITS
	OR	DUART_MR1_RXRTS		; ALWAYS ENABLE RECEIVER CONTROL OF RTS
	DUART_OUTP(DUART_MR)		; WRITE MR1 (AND SET MR POINTER TO MR2)
;
	; SET STOP BITS
	LD	A,E			; GET LOW WORD OF CONFIG IN A
	LD	B,DUART_MR2_STOP1	; BUILD MR2 IN B
	AND	%00000100		; KEEP ONLY STOP BITS
	JR	Z,DUART_INITDEV4	; 1 STOP BIT
	LD	B,DUART_MR2_STOP2	; 2 STOP BITS, REPLACE B
;
DUART_INITDEV4:
	LD	A,B			; GET MR2 IN A
	;OR	DUART_MR2_TXCTS		; ALWAYS ENABLE CTS CONTROL OF TRANSMITTER
	DUART_OUTP(DUART_MR)		; WRITE MR2
;
	; RE-ENABLE RECEIVER AND TRANSMITTER
	LD	A,DUART_CR_ENA_RX | DUART_CR_ENA_TX
	DUART_OUTP(DUART_CR)		; ENABLE RECEIVER AND TRANSMITTER
;
	; EXPLICITLY ASSERT RTS (SEEMS TO BE REQUIRED FOR SOME CHIPS TO DO AUTO-RTS)
	LD	L,%00000001		; RTS FOR CHANNEL A IS IN BIT 0
	LD	A,(IY)			; GET UNIT NUMBER IN A
	AND	L			; MASK ALL BUT CHANNEL
	JR	Z,DUART_INITDEV5	; ZERO INDICATES CHANNEL A
	SLA	L			; MOVE INTO BIT 1, RTS FOR CHANNEL B
;
DUART_INITDEV5:
	LD	A,(IY + 2)		; GET BASE ADDRESS OF CHIP
	ADD	A,DUART_SOPR		; SET OUTPUT BITS
	LD	C,A			; GET PORT IN C
	OUT	(C),L			; OUTPUT PORT IS INVERTED BUT SO IS RTS
;
	XOR	A			; SIGNAL SUCCESS
	RET
;
DUART_INITDEVZ:
;
	; INVALID BAUD RATE
	DEC	A			; A WAS $00, GET BACK $FF
	RET				; RETURN ERROR STATUS
;
; INITIALIZE BRG FOR '681 DUART
;
DUART_SETBAUD_681:
	; SET ACR
	LD	C,(IY + 6)		; GET SHADOW ACR FOR THIS CHIP
	LD	B,(IY + 7)		; BC IS POINTER
	LD	A,(BC)			; GET SHADOW ACR IN A
	AND	%01111111		; MASK OUT BIT 7
	LD	H,A			; SAVE IT IN H
	LD	A,L			; TABLE ENTRY IS IN L, GET IT IN A
	AND	DUART_BAUD_ACR7		; SEE IF ACR[7] SHOULD BE SET (BIT MASK SHOULD ACTUALLY _BE_ BIT 7)
	OR	H			; MERGE IN REST OF ACR
	LD	H,A			; SAVE IT IN H
	LD	A,(IY + 2)		; GET CHIP BASE IN A
	ADD	A,DUART_ACR		; ADD OFFSET OF ACR
	LD	C,A			; C = ACR PORT
	; YES, THIS OVERWRITES ACR[7] REGARDLESS OF THE OTHER CHANNEL,
	; BUT CURRENTLY THE TABLE IS SET SO EVERY VALID RATE HAS ACR[7] SET
	OUT	(C),H			; WRITE VALUE
	; SELECT PER-CHANNEL EXTENDED TABLE
	LD	A,L			; CALLED WITH TABLE ENTRY IN L, MOVE IT TO A
	AND	DUART_BAUD_X1		; SEE IF SELECT EXTEND BIT SHOULD BE SET
	JR	Z,DUART_SETBAUD_681A	; NO, CLEAR IT
	LD	A,DUART_CR_SET_RX_X	; YES, SET EXTEND BIT
	DUART_OUTP(DUART_CR)		; SET FOR RECEIVER
	LD	A,DUART_CR_SET_TX_X
	DUART_OUTP(DUART_CR)		; SET FOR TRANSMITTER
	JR	DUART_SETBAUD_681B
;
DUART_SETBAUD_681A:
	; CLEAR EXTEND BIT
	LD	A,DUART_CR_CLR_RX_X
	DUART_OUTP(DUART_CR)		; CLEAR FOR RECEIVER
	LD	A,DUART_CR_CLR_TX_X
	DUART_OUTP(DUART_CR)		; CLEAR FOR TRANSMITTER
;
DUART_SETBAUD_681B:
	; SET BRG CLOCK SELECT
	LD	A,L			; GET BAUD TABLE ENTRY IN A
	AND	$0F			; GET CLOCK SELECT BITS
	LD	L,A			; SAVE IT IN L
	RLA
	RLA
	RLA
	RLA				; MOVE IT INTO THE HIGH NIBBLE
	OR	L			; AND MERGE BACK IN LOW NIBBLE
	DUART_OUTP(DUART_CSR)		; SET CLOCK SELECT
	RET
;
DUART_BAUDTBL_681:
	; ASSUME XR88C681 RUNS AT 3.6864MHZ
	.DB	%0000 | DUART_BAUD_X1	; 75
	.DB	%0011 | DUART_BAUD_X1	; 150
	.DB	%0100			; 300
	.DB	%0101			; 600
	.DB	%0110			; 1200
	.DB	%1000			; 2400
	.DB	%1001			; 4800
	.DB	%1011			; 9600
	.DB	%1100 | DUART_BAUD_X1	; 19200
	.DB	%1100			; 38400
	.DB	DUART_BAUD_INV		; 76800
	.DB	DUART_BAUD_INV		; 153600
	.DB	DUART_BAUD_INV		; 307200
	.DB	DUART_BAUD_INV		; 614400
	.DB	DUART_BAUD_INV		; 1228800
	.DB	DUART_BAUD_INV		; 2457600
	.DB	DUART_BAUD_INV		; 225
	.DB	DUART_BAUD_INV		; 450
	.DB	DUART_BAUD_INV		; 900
	.DB	%1010 | DUART_BAUD_X1	; 1800
	.DB	%0100 | DUART_BAUD_X1	; 3600
	.DB	%1010			; 7200
	.DB	%0101 | DUART_BAUD_X1	; 14400
	.DB	%0110 | DUART_BAUD_X1	; 28800
	.DB	%0111 | DUART_BAUD_X1	; 57600
	.DB	%1000 | DUART_BAUD_X1	; 115200
	.DB	DUART_BAUD_INV		; 230400
	.DB	DUART_BAUD_INV		; 460800
	.DB	DUART_BAUD_INV		; 921600
	.DB	DUART_BAUD_INV		; 1843200
	.DB	DUART_BAUD_INV		; 3686400
	.DB	DUART_BAUD_INV		; 7372800
;
; INITIALIZE BRG FOR '92 DUART
;
DUART_SETBAUD_92:
	; SET ACR
	LD	C,(IY + 6)		; GET SHADOW ACR FOR THIS CHIP
	LD	B,(IY + 7)		; BC IS POINTER
	LD	A,(BC)			; GET SHADOW ACR IN A
	AND	%01111111		; MASK OUT BIT 7
	LD	H,A			; SAVE IT IN H
	LD	A,L			; TABLE ENTRY IS IN L, GET IT IN A
	AND	DUART_BAUD_ACR7		; SEE IF ACR[7] SHOULD BE SET (BIT MASK SHOULD ACTUALLY _BE_ BIT 7)
	OR	H			; MERGE IN REST OF ACR
	LD	H,A			; SAVE IT IN H
	LD	A,(IY + 2)		; GET CHIP BASE IN A
	ADD	A,DUART_ACR		; ADD OFFSET OF ACR
	LD	C,A			; C = ACR PORT
	; YES, THIS OVERWRITES ACR[7] REGARDLESS OF THE OTHER CHANNEL,
	; BUT CURRENTLY THE TABLE IS SET SO EVERY VALID RATE HAS ACR[7] SET
	OUT	(C),H			; WRITE VALUE
	; SELECT NORMAL OR EXTENDED BAUD RATE TABLES
	LD	H,DUART_MR0_NORMAL	; ASSUME NORMAL
	LD	A,L			; GET TABLE ENTRY IN A AGAIN
	AND	DUART_BAUD_EXT1		; SHOULD EXT1 BE SET?
	JR	Z,DUART_SETBAUD_92A	; NO, CHECK NEXT VALUE
	LD	H,DUART_MR0_EXT1	; YES, SET IT
	JR	DUART_SETBAUD_92C
;
DUART_SETBAUD_92A:
	LD	A,L			; GET TABLE ENTRY IN A ONCE MORE
	AND	DUART_BAUD_EXT2		; SHOULD EXT2 BE SET?
	JR	Z,DUART_SETBAUD_92C	; NO, CONTINUE
	LD	H,DUART_MR0_EXT2	; YES, SET IT
;
DUART_SETBAUD_92C:
	; H NOW CONTAINS MR0
	LD	A,(IY + 2)		; GET CHIP BASE IN A
	ADD	A,DUART_CR		; WE WANT TO WRITE THE COMMAND REGISTER OF CHANNEL A, EVEN IF WE'RE CHANNEL B
	LD	C,A			; C = CRA
	LD	A,DUART_CR_MR0		; RESET MR POINTER TO MR0
	OUT	(C),A			; WRITE COMMAND
	LD	A,(IY + 2)		; GET CHIP BASE IN A
	ADD	A,DUART_MR		; NOW WE WANT TO WRITE TO MR0 OF CHANNEL A
	LD	C,A			; C = MRA
	; AS WITH ACR[7] THE TABLE IS SET SO EVERY VALID RATE IS FROM
	; THE SAME TABLE
	OUT	(C),H
	; SET BRG CLOCK SELECT
	LD	A,L			; GET BAUD TABLE ENTRY IN A YET AGAIN
	AND	$0F			; GET CLOCK SELECT BITS
	LD	L,A			; SAVE IT IN L
	RLA
	RLA
	RLA
	RLA				; MOVE IT INTO THE HIGH NIBBLE
	OR	L			; AND MERGE BACK IN LOW NIBBLE
	DUART_OUTP(DUART_CSR)		; SET CLOCK SELECT OF CURRENT CHANNEL
	RET
;
DUART_BAUDTBL_92:
	; ASSUME SC26C92 RUNS AT 7.3728MHZ
	.DB	DUART_BAUD_INV					; 75
	.DB	DUART_BAUD_INV					; 150
	.DB	DUART_BAUD_INV					; 300
	.DB	DUART_BAUD_INV					; 600
	.DB	DUART_BAUD_INV					; 1200
	.DB	DUART_BAUD_INV					; 2400
	.DB	DUART_BAUD_INV					; 4800
	.DB	%1001 | DUART_BAUD_EXT2 | DUART_BAUD_ACR7	; 9600
	.DB	%1011 | DUART_BAUD_EXT2 | DUART_BAUD_ACR7	; 19200
	.DB	%1100 | DUART_BAUD_EXT2 | DUART_BAUD_ACR7	; 38400
	.DB	DUART_BAUD_INV					; 76800
	.DB	DUART_BAUD_INV					; 153600
	.DB	DUART_BAUD_INV					; 307200
	.DB	DUART_BAUD_INV					; 614400
	.DB	DUART_BAUD_INV					; 1228800
	.DB	DUART_BAUD_INV					; 2457600
	.DB	DUART_BAUD_INV					; 225
	.DB	DUART_BAUD_INV					; 450
	.DB	DUART_BAUD_INV					; 900
	.DB	DUART_BAUD_INV					; 1800
	.DB	DUART_BAUD_INV					; 3600
	.DB	DUART_BAUD_INV					; 7200
	.DB	%0000 | DUART_BAUD_EXT2 | DUART_BAUD_ACR7	; 14400
	.DB	%0011 | DUART_BAUD_EXT2 | DUART_BAUD_ACR7	; 28800
	.DB	%0100 | DUART_BAUD_EXT2 | DUART_BAUD_ACR7	; 57600
	.DB	%0101 | DUART_BAUD_EXT2 | DUART_BAUD_ACR7	; 115200
	.DB	%0110 | DUART_BAUD_EXT2 | DUART_BAUD_ACR7	; 230400
	.DB	DUART_BAUD_INV					; 460800
	.DB	DUART_BAUD_INV					; 921600
	.DB	DUART_BAUD_INV					; 1843200
	.DB	DUART_BAUD_INV					; 3686400
	.DB	DUART_BAUD_INV					; 7372800
;
DUART_PARTBL:
	.DB	DUART_MR1_PARNONE	; 0 = NO PARITY (ALSO ALL EVEN ENTRIES)
	.DB	DUART_MR1_PARODD	; 1 = ODD PARITY
	.DB	DUART_MR1_PARNONE
	.DB	DUART_MR1_PAREVEN	; 3 = EVEN PARITY
	.DB	DUART_MR1_PARNONE
	.DB	DUART_MR1_PARMARK	; 5 = MARK PARITY
	.DB	DUART_MR1_PARNONE
	.DB	DUART_MR1_PARSPACE	; 7 = SPACE PARITY
;
;
;
DUART_QUERY:
	LD	E,(IY + 8)		; FIRST CONFIG BYTE TO E
	LD	D,(IY + 9)		; SECOND CONFIG BYTE TO D
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
;
;
DUART_DEVICE:
	LD	D,CIODEV_DUART		; D := DEVICE TYPE
	LD	E,(IY)			; E := PHYSICAL UNIT
	LD	C,$00			; C := DEVICE TYPE, 0x00 IS RS-232
	LD	H,0			; H := 0, DRIVER HAS NO MODES
	LD	L,(IY+3)		; L := BASE I/O ADDRESS
	XOR	A			; SIGNAL SUCCESS
	RET
;
; DUART DETECTION ROUTINE
;
DUART_DETECT:
;
	; FIRST SEE IF IT LOOKS LIKE A 16X50-STYLE UART
	LD	A,(IY + 2)		; GET BASE PORT OF CHIP
	ADD	A,4			; BASE + 4 = ACR (DUART), MCR (1ST 16X50)
	LD	H,A			; H := ACR/MCR PORT ADDRESS
	ADD	A,2			; BASE + 6 = CTU (DUART), MSR (1ST 16X50)
	LD	B,A			; B := CTU/MSR PORT ADDRESS
	INC	A			; BASE + 7 = CTL (DUART), SCR (1ST 16X50)
	LD	D,A			; D := CTL/SCR PORT ADDRESS
	ADD	A,7			; BASE + 14 = STCR (DUART), MSR (2ND 16X50)
	LD	E,A			; E := STCR/MSR PORT ADDRESS
	INC	A			; BASE + 15 = SPCR (DUART), SCR (2ND 16X50)
	LD	C,A			; SPCR
	IN	A,(C)			; STOP COUNTER/TIMER, JUST IN CASE
	LD	C,H			; ACR/MCR
	IN	L,(C)			; GET ORIGINAL VALUE (ACTUALLY IPCR ON DUART) IN L
	LD	A,$30			; ENABLE A SOURCE FOR THE COUNTER/TIMER
	OUT	(C),A			; WRITE TO ACR/MCR
	LD	A,$A5			; TEST VALUE
	LD	C,B			; CTU
	OUT	(C),A			; WRITE TEST VALUE TO CTU
	LD	A,$FF			; LARGE VALUE TO PREVENT CTL FROM ROLLING OVER WHILE WE TEST
	LD	C,D			; CTL
	OUT	(C),A			; WRITE LARGE VALUE TO CTL
	LD	C,E			; STCR
	IN	A,(C)			; START COUNTER/TIMER (LATCH CTU, CTL)
	INC	C			; C := SPCR
	IN	A,(C)			; STOP COUNTER/TIMER
	LD	C,H			; ACR/MCR
	OUT	(C),L			; WRITE ORIGINAL VALUE OF MCR (ACR GETS SET ON DUART LATER)
	LD	C,B			; CTU
	IN	A,(C)			; READ BACK TEST VALUE
	CP	$A5			; CHECK FOR TEST VALUE
	JR	NZ,DUART_DETECT_NONE	; NO, PROBABLY NOT A DUART
	IN	A,(C)			; CHECK TEST VALUE AGAIN,
	CP	$A5			; ... IN RARE CASE DELTAS IN MSR WERE SET TO OUR TEST
	JR	NZ,DUART_DETECT_NONE	; ALMOST CERTAINLY NOT A DUART
	; SEE IF MR1 AND MR2 ARE DISTINCT
	LD	A,DUART_CR_MR1		; SET MR POINTER TO MR1
	DUART_OUTP(DUART_CR)		; SEND COMMAND
	LD	A,1			; WRITE TEST VALUE TO MR1
	DUART_OUTP(DUART_MR)		; WRITE MR1 AND SET POINTER TO MR2
	XOR	A			; WRITE 0 TO MR2
	DUART_OUTP(DUART_MR)		; WRITE MR2 AND KEEP POINTER TO MR2
	LD	A,DUART_CR_MR1		; SET MR POINTER TO MR1 (AGAIN)
	DUART_OUTP(DUART_CR)		; SEND COMMAND
	DUART_INP(DUART_MR)		; GET VALUE OF MR1 IN A
	CP	1			; CHECK FOR TEST VALUE
	JR	NZ,DUART_DETECT_NONE	; NOPE, UNKNOWN DEVICE OR NOT PRESENT
;
	; TEST FOR FUNCTIONAL GENERAL PURPOSE REG, IF NOT, WE HAVE A 2681
	LD	A,$5A			; LOAD TEST VALUE
	DUART_OUTP(DUART_GPR)		; PUT IT IN GENERAL PURPOSE REGISTER
	DUART_INP(DUART_GPR)		; READ IT BACK
	CP	$5A			; CHECK IT
	JR	NZ,DUART_DETECT_2681	; OLD CHIP
;
	; TEST FOR MR0 REGISTER, IN WHICH CASE WE HAVE A 26C92 OF SOME SORT
	LD	A,DUART_CR_MR0		; SET MR POINTER TO MR0
	DUART_OUTP(DUART_CR)		; THIS IS HARMLESS ON OTHER CHIPS
	LD	A,1			; WRITE TEST VALUE TO MR0
	DUART_OUTP(DUART_MR)		; WRITE TO MR0 ON 26C92, MR2 STILL SET ON OTHERS
	LD	A,DUART_CR_MR1		; SET MR POINTER TO MR1
	DUART_OUTP(DUART_CR)		; THIS WORKS ON ALL CHIPS
	XOR	A			; WRITE 0 TO MR1
	DUART_OUTP(DUART_MR)		; WRITE MR1 AND SET POINTER TO MR2
	XOR	A			; ALSO WRITE 0 TO MR2
	DUART_OUTP(DUART_MR)		; WRITE MR2 AND KEEP POINTER TO MR2
	LD	A,DUART_CR_MR0		; SET POINTER TO MR0
	DUART_OUTP(DUART_CR)		; POINTER IS STILL MR2 ON OTHER CHIPS
	DUART_INP(DUART_MR)		; GET VALUE OF MR0 IN A
	AND	1			; MASK TEST VALUE IN BIT 1
	JR	NZ,DUART_DETECT_26C92	; IF IT'S SET, THIS MUST BE A '92 WITH MR0

	JR	DUART_DETECT_XR88C681	; ASSUME WE HAVE A FANCY EXAR CHIP
;
DUART_DETECT_NONE:
	LD	A,DUART_NONE
	RET
;
DUART_DETECT_2681:
	LD	A,DUART_2681
	RET
;
DUART_DETECT_26C92:
	LD	A,DUART_26C92
	RET
;
DUART_DETECT_XR88C681
	LD	A,DUART_XR88C681
	RET
;
;
;
DUART_PRTCFG:
	; ANNOUNCE PORT
	CALL	NEWLINE			; FORMATTING
	PRTS("DUART$")			; FORMATTING
	LD	A,(IY)			; DEVICE NUM
	CALL	PRTDECB			; PRINT DEVICE NUM
	PRTS(": IO=0x$")		; FORMATTING
	LD	A,(IY + 3)		; GET CHANNEL BASE PORT
	CALL	PRTHEXBYTE		; PRINT BASE PORT

	; PRINT THE DUART TYPE
	CALL	PC_SPACE		; FORMATTING
	LD	A,(IY + 1)		; GET DUART TYPE BYTE
	RLCA				; MAKE IT A WORD OFFSET
	LD	HL,DUART_TYPE_MAP	; POINT HL TO TYPE MAP TABLE
	CALL	ADDHLA			; HL := ENTRY
	LD	E,(HL)			; DEREFERENCE
	INC	HL			; ...
	LD	D,(HL)			; ... TO GET STRING POINTER
	CALL	WRITESTR		; PRINT IT
;
	; ALL DONE IF NO DUART WAS DETECTED
	LD	A,(IY + 1)		; GET DUART TYPE BYTE
	OR	A			; SET FLAGS
	RET	Z			; IF ZERO, NOT PRESENT
;
	PRTS(" MODE=$")			; FORMATTING
	LD	E,(IY + 8)		; LOAD CONFIG
	LD	D,(IY + 9)		; ... WORD TO DE
	CALL	PS_PRTSC0		; PRINT CONFIG
;
	XOR	A
	RET
;
; ROUTINES TO READ/WRITE PORTS INDIRECTLY
;
; READ VALUE OF DUART PORT ON TOS INTO REGISTER A
;
DUART_INP_IMP:
	EX	(SP),HL		; SWAP HL AND TOS
	PUSH	BC		; PRESERVE BC
	LD	A,(IY + 3)	; GET DUART IO BASE PORT
	OR	(HL)		; OR IN REGISTER ID BITS
	LD	C,A		; C := PORT
	IN	A,(C)		; READ PORT INTO A
	POP	BC		; RESTORE BC
	INC	HL		; BUMP HL PAST REG ID PARM
	EX	(SP),HL		; SWAP BACK HL AND TOS
	RET
;
; WRITE VALUE IN REGISTER A TO DUART PORT ON TOS
;
DUART_OUTP_IMP:
	EX	(SP),HL		; SWAP HL AND TOS
	PUSH	BC		; PRESERVE BC
	LD	B,A		; PUT VALUE TO WRITE IN B
	LD	A,(IY + 3)	; GET DUART IO BASE PORT
	OR	(HL)		; OR IN REGISTER ID BITS
	LD	C,A		; C := PORT
	OUT	(C),B		; WRITE VALUE TO PORT
	POP	BC		; RESTORE BC
	INC	HL		; BUMP HL PAST REG ID PARM
	EX	(SP),HL		; SWAP BACK HL AND TOS
	RET
;
;
;
DUART_TYPE_MAP:
			.DW	DUART_STR_NONE
			.DW	DUART_STR_2681
			.DW	DUART_STR_26C92
			.DW	DUART_STR_XR88C681

DUART_STR_NONE		.DB	"<NOT PRESENT>$"
DUART_STR_2681		.DB	"2681$"
DUART_STR_26C92		.DB	"26C92$"
DUART_STR_XR88C681	.DB	"XR88C681$"
;
; WORKING VARIABLES
;
DUART_DEV		.DB	0		; DEVICE NUM USED DURING INIT
;
; PER-CHIP VARIABLES
;
DUART0_ACR		.DB	0		; SHADOW ACR (DUART 0)
;
#IF (DUARTCNT >= 2)
;
DUART1_ACR		.DB	0		; SHADOW ACR (DUART 1)
;
#ENDIF
;
; DUART PORT TABLE
;
DUART_CFG:
;
DUART0A_CFG:
	; 1ST DUART MODULE CHANNEL A
	.DB	0			; IY	DEVICE NUMBER (SET DURING INIT)
	.DB	0			; IY+1	DUART TYPE (SET DURING INIT)
	.DB	DUART0BASE		; IY+2	BASE PORT (CHIP)
	.DB	DUART0BASE + $00	; IY+3	BASE PORT (CHANNEL)
	.DW	DUART0B_CFG		; IY+4	POINTER TO OTHER CHANNEL
	.DW	DUART0_ACR		; IY+6	POINTER TO SHADOW ACR FOR THIS CHIP
	.DW	DUART0ACFG		; IY+8	LINE CONFIGURATION
	.DB	1			; IY+10	MULTIPLIER WRT 3.6864MHZ CLOCK
;
	DEVECHO	"DUART: IO="
	DEVECHO	DUART0BASE + $00
	DEVECHO	", CHANNEL A\n"
;
DUART_CFGSIZ	.EQU	$ - DUART_CFG	; SIZE OF ONE CFG TABLE ENTRY
;
DUART0B_CFG:
	; 1ST DUART MODULE CHANNEL B
	.DB	0			; DEVICE NUMBER (SET DURING INIT)
	.DB	0			; DUART TYPE (SET DURING INIT)
	.DB	DUART0BASE		; BASE PORT (CHIP)
	.DB	DUART0BASE + $08	; BASE PORT (CHANNEL)
	.DW	DUART0A_CFG		; POINTER TO OTHER CHANNEL
	.DW	DUART0_ACR		; POINTER TO SHADOW ACR FOR THIS CHIP
	.DW	DUART0BCFG		; LINE CONFIGURATION
	.DB	1			; MULTIPLIER WRT 3.6864MHZ CLOCK
;
	DEVECHO	"DUART: IO="
	DEVECHO	DUART0BASE + $08
	DEVECHO	", CHANNEL B\n"
;
#IF (DUARTCNT >= 2)
;
DUART1A_CFG:
	; 2ND DUART MODULE CHANNEL A
	.DB	0			; DEVICE NUMBER (SET DURING INIT)
	.DB	0			; DUART TYPE (SET DURING INIT)
	.DB	DUART1BASE		; BASE PORT (CHIP)
	.DB	DUART1BASE + $00	; BASE PORT (CHANNEL)
	.DW	DUART1B_CFG		; POINTER TO OTHER CHANNEL
	.DW	DUART1_ACR		; POINTER TO SHADOW ACR FOR THIS CHIP
	.DW	DUART1ACFG		; LINE CONFIGURATION
	.DB	1			; MULTIPLIER WRT 3.6864MHZ CLOCK
;
	DEVECHO	"DUART: IO="
	DEVECHO	DUART1BASE + $00
	DEVECHO	", CHANNEL A\n"
;
DUART1B_CFG:
	; 2ND DUART MODULE CHANNEL B
	.DB	0			; DEVICE NUMBER (SET DURING INIT)
	.DB	0			; DUART TYPE (SET DURING INIT)
	.DB	DUART1BASE		; BASE PORT (CHIP)
	.DB	DUART1BASE + $08	; BASE PORT (CHANNEL)
	.DW	DUART1A_CFG		; POINTER TO OTHER CHANNEL
	.DW	DUART1_ACR		; POINTER TO SHADOW ACR FOR THIS CHIP
	.DW	DUART1BCFG		; LINE CONFIGURATION
	.DB	1			; MULTIPLIER WRT 3.6864MHZ CLOCK
;
	DEVECHO	"DUART: IO="
	DEVECHO	DUART1BASE + $08
	DEVECHO	", CHANNEL B\n"
;
#ENDIF
;
DUART_CFGCNT	.EQU    ($ - DUART_CFG) / DUART_CFGSIZ
