; N8VEM	 PPI IDE test program for checkout of IDE drive connected to the 8255 PPI
;
; Written by Max Scane	July 2009
; Based on work by Paul Stoffregen  (www.pjrc.com)
;
; Note: due to a known anomaly in the 8255, some signals ( all active low signals) on the IDE bus require an inverter (74LS04 or 74LS14)
; between the 8255 and the IDE drive.
; This is due to the 8255 returning all signals to 0 (low) when a mode change is performed (for read and write to IDE data bus).
;
; This test program will allow you to check out an attached IDE drive using the basic commands:
;
; u - Spin up the drive
; d - Spin down the drive
; s - Read and print out drive status
; i - Execute drive ID command and print result correctly
; r - Read the current LBA into the sector buffer and print status
; w - Write the sector buffer to the current LBA and print status
; l - Change the current LBA
; h - Dump the current sector buffer in hexdump format
; f - format drive for CP/M use (fill with 0xE5)
; e - Display drive error information
; x - Return to CP/M
; n - read and hexdump next LBA
; ? - Display command menu help
; p - set PPI port
;
;
;
;
; - Updated December 2014 MS - changed IO routines to support different PPI ports.
; - Updated July 2021 Andrew Lynch - Minor cosmetic updates

;********************* HARDWARE IO ADR ************************************

DEFBASE:	.EQU	60H		; PPI base I/O address default

;
; Offsets to the various PPI registers

IDELSB:		.EQU	0		; LSB
IDEMSB:		.EQU	1		; MSB
IDECTL:		.EQU	2		; Control Signals
PIOCONT:	.EQU	3		; CONTROL BYTE PIO 82C55

; PPI control bytes for read and write to IDE drive

rd_ide_8255:	.EQU	10010010b	; ide_8255_ctl out, ide_8255_lsb/msb input
wr_ide_8255:	.EQU	10000000b	; all three ports output

;IDE control lines for use with ide_8255_ctl.  Change these 8
;constants to reflect where each signal of the 8255 each of the
;IDE control signals is connected.  All the control signals must
;be on the same port, but these 8 lines let you connect them to
;whichever pins on that port.

ide_a0_line:	.EQU	01H		; direct from 8255 to IDE interface
ide_a1_line:	.EQU	02H		; direct from 8255 to IDE interface
ide_a2_line:	.EQU	04H		; direct from 8255 to IDE interface
ide_cs0_line:	.EQU	08H		; inverter between 8255 and IDE interface
ide_cs1_line:	.EQU	10H		; inverter between 8255 and IDE interface
ide_wr_line:	.EQU	20H		; inverter between 8255 and IDE interface
ide_rd_line:	.EQU	40H		; inverter between 8255 and IDE interface
ide_rst_line:	.EQU	80H		; inverter between 8255 and IDE interface


;------------------------------------------------------------------
; More symbolic constants... these should not be changed, unless of
; course the IDE drive interface changes, perhaps when drives get
; to 128G and the PC industry will do yet another kludge.

;some symbolic constants for the ide registers, which makes the
;code more readable than always specifying the address pins

ide_data:	.EQU	ide_cs0_line
ide_err:	.EQU	ide_cs0_line + ide_a0_line
ide_sec_cnt:	.EQU	ide_cs0_line + ide_a1_line
ide_sector:	.EQU	ide_cs0_line + ide_a1_line + ide_a0_line
ide_cyl_lsb:	.EQU	ide_cs0_line + ide_a2_line
ide_cyl_msb:	.EQU	ide_cs0_line + ide_a2_line + ide_a0_line
ide_head:	.EQU	ide_cs0_line + ide_a2_line + ide_a1_line
ide_command:	.EQU	ide_cs0_line + ide_a2_line + ide_a1_line + ide_a0_line
ide_status:	.EQU	ide_cs0_line + ide_a2_line + ide_a1_line + ide_a0_line
ide_control:	.EQU	ide_cs1_line + ide_a2_line + ide_a1_line
ide_astatus:	.EQU	ide_cs1_line + ide_a2_line + ide_a1_line + ide_a0_line

