;
;=============================================================================
;   IMM DISK DRIVER
;=============================================================================
;
; PARALLEL PORT INTERFACE FOR SCSI DISK DEVICES USING A PARALLEL PORT
; ADAPTER.  PRIMARILY TARGETS PARALLEL PORT IOMEGA ZIP DRIVES.
;
; INTENDED TO CO-EXIST WITH LPT DRIVER.
;
; CREATED BY WAYNE WARTHEN FOR ROMWBW HBIOS.
; MUCH OF THE CODE IS DERIVED FROM LINUX AND FUZIX (ALAN COX).
; - https://github.com/EtchedPixels/FUZIX
; - https://github.com/torvalds/linux
;
; 05/23/2023 WBW - INITIAL RELEASE
; 05/26/2023 WBW - CLEAN UP, LED ACTIVITY
; 05/27/2023 WBW - ADDED SPP MODE
; 06/06/2023 WBW - OPTIMIZE BLOCK READ AND WRITE
;
;=============================================================================
;
;  IBM PC STANDARD PARALLEL PORT (SPP):
;  - NHYODYNE PRINT MODULE
;
;  PORT 0 (OUTPUT):
;
;	D7	D6	D5	D4	D3	D2	D1	D0
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;     | PD7   | PD6   | PD5   | PD4   | PD3   | PD2   | PD1   | PD0   |
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;
;  PORT 1 (INPUT):
;
;	D7	D6	D5	D4	D3	D2	D1	D0
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;     | /BUSY | /ACK  | POUT  | SEL   | /ERR  | 0     | 0     | 0     |
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;
;  PORT 2 (OUTPUT):
;
;	D7	D6	D5	D4	D3	D2	D1	D0
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;     | STAT1 | STAT0 | ENBL  | PINT  | SEL   | RES   | LF    | STB   |
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;
;=============================================================================
;
;  MG014 STYLE INTERFACE:
;  - RCBUS MG014 MODULE
;
;  PORT 0 (OUTPUT):
;
;	D7	D6	D5	D4	D3	D2	D1	D0
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;     | PD7   | PD6   | PD5   | PD4   | PD3   | PD2   | PD1   | PD0   |
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;
;  PORT 1 (INPUT):
;
;	D7	D6	D5	D4	D3	D2	D1	D0
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;     |	      |	      |	      | /ERR  | SEL   | POUT  | BUSY  | /ACK  |
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;
;  PORT 2 (OUTPUT):
;
;	D7	D6	D5	D4	D3	D2	D1	D0
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;     | LED   |	      |	      |	      | /SEL  | /RES  | /LF   | /STB  |
;     +-------+-------+-------+-------+-------+-------+-------+-------+
;
;=============================================================================
;
; TODO:
;
; NOTES:
;
; - THIS DRIVER IS FOR THE ZIP DRIVE IMM INTERFACE.  IT WILL SIMPLY
;   FAIL TO EVEN RECOGNIZE A ZIP DRIVE WITH THE OLDER PPA INTERFACE.
;   THERE DOES NOT SEEM TO BE A WAY TO VISUALLY DETERMINE IF A ZIP
;   DRIVE IS PPA OR IMM.  SIGH.
;
; - THIS DRIVER OPERATES USES NIBBLE READ MODE.  ALTHOUGH THE 8255
;   (MG014) CAN READ OR WRITE TO PORT A (DATA), IT "GLITCHES" WHEN
;   THE MODE IS CHANGED CAUSING THE CONTROL LINES TO CHANGE AND
;   BREAKS THE PROTOCOL.  I SUSPECT THE MBC SPP CAN SUPPORT FULL BYTE
;   MODE, (PS2 STYLE), BUT I HAVE NOT ATTEMPTED IT.
;
; - RELATIVE TO ABOVE, THIS BEAST IS SLOW.  IN ADDITION TO THE
;   NIBBLE MODE READS, THE MG014 ASSIGNS SIGNALS DIFFERENTLY THAN
;   THE STANDARD IBM PARALLEL PORT WHICH NECESSITATES A BUNCH OF EXTRA
;   BIT FIDDLING ON EVERY READ.
;
; - SOME OF THE DATA TRANSFERS HAVE NO BUFFER OVERRUN CHECKS.  IT IS
;   ASSUMED SCSI DEVICES WILL SEND/REQUEST THE EXPECTED NUMBER OF BYTES.
;
; IMM PORT OFFSETS
;
IMM_IODATA	.EQU	0		; PORT A, DATA, OUT
IMM_IOSTAT	.EQU	1		; PORT B, STATUS, IN
IMM_IOCTRL	.EQU	2		; PORT C, CTRL, OUT
IMM_IOSETUP	.EQU	3		; PPI SETUP
;
; SCSI UNIT IDS
;
IMM_SELF	.EQU	7
IMM_TGT		.EQU	6
;
; IMM DEVICE STATUS
;
IMM_STOK	.EQU	0
IMM_STNOMEDIA	.EQU	-1
IMM_STCMDERR	.EQU	-2
IMM_STIOERR	.EQU	-3
IMM_STTO	.EQU	-4
IMM_STNOTSUP	.EQU	-5
;
; IMM DEVICE CONFIGURATION
;
IMM_CFGSIZ	.EQU	12		; SIZE OF CFG TBL ENTRIES
;
; PER DEVICE DATA OFFSETS IN CONFIG TABLE ENTRIES
;
IMM_DEV		.EQU	0		; OFFSET OF DEVICE NUMBER (BYTE)
IMM_MODE	.EQU	1		; OPERATION MODE: IMM MODE (BYTE)
IMM_STAT	.EQU	2		; LAST STATUS (BYTE)
IMM_IOBASE	.EQU	3		; IO BASE ADDRESS (BYTE)
IMM_MEDCAP	.EQU	4		; MEDIA CAPACITY (DWORD)
IMM_LBA		.EQU	8		; OFFSET OF LBA (DWORD)
;
; MACROS
;
#DEFINE IMM_WCTL(VAL)	LD A,VAL \ CALL IMM_WRITECTRL
#DEFINE IMM_WDATA(VAL)	LD A,VAL \ CALL IMM_WRITEDATA
;
; INCLUDE MG014 NIBBLE MAP FOR MG014 MODE
;
#IF (IMMMODE == IMMMODE_MG014)
  #DEFINE MG014_MAP
#ENDIF
;
;=============================================================================
; INITIALIZATION ENTRY POINT
;=============================================================================
;
IMM_INIT:
	LD	IY,IMM_CFG		; POINT TO START OF CONFIG TABLE
;
IMM_INIT1:
	LD	A,(IY)			; LOAD FIRST BYTE TO CHECK FOR END
	CP	$FF			; CHECK FOR END OF TABLE VALUE
	JR	NZ,IMM_INIT2		; IF NOT END OF TABLE, CONTINUE
	XOR	A			; SIGNAL SUCCESS
	RET				; AND RETURN
;
IMM_INIT2:
	CALL	NEWLINE			; FORMATTING
	PRTS("IMM:$")			; DRIVER LABEL
;
	PRTS(" IO=0x$")			; LABEL FOR IO ADDRESS
	LD	A,(IY+IMM_IOBASE)	; GET IO BASE ADDRES
	CALL	PRTHEXBYTE		; DISPLAY IT
;
	PRTS(" MODE=$")			; LABEL FOR MODE
	LD	A,(IY+IMM_MODE)		; GET MODE BITS
	LD	HL,IMM_STR_MODE_MAP
	ADD	A,A
	CALL	ADDHLA
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	CALL	WRITESTR
;
	; CHECK FOR HARDWARE PRESENCE
	CALL	IMM_DETECT		; PROBE FOR INTERFACE
	JR	Z,IMM_INIT4		; IF FOUND, CONTINUE
	CALL	PC_SPACE		; FORMATTING
	LD	DE,IMM_STR_NOHW		; NO IMM MESSAGE
	CALL	WRITESTR		; DISPLAY IT
	JR	IMM_INIT6		; SKIP CFG ENTRY
