;------------------------------------------------------------------------------
; TEST PROTOTYPE IDE HARD DISK TEST PROGRAM
; BY ANDREW LYNCH
; USING IDE CIRCUIT FROM HANS SUMMERS
; 5 APR 2007
;------------------------------------------------------------------------------

; DATA CONSTANTS

; IDE REGISTER		IO PORT		; FUNCTION
IDELO		.EQU	$20		; DATA PORT (LOW BYTE)
IDEERR		.EQU	$21		; READ: ERROR REGISTER; WRITE: PRECOMP
IDESECTC	.EQU	$22		; SECTOR COUNT
IDESECTN	.EQU	$23		; SECTOR NUMBER
IDECYLLO	.EQU	$24		; CYLINDER LOW
IDECYLHI	.EQU	$25		; CYLINDER HIGH
IDEHEAD		.EQU	$26		; DRIVE/HEAD
IDESTTS		.EQU	$27		; READ: STATUS; WRITE: COMMAND
IDEHI		.EQU	$28		; DATA PORT (HIGH BYTE)
IDECTRL		.EQU	$2E		; READ: ALTERNATIVE STATUS; WRITE; DEVICE CONTROL
IDEADDR		.EQU	$2F		; DRIVE ADDRESS (READ ONLY)

CR		.EQU	$0D		; CARRIAGE RETURN CHARACTER
LF		.EQU	$0A		; LINE FEED CHARACTER
END		.EQU	'$'		; LINE TERMINATOR FOR CP/M STRINGS


; MAIN PROGRAM BEGINS HERE

	.ORG	$0100
;*      Input		: HL = start address block
;*			: BC = length of block
;			: A = value to fill with

	
	LD	HL,IDE_SECTOR_BUFFER	; INITIALIZE SECTOR BUFFER TO KNOWN VALUE $00
	LD	BC,$0200
	LD	A,$00
	CALL	FILL_MEM

	LD	DE,MSG_START
	LD	C,09H			; CP/M WRITE START STRING TO CONSOLE CALL
	CALL	0005H

	CALL	IDE_SOFT_RESET		; RESET IDE DRIVE TO BEGIN TEST

	CALL	IDE_GET_ID		; GET IDE HD IDENTIFICATION
					; NOTE: HD IDE DATA STORED AT MEMORY LOCATION
					; IDE_SECTOR_BUFFER AND REQUIRES MONITOR TO
					; INSPECT CONTENTS
	
	JR	C,EXIT			; NO ERROR IF IDE_GET_ID RETURNS CARRY SET
					; ELSE, PRINT ERROR MESSAGE

	LD	DE,MSG_ERROR
	LD	C,09H			; CP/M WRITE ERROR STRING TO CONSOLE CALL
	CALL	0005H

EXIT:
	LD	DE,MSG_END
	LD	C,09H			; CP/M WRITE END STRING TO CONSOLE CALL
	CALL	0005H

	LD	C,00H			; CP/M SYSTEM RESET CALL
	CALL	0005H			; RETURN TO PROMPT

; MAIN PROGRAM ENDS HERE

;------------------------------------------------------------------------------
;- IDE ROUTINES FOR Z80 PROJECT BY PHIL RUSTON 2005 ---------------------------
; MODIFIED BY ANDREW LYNCH FOR TEST PROTOTYPE
;------------------------------------------------------------------------------
		
IDE_READ_SECTOR:

	CALL	IDE_WAIT_BUSY_READY	;MAKE SURE DRIVE IS READY TO PROCEED
	RET	NC
	CALL	IDE_SETUP_LBA		;TELL DRIVE WHAT SECTOR IS REQUIRED
;	LD	A,%10010111		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=7
;	OUT	(IDE_CONTROL_PORT),A	
	LD	A,$20			
;	OUT	(IDE_PORT_LO),A		;$20 = IDE 'READ SECTOR' COMMAND 
	OUT	(IDESTTS),A		;$20 = IDE 'READ SECTOR' COMMAND 

IDE_SREX:
	CALL	IDE_WAIT_BUSY_READY	;MAKE SURE DRIVE IS READY TO PROCEED
	RET	NC
	CALL	IDE_TEST_ERROR		;ENSURE NO ERROR WAS REPORTED
	RET	NC
	CALL	IDE_WAIT_BUFFER		;WAIT FOR FULL BUFFER SIGNAL FROM DRIVE
	RET	NC
	CALL	IDE_READ_BUFFER		;GRAB THE 256 WORDS FROM THE BUFFER
	SCF				;CARRY = 1 ON RETURN = OPERATION OK
	RET
		