;IDE Command Constants.	 These should never change.
ide_cmd_recal:		.EQU	10H
ide_cmd_read:		.EQU	20H
ide_cmd_write:		.EQU	30H
ide_cmd_init:		.EQU	91H
ide_cmd_id:		.EQU	0ECH
ide_cmd_spindown:	.EQU	0E0H
ide_cmd_spinup:		.EQU	0E1H

CR:		.EQU	0Dh
LF:		.EQU	0Ah
BELL:		.EQU	07H


	.org	100H

start:

; save stack pointer so that we can return to calling program

	ld	(savsp),sp
	ld	sp,stack

	call	set_ppi_rd		; setup PPI chip to known state

	ld	hl,lba1			; zero LBA variables
	call	clrlba

	ld	hl,lba2
	call	clrlba

	ld	hl,lba3
	call	clrlba


	call	print
	.db	"PPI IDE test program v0.6b",CR,LF,0

	call	prport
	call	prstatus
	call	prlba

	call	ide_init
	call	prstatus

	call	crlf

menu:
	call	print			; display prompt
	.db	"Enter command (u,d,s,i,r,w,l,h,f,e,n,p,?,x) > ",0

	call	cin			; get command from console in reg A
	push	af
	call	crlf
	pop	af

mnu1:
	cp	'd'			; spin down command
	jr	nz,mnu2
	call	spindown
	jr	menu
mnu2:
	cp	'u'			; spinup command
	jr	nz,mnu3
	call	spinup
	jr	menu

mnu3:
	cp	's'			; print IDE status reg contents
	jr	nz,mnu4
	call	prstatus
	jr	menu

mnu4:
	cp	'i'
	jr	nz,mnu5			; drive ID command
	call	drive_id
	jr	menu

mnu5:
	cp	'r'
	jr	nz,mnu6			; read command
	call	prlba			; print out the current LBA
	call	read_sector		; read current LBA
	call	prstatus
	jr	menu

mnu6:
	cp	'w'
	jr	nz,mnu7			; write command
	call	prlba			; print out the current LBA
	call	write_sector		; write current LBA
	call	prstatus
	jr	menu

mnu7:
	cp	'l'
	jr	nz,mnu8			; LBA command
	call	prlba			; print out the current LBA
mnu7a:
	call	print
	.db	"Enter new LBA: ",0

	ld	de,lba1			; get LBA in lba1
	call	getlba
	jp	nc,menu			; valid, finished

	call	print
	.db	"Invalid LBA",CR,LF,0

	jr	mnu7a			; try again


mnu8:
	cp	'h'
	jr	nz,mnu9			; hexdump command
	call	hexdump			; hexdump the current sector buffer
	jp	menu

mnu9:
	cp	'f'
	jr	nz,mnua			; drive format
	call	format
	jp	menu

mnua:
	cp	'e'
	jr	nz,mnub			; get error register
	call	get_err
	push	af
	call	print
	.db	"Error register is: ",0

	pop	af
	call	prhex
	call	crlf


	ld	a,ide_head
	call	ide_read
	ld	a,c
	call	prhex

	ld	a,ide_cyl_msb
	call	ide_read
	ld	a,c
	call	prhex

	ld	a,ide_cyl_lsb
	call	ide_read
	ld	a,c
	call	prhex


	ld	a,ide_sector
	call	ide_read
	ld	a,c
	call	prhex

	call	crlf

	ld	a,ide_sec_cnt
	call	ide_read
	ld	a,c
	call	prhex
	call	crlf

	jp	menu


mnub:
	cp	'n'
	jr	nz,mnuc
	ld	hl,lba1
	call	inclba
	call	prlba
	call	read_sector
	call	hexdump
	jp	menu

mnuc:
	cp	'p'
	jr	nz,mnux
	call	prport
mnuca:
	call	print
	.db	"Enter new PPI base port: ",0

	ld	a,(ppibase)
	call	gethexbyte
	jr	c,mnucb
	ld	(ppibase),a		; save it
	call	prport
	jp	menu

