TITLE	'IDE3SCSI.ASM for the SCSI S100Computers SCSI board'
;
; This CPM3 BIOS combines the Dual IDE/CF card board software with
; the SCSI NRC 5830 SCSI Board
;
; IDE drives A: and B: are DPH0/DPH1; SCSI drive C: is DPH2.
; Various versions of the file exist in the "IDE-AB CF+SCSI" Folder
; They were step by step optimized for speed (1-11) by Chat GPT.
; If your hardware cannot keep up try a slower version.
;
; Remember this is for a non-banked version of CPM3
;
TRUE		EQU	-1
FALSE		EQU	NOT TRUE

BANKED          EQU	FALSE

CRTSTAT		EQU	0H		;STATUS PORT TO CHECK CRT OUTPUT STATUS
KEYSTAT		EQU	0H
KEYIN		EQU	01H
KEYOUT		EQU	01H

BELL	        EQU	07H
CR	        EQU	0DH
LF	        EQU	0AH

;Ports for 8255 chip. Change these to specify where the 8255 is addressed,
;and which of the 8255's ports are connected to which IDE signals.
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last one is for mode setting for the
;8255 to configure its ports, which must correspond to the way that
;the first three lines define which ports are connected.

IDEportA	EQU	030H		;lower 8 bits of IDE interface
IDEportB	EQU	031H		;upper 8 bits of IDE interface
IDEportC	EQU	032H		;control lines for IDE interface
IDEportCtrl	EQU	033H		;8255 configuration port
IDEDrive	EQU	034H		;Bit 0 = 0 for Drive A: = 1 for F:

READcfg8255	EQU	10010010b	;Set 8255 IDEportC out, IDEportA/B input
WRITEcfg8255	EQU	10000000b	;Set all three 8255 ports output

;IDE control lines for use with IDEportC.  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.

IDEa0line	EQU	01H	;direct from 8255 to IDE interface
IDEa1line	EQU	02H	;direct from 8255 to IDE interface
IDEa2line	EQU	04H	;direct from 8255 to IDE interface
IDEcs0line	EQU	08H	;inverter between 8255 and IDE interface
IDEcs1line	EQU	10H	;inverter between 8255 and IDE interface
IDEwrline	EQU	20H	;inverter between 8255 and IDE interface
IDErdline	EQU	40H	;inverter between 8255 and IDE interface
IDErstline	EQU	80H	;inverter between 8255 and IDE interface
;
;Symbolic constants for the IDE Drive registers, which makes the
;code more readable than always specifying the address pins

REGdata		EQU	IDEcs0line
REGerr		EQU	IDEcs0line + IDEa0line
REGseccnt	EQU	IDEcs0line + IDEa1line
REGsector	EQU	IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB	EQU	IDEcs0line + IDEa2line
REGcylinderMSB	EQU	IDEcs0line + IDEa2line + IDEa0line
REGshd		EQU	IDEcs0line + IDEa2line + IDEa1line		;(0EH)
REGcommand	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line	;(0FH)
REGstatus	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol	EQU	IDEcs1line + IDEa2line + IDEa1line
REGastatus	EQU	IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;IDE Command Constants.  These should never change.

COMMANDrecal	EQU	10H
COMMANDread	EQU	20H
COMMANDwrite	EQU	30H
COMMANDinit	EQU	91H
COMMANDid	EQU	0ECH
COMMANDspindown	EQU	0E0H
COMMANDspinup	EQU	0E1H

DRIVE$SELECT	EQU	25	;DPH offset for Drive Hardware Select (Relative Drive Offset does not work)

; 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
;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SCSI BOARD EQUATES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

NCRBASE EQU     048H

ODR             EQU     NCRBASE+0       ; Output Data Register, write
CSD             EQU     NCRBASE+0       ; Current SCSI Data, read
ICR             EQU     NCRBASE+1       ; Initiator Command Register
MR2             EQU     NCRBASE+2       ; Mode Register 2
TCR             EQU     NCRBASE+3       ; Target Command Register
SER             EQU     NCRBASE+4       ; Select Enable Register
CSB             EQU     NCRBASE+4       ; Current SCSI Bus Status, read
BSR             EQU     NCRBASE+5       ; Bus and Status Register
IDR             EQU     NCRBASE+6       ; Input Data Register
RPI             EQU     NCRBASE+7       ; Reset Parity/Interrupt

; NCR5380 Initiator Command Register bits

ICR$DBUS        EQU    001H            ; Assert data bus
ICR$ATN         EQU    002H            ; Assert ATN
ICR$SEL         EQU    004H            ; Assert SEL
ICR$BSY         EQU    008H            ; Assert BSY
ICR$ACK         EQU    010H            ; Assert ACK
ICR$RST         EQU    080H            ; Assert SCSI RST

; SCSI bus phase values as used in the NCR5380 TCR/CSB phase field

PH$DATAOUT      EQU  000H
PH$DATAIN       EQU  001H
PH$COMMAND      EQU  002H
PH$STATUS       EQU  003H
PH$MSGOUT       EQU  006H
PH$MSGIN        EQU  007H

CSB$REQ         EQU  020H
CSB$BSY         EQU  040H
CSB$PHASE       EQU  01CH

PHASE$DATAOUT   EQU 000H
PHASE$DATAIN    EQU 004H
PHASE$STATUS    EQU 00CH
PHASE$MSGIN     EQU 01CH