;
IMM_INIT4:
	; UPDATE DRIVER RELATIVE UNIT NUMBER IN CONFIG TABLE
	LD	A,(IMM_DEVNUM)		; GET NEXT UNIT NUM TO ASSIGN
	LD	(IY+IMM_DEV),A		; UPDATE IT
	INC	A			; BUMP TO NEXT UNIT NUM TO ASSIGN
	LD	(IMM_DEVNUM),A		; SAVE IT
;
	; ADD UNIT TO GLOBAL DISK UNIT TABLE
	LD	BC,IMM_FNTBL		; BC := FUNC TABLE ADR
	PUSH	IY			; CFG ENTRY POINTER
	POP	DE			; COPY TO DE
	CALL	DIO_ADDENT		; ADD ENTRY TO GLOBAL DISK DEV TABLE
;
	CALL	IMM_RESET		; RESET/INIT THE INTERFACE
#IF (IMMTRACE <= 1)
	CALL	NZ,IMM_PRTSTAT
#ENDIF
	JR	NZ,IMM_INIT6
;
	; START PRINTING DEVICE INFO
	CALL	IMM_PRTPREFIX		; PRINT DEVICE PREFIX
;
IMM_INIT5:
	; PRINT STORAGE CAPACITY (BLOCK COUNT)
	PRTS(" BLOCKS=0x$")		; PRINT FIELD LABEL
	LD	A,IMM_MEDCAP		; OFFSET TO CAPACITY FIELD
	CALL	LDHLIYA			; HL := IY + A, REG A TRASHED
	CALL	LD32			; GET THE CAPACITY VALUE
	CALL	PRTHEX32		; PRINT HEX VALUE
;
	; PRINT STORAGE SIZE IN MB
	PRTS(" SIZE=$")			; PRINT FIELD LABEL
	LD	B,11			; 11 BIT SHIFT TO CONVERT BLOCKS --> MB
	CALL	SRL32			; RIGHT SHIFT
	CALL	PRTDEC32		; PRINT DWORD IN DECIMAL
	PRTS("MB$")			; PRINT SUFFIX
;
IMM_INIT6:
	LD	DE,IMM_CFGSIZ		; SIZE OF CFG TABLE ENTRY
	ADD	IY,DE			; BUMP POINTER
	JP	IMM_INIT1		; AND LOOP
;
;----------------------------------------------------------------------
; PROBE FOR IMM HARDWARE
;----------------------------------------------------------------------
;
; ON RETURN, ZF SET INDICATES HARDWARE FOUND
;
IMM_DETECT:
#IF (IMMTRACE >= 3)
	PRTS("\r\nDETECT:$")
#ENDIF
;
#IF (IMMMODE == IMMMODE_MG014)
	; INITIALIZE 8255
	LD	A,(IY+IMM_IOBASE)	; BASE PORT
	ADD	A,IMM_IOSETUP		; BUMP TO SETUP PORT
	LD	C,A			; MOVE TO C FOR I/O
	LD	A,$82			; CONFIG A OUT, B IN, C OUT
	EZ80_IO
	OUT	(C),A			; DO IT
	CALL	DELAY			; BRIEF DELAY FOR GOOD MEASURE
#ENDIF
;
	; ATTEMPT TO ESTABLISH A CONNECTION TO THE IMM DEVICE AND
	; ISSUE A SCSI BUS RESET.  WE DON'T KNOW IF DEVICE IS THERE
	; YET.  THIS IS DONE BLIND ASSUMING IT IS THERE.
	CALL	IMM_DISCONNECT
	CALL	IMM_CONNECT
	CALL	IMM_RESETPULSE		; ISSUE A SCSI BUS RESET
	LD	DE,62			; WAIT A BIT
	CALL	VDELAY
;
	; USE AN ABBREVIATED VERSION OF SELECT PROCESSING TO
	; CHECK IF DEVICE EXISTS.
	IMM_WCTL($0C)
	CALL	IMM_READSTATUS
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
;
	; READY FOR SELECT?
	AND	$08
	CP	$00
	JR	NZ,IMM_DETECT1
;
	IMM_WCTL($04)
	LD	A,$80 | (1 << IMM_TGT)
	CALL	IMM_WRITEDATA
	IMM_WCTL($0C)
	IMM_WCTL($0D)
	CALL	DELAY
	CALL	IMM_READSTATUS
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
;
	; DID SELECT SUCCEED?
	AND	$08
	CP	$08
;
IMM_DETECT1:
	PUSH	AF
	CALL	IMM_DISCONNECT
	POP	AF
	RET
;
;=============================================================================
; DRIVER FUNCTION TABLE
;=============================================================================
;
IMM_FNTBL:
	.DW	IMM_STATUS
	.DW	IMM_RESET
	.DW	IMM_SEEK
	.DW	IMM_READ
	.DW	IMM_WRITE
	.DW	IMM_VERIFY
	.DW	IMM_FORMAT
	.DW	IMM_DEVICE
	.DW	IMM_MEDIA
	.DW	IMM_DEFMED
	.DW	IMM_CAP
	.DW	IMM_GEOM
#IF (($ - IMM_FNTBL) != (DIO_FNCNT * 2))
	.ECHO	"*** INVALID IMM FUNCTION TABLE ***\n"
#ENDIF
;
IMM_VERIFY:
IMM_FORMAT:
IMM_DEFMED:
	SYSCHKERR(ERR_NOTIMPL)		; NOT IMPLEMENTED
	RET
;
;
;
IMM_READ:
	CALL	HB_DSKREAD		; HOOK DISK READ CONTROLLER
	LD	A,SCSI_CMD_READ		; SETUP SCSI READ
	LD	(IMM_CMD_RW),A		; AND SAVE IT IN SCSI CMD
	JP	IMM_IO			; DO THE I/O
;
;
;
IMM_WRITE:
	CALL	HB_DSKWRITE		; HOOK DISK WRITE CONTROLLER
	LD	A,SCSI_CMD_WRITE	; SETUP SCSI WRITE
	LD	(IMM_CMD_RW),A		; AND SAVE IT IN SCSI CMD
	JP	IMM_IO			; DO THE I/O
;
;
;
IMM_IO:
	PUSH	HL
	CALL	IMM_CHKERR		; CHECK FOR ERR STATUS AND RESET IF SO
	POP	HL
	JR	NZ,IMM_IO3		; BAIL OUT ON ERROR
;
	LD	(IMM_DSKBUF),HL		; SAVE DISK BUFFER ADDRESS
;
;;;#IF (DSKYENABLE)
;;; #IF (DSKYDSKACT)
	LD	A,IMM_LBA
	CALL	LDHLIYA
	CALL	HB_DSKACT		; SHOW ACTIVITY
;;;  #ENDIF
;;;#ENDIF
;
	; SETUP LBA
	; 3 BYTES, LITTLE ENDIAN -> BIG ENDIAN
	LD	HL,IMM_CMD_RW+1		; START OF LBA FIELD IN CDB (MSB)
	LD	A,(IY+IMM_LBA+2)	; THIRD BYTE OF LBA FIELD IN CFG (MSB)
	LD	(HL),A
	INC	HL
	LD	A,(IY+IMM_LBA+1)
	LD	(HL),A
	INC	HL
	LD	A,(IY+IMM_LBA+0)
	LD	(HL),A
	INC	HL
	; DO SCSI IO
	LD	DE,(IMM_DSKBUF)		; DISK BUFFER TO DE
	LD	A,1			; BLOCK I/O, ONE SECTOR
	LD	HL,IMM_CMD_RW		; POINT TO READ/WRITE CMD TEMPLATE
	CALL	IMM_RUNCMD		; RUN THE SCSI ENGINE
	CALL	Z,IMM_CHKCMD		; IF EXIT OK, CHECK SCSI RESULTS
	JR	NZ,IMM_IO2		; IF ERROR, SKIP INCREMENT
	; INCREMENT LBA
	LD	A,IMM_LBA		; LBA OFFSET
	CALL	LDHLIYA			; HL := IY + A, REG A TRASHED
	CALL	INC32HL			; INCREMENT THE VALUE
	; INCREMENT DMA
	LD	HL,IMM_DSKBUF+1		; POINT TO MSB OF BUFFER ADR
	INC	(HL)			; BUMP DMA BY
	INC	(HL)			; ... 512 BYTES
	XOR	A			; SIGNAL SUCCESS