mnucb:
	call	print
	.db	"Invalid PPI base port value",CR,LF,0
	jr	mnuca

mnux:
	cp	'x'			; exit command
	jp	nz,mnuhlp
	ld	sp,(savsp)
	ret


mnuhlp:
	call	print
	.db	"Commands available:",CR,LF,LF
	.db	"u - Spin Up drive",CR,LF
	.db	"d - Spin Down drive",CR,LF
	.db	"s - Print drive Status",CR,LF
	.db	"i - Query drive using ID command",CR,LF
	.db	"r - Read a sector addressed by the lba variable",CR,LF
	.db	"w - Write a sector adresses by the lba variable",CR,LF
	.db	"l - Change the current LBA variable",CR,LF
	.db	"h - Hexdump the current buffer",CR,LF
	.db	"f - Format the drive for CP/M use (fill with 0xE5)",CR,LF
	.db	"e - Display drive Error information",CR,LF
	.db	"p - Change base IO port",CR,LF
	.db	"? - Display command menu help",CR,LF
	.db	"x - eXit from this utility",CR,LF,LF,0
	jp	menu


format:
	call	print
	.db	"Warning - this command will write data to the drive",CR,LF,LF
	.db	"All existing data will be over written",CR,LF,LF
	.db	"Is that what you want to do ? ",0

	call	cin			; get answer
	cp	'y'
	jr	z,fmt1			; if yes then continue
	call	print
	.db	" Command aborted",CR,LF,0
	ret

fmt1:
	ld	a,0E5h
	call	fillbuf			; setup sector buffer
fmt2:

	call	print
	.db	CR,LF,"Enter starting LBA: ",0

	ld	de,lba2			; starting LBA
	call	getlba
	jr	nc,fmt3

	call	print
	.db	"Invalid LBA",CR,LF,0
	jr	fmt2			; try again

fmt3:

	call	crlf

fmt4:
	call	print
	.db	"Enter ending LBA: ",0

	ld	de,lba3			; ending LBA
	call	getlba
	jr		nc,fmt5

	call	print
	.db	"Invalid LBA",CR,LF,0

	jr	fmt4			; try again

fmt5:
	call	crlf

	call	print			; say what is going to happen
	.db	"Format will start at LBA ",0

	ld	a,(lba2+3)
	call	prhex
	ld	a,(lba2+2)
	call	prhex
	ld	a,(lba2+1)
	call	prhex
	ld	a,(lba2)
	call	prhex

	call	print
	.db	" and finish at LBA ",0
	ld	a,(lba3+3)
	call	prhex
	ld	a,(lba3+2)
	call	prhex
	ld	a,(lba3+1)
	call	prhex
	ld	a,(lba3)
	call	prhex

	call	crlf
	call	print
	.db	"Type y to continue or any other key to abort ",0

	call	cin
	cp	'y'
	jp	nz,fmtx
	call	crlf

	; add the actual format code here
	; get starting LBA
	; get ending LBA
	; fill buffer with E5

	ld	hl,lba2
	ld	de,lba1
	call	cpylba			; copy start LBA to LBA
	call	inclba


fmt6:
	push	hl
	call	print			; display progress
	.db	"Writing LBN: ",0
	ld	a,(lba1+3)
	call	prhex
	ld	a,(lba1+2)
	call	prhex
	ld	a,(lba1+1)
	call	prhex
	ld	a,(lba1)
	call	prhex
	ld	a,CR
	call	cout
	pop	hl

;	do some stuff to format here
	call	write_sector

; need to check status after each call and check for errors
;
	ld	hl,lba1			; LBA for disk operation
	call	inclba

	ld	hl,lba1
	ld	de,lba3
	call	cplba

	jp	nz,fmt6

	call	crlf

fmtx:
	call	print			; finished
	.db	"Format complete",CR,LF,0
	ret


getlba:					; get an LBA value from the console and validate it
	push	de			; save LBA variable
	ld	c,0ah			; bdos read console buffer
	ld	de,conbuf
	call	5			; get edited string

	call	crlf

	; ok we now have an ascii string representing the LBA.	now we have to validate it

	pop	de
	ld	hl,conbuf
	inc	hl
	ld	b,(hl)			; get character count