;-----------------------------------------------------------------------------


IDE_WRITE_SECTOR:

	CALL	IDE_WAIT_BUSY_READY	;MAKE SURE DRIVE IS READY TO PROCEED
	RET	NC
	CALL	IDE_SETUP_LBA		;TELL DRIVE WHAT SECTOR IS REQUIRED
;	LD	A,%10010111		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=7
;	OUT	(IDE_CONTROL_PORT),A	
	LD	A,$30			
;	OUT	(IDE_PORT_LO),A		;$30 = IDE 'WRITE SECTOR' COMMAND 
	OUT	(IDESTTS),A		;$30 = IDE 'WRITE SECTOR' COMMAND 
	CALL	IDE_WAIT_BUSY_READY
	RET	NC
	CALL	IDE_TEST_ERROR		;ENSURE NO ERROR WAS REPORTED
	RET	NC
	CALL	IDE_WAIT_BUFFER		;WAIT FOR BUFFER READY SIGNAL FROM DRIVE
	RET	NC
	CALL	IDE_WRITE_BUFFER	;SEND 256 WORDS TO DRIVE'S BUFFER
	CALL	IDE_WAIT_BUSY_READY	;MAKE SURE DRIVE IS READY TO PROCEED
	RET	NC
	CALL	IDE_TEST_ERROR		;ENSURE NO ERROR WAS REPORTED
	RET	NC
	SCF				;CARRY = 1 ON RETURN = OPERATION OK
	RET

;-----------------------------------------------------------------------------


IDE_GET_ID:
	
	CALL	IDE_WAIT_BUSY_READY
	RET	NC
;	LD	A,%10010110		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=6
;	OUT	(IDE_CONTROL_PORT),A	
	LD	A,%10100000
;	OUT	(IDE_PORT_LO),A		;SELECT MASTER DEVICE
	OUT	(IDEHEAD),A		;SELECT MASTER DEVICE
	CALL	IDE_WAIT_BUSY_READY
	RET	NC
;	LD	A,%10010111		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=7
;	OUT	(IDE_CONTROL_PORT),A	
	LD	A,$EC			
;	OUT	(IDE_PORT_LO),A		;$EC = IDE 'ID DRIVE' COMMAND 
	OUT	(IDESTTS),A		;$EC = IDE 'ID DRIVE' COMMAND 
	JR	IDE_SREX

;-----------------------------------------------------------------------------

IDE_SOFT_RESET

;	LD	A,%10001110		;KB IRQMASK=1,/CS1=0,/CS0=1,A0:A2=6
;	OUT	(IDE_CONTROL_PORT),A	;SELECT 2ND STATUS/RESET/IRQ REGISTER
	LD	A,%00000110		;NO INTERRUPTS, RESET DRIVE = 1
;	OUT	(IDE_PORT_LO),A
	OUT	(IDECTRL),A
	LD	A,%00000010		;NO INTERRUPTS, RESET DRIVE = 0
;	OUT	(IDE_PORT_LO),A
	OUT	(IDECTRL),A
	CALL	IDE_WAIT_BUSY_READY
	RET


;--------------------------------------------------------------------------------
; IDE INTERNAL SUBROUTINES 
;--------------------------------------------------------------------------------


IDE_WAIT_BUSY_READY:
	
;	LD	A,%10010111		;KB IRQMASK=1, /CS1=1,/CS0=0, A0:A2=7
;	OUT	(IDE_CONTROL_PORT),A	;SELECT STATUS REGISTER
	LD	DE,0

IDE_WBSY:
	LD	B,5

IDE_DLP:
	DJNZ	IDE_DLP
	INC	DE
	LD	A,D
	OR	E
	JR	Z,IDE_TO
;	IN	A,(IDE_PORT_LO)		;READ ERROR REG
	IN	A,(IDESTTS)		;READ ERROR REG
	AND	%11000000		;MASK OFF BUSY AND RDY BITS
	XOR	%01000000		;WE WANT BUSY(7) TO BE 0 AND RDY(6) TO BE 1
	JR	NZ,IDE_WBSY
	SCF				;CARRY 1 = OK
	RET

IDE_TO:
	XOR	A			;CARRY 0 = TIMED OUT
	RET
	