;
IMM_IO2:
IMM_IO3:
	LD	HL,(IMM_DSKBUF)		; CURRENT DMA TO HL
	OR	A			; SET FLAGS BASED ON RETURN CODE
	RET	Z			; RETURN IF SUCCESS
	LD	A,ERR_IO		; SIGNAL IO ERROR
	OR	A			; SET FLAGS
	RET				; AND DONE
;
;
;
IMM_STATUS:
	; RETURN UNIT STATUS
	LD	A,(IY+IMM_STAT)		; GET STATUS OF SELECTED DEVICE
	OR	A			; SET FLAGS
	RET				; AND RETURN
;
;
;
IMM_RESET:
	JP	IMM_INITDEV		; JUST (RE)INIT DEVICE
;
;
;
IMM_DEVICE:
	LD	D,DIODEV_IMM		; D := DEVICE TYPE
	LD	E,(IY+IMM_DEV)		; E := PHYSICAL DEVICE NUMBER
	LD	C,%01111001		; C := REMOVABLE HARD DISK
	LD	H,(IY+IMM_MODE)		; H := MODE
	LD	L,(IY+IMM_IOBASE)	; L := BASE I/O ADDRESS
	XOR	A			; SIGNAL SUCCESS
	RET
;
; IMM_GETMED
;
IMM_MEDIA:
	LD	A,E			; GET FLAGS
	OR	A			; SET FLAGS
	JR	Z,IMM_MEDIA1		; JUST REPORT CURRENT STATUS AND MEDIA
;
	CALL	IMM_RESET		; RESET INCLUDES MEDIA CHECK
;
IMM_MEDIA1:
	LD	A,(IY+IMM_STAT)		; GET STATUS
	OR	A			; SET FLAGS
	LD	D,0			; NO MEDIA CHANGE DETECTED
	LD	E,MID_HD		; ASSUME WE ARE OK
	RET	Z			; RETURN IF GOOD INIT
	LD	E,MID_NONE		; SIGNAL NO MEDIA
	LD	A,ERR_NOMEDIA		; NO MEDIA ERROR
	OR	A			; SET FLAGS
	RET				; AND RETURN
;
;
;
IMM_SEEK:
	BIT	7,D			; CHECK FOR LBA FLAG
	CALL	Z,HB_CHS2LBA		; CLEAR MEANS CHS, CONVERT TO LBA
	RES	7,D			; CLEAR FLAG REGARDLESS (DOES NO HARM IF ALREADY LBA)
	LD	(IY+IMM_LBA+0),L	; SAVE NEW LBA
	LD	(IY+IMM_LBA+1),H	; ...
	LD	(IY+IMM_LBA+2),E	; ...
	LD	(IY+IMM_LBA+3),D	; ...
	XOR	A			; SIGNAL SUCCESS
	RET				; AND RETURN
;
;
;
IMM_CAP:
	LD	A,(IY+IMM_STAT)		; GET STATUS
	PUSH	AF			; SAVE IT
	LD	A,IMM_MEDCAP		; OFFSET TO CAPACITY FIELD
	CALL	LDHLIYA			; HL := IY + A, REG A TRASHED
	CALL	LD32			; GET THE CURRENT CAPACITY INTO DE:HL
	LD	BC,512			; 512 BYTES PER BLOCK
	POP	AF			; RECOVER STATUS
	OR	A			; SET FLAGS
	RET
;
;
;
IMM_GEOM:
	; FOR LBA, WE SIMULATE CHS ACCESS USING 16 HEADS AND 16 SECTORS
	; RETURN HS:CC -> DE:HL, SET HIGH BIT OF D TO INDICATE LBA CAPABLE
	CALL	IMM_CAP			; GET TOTAL BLOCKS IN DE:HL, BLOCK SIZE TO BC
	LD	L,H			; DIVIDE BY 256 FOR # TRACKS
	LD	H,E			; ... HIGH BYTE DISCARDED, RESULT IN HL
	LD	D,16 | $80		; HEADS / CYL = 16, SET LBA CAPABILITY BIT
	LD	E,16			; SECTORS / TRACK = 16
	RET				; DONE, A STILL HAS IMM_CAP STATUS
;
;=============================================================================
; FUNCTION SUPPORT ROUTINES
;=============================================================================
;
; OUTPUT BYTE IN A TO THE DATA PORT
;
IMM_WRITEDATA:
	LD	C,(IY+IMM_IOBASE)	; DATA PORT IS AT IOBASE
	EZ80_IO
	OUT	(C),A			; WRITE THE BYTE
	;CALL	DELAY			; IS THIS NEEDED???
	RET				; DONE
;
;
;
IMM_WRITECTRL:
	; IBM PC INVERTS ALL BUT C2 ON THE BUS, MG014 DOES NOT.
	; BELOW TRANSLATES FROM IBM -> MG014.	IT ALSO INVERTS THE
	; MG014 LED SIMPLY TO MAKE IT EASY TO KEEP LED ON DURING
	; ALL ACTIVITY.
;
#IF (IMMMODE == IMMMODE_MG014
	XOR	$0B | $80		; HIGH BIT IS MG014 LED
#ENDIF
;#IF (IMMMODE == IMMMODE_SPP
;	AND	%00001111
;	OR	%11000000
;#ENDIF
	LD	C,(IY+IMM_IOBASE)	; GET BASE IO ADDRESS
	INC	C			; BUMP TO CONTROL PORT
	INC	C
	EZ80_IO
	OUT	(C),A			; WRITE TO CONTROL PORT
	;CALL	DELAY			; IS THIS NEEDED?
	RET				; DONE
;
; READ THE PARALLEL PORT INPUT LINES (STATUS) AND MAP SIGNALS FROM
; MG014 TO IBM STANDARD.  NOTE POLARITY CHANGE REQUIRED FOR BUSY.
;
; 	MG014		IBM PC (SPP)
;	--------	--------
;	0: /ACK		6: /ACK
;	1: BUSY		7: /BUSY
;	2: POUT		5: POUT
;	3: SEL		4: SEL
;	4: /ERR		3: /ERR
;
IMM_READSTATUS:
	LD	C,(IY+IMM_IOBASE)	; IOBASE TO C
	INC	C			; BUMP TO STATUS PORT
	EZ80_IO
	IN	A,(C)			; READ IT
;
#IF (IMMMODE == IMMMODE_MG014)
;
	; SHUFFLE BITS ON MG014
	LD	C,0			; INIT RESULT
	BIT	0,A			; 0: /ACK
	JR	Z,IMM_READSTATUS1
	SET	6,C			; 6: /ACK
IMM_READSTATUS1:
	BIT	1,A			; 1: BUSY
	JR	NZ,IMM_READSTATUS2	; POLARITY CHANGE!
	SET	7,C			; 7: /BUSY
IMM_READSTATUS2:
	BIT	2,A			; 2: POUT
	JR	Z,IMM_READSTATUS3
	SET	5,C			; 5: POUT
IMM_READSTATUS3:
	BIT	3,A			; 3: SEL
	JR	Z,IMM_READSTATUS4
	SET	4,C			; 4: SEL
IMM_READSTATUS4:
	BIT	4,A			; 4: /ERR
	JR	Z,IMM_READSTATUS5
	SET	3,C			; 3: /ERR
IMM_READSTATUS5:
	LD	A,C			; RESULT TO A
;
#ENDIF
;
	RET