OURID   EQU     080H            ; Host adapter SCSI ID 7 bit mask
TGTID   EQU     001H            ; Disk target SCSI ID 0 bit mask

SCSI$DRIVE      EQU 2           ; CP/M drive C:


	; DEFINE PUBLIC LABELS:
	PUBLIC	DPH0,DPH1,DPH2	;DISK PARAMETER HEADERS
	PUBLIC	SCSI$INIT,SCSI$LOGIN,SCSI$RD,SCSI$WRT

	; DEFINE EXTERNAL LABELS:
	EXTRN	@ADRV,@RDRV
	EXTRN	@DMA,@TRK,@SECT
	EXTRN	@CBNK
	EXTRN	@DBNK			;BANK FOR DMA OPERATION
	EXTRN	@ERMDE			;BDOS ERROR MODE
	EXTRN	?WBOOT			;WARM BOOT VECTOR
	EXTRN	?PMSG			;PRINT MESSAGE @<HL> UP TO 00, SAVES
					; [BC] AND [DE]
	EXTRN	?PDERR			;PRINT BIOS DISK ERROR HEADER
	EXTRN	?CONIN,?CONO		;CONSOLE IN AND OUT
	EXTRN	?CONST			;CONSOLE STATUS
	EXTRN	?BNKSL			;SELECT PROCESSOR MEMORY BANK
	EXTRN	?SMSG			;MY ROUTINE TO SPEAK A MESSAGE

	; INCLUDE CP/M 3.0 DISK DEFINITION MACROS:
	MACLIB	CPM3

	; INCLUDE Z-80 MACRO LIBRARY:
	MACLIB	Z80


	DSEG			;PUT IN OP SYS BANK IF BANKING

	; EXTENDED DISK PARAMETER HEADER FOR DRIVE 0:
	DW	HDWRT		;HARD DISK WRITE ROUTINE
	DW	HDRD		;HARD DISK READ ROUTINE
	DW	HDLOGIN$0	;HARD DISK LOGIN PROCEDURE
	DW	HDINIT$0	;HARD DISK DRIVE INITIALIZATION ROUTINE
	DB	0		;RELATIVE DRIVE 0 ON THIS CONTROLLER
	DB	0		;MEDIA TYPE:
				;  HI BIT SET : DRIVE NEEDS RECALIBRATING
DPH0:	DPH	0,IDEHD$DPB,0
	DB	0		;Drive A: Hardware Select, (IX offset 25)

	; EXTENDED DISK PARAMETER HEADER FOR DRIVE 1:
	DW	HDWRT		;HARD DISK WRITE ROUTINE
	DW	HDRD		;HARD DISK READ ROUTINE
	DW	HDLOGIN$1	;HARD DISK LOGIN PROCEDURE
	DW	HDINIT$1	;HARD DISK DRIVE INITIALIZATION ROUTINE
	DB	0		;RELATIVE DRIVE 0 ON THIS CONTROLLER
	DB	0		;MEDIA TYPE:
				;  HI BIT SET : DRIVE NEEDS RECALIBRATING
DPH1:	DPH	0,IDEHD$DPB,0
	DB	1		;Drive B: Hardware Select, (IX offset 25)


	; MAKE SURE DPB'S ARE IN COMMON MEMORY:

	; EXTENDED DISK PARAMETER HEADER FOR DRIVE 2, NCR5380 SCSI DRIVE C:
	DW	SCSI$WRT	;SCSI HARD DISK WRITE ROUTINE
	DW	SCSI$RD		;SCSI HARD DISK READ ROUTINE
	DW	SCSI$LOGIN	;SCSI HARD DISK LOGIN PROCEDURE
	DW	SCSI$INIT	;SCSI HARD DISK DRIVE INITIALIZATION ROUTINE
	DB	0		;RELATIVE DRIVE 0 ON THIS CONTROLLER
	DB	0		;MEDIA TYPE:
				;  HI BIT SET : DRIVE NEEDS RECALIBRATING
DPH2:	DPH	0,SCSI$DPB,0
	DB	SCSI$DRIVE	;Drive C: Hardware Select, (IX offset 25)

	CSEG

	; 512 BYTE SECTORS ON HARD DISK (512X61X256) = 7,995,392 Bytes)
	; with 16 heads thisis less than 16 drive cylinders!
	
IDEHD$DPB:
;	DPB	512,32,512,2048,1024,2,8000H	
;	DPB	256,32,1001,2048,1024,2,8000H	
;	DPB	512,60,256,2048,1024,1,8000H
;	DPB	512,62,252,2048,1024,1,8000H
	DPB	512,61,256,2048,1024,1,8000H


SCSI$DPB:
	DPB	512,64,256,2048,1024,1,8000H

	DSEG			;CAN SET BACK TO BANKED SEGMENT IF BANKING



;-----------------INITILIZE THE IDE HARD DISK -----------------------------------------

	;;;;; HDINIT:
HDINIT0:RET			      ;Pospone until Logon fot IDE Drives
HDINIT1:RET



	;;;;; HDLOGIN
;-----------------IDE LOGIN A DISK  -----------------------------------------
;	on entry:	[de] = pointer to XDPH of drive to be logged-in
;
;	on exit:	XDPH address on the stack at entry has been
;			replaced with a null address if the drive has
;			been determined to be an invalid one
;-----------------------------------------------------------------------
HDLOGIN$0:
	PUSH	D		;Save it just in case
	MVI	A,0
	OUT	IDEDrive	;Select Drive/CF card (A:) 
	JMP	COMMON$INIT

