;======================================================================
;	VIDEO DRIVER FOR FPGA VGA
;	http://s100computers.com/My%20System%20Pages/FPGA%20Z80%20SBC/FPGA%20Z80%20SBC.htm
;
;	WRITTEN BY: WAYNE WARTHEN -- 9/2/2024
;======================================================================
;
; FPGA VGA EXPOSES A FRAME BUFFER STARTING AT $E000.
; PORT $08 CONTROLS ACCESS TO THE FRAME BUFFER.
;   - WHEN $01, FRAME BUFFER APPEARS AT $E000 IN CPU ADDRESS SPACE
;   - WHEN $00, FRAME BUFFER IS INACCESSIBLE BY CPU
; PORT $C0: SET/GET CURSOR COL
; PORT $C1: SET/GET CURSOR ROW
; PORT $C2: CONTROLS VGA OUTPUT
;  BIT 0: BLUE
;  BIT 1: GREEN
;  BIT 2: RED
;  BIT 3: UNUSED?
;  BIT 4: CURSOR MODE
;  BIT 5: CURSOR BLINK
;  BIT 6: CURSOR ENABLE
;  BIT 7: VGA SIGNAL OUTPUT ENABLE
; PORT $08: BUFFER SELECT, 1=SELECTED
;
; TODO:
;
;======================================================================
; FPGA VGA DRIVER - CONSTANTS
;======================================================================
;
FV_FBUF		.EQU	$E000		; ADDRESS OF FRAME BUFFER
FV_BASE		.EQU	$C0		; BASE I/O ADDRESS
FV_CCOL		.EQU	FV_BASE+0	; CUR COL PORT
FV_CROW		.EQU	FV_BASE+1	; CUR ROW PORT
FV_CTL		.EQU	FV_BASE+2	; VGA CONTROL PORT
;
FV_BUFCTL	.EQU	$08
;
FV_KBDDATA	.EQU	$03
FV_KBDST	.EQU	$02
;
FV_ROWS	.EQU	40
FV_COLS	.EQU	80
;
TERMENABLE	.SET	TRUE		; INCLUDE TERMINAL PSEUDODEVICE DRIVER
;;;KBDENABLE	.SET	TRUE		; INCLUDE KBD KEYBOARD SUPPORT
;
		DEVECHO	"FV: IO="
		DEVECHO	FV_BASE
		DEVECHO	", KBD MODE=FV"
		DEVECHO	", KBD IO="
		DEVECHO	FV_KBDDATA
		DEVECHO	"\n"
;
;======================================================================
; FPGA VGA DRIVER - INITIALIZATION
;======================================================================
;
FV_INIT:
	LD	IY,FV_IDAT		; POINTER TO INSTANCE DATA
;
	CALL	NEWLINE			; FORMATTING
	PRTS("FV: IO=0x$")
	LD	A,FV_BASE
	CALL	PRTHEXBYTE
	CALL	FV_PROBE		; CHECK FOR HW PRESENCE
	JR	Z,FV_INIT1		; CONTINUE IF HW PRESENT
;
	; HARDWARE NOT PRESENT
	PRTS(" NOT PRESENT$")
	OR	$FF			; SIGNAL FAILURE
	RET
;
FV_INIT1:
	; RECORD DRIVER ACTIVE
	OR	$FF
	LD	(FV_ACTIVE),A
	; DISPLAY CONSOLE DIMENSIONS
	LD	A,FV_COLS
	CALL	PC_SPACE
	CALL	PRTDECB
	LD	A,'X'
	CALL	COUT
	LD	A,FV_ROWS
	CALL	PRTDECB
	PRTS(" TEXT$")

	; HARDWARE INITIALIZATION
	CALL 	FV_CRTINIT		; SETUP THE FPGA VGA CHIP REGISTERS
	CALL	FV_VDAINI		; INITIALIZE
	;CALL	KBD_INIT		; INITIALIZE KEYBOARD DRIVER

	; ADD OURSELVES TO VDA DISPATCH TABLE
	LD	BC,FV_FNTBL		; BC := FUNCTION TABLE ADDRESS
	LD	DE,FV_IDAT		; DE := FPGA VGA INSTANCE DATA PTR
	CALL	VDA_ADDENT		; ADD ENTRY, A := UNIT ASSIGNED

	; INITIALIZE EMULATION
	LD	C,A			; C := ASSIGNED VIDEO DEVICE NUM
	LD	DE,FV_FNTBL		; DE := FUNCTION TABLE ADDRESS
	LD	HL,FV_IDAT		; HL := FPGA VGA INSTANCE DATA PTR
	CALL	TERM_ATTACH		; DO IT

	XOR	A			; SIGNAL SUCCESS
	RET
