;------------------------------------------------------------------------------
; TEST PROTOTYPE IDE HARD DISK PARTITION #2 FORMAT PROGRAM
; BY ANDREW LYNCH
; USING IDE CIRCUIT FROM HANS SUMMERS
; 16 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

sectors		.EQU	$20		; number of blocks reserved for directory


; MAIN PROGRAM BEGINS HERE

	.ORG	$0100
;*      Input		: HL = start address block
;*			: BC = length of block
;			: A = value to fill with

	
	LXI	H,IDE_SECTOR_BUFFER	; INITIALIZE SECTOR BUFFER TO KNOWN VALUE $E5
	LXI	B,$0200			; IDE HD sector size is 512 bytes
	MVI	A,$E5			; fill directory blocks with empty directory entries
	CALL	FILL_MEM		

	LXI	D,MSG_START
	MVI	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


					; INITIALIZE STARTING LBA SECTOR ON IDE HD
	LXI	H,$FC00			; INITIALIZE LBA OFFSET SECTOR LO WORD 
	SHLD	LBA_OFFSET_LO

	LXI	H,$0000			; INITIALIZE LBA OFFSET SECTOR HI WORD
	SHLD	LBA_OFFSET_HI


; Generic FOR-NEXT loop algorithm
;
	MVI	A,$40			; set A=0 index counter of FOR loop
					; first sector of directory is on track 1 since
					; track 0 is reserved for system per DPB definition
					; $40 first sector of track 1
FOR_LOOP:
	PUSH	PSW			; save accumulator as it is the index counter in FOR loop
;					; outer loop is 0 -> # of directory blocks

	MOV	L,A
	MVI	H,$00			; store result in HL
	ADI	$30

	LDA	LBA_OFFSET_LO		; 16 BIT ADD OF LBA_OFFSET_LO WITH HL
	ADD	L
	STA	LBA_TARGET_LO
	LDA	LBA_OFFSET_LO+1
	ADC	H
	STA	LBA_TARGET_LO+1		; STORE OVERFLOW BIT IN CARRY

	LXI	H,$0000
	LDA	LBA_OFFSET_HI		; 16 BIT ADD WITH CARRY OF LBA_OFFSET_HI WITH $0000
	ADC	L
	STA	LBA_TARGET_HI
	LDA	LBA_OFFSET_HI+1
	ADC	H
	STA	LBA_TARGET_HI+1

	LDA	LBA_TARGET_LO		; LOAD LBA REGISTER 0 WITH SECTOR ADDRESS TO READ
	STA	IDE_LBA0 
	LDA	LBA_TARGET_LO+1		; LOAD LBA REGISTER 1 WITH SECTOR ADDRESS TO READ
	STA	IDE_LBA1
	LDA	LBA_TARGET_HI		; LOAD LBA REGISTER 2 WITH SECTOR ADDRESS TO READ
	STA	IDE_LBA2
	LDA	LBA_TARGET_HI+1		; LOAD LBA REGISTER 3 WITH SECTOR ADDRESS TO READ
	ANI	%00001111		; ONLY LOWER FOUR BITS ARE VALID
	ADI	%11100000		; ENABLE LBA BITS 5:7=111 IN IDE_LBA3
	STA	IDE_LBA3

	CALL	IDE_WRITE_SECTOR	; WRITE THE IDE HARD DISK SECTOR WITH EMPTY DIRECTORY ENTRIES

; NEED TO ADD ERROR CHECKING HERE, CARRY FLAG IS SET IF IDE_WRITE_SECTOR SUCCESSFUL!

	JC	WRITE_OK		; NO ERROR IF IDE_GET_ID RETURNS CARRY SET
	LDA	$FF			; ERROR_CODE DEFAULTS TO FALSE ($00), 
	STA	ERROR_CODE		; ANY ERROR WILL MAKE IT TRUE ($FF)

WRITE_OK:
	POP	PSW			; recover accumulator as it is the index counter in FOR loop
	INR	A			; increment A in FOR loop (A=A+1)
	CPI	$40+sectors		; is A < number of sectors per block ?
	JNZ	FOR_LOOP		; No, do FOR loop again

	LDA	ERROR_CODE		; WAS AN ERROR DETECTED DURING FORMAT?
	ORA	A			; SET FLAGS
	JZ	EXIT			; IF NONE, JUMP TO EXIT 
					; ELSE, PRINT ERROR MESSAGE

	LXI	D,MSG_ERROR
	MVI	C,09H			; CP/M WRITE ERROR STRING TO CONSOLE CALL
	CALL	0005H