HDLOGIN$1:
	PUSH	D		;Save it just in case
	MVI	A,1
	OUT	IDEDrive	;Select Drive/CF card (F:) 
	JMP	COMMON$INIT


COMMON$INIT:			;Initilze the IDE Drive 8255 and drive then do a hard reset on the drive, 
	MVI	A,READcfg8255	;Config 8255 chip (10010010B), read mode on return
	OUT	IDEportCtrl	;Config 8255 chip, READ mode
				
				;Hard reset the disk drive 
				;For some reason some CF cards need to the RESET line 
				;pulsed very carefully. You may need to play around   
	MVI	A,IDErstline	;with the pulse length. Symptoms are: incorrect data comming
	OUT	IDEportC	;back from a sector read (often due to the wrong sector being read)
				;I have a (negative)pulse of 2.7uSec. (10Mz Z80, two IO wait states).
	
	MVI	B,20H		;Which seem to work for the 5 different CF cards I have.
ResetDelay:
	DCR	B
	JNZ	ResetDelay	;Delay (reset pulse width)
	XRA	A
	OUT	IDEportC	;No IDE control lines asserted (just bit 7 of port C)
	
	CALL	DELAY$32	;Allow time for hardware to recover
			
	MVI	D,11100000b	;Data for IDE SDH reg (512bytes, LBA mode,single drive,head 0000)
				;For Trk,Sec,head (non LBA) use 10100000
				;Note. Cannot get LBA mode to work with an old Seagate Medalist 6531 drive.
				;have to use the non-LBA mode. (Common for old hard disks).

	MVI	E,REGshd	;00001110,(0EH) for CS0,A2,A1,  
	CALL	IDEwr8D		;Write byte to select the MASTER device

	MVI	B,080H		;<<< May need to adjust delay time
WaitInit:	
	MVI	E,REGstatus	;Get status after initilization
	CALL	IDErd8D		;Check Status (info in [D])
	BIT	7,D
	JZ	DoneInit	;Return if ready bit is zero
				;Delay to allow drive to get up to speed
	PUSH	B
	LXI	B,0FFFFH	
DELAY2:	MVI	D,2		;May need to adjust delay time to allow cold drive to
DELAY1:	DCR	D		;to speed
	JNZ	DELAY1
	DCX	B
	MOV	A,C
	ORA	B
	JNZ	DELAY2
	POP	B

	DJNZ	WaitInit
	CALL	SetErrorFlag	;Ret with NZ flag set if error (probably no drive)
	LXI	H,MSG$INIT$ERR	;RESTORE FAILED
	CALL	?PMSG
	XRA	A
	DCR	A
	POP	D		;[DE] has calling routine's return address
	LXI	H,0		;This will be invalid drive from now on in the CPM BDOS
	RET			;Return NZ

DoneInit:
	POP	D		;And origional DE
	XRA	A		;RETURN WITH NO ERROR
	RET			;Return Z as no error




;------------------- IDE SECTOR WRITE ROUTINE ----------------------------
;
;	on entry:	[de] = XDPH address for current drive
;
;	on exit:	[a] =   0 --> successful write operation
;				1 --> unsuccessful write operation
;                               2 --> Disk is write protected
;
; Assumes valid track in (@TRK) & (@SECT), Address in (@DMA) from CPM 
; Note: The XDPH table is used to get the current drive.   It is at IX+DRIVE$SELECT.
;----------------------------------------------------------------------------------------
;
HDWRT:	PUSH	D		;Save it just in case CPM uses it later
	PUSHIX			;Save [IX]
	
	PUSH	D		;[DE]->[IX]
	POPIX			;get XDPH address for current requested drive to [ix]
	LDX	A,DRIVE$SELECT	;[A] <- (IX + DRIVE$SELECT). Drive #, (IDE Board expects a 0H or 1H)
	OUT	IDEDrive	;Select Drive/CF card (A: or B:) 

	XRA	A
	STA	ERFLG
	
	CALL	wrlba		;Send to drive the sector we want to read. Converting
				;CPM TRK/SEC info to Drive TRK/SEC/Head
				;send before error check so info is updated
	CALL	IDEwaitnotbusy	;make sure drive is ready
	JC	SetErrorFlag

	MVI	D,COMMANDwrite
	MVI	E,REGcommand
	CALL	IDEwr8D		;tell drive to write a sector

	CALL	IDEwaitdrq	;wait unit it wants the data
	JC	SetErrorFlag	;If problem abort

	LHLD    @DMA		;DMA address
	
	MVI	A,WRITEcfg8255
	OUT	IDEportCtrl
	MVI	B,0		;256X2 bytes
	
WRSEC1:	MOV	A,M
	INX	H
	OUT	IDEportA	;LOW byte on A (Was wrong order in earlier versions)
	MOV	A,M
	INX	H
	OUT	IDEportB	;Then HIGH byte on B
	MVI	A,REGdata
	PUSH	PSW
	OUT	IDEportC	;Send write command
	
	ORI	IDEwrline	;Send WR pulse
	OUT	IDEportC
	POP	PSW
	OUT	IDEportC
	DJNZ	WRSEC1

	MVI	A,READcfg8255	;Set 8255 back to read mode
	OUT	IDEportCtrl
		