;----------------------------------------------------------------------------

IDE_TEST_ERROR:
	
	SCF
;	LD	A,%10010111		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=7
;	OUT	(IDE_CONTROL_PORT),A	
;	IN	A,(IDE_PORT_LO)
	IN	A,(IDESTTS)
	BIT	0,A			;TEST ERROR BIT
	RET	Z
	BIT	5,A
	JR	NZ,IDE_ERR		;TEST WRITE ERROR BIT
;	LD	A,%10010001		;KB IRQMASK=1, /CS1=1,/CS0=0, A0:A2=1
;	OUT	(IDE_CONTROL_PORT),A	;RETURN ERROR CODE IN A
;	IN	A,(IDE_PORT_LO)		;READ ERROR FLAGS
	IN	A,(IDEERR)		;READ ERROR FLAGS

IDE_ERR:
	OR	A			;CARRY 0 = ERROR
	RET				;IF A = 0, IDE BUSY TIMED OUT

;-----------------------------------------------------------------------------
	
IDE_WAIT_BUFFER:
	
;	LD	A,%10010111		;KB IRQMASK=1, /CS1=1,/CS0=0, A0:A2=7
;	OUT	(IDE_CONTROL_PORT),A	;SET TO READ STATUS REGISTER	
	LD	DE,0

IDE_WDRQ:
	LD	B,5

IDE_BLP:
	DJNZ	IDE_BLP
	INC	DE
	LD	A,D
	OR	E
	JR	Z,IDE_TO2
;	IN	A,(IDE_PORT_LO)		;WAIT FOR DRIVE'S 512 BYTE READ BUFFER 
	IN	A,(IDESTTS)		;WAIT FOR DRIVE'S 512 BYTE READ BUFFER 
	BIT	3,A			;TO FILL (OR READY TO FILL)
	JR	Z,IDE_WDRQ
	SCF				;CARRY 1 = OK
	RET

IDE_TO2:
	XOR	A			;CARRY 0 = TIMED OUT
	RET

;------------------------------------------------------------------------------

IDE_READ_BUFFER:

	PUSH	IX
	LD	IX,IDE_SECTOR_BUFFER
;	LD	A,%10010000		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=0
;	OUT	(IDE_CONTROL_PORT),A
	LD	B,0			;256 WORDS (512 BYTES PER SECTOR)

IDEBUFRD:
;	IN	A,(IDE_PORT_LO)		;LOW BYTE OF WORD FIRST	
	IN	A,(IDELO)		;LOW BYTE OF WORD FIRST	
	LD	(IX+1),A
;	IN	A,(IDE_PORT_HI)		;THEN HIGH BYTE OF WORD
	IN	A,(IDEHI)		;THEN HIGH BYTE OF WORD
	LD	(IX),A
	INC	IX
	INC	IX
	DJNZ	IDEBUFRD
	POP	IX
	RET
	
;-----------------------------------------------------------------------------

IDE_WRITE_BUFFER:

	PUSH	IX
	LD	IX,IDE_SECTOR_BUFFER
;	LD	A,%10010000		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=0
;	OUT	(IDE_CONTROL_PORT),A
	LD	B,0			;256 WORDS (512 BYTES PER SECTOR)

IDEBUFWT:
	LD	A,(IX)			
;	OUT	(IDE_PORT_HI),A		;SET UP HIGH LATCHED BYTE BEFORE
	OUT	(IDEHI),A		;SET UP HIGH LATCHED BYTE BEFORE
	LD	A,(IX+1)
;	OUT	(IDE_PORT_LO),A		;WRITING WORD WITH WRITE TO LOW BYTE
	OUT	(IDELO),A		;WRITING WORD WITH WRITE TO LOW BYTE
	INC	IX
	INC	IX
	DJNZ	IDEBUFWT
	POP	IX
	RET
	
;-----------------------------------------------------------------------------

IDE_SETUP_LBA:
	
;	LD	A,%10010010		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=2
;	OUT	(IDE_CONTROL_PORT),A	
	LD	A,1			
;	OUT	(IDE_PORT_LO),A		;SET SECTOR COUNT = 1	
	OUT	(IDESECTC),A		;SET SECTOR COUNT = 1	

;	LD	A,%10010011		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=3
;	OUT	(IDE_CONTROL_PORT),A	
	LD	A,(IDE_LBA0)