glba1:
	inc	hl			; HL = address of buffer
	ld	a,(hl)			; get next character

	call	ishex
	ret	c			; return with carry set if any char is invalid

	; ok we are here when we have a valid character (0-9,A-F,a-f) need to convert to binary
	; character is still in A

	cp	3AH			; test for 0-9
	jp	m,glba4
	cp	47H			; test for A-F
	jp	m,glba3
	cp	67H			; test for a-f
	jp	m,glba2

glba2:
	sub	20H			; character is a-f
glba3:
	sub	07h			; character is A-F
glba4:
	sub	030H			; character is 0-9

	ld	(hl),a			; save back in buffer as binary
	djnz	glba1			; continue checking the buffer

; need to pack bytes into the destination LBA which is in de and points to the LSB

glba5:

;  - need to change the endian-ness
	push	de
	ex	de,hl			; clear LBA ready
;	push	de			; address of LBA
;	pop	hl			; address of input buffer
	ld	a,0			; zero existing LBA variable
	ld	(hl),a
	inc	hl
	ld	(hl),a
	inc	hl
	ld	(hl),a
	inc	hl
	ld	(hl),a

	ex	de,hl			; de now positioned at end of LBA
	pop	de			; restore LBA
	; de still contains dest address
	; now pack and store LBA

	ld	hl,conbuf+1		; get character count
	ld	b,(hl)
	inc	hl			; point to first character in buffer

glba6:
	push	de			; save starting address for next subsequent rotations
	ld	a,(hl)			; get next char from buffer
	inc	hl			; next character
	ex	de,hl			; switch to LBA
	rld				; shift nibble into LBA
	inc	hl
	rld
	inc	hl
	rld
	inc	hl
	rld
	ex	de,hl			; back to console buffer
	pop	de			; restore address of LBA
	djnz	glba6			; process next character

	scf
	ccf				; exit with carry clear = success
	ret


gethexbyte:
	push	af			; save incoming value
	ld	c,0ah			; bdos read console buffer
	ld	de,conbuf
	call	5			; get edited string
	call	crlf
	pop	de			; restore incoming to d

	; ok we should now have a string with a hex number
	ld	hl,conbuf
	inc	hl
	ld	a,(hl)			; get character count
	inc	hl
	cp	3
	jr	c,ghb0			; ok if <= 2 chars
	scf				; signal error
	ret				; and return

ghb0:
	or	a			; set flags
	jr	nz,ghb1			; got chars, go ahead
	ld	a,d			; restore incoming value
	or	a			; signal success
	ret				; and done

ghb1:
	ld	b,a			; count to b
	ld	c,0			; initial value

ghb2:
	ld	a,(hl)			; get next char
	inc	hl
	call	ishex
	ret	c			; abort on non-hex char

	; ok we are here when we have a valid character (0-9,A-F,a-f) need to convert to binary
	; character is still in A

	cp	3AH			; test for 0-9
	jp	m,ghb2c
	cp	47H			; test for A-F
	jp	m,ghb2b
	cp	67H			; test for a-f
	jp	m,ghb2a
ghb2a:	sub	20H			; character is a-f
ghb2b:	sub	07H			; character is A-F
ghb2c:	sub	30H			; character is 0-9

	rlc	c			; multiply cur value by 16
	rlc	c
	rlc	c
	rlc	c
	add	a,c			; add to accum
	ld	c,a			; put back in c

	djnz	ghb2			; loop thru all chars

	ld	a,c			; into a for return
	or	a			; signal success
	ret				; done

ishex:
	cp	30h			; check if less than character 0
	jp	m,nothex
	cp	3Ah			; check for > 9
	jp	m,ishx1			; ok, character is 1-9

	cp	41h			; check for character less than A
	jp	m,nothex
	cp	47H			; check for characters > F
	jp	m,ishx1

	cp	61H			; check for characters < a
	jp	m,nothex

	cp	67H			; check for character > f
	jp	m,ishx1
nothex:
	scf				; set carry to indicate fail
	ret