CHECK$RW:
	MVI	E,REGstatus	;Check R/W status when done
	CALL	IDErd8D
	MOV	A,D
	ANI	01H
	STA	ERFLG		;Ret Z if All OK
	
	POPIX			;Return with origional IX
	POP	D		;and DE
	RZ
	
SetErrorFlag:			;For now just return with error flag set
	XRA	A
	DCR	A
	STA	ERFLG		;Ret NZ if problem
	RET
;

;------------------- IDE SECTOR READ ROUTINE ----------------------------
;
;	on entry:	[de] = XDPH address for current drive
;
;	on exit:	[a] =   0 --> successful write operation
;				1 --> unsuccessful write operation
;                               2 --> Disk is write protected
;
; Assumes valid track in (@TRK) & (@SECT), Address in (@DMA) from CPM 
; Note: The XDPH table is used to get the current drive.   It is at IX+DRIVE$SELECT.
;----------------------------------------------------------------------------------------
;
HDRD:	PUSH	D		;Save it just in case CPM uses it later
	PUSHIX			;Save [IX]
	
	PUSH	D		;[DE]->[IX]
	POPIX			;get XDPH address for current requested drive to [ix]
	LDX	A,DRIVE$SELECT	;[A] <- (IX + DRIVE$SELECT). Drive #, (IDE Board expects a 0H or 1H)
	OUT	IDEDrive	;Select Drive/CF card (A: or F:) 

	XRA	A
	STA	ERFLG	     	;CLEAR THE ERROR FLAG
	CALL	wrlba		;Send to drive the sector we want to read. Converting
				;CPM TRK/SEC info to Drive TRK/SEC/Head
				;Send before error check so info is updated
	CALL	IDEwaitnotbusy	;make sure drive is ready
	JC	SetErrorFlag	;Returned with NZ set if error

	MVI	D,COMMANDread
	MVI	E,REGcommand
	CALL	IDEwr8D		;Send sec read command to drive.
	
	CALL	IDEwaitdrq	;Wait until it's got the data
	JC	SetErrorFlag	;If problem abort
		
	LHLD  	@DMA		;DMA address

	MVI	B,0
MoreRD16:
	MVI	A,REGdata	;REG regsiter address
	OUT	IDEportC	

	ORI	IDErdline	;08H+40H, Pulse RD line
	OUT	IDEportC	


	IN	IDEportA	;read the LOW byte
	MOV	M,A
	INX	H
	IN	IDEportB	;THEN read the HIGH byte
	MOV	M,A
	INX	H
	
	MVI	A,REGdata	;Deassert RD line
	OUT	IDEportC

	DJNZ	MoreRD16

	POP	PSW
	CALL	?BNKSL
	JMP	CHECK$RW


;-----------IDE Card SUPPORT ROUTINES ----------------------------------------------------------
;
wrlba:				;Write the logical block address to the drive's registers
	LDA	@SECT		;Get CPM requested sector
	INR	A		;Disk sectors are numbered  1...MAXSEC
	MOV	D,A
	MVI	E,REGsector	;Send info to drive
	CALL	IDEwr8D

	LHLD	@TRK		;We have are in LBA mode so send directly
	MOV	D,L		;Send Low TRK#
	MVI	E,REGcylinderLSB
	CALL	IDEwr8D

	MOV	D,H		;Send High TRK#
	MVI	E,REGcylinderMSB
	CALL	IDEwr8D

	MVI	D,1		;For now, one sector at a time
	MVI	E,REGseccnt
	CALL	IDEwr8D
	RET


IDEwaitnotbusy:			;Drive READY if 01000000
	MVI	B,0FFH
	MVI	C,0FFH		;Delay, must be above 80H for 4MHz Z80. Leave longer for slower drives
MoreWait:
	MVI	E,REGstatus	;wait for RDY bit to be set
	CALL	IDErd8D
	MOV	A,D
	ANI	11000000B
	XRI	01000000B
	JZ	DoneNotbusy	
	DJNZ	MoreWait
	DCR	C
	JNZ	MoreWait
	STC			;Set carry to indicqate an error
	ret
DoneNotBusy:
	ORA	A		;Clear carry it indicate no error
	RET
				;Wait for the drive to be ready to transfer data.
				;Returns the drive's status in Acc
IDEwaitdrq:
	MVI	B,0FFH
	MVI	C,0FFH		;Delay, must be above 80H for 4MHz Z80. Leave longer for slower drives
MoreDRQ:
	MVI	E,REGstatus	;wait for DRQ bit to be set
	CALL	IDErd8D
	MOV	A,D
	ANI	10001000B
	CPI	00001000B
	JZ	DoneDRQ
	DJNZ	MoreDRQ
	DCR	C
	JNZ	MoreDRQ
	STC			;Set carry to indicate error
	RET
DoneDRQ:
	ORA	A		;Clear carry
	RET


DELAY$32: MVI	A,40		;DELAY ~32 MS (DOES NOT SEEM TO BE CRITICAL)
DELAY3:	MVI	B,0
M0:	DJNZ	M0
	DCR	A
	JNZ	DELAY3 
	RET