EXIT:
	LXI	D,MSG_END
	MVI	C,09H			; CP/M WRITE END STRING TO CONSOLE CALL
	CALL	0005H

	MVI	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
	RNC
	CALL	IDE_SETUP_LBA		;TELL DRIVE WHAT SECTOR IS REQUIRED
	MVI	A,$20			
	OUT	IDESTTS			;$20 = IDE 'READ SECTOR' COMMAND 

IDE_SREX:
	CALL	IDE_WAIT_BUSY_READY	;MAKE SURE DRIVE IS READY TO PROCEED
	RNC
	CALL	IDE_TEST_ERROR		;ENSURE NO ERROR WAS REPORTED
	RNC
	CALL	IDE_WAIT_BUFFER		;WAIT FOR FULL BUFFER SIGNAL FROM DRIVE
	RNC

	CALL	IDE_READ_BUFFER		;GRAB THE 256 WORDS FROM THE BUFFER
	STC				;CARRY = 1 ON RETURN = OPERATION OK
	RET
		
;-----------------------------------------------------------------------------

IDE_WRITE_SECTOR:

	CALL	IDE_WAIT_BUSY_READY	;MAKE SURE DRIVE IS READY TO PROCEED
	RNC
	CALL	IDE_SETUP_LBA		;TELL DRIVE WHAT SECTOR IS REQUIRED
	MVI	A,$30			
	OUT	IDESTTS			;$30 = IDE 'WRITE SECTOR' COMMAND 
	CALL	IDE_WAIT_BUSY_READY
	RNC
	CALL	IDE_TEST_ERROR		;ENSURE NO ERROR WAS REPORTED
	RNC
	CALL	IDE_WAIT_BUFFER		;WAIT FOR BUFFER READY SIGNAL FROM DRIVE
	RNC
	CALL	IDE_WRITE_BUFFER	;SEND 256 WORDS TO DRIVE'S BUFFER
	CALL	IDE_WAIT_BUSY_READY	;MAKE SURE DRIVE IS READY TO PROCEED
	RNC
	CALL	IDE_TEST_ERROR		;ENSURE NO ERROR WAS REPORTED
	RNC
	STC				;CARRY = 1 ON RETURN = OPERATION OK
	RET

;-----------------------------------------------------------------------------

IDE_GET_ID:
	
	CALL	IDE_WAIT_BUSY_READY
	RNC
	MVI	A,%10100000
	OUT	IDEHEAD			;SELECT MASTER DEVICE
	CALL	IDE_WAIT_BUSY_READY
	RNC
	MVI	A,$EC			
	OUT	IDESTTS			;$EC = IDE 'ID DRIVE' COMMAND 
	CALL	IDE_WAIT_BUSY_READY	;MAKE SURE DRIVE IS READY TO PROCEED
	RNC
	CALL	IDE_TEST_ERROR		;ENSURE NO ERROR WAS REPORTED
	RNC
	CALL	IDE_WAIT_BUFFER		;WAIT FOR FULL BUFFER SIGNAL FROM DRIVE
	RNC
	CALL	IDE_READ_BUFFER_BE	;GRAB THE 256 WORDS FROM THE BUFFER
	STC				;CARRY = 1 ON RETURN = OPERATION OK
	RET

;-----------------------------------------------------------------------------

IDE_SOFT_RESET:

	MVI	A,%00000110		;NO INTERRUPTS, RESET DRIVE = 1
	OUT	IDECTRL
	MVI	A,%00000010		;NO INTERRUPTS, RESET DRIVE = 0
	OUT	IDECTRL
	CALL	IDE_WAIT_BUSY_READY
	RET

;--------------------------------------------------------------------------------
; IDE INTERNAL SUBROUTINES 
;--------------------------------------------------------------------------------

IDE_WAIT_BUSY_READY:
	
	LXI	D,0

IDE_WBSY:

	MVI	B,5

IDE_DLP:

	DCR	B
	JNZ	IDE_DLP

	INX	D
	MOV	A,D
	ORA	E

	JZ	IDE_TO

	IN	IDESTTS			;READ ERROR REG
	ANI	%11000000		;MASK OFF BUSY AND RDY BITS
	XRI	%01000000		;WE WANT BUSY(7) TO BE 0 AND RDY(6) TO BE 1

	JNZ	IDE_WBSY

	STC				;CARRY 1 = OK
	RET

IDE_TO:
	XRA	A			;CARRY 0 = TIMED OUT
	RET
	
;----------------------------------------------------------------------------

IDE_TEST_ERROR:
	
	STC
	IN	IDESTTS
	MOV	B,A			;new
	ANI	%00000001		;TEST ERROR BIT
	STC				;new
	RZ

	MOV	A,B			;new
	ANI	%00100000
	STC
	JNZ	IDE_ERR			;TEST WRITE ERROR BIT
	
	IN	IDEERR			;READ ERROR FLAGS