ishx1:
	scf
	ccf
	ret



fillbuf:
					; fill sector buffer with character specified in A
	ld		hl,buffer
	ld		b,0
fb1:
	ld		(hl),a		; store character in buffer
	inc		hl
	ld		(hl),a
	inc		hl
	djnz	fb1
	ret


hexdump:

	call	print			; print heading
	.db	"Current sector buffer contents:",CR,LF,LF,0

	ld	b,32			;  line counter
	ld	hl,buffer		; address of buffer to dump
hxd1:

	push	bc			; save loop counter
	push	hl			; save address pointer

	push	hl
	ld	a,h
	call	prhex			; print hi byte of address
	pop	hl

	push	hl
	ld	a,l
	call	prhex			; print lo byte of address
	ld	a,' '
	call	cout
	pop	hl


	ld	b,16			; how many characters do we display
hxd2:
	push	bc

	ld	a,(hl)			; get byte from buffer
	inc	hl
	push	hl

	call	prhex			; display it in hex
	ld	a,' '
	call	cout

	pop	hl
	pop	bc
	djnz	hxd2

	pop	hl

	ld	b,16			; how many characters do we display
hxd3:
	push	bc

	ld	a,(hl)			; get byte from buffer
	inc	hl
	push	hl

	call	prascii			; display it in ASCII

	pop	hl
	pop	bc
	djnz	hxd3

	push	hl
	call	crlf

	pop	hl
	pop	bc

	ld	a,b				; check for screen pause
	cp	16
	jp	nz,hxd4

	push	hl
	push	bc
	call	cin				; wait for a character
	pop	bc
	pop	hl

hxd4:
	djnz	hxd1			; continue if not at end of buffer

	call	crlf
	call	crlf
	ret

prascii:

	cp	20H
	jp	m,pra1			; anything less than 20H is non-printable
	cp	7fH			; anything greater than 7E is non-printable
	jp	m,pra2
pra1:
	ld	a,'.'
pra2:
	call	cout
	ret
;
;
;
; -------------------------------------------------------------------------
;
; LBA manipulation routines;
;
cpylba:
					; copy LBA to LBA
					; source = HL,	Destination = DE
	ld	bc,04H
	ldir
	ret

; -------------------------------------------------------------------------

inclba:
	ld	a,(hl)			; first byte
	add	a,1
	ld	(hl),a
	ret	nc

	inc	hl			;second byte
	ld	a,(hl)
	add	a,1
	ld	(hl),a
	ret	nc

	inc	hl			; third byte
	ld	a,(hl)
	add	a,1
	ld	(hl),a
	ret	nc

	inc	hl			; fourth byte (MSB)
	ld	a,(hl)
	add	a,1
	ld	(hl),a

	ret

; -------------------------------------------------------------------------

cplba:					; compare LBA
					; addresses by HL and DE

	ld	a,(hl)			; start at LSB
	inc	hl
	ex	de,hl
	cp	(hl)
	ret	nz
	inc	hl
	ex	de,hl

	ld	a,(hl)
	inc	hl
	ex	de,hl
	cp	(hl)
	ret	nz
	inc	hl
	ex	de,hl

	ld	a,(hl)
	inc	hl
	ex	de,hl
	cp	(hl)
	ret	nz
	inc	hl
	ex	de,hl

	ld	a,(hl)
	inc	hl
	ex	de,hl
	cp	(hl)
	ret

	ret	nz
	inc	hl
	ex	de,hl

	ret

; -------------------------------------------------------------------------

prlba:

	call	print
	.db	"Current LBA = ",0
	ld	a,(lba1+3)
	call	prhex
	ld	a,(lba1+2)
	call	prhex
	ld	a,(lba1+1)
	call	prhex
	ld	a,(lba1)
	call	prhex
	call	crlf
	call	crlf
	ret

; -------------------------------------------------------------------------

prport:
	call	print
	.db	"Current PPI base port: 0x",0
	ld	a,(ppibase)
	call	prhex
	call	crlf
	ret


clrlba:
	ld	a,0
	ld	b,4