;
; SIGNAL SEQUENCE TO CONNECT/DISCONNECT
; VALUE IN A IS WRITTEN TO DATA PORT DURING SEQUENCE
;
IMM_CPP:
	PUSH	AF
	IMM_WCTL($0C)
	IMM_WDATA($AA)
	IMM_WDATA($55)
	IMM_WDATA($00)
	IMM_WDATA($FF)
	CALL	IMM_READSTATUS
	AND	$B8
	LD	(IMM_S1),A
	IMM_WDATA($87)
	CALL	IMM_READSTATUS
	AND	$B8
	LD	(IMM_S2),A
	IMM_WDATA($78)
	CALL	IMM_READSTATUS
	AND	$38
	LD	(IMM_S3),A
	POP	AF
	CALL	IMM_WRITEDATA
	IMM_WCTL($0C)
	IMM_WCTL($0D)
	IMM_WCTL($0C)
	IMM_WDATA($FF)
;
	; CONNECT: S1=$B8 S2=$18 S3=$30
	; DISCONNECT: S1=$B8 S2=$18 S3=$38
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nCPP: S1=$")
	LD	A,(IMM_S1)
	CALL	PRTHEXBYTE
	PRTS(" S2=$")
	LD	A,(IMM_S2)
	CALL	PRTHEXBYTE
	PRTS(" S3=$")
	LD	A,(IMM_S3)
	CALL	PRTHEXBYTE
#ENDIF
;
	XOR	A		; ASSUME SUCCESS FOR NOW
	RET
;
IMM_S1	.DB	0
IMM_S2	.DB	0
IMM_S3	.DB	0
;
; SEQUENCE TO CONNECT TO DEVICE ON PARALLEL PORT BUS.
;
IMM_CONNECT:
	LD	A,$E0
	CALL	IMM_CPP
	LD	A,$30
	CALL	IMM_CPP
	LD	A,$E0
	CALL	IMM_CPP
	RET
;
; SEQUENCE TO DISCONNECT FROM DEVICE ON PARALLEL PORT BUS.
; THE FINAL IMM_WRITECTRL IS ONLY TO TURN OFF THE MG014 STATUS LED.
;
IMM_DISCONNECT:
	LD	A,$30
	CALL	IMM_CPP
;
	; TURNS OFF MG014 LED
	IMM_WCTL($8C)
;
	RET
;
; INITIATE A SCSI BUS RESET.
;
IMM_RESETPULSE:
	IMM_WCTL($04)
	IMM_WDATA($40)
	CALL	DELAY		; 16 US, IDEALLY, 1 US
	IMM_WCTL($0C)
	IMM_WCTL($0D)
	CALL	DELAY		; 48 US, IDEALLY, 50 US
	CALL	DELAY
	CALL	DELAY
	IMM_WCTL($0C)
	IMM_WCTL($04)
	RET
;
; SCSI SELECT PROCESS
;
IMM_SELECT:
#IF (IMMTRACE >= 3)
	PRTS("\r\nSELECT: $")
#ENDIF
	IMM_WCTL($0C)
;
	LD	B,0			; TIMEOUT COUNTER
IMM_SELECT1:
	CALL	IMM_READSTATUS
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
	AND	$08
	JR	Z,IMM_SELECT2		; IF CLEAR, MOVE ON
	DJNZ	IMM_SELECT1
	JP	IMM_CMD_TIMEOUT		; TIMEOUT
;
IMM_SELECT2:
	IMM_WCTL($04)
	; PLACE HOST AND TARGET BIT ON DATA BUS
	LD	A,$80 | (1 << IMM_TGT)
	CALL	IMM_WRITEDATA
	IMM_WCTL($0C)
	IMM_WCTL($0D)
;
	LD	B,0			; TIMEOUT COUNTER
IMM_SELECT3:
	CALL	IMM_READSTATUS
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
	AND	$08
	JR	NZ,IMM_SELECT4		; IF SET, MOVE ON
	DJNZ	IMM_SELECT3
	JP	IMM_CMD_TIMEOUT		; TIMEOUT
;
IMM_SELECT4:
	IMM_WCTL($0C)
;
	XOR	A
	RET
;
; SEND SCSI CMD BYTE STRING.  AT ENTRY, HL POINTS TO START OF
; COMMAND BYTES.  THE LENGTH OF THE COMMAND STRING MUST PRECEED
; THE COMMAND BYTES (HL - 1).
;
; NOTE THAT DATA IS SENT AS BYTE PAIRS!  EACH LOOP SENDS 2 BYTES.
; DATA OUTPOUT IS BURSTED (NO CHECK FOR BUSY).  SEEMS TO WORK FINE.
;
IMM_SENDCMD:
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nSENDCMD:$")
#ENDIF
;
	DEC	HL		; BACKUP TO LENGTH BYTE
	LD	B,(HL)		; PUT IN B FOR LOOP COUNTER
;
#IF (IMMTRACE >= 3)
	LD	A,B
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
	PRTS(" BYTES$")
#ENDIF
;
	INC	HL		; BACK TO FIRST CMD BYTE
IMM_SENDCMD1:
	IMM_WCTL($04)
	LD	A,(HL)		; LOAD CMD BYTE
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
;
	CALL	IMM_WRITEDATA	; PUT IT ON THE BUS
	INC	HL		; BUMP TO NEXT BYTE
	DEC	B		; DEC LOOP COUNTER
	IMM_WCTL($05)
	LD	A,(HL)		; LOAD CMD BYTE
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
;
	CALL	IMM_WRITEDATA	; PUT IT ON THE BUS
	INC	HL		; BUMP TO NEXT BYTE
	IMM_WCTL($00)
	DJNZ	IMM_SENDCMD1	; LOOP TILL DONE
;
IMM_SENDCMD2:
	IMM_WCTL($04)
;
	RET
;
; WAIT FOR SCSI BUS TO BECOME READY WITH A TIMEOUT.
;
IMM_WAITLOOP:
	CALL	IMM_READSTATUS
	BIT	7,A
	RET	NZ			; DONE, STATUS IN A
	DEC	HL
	LD	A,H
	OR	L
	RET	Z			; TIMEOUT
	JR	IMM_WAITLOOP
;
IMM_WAIT:
	LD	HL,500			; GOOD VALUE???
	IMM_WCTL($0C)
	CALL	IMM_WAITLOOP
	JP	Z,IMM_CMD_TIMEOUT	; HANDLE TIMEOUT
	PUSH	AF
	IMM_WCTL($04)
	POP	AF
	AND	$B8
	RET				; RETURN W/ RESULT IN A
;
; MAX OBSERVED WAITLOOP ITERATIONS IS $0116B3 @ 7.3728 MHZ ON MG014
; MAX OBSERVED WAITLOOP ITERATIONS IS $028EFE @ 8.000 MHZ ON MBC SPP
;
IMM_LONGWAIT:
	LD	A,(CB_CPUMHZ)		; LOAD CPU SPEED IN MHZ
	SRL	A			; DIVIDE BY 2, GOOD ENOUGH
	LD	B,A			; USE FOR OUTER LOOP COUNT
	IMM_WCTL($0C)
IMM_LONGWAIT1:
	LD	HL,0
	CALL	IMM_WAITLOOP
	JR	NZ,IMM_LONGWAIT2	; HANDLE SUCCESS
	DJNZ	IMM_LONGWAIT1		; LOOP TILL COUNTER EXHAUSTED
	JP	IMM_CMD_TIMEOUT		; HANDLE TIMEOUT
;
IMM_LONGWAIT2:
	PUSH	AF
	IMM_WCTL($04)
;
#IF 0
	CALL	PC_GT
	LD	A,B
	CALL	PRTHEXBYTE
	CALL	PC_COLON
	CALL	PRTHEXWORDHL
#ENDIF
;
	POP	AF
	AND	$B8
	RET				; RETURN W/ RESULT IN A
;
; PEROFRM SCSI BUS NEGOTIATION.  REQURIED PRIOR TO DATA READS.
;
IMM_NEGOTIATE:
#IF (IMMTRACE >= 3)
	PRTS("\r\nNEGO: $")
#ENDIF
	IMM_WCTL($04)
	CALL	DELAY			; 16 US, IDEALLY 5 US
	IMM_WDATA($00)
	LD	DE,7			; 112 US, IDEALLY 100 US
	CALL	VDELAY
	IMM_WCTL($06)
	CALL	DELAY			; 16 US, IDEALLY 5 US
	CALL	IMM_READSTATUS
	PUSH	AF			; SAVE RESULT
	CALL	DELAY			; 16 US, IDEALLY 5 US
	IMM_WCTL($07)
	CALL	DELAY			; 16 US, IDEALLY 5 US
	IMM_WCTL($06)