;
;======================================================================
; FPGA VGA DRIVER - VIDEO DISPLAY ADAPTER (VDA) FUNCTIONS
;======================================================================
;
FV_FNTBL:
	.DW	FV_VDAINI
	.DW	FV_VDAQRY
	.DW	FV_VDARES
	.DW	FV_VDADEV
	.DW	FV_VDASCS
	.DW	FV_VDASCP
	.DW	FV_VDASAT
	.DW	FV_VDASCO
	.DW	FV_VDAWRC
	.DW	FV_VDAFIL
	.DW	FV_VDACPY
	.DW	FV_VDASCR
	.DW	FV_STAT
	.DW	FV_FLUSH
	.DW	FV_READ
	.DW	FV_VDARDC
#IF (($ - FV_FNTBL) != (VDA_FNCNT * 2))
	.ECHO	"*** INVALID FV FUNCTION TABLE ***\n"
	!!!!!
#ENDIF

FV_VDAINI:
	; RESET VDA
	CALL	FV_VDARES	; RESET VDA
	LD	HL,0		; ZERO
	LD	(FV_POS),HL	; ... TO POSITION
	LD	A,' '		; BLANK THE SCREEN
	LD	DE,FV_ROWS*FV_COLS	; FILL ENTIRE BUFFER
	CALL	FV_FILL		; DO IT
	LD	DE,0		; ROW = 0, COL = 0
	CALL	FV_XY		; SEND CURSOR TO TOP LEFT
	CALL	FV_SHOWCUR	; NOW SHOW THE CURSOR
	XOR	A		; SIGNAL SUCCESS
	RET

FV_VDAQRY:
	LD	C,$00		; MODE ZERO IS ALL WE KNOW
	LD	D,FV_ROWS	; ROWS
	LD	E,FV_COLS	; COLS
	LD	HL,0		; EXTRACTION OF CURRENT BITMAP DATA NOT SUPPORTED
	XOR	A		; SIGNAL SUCCESS
	RET

FV_VDARES:
	CALL	 FV_CRTINIT
	XOR	A		; SIGNAL SUCCESS
	RET

FV_VDADEV:
	LD	D,VDADEV_FV	; D := DEVICE TYPE
	LD	E,0		; E := PHYSICAL UNIT IS ALWAYS ZERO
	LD	H,0		; H := 0, DRIVER HAS NO MODES
	LD	L,FV_BASE	; L := BASE I/O ADDRESS
	XOR	A		; SIGNAL SUCCESS
	RET

FV_VDASCS:
	SYSCHKERR(ERR_NOTIMPL)	; NOT IMPLEMENTED (YET)
	RET

FV_VDASCP:
	CALL	FV_XY		; SET CURSOR POSITION
	XOR	A		; SIGNAL SUCCESS
	RET

FV_VDASAT:
	; ATTRIBUTES NOT SUPPORTED BY HARDWARE
	XOR	A
	RET

FV_VDASCO:
	; CHARACTER COLOR NOT SUPPORT BY HARDWARE
	XOR	A		; SIGNAL SUCCESS
	RET			; DONE

FV_VDAWRC:
	LD	A,E		; CHARACTER TO WRITE GOES IN A
	CALL	FV_PUTCHAR	; PUT IT ON THE SCREEN
	XOR	A		; SIGNAL SUCCESS
	RET

FV_VDAFIL:
	LD	A,E		; FILL CHARACTER GOES IN A
	EX	DE,HL		; FILL LENGTH GOES IN DE
	CALL	FV_FILL		; DO THE FILL
	XOR	A		; SIGNAL SUCCESS
	RET