clr32b1:
	ld	(hl),a
	inc	hl
	djnz	clr32b1
	ret


; -------------------------------------------------------------------------

prstatus:
	call	print
	.db	"status = ",0

	ld	a,ide_status		; read IDE status register
	call	ide_read
	ld	a,c			; returned value
	call	prhex

	call	crlf
	ret




print:
	pop	hl			; get address of text
	ld	a,(hl)			; get next character
	inc	hl
	push	hl
	cp	0
	ret	z			; end of text found
	call	cout			; output character
	jp	print

; -------------------------------------------------------------------------

prhex:					; print hexadecimal digit in A
	push	af
	srl	a			; move high nibble to low
	srl	a
	srl	a
	srl	a
	call	hexnib			; convert to ASCII Hex
	call	cout			; send character to output device
	pop	af
	call	hexnib
	call	cout			; send character to output device
	ret



hexnib:
	and		0fh		; strip high order nibble
	add		a,30H		; add ASCII ofset
	cp		3ah		; correction necessary?
	ret		m
	add		a,7		; correction for A to F
	ret

; -------------------------------------------------------------------------

cout:
	ld	e,a
	ld	c,02h			; Console output byte call
	call	5
	ret

; -------------------------------------------------------------------------

cin:
	ld	c,01h			; BDOS console function
	call	5
	ret

; -------------------------------------------------------------------------

crlf:
	ld	a,CR
	call	cout
	ld	a,LF
	call	cout
	ret

;------------------------------------------------------------------
; Routines that talk with the IDE drive, these should be called by
; the main program.


	; read a sector, specified by the 4 bytes in "lba",
	; Return, acc is zero on success, non-zero for an error