;
	POP	AF
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
;
	AND	$20
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PC_GT
	CALL	PRTHEXBYTE
#ENDIF
;
	CP	$20			; $20 MEANS DATA READY
	JP	NZ,IMM_CMD_IOERR
	RET
;
; GET A BYTE OF DATA FROM THE SCSI DEVICE.  THIS IS A NIBBLE READ.
; BYTE RETURNED IN A.
;
IMM_GETBYTE:
	CALL	IMM_WAIT
	IMM_WCTL($06)
	CALL	IMM_READSTATUS
	AND	$F0
	RRCA
	RRCA
	RRCA
	RRCA
	PUSH 	AF
	IMM_WCTL($05)
	CALL	IMM_READSTATUS
	AND	$F0
	POP	HL
	OR	H
	PUSH	AF
	IMM_WCTL($04)
	POP	AF
	RET
;
; GET A CHUNK OF DATA FROM SCSI BUS.  THIS IS SPECIFICALLY FOR
; READ PHASE.  IF TRANSFER MODE IS NON-ZERO, THEN A BLOCK (512 BYTES)
; OF DATA WILL BE READ.  OTHERWISE, DATA IS WRITTEN AS
; LONG AS SCSI DEVICE WANTS TO CONTINUE RECEIVING (NO OVERRUN
; CHECK IN THIS CASE).
;
; THIS IS A NIBBLE READ.
;
; DE=BUFFER
; A=TRANSFER MODE (0=VARIABLE, 1=BLOCK)
;
IMM_GETDATA:
	; BRANCH TO CORRECT ROUTINE
	OR	A
	JR	NZ,IMM_GETBLOCK		; DO BLOCK READ
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nGETDATA:$")
#ENDIF
;
IMM_GETDATA1:
	PUSH	HL			; SAVE BYTE COUNTER
	CALL	IMM_WAIT		; WAIT FOR BUS READY
	POP	HL			; RESTORE BYTE COUNTER
	CP	$98			; CHECK FOR READ PHASE
	JR	NZ,IMM_GETDATA2		; IF NOT, ASSUME WE ARE DONE
	IMM_WCTL($06)
	CALL	IMM_READSTATUS		; GET FIRST NIBBLE
	AND	$F0			; ISOLATE BITS
	RRCA				; AND SHIFT TO LOW NIBBLE
	RRCA
	RRCA
	RRCA
	PUSH 	AF			; SAVE WORKING VALUE
	IMM_WCTL($05)
	CALL	IMM_READSTATUS		; GET SECOND NIBBLE
	AND	$F0			; ISOLATE BITS
	POP	BC			; RECOVER LOW NIBBLE
	OR	B			; COMBINE
	LD	(DE),A			; AND SAVE THE FULL BYTE VALUE
	INC	DE			; NEXT BUFFER POS
	INC	HL			; INCREMENT BYTES COUNTER
	IMM_WCTL($04)
	JR	IMM_GETDATA1		; LOOP TILL DONE
;
IMM_GETDATA2:
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXWORDHL
	PRTS(" BYTES$")
#ENDIF
;
	IMM_WCTL($0C)
	RET
;
IMM_GETBLOCK:
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nGETBLK:$")
#ENDIF
;
	IMM_WCTL($04)
	LD	B,0			; LOOP COUNTER
	EXX				; SWITCH TO ALT REGS
	EX	AF,AF'			; SWITCH TO ALT AF
	; SAVE ALT REGS
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
	; C: PORT C	
	LD	A,(IY+IMM_IOBASE)	; BASE PORT
	INC	A			; STATUS PORT
	LD	(IMM_GETBLOCK_A),A	; FILL IN
	LD	(IMM_GETBLOCK_B),A	; ... DYNAMIC BITS OF CODE
	INC	A			; CONTROL PORT
	LD	C,A			; ... TO C
#IF (IMMMODE == IMMMODE_MG014)
	; DE: CLOCK VALUES
	LD	D,$06 ^ ($0B | $80)
	LD	E,$05 ^ ($0B | $80)
	; HL: STATMAP
	LD	H,MG014_STATMAPLO >> 8
#ENDIF
#IF (IMMMODE == IMMMODE_SPP)
	; DE: CLOCK VALUES
	LD	D,$06
	LD	E,$05
#ENDIF
	EXX				; SWITCH TO PRI REGS
	EX	AF,AF'			; SWITCH TO PRI AF
	CALL	IMM_GETBLOCK1		; LOOP TWICE
	CALL	IMM_GETBLOCK1		; ... FOR 512 BYTES
	; RESTORE ALT REGS
	EXX				; SWITCH TO ALT REGS
	EX	AF,AF'			; SWITCH TO ALT AF
	POP	HL
	POP	DE
	POP	BC
	POP	AF
	EXX				; SWITCH TO PRI REGS
	EX	AF,AF'			; SWITCH TO PRI AF
	IMM_WCTL($0C)
	RET
;
IMM_GETBLOCK1:
	EXX				; ALT REGS
	EZ80_IO
	OUT	(C),D			; SEND FIRST CLOCK
	EZ80_IO
	IN	A,($FF)			; GET LOW NIBBLE
IMM_GETBLOCK_A	.EQU	$-1
#IF (IMMMODE == IMMMODE_MG014)
	AND	$0F			; RELEVANT BITS ONLY
	ADD	A,MG014_STATMAPLO & $FF	; LOW BYTE OF MAP PTR
	LD	L,A			; PUT IN L
	LD	A,(HL)			; LOOKUP LOW NIBBLE VALUE
	EX	AF,AF'			; ALT AF, SAVE NIBBLE
#ENDIF
#IF (IMMMODE == IMMMODE_SPP)
	AND	$F0			; RELEVANT BITS ONLY
	RLCA				; MOVE TO LOW NIBBLE
	RLCA				; MOVE TO LOW NIBBLE
	RLCA				; MOVE TO LOW NIBBLE
	RLCA				; MOVE TO LOW NIBBLE
	LD	L,A			; SAVE NIBBLE IN L
#ENDIF
	EZ80_IO
	OUT	(C),E			; SEND SECOND CLOCK
	EZ80_IO
	IN	A,($FF)			; GET HIGH NIBBLE
IMM_GETBLOCK_B	.EQU	$-1
#IF (IMMMODE == IMMMODE_MG014)
	AND	$0F			; RELEVANT BITS ONLY
	ADD	A,MG014_STATMAPHI & $FF	; HIGH BYTE OF MAP PTR
	LD	L,A			; PUT IN L
	EX	AF,AF'			; PRI AF, RECOVER LOW NIBBLE VALUE
	OR	(HL)			; COMBINE WITH HIGH NIB VALUE
#ENDIF
#IF (IMMMODE == IMMMODE_SPP)
	AND	$F0			; RELEVANT BITS ONLY
	OR	L			; COMBINE WITH HIGH NIB VALUE
#ENDIF
	EXX				; SWITCH TO PRI REGS
	LD	(DE),A			; SAVE BYTE
	INC	DE			; BUMP BUF PTR
	DJNZ	IMM_GETBLOCK1		; LOOP
	RET				; DONE