FV_VDACPY:
	; LENGTH IN HL, SOURCE ROW/COL IN DE, DEST IS FV_POS
	; BLKCPY USES: HL=SOURCE, DE=DEST, BC=COUNT
	PUSH	HL		; SAVE LENGTH
	CALL	FV_XY2IDX	; ROW/COL IN DE -> SOURCE ADR IN HL
	POP	BC		; RECOVER LENGTH IN BC
	LD	DE,(FV_POS)	; PUT DEST IN DE
	JP	FV_BLKCPY	; DO A BLOCK COPY

FV_VDASCR:
	LD	A,E		; LOAD E INTO A
	OR	A		; SET FLAGS
	RET	Z		; IF ZERO, WE ARE DONE
	PUSH	DE		; SAVE E
	JP	M,FV_VDASCR1	; E IS NEGATIVE, REVERSE SCROLL
	CALL	FV_SCROLL	; SCROLL FORWARD ONE LINE
	POP	DE		; RECOVER E
	DEC	E		; DECREMENT IT
	JR	FV_VDASCR	; LOOP
FV_VDASCR1:
	CALL	FV_RSCROLL	; SCROLL REVERSE ONE LINE
	POP	DE		; RECOVER E
	INC	E		; INCREMENT IT
	JR	FV_VDASCR	; LOOP

FV_STAT:
	IN	A,(FV_KBDST)		; GET STATUS
	AND	$01			; ISOLATE DATA WAITING BIT
	JP	Z,CIO_IDLE		; NO DATA, EXIT VIA IDLE PROCESS
	RET

FV_FLUSH:
	XOR	A			; SIGNAL SUCCESS
	RET

FV_READ:
	CALL	FV_STAT			; GET STATUS
	JR	Z,FV_READ		; LOOP TILL DATA READY
	IN	A,(FV_KBDDATA)		; GET BYTE
	LD	E,A			; PUT IN E FOR RETURN
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE

;----------------------------------------------------------------------
; READ VALUE AT CURRENT VDU BUFFER POSITION
; RETURN E = CHARACTER, B = COLOUR, C = ATTRIBUTES
;----------------------------------------------------------------------

FV_VDARDC:
	CALL	FV_GETCHAR	; GET THE CHARACTER AT CUR CUR POS
	LD	E,A		; PUT IN E
	LD	BC,0		; COLOR AND ATTR NOT SUPPORTED
	XOR	A		; SIGNAL SUCCESS
	RET
;
;======================================================================
; FPGA VGA DRIVER - PRIVATE DRIVER FUNCTIONS
;======================================================================
;
;
;----------------------------------------------------------------------
; PROBE FOR FPGA VGA HARDWARE
;----------------------------------------------------------------------
;
; ON RETURN, ZF SET INDICATES HARDWARE FOUND
;
FV_PROBE:
	XOR	A			; ASSUME H/W EXISTS
	RET
;
;----------------------------------------------------------------------
; CRTC DISPLAY CONTROLLER CHIP INITIALIZATION
;----------------------------------------------------------------------
;
FV_CRTINIT:
	LD	A,%11001111		; WHITE ON BLACK, CURSOR ON, ENABLE OUTPUT
	OUT	(FV_CTL),A		; WRITE TO CONTROL PORT
	XOR	A			; ZERO ACCUM
	RET				; DONE
;
;----------------------------------------------------------------------
; SET CURSOR POSITION TO ROW IN D AND COLUMN IN E
;----------------------------------------------------------------------
;
FV_XY:
	CALL	FV_HIDECUR		; HIDE THE CURSOR
	PUSH	DE			; SAVE NEW POSITION FOR NOW
	CALL	FV_XY2IDX		; CONVERT ROW/COL TO BUF IDX
	LD	(FV_POS),HL		; SAVE THE RESULT (DISPLAY POSITION)
	POP	DE			; RECOVER INCOMING ROW/COL
	LD	A,D			; GET ROW
	OUT	(FV_CROW),A		; SET ROW REGISTER
	LD	A,E			; GET COL
	INC	A			; 1..79,0 (WHY???)
	CP	80			; COL 80?
	JR	NZ, FV_XY1		; SKIP IF NOT
	XOR	A			; ELSE MAKE IT ZERO!
FV_XY1:
	OUT	(FV_CCOL),A		; SET COL REGISTER
	JP	FV_SHOWCUR		; SHOW THE CURSOR AND EXIT