;------------------------------------------------------------------
; Low Level 8 bit R/W to the drive controller.  These are the routines that talk
; directly to the drive controller registers, via the 8255 chip.  
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly 
; in the routines HDRD & HDWRT for speed reasons.
;
IDErd8D:			;READ 8 bits from IDE register in [E], return info in [D]
	MOV	A,E
	OUT	IDEportC	;drive address onto control lines

	ORI	IDErdline	;RD pulse pin (40H)
	OUT	IDEportC	;assert read pin

	IN	IDEportA
	MOV	D,A		;return with data in [D]

	MOV	A,E		;<---Ken Robbins suggestion
	OUT	IDEportC	;deassert RD pin first

	XRA	A
	OUT	IDEportC	;Zero all port C lines
	ret
;
;
IDEwr8D:			;WRITE Data in [D] to IDE register in [E]
	MVI	A,WRITEcfg8255	;Set 8255 to write mode
	OUT	IDEportCtrl

	MOV	A,D		;Get data put it in 8255 A port
	OUT	IDEportA

	MOV	A,E		;select IDE register
	OUT	IDEportC

	ORI	IDEwrline	;lower WR line
	OUT	IDEportC

	MOV	A,E		;<---Ken Robbins suggestion
	OUT	IDEportC	;deassert WR pin first

	XRA	A		;Deselect all lines including WR line
	OUT	IDEportC

	MVI	A,READcfg8255	;Config 8255 chip, read mode on return
	OUT	IDEportCtrl
	RET
;
;
;------------------------------ SCSI DRIVE ROUTINES ------------------------

SCSI$INIT:
        XRA     A
        OUT     ICR
        OUT     TCR
        OUT     MR2
        OUT     SER
        RET

;----------------- SCSI LOGIN A DISK ---------------------------------------
;
; on entry:      [DE] = pointer to XDPH of drive to be logged-in
;
; on exit:       XDPH address on stack at entry is replaced with
;                null address if this drive is invalid.
;-----------------------------------------------------------------------

SCSI$LOGIN:
        PUSH    D               ; Save XDPH pointer, as in IDE module
				; Clear the NCR5380 and give the target time to settle.
        XRA     A
        OUT     ICR
        OUT     TCR
        OUT     MR2
        OUT     SER
				; Optional SCSI bus reset pulse.  Comment out this section if
				; other SCSI devices should not be reset during login.
        MVI     A,ICR$RST
        OUT     ICR
        CALL    BIG$DELAY
        XRA     A
        OUT     ICR
        CALL    BIG$DELAY

        CALL    SCSI$TEST$READY
        ORA     A
        JZ      SCSI$LOGIN$OK

        LXI     H,MSG$SCSI$INIT$ERR
        CALL    ?PMSG
        XRA     A
        DCR     A               ; A=FF, NZ error
        POP     D
        LXI     H,0             ; Invalid drive from now on in BDOS
        RET                     ; Return NZ

SCSI$LOGIN$OK:
        POP     D               ; Restore original DE
        XRA     A               ; Return Z, no error
        RET


;------------------- SCSI SECTOR WRITE ROUTINE -----------------------------
;
; on entry:      [DE] = XDPH address for current drive
;
; on exit:       [A] = 0 successful write
;                      1 unsuccessful write
;                      2 disk write protected, not used here
;
; Assumes valid track in (@TRK), sector in (@SECT), DMA in (@DMA).
; @SECT is expected as a CP/M 128-byte sector number 0..63 or 1..64.
; The routine accepts either convention by normalizing 0 to sector 0
; and 1..64 to sector-1 internally.
;-----------------------------------------------------------------------

;------------------- SECTOR WRITE ROUTINE -----------------------------
;
; on entry:      [DE] = XDPH address for current drive
;
; on exit:       [A] = 0 successful write
;                      1 unsuccessful write
;
; IMPORTANT: This version assumes the CP/M 3 DPB uses 512-byte
; sectors for this drive:
;
;       DPB 512,64,256,2048,1024,1,8000H
;
; Therefore CP/M's @DMA points to a 512-byte sector buffer and
; @SECT is a 512-byte sector number 0..63 within the track.
; No 128-byte deblocking is done in this BIOS routine.
;-----------------------------------------------------------------------

SCSI$WRT:
        PUSH    D
        XRA     A
        STA     ERFLG

        CALL    MAKE$LBA        ; LBA = (@TRK * 64) + @SECT

        ; Direct 512-byte write from CP/M DMA address.
        ; This avoids copying DMA -> SECBUF before every SCSI WRITE.
        LHLD    @DMA
        SHLD    XFERPTR

        CALL    SCSI$WRITE512
        ORA     A
        JNZ     SCSI$RW$ERR

        POP     D
        XRA     A
        STA     ERFLG
        RET

;------------------- SCSI SECTOR READ ROUTINE ------------------------------
;
; on entry:      [DE] = XDPH address for current drive
;
; on exit:       [A] = 0 successful read
;                      1 unsuccessful read
;-----------------------------------------------------------------------

SCSI$RD:
        PUSH    D
        XRA     A
        STA     ERFLG

        CALL    MAKE$LBA        ; LBA = (@TRK * 64) + @SECT

        ; Direct 512-byte read into CP/M DMA address.
        ; This avoids copying SECBUF -> DMA after every SCSI READ.
        LHLD    @DMA
        SHLD    XFERPTR

        CALL    SCSI$READ512
        ORA     A
        JNZ     SCSI$RW$ERR

        POP     D
        XRA     A
        STA     ERFLG
        RET

SCSI$RW$ERR:
        MVI     A,1
        STA     ERFLG
        POP     D
        MVI     A,1
        RET