;	OUT	(IDE_PORT_LO),A		;SET LBA 0:7
	OUT	(IDESECTN),A		;SET LBA 0:7

;	LD	A,%10010100		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=4
;	OUT	(IDE_CONTROL_PORT),A	
	LD	A,(IDE_LBA1)
;	OUT	(IDE_PORT_LO),A		;SET LBA 8:15
	OUT	(IDECYLLO),A		;SET LBA 8:15

;	LD	A,%10010101		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=5
;	OUT	(IDE_CONTROL_PORT),A	
	LD	A,(IDE_LBA2)
;	OUT	(IDE_PORT_LO),A		;SET LBA 16:23
	OUT	(IDECYLHI),A		;SET LBA 16:23

;	LD	A,%10010110		;KB IRQMASK=1,/CS1=1,/CS0=0,A0:A2=6
;	OUT	(IDE_CONTROL_PORT),A	
	LD	A,(IDE_LBA3)
	AND	%00001111		;LOWEST 4 BITS USED ONLY
	OR	%11100000		;TO ENABLE LBA MODE
;	OUT	(IDE_PORT_LO),A		;SET LBA 24:27 + BITS 5:7=111
	OUT	(IDEHEAD),A		;SET LBA 24:27 + BITS 5:7=111
	RET
	
;******************************************************************
;*	FILL_MEM
;*	Function	: fill memory with a value
;*      Input		: HL = start address block
;*			: BC = length of block
;			: A = value to fill with
;*	Uses		: DE, BC
;*	Output		:
;*	calls		: 
;*	tested		: 13 Feb 2007
;******************************************************************


FILL_MEM:

;; This code snippet will show one method to fill a block
;; of memory with a single data byte using Z80 assembly
;; language.

;;--------------------------------------------------

					;; HL = start address of block
;	ld	hl,&4000

					;; DE = HL + 1
	ld	e,l
	ld	d,h
	inc	de

					;; initialise first byte of block
					;; with data byte (&00)
					;; with data byte in A
;	ld	(hl),&00
	ld	(hl),A
	
					;; BC = length of block in bytes
					;; HL+BC-1 = end address of block

;	ld	bc,&4000	

					;; fill memory
	ldir

	RET				;; return to caller

;;--------------------------------------------------



;; For each iteration of the LDIR command:
;;
;; 1. This command will copy the byte from the memory 
;; address pointed to by HL to the memory address pointed to by DE.
;; i.e. (DE) = (HL).
;; 2. Then HL and DE will be incremented. BC will be decremented.
;;
;;
;; For the first byte:
;; 
;; HL = start
;; DE = start+1
;; BC = length
;; (HL)=0
;; 
;; For the second byte:
;; 
;; HL = start + 1 (initialised to 0 by the previous iteration)
;; DE = start + 2
;; BC = length - 1
;;
;; For the third byte:
;;
;; HL = start + 2 (initialised to 0 by the previous iteration)
;; DE = start + 3
;; BC = length - 2
;;
;; etc....






	
;-----------------------------------------------------------------------------

; TEXT CONSTANTS

MSG_START:
	.TEXT	"START IDE TEST PROGRAM"
	.DB	LF, CR			; LINE FEED AND CARRIAGE RETURN
	.DB	END			; LINE TERMINATOR

MSG_ERROR:
	.TEXT	"ERROR"
	.DB	LF, CR			; LINE FEED AND CARRIAGE RETURN
	.DB	END			; LINE TERMINATOR

MSG_D1:
	.TEXT	"PASSED IDE_WAIT_BUSY_READY"
	.DB	LF, CR			; LINE FEED AND CARRIAGE RETURN
	.DB	END			; LINE TERMINATOR

MSG_END:
	.TEXT	"END IDE TEST PROGRAM"
	.DB	LF, CR			; LINE FEED AND CARRIAGE RETURN
	.DB	END			; LINE TERMINATOR

; DATA MEMORY RESERVED RANGES FOR VARIABLES AND SECTOR BUFFER

IDE_LBA0:		.DS	$01	;SET LBA 0:7
IDE_LBA1:		.DS	$01	;SET LBA 8:15
IDE_LBA2:		.DS	$01	;SET LBA 16:23
IDE_LBA3:		.DS	$01	;LOWEST 4 BITS USED ONLY 
					;TO ENABLE LBA MODE 
					;SET LBA 24:27 + BITS 5:7=111
IDE_SECTOR_BUFFER:	.DS	$0200

	.END