;
;----------------------------------------------------------------------
; CONVERT XY COORDINATES IN DE INTO LINEAR INDEX IN HL
; D=ROW, E=COL
;----------------------------------------------------------------------
;
FV_XY2IDX:
	LD	A,E			; SAVE COLUMN NUMBER IN A
	LD	H,D			; SET H TO ROW NUMBER
	LD	E,FV_COLS		; SET E TO ROW LENGTH
	CALL	MULT8			; MULTIPLY TO GET ROW OFFSET, H * E = HL, E=0, B=0
	LD	E,A			; GET COLUMN BACK
	ADD	HL,DE			; ADD IT IN
	RET				; RETURN
;
;----------------------------------------------------------------------
; SHOW OR HIDE CURSOR
;----------------------------------------------------------------------
;
FV_SHOWCUR:
	LD	A,%11001111		; CONTROL PORT VALUE
	;;;LD	A,%11111111		; CONTROL PORT VALUE
	OUT	(FV_CTL),A		; SET REGISTER
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
FV_HIDECUR:
	LD	A,%11001111		; CONTROL PORT VALUE
	;;;LD	A,%11111111		; CONTROL PORT VALUE
	OUT	(FV_CTL),A		; SET REGISTER
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
;----------------------------------------------------------------------
; (DE)SELECT FRAME BUFFER
;----------------------------------------------------------------------
;
FV_BUFSEL:
	PUSH	AF
	LD	A,$01
	OUT	(FV_BUFCTL),A
	POP	AF
	RET
;
FV_BUFDESEL:
	PUSH	AF
	XOR	A
	OUT	(FV_BUFCTL),A
	POP	AF
	RET
;
;----------------------------------------------------------------------
; WRITE VALUE IN A TO CURRENT VDU BUFFER POSITION, ADVANCE CURSOR
;----------------------------------------------------------------------
;
FV_PUTCHAR:
	; WRITE CHAR AT CURRENT CURSOR POSITION.
	PUSH	AF			; SAVE INCOMING CHAR
	CALL	FV_HIDECUR		; HIDE CURSOR
	CALL	FV_BUFSEL		; SELECT FRAME BUFFER
	POP	AF
	LD	HL,(FV_POS)		; GET CUR BUF POSITION
	LD	DE,FV_FBUF		; START OF FRAME BUF
	ADD	HL,DE			; ADD IT IN
	LD	(HL),A			; PUT THE CHAR
;
	; SET NEW POSITION
	LD	HL,(FV_POS)		; GET POSITION
	INC	HL			; BUMP POSITION
	LD	(FV_POS),HL		; SAVE NEW POSITION
;
	; PUT CUROR IN PLACE
	LD	DE,FV_COLS		; COLS PER LINE
	CALL	DIV16			; BC=ROW, HL=COL
	LD	D,C
	LD	E,L
	CALL	FV_XY
	CALL	FV_BUFDESEL		; DESELECT FRAME BUFFER
	JP	FV_SHOWCUR		; SHOW IT AND RETURN
;
;----------------------------------------------------------------------
; GET CHAR VALUE TO A FROM CURRENT VDU BUFFER POSITION
;----------------------------------------------------------------------
;
FV_GETCHAR:
	XOR	A
	RET
;
;----------------------------------------------------------------------
; FILL AREA IN BUFFER WITH SPECIFIED CHARACTER AND CURRENT COLOR/ATTRIBUTE
; STARTING AT THE CURRENT FRAME BUFFER POSITION
;   A: FILL CHARACTER
;   DE: NUMBER OF CHARACTERS TO FILL
;----------------------------------------------------------------------
;
FV_FILL:
	PUSH	AF			; SAVE INCOMING FILL CHAR
	CALL	FV_HIDECUR		; HIDE CURSOR
	CALL	FV_BUFSEL		; SELECT BUFFER
	LD	HL,(FV_POS)		; CUR POS TO HL
	LD	BC,FV_FBUF		; ADR OF FRAME
	ADD	HL,BC			; ADD IT IN
	POP	AF
	LD	C,A			; FILL CHAR TO C
FV_FILL1:
	LD	A,D			; CHECK FILL
	OR	E			; ... COUNTER
	JR	Z,FV_FILL2		; DONE IF ZERO
	LD	(HL),C			; FILL ONE CHAR
	INC	HL			; BUMP BUF PTR
	DEC	DE			; DEC FILL COUNTER
	JR	FV_FILL1		; LOOP