IDE_ERR:
	ORA	A			;CARRY 0 = ERROR
	RET				;IF A = 0, IDE BUSY TIMED OUT

;-----------------------------------------------------------------------------
	
IDE_WAIT_BUFFER:
	
	LXI	D,0

IDE_WDRQ:
	MVI	B,5

IDE_BLP:
	DCR	B
	JNZ	IDE_BLP

	INX	D
	MOV	A,D
	ORA	E
	JZ	IDE_TO2

	IN	IDESTTS			;WAIT FOR DRIVE'S 512 BYTE READ BUFFER 
	ANI	%00001000		;TO FILL (OR READY TO FILL)
	JZ	IDE_WDRQ

	STC				;CARRY 1 = OK
	RET

IDE_TO2:
	XRA	A			;CARRY 0 = TIMED OUT
	RET

;------------------------------------------------------------------------------

IDE_READ_BUFFER_BE:

	PUSH	H
	LXI	H,IDE_SECTOR_BUFFER
	MVI	B,0			;256 WORDS (512 BYTES PER SECTOR)

IDEBUFRD_BE:

	IN	IDELO			;LOW BYTE OF WORD FIRST	
	INX	H
	MOV	M,A
	DCX	H
	IN	IDEHI			;THEN HIGH BYTE OF WORD
	MOV	M,A
	INX	H
	INX	H
	DCR	B
	JNZ	IDEBUFRD_BE

	POP	H
	RET
	
;------------------------------------------------------------------------------

IDE_READ_BUFFER:

	PUSH	H
	LXI	H,IDE_SECTOR_BUFFER
	MVI	B,0			;256 WORDS (512 BYTES PER SECTOR)

IDEBUFRD:
	IN	IDELO			;LOW BYTE OF WORD FIRST	
	MOV	M,A
	IN	IDEHI			;THEN HIGH BYTE OF WORD
	INX	H
	MOV	M,A
	INX	H
	DCR	B
	JNZ	IDEBUFRD

	POP	H
	RET

;-----------------------------------------------------------------------------

IDE_WRITE_BUFFER:

	PUSH	H
	LXI	H,IDE_SECTOR_BUFFER
	MVI	B,0			;256 WORDS (512 BYTES PER SECTOR)

IDEBUFWT:

	INX	H
	MOV	A,M
	DCX	H
	OUT	IDEHI			;SET UP HIGH LATCHED BYTE BEFORE
	MOV	A,M
	OUT	IDELO			;WRITING WORD WITH WRITE TO LOW BYTE
	INX	H
	INX	H
	DCR	B
	JNZ	IDEBUFWT

	POP	H
	RET
	
;-----------------------------------------------------------------------------

IDE_SETUP_LBA:
	
	MVI	A,1			
	OUT	IDESECTC		;SET SECTOR COUNT = 1	

	LDA	IDE_LBA0
	OUT	IDESECTN		;SET LBA 0:7

	LDA	IDE_LBA1
	OUT	IDECYLLO		;SET LBA 8:15

	LDA	IDE_LBA2
	OUT	IDECYLHI		;SET LBA 16:23

	LDA	IDE_LBA3
	ANI	%00001111		;LOWEST 4 BITS USED ONLY
	ORI	%11100000		;TO ENABLE LBA MODE
	OUT	IDEHEAD			;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
					;; DE = HL + 1
	MOV	E,L
	MOV	D,H

	INX	D
					;; initialise first byte of block
					;; with data byte (&00)
					;; with data byte in A
	MOV	M,A
	
					;; BC = length of block in bytes
					;; HL+BC-1 = end address of block

					;; fill memory
LDIR:	MOV A,M
	STAX D
	INX D
	INX H
	DCX B
	MOV A,B
	ORA C
	JNZ 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

ERROR_CODE:		.DB	$00	; ASSUME SUCCESS UNLESS TOLD OTHERWISE
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
LBA_OFFSET_LO		.DW		; IDE HD PARTITION STARTING SECTOR (LOW 16 BITS)
LBA_OFFSET_HI		.DW		; IDE HD PARTITION STARTING SECTOR (HI 16 BITS, 12 USED)
LBA_TARGET_LO		.DW		; IDE HD PARTITION TARGET SECTOR (LOW 16 BITS)
LBA_TARGET_HI		.DW		; IDE HD PARTITION TARGET SECTOR (HI 16 BITS, 12 USED)
IDE_SECTOR_BUFFER:	.DS	$0200

	.END