;
; PUT A CHUNK OF DATA TO THE SCSI BUS.  THIS IS SPECIFICALLY FOR
; WRITE PHASE.  IF TRANSFER MODE IS NON-ZERO, THEN A BLOCK (512 BYTES)
; OF DATA WILL BE WRITTEN.  OTHERWISE, DATA IS WRITTEN AS
; LONG AS SCSI DEVICE WANTS TO CONTINUE RECEIVING (NO OVERRUN
; CHECK IN THIS CASE).
;
; DE=BUFFER
; A=TRANSFER MODE (0=VARIABLE, 1=BLOCK)
;
IMM_PUTDATA:
	; BRANCH TO CORRECT ROUTINE
	OR	A
	JR	NZ,IMM_PUTBLOCK		; DO BLOCK WRITE
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nPUTDATA:$")
#ENDIF
;
IMM_PUTDATA1:
	PUSH	HL			; SAVE BYTE COUNTER
	CALL	IMM_WAIT		; WAIT FOR BUS READY
	POP	HL			; RESTORE BYTE COUNTER
	CP	$88			; CHECK FOR WRITE PHASE
	JR	NZ,IMM_PUTDATA2		; IF NOT, ASSUME WE ARE DONE
	;IMM_WCTL($04)
	LD	A,(DE)			; GET NEXT BYTE TO WRITE (FIRST OF PAIR)
	CALL	IMM_WRITEDATA		; PUT ON BUS
	INC	DE			; BUMP TO NEXT BUF POS
	INC	HL			; INCREMENT COUNTER
	IMM_WCTL($05)
	LD	A,(DE)			; GET NEXT BYTE TO WRITE (SECOND OF PAIR)
	CALL	IMM_WRITEDATA		; PUT ON BUS
	INC	DE			; BUMP TO NEXT BUF POS
	INC	HL			; INCREMENT COUNTER
	IMM_WCTL($00)
	JR	IMM_PUTDATA1		; LOOP TILL DONE
;
IMM_PUTDATA2:
	IMM_WCTL($04)
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXWORDHL
	PRTS(" BYTES$")
#ENDIF
;
	RET
;
IMM_PUTBLOCK:
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nPUTBLK:$")
#ENDIF
;
	IMM_WCTL($04)
	LD	B,0			; LOOP COUNTER
	LD	A,(IY+IMM_IOBASE)	; GET BASE IO ADR
	LD	(IMM_PUTBLOCK_A),A	; FILL IN
	LD	(IMM_PUTBLOCK_B),A	; ... DYNAMIC BITS OF CODE
	INC	A			; STATUS PORT
	INC	A			; CONTROL PORT
	LD	C,A			; ... TO C
	; HL: CLOCK VALUES
#IF (IMMMODE == IMMMODE_MG014)
	LD	H,$05 ^ ($0B | $80)
	LD	L,$00 ^ ($0B | $80)
#ENDIF
#IF (IMMMODE == IMMMODE_SPP)
	LD	H,$05
	LD	L,$00
#ENDIF
	CALL	IMM_PUTBLOCK1		; ONE LOOP CUZ BYTE PAIRS
	IMM_WCTL($04)
	RET
;
IMM_PUTBLOCK1:
	LD	A,(DE)			; GET NEXT BYTE
IMM_PUTBLOCK_A	.EQU	$+1
	EZ80_IO
	OUT	($FF),A			; PUT ON BUS
	INC	DE			; INCREMENT BUF POS
	EZ80_IO
	OUT	(C),H			; FIRST CLOCK
	LD	A,(DE)			; GET NEXT BYTE
IMM_PUTBLOCK_B	.EQU	$+1
	EZ80_IO
	OUT	($FF),A			; PUT ON BUS
	INC	DE			; INCREMENT BUF POS
	EZ80_IO
	OUT	(C),L			; SECOND CLOCK
	DJNZ	IMM_PUTBLOCK1		; LOOP
	RET				; DONE
;
; READ SCSI COMMAND STATUS
;
IMM_GETSTATUS:
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nSTATUS:$")
#ENDIF
;
	CALL	IMM_GETBYTE		; GET ONE BYTE
	LD	(IMM_CMDSTAT),A		; SAVE AS FIRST STATUS BYTE
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
;
	CALL	IMM_WAIT		; CHECK FOR OPTIONAL SECOND BYTE
	CP	$B8			; STILL IN STATUS PHASE?
	RET	NZ			; IF NOT, DONE
	CALL	IMM_GETBYTE		; ELSE, GET THE SECOND BYTE
	LD	(IMM_CMDSTAT+1),A	; AND SAVE IT
;
#IF (IMMTRACE >= 3)
	CALL	PC_SPACE
	CALL	PRTHEXBYTE
#ENDIF
;
	RET
;
; TERMINATE A BULK READ OPERATION
;
IMM_ENDREAD:
	IMM_WCTL($04)
	IMM_WCTL($0C)
	IMM_WCTL($0E)
	IMM_WCTL($04)
	RET
;
; THIS IS THE MAIN SCSI ENGINE.  BASICALLY, IT SELECTS THE DEVICE
; ON THE BUS, SENDS THE COMMAND, THEN PROCESSES THE RESULT.
;
; HL: COMMAND BUFFER
; DE: TRANSFER BUFFER
; A: TRANSFER MODE (0=VARIABLE, 1=BLOCK)
;
IMM_RUNCMD:
	; THERE ARE MANY PLACES NESTED WITHIN THE ROUTINES THAT
	; ARE CALLED HERE.  HERE WE SAVE THE STACK SO THAT WE CAN
	; EASILY AND QUICKLY ABORT OUT OF ANY NESTED ROUTINE.
	; SEE IMM_CMD_ERR BELOW.
	LD	(IMM_CMDSTK),SP		; FOR ERROR ABORTS
	LD	(IMM_DSKBUF),DE		; SAVE BUF PTR
	LD	(IMM_XFRMODE),A		; SAVE XFER LEN
	PUSH	HL
	CALL	IMM_CONNECT		; PARALLEL PORT BUS CONNECT
	CALL	IMM_SELECT		; SELECT TARGET DEVICE
	CALL	IMM_WAIT		; WAIT TILL READY
	POP	HL
	CALL	IMM_SENDCMD		; SEND THE COMMAND
;
IMM_RUNCMD_PHASE:
	; WAIT FOR THE BUS TO BE READY.  WE USE AN EXTRA LONG WAIT
	; TIMEOUT HERE BECAUSE THIS IS WHERE WE WILL WAIT FOR LONG
	; OPERATIONS TO COMPLETE.  IT CAN TAKE SOME TIME IF THE
	; DEVICE HAS GONE TO SLEEP BECAUSE IT WILL NEED TO WAKE UP
	; AND SPIN UP BEFORE PROCESSING AN I/O COMMAND.
	CALL	IMM_LONGWAIT		; WAIT TILL READY
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nPHASE: $")
	CALL	PRTHEXBYTE
#ENDIF
;
	CP	$88			; DEVICE WANTS TO RCV DATA
	JR	Z,IMM_RUNCMD_WRITE
	CP	$98			; DEVICE WANTS TO SEND DATA
	JR	Z,IMM_RUNCMD_READ
	CP	$B8			; DEVICE WANTS TO BE DONE
	JR	Z,IMM_RUNCMD_END
	JR	IMM_CMD_IOERR
;
IMM_RUNCMD_WRITE:
	LD	DE,(IMM_DSKBUF)		; XFER BUFFER
	LD	A,(IMM_XFRMODE)		; XFER MODE
	CALL	IMM_PUTDATA		; SEND DATA NOW
	JR	IMM_RUNCMD_PHASE	; BACK TO DISPATCH
;
IMM_RUNCMD_READ:
	CALL	IMM_NEGOTIATE		; NEGOTIATE FOR READ
	CALL	IMM_WAIT		; WAIT TILL READY
	; CHECK FOR STATUS $98???
	LD	DE,(IMM_DSKBUF)		; XFER BUFFER
	LD	A,(IMM_XFRMODE)		; XFER MODE
	CALL	IMM_GETDATA		; GET THE DATA NOW
	CALL	IMM_ENDREAD		; TERMINATE THE READ
	JR	IMM_RUNCMD_PHASE	; BACK TO DISPATCH
;
IMM_RUNCMD_END:
	CALL	IMM_NEGOTIATE		; NEGOTIATE FOR READ (STATUS)
	CALL	IMM_WAIT		; WAIT TILL READY
	; CHECK FOR STATUS $B8???
	CALL	IMM_GETSTATUS		; READ STATUS BYTES
	CALL	IMM_ENDREAD		; TERMINATE THE READ
	CALL	IMM_DISCONNECT		; PARALLEL PORT BUS DISCONNECT
	XOR	A			; SIGNAL SUCCESS
	RET