;
FV_FILL2:
	CALL	FV_BUFDESEL		; DESELECT BUFFER
	JP	FV_SHOWCUR		; EXIT VIA SHOW CURSOR
;
;----------------------------------------------------------------------
; SCROLL ENTIRE SCREEN FORWARD BY ONE LINE (CURSOR POSITION UNCHANGED)
;----------------------------------------------------------------------
;
FV_SCROLL:
	CALL	FV_BUFSEL				; SELECT FRAME BUFFER
;
	; COPY "UP" ONE LINE
	LD	HL,FV_FBUF + FV_COLS 			; FROM SECOND LINE
	LD	DE,FV_FBUF				; TO FIRST LINE
	LD	BC,+(FV_ROWS - 1) * FV_COLS		; ALL BUT ONE LINE
	LDIR						; DO IT
;
	; FILL LAST LINE OF SCREEN
	LD	HL,FV_FBUF + ((FV_ROWS - 1) * FV_COLS)	; LAST LINE
	LD	A,' '					; FILL CHAR
	LD	(HL),A					; COPY 1 CHAR
	LD	DE,FV_FBUF + ((FV_ROWS - 1) * FV_COLS) + 1	; SECOND POS IN LAST LINE
	LD	BC,FV_COLS - 1				; COLS PER LINE - 1
	LDIR						; FILL IT
;
	CALL	FV_BUFDESEL				; DESELECT FRAME BUFFER
	RET						; DONE
;
;----------------------------------------------------------------------
; REVERSE SCROLL ENTIRE SCREEN BY ONE LINE (CURSOR POSITION UNCHANGED)
;----------------------------------------------------------------------
;
FV_RSCROLL:
	CALL	FV_BUFSEL				; SELECT FRAME BUFFER
;
	; COPY "DOWN" ONE LINE
	LD	HL,FV_FBUF + (FV_COLS * (FV_ROWS - 1)) - 1 	; FROM END OF SECOND TO LAST LINE
	LD	DE,FV_FBUF + (FV_COLS * FV_ROWS) - 1	; TO END OF LAST LINE
	LD	BC,+(FV_ROWS - 1) * FV_COLS		; ALL BUT ONE LINE
	LDDR						; DO IT IN REVERSE
;
	; FILL FIRST LINE OF SCREEN
	LD	HL,FV_FBUF				; FIRST LINE
	LD	A,' '					; FILL CHAR
	LD	(HL),A					; COPY 1 CHAR
	LD	DE,FV_FBUF + 1				; SECOND POS IN FIRST LINE
	LD	BC,FV_COLS - 1				; COLS PER LINE - 1
	LDIR						; FILL IT
;
	CALL	FV_BUFDESEL				; DESELECT FRAME BUFFER
	RET						; DONE
;
;----------------------------------------------------------------------
; BLOCK COPY BC BYTES FROM HL TO DE
;----------------------------------------------------------------------
;
FV_BLKCPY:

	CALL	FV_BUFSEL				; SELECT FRAME BUFFER
	PUSH	BC					; SAVE LENGTH
	LD	BC,FV_FBUF				; FRAME BUFFER ADR
	ADD	HL,BC					; ADD TO SOURCE
	EX	DE,HL					; EXCHANGE
	ADD	HL,BC					; ADD TO DEST
	EX	DE,HL					; EXCHANGE
	POP	BC					; RECOVER LENGTH
	LDIR						; LDIR DOES THE COPY
	CALL	FV_BUFDESEL				; DESELECT FRAME BUFFER
	RET						; DONE
;
;==================================================================================================
;   FPGA VGA DRIVER - DATA
;==================================================================================================
;
FV_POS		.DW 	0	; CURRENT DISPLAY POSITION
FV_ACTIVE	.DB	FALSE	; FLAG FOR DRIVER ACTIVE
;
;==================================================================================================
;   VGA DRIVER - INSTANCE DATA
;==================================================================================================
;
FV_IDAT:
	.DB	KBDMODE_FV	; FPGA VGA KEYBOARD CONTROLLER
	.DB	FV_KBDST
	.DB	FV_KBDDATA