;----------- SCSI LBA SUPPORT FOR 512-BYTE CP/M SECTORS --------------------
;
; CP/M 3 DPB for this drive says 512-byte sectors and 64 sectors/track.
;
;       physical_lba = (@TRK * 64) + @SECT
;
; @SECT is assumed zero-based, 0..63, matching the existing IDE BIOS
; convention.  Do NOT subtract 1 here.
;-----------------------------------------------------------------------

MAKE$LBA:
        LHLD    @TRK            ; HL = track
        DAD     H               ; *2
        DAD     H               ; *4
        DAD     H               ; *8
        DAD     H               ; *16
        DAD     H               ; *32
        DAD     H               ; *64
        XCHG                    ; DE = track * 64
        LHLD    @SECT           ; HL = sector 0..63
        DAD     D               ; HL = LBA

        MOV     A,L
        STA     LBA0
        MOV     A,H
        STA     LBA1
        XRA     A
        STA     LBA2
        RET

;----------- SCSI 512-BYTE COPY SUPPORT ------------------------------------
;
; SCSI$READ512 and SCSI$WRITE512 use SECBUF internally.  Since CP/M is
; using 512-byte sectors for this DPB, copy all 512 bytes between @DMA
; and SECBUF.
;-----------------------------------------------------------------------

COPY$SECBUF512$TO$DMA:
        LXI     H,SECBUF        ; source
        LHLD    @DMA
        XCHG                    ; DE = DMA destination, HL lost
        LXI     H,SECBUF        ; HL = source again
        LXI     B,512
        CALL    MOVE$BC
        RET

COPY$DMA512$TO$SECBUF:
        LHLD    @DMA            ; HL = DMA source
        LXI     D,SECBUF        ; DE = destination
        LXI     B,512
        CALL    MOVE$BC
        RET

MOVE$BC:
        MOV     A,B
        ORA     C
        RZ
MOVE$BC1:
        MOV     A,M
        STAX    D
        INX     H
        INX     D
        DCX     B
        MOV     A,B
        ORA     C
        JNZ     MOVE$BC1
        RET

;----------- NCR5380/SCSI SUPPORT ROUTINES ----------------------------
;
; These are non-DMA, polled NCR5380 routines.  They issue SCSI READ(6),
; WRITE(6), TEST UNIT READY, and REQUEST SENSE commands.
;-----------------------------------------------------------------------

SCSI$READ512:
        CALL    BUILD$READ$CDB
        CALL    SCSI$SELECT
        RNZ
        LXI     H,CDB
        MVI     B,6
        CALL    SCSI$SEND$CMD
        RNZ
        CALL    SCSI$WAIT$REQ$PHASE
        RNZ
        LDA     LASTPHASE
        CPI     PHASE$DATAIN
        JZ      SRD$DATAIN
        CPI     PHASE$STATUS
        JZ      SRD$STATUS$ONLY
        MVI     A,1
        RET

SRD$DATAIN:
        LHLD    XFERPTR         ; destination = CP/M DMA
        LXI     D,512
        CALL    SCSI$RECV$512
        RNZ
        CALL    SCSI$GET$STATUS
        RET

SRD$STATUS$ONLY:
        CALL    SCSI$GET$STATUS
        MVI     A,1
        RET

SRD$ERR:
        MVI     A,1
        RET

SCSI$WRITE512:
        CALL    BUILD$WRITE$CDB
        CALL    SCSI$SELECT
        RNZ
        LXI     H,CDB
        MVI     B,6
        CALL    SCSI$SEND$CMD
        RNZ

        ; Target must request DATA OUT before we send the 512 bytes.
        CALL    SCSI$WAIT$REQ$PHASE
        RNZ
        LDA     LASTPHASE
        CPI     PHASE$DATAOUT
        JNZ     SWR$ERR

        LHLD    XFERPTR         ; source = CP/M DMA
        LXI     D,512
        CALL    SCSI$SEND$512
        RNZ

        ; After data out, explicitly wait for STATUS phase.
        CALL    SCSI$WAIT$REQ$PHASE
        RNZ
        LDA     LASTPHASE
        CPI     PHASE$STATUS
        JNZ     SWR$ERR

        CALL    SCSI$GET$STATUS
        RET

SWR$ERR:
        MVI     A,1
        RET

BUILD$READ$CDB:
        LXI     H,CDB
        MVI     M,008H          ; SCSI READ(6)
        INX     H
        LDA     LBA2
        ANI     01FH
        MOV     M,A
        INX     H
        LDA     LBA1
        MOV     M,A
        INX     H
        LDA     LBA0
        MOV     M,A
        INX     H
        MVI     M,001H          ; One 512-byte physical sector
        INX     H
        MVI     M,000H
        RET

BUILD$WRITE$CDB:
        LXI     H,CDB
        MVI     M,00AH          ; SCSI WRITE(6)
        INX     H
        LDA     LBA2
        ANI     01FH
        MOV     M,A
        INX     H
        LDA     LBA1
        MOV     M,A
        INX     H
        LDA     LBA0
        MOV     M,A
        INX     H
        MVI     M,001H          ; One 512-byte physical sector
        INX     H
        MVI     M,000H
        RET

SCSI$SELECT:
        XRA     A
        OUT     ICR
        OUT     TCR
        OUT     MR2