read_sector:
	call	ide_wait_not_busy	;make sure drive is ready
	call	wr_lba			;tell it which sector we want

	ld	a, ide_command
	ld	c, ide_cmd_read
	call	ide_write		; ask the drive to read it

	call	ide_wait_drq		;wait until it's got the data

	bit	0,a
	jp	nz, get_err
	ld	hl, buffer
	call	read_data		;grab the data
	ld	a,0
	ret


	; when an error occurs, we get acc.0 set from a call to ide_drq
	; or ide_wait_not_busy (which read the drive's status register).  If
	; that error bit is set, we should jump here to read the drive's
	; explanation of the error, to be returned to the user.	 If for
	; some reason the error code is zero (shouldn't happen), we'll
	; return 255, so that the main program can always depend on a
	; return of zero to indicate success.
get_err:
	ld	a,ide_err
	call	ide_read
	ld	a,c
	jp	z,gerr2
	ret
gerr2:
	ld	a, 255
	ret


	;write a sector, specified by the 4 bytes in "lba",
	;whatever is in the buffer gets written to the drive!
	;Return, acc is zero on success, non-zero for an error
write_sector:
	call	ide_wait_not_busy	; make sure drive is ready
	call	wr_lba				; tell it which sector we want
	ld	a, ide_command
	ld	c, ide_cmd_write

	call	ide_write		;tell drive to write a sector
	call	ide_wait_drq		;wait unit it wants the data
	bit	0,a			; check for error returned
	jp	nz,get_err

	ld	hl, buffer
	call	write_data		;give the data to the drive
	call	ide_wait_not_busy	;wait until the write is complete

	bit	0,a
	jp	nz,get_err

	ld	a,0
	ret


	; do the identify drive command, and return with the buffer
	; filled with info about the drive
drive_id:
	call	ide_wait_not_busy
	ld	a,ide_head
	ld	c,10100000b
	call	ide_write		;select the master device
	call	ide_wait_ready
	ld	a,ide_command
	ld	c,0ech
	call	ide_write		;issue the command
	call	ide_wait_drq
	ld	hl, buffer
	call	read_data
	ret


	; tell the drive to spin up
spinup:
	ld	c,ide_cmd_spinup
	ld	a,ide_command
	call	ide_write
	call	ide_wait_not_busy
	ret

	; tell the drive to spin down
spindown:
	call	ide_wait_not_busy
	ld	c,ide_cmd_spindown
	ld	a,ide_command
	call	ide_write
	call	ide_wait_not_busy
	ret

	; initialize the IDE drive
ide_init:
	ld	a, ide_head
	ld	b, 0
	ld	c, 10100000b		; select the master device
	call	ide_write
init1:
	ld	a, ide_status
	call	ide_read
	ld	a, c

	; should probably check for a timeout here
	bit	6,a			; wait for RDY bit to be set
	jp	z,init1
	bit	7,a
	jp	nz,init1		; wait for BSY bit to be clear

	ret



; IDE Status Register:
;  bit 7: Busy	1=busy, 0=not busy
;  bit 6: Ready		1=ready for command, 0=not ready yet
;  bit 5: DF		1=fault occured inside drive
;  bit 4: DSC	1=seek complete
;  bit 3: DRQ	1=data request ready, 0=not ready to xfer yet
;  bit 2: CORR	1=correctable error occured
;  bit 1: IDX	vendor specific
;  bit 0: ERR	1=error occured





;------------------------------------------------------------------
; Not quite as low, low level I/O.  These routines talk to the drive,
; using the low level I/O.  Normally a main program should not call
; directly to these.


	;Read a block of 512 bytes (one sector) from the drive
	;and store it in memory @ HL
read_data:
	ld	b, 0
rdblk2:
	push	bc
	push	hl
	ld	a, ide_data
	call	ide_read
	pop	hl
	ld	a, c
	ld	(hl), a
	inc	hl
	ld	a, b
	ld	(hl), a
	inc	hl
	pop	bc
	djnz	rdblk2
	ret


	; Write a block of 512 bytes (at HL) to the drive
write_data:
	ld	b,0
wrblk2:
	push	bc
	ld	a,(hl)
	ld	c, a			; LSB
	inc	hl
	ld	a,(hl)
	ld	b, a			; MSB
	inc	hl
	push	hl

	ld	a, ide_data
	call	ide_write
	pop	hl
	pop	bc
	djnz	wrblk2
	ret



	; write the logical block address to the drive's registers
wr_lba:
	ld	a,(lba1+3)		; MSB
	and	0fh
	or	0e0h
	ld	c,a
	ld	a,ide_head
	call	ide_write

	ld	a,(lba1+2)
	ld	c,a
	ld	a,ide_cyl_msb
	call	ide_write

	ld	a,(lba1+1)
	ld	c,a
	ld	a,ide_cyl_lsb
	call	ide_write

	ld	a,(lba1+0)		; LSB
	ld	c,a
	ld	a,ide_sector
	call	ide_write

	ld	c,1
	ld	a,ide_sec_cnt
	call	ide_write


	ret


ide_wait_not_busy:
	ld	a,ide_status		;wait for RDY bit to be set
	call	ide_read
	bit	7,c
	jp	nz,ide_wait_not_busy
	; should probably check for a timeout here

	ret


ide_wait_ready:
	ld	a,ide_status		; wait for RDY bit to be set
	call	ide_read
	bit	6,c			; test for XXX
	jp	z,ide_wait_ready
	bit	7,c
	jp	nz,ide_wait_ready

	;should probably check for a timeout here
	ret



	; Wait for the drive to be ready to transfer data.
	; Returns the drive's status in Acc
ide_wait_drq:
	ld	a,ide_status			;wait for DRQ bit to be set
	call	ide_read
	bit	7,c
	jp	nz,ide_wait_drq			; check for busy
	bit	3,c					; wait for DRQ
	jp	z,ide_wait_drq

	; should probably check for a timeout here

	ret



;-----------------------------------------------------------------------------
; Low Level I/O to the drive.  These are the routines that talk
; directly to the drive, via the 8255 chip.  Normally a main
; program would not call to these.

	; Do a read bus cycle to the drive, using the 8255.
	; input acc = IDE register address
	; output C = lower byte read from IDE drive
	; output B = upper byte read from IDE drive




ide_read:
	push	af			; save register value
	push	bc
	call	set_ppi_rd		; setup for a read cycle
	pop	bc

	pop	af			; restore register value
	call	wrppictl		; write to control sigs

	or	ide_rd_line		; assert RD pin
	call	wrppictl		; write to control sigs

	push	af			; save register value
	call	rdppilsb		; read LSB register into A
	ld	c,a			; save in reg C

	call	rdppimsb		; read MSB register into A
	ld	b,a			; save in reg C


	pop	af			; restore register value
	xor	ide_rd_line		; de-assert RD signal
	call	wrppictl		; write to control sigs

	ld	a,0
	call	wrppictl		; write to control sigs
	ret



	; Do a write bus cycle to the drive, via the 8255
	; input acc = IDE register address
	; input register C = LSB to write
	; input register B = MSB to write
	;

ide_write:
	push	af			; save IDE register value

	push	bc
	call	set_ppi_wr		; setup for a write cycle
	pop	bc

	ld	a,c			; get value to be written
	call	wrppilsb

	ld	a,b			; get value to be written
	call	wrppimsb

	pop	af			; get saved IDE register
	call	wrppictl		; write to control sigs

	or	ide_wr_line		; assert write pin
	call	wrppictl		; write to control sigs

	xor	ide_wr_line		; de assert WR pin
	call	wrppictl		; write to control sigs

	ld	a,0
	call	wrppictl		; write to control sigs
	ret


;-------------------------------------------------------------------------------------------

ide_hard_reset:
	call	set_ppi_rd
	ld	a,ide_rst_line
	call	wrppictl		; write to control register
	ld	bc,0
rstdly:
	djnz	rstdly
	ld	a,0
	call	wrppictl		; write to control registers
	ret

;-----------------------------------------------------------------------------------
; PPI setup routine to configure the appropriate PPI mode
;
;------------------------------------------------------------------------------------

set_ppi_rd:
	ld	a,(ppibase)
	add	a,PIOCONT		; select Control register
	ld	c,a
	ld	a,rd_ide_8255		; configure 8255 chip, read mode
	out	(c),a
	ret

set_ppi_wr:
	ld	a,(ppibase)
	add	a,PIOCONT		; select Control register
	ld	c,a
	ld	a,wr_ide_8255		; configure 8255 chip, write mode
	out	(c),a
	ret

;------------------------------------------------------------------------------------

rdppilsb:				; read LSB
					; returns data in A
	push	bc
	ld	a,(ppibase)
	add	a,IDELSB		; select Control register
	ld	c,a
	in	a,(c)
	pop	bc
	ret


wrppilsb:				; write LSB
					; data to be written in A
	push	bc
	push	af
	ld	a,(ppibase)
	add	a,IDELSB		; select Control register
	ld	c,a
	pop	af
	out	(c),a
	pop	bc
	ret

;--------------------------------------------------------------------------

rdppimsb:				; read MSB
					; returns data in A
	push	bc
	ld	a,(ppibase)
	add	a,IDEMSB		; select MSB Register
	ld	c,a
	in	a,(c)
	pop	bc
	ret


wrppimsb:				; write LSB
					; data to be written in A
	push	bc
	push	af
	ld	a,(ppibase)
	add	a,IDEMSB		; select MSB Register
	ld	c,a
	pop	af
	out	(c),a
	pop	bc
	ret

;--------------------------------------------------------------------------

wrppictl:				; write to control signals
					; data to be written in A
	push	bc
	push	af
	ld	a,(ppibase)
	add	a,IDECTL		; select CTL Register
	ld	c,a
	pop	af
	out	(c),a
	pop	bc
	ret

;--------------------------------------------------------------------------
; Storage area follows

savsp:		.dw	0		; saved stack pointer
lba1:		.dw	0,0		; LBA used for read/write operations
lba2:		.dw	0,0		; Start LBA for format
lba3:		.dw	0,0		; End LBA for format
ppibase:	.db	DEFBASE		; base address of PPI chip

		.fill	0C00H - $

buffer:		.fill	512		; sector buffer for IDE transfers
conbuf:		.db	8		; maximum chars
		.db	0		; count
		.fill	8		; size of buffer

		.fill	100
stack:

	.end