;
IMM_CMD_IOERR:
	LD	A,IMM_STIOERR		; ERROR VALUE TO A
	JR	IMM_CMD_ERR		; CONTINUE
;
IMM_CMD_TIMEOUT:
	LD	A,IMM_STTO		; ERROR VALUE TO A
	JR	IMM_CMD_ERR		; CONTINUE
;
IMM_CMD_ERR:
	LD	SP,(IMM_CMDSTK)		; UNWIND STACK
	PUSH	AF			; SAVE STATUS
	;CALL	IMM_RESETPULSE		; CLEAN UP THE MESS???
	LD	DE,62			; DELAY AFTER RESET PULSE
	CALL	VDELAY
	CALL	IMM_DISCONNECT		; PARALLEL PORT BUS DISCONNECT
	LD	DE,62			; DELAY AFTER DISCONNECT
	CALL	VDELAY
	POP	AF			; RECOVER STATUS
	JP	IMM_ERR			; NOW DO STANDARD ERR PROCESSING
;
; ERRORS SHOULD GENERALLY NOT CAUSE SCSI PROCESSING TO FAIL.  IF A
; DEVICE ERROR (I.E., READ ERROR) OCCURS, THEN THE SCSI PROTOCOL WILL
; PROVIDE ERROR INFORMATION.  THE STATUS RESULT OF THE SCSI COMMAND
; WILL INDICATE IF AN ERROR OCCURRED.  ADDITIONALLY, IF THE ERROR IS
; A CHECK CONDITION ERROR, THEN IT IS MANDATORY TO ISSUE A SENSE
; REQUEST SCSI COMMAND TO CLEAR THE ERROR AND RETRIEVE DETAILED ERROR
; INFO.
;
IMM_CHKCMD:
	; SCSI COMMAND COMPLETED, CHECK SCSI CMD STATUS
	LD	A,(IMM_CMDSTAT)		; GET STATUS BYTE
	OR	A			; SET FLAGS
	RET	Z			; IF ZERO, ALL GOOD, DONE
;
	; DO WE HAVE A CHECK CONDITION?
	CP	2			; CHECK CONDITION RESULT?
	JR	Z,IMM_CHKCMD1		; IF SO, REQUEST SENSE
	JP	IMM_IOERR		; ELSE, GENERAL I/O ERROR
;
IMM_CHKCMD1:
	; USE REQUEST SENSE CMD TO GET ERROR DETAILS
	LD	DE,HB_WRKBUF		; PUT DATA IN WORK BUF
	LD	A,0			; VARIABLE LENGTH REQUEST
	LD	HL,IMM_CMD_SENSE	; REQUEST SENSE CMD
	CALL	IMM_RUNCMD		; DO IT
	JP	NZ,IMM_IOERR		; BAIL IF ERROR IN CMD
;
	; REQ SENSE CMD COMPLETED
#IF (IMMTRACE >= 3)
	PRTS("\r\nSENSE:$")
	LD	A,$19
	LD	DE,HB_WRKBUF
	CALL	Z,PRTHEXBUF
#ENDIF
;
	; CHECK SCSI CMD STATUS
	LD	A,(IMM_CMDSTAT)		; GET STATUS BYTE
	OR	A			; SET FLAGS
	JP	NZ,IMM_IOERR		; IF FAILED, GENERAL I/O ERROR
;
	; RETURN RESULT BASED ON REQ SENSE DATA
	; TODO: WE NEED TO CHECK THE SENSE KEY FIRST!!!
	LD	A,(HB_WRKBUF+12)	; GET ADDITIONAL SENSE CODE
	CP	$3A			; NO MEDIA?
	JP	Z,IMM_NOMEDIA		; IF SO, RETURN NO MEDIA ERR
	JP	IMM_IOERR		; ELSE GENERAL I/O ERR
;
; CHECK CURRENT DEVICE FOR ERROR STATUS AND ATTEMPT TO RECOVER
; VIA RESET IF DEVICE IS IN ERROR.
;
IMM_CHKERR:
	LD	A,(IY+IMM_STAT)		; GET STATUS
	OR	A			; SET FLAGS
	CALL	NZ,IMM_RESET		; IF ERROR STATUS, RESET BUS
	RET
;
; (RE)INITIALIZE DEVICE
;
IMM_INITDEV:
;
#IF (IMMMODE == IMMMODE_MG014)
	; INITIALIZE 8255
	LD	A,(IY+IMM_IOBASE)	; BASE PORT
	ADD	A,IMM_IOSETUP		; BUMP TO SETUP PORT
	LD	C,A			; MOVE TO C FOR I/O
	LD	A,$82			; CONFIG A OUT, B IN, C OUT
	OUT	(C),A			; DO IT
	CALL	DELAY			; SHORT DELAY FOR BUS SETTLE
#ENDIF
;
	CALL	IMM_DISCONNECT		; DISCONNECT FIRST JUST IN CASE
	CALL	IMM_CONNECT		; NOW CONNECT TO BUS
	CALL	IMM_RESETPULSE		; ISSUE A SCSI BUS RESET
	LD	DE,62			; WAIT A BIT
	CALL	VDELAY
	CALL	IMM_DISCONNECT		; AND DISCONNECT FROM BUS
	LD	DE,62			; WAIT A BIT MORE
	CALL	VDELAY
;
	; INITIALLY, THE DEVICE MAY REQUIRE MULTIPLE REQUEST SENSE
	; COMMANDS BEFORE IT WILL ACCEPT I/O COMMANDS.  THIS IS DUE
	; TO THINGS LIKE BUS RESET NOTIFICATION, MEDIA CHANGE, ETC.
	; HERE, WE RUN A FEW REQUEST SENSE COMMANDS.  AS SOON AS ONE
	; INDICATES NO ERRORS, WE CAN CONTINUE.
	LD	B,4			; TRY UP TO 4 TIMES
IMM_INITDEV1:
	PUSH	BC			; SAVE LOOP COUNTER
;
	; REQUEST SENSE COMMAND
	LD	DE,HB_WRKBUF		; BUFFER FOR SENSE DATA
	LD	A,0			; READ WHATEVER IS SENT
	LD	HL,IMM_CMD_SENSE	; POINT TO CMD BUFFER
	CALL	IMM_RUNCMD		; RUN THE SCSI ENGINE
	JR	NZ,IMM_INITDEV2		; CMD PROC ERROR
;
	; CHECK CMD STATUS
	LD	A,(IMM_CMDSTAT)		; GET STATUS BYTE
	OR	A			; SET FLAGS
	JR	NZ,IMM_INITDEV2		; IF ERROR, LOOP
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nSENSE:$")
	LD	A,$19
	LD	DE,HB_WRKBUF
	CALL	PRTHEXBUF
#ENDIF
;
	; CHECK SENSE KEY
	LD	A,(HB_WRKBUF + 2)	; GET SENSE KEY
	OR	A			; SET FLAGS
;
IMM_INITDEV2:
	POP	BC			; RESTORE LOOP COUNTER
	JR	Z,IMM_INITDEV3		; IF NO ERROR, MOVE ON
	DJNZ	IMM_INITDEV1		; TRY UNTIL COUNTER EXHAUSTED
	JP	IMM_IOERR		; BAIL OUT WITH ERROR
;
IMM_INITDEV3:
	; READ & RECORD DEVICE CAPACITY
	LD	DE,HB_WRKBUF		; BUFFER TO CAPACITY RESPONSE
	LD	A,0			; READ WHATEVER IS SENT
	LD	HL,IMM_CMD_RDCAP	; POINT TO READ CAPACITY CMD
	CALL	IMM_RUNCMD		; RUN THE SCSI ENGINE
	CALL	Z,IMM_CHKCMD		; CHECK AND RECORD ANY ERRORS
	RET	NZ			; BAIL OUT ON ERROR
;
#IF (IMMTRACE >= 3)
	PRTS("\r\nRDCAP:$")
	LD	A,8
	LD	DE,HB_WRKBUF
	CALL	PRTHEXBUF