SEL$WAIT:
        IN      CSB
        ANI     CSB$BSY
        JNZ     SEL$WAIT

        MVI     A,OURID+TGTID
        OUT     ODR
        MVI     A,ICR$DBUS+ICR$SEL
        OUT     ICR

        ; No added select-settle delay.  If this becomes marginal,
        ; go back to IDE3S9 or IDE3S8F.
SEL$BUSY:
        IN      CSB
        ANI     CSB$BSY
        JZ      SEL$BUSY

        XRA     A
        OUT     ICR
        RET

SCSI$SEND$CMD:
        MVI     A,PH$COMMAND
        OUT     TCR
SCMD$1:
        MOV     A,M
        CALL    SCSI$SEND$BYTE
        RNZ
        INX     H
        DCR     B
        JNZ     SCMD$1
        XRA     A
        RET

SCSI$SEND$DATA:
        ; Fast Z80/8080-compatible inline DATA OUT loop.
        ; HL = source buffer, DE = byte count, normally 512.
        ; Avoids CALL/RET overhead for every byte.
        MVI     A,PH$DATAOUT
        OUT     TCR

SDO$1:
        MOV     A,D
        ORA     E
        JZ      SDO$DONE

        MOV     A,M
        OUT     ODR
        MVI     A,ICR$DBUS
        OUT     ICR

SDO$WAIT1:
        IN      CSB
        ANI     CSB$REQ
        JZ      SDO$WAIT1

        MVI     A,ICR$DBUS+ICR$ACK
        OUT     ICR

SDO$WAIT0:
        IN      CSB
        ANI     CSB$REQ
        JNZ     SDO$WAIT0

        XRA     A
        OUT     ICR
        INX     H
        DCX     D
        JMP     SDO$1

SDO$DONE:
        XRA     A
        RET

SCSI$RECV$DATA:
        ; Fast inline DATA IN loop.
        ; HL = destination buffer, DE = byte count, normally 512.
        ; Keeps the short settling delays that made C: stable.
        MVI     A,PH$DATAIN
        OUT     TCR

SDI$1:
        MOV     A,D
        ORA     E
        JZ      SDI$DONE

SDI$WAIT1:
        IN      CSB
        ANI     CSB$REQ
        JZ      SDI$WAIT1

        ; No extra settling delay.  If this version produces false
        ; directory entries or CRCK errors, go back to IDE3S5.
        IN      CSD
        MOV     M,A

        MVI     A,ICR$ACK
        OUT     ICR

SDI$WAIT0:
        IN      CSB
        ANI     CSB$REQ
        JNZ     SDI$WAIT0

        XRA     A
        OUT     ICR
        INX     H
        DCX     D
        JMP     SDI$1

SDI$DONE:
        XRA     A
        RET

;----------- SCSI FIXED 512-BYTE FAST DATA TRANSFER ROUTINES ---------------
;
; These are used only for normal disk sector transfers.  They avoid the
; DE byte-count test on every byte.  B=0 gives 256 DJNZ iterations.
; C=2 repeats that twice = 512 bytes.
;-----------------------------------------------------------------------

SCSI$SEND$512:
        MVI     A,PH$DATAOUT
        OUT     TCR
        MVI     C,2

SS512$OUTER:
        MVI     B,0

SS512$1:
        MOV     A,M
        OUT     ODR
        MVI     A,ICR$DBUS
        OUT     ICR

SS512$WAIT1:
        IN      CSB
        ANI     CSB$REQ
        JZ      SS512$WAIT1

        MVI     A,ICR$DBUS+ICR$ACK
        OUT     ICR

SS512$WAIT0:
        IN      CSB
        ANI     CSB$REQ
        JNZ     SS512$WAIT0

        XRA     A
        OUT     ICR
        INX     H
        DJNZ    SS512$1

        DCR     C
        JNZ     SS512$OUTER

        XRA     A
        RET


SCSI$RECV$512:
        MVI     A,PH$DATAIN
        OUT     TCR
        MVI     C,2

SR512$OUTER:
        MVI     B,0

SR512$1:
SR512$WAIT1:
        IN      CSB
        ANI     CSB$REQ
        JZ      SR512$WAIT1

        IN      CSD
        MOV     M,A

        MVI     A,ICR$ACK
        OUT     ICR

SR512$WAIT0:
        IN      CSB
        ANI     CSB$REQ
        JNZ     SR512$WAIT0

        XRA     A
        OUT     ICR
        INX     H
        DJNZ    SR512$1

        DCR     C
        JNZ     SR512$OUTER

        XRA     A
        RET


SCSI$SEND$BYTE:
        OUT     ODR             ; Put byte into NCR5380 output register
        MVI     A,ICR$DBUS      ; Assert data bus BEFORE target REQ
        OUT     ICR

WSREQ$1:
        IN      CSB
        ANI     CSB$REQ
        JZ      WSREQ$1
        MVI     A,ICR$DBUS+ICR$ACK
        OUT     ICR             ; ACK while data bus is still asserted

WSREQ$0:
        IN      CSB
        ANI     CSB$REQ
        JNZ     WSREQ$0
        XRA     A
        OUT     ICR             ; Drop ACK and release data bus
        RET


SCSI$RECV$BYTE:
WRREQ$1:
        IN      CSB
        ANI     CSB$REQ
        JZ      WRREQ$1

        ; Short settling delay after REQ.  The older test version used
        ; many NOPs here; this step keeps only a small guard delay.
        NOP
        NOP
        NOP

        IN      CSD             ; This board reads data from CSD, not IDR
        PUSH    PSW

        MVI     A,ICR$ACK
        OUT     ICR

        ; Hold ACK briefly, then wait for target to drop REQ.
        NOP
        NOP

WRREQ$0:
        IN      CSB
        ANI     CSB$REQ
        JNZ     WRREQ$0

        XRA     A
        OUT     ICR

        POP     PSW
        RET


SCSI$GET$STATUS:
        MVI     A,PH$STATUS
       OUT     TCR
        CALL    SCSI$RECV$BYTE
        STA     SCSISTAT
        MVI     A,PH$MSGIN
        OUT     TCR
        CALL    SCSI$RECV$BYTE
        STA     SCSIMSG
        LDA     SCSISTAT
        ORA     A
        JZ      SGOK
        MVI     A,1
        RET
SGOK:
        XRA     A
        RET



SCSI$WAIT$REQ$PHASE:
        LXI     H,0FFFFH
SWRP$1:
        IN      CSB
        ANI     CSB$REQ
        JNZ     SWRP$2
        DCX     H
        MOV     A,H
        ORA     L
        JNZ     SWRP$1
        MVI     A,1
        RET
SWRP$2:
        IN      CSB
        ANI     CSB$PHASE
        STA     LASTPHASE
        XRA     A
        RET

;----------- SCSI TEST UNIT READY / REQUEST SENSE -------------------

SCSI$TEST$READY:
        MVI     A,40
        STA     TURTRIES
TUR$LOOP:
        CALL    BUILD$TUR$CDB
        CALL    SCSI$SELECT
        JNZ     TUR$FAIL$ONE
        LXI     H,CDB
        MVI     B,6
        CALL    SCSI$SEND$CMD
        JNZ     TUR$FAIL$ONE
        CALL    SCSI$WAIT$REQ$PHASE
        JNZ     TUR$FAIL$ONE
        LDA     LASTPHASE
        CPI     PHASE$STATUS
        JNZ     TUR$FAIL$ONE
        CALL    SCSI$GET$STATUS
        LDA     SCSISTAT
        ORA     A
        JZ      TUR$READY
        CALL    SCSI$REQUEST$SENSE
TUR$FAIL$ONE:
        CALL    BIG$DELAY
        LXI     H,TURTRIES
        DCR     M
        JNZ     TUR$LOOP
        MVI     A,1
        RET
TUR$READY:
        XRA     A
        RET

BUILD$TUR$CDB:
        LXI     H,CDB
        XRA     A               ; TEST UNIT READY = 00H
        MOV     M,A
        INX     H
        MOV     M,A
        INX     H
        MOV     M,A
        INX     H
        MOV     M,A
        INX     H
        MOV     M,A
        INX     H
        MOV     M,A
        RET

BUILD$SENSE$CDB:
        LXI     H,CDB
        MVI     M,003H          ; REQUEST SENSE
        INX     H
        XRA     A
        MOV     M,A
        INX     H
        MOV     M,A
        INX     H
        MOV     M,A
        INX     H
        MVI     M,18            ; 18 bytes
        INX     H
        MOV     M,A
        RET

SCSI$REQUEST$SENSE:
        CALL    BUILD$SENSE$CDB
        CALL    SCSI$SELECT
        RNZ
        LXI     H,CDB
        MVI     B,6
        CALL    SCSI$SEND$CMD
        RNZ
        CALL    SCSI$WAIT$REQ$PHASE
        RNZ
        LDA     LASTPHASE
        CPI     PHASE$DATAIN
        JZ      SRS$DATA
        CPI     PHASE$STATUS
        JZ      SRS$STATUS
        MVI     A,1
        RET
SRS$DATA:
        LXI     H,SENSEBUF
        LXI     D,18
        CALL    SCSI$RECV$DATA
        RNZ
SRS$STATUS:
        CALL    SCSI$GET$STATUS
        XRA     A
        RET

;----------- DELAY ROUTINES -------------------------------------

DELAY:
        LXI     B,2000H
DLY$1:
        DCX     B
        MOV     A,B
        ORA     C
        JNZ     DLY$1
        RET

BIG$DELAY:
        MVI     B,20
BD$1:
        PUSH    B
        CALL    DELAY
        POP     B
        DCR     B
        JNZ     BD$1
        RET

ZCI:	IN	KEYSTAT       ;For local debugging
	ANI	02H
	JZ	ZCI
	IN	KEYIN
	ANI	7FH
	RET

ZCO:    IN	CRTSTAT
	ANI	04H
	JZ	ZCO
	MOV	A,C
	OUT	KEYOUT
	RET

;--------------------------------------------------------------------------------

MSG$INIT$ERR	DB	'Initilization of IDE drive failed.',BELL,CR,LF,0
MSG$SCSI$INIT$ERR:
                DB      'Initialization of SCSI drive C: failed.',BELL,CR,LF,0

LBA0:       DB  0H
LBA1:       DB  0H
LBA2:       DB  0H
SECOFFL:    DB  0H
SECOFFH:    DB  0H
CDB:        DS  6
SCSISTAT:   DB  0H
SCSIMSG:    DB  0H
LASTPHASE:  DB  0H
TURTRIES:   DB  0H
SENSEBUF:   DS  18
SECBUF:     DS  512
XFERPTR:    DW  0H

ERFLG:	    DB	0H

	END