#ENDIF
;
	; CAPACITY IS RETURNED IN A 4 BYTE, BIG ENDIAN FIELD AND
	; INDICATES THE LAST LBA VALUE.  WE NEED TO CONVERT THIS TO
	; LITTLE ENDIAN AND INCREMENT THE VALUE TO MAKE IT A CAPACITY
	; COUNT INSTEAD OF A LAST LBA VALUE.
	LD	A,IMM_MEDCAP		; OFFSET IN CFG FOR CAPACITY
	CALL	LDHLIYA			; POINTER TO HL
	PUSH	HL			; SAVE IT
	LD	HL,HB_WRKBUF		; POINT TO VALUE IN CMD RESULT
	CALL	LD32			; LOAD IT TO DE:HL
	LD	A,L			; FLIP BYTES
	LD	L,D			; ... BIG ENDIAN
	LD	D,A			; ... TO LITTLE ENDIAN
	LD	A,H
	LD	H,E
	LD	E,A
	CALL	INC32			; INCREMENT TO FINAL VALUE
	POP	BC			; RECOVER SAVE LOCATION
	CALL	ST32			; STORE VALUE
;
	XOR	A			; SIGNAL SUCCESS
	LD	(IY+IMM_STAT),A		; RECORD IT
	RET
;
;=============================================================================
; ERROR HANDLING AND DIAGNOSTICS
;=============================================================================
;
; ERROR HANDLERS
;
IMM_NOMEDIA:
	LD	A,IMM_STNOMEDIA
	JR	IMM_ERR
;
IMM_CMDERR:
	LD	A,IMM_STCMDERR
	JR	IMM_ERR
;
IMM_IOERR:
	LD	A,IMM_STIOERR
	JR	IMM_ERR
;
IMM_TO:
	LD	A,IMM_STTO
	JR	IMM_ERR
;
IMM_NOTSUP:
	LD	A,IMM_STNOTSUP
	JR	IMM_ERR
;
IMM_ERR:
	LD	(IY+IMM_STAT),A		; SAVE NEW STATUS
;
IMM_ERR2:
#IF (IMMTRACE >= 2)
	CALL	IMM_PRTSTAT
#ENDIF
	OR	A			; SET FLAGS
	RET
;
;
;
IMM_PRTERR:
	RET	Z			; DONE IF NO ERRORS
	; FALL THRU TO IMM_PRTSTAT
;
; PRINT FULL DEVICE STATUS LINE
;
IMM_PRTSTAT:
	PUSH	AF
	PUSH	DE
	PUSH	HL
	LD	A,(IY+IMM_STAT)
	CALL	IMM_PRTPREFIX		; PRINT UNIT PREFIX
	CALL	PC_SPACE		; FORMATTING
	CALL	IMM_PRTSTATSTR
	POP	HL
	POP	DE
	POP	AF
	RET
;
; PRINT STATUS STRING
;
IMM_PRTSTATSTR:
	PUSH	AF
	PUSH	DE
	PUSH	HL
	LD	A,(IY+IMM_STAT)
	NEG
	LD	HL,IMM_STR_ST_MAP
	ADD	A,A
	CALL	ADDHLA
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	CALL	WRITESTR
	POP	HL
	POP	DE
	POP	AF
	RET
;
; PRINT DEVICE/UNIT PREFIX
;
IMM_PRTPREFIX:
	PUSH	AF
	CALL	NEWLINE
	PRTS("IMM$")
	LD	A,(IY+IMM_DEV)		; GET CURRENT DEVICE NUM
	CALL	PRTDECB
	CALL	PC_COLON
	POP	AF
	RET
;
;=============================================================================
; STRING DATA
;=============================================================================
;
IMM_STR_ST_MAP:
	.DW		IMM_STR_ST_OK
	.DW		IMM_STR_ST_NOMEDIA
	.DW		IMM_STR_ST_CMDERR
	.DW		IMM_STR_ST_IOERR
	.DW		IMM_STR_ST_TO
	.DW		IMM_STR_ST_NOTSUP
;
IMM_STR_ST_OK		.TEXT	"OK$"
IMM_STR_ST_NOMEDIA	.TEXT	"NO MEDIA$"
IMM_STR_ST_CMDERR	.TEXT	"COMMAND ERROR$"
IMM_STR_ST_IOERR	.TEXT	"IO ERROR$"
IMM_STR_ST_TO		.TEXT	"TIMEOUT$"
IMM_STR_ST_NOTSUP	.TEXT	"NOT SUPPORTED$"
IMM_STR_ST_UNK		.TEXT	"UNKNOWN ERROR$"
;
IMM_STR_MODE_MAP:
	.DW	IMM_STR_MODE_NONE
	.DW	IMM_STR_MODE_SPP
	.DW	IMM_STR_MODE_MG014
;
IMM_STR_MODE_NONE	.TEXT	"NONE$"
IMM_STR_MODE_SPP	.TEXT	"SPP$"
IMM_STR_MODE_MG014	.TEXT	"MG014$"
;
IMM_STR_NOHW		.TEXT	"NOT PRESENT$"
;
;=============================================================================
; DATA STORAGE
;=============================================================================
;
IMM_DEVNUM	.DB	0		; TEMP DEVICE NUM USED DURING INIT
IMM_CMDSTK	.DW	0		; STACK PTR FOR CMD ABORTING
IMM_DSKBUF	.DW	0		; WORKING DISK BUFFER POINTER
IMM_XFRMODE	.DB	0		; 0=VARIABLE, 1=BLOCK (512 BYTES)
IMM_CMDSTAT	.DB	0, 0		; CMD RESULT STATUS
;
; SCSI COMMAND TEMPLATES (LENGTH PREFIXED)
;
		.DB	6
IMM_CMD_RW	.DB	$00, $00, $00, $00, $01, $00	; READ/WRITE SECTOR
		.DB	6
IMM_CMD_SENSE	.DB	$03, $00, $00, $00, $FF, $00	; REQUEST SENSE DATA
		.DB	10
IMM_CMD_RDCAP	.DB	$25, $00, $00, $00, $00, $00, $00, $00, $00, $00 ; READ CAPACITY
;
; IMM DEVICE CONFIGURATION TABLE
;
IMM_CFG:
;
#IF (IMMCNT >= 1)
;
IMM0_CFG:	; DEVICE 0
	.DB	0			; DRIVER DEVICE NUMBER (FILLED DYNAMICALLY)
	.DB	IMMMODE			; DRIVER DEVICE MODE
	.DB	0			; DEVICE STATUS
	.DB	IMM0BASE		; IO BASE ADDRESS
	.DW	0,0			; DEVICE CAPACITY
	.DW	0,0			; CURRENT LBA
;
	DEVECHO	"IMM: MODE="
  #IF (IMMMODE == IMMMODE_SPP)
	DEVECHO	"SPP"
  #ENDIF
  #IF (IMMMODE == IMMMODE_MG014)
	DEVECHO	"MG014"
  #ENDIF
	DEVECHO	", IO="
	DEVECHO	IMM0BASE
	DEVECHO	"\n"
#ENDIF
;
#IF (IMMCNT >= 2)
;
IMM1_CFG:	; DEVICE 1
	.DB	0			; DRIVER DEVICE NUMBER (FILLED DYNAMICALLY)
	.DB	IMMMODE			; DRIVER DEVICE MODE
	.DB	0			; DEVICE STATUS
	.DB	IMM1BASE		; IO BASE ADDRESS
	.DW	0,0			; DEVICE CAPACITY
	.DW	0,0			; CURRENT LBA
;
	DEVECHO	"IMM: MODE="
  #IF (IMMMODE == IMMMODE_SPP)
	DEVECHO	"SPP"
  #ENDIF
  #IF (IMMMODE == IMMMODE_MG014)
	DEVECHO	"MG014"
  #ENDIF
	DEVECHO	", IO="
	DEVECHO	IMM1BASE
	DEVECHO	"\n"
#ENDIF
;
#IF ($ - IMM_CFG) != (IMMCNT * IMM_CFGSIZ)
	.ECHO	"*** INVALID IMM CONFIG TABLE ***\n"
#ENDIF
;
	.DB	$FF			; END MARKER
