;
;	File:		SWIMDriver.aii
;
;	Contains:	IOP Based SWIM/SuperDrive Floppy Disk Driver.
;
;	Written by:	Gary G. Davidian 	Created: 01-Aug-87
;
;	Copyright:	© 1987-1990 by Apple Computer, Inc., all rights reserved.
;
;	Change History (most recent first):
;
;		 <3>	 1/14/90	GGD		NEEDED FOR ZONE-5: Fixing occasional problem with Raw Track Read
;									getting an overrun when doing an immediate GCR track dump.
;									Adjusted MFMreWriteDelay timing to give proper write splices.
;									Changed Polling Task to disable the drive before calling the
;									68030 to post disk inserted events, to prevent clicking noises
;									during the early booting stages.
;		 <2>	  1/2/90	GGD		NEEDED FOR ZONE-5:Adding support for Raw Track I/O to better
;									support copy protection. Fixed bug in GCR formatting that wrote
;									199 bad sync patterns at the beginning of the track, but since
;									it gets mostly overwritten by the last sector on the track, and
;									was followed by 8 good ones, it was pretty harmless. This was
;									introduced by change <1.8>. Re-organized and optimized code to
;									regain some additional RAM space. Improved the performance of
;									reporting GCRonMFMErr, eliminated the retries (which took a long
;									time), since it is not a recoverable error.
;	   <2.0>	12/11/89	GGD		NEEDED FOR ZONE-5 Added support for the Disk Duplicator.
;									Implemented a dummy routine for the Get Raw Data track dump
;									command which just returns ControlErr for now. Added some extra
;									entries to the request dispatch table to allow easier patching.
;	   <1.9>	11/19/89	GGD		NEEDED FOR ZONE-5 Changed StartPolling and implemented
;									StopPolling, to more closely match how they are documented in
;									the ERS. Added a prefetcher task to fill the cache with data
;									that is under the heads during the last second before turning
;									off the drive motor. Added TimeOut checks in DMA transfer loops
;									to prevent infinate loops if DMA hangs.
;	   <1.8>	 11/2/89	GGD		NEEDED FOR ZONE-5 Added DMA support to GCR Format, MFM
;									Read/Write/Format so that ADB can get more processor time while
;									the floppy is in use. This feature will result in a smoother
;									mouse/cursor if the user moves the mouse while the floppy is
;									being used, which will boost the sales of ZONE-5's and will have
;									a direct impact on profit sharing.
;	   <1.7>	10/10/89	GGD		Reorganized some code to allow more sharing between routines and
;									save space. I/O routines now look for recent ADB activity, and
;									try to give ADB some more time to make the mouse smoother. Added
;									seek optimization for large I/O requests to start at the end of
;									the request that is closest to the current head position.
;	   <1.6>	 9/22/89	GGD		Implemented first pass at Track Cacheing and Error Recovery.
;	   <1.5>	  7/8/89	CCH		Added EASE comments to file.
;	  <•1.4>	 6/15/89	GGD		Updated to use equates for the latest rev of the IOP chip,
;									re-formated tab stops in source.
;	   <1.3>	  2/9/89	GGD		Added temporary version of track buffer support to greatly
;									improve Floppy performance. Also coded around SWIM problem with
;									MFM writes not always getting 12 bytes of zeros for a sync
;									field.
;	   <1.2>	  2/5/89	GGD		Implemented Format/Verify support. Rolled in changes to
;									SWIM/SuperDrive support that were uncovered during Mac IIx
;									development (new SWIM params, GCR on HD media, etc).
;	   <1.1>	11/16/88	GGD		Fixed bug with incorrect sensing of eject button in some cases.
;	   <1.0>	 11/9/88	CCH		Adding to EASE.
;
;	To Do:
;
				eject
				title	'IOP SWIM Driver'
; File: SWIMDriver.aii  -	IOP code for IOP Based SWIM driver.
;
;______________________________________________________________________________
;
; IOP Based SWIM/SuperDrive Floppy Disk Driver.
;
;		written by Gary G. Davidian 	Created: 01-Aug-87
;
; Copyright © Apple Computer, Inc. 1987-1989
;
;______________________________________________________________________________

				machine m65C02
				codechk off
				datachk off
				longa	off
				longi	off
				print	nomdir
				include 'IOPDefs.aii'
				include 'SWIMDefs.aii'

				import	ADBActivity

				import	GetTMPB
				import	StartTMPBTimer
				import	WaitTMPBdone
				import	CancelTMPBtimer
				import	TMPBtimeH
				import	TMPBtimeL

				import	GetBCPB
				import	MakeBCPBreq
				import	WaitBCPBdone
				import	BCPBcmd
				import	BCPBiopAddrH
				import	BCPBiopAddrL
				import	BCPBbyteCountH
				import	BCPBbyteCountL
				import	BCPBhostAddrB0
				import	BCPBhostAddrB1
				import	BCPBhostAddrB2
				import	BCPBhostAddrB3
				import	BCPBcompRel

				entry	InitSWIMChip
				entry	AdrAndSense
				entry	AdrAndStrb
				entry	GetDriveStatus
				entry	DiskSelect
				entry	FindDriveKind
				entry	TurnOffDrive
				entry	CheckStaticAttr
				entry	SetUpDrive
				entry	ReadSectorHdr
				entry	ReadHdrSetup
				entry	ReadSectorData
				entry	WriteSectorData
				entry	SkipSectorData
				entry	BlockToPhysAddr
				entry	Seek
				entry	ReCalibrate
				entry	WaitDriveReady
				entry	WaitMotorSettled
				entry	SleepLong
				entry	SleepShort
				entry	TurnOnDrive
				entry	LoadSwimParams
				entry	StartReadyTimer
				entry	Sense
				entry	Pulse
				entry	CreateTrackCache
				entry	FlushTrackCache
				entry	FlushIfDisabled
				entry	FindCacheEntry
				title	'IOP SWIM Driver - Global Variables'

;	Conditional assembly switches

CheckEncodings		equ 	0				; don't check gcr nibbles for legal encodings in loops
UseDmaHardware		equ 	1				; use the DMA Hardware where appropriate


;	Disk Format constants

LastCylinder		equ 	80-1			; cylinders are numbered 0..79
MinSectors			equ 	9				; minimum 9 sectors supported per head	(720K)
MaxSectors			equ 	18				; maximum 18 sectors supported per head	(1440K)

TagSize 			equ 	12				; Tags are 12 bytes long
BlockSize			equ 	512 			; Disk blocks contain 512 data bytes

GCRAddrMarkSize 	equ 	3				; size of GCR Address Mark		(D5 AA 96)
GCRAddrSlipMarkSize equ 	3				; size of GCR Address Slip Mark (DE AA FF)
GCRGapSyncSize		equ 	6				; size of GCR Gap Sync			(FF 3F CF F3 FC FF)
GCRDataMarkSize 	equ 	3				; size of GCR Data Mark 		(D5 AA AD)
GCRDataSlipMarkSize equ 	4				; size of GCR Data Slip Mark	(DE AA FF FF)
GCRInitialSyncCount equ 	200 			; start track with 200 sync groups before first sector
GCRMinSyncCount 	equ 	4				; no less than 4 sync groups between GCR sectors
GCRDefaultSyncCount equ 	8				; start with 8 sync groups between GCR sectors
MFMInitialGapSize	equ 	80				; size of MFM Gap between index and index mark
MFMSyncSize 		equ 	12				; size of MFM sync field before Index/Addr/Data marks
MFMIndexMarkSize	equ 	4				; size of MFM Index Mark		(C2 C2 C2 FC)
MFMIndexMarkGapSize equ 	50				; size of MFM Gap between index mark and sector 1
MFMAddrMarkSize 	equ 	4				; size of MFM Address Mark		(A1 A1 A1 FE)
MFMAddrMarkGapSize	equ 	22				; size of MFM Gap between address and data
MFMDataMarkSize 	equ 	4				; size of MFM Data Mark 		(A1 A1 A1 FB)
MFM720KDataGapSize	equ 	80				; size of MFM Gap following 720K sectors
MFM1440KDataGapSize equ 	101 			; size of MFM Gap following 1440K sectors
MFMFinalGapSize 	equ 	1000			; size of MFM Gap between last sector to index (max)


;	Delay Timing / Timeout constants

PollRate			equ 	TMPB1second/2	; wake up and look around every 1/2 second
MotorTimeoutCount	equ		5				; motor timeout is 2.0 .. 2.5 seconds (5 polls)

ReadyPollRate		equ 	TMPB1ms*3/4 	; check ready every 750 uSeconds
ReadyTimeoutCount	equ		TMPB1second\
							/ReadyPollRate	; max poll time is 1 second

MotorOnDelay		equ 	TMPB1second/20	; Delay 50ms after motor on before checking ready
ChuckingDelay		equ 	TMPB1second/2\
							-MotorOnDelay	; Delay 500ms for proper disk chucking
MotorOnSettle		equ 	TMPB100ms*3 	; Motor speed within 1.5 % 300ms after drive ready
MotorStopDelay		equ 	TMPB1second/5	; Delay 200ms after MotorOff to spin down
SeekDelay			equ 	TMPB1ms/2		; wait 500usec after seek before checking ready
DMAxferDelay		equ 	2000*TMPB1second/1000000	; go idle for 2.0 ms during DMA xfer
GCRSectorDataDelay 	equ 	12124*TMPB1second/1000000	; 12.124 ms is time of data & gap portion of GCR sector
MFMSectorDataDelay 	equ 	9472*TMPB1second/1000000	;  9.472 ms is time of data & gap portion of MFM sector
TypicalADBDelay		equ		3800*TMPB1second/1000000	;  3.800 ms is typical time of ADB poll with 2 data bytes

SectorDelay 		equ 	TMPB10ms+TMPB1ms; 11ms is approx time of 1 sector

EjectPollRate		equ 	TMPB100ms		; check eject done every 100 ms
EjectTimeoutCount	equ		TMPB100ms*15\
							/EjectPollRate	; give up after 1.5 seconds
EjectSettleDelay	equ		TMPB1ms*15/100	; leave drive enabled 150usec after eject

GCRWriteToRead		equ 	620 			; 620µs max time from write to read data valid again

AddrSearchMax		equ 	1500			; search 1500 bytes looking for an address mark
BytePollMax 		equ 	8				; give up on a byte after 8 polls
DataSearchMax		equ 	48				; search 48 bytes looking for data mark

;		MFM bytes are 16µsec, which is 31.3344 clocks @1.9584MHz.
;		About 204 clocks of overhead is already consumed by the routine.
;		Delay loop is 5 clocks per iteration.
MFMreWriteDelay		equ 	(((MFMAddrMarkGapSize*313344)/10000)-204)/5

GiveADBTimeMask		equ		%11111111		; Consider the last 8 ADB polls for activity


;	Sector Buffer / Track Cache constants

TotalBuffers		equ		36				; buffer storage for 36 sectors
MaxCacheEntries		equ		TotalBuffers\
							/MinSectors		; Max number of tracks that fit into cache
MinCacheEntries		equ		TotalBuffers\
							/MaxSectors		; Min number of tracks that fit into cache


;	Raw I/O Buffer constants

RawTempBufferNum	equ		0				; 1 temp buffer needed to read sector data
RawDataBufferNum	equ		1				; large buffer space for raw track data
RawClockBufferNum	equ		(TotalBuffers-1)*8/9+RawDataBufferNum
RawByteCountMax		equ		(RawClockBufferNum-RawDataBufferNum)\
							*BlockSize		; max number of bytes for Raw I/O


;	Error handling / recovery constants

RecalsMax			equ		1				; Max Recals allowed before hard error
IOErrorsMax			equ		9				; Max I/O errors allowed before Recal
WrongSectsMax		equ		64				; Max wrong sectors allowed before Recal


					seg 	'PageData'
SectorBuffers		record					; Sector Buffer Storage (must be Page Aligned)
MFMGapBuffer		equ		*				; MFM format gap buffer for DMA
TagBufStart			equ		*				; start of tag buffer
					ds.b	4				; padding so that a tag doesn't cross a page
TagBuffers			ds.b	TotalBuffers\
							*TagSize		; sector tag storage
					ds.b	4				; get back to a page boundary
TagBufEnd			equ		*				; end of tag buffer

BufferStatus		ds.b	TotalBuffers	; status flags for each buffer
ReadDataValid		equ		%10000000		; Valid disk data has been read into the buffer
IORequested			equ		%01000000		; Buffer needs to be Read/Written for current request.

TagPtrs				ds.b	TotalBuffers	; offsets divided by 2 to tag buffers

DataBuffers			ds.b	TotalBuffers\
							*BlockSize		; sector data storage
DataBufSize			equ		*-DataBuffers	; number of bytes in data buffers
GCRGapBuffer		equ		*-$500			; GCR format gap sync buffer for DMA
					endr

					seg 	'zdata'
SWIMVarsZ			record

SWIMVarsZStart		equ 	*

DriverActive		ds.b	1				; idle = 0, active <> 0
CurrentDrive		ds.b	1				; drive currently selected or powered up
LastActiveDrive 	ds.b	1				; drive number of last successfully accessed drive
StatusMsgPtr		ds.w	1				; Pointer to message that gets drive status
TagBufPtr			ds.w	1				; pointer to 12 byte tag buffer
DataBufPtr			ds.w	1				; pointer to 512 byte data buffer
GCRCkSumA			ds.b	1				; byte A of GCR data checksum
GCRCkSumB			ds.b	1				; byte B of GCR data checksum
GCRCkSumC			ds.b	1				; byte C of GCR data checksum

WrTagBytesA 		ds.b	4				; bytes 10, 7, 4, 1 of processed tag bytes
WrTagBytesB 		ds.b	4				; bytes 11, 8, 5, 2 of processed tag bytes
WrTagBytesC 		ds.b	4				; bytes 12, 9, 6, 3 of processed tag bytes

SectHdrFmtIndex		ds.b	1				; DiskFormat*2 (jump table index)
AdrSearchCountH		ds.b	1				; high byte of address mark search timeout
AdrSearchCountL		ds.b	1				; low byte of address mark search timeout
SectHdrCylinder		ds.b	1				; Cylinder number from sector header
SectHdrHead 		ds.b	1				; Head number from sector header
SectHdrSector		ds.b	1				; Sector number from sector header
SectHdrFmtKind		ds.b	1				; Format Kind or Block Size from sector header
SectHdrChkSumH		ds.b	1				; high byte of Check Sum from sector header
SectHdrChkSumL		ds.b	1				; low byte of Check Sum from sector header
SectHdrHandshake	ds.b	1				; Handshake register after reading last byte
SectHdrError		ds.b	1				; Error register after reading last byte

StepCount			ds.b	1				; Head Step counter for Seek/ReCalibrate
SeekingCylinder		ds.b	1				; cylinder number that we are seeking
SeekingHead 		ds.b	1				; head number that we are seeking
SeekDirection		ds.b	1				; seek direction for request, > 0 is forward, < 0 is reverse
SkipBlockToPhys		ds.b	1				; Skip first call to BlockToPhys when <> 0

LogicalBlockH		ds.b	1				; Logical Block number input to BlockToPhysAddr
LogicalBlockL		ds.b	1				; Logical Block number input to BlockToPhysAddr
PhysCylinder		ds.b	1				; Physical Cylinder returned by BlockToPhysAddr
PhysHead			ds.b	1				; Physical Head returned by BlockToPhysAddr
PhysSector			ds.b	1				; Physical Sector (head relative) returned by BlockToPhysAddr
PhysCylSector		ds.b	1				; Physical Sector (cylinder relative) returned by BlockToPhysAddr
PhysSectsPerCyl		ds.b	1				; Number of Sectors per cylinder returned by BlockToPhysAddr
PhysSectsPerHead	ds.b	1				; Number of Sectors per head returned by BlockToPhysAddr
PhysCylBias 		ds.b	1				; Starting Cylinder number of speed group
Interleave			ds.b	1				; sector interleave factor
WritingForFmt		ds.b	1				; flag to indicate that write sector data code called from Format
SectorsNeeded		ds.b	1				; number of sectors needed on this track
BaseBufferNum		ds.b	1				; Buffer number for sector 0 of this track
SectBufferNum		ds.b	1				; Buffer number for current sector of this track
TagBlockNumL		ds.b	1				; low byte of running block number for tag writes
TagBlockNumH		ds.b	1				; high byte of running block number for tag writes
BlocksToDoH 		ds.b	1				; remaining blocks to read/write (high)
BlocksToDoL 		ds.b	1				; remaining blocks to read/write (low)
RecalsLeft			ds.b	1				; number of Recals remaining before hard error
IOErrorsLeft		ds.b	1				; number of I/O errors remaining before Recal
WrongSectsLeft		ds.b	1				; number of wrong sectors remaining before Recal
DMARetryStatus		ds.b	1				; > 0 if ints during DMA took more than xfer time
RawIOFmtMFM			ds.b	1				; $FF if MFM, $00 if GCR, disk format for raw I/O

SwimWriteReg		ds.b	2				; pointer to SWIM reg (usually wData)

LoopCountL			ds.b	1				; low byte of general purpose loop counter
LoopCountH			ds.b	1				; high byte of general purpose loop counter

AsyncPcH			ds.b	1				; saved high byte of PC when waiting asynchronously
AsyncPcL			ds.b	1				; saved low byte of PC when waiting asynchronously
ReadyTMPB			ds.b	1				; timer used for Drive Ready / Sleep delay processing

CacheMRUPtr			ds.b	1				; index of the Most Recently Used Cache Entry
DataBCPB			ds.b	1				; Block Copy Paramater Block for data transfers
DataBCPB2			ds.b	1				; Block Copy Paramater Block for data transfers
PrevDataBCPB		ds.b	1				; Previous Block Copy Paramater Block for data transfers
CurDataBCPB			ds.b	1				; Current Block Copy Paramater Block for data transfers
TagBCPB 			ds.b	1				; Block Copy Paramater Block for tag transfers
TagBCPB2 			ds.b	1				; Block Copy Paramater Block for tag transfers
PrevTagBCPB 		ds.b	1				; Previous Block Copy Paramater Block for tag transfers
CurTagBCPB 			ds.b	1				; Current Block Copy Paramater Block for tag transfers

SectorGapSize		ds.b	1				; number of gap bytes or sync groups to fmt between sectors
FmtByteBlkSize		ds.b	1				; GCR Format byte / MFM block size for formatting
SectorMap			ds.b	MaxSectors		; sector interleave buffer for formatting

PollingDriveNum 	ds.b	1				; drive currently being polled
MotorTimingOut		ds.b	1				; number of polls to left before turning drive off
ADBActivityMask		ds.b	1				; mask to use to see if ADB deserves time

SectHdrInfo 		ds.b	5				; buffer used in formatting, for sector header info

SWIMVarsZSize		equ 	*-SWIMVarsZStart; number of bytes of SWIMVarsZ
					endr

					seg 	'data'
SWIMVars			record

SWIMVarsStart		equ 	*

; drive independent variables
OpenStatus			ds.b	1				; Flag indicating Initialization status
PassThruBroken		ds.b	1				; HD20 pass through connector not working
DrivesFound 		ds.b	1				; Number of valid disk drives found
CacheEnabled		ds.b	1				; disabled = 0, enabled <> 0
TagBuffHostAddr 	ds.b	4				; host address of separate tag buffer
TagBuffEnabled		ds.b	1				; disabled = 0, enabled <> 0
HFSTagAddr			ds.b	4				; host address of HFS tag buffer
HFSTagAddrValid		ds.b	1				; invalid = 0, valid <> 0
PollingEnabled		ds.b	1				; disabled = 0, enabled <> 0
PollingTMPB 		ds.b	1				; TMPB index of drive polling task
PollingPcH			ds.b	1				; high byte of PC when waiting for host to post event
PollingPcL			ds.b	1				; low byte of PC when waiting for host to post event
SettledTMPB 		ds.b	1				; timer used for Motor speed settled ≈1.5% timing
SettleTimeL 		ds.b	1				; low byte of settle time after drive ready
SettleTimeH 		ds.b	1				; high byte of settle time after drive ready
ReCalPcH			ds.b	1				; high byte of PC when waiting in ReCalibrate
ReCalPcL			ds.b	1				; low byte of PC when waiting in ReCalibrate
WaitReadyCount		ds.b	2				; timeout counter for WaitDriveReady
EjectPollCount		ds.b	1				; timeout counter for SWIMEject
LastParamSet		ds.b	1				; index if last set of parameters loaded
FmtSearchIndex		ds.b	1				; index into table of formats to search
Sect0CkSum			ds.b	1				; GCR header Checksum for sector zero when formatting
PrefetchActive		ds.b	1				; Cache Prefetching Active when <> 0
RequestPending		ds.b	1				; <> 0 if request received while DriverActive <> 0
CurHeadsPerCyl		ds.b	1				; number of heads per cylinder for current disk
CurSectsPerHead		ds.b	1				; number of sectors per head for current cylinder
PrefetchCachePtr	ds.b	1				; cache index for entry being prefetched

; drive dependent variables
PhysDriveNumber 	ds.b	NumberOfDrives	; physical drive number
DriveKind			ds.b	NumberOfDrives	; kind of drive attached
MediaKind			ds.b	NumberOfDrives	; kind of disk media in drive
DiskFormat			ds.b	NumberOfDrives	; format of disk in drive
CylinderValid		ds.b	NumberOfDrives	; current Cylinder (high byte)
CurrentCylinder 	ds.b	NumberOfDrives	; current Cylinder (low byte)
ErrorCountH 		ds.b	NumberOfDrives	; Number of errors (high byte)
ErrorCountL 		ds.b	NumberOfDrives	; Number of errors (low byte)

WriteEnabledFlag	equ 	%10000000		; disk is not write protected
DriveFlags			ds.b	NumberOfDrives	; flag bits


; Track Cache variables
CacheNextMRU		ds.b	MaxCacheEntries	; offset to next Most Recently Used entry
CacheCylinder		ds.b	MaxCacheEntries	; Cylinder number of cached track
CacheHead			ds.b	MaxCacheEntries	; Head number of cached track
CacheBufferNum		ds.b	MaxCacheEntries	; First Buffer number for cached track
CacheInvalidCount	ds.b	MaxCacheEntries	; Number of entries that need to be read (-1=all)

SWIMVarsSize		equ 	*-SWIMVarsStart ; number of bytes of SWIMVars
					endr
				title	'IOP SWIM Driver - Request Decode / Dispatching'

				seg 	'code'
SWIMDriver		proc						; SWIM driver requests use MSG 2
				entry	UnKnownSWIMReq
				export	HandleRCVMsg2
				export	SWIMReqDone

				entry	SWIMReqDecode
				entry	SWIMReqStart
				entry	SWIMReqEnd
				with	SWIMVarsZ
				with	SWIMVars
HandleRCVMsg2	lda 	#MessageReceived	; indicate that the message
				sta 	RCVState			; was received
				lda		#$FF				; indicate driver active
				tsb		<DriverActive		; mark driver as running, check prior state
				bne		BusyReturn			; if was active (prefetching), wait until it is done.
				trb		PrefetchActive		; clear prefetching, see if it was active
				beq		@PrefetchIdle		; if idle, don't touch the timer
				ldy 	<ReadyTMPB			; get the Ready timer TMPB index
				jsr 	CancelTMPBtimer		; cancel prefetch sleep timer, preventing future prefetches
@PrefetchIdle	stz		<ADBActivityMask	; give disk priority next time
				lda 	ReqKind 			; get the request Kind
				cpa 	#(SWIMReqEnd-SWIMReqStart)/2	; range check it
				bge 	UnKnownSWIMReq		; if request kind out of range
				asl 	a					; compute the table index
				tax 						; load the index register
				lda 	OpenStatus			; see if driver has been opened
				jmp 	(SWIMReqDecode,x)	; dispatch to the routine

BusyReturn		sta		RequestPending		; remember to re-run the request
				rts


UnKnownSWIMReq	lda 	#paramErr			; parameter error
SWIMReqDone 	sta 	ReqErrorCode+1		; lower byte of error code
				beq 	ErrHDone			; if noErr
				stz 	<LastActiveDrive	; abnormal completion, force drive change path
				bmi 	ErrNeg				; all errors are negative, and most fit in 1 byte
				lda 	#>gcrOnMfmErr		; except this one, which needs 2 bytes
				bra 	ErrHDone			; store the upper byte
ErrNeg			lda 	#-1 				; sign extend error code
ErrHDone		sta 	ReqErrorCode		; upper byte of error code
				lda 	#MessageCompleted	; message completed state
				sta 	RCVState			; indicate that it is done
				stz 	<DriverActive		; indicate that driver is no longer running
				lda 	#MsgCompletedINT	; get the interrupt bit
				sta 	HostControl 		; interrupt the main CPU
				rts 						; all done
				endwith
				endwith
				endproc

				seg 	'WordConsts'
SWIMReqDecode	proc
				entry	SWIMReqStart
				entry	SWIMReqEnd

				entry	SWIMInitialize
				entry	SWIMShutDown
				entry	SWIMStartPolling
				entry	SWIMStopPolling
				entry	SWIMSetHFSTagAddr
				entry	SWIMDriveStatus
				entry	SWIMEject
				entry	SWIMFormat
				entry	SWIMFormatVerify
				entry	SWIMWrite
				entry	SWIMRead
				entry	SWIMReadVerify
				entry	SWIMCacheControl
				entry	SWIMTagBufferControl
				entry	SWIMGetIcon
				entry	SWIMDiskDupInfo
				entry	SWIMGetRawData

SWIMReqStart	dc.w	UnKnownSWIMReq		; $00 - reserved
				dc.w	SWIMInitialize		; $01 - Initialize
				dc.w	SWIMShutDown		; $02 - ShutDown
				dc.w	SWIMStartPolling	; $03 - StartPolling
				dc.w	SWIMStopPolling 	; $04 - StopPolling
				dc.w	SWIMSetHFSTagAddr	; $05 - SetHFSTagAddr
				dc.w	SWIMDriveStatus 	; $06 - DriveStatus
				dc.w	SWIMEject			; $07 - Eject
				dc.w	SWIMFormat			; $08 - Format
				dc.w	SWIMFormatVerify	; $09 - FormatVerify
				dc.w	SWIMWrite			; $0A - Write
				dc.w	SWIMRead			; $0B - Read
				dc.w	SWIMReadVerify		; $0C - ReadVerify
				dc.w	SWIMCacheControl	; $0D - CacheControl
				dc.w	SWIMTagBufferControl; $0E - TagBufferControl
				dc.w	SWIMGetIcon 		; $0F - GetIcon
				dc.w	SWIMDiskDupInfo 	; $10 - DiskDupInfo
				dc.w	SWIMGetRawData	 	; $11 - GetRawData
				dc.w	UnKnownSWIMReq	 	; $12 - unknown
				dc.w	UnKnownSWIMReq	 	; $13 - unknown
				dc.w	UnKnownSWIMReq	 	; $14 - unknown
				dc.w	UnKnownSWIMReq	 	; $15 - unknown
				dc.w	UnKnownSWIMReq	 	; $16 - unknown
				dc.w	UnKnownSWIMReq	 	; $17 - unknown
				dc.w	UnKnownSWIMReq	 	; $18 - unknown
SWIMReqEnd
				endproc
				
				seg 	'code'
Unimplemented	proc
				export	HandleDMA2Int
HandleDMA2Int
				brk
				bra 	Unimplemented

				entry	SWIMShutDown
				entry	SWIMGetIcon
SWIMShutDown								; unimplemented
SWIMGetIcon									; unimplemented
				bra		UnKnownSWIMReq
				endproc
				title	'IOP SWIM Driver - SWIM Initialize'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMInitialize
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		InitSWIMChip, DiskSelect, FindDriveKind, TurnOffDrive
;				PollingTask, GetTMPB, GetBCPB
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Initializes the IOP based SWIM Driver variables, and
;				returns a list of installed drives to the Host processor.
;
;_______________________________________________________________________

				seg 	'code'
SWIMInitialize	proc
				entry	PollingTask
				with	SWIMVarsZ
				with	SWIMVars

				eor 	#openErr			; check for already open
				bne 	InitErr 			; return error if already initialized

				lda 	#BYPASS 			; prepare to turn off bypass mode
				trb 	SCCControlReg		; let IOP control the SWIM chip now

				ldx 	#SWIMVarsZSize		; size of page zero globals space
InitVarsZloop	dex 						; dec loop counter / index
				stz 	<SWIMVarsZStart,x	; zero a byte
				bne 	InitVarsZloop		; initialize all of the bytes

				ldx 	#SWIMVarsSize		; size of globals space
InitVarsloop	dex 						; dec loop counter / index
				stz 	SWIMVarsStart,x 	; zero a byte
				bne 	InitVarsloop		; initialize all of the bytes

;	Initialize the TagPtrs array to point to the corresponding TagBuffers entry

				with	SectorBuffers
				lda		#((TagBuffers-SectorBuffers)+TotalBuffers*TagSize)/2
				ldx		#TotalBuffers-1		; loop count/index
@tagPtrLoop		sec							; setup for subtract
				sbc		#TagSize/2			; point to previous buffer
				sta		TagPtrs,x			; initialize the pointer
				dex							; update index
				bpl		@tagPtrLoop			; loop for each buffer
				endwith

				ldx 	#<RCVData			; get low byte of RCV / XMT Data address
				stx 	<StatusMsgPtr		; initialize low byte of status message pointer

				ldx 	#<wData 			; get low byte of SWIM wData reg
				stx 	<SwimWriteReg		; initialize low byte of SWIM Write Reg pointer
				ldx 	#>wData 			; get high byte of SWIM wData reg
				stx 	<SwimWriteReg+1 	; initialize high byte of SWIM Write Reg pointer

*				stz 	<CurrentDrive		; initialize CurrentDrive to default invalid drive
				jsr 	InitSWIMChip		; initialize the chip
				beq 	InitCheckConfig 	; check configuration if chip initialized
				ldx 	#openErr			; not opened status value
				stx 	OpenStatus			; indicate not opened yet (in case of error)
InitErr 		jmp 	SWIMReqDone 		; return with error status

InitCheckConfig jsr 	GetTMPB 			; get a TiMer Parameter Block
				sty 	<ReadyTMPB			; timer used for Drive Ready / Sleep delay processing
				jsr 	GetTMPB 			; get a TiMer Parameter Block
				sty 	SettledTMPB 		; timer used for Motor speed settled ≈1.5% timing
				jsr 	GetTMPB 			; get a TiMer Parameter Block
				sty 	PollingTMPB 		; timer used for Motor timeout/Insert/Eject polling
				jsr 	GetBCPB 			; get a Block Copy Parameter Block
				sty 	<DataBCPB			; Block Copy Paramater Block for data transfers
				jsr 	GetBCPB 			; get a Block Copy Parameter Block
				sty 	<DataBCPB2			; Block Copy Paramater Block for data transfers
				jsr 	GetBCPB 			; get a Block Copy Parameter Block
				sty 	<TagBCPB 			; Block Copy Paramater Block for tag transfers
				jsr 	GetBCPB 			; get a Block Copy Parameter Block
				sty 	<TagBCPB2 			; Block Copy Paramater Block for tag transfers

;		if the HD-20 pass through connector is not working, then there will appear
;		to be an infinate number of HD-20s connected (really, the first one that
;		doesn't pass-through will be continually addressed)
;				stz 	PassThruBroken		; initialize PassThru flag, assume it works
				ldx 	#MaxDriveNumber+5	; a large drive number
				jsr 	DiskSelect			; try to select it
				jsr 	FindDriveKind		; see what kind of drive we find
				cpa 	#HD20DriveKind		; see if it's an HD-20
				bne 	PassThruWorks		; if its not an HD-20, then the pass thru works
				dec 	PassThruBroken		; it was an HD-20, pass thru must be broke.
PassThruWorks

*				stz 	DrivesFound 		; no drives found yet
FloppyLoop		ldx 	<CurrentDrive		; get the drive number
				jsr 	DiskSelect			; attempt to select the drive
				beq 	NextFloppy			; if drive couldn't be selected
				jsr 	FindDriveKind		; see what kind of drive we found
				cpa 	#SSGCRDriveKind+1	; see if it's a floppy drive we support
				blt 	NextFloppy			; ignore noDrive, unknown, and 400K drives
				cpa 	#HD20DriveKind		; ignore HD20s too
				beq 	NextFloppy			; if HD20

				inc 	DrivesFound 		; found a live one, count it
				ldx 	DrivesFound 		; get the count
				sta 	DriveKind,x 		; record the drive kind
				lda 	<CurrentDrive		; get the physical drive number
				sta 	PhysDriveNumber,x	; record it

NextFloppy		inc 	<CurrentDrive		; go onto next drive
				lda 	<CurrentDrive		; prepare to test for end
				cpa 	#FirstHD20DriveNumber	; see if past the range of floppies
				blt 	FloppyLoop			; loop through all floppy drive numbers

HD20Loop		ldx 	<CurrentDrive		; get the drive number
				jsr 	DiskSelect			; attempt to select the drive
				beq 	NextHD20			; if drive couldn't be selected
				jsr 	FindDriveKind		; see what kind of drive we found
				cpa 	#HD20DriveKind		; look for HD20s
				bne 	NextHD20			; if HD20

				inc 	DrivesFound 		; found a live one, count it
				ldx 	<CurrentDrive		; get the drive number
				sta 	DriveKind,x 		; record the drive kind
				txa 						; get the physical drive number
				sta 	PhysDriveNumber,x	; record it

NextHD20		inc 	<CurrentDrive		; go onto next drive
				lda 	<CurrentDrive		; prepare to test for end
				cpa 	#MaxDriveNumber+1	; see if past the range of HD20s
				blt 	HD20Loop			; loop through all HD20 drive numbers

				jsr 	TurnOffDrive		; de-select all drives

				ldx 	#28-1				; return 28 bytes of drive kinds
DriveKindsLoop	lda 	#noDriveKind		; assume no drive
				cpx 	#NumberOfDrives		; see if in range of drives supported
				bge 	ReturnDriveKind		; use default if out of range
				lda 	DriveKind,x 		; get the drive kind
ReturnDriveKind sta 	ReqDriveKinds,x		; return the drive kind
				dex 						; dec count / index
				bpl 	DriveKindsLoop		; loop through all possible drives

				lda 	#noErr				; return no error
*				sta 	OpenStatus			; indicate successful Initialization (noErr)
				jsr 	SWIMReqDone 		; let the HOST know we're done
				jmp 	PollingTask 		; start the polling task

				export	InitSWIMDrvr
InitSWIMDrvr	lda 	#openErr			; not opened status value
				sta 	OpenStatus			; indicate not opened yet
				rts

				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Drive Status'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMDriveStatus
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, z, c
;  Calls:		GetDriveStatus
;  Called by:	SWIM Request Dispatcher, SWIMEject, SWIMFormat
;
;  Function:	Returns the drive / media status information for the
;				drive specified in the Drive Status request message
;				returns a list of installed drives to the Host processor.
;
;_______________________________________________________________________

				seg 	'code'
SWIMDriveStatus proc
				bne 	StatusDone			; return openErr if SWIM Driver not initialized
				lda 	#nsDrvErr			; return no such drive error if out of range
				ldx 	ReqDriveNumber		; see what drive is requested
				cpx 	#MaxDriveNumber+1	; see if in range
				bge 	StatusDone			; return nsDrvErr if out of range
				lda 	#>ReqDriveStatus	; get page number of status message buffer
				jsr 	GetDriveStatus		; get the status
				lda 	#noErr				; indicate successful completion
StatusDone		jmp 	SWIMReqDone 		; let the HOST know we're done
				endproc
				title	'IOP SWIM Driver - SWIM Disk Duplicator Info'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMDiskDupInfo
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, n, z
;  Calls:		none
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Returns the Disk Duplicator Version number, and the format byte
;				for the last sector that was read.
;
;_______________________________________________________________________

				seg 	'code'
SWIMDiskDupInfo proc
				with	SWIMVarsZ
				bne 	DupInfoDone			; return openErr if SWIM Driver not initialized
				lda		#>$0410				; Current Version is 4.1
				sta		ReqDupVersion+0		; upper byte of version
				lda		#<$0410				; get low byte
				sta		ReqDupVersion+1		; lower byte of version
				lda		<SectHdrFmtKind		; get the Format Kind / Block Size from sector header
				sta		ReqHdrFmtKind		; return the Format Kind from the last sector hdr
				lda 	#noErr				; indicate successful completion
DupInfoDone		jmp 	SWIMReqDone 		; let the HOST know we're done
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Cache Control'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMCacheControl
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		FlushIfDisabled
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Enables / Disables the track cache.
;
;_______________________________________________________________________

				seg 	'code'
SWIMCacheControl proc
				with	SWIMVars
				bne 	CacheError			; return openErr if SWIM Driver not initialized
				jsr		FlushIfDisabled		; Need to flush if going from disabled to enabled
				stz 	CacheEnabled		; assume disabled
				ldx 	ReqAdditionalParams+1 ; get install flag
				bmi 	CacheDone			; disable cache if remove request
				ldx 	ReqAdditionalParams+0 ; get enable flag
				stx 	CacheEnabled		; update enable flag
CacheDone		lda 	#noErr				; indicate successful completion
CacheError		jmp 	SWIMReqDone 		; let the HOST know we're done
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Set HFS Tag Addr'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMSetHFSTagAddr
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, n, z
;  Calls:		none
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Sets the HOST address of the HFS tag buffer area.
;
;_______________________________________________________________________

				seg 	'code'
SWIMSetHFSTagAddr proc
				with	SWIMVars
				bne 	HFStagDone			; return openErr if SWIM Driver not initialized
				stz 	HFSTagAddrValid		; initialize enable flag
				ldx 	#3					; loop counter / index
loop			lda 	ReqRAMAddress,x		; get buffer address
				sta 	HFSTagAddr,x		; update buffer address
				tsb 	HFSTagAddrValid		; update enable flag
				dex 						; point to previous byte
				bpl 	loop				; loop through all bytes
				lda 	#noErr				; indicate successful completion
HFStagDone		jmp 	SWIMReqDone 		; let the HOST know we're done
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Tag Buffer Control'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMTagBufferControl
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, n, z
;  Calls:		none
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Enables / Disables a separate tag buffer.
;
;_______________________________________________________________________

				seg 	'code'
SWIMTagBufferControl proc
				with	SWIMVars
				bne 	TagBufferDone		; return openErr if SWIM Driver not initialized
				stz 	TagBuffEnabled		; initialize enable flag
				ldx 	#3					; loop counter / index
loop			lda 	ReqAdditionalParams,x	; get buffer address
				sta 	TagBuffHostAddr,x	; update buffer address
				tsb 	TagBuffEnabled		; update enable flag
				dex 						; point to previous byte
				bpl 	loop				; loop through all bytes
				lda 	#noErr				; indicate successful completion
TagBufferDone	jmp 	SWIMReqDone 		; let the HOST know we're done
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Start Polling'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMStartPolling
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, n, z
;  Calls:		None
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Indicates that Disk Insert/Eject polling should be enabled.
;
;_______________________________________________________________________

				seg 	'code'
SWIMStartPolling proc
				with	SWIMVars
				bne 	StartPollDone		; return openErr if SWIM Driver not initialized
				dea							; indicate that Insert/Eject polling is enabled
				sta 	PollingEnabled		; set enable to $FF
				lda 	#noErr				; indicate successful completion
StartPollDone	jmp 	SWIMReqDone 		; let the HOST know we're done
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Start Polling'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMStopPolling
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, n, z
;  Calls:		None
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Indicates that Disk Insert/Eject polling should be disabled.
;
;_______________________________________________________________________

				seg 	'code'
SWIMStopPolling proc
				with	SWIMVars
				bne 	StopPollDone		; return openErr if SWIM Driver not initialized
				stz 	PollingEnabled		; indicate that Insert/Eject polling is disabled
*				lda 	#noErr				; indicate successful completion
StopPollDone	jmp 	SWIMReqDone 		; let the HOST know we're done
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Polling Task'

;_______________________________________________________________________
;
;  Routine: 	jmp 	PollingTask
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		StartTMPBTimer, WaitTMPBdone, PrefetchIntoCache, TurnOffDrive,
;				DiskSelect, AdrAndSense, AdrAndStrb, CheckStaticAttr,
;				GetDriveStatus
;  Called by:	SWIMInitialize
;
;  Function:	Timer task that polls all of the installed drives,
;				turning off the drive motor if the timeout period has expired,
;				and looks for disk inserted, or eject button events, and
;				notifies the HOST processor if they are found.
;				Starts prefetching data when the motor timeout is half expired.
;
;_______________________________________________________________________

				seg 	'code'
PollingTask		proc
				entry	PrefetchIntoCache
				with	SWIMVarsZ
				with	SWIMVars
			 	ldy 	PollingTMPB 		; get the TMPB index
				lda 	#>PollRate			; get high byte of poll rate
				sta 	TMPBtimeH,y 		; store it into the TMPB
				lda 	#<PollRate			; get low byte of poll rate
				sta 	TMPBtimeL,y 		; store it into the TMPB
				jsr 	StartTMPBTimer		; start the timer
				ldy 	PollingTMPB 		; get the TMPB index
				jsr 	WaitTMPBdone		; wait for it to time out

;		when the timer runs out, we will wake up and snoop around

				lda 	<DriverActive		; see if driver is active
				bne 	PollingTask 		; skip poll if active
				lda 	<MotorTimingOut		; see if timing out a drive motor
				beq 	PollDrives			; not timing out, do the poll
				cpa		#((MotorTimeoutCount+1)/2)+1	; see if half left
				bne		@noPrefetch			; start prefetching at half way point
				jsr		PrefetchIntoCache	; prefetch into the cache while idle
@noPrefetch		dec 	<MotorTimingOut		; decrement the timeout counter
				bne 	PollingTask 		; exit if count not expired

				jsr 	TurnOffDrive		; it is time to turn off the drive motor

PollDrives		ldx 	#0					; initial drive number
PollDriveLoop	ldy 	DriveKind,x 		; get the drive kind
				beq 	PollNextDrive		; skip it if not installed
				stx 	<PollingDriveNum	; save drive number
				lda 	MediaKind,x 		; see if any media in drive
				beq 	PollDiskInPlace		; no media, check for disk inserted
				jmp 	PollEjectSwitch		; there is a disk, see if eject pressed

PollDiskInPlace lda 	PhysDriveNumber,x	; get the physical drive number
				tax 						; setup drive number for DiskSelect
				cpy 	#HD20DriveKind		; check for HD-20 (special case)
				beq 	PollHD20InPlace 	; if HD-20
				jsr 	DiskSelect			; select the drive
				ldx 	<PollingDriveNum	; restore drive number
				lda 	#rNoDiskInPlAdr		; prepare to test for disk in place
				jsr 	AdrAndSense			; read the status bit
				bne 	ThisPollDone		; if still no disk in place
				lda 	#wNoDiskInPlAdr		; make it look like the disk ejected
				jsr 	AdrAndStrb			; to reset the eject latch

				jsr 	CheckStaticAttr		; check the media kind and write protect
				
PostDiskInPlace lda 	#DiskInsertedXmtReq	; post the inserted event
				jsr 	PostEvent			; send it to the host
				beq 	EventPosted 		; all done if not error
				ldx 	<PollingDriveNum	; restore drive number
				stz 	MediaKind,x 		; indicate NoMediaKind if couldn't mount

EventPosted 	lda 	<DriverActive		; see if driver is active
				bne 	PollingExit 		; exit poll if active
ThisPollDone	ldx 	<PollingDriveNum	; restore drive number
PollNextDrive	inx 						; point to next
				cpx 	#NumberOfDrives		; compare to limit
				blt 	PollDriveLoop		; loop through all drives
PollingExit 	jsr		DeselectDrives		; deselect both drives
				jmp 	PollingTask 		; keep on polling

PollHD20InPlace jsr 	DiskSelect			; select the drive
				ldx 	<PollingDriveNum	; restore drive number
				lda 	#rMotorOffAdr		; prepare to test /busy
				jsr 	AdrAndSense 		; read the status bit
				beq 	ThisPollDone		; if /busy = 0, don't post event
				lda 	#HD20MediaKind		; indicate HD-20 media in drive
				sta 	MediaKind,x 		; update media kind
				lda 	#HD20Format 		; indicate HD-20 format
				sta 	DiskFormat,x		; update format kind
				lda 	DriveFlags,x		; get the flags
				ora 	#WriteEnabledFlag	; force write enabled
				sta 	DriveFlags,x		; update flags
				bra 	PostDiskInPlace		; post the event
				
PollEjectSwitch lda 	DriveKind,x 		; see what kind of drive we have
				cpa 	#HD20DriveKind		; check for HD-20 (special case)
				beq 	ThisPollDone		; if HD-20, can't eject, so don't check
				cpa 	#SSGCRDriveKind 	; check for 400K drive (special case)
				beq 	ThisPollDone		; if 400K drive, can't eject, so don't check

				lda 	PhysDriveNumber,x	; get the physical drive number
				tax 						; setup drive number for DiskSelect
				jsr 	DiskSelect			; select the drive
				lda 	#rEjectOnAdr		; prepare to test for eject latch set
				jsr 	AdrAndSense 		; read the status bit
				beq 	ThisPollDone		; if latch not set (eject button hadn't been pushed)
				lda 	#wNoDiskInPlAdr		; make it look like the disk ejected
				jsr 	AdrAndStrb			; to reset the eject latch
				lda 	#DiskEjectedXmtReq	; post the dismount event
				jsr 	PostEvent			; send it to the host (ignore errors)
				bra 	EventPosted 		; keep on polling

PostEvent		ldy		PollingEnabled		; see if polling is enabled
				beq		DeselectDrives		; if not, don't post any events, return noErr
				ply 						; pop pcL
				sty 	PollingPcL			; save pcL
				ply 						; pop pcH
				sty 	PollingPcH			; save pcH
				sta 	XmtKind 			; store the command kind
				stx 	XmtDriveNumber		; store the drive number
				lda 	#>XmtDriveStatus	; get page number of status message buffer
				jsr 	GetDriveStatus		; return the drive status
				lda 	#NewMessageSent		; get the new message state
				sta 	XMTState			; set the new message state
				lda 	#NewMsgSentINT		; prepare to interrupt the host
				sta 	HostControl 		; interrupt the host
DeselectDrives	lda 	#StartAction+WriteMode	; prepare to clear action and write mode
				sta 	wZeroes 			; clear action and write mode (prepare for motoroff)
				lda 	#MotorOn+Drive1Enabled+Drive2Enabled ; prepare to disable the drive
				sta 	wZeroes 			; reset MotorOn and Enables, disabling all drives
				rts 						; return to callers caller

				export	HandleXmtMsg2		; resume when Host is done
HandleXmtMsg2	lda 	#Idle				; Indicate that response was received
				sta 	XMTState			; set the new message state
				lda 	PollingPcH			; get pcH
				pha 						; push pcH
				lda 	PollingPcL			; get pcL
				pha 						; push pcL
				lda 	XmtErrorCode		; get the high byte of the error code
				ora 	XmtErrorCode+1		; or with low byte (to test for non-zero)
				rts 						; return to polling loop
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Eject'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMEject
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v,  z, c
;  Calls:		SetUpDrive, Seek, ReCalibrate, WaitDriveReady, DiskSelect. AdrAndStrb,
;				SleepLong, AdrAndSense, SleepShort, TurnOffDrive, SWIMDriveStatus
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Ejects the disk in the specified drive
;
;_______________________________________________________________________

				seg 	'code'
SWIMEject		proc
				with	SWIMVarsZ
				with	SWIMVars
				bne 	EjectError			; return if errors found
				jsr 	SetUpDrive			; select and power up the disk drive
				beq 	EjectSeek			; if no error in SetUp

				cpa 	#OffLinErr			; see if there is a disk to eject
				beq 	EjectOut			; if disk is already out

;		Ignore some of the errors returned by SetUpDrive, since we are just going to
;		eject the disk.  Only errors related to drive number will be flagged
				cpa 	#nsDrvErr			; return no such drive error if out of range
				beq 	EjectError			; report drive number errors
				cpa 	#noDriveErr 		; return no drive error if drive not installed
				bne 	EjectDiskette		; if any other error, skip seek, just eject
EjectError		tax 						; set flags based upon error code
				jmp 	SWIMReqDone 		; let the HOST know we're done

EjectSeek		ldy 	#LastCylinder		; position head to last cylinder before eject
				jsr 	Seek				; to prevent major data loss if heads damage media
				beq 	EjectDiskette		; if seek was ok, eject the disk

				jsr 	ReCalibrate 		; try to recalibrate if seek failed
				bne 	EjectDiskette		; if couldn't recalibrate, forget about seeking

				ldy 	#LastCylinder		; position head to last cylinder before eject
				jsr 	Seek				; one last attempt at seeking

EjectDiskette	jsr 	WaitDriveReady		; wait for motor speed and heads to settle

;		Re-Enable the drive, in case it was disabled by an error.
				ldy 	ReqDriveNumber		; see what drive is requested
				ldx 	PhysDriveNumber,y	; get the new physical drive number
				jsr 	DiskSelect			; select and enable the new drive

				lda 	#wMotorOffAdr		; motor off drive command
				jsr 	AdrAndStrb			; turn off the drive motor

				lda 	#<MotorStopDelay	; low byte of delay time
				ldx 	#>MotorStopDelay	; high byte of delay time
				jsr 	SleepLong			; sleep until disk stops spinning

				lda 	#wEjectOnAdr		; eject disk drive command
				jsr 	AdrAndStrb			; start the eject process

				lda 	#EjectTimeoutCount	; max number of polls
				sta 	EjectPollCount		; setup polling counter

EjectPollLoop	lda 	#<EjectPollRate 	; low byte of delay time
				ldx 	#>EjectPollRate 	; high byte of delay time
				jsr 	SleepLong			; sleep waiting for disk to eject

				lda 	#rNoDiskInPlAdr 	; prepare to see if disk is out
				jsr 	AdrAndSense 		; read the drive status bit
				bne 	EjectOut			; disk is out, wait for things to settle

				dec 	EjectPollCount		; decrement poll timeout counter
				bne 	EjectPollLoop		; loop if more polls left

EjectOut		lda 	#<EjectSettleDelay	; delay before killing enable
				jsr 	SleepShort			; wait the specified amount of time

				ldx 	<CurrentDrive		; get the drive number
				stz 	MediaKind,x 		; indicate NoMediaKind
				stz 	DiskFormat,x		; indicate uncheckedFormat
				stz 	DriveFlags,x		; clear all flags
				stz 	<LastActiveDrive	; indicate drive state is unknown

				jsr 	TurnOffDrive		; disable the drive
				lda		#noErr				; indicate success
				jmp 	SWIMDriveStatus		; finish up by returning the drive status
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Read / ReadVerify'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMRead / SWIMReadVerify
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		SetUpDrive, ReadWriteSetUp, WaitBCPBdone, SetupTrackInfo,
;				UpdateTagInfo, UpdateDataInfo, UpdateBlockInfo, WaitDriveReady,
;				Seek, ReadSectorHdr, ReadSectorData, MakeBCPBreq, ReCalibrate,
;				SkipSectorData
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Reads data from the specified drive, at the specified
;				block number, and transfers or compares the data to the host.
;
;_______________________________________________________________________

				seg 	'code'
SWIMRead		proc
				entry	SWIMReadVerify
				entry	ReadWriteSetUp
				entry	SetupTrackInfo
				entry	UpdateTagInfo
				entry	UpdateDataInfo
				entry	UpdateBlockInfo

				with	SWIMVarsZ
				with	SWIMVars
				with	SectorBuffers

SWIMReadVerify	jsr 	SetUpDrive			; select and power up the disk drive
				bne 	ReadError			; if error powering up

				ldx		<CacheMRUPtr		; get index of MRU entry
				lda		CacheNextMRU,x		; get index of next (if any)
				asl		a					; single buffer if next entry is nil
				jsr 	ReadWriteSetUp		; get the paramaters, and setup data structures
				beq		ReadTrackLoop		; if no errors, start reading

ReadDone		ldy 	<PrevTagBCPB		; get the prior tag copy request
				bmi 	@TagXferDone		; skip it if not enabled
				jsr 	WaitBCPBdone		; wait for buffer to be free before returning
				ldy 	<CurTagBCPB			; get the current tag copy request
				jsr 	WaitBCPBdone		; wait for buffer to be free before returning
@TagXferDone
				ldy 	<PrevDataBCPB		; get the prior data copy request
				jsr 	WaitBCPBdone		; wait for buffer to be free before returning
				ldx		BCPBcompRel,y		; see if it compared ok
				beq		@prevOK				; of so, leave error code alone
				lda 	#dataVerErr 		; indicate compare failed
@prevOK			ldy 	<CurDataBCPB		; get the current data copy request
				jsr 	WaitBCPBdone		; wait for buffer to be free before returning
				ldx		BCPBcompRel,y		; see if it compared ok
				beq		@curOK				; of so, leave error code alone
				lda 	#dataVerErr 		; indicate compare failed
@curOK			tax							; set flags based on error code
ReadError		jmp 	SWIMReqDone 		; return the error



;	Read the data looping through each track of the request

ReadTrackLoop	jsr		SetupTrackInfo		; find the track address and cache buffers
				bcs 	ReadDone			; if none left, or error, request is complete


;	Update the TAG data transfer addresses and counts

				ldy 	<PrevTagBCPB		; get the prior tag copy request
				bmi		@skipTagXfer		; if not enabled, skip it to save time
				jsr 	WaitBCPBdone		; wait for buffer to be free before re-using it
				jsr		UpdateTagInfo		; update the TAG data transfer addresses and counts
@skipTagXfer


;	Update the DATA data transfer addresses and counts

				ldy 	<PrevDataBCPB		; get the prior data copy request
				jsr 	WaitBCPBdone		; wait for buffer to be free before re-using it
				ldx 	BCPBcompRel,y		; get the compare relation (0 means equal)
				bne 	ReadDone			; return with error if compared not equal
				jsr		UpdateDataInfo		; update the DATA data transfer addresses and counts


;	Update the DISK block addresses and counts

				jsr		UpdateBlockInfo		; update the DISK block addresses and counts


;	Update the Buffer Status for each sector and check for cache hits

				ldy		<SectorsNeeded		; setup loop counter
				ldx		<SectBufferNum		; starting buffer number
@sectorLoop		lda		BufferStatus,x		; get the status for this sector buffer
				assert	ReadDataValid=$80	; sign bit is valid bit
				bmi		@cacheHit			; if cache hit, no need to read it in
@NeedsRead		ora		#IORequested		; indicate the Read Request
				sta		BufferStatus,x		; update the buffer status
				bra		@nextSector			; go on to the next sector

@cacheHit									; x = last buffer number for tag copy at ReadXfer
				dec		<SectorsNeeded		; since it is a hit, we don't need to read it
				beq		ReadXfer			; if all sectors hit in the cache, skip the read
@nextSector		inx							; update buffer number
				dey							; decrement loop count
				bne		@sectorLoop			; loop through all sectors of the request

;	Seek to the requested cylinder

				lda		#RecalsMax			; Max Recals allowed before hard error
				sta		<RecalsLeft			; number of Recals remaining before hard error

SeekToCyl		jsr 	WaitDriveReady		; wait for motor speed and heads to settle
				bne 	RecalAndRetry		; if error powering up
				lda 	<PhysHead			; get the head we want
				sta 	<SeekingHead		; pass it to ReadSectorHdr
				ldy 	<PhysCylinder		; get the cylinder number
				jsr 	Seek				; Seek to the requested cylinder
				lda		#IOErrorsMax		; Max I/O errors allowed before Recal
				sta		<IOErrorsLeft		; number of I/O errors remaining before Recal
				jsr 	WaitDriveReady		; wait for the seek to complete
				bne 	RecalAndRetry		; if error seeking, try to recal

;	Read the sectors requested for this track

ReadHeader		lda		#WrongSectsMax		; successful read, re-init Wrong Sector Count
				sta		<WrongSectsLeft		; number of wrong sectors remaining before Recal
ReadAfterSkip	jsr 	ReadSectorHdr		; read the next sector header that comes along
				bcs 	ReadAddrError		; handle sector address errors

				assert	ReadDataValid=$80	; sign bit is valid bit
				assert	IORequested=$40		; overflow bit is requested bit
				bvc		ReadIntoCache		; if not requested, just fill the cache

				jsr 	ReadSectorData		; read the data into IOP memory
				bne		ReadDataError		; if I/O error, handle sector data errors

				ldx		<CacheMRUPtr		; get index to current cache entry
				dec		CacheInvalidCount,x	; decrement the invalid entry count

				ldx		<SectBufferNum		; get the buffer number for this sector
				assert	(IORequested<<1)=ReadDataValid
				asl		BufferStatus,x		; set ReadDataValid, clear IORequested

				dec 	<SectorsNeeded		; account for this sector
				bne 	ReadHeader			; if more to go, keep reading

;	Transfer the data for this track to the Host buffer

ReadXfer									; x = the last buffer number transferred
				lda		TagPtrs,x			; get the tag buffer pointer for this sector
				asl		a					; unpack 9th bit into carry
				sta		<TagBufPtr			; setup low byte of tag pointer
				lda		#0					; setup to add in tag addr carry
				adc		#>TagBuffers		; add in starting page of tag buffers
				sta		<TagBufPtr+1		; setup page number
				ldy 	#TagSize-1			; index / loop counter
@TagCopyLoop	lda 	(<TagBufPtr),y		; get tag byte from tag buffer
				sta 	ReqMfsTagData,y		; return the last tag read in the message
				dey 						; update index / count
				bpl 	@TagCopyLoop		; loop through all of the bytes

				ldy 	<CurDataBCPB		; the the BCPB index
				jsr 	MakeBCPBreq 		; issue the copy request

				ldy 	<CurTagBCPB 		; the the BCPB index
				bmi 	@TagXferDone		; skip it if not enabled
				jsr 	MakeBCPBreq 		; issue the copy request
@TagXferDone	jmp		ReadTrackLoop		; transfer the data, continue with the next track


;	Load the cache with sectors that were not part of the request

ReadIntoCache	bmi		SkipSector			; if already in cache, skip it
				jsr 	ReadSectorData		; read the data into the cache buffer
				bne 	IgnoreData			; if error, don't validate it, just skip it

				ldx		<CacheMRUPtr		; get index to current cache entry
				dec		CacheInvalidCount,x	; decrement the invalid entry count

				ldx		<SectBufferNum		; get the buffer number for this sector
				lda		#ReadDataValid		; mark the cache entry valid
				sta		BufferStatus,x		; set ReadDataValid, clear IORequested

IgnoreData		dec 	<WrongSectsLeft		; adjust count of sectors searched
				bpl		ReadAfterSkip		; if count not exhausted, keep on reading
				lda 	#sectNFErr			; assume sector not found
RecalAndRetry	dec		<RecalsLeft			; see if RecalsMax exceeded
				bmi		ToReadDone			; if too many retrys, die with hard error
				jsr 	ReCalibrate 		; move heads to track zero, init the drive
				beq 	SeekToCyl			; if Recal OK, retry seek
ToReadDone		jmp		ReadDone			; if Recal failed, die with hard error


;	Error handling and recovery

ReadAddrError	beq		SkipSector			; if sect num out of range, just ignore it
				cpa		#SeekErr			; see if it was a seek error
				beq		RecalAndRetry		; if so, recal, and then retry
DataError		ldx 	<CurrentDrive		; get current drive number
				inc		ErrorCountL,x		; update the soft error count for this drive
				bne		@ErrorCountDone		; if no carry, skip high byte
				inc		ErrorCountH,x		; update the high byte of the error count
@ErrorCountDone	dec		<IOErrorsLeft		; number of I/O errors remaining before Recal
				bmi		RecalAndRetry		; if too many I/O errors, recal and try again
				bra 	ReadHeader			; if more to go, keep reading

ReadDataError	cpa		<DMARetryStatus		; see if it was a DMA timeout error
				beq		IgnoreData			; if so, just re-try it without ints during DMA
				cpa		#<gcrOnMfmErr		; see if GCR data on High Density Disk
				bne 	DataError			; if real I/O error, handle sector data errors
				bra		ToReadDone			; if GCR on MFM, don't waste time retrying

SkipSector		jsr 	SkipSectorData		; give ADB some time
				bra 	IgnoreData			; ignore this sector
				endwith
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Write'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMWrite
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		SetUpDrive, ReadWriteSetUp, SetupTrackInfo,
;				UpdateTagInfo, UpdateDataInfo, UpdateBlockInfo, MakeBCPBreq,
;				WaitDriveReady, WaitMotorSettled, Seek, WaitBCPBdone, ReadSectorHdr,
;				WriteSectorData, ReadSectorData, ReCalibrate, SkipSectorData
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Write data to the specified drive, at the specified
;				block number, and transfers data from the host.
;
;_______________________________________________________________________

				seg 	'code'
SWIMWrite		proc
				entry	ReadWriteSetUp
				entry	SetupTrackInfo
				entry	UpdateTagInfo
				entry	UpdateDataInfo
				entry	UpdateBlockInfo

				with	SWIMVarsZ
				with	SWIMVars
				with	SectorBuffers
				stz		<WritingForFmt		; we are not writing for format
				jsr 	SetUpDrive			; select and power up the disk drive
				bne 	WriteError			; if error powering up

				ldx 	<CurrentDrive		; get drive number
				lda 	#WriteEnabledFlag	; prepare to test write enable
				bit 	DriveFlags,x		; test the enable bit
				beq 	WriteProtected		; if write protected, return the error
				sec							; single buffer only
				jsr 	ReadWriteSetUp		; get the paramaters, and setup data structures
				beq		WriteTrackLoop		; if no errors, start the write request

WriteDone		tax							; set flags based on error code
WriteError		jmp 	SWIMReqDone 		; return the error
WriteProtected	lda 	#wPrErr 			; indicate write protected disk
				bra 	WriteError			; return the error



;	Write the data looping through each track of the request

WriteTrackLoop	jsr		SetupTrackInfo		; find the track address and cache buffers
				bcs 	WriteDone			; if none left, or error, request is complete


;	Update the DATA data transfer addresses and counts

				ldy 	<PrevDataBCPB		; get the prior data copy request
				jsr		UpdateDataInfo		; update the DATA data transfer addresses and counts
				jsr 	MakeBCPBreq 		; issue the copy request


;	Update the DISK block addresses and counts

				jsr		UpdateBlockInfo		; update the DISK block addresses and counts


;	Update the Buffer Status for each sector

				lda		<SectorsNeeded		; setup loop counter
				sta		<LoopCountL			; initialize loop counter
				ldx		<SectBufferNum		; starting buffer number
				bra		@loopStart			; skip the tag block increment the first time
@sectorLoop		inc 	ReqMfsTagData+7		; increment tag block number (low byte)
				bne 	@loopStart	 		; if no carry out
				inc 	ReqMfsTagData+6		; increment tag block number (high byte)
@loopStart		lda		BufferStatus,x		; get the status for this sector buffer
				assert	ReadDataValid=$80	; sign bit is valid bit
				bpl		@setupTag			; if already invalid, don't need to invalidate
				txa							; preserve x
				ldx		<CacheMRUPtr		; get index to current cache entry
				inc		CacheInvalidCount,x	; increment the invalid entry count
				tax							; restore x
@setupTag		lda		TagPtrs,x			; get the tag buffer pointer for this sector
				asl		a					; unpack 9th bit into carry
				sta		<TagBufPtr			; remember it for the tag copy
				lda		#0					; setup to add in tag addr carry
				adc		#>TagBuffers		; add in starting page of tag buffers
				sta		<TagBufPtr+1		; setup page number
				ldy 	#TagSize-1			; index / loop counter
@TagCopyLoop	lda 	ReqMfsTagData,y		; setup the tag from the message request
				sta 	(<TagBufPtr),y		; store the tags into the tag buffer
				dey 						; update index / count
				bpl 	@TagCopyLoop		; loop through all of the bytes
				lda		#IORequested		; indicate the Write Request, clear valid flag
				sta		BufferStatus,x		; update the buffer status
@nextSector		inx							; update buffer number
				dec		<LoopCountL			; decrement loop count
				bne		@sectorLoop			; loop through all sectors of the request


;	Update the TAG data transfer addresses and counts

				ldy 	<PrevTagBCPB		; get the prior tag copy request
				bmi		@skipTagXfer		; if not enabled, skip it to save time
				jsr		UpdateTagInfo		; update the TAG data transfer addresses and counts
				jsr 	MakeBCPBreq 		; issue the copy request
@skipTagXfer


;	Seek to the requested cylinder

				lda		#RecalsMax			; Max Recals allowed before hard error
				sta		<RecalsLeft			; number of Recals remaining before hard error

SeekToCyl		jsr 	WaitDriveReady		; wait for motor speed and heads to settle
				bne 	RecalAndRetry		; if error powering up
				jsr 	WaitMotorSettled	; wait for motor speed to be in 1.5% tolerance
				lda 	<PhysHead			; get the head we want
				sta 	<SeekingHead		; pass it to WriteSectorHdr
				ldy 	<PhysCylinder		; get the cylinder number
				jsr 	Seek				; Seek to the requested cylinder

;	Transfer the data for this track from the Host buffer

				ldy 	<CurDataBCPB		; get the DATA BCPB index
				jsr 	WaitBCPBdone 		; wait for the data to arrive
				ldy 	<CurTagBCPB 		; get the TAG BCPB index
				bmi 	@TagXferDone		; skip it if not enabled
				jsr 	WaitBCPBdone 		; wait for the data to arrive
@TagXferDone
				lda		#IOErrorsMax		; Max I/O errors allowed before Recal
				sta		<IOErrorsLeft		; number of I/O errors remaining before Recal
				jsr 	WaitDriveReady		; wait for the seek to complete
				bne 	RecalAndRetry		; if error seeking, try to recal

;	Write the sectors requested for this track

ReadHeader		lda		#WrongSectsMax		; successful write, re-init Wrong Sector Count
				sta		<WrongSectsLeft		; number of wrong sectors remaining before Recal
ReadAfterSkip	jsr 	ReadSectorHdr		; read the next sector header that comes along
				bcs 	ReadAddrError		; handle sector address errors

				assert	ReadDataValid=$80	; sign bit is valid bit
				assert	IORequested=$40		; overflow bit is requested bit
				bvc		ReadIntoCache		; if not requested, just fill the cache

				jsr 	WriteSectorData		; write the data from IOP memory
				beq		@writeOK			; if successfull write, update sector info

				cpa		<DMARetryStatus		; see if it was a DMA timeout error
				beq		IgnoreData			; if so, just re-try it without ints during DMA
				bra 	WriteDataError		; if real I/O error, handle sector data errors

@writeOK		ldx		<SectBufferNum		; get the buffer number for this sector
				stz		BufferStatus,x		; clear ReadDataValid, clear IORequested

				dec 	<SectorsNeeded		; account for this sector
				bne 	ReadHeader			; if more to go, keep writing

;	If there are no valid entries left on this track, we will invalidate the entire
;	track so that it will be reused soonest even though it is MRU.
				ldx		<CacheMRUPtr		; get index to current cache entry
				lda		CacheInvalidCount,x	; get the invalid entry count
				cpa		<PhysSectsPerHead	; see if all sectors are invalid
				blt		@validDone			; if some sectors valid, leave the track valid
				lda		#-1					; if all invalid, invalidate the entire track
				sta		CacheInvalidCount,x	; decrement the invalid entry count
@validDone		jmp 	WriteTrackLoop		; transfer the data, continue with the next track


;	Load the cache with sectors that were not part of the request

ReadIntoCache	bmi		SkipSector			; if already in cache, skip it
				ldy		#GiveADBTimeMask	; see if ADB has been active recently
				and		<ADBActivity		; check against the ADB activity history
				bne		SkipSector			; if so, don't fill the cache, give ADB some time instead
				jsr 	ReadSectorData		; read the data into the cache buffer
				bne 	IgnoreData			; if error, don't validate it, just skip it

				ldx		<CacheMRUPtr		; get index to current cache entry
				dec		CacheInvalidCount,x	; decrement the invalid entry count

				ldx		<SectBufferNum		; get the buffer number for this sector
				lda		#ReadDataValid		; mark the cache entry valid
				sta		BufferStatus,x		; set ReadDataValid, clear IORequested

IgnoreData		dec 	<WrongSectsLeft		; adjust count of sectors searched
				bpl		ReadAfterSkip		; if count not exhausted, keep on reading
				lda 	#sectNFErr			; assume sector not found
RecalAndRetry	dec		<RecalsLeft			; see if RecalsMax exceeded
				bmi		ToWriteDone			; if too many retrys, die with hard error
				jsr 	ReCalibrate 		; move heads to track zero, init the drive
				beq 	SeekToCyl			; if Recal OK, retry seek
ToWriteDone		jmp		WriteDone			; if Recal failed, die with hard error


;	Error handling and recovery

ReadAddrError	beq		SkipSector			; if sect num out of range, just ignore it
				cpa		#SeekErr			; see if it was a seek error
				beq		RecalAndRetry		; if so, recal, and then retry
WriteDataError	ldx 	<CurrentDrive		; get current drive number
				inc		ErrorCountL,x		; update the soft error count for this drive
				bne		@ErrorCountDone		; if no carry, skip high byte
				inc		ErrorCountH,x		; update the high byte of the error count
@ErrorCountDone
;	if there was a problem reading a header, or writing data, recalibrate and retry
;	first, to reduce the amount of data that will be written if the head is slightly
;	off track.  If all of the recals have been used, then we will stay on track and
;	use the remaining retries on this track.
				ldx		<RecalsLeft			; see if RecalsMax reached
				bne		RecalAndRetry		; if not, recal, and then retry
				dec		<IOErrorsLeft		; number of I/O errors remaining before Recal
				bmi		RecalAndRetry		; if too many I/O errors, recal and try again
				bra 	ReadHeader			; if more to go, keep reading

SkipSector		jsr 	SkipSectorData		; give ADB some time
				bra 	IgnoreData			; ignore this sector
				endwith
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Read / Write Setup'

;_______________________________________________________________________
;
;  Routine: 	jsr 	ReadWriteSetUp
;  Inputs:		c - Cleared if double buffering allowed
;				ReqKind - (WriteReq,ReadReq,ReadVerifyReq)
;  Outputs: 	A, n, z - error status code
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		SetupBCPB, BlockToPhysAddr
;  Called by:	SWIMRead, SWIMReadVerify, SWIMWrite
;
;  Function:	Utility routines used by Read, ReadVerify, and Write
;
;_______________________________________________________________________

				seg 	'code'
ReadWriteSetUp	proc
				entry	TagCopyCmd
				entry	DataCopyCmd

				with	SWIMVarsZ
				with	SWIMVars
				with	SectorBuffers

;		setup the fields in the TagBCPB

				ldx		#-1					; assume no tag buffer
				ldy		#-1					; assume no tag buffer
				lda		TagBuffEnabled		; see if we need tag buffer copying
				beq		@skipTagSetup		; if not, skip it to save time
				ldx		ReqKind				; get the I/O command
				lda		TagCopyCmd-WriteReq,x	; get the copy command
				ldy 	<TagBCPB 			; get the BCPB index
				sta 	BCPBcmd,y			; setup the block copy operation
				tya							; assume single buffer
				tax							; both prev and current are the same
				bcs		@singleTagBuff		; if only 1 cache entry, don't double buffer reads
				ldx 	<TagBCPB2 			; use second buffer if enough cache entries
@singleTagBuff
				lda 	TagBuffHostAddr+0	; setup the initial HOST RAM Address
				sta 	BCPBhostAddrB0,y
				lda 	TagBuffHostAddr+1
				sta 	BCPBhostAddrB1,y
				lda 	TagBuffHostAddr+2
				sta 	BCPBhostAddrB2,y
				lda 	TagBuffHostAddr+3
				sta 	BCPBhostAddrB3,y

				lda 	#0					; initialize count to zero
				sta 	BCPBbyteCountH,y
				sta 	BCPBbyteCountL,y
@skipTagSetup
				sty		<CurTagBCPB			; setup current
				stx		<PrevTagBCPB		; setup previous

;		setup the fields in the DataBCPB

				ldy		<DataBCPB			; get the BCPB index
				sty		<CurDataBCPB		; setup current
				tya							; assume single buffer
				bcs		@singleDataBuff		; if only 1 entry, don't double buffer reads
				lda		<DataBCPB2			; get the second BCPB index
@singleDataBuff	sta		<PrevDataBCPB		; setup previous
				tax							; get prev buffer
				stz		BCPBcompRel,x		; assume verify good for previous buffer

				ldx 	ReqKind 			; get the request kind
				lda		DataCopyCmd-WriteReq,x	; get the copy command

				ldx		#ReqRAMAddress-RCVData
				jsr		SetupBCPB			; setup the BCPB fields, use ReqRAMAddress

;		get remaining parameters from the request message

				lda 	ReqBlockNumber+2	; use low word of long block number
				sta 	<LogicalBlockH
				lda 	ReqBlockNumber+3
				sta 	<LogicalBlockL

				lda 	ReqBlockCount+2		; use low word of long block count
				sta 	<BlocksToDoH
				lda 	ReqBlockCount+3
				sta 	<BlocksToDoL

				lda 	ReqMfsTagData+6		; get high byte of tag block number
				sta 	<TagBlockNumH		; initialize the count
				lda 	ReqMfsTagData+7		; get low byte of tag block number
				sta 	<TagBlockNumL		; initialize the count

;		check high word of block number and block count

				lda 	ReqBlockNumber+0
				ora 	ReqBlockNumber+1
				ora 	ReqBlockCount+0
				ora 	ReqBlockCount+1
				beq 	SetupDirection		; if block number / block count in range
				lda 	#paramErr			; Parameter Error if out of range
SetupError		rts 						; return with result

;		Determine the Seek Direction, and adjust addresses if needed

SetupDirection
				lda		#1					; indicate incrementing track order
				sta		<SeekDirection		; setup the default direction
				sta		<SkipBlockToPhys	; skip first call to BlockToPhys
				jsr 	BlockToPhysAddr		; translate the starting block number
				bne 	SetupError			; die on errors

;	See if requested data does not cross cylinders (no seeks within the request)

				lda		<PhysSectsPerCyl	; Number of Sectors per cylinder
				sec							; setup for subtraction
				sbc		<PhysCylSector		; compute sectors remaining before seek needed
				cpa		<BlocksToDoL		; compare to number of sectors requested
				lda		#0					; setup to compare high byte too
				sbc		<BlocksToDoH		; see if sectors till end of cyl >= BlocksToDo
				bge		@setupDone			; if request fits on 1 cylinder, seek direction doesn't matter

;	See if we would need to seek ahead to reach the first cylinder of the request

				ldx 	<CurrentDrive		; get the active drive number
				lda 	<PhysCylinder		; get desired cylinder
				sec							; setup for subtraction
				sbc 	CurrentCylinder,x	; step count := desired - current
				bge 	@setupDone			; if seeking ahead, use incrementing track order
				ldy 	CylinderValid,x		; see if cylinder is valid
				bne 	@setupDone			; if invalid, just use default

;	See which end of the request the head is closest to

				asl		a					; negative step count * 2  (also set carry for subtract)
				sbc		<PhysCylinder		; compute negative end cyl where seek ahead is a win
				sta		<LoopCountL			; save the negative cylinder number
				ldx		<BlocksToDoH		; get high byte
				lda		<BlocksToDoL		; get low byte
				bne		@noBlockBorrow		; if non-zero, decrement won't borrow
				dex							; if borrow, decrement upper byte
@noBlockBorrow	dea							; compute BlocksToDo - 1
				clc							; clear carry for addition
				adc		<LogicalBlockL		; LogicalBlock := LogicalBlock + BlocksToDo - 1
				sta 	<LogicalBlockL
				txa							; get BlocksToDoH
				adc		<LogicalBlockH
				sta		<LogicalBlockH
				jsr 	BlockToPhysAddr		; translate the ending block number
				bne 	SetupError			; die on errors
				lda		<LoopCountL			; get the negative cylinder number
				clc							; setup carry for addition
				adc		<PhysCylinder		; see if ending cylinder is closer to head
				blt		@seekReverse		; if so, start at end, use decrementing track order

;	Head is closer to beginning of request

				stz		<SkipBlockToPhys	; Don't skip first call to BlockToPhys
				lda 	ReqBlockNumber+2	; restore starting logical block number
				sta 	<LogicalBlockH
				lda 	ReqBlockNumber+3
				sta 	<LogicalBlockL

@setupDone		lda		#noErr				; indicate success
				rts 						; return with result


;	Head is closer to end of request

@seekReverse	lda		#-1					; indicate decrementing track order
				sta		<SeekDirection		; setup the reverse direction

;		correct the address in the TagBCPB

				ldx		<CurTagBCPB			; get current tag BCPB, see if tags enabled
				bmi		@skipTagSetup		; if not enabled, skip it to save time
				ldy		#TagSize			; multiplier/loop counter
@tagMultLoop	clc							; clear carry for addition
				lda 	BCPBhostAddrB3,x	; get low byte
				adc		<BlocksToDoL		; add in count
				sta 	BCPBhostAddrB3,x	; update low byte
				lda 	BCPBhostAddrB2,x	; get next byte
				adc		<BlocksToDoH		; add in count
				sta 	BCPBhostAddrB2,x	; update next byte
				bcc		@noTagCarry			; see if carry out
				inc		BCPBhostAddrB1,x	; propagate the carry
				bne		@noTagCarry			; see if carry out
				inc		BCPBhostAddrB0,x	; propagate the carry
@noTagCarry		dey							; update loop counter
				bne		@tagMultLoop		; loop until multiply is done
@skipTagSetup


;		correct the address in the DataBCPB

				ldx		<CurDataBCPB		; get current data BCPB
				ldy		#BlockSize/256		; multiplier/loop counter
@dataMultLoop	clc							; clear carry for addition
				lda 	BCPBhostAddrB2,x	; get low byte
				adc		<BlocksToDoL		; add in count
				sta 	BCPBhostAddrB2,x	; update low byte
				lda 	BCPBhostAddrB1,x	; get next byte
				adc		<BlocksToDoH		; add in count
				sta 	BCPBhostAddrB1,x	; update next byte
				bcc		@noDataCarry		; see if carry out
				inc		BCPBhostAddrB0,x	; propagate the carry
@noDataCarry	dey							; update loop counter
				bne		@dataMultLoop		; loop until multiply is done


;		correct the Tag Block Numbers

				clc							; clear carry for addition
				lda		<BlocksToDoL		; get low byte
				adc		<TagBlockNumL		; TagBlockNum := TagBlockNum + BlocksToDo
				sta		<TagBlockNumL
				lda		<BlocksToDoH		; get high byte
				adc		<TagBlockNumH
				sta		<TagBlockNumH
				bra		@setupDone			; indicate success and return
				title	'IOP SWIM Driver - Copy Host Address'

;_______________________________________________________________________
;
;  Routine: 	jsr 	SetupBCPB
;  Inputs:		A - BCPB command value
;				X - Offset into RVCData of address to copy
;				Y - BCPB index of destination BCPB
;  Outputs: 	Y - set to $FF if HostAddr was zero, otherwise remains unchanged
;  Destroys:	A, Y, n, v, z, c, LoopCountL
;  Calls:		none
;  Called by:	ReadWriteSetUp, SWIMFormat, SWIMGetRawData
;
;  Function:	BCPBcmd[y] := A
;				BCPBbyteCount[y] := $0000
;				BCPBcompRel[y] := $00
;				BCPBhostAddr[y] := RVCData[x]
;				if RVCData[x] = 0 then y := $FF
;
;_______________________________________________________________________

				entry	SetupBCPB
SetupBCPB		sta 	BCPBcmd,y			; setup the block copy operation

				lda 	#0					; initialize count to zero
				sta 	BCPBbyteCountH,y
				sta 	BCPBbyteCountL,y

				sta 	BCPBcompRel,y		; assume verify good for current buffer

				lda 	RCVData+0,x			; setup the Sector Data HOST RAM Address
				sta 	BCPBhostAddrB0,y
				sta		<LoopCountL			; or the bytes to check for nil
				lda 	RCVData+1,x
				sta 	BCPBhostAddrB1,y
				tsb		<LoopCountL			; or the bytes to check for nil
				lda 	RCVData+2,x
				sta 	BCPBhostAddrB2,y
				tsb		<LoopCountL			; or the bytes to check for nil
				lda 	RCVData+3,x
				sta 	BCPBhostAddrB3,y
				ora		<LoopCountL			; or the bytes to check for nil
				bne		@notNil				; if valid address, use it
				ldy		#-1					; otherwise, remember that it is disabled
@notNil			rts							; all done
				title	'IOP SWIM Driver - Inc/Dec Host Address'

;_______________________________________________________________________
;
;  Routine: 	jsr 	IncHostAddr/DecHostAddr
;  Inputs:		x - Previous BCPB index
;				y - Current BCPB index
;  Outputs: 	none
;  Destroys:	A, n, v, z, c
;  Calls:		none
;  Called by:	UpdateTagInfo, UpdateDataInfo
;
;  Function:	BCPBhostAddr[y] := BCPBhostAddr[x] + BCPBbyteCount[x]
;				BCPBcmd[y] := BCPBcmd[x]
;
;				BCPBhostAddr[y] := BCPBhostAddr[x] - BCPBbyteCount[y]
;				BCPBcmd[y] := BCPBcmd[x]
;
;_______________________________________________________________________

				entry	IncHostAddr
IncHostAddr		clc 						; setup carry for addition
				lda 	BCPBhostAddrB3,x	; update the RAM Address
				adc 	BCPBbyteCountL,x	; by the prior byte count
				sta 	BCPBhostAddrB3,y
				lda 	BCPBhostAddrB2,x
				adc 	BCPBbyteCountH,x
				sta 	BCPBhostAddrB2,y
				lda 	BCPBhostAddrB1,x
				adc 	#0
				sta 	BCPBhostAddrB1,y
				lda 	BCPBhostAddrB0,x
				adc 	#0
				sta 	BCPBhostAddrB0,y
		 		lda 	BCPBcmd,x			; setup the block copy operation
		 		sta 	BCPBcmd,y
				rts 						; all done

				entry	DecHostAddr
DecHostAddr		sec 						; setup carry for subtraction
				lda 	BCPBhostAddrB3,x	; update the RAM Address
				sbc 	BCPBbyteCountL,y	; by the prior byte count
				sta 	BCPBhostAddrB3,y
				lda 	BCPBhostAddrB2,x
				sbc 	BCPBbyteCountH,y
				sta 	BCPBhostAddrB2,y
				lda 	BCPBhostAddrB1,x
				sbc 	#0
				sta 	BCPBhostAddrB1,y
				lda 	BCPBhostAddrB0,x
				sbc 	#0
				sta 	BCPBhostAddrB0,y
		 		lda 	BCPBcmd,x			; setup the block copy operation
		 		sta 	BCPBcmd,y
				rts 						; all done
				title	'IOP SWIM Driver - Setup Track Info'

;_______________________________________________________________________
;
;  Routine: 	jsr 	SetupTrackInfo
;  Inputs:		BlocksToDoH/L	- remaining blocks to read/write
;				CurrentDrive	- active drive number
;				LogicalBlockH/L - Logical Block Number
;  Outputs: 	c - true if error returned, or no more blocks
;				A - error status code
;				n,z - based on value returned in A
;				PhysCylinder	- Physical Cylinder number
;				PhysHead		- Physical Head number
;				PhysSector		- Physical Sector number
;				PhysSectsPerHead- Physical Number of Sectors per head on this Cylinder
;				PhysSectsPerCyl - Physical Number of Sectors on this Cylinder
;				CacheMRUPtr		- Index of cache entry for this Cylinder/Head
;				BaseBufferNum	- Buffer number for sector 0 of this track
;				SectBufferNum	- Buffer number for current sector of this track
;				SectorsNeeded	- number of sectors needed on this track
;  Destroys:	X, Y, n, v, z, c
;  Calls:		BlockToPhysAddr, FindCacheEntry
;  Called by:	SWIMRead, SWIMReadVerify, and SWIMWrite
;
;  Function:	Sets up the variables that describe this track of the request.
;
;_______________________________________________________________________

				entry	SetupTrackInfo
SetupTrackInfo	lda 	<BlocksToDoH 		; lots of blocks left?
				ora 	<BlocksToDoL 		; how many left in the request
				beq 	@Done				; if none left, request is complete

				lda		<SkipBlockToPhys	; see if we should skip the BlockToPhys call
				stz		<SkipBlockToPhys	; Don't skip it next time
				bne		@skipped			; if already called, save time by not calling again
				jsr 	BlockToPhysAddr		; translate the starting block number
				bne 	@Done				; die on errors
@skipped		jsr		FindCacheEntry		; search the cache for this track

				lda		CacheBufferNum,x	; get starting buffer number for this track
				sta		<BaseBufferNum		; remember the starting buffer for this track
				bit		<SeekDirection		; see which way we are going
				bmi		@decrementing		; if decrementing, handle it specially

				clc							; setup for addition
				adc		<PhysSector			; get starting sector number of request
				sta		<SectBufferNum		; save the starting buffer number

				sec 						; setup for subtract
				lda 	<PhysSectsPerHead	; max sectors needed
				sbc 	<PhysSector 		; compute sectors till end of track
				sta 	<SectorsNeeded		; remember how many sectors are needed
				lda 	<BlocksToDoH 		; lots of blocks left?
				bne 	@GotNeeded			; if so, Sectors needed is correct
				lda 	<BlocksToDoL 		; how many left in the request
				cpa 	<SectorsNeeded		; does track have more than we need
				bge 	@GotNeeded			; if not, use remainder of track
				sta 	<SectorsNeeded		; otherwise, use just what was requested
@GotNeeded		clc							; indicate track exists
				rts							; all done
@Done			sec							; indicate error, or no more blocks
				rts							; all done

@decrementing	sta		<SectBufferNum		; save the starting buffer number
				lda		<PhysSector			; get ending sector number of request
				ina							; + 1 to get sector count
				sta 	<SectorsNeeded		; remember how many sectors are needed
				ldx 	<BlocksToDoH 		; lots of blocks left?
				bne 	@GotNeeded			; if so, Sectors needed is correct

				sec 						; setup for subtract
				sbc		<BlocksToDoL		; see if SectorsNeeded < BlocksToDo
				blt		@GotNeeded			; if so, Sectors Needed is correct
				clc							; setup for addition
				adc		<BaseBufferNum		; get starting buffer number of track
				sta		<SectBufferNum		; save the starting buffer number
				lda 	<BlocksToDoL 		; how many left in the request
				sta 	<SectorsNeeded		; remember how many sectors are needed
*				clc							; indicate track exists
				rts							; all done
				title	'IOP SWIM Driver - Update Tag Info'

;_______________________________________________________________________
;
;  Routine: 	jsr 	UpdateTagInfo
;  Inputs:		CurTagBCPB		- Current BCPB index
;				y, PrevTagBCPB	- Previous BCPB index
;				SectBufferNum	- Buffer number for current sector of this track
;				SectorsNeeded	- number of sectors needed on this track
;  Outputs:		y, CurTagBCPB	- former PrevTagBCPB
;				PrevTagBCPB		- former CurTagBCPB
;				BCPBiopAddrH/L	- IOP address of first tag to transfer for this track
;				BCPBhostAddr	- Updated to point to corresponding Host address of tag buffer 
;				BCPBbyteCountH/L- Number of bytes of tag data for this track
;  Destroys:	A, X, n, v, z, c
;  Calls:		IncHostAddr, DecHostAddr
;  Called by:	SWIMRead, SWIMReadVerify, SWIMWrite, SWIMFormat
;
;  Function:	Updates the variables associated with the tag data transfer for this track.
;
;_______________________________________________________________________

				entry	UpdateTagInfo		; update the TAG data transfer addresses and counts
UpdateTagInfo	ldx 	<CurTagBCPB			; get the current tag copy request
				bit		<SeekDirection		; see which way we are going
				bmi		@decrementing		; if decrementing, handle it specially
				jsr 	IncHostAddr			; point to new host address
@common			stx 	<PrevTagBCPB		; swap buffers cur -> prev
				sty 	<CurTagBCPB			; swap buffers prev -> cur
				ldx		<SectBufferNum		; get the starting buffer number
				lda		TagPtrs,x			; get the tag buffer offset
				asl		a					; unpack 9th bit into carry
				sta		BCPBiopAddrL,y		; remember the tag offset
				lda		#0					; setup to add in tag addr carry
				sta		BCPBbyteCountH,y	; high byte of byte count always zero
				adc		#>TagBuffers		; add in starting page of tag buffers
				sta		BCPBiopAddrH,y		; remember the tag page
				assert	TagSize=12			; multiply by 12 is hard coded
				lda 	<SectorsNeeded		; sector count for this track
				asl		a					; SectorsNeeded*2
				adc		<SectorsNeeded		; SectorsNeeded*3
				ldx		<CurTagBCPB			; x := y := CurTagBCPB
				asl		a					; SectorsNeeded*6
				rol		BCPBbyteCountH,x	; shift into high byte of byte count
				asl		a					; SectorsNeeded*12
				rol		BCPBbyteCountH,x	; shift into high byte of byte count
				sta		BCPBbyteCountL,y	; setup low byte of byte count
				rts							; all done

@decrementing	jsr		@common				; do everything but increment
				ldx 	<PrevTagBCPB		; get the previous tag copy request
				jmp		DecHostAddr			; exit through DecHostAddr
				title	'IOP SWIM Driver - Update Data Info'

;_______________________________________________________________________
;
;  Routine: 	jsr 	UpdateDataInfo
;  Inputs:		CurDataBCPB		- Current BCPB index
;				y, PrevTagBCPB	- Previous BCPB index
;				SectBufferNum	- Buffer number for current sector of this track
;				SectorsNeeded	- number of sectors needed on this track
;  Outputs:		y, CurDataBCPB	- former PrevTagBCPB
;				PrevTagBCPB		- former CurTagBCPB
;				BCPBiopAddrH/L	- IOP address of first sector to transfer for this track
;				BCPBhostAddr	- Updated to point to corresponding Host address of sector buffer 
;				BCPBbyteCountH/L- Number of bytes of sector data for this track
;  Destroys:	A, X, n, v, z, c
;  Calls:		IncHostAddr, DecHostAddr
;  Called by:	SWIMRead, SWIMReadVerify, SWIMWrite, SWIMFormat
;
;  Function:	Updates the variables associated with the sector data transfer for this track.
;
;_______________________________________________________________________

				entry	UpdateDataInfo		; update the DATA data transfer addresses and counts
UpdateDataInfo	ldx 	<CurDataBCPB		; get the current data copy request
				bit		<SeekDirection		; see which way we are going
				bmi		@decrementing		; if decrementing, handle it specially
				jsr 	IncHostAddr			; point to new host address
@common			stx 	<PrevDataBCPB		; swap buffers cur -> prev
				sty 	<CurDataBCPB		; swap buffers prev -> cur
				lda		<SectBufferNum		; get the starting buffer number
				assert	BlockSize=(2*256)	; multiply by 2 is hard coded
				asl		a					; buffer number * 2 for 2*256 byte blocks
				adc		#>DataBuffers		; add in starting page of data buffers
				sta		BCPBiopAddrH,y		; remember the data page
				lda 	<SectorsNeeded		; sector count for this track
				asl		a					; SectorsNeeded*2 for 2*256 byte blocks
				sta		BCPBbyteCountH,y	; setup high byte of byte count
				lda		#0					; setup data buffer offset
				sta		BCPBiopAddrL,y		; data is page aligned in the buffer
				sta		BCPBbyteCountL,y	; setup low byte of byte count
				rts							; all done

@decrementing	jsr		@common				; do everything but increment
				ldx 	<PrevDataBCPB		; get the previous data copy request
				jmp		DecHostAddr			; exit through DecHostAddr
				title	'IOP SWIM Driver - Update Block Info'

;_______________________________________________________________________
;
;  Routine: 	jsr 	UpdateBlockInfo
;  Inputs:		LogicalBlockH/L - Logical Block Number
;				SectorsNeeded	- number of sectors needed on this track
;				BlocksToDoH/L	- remaining blocks to read/write
;				TagBlockNumH/L	- tag buffer 
;  Outputs:		LogicalBlockH/L - Logical Block Number
;				BlocksToDoH/L	- remaining blocks to read/write
;  Destroys:	A, n, v, z, c
;  Calls:		none
;  Called by:	SWIMRead, SWIMReadVerify, SWIMWrite
;
;  Function:	Updates the variables associated with the block addressing for this track.
;
;_______________________________________________________________________

				entry	UpdateBlockInfo		; update the DISK block addresses and counts
UpdateBlockInfo	lda 	<BlocksToDoL 		; get low byte of block count
				sec 						; setup for subtraction
				sbc 	<SectorsNeeded		; account for blocks on this track
				sta 	<BlocksToDoL 		; update block count
				bcs 	@BlockCountDone		; if no borrow
				dec 	<BlocksToDoH 		; update high byte
@BlockCountDone
				bit		<SeekDirection		; see which way we are going
				bmi		@decrementing		; if decrementing, handle it specially
				lda 	<LogicalBlockL		; get block number
				clc 						; setup for addition
				adc 	<SectorsNeeded		; account for blocks on this track
				sta 	<LogicalBlockL		; update block number
				bcc 	@BlockNumIncDone	; if no carry
				inc 	<LogicalBlockH
@BlockNumIncDone
				lda 	<TagBlockNumL		; get low byte of tag block number
				sta 	ReqMfsTagData+7		; initialize the count
				clc 						; setup for addition
				adc 	<SectorsNeeded		; account for blocks on this track
				sta		<TagBlockNumL		; update tag block number
				lda 	<TagBlockNumH		; get high byte of tag block number
				sta 	ReqMfsTagData+6		; initialize the count
				adc		#0					; add in carry
				sta		<TagBlockNumH		; update tag block number
				rts							; all done


@decrementing	lda 	<LogicalBlockL		; get block number
				sec 						; setup for subtraction
				sbc 	<SectorsNeeded		; account for blocks on this track
				sta 	<LogicalBlockL		; update block number
				bcs 	@BlockNumDecDone	; if no borrow
				dec 	<LogicalBlockH
@BlockNumDecDone
				lda 	<TagBlockNumL		; get low byte of tag block number
				sec 						; setup for subtraction
				sbc 	<SectorsNeeded		; account for blocks on this track
				sta 	ReqMfsTagData+7		; initialize the count
				sta		<TagBlockNumL		; update tag block number
				lda 	<TagBlockNumH		; get high byte of tag block number
				sbc		#0					; subtract borrow
				sta 	ReqMfsTagData+6		; initialize the count
				sta		<TagBlockNumH		; update tag block number
				rts							; all done
				endwith
				endwith
				endwith
				endproc

				seg 	'constants'
TagCopyCmd		proc
;		table of Tag Copy BCPBcmd bytes indexed by (ReqKind)-WriteReq
				dc.b	bcHOSTtoIOP			;  0 - WriteReq
				dc.b	bcIOPtoHOST			;  1 - ReadReq
				dc.b	bcIOPtoHOST			;  2 - ReadVerifyReq

				entry	DataCopyCmd
DataCopyCmd
;		table of Data Copy BCPBcmd bytes indexed by (ReqKind)-WriteReq
				dc.b	bcHOSTtoIOP			;  0 - WriteReq
				dc.b	bcIOPtoHOST			;  1 - ReadReq
				dc.b	bcCompare			;  2 - ReadVerifyReq
				endproc
				title	'IOP SWIM Driver - SWIM Format'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMFormat
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		SetUpDrive, SetupBCPB, GetDriveStatus, WaitDriveReady,
;				CreateTrackCache, ReCalibrate, WaitMotorSettled, Seek,
;				UpdateTagInfo, MakeBCPBreq, WaitBCPBdone, UpdateDataInfo,
;				FormatTrack, BlockToPhysAddr, SWIMDriveStatus
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Disk formatting routine
;
;_______________________________________________________________________

				seg 	'code'
SWIMFormat		proc
				entry	FmtIndexTable
				entry	DefaultInterleave
				entry	DefaultGapSize
				entry	DefaultFmtByte
				entry	FormatTrack
				with	SWIMVarsZ
				with	SWIMVars

				jsr 	SetUpDrive			; select and power up the disk drive
				bne 	@Done				; if error powering up

				ldy		<DataBCPB			; get the BCPB index
				lda		#bcHOSTtoIOP		; setup the command
				ldx		#ReqFmtDataAddress\
						-RCVData			; offset of start of address field
				jsr		SetupBCPB			; copy the FmtDataAddress
				sty		<CurDataBCPB		; setup current

				ldy		<TagBCPB			; get the BCPB index
				lda		#bcHOSTtoIOP		; setup the command
				ldx		#ReqFmtTagAddress\
						-RCVData			; offset of start of address field
				jsr		SetupBCPB			; copy the ReqFmtTagAddress
				sty		<CurTagBCPB			; setup current

				lda		ReqHdrFmtKind		; get the desired HdrFmtKind byte
				sta 	<FmtByteBlkSize		; setup Fmt byte / Block Size

				lda		ReqFmtInterleave	; get the desired Interleave factor
				sta 	<Interleave 		; setup sector interleave factor

				lda 	ReqFormatKind		; get high byte of format kind
				bne 	@ParamErr			; if non-zero, it's an error
				lda 	ReqFormatKind+1 	; get low byte of format kind
				pha 						; save for later
				lda 	#>ReqDriveStatus	; get page number of status message buffer
				ldx 	<CurrentDrive		; get the drive number
				jsr 	GetDriveStatus		; get the status for disk size
				lda 	#1					; defaults are first index into current format
				plx 						; restore low byte of format kind
				beq 	@CopyFmtList		; if zero, use defaults (x=0)
				txa 						; set desired format index
				ldx 	#StatFmtAllowedH\
						-StatCurrentFmtH	; use formats allowed list
@CopyFmtList	ldy 	RCVData\
						+StatCurrentFmtH,x	; get high byte of list
				sty 	<LoopCountH 		; save it in a temp
				ldy 	RCVData\
						+StatCurrentFmtL,x	; get low byte of list
				sty 	<LoopCountL 		; save it in a temp
				ldx 	#0					; initialize table index
@FmtSearchLoop	ldy 	FmtIndexTable,x		; get the format for this index
				bmi 	@ParamErr			; return ParamErr if formatKind too big
				inx 						; update index
				ror 	<LoopCountH 		; shift high byte
				ror 	<LoopCountL 		; shift low byte
				bcc 	@FmtSearchLoop		; loop if this format not available
				dea 						; decrement format kind
				bne 	@FmtSearchLoop		; loop if not the desired format kind

				ldx 	<CurrentDrive		; get drive number
				lda 	#WriteEnabledFlag	; prepare to test write enable
				bit 	DriveFlags,x		; test the enable bit
				bne 	@WriteEnabled		; if ok to write
				lda 	#wPrErr 			; indicate write protected disk
				bra 	@Done				; return the error

@ParamErr		lda 	#paramErr			; format kind out of range
@Done			stz		<WritingForFmt		; formatting complete, not writing for format now
				jmp 	SWIMReqDone 		; return the error
				
@WriteEnabled	tya 						; get new format
				sta 	DiskFormat,x		; change to new disk format
				lda 	DefaultGapSize,y	; get the default inter sector gap size/sync count
				sta 	<SectorGapSize		; setup sector gap info

				lda 	<FmtByteBlkSize		; get Fmt byte / Block Size from request
				bne		@gotFmtByte			; if non-default, use it
				lda 	DefaultFmtByte,y	; get default format byte / block size byte value
@gotFmtByte		sta 	<FmtByteBlkSize		; setup Fmt byte / Block Size

				ldx 	<Interleave			; get sector interleave factor from request
				bne		@gotInterleave		; if non-default, use it
				ldx 	DefaultInterleave,y	; get the default interleave for this format
				bpl		@gotInterleave		; if positive, use it
				and		#$1F				; get the interleave from the format byte
				tax							; setup to store it
				bne		@gotInterleave		; if non-zero, use it
				inx							; if zero, change it to one
@gotInterleave	stx 	<Interleave 		; setup sector interleave factor

				jsr		CreateTrackCache	; create a cache using the new Format
				jsr 	ReCalibrate 		; move heads to track zero, init the drive
				bne 	@Done				; if error powering up

				lda		#1					; indicate incrementing track order
				sta		<SeekDirection		; setup the default direction

				stz 	<LogicalBlockL		; start with block number zero
				stz 	<LogicalBlockH		; setup high byte

				jsr 	WaitMotorSettled	; wait for motor speed to be in 1.5% tolerance
				dec		<WritingForFmt		; we be formatting now, tell with world
				bra 	@Start				; start formatting

@CylinderLoop	ldy 	<PhysCylinder		; get the cylinder number
				jsr 	Seek				; start the seek to the cylinder
				bne 	@Done				; die on errors

				lda 	<LogicalBlockL		; get first block of this cylinder
				clc 						; setup for addition
				adc 	<PhysSectsPerCyl	; add in the cylinder size
				sta 	<LogicalBlockL		; update low byte
				bcc 	@BlockNumOK 		; if no carry into high byte
				inc 	<LogicalBlockH		; update high byte
@BlockNumOK

;		build the sector interleave map

				ldy 	<PhysSectsPerHead	; number of sectors to interleave
				lda 	#$FF				; flag for available sector
@MapInit		dey 						; decrement sector count
				sta 	SectorMap,y 		; mark sector as available
				bne 	@MapInit			; initialize all of the sectors
@Occupied		ina 						; try the next sector
@MapNext		cpa 	<PhysSectsPerHead	; see if still in range
				blt 	@NoWrap 			; if it didn't wrap around
;				sec 						; carry set for subtract from CPA above
				sbc 	<PhysSectsPerHead	; if wrap around, back up
				clc 						; clear carry for future addition
@NoWrap 		tax 						; setup map index
				bit 	<SectorMap,x		; see if it is already occupied
				bpl 	@Occupied			; if so, try another one
				sty 	<SectorMap,x		; if not, update the map
				adc 	<Interleave 		; bump index by interleave
				iny 						; update sector number
				cpy 	<PhysSectsPerHead	; see if end reached
				blt 	@MapNext			; if not, keep on mapping

				stz		<SectBufferNum		; starting buffer number is always zero
				lda 	<PhysSectsPerCyl	; get number of blocks on this cylinder
				sta 	<SectorsNeeded		; sector count for this track

;		get the tag data

				ldy		<CurTagBCPB			; get the current tag copy info
				bmi		@tagXferDone		; if not enabled, skip it
				jsr		UpdateTagInfo		; update the data xfer info
				jsr 	MakeBCPBreq 		; issue the copy request
				ldy		<CurTagBCPB			; get the current tag copy info
				jsr 	WaitBCPBdone	 	; wait for it to complete
@tagXferDone

;		get the sector data

				ldy		<CurDataBCPB		; get the current data copy info
				bmi		@dataXferDone		; if not enabled, skip it
				jsr		UpdateDataInfo		; update the data xfer info
				jsr 	MakeBCPBreq 		; issue the copy request
				ldy		<CurDataBCPB		; get the current data copy info
				jsr 	WaitBCPBdone 		; wait for it to complete
@dataXferDone

				jsr 	WaitDriveReady		; wait for the seek to complete
				beq 	@HeadLoop			; continue with next head if successful
@toDone			jmp		@Done				; die on errors
@HeadLoop
				jsr 	FormatTrack 		; format the track
				bne 	@toDone				; exit if formatting error
				inc 	<PhysHead			; go on to the next head
				lda 	<PhysSectsPerCyl	; get remaining blocks on this cylinder
				sec 						; setup for subtract
				sbc 	<PhysSectsPerHead	; decrement by blocks on this head
				sta 	<PhysSectsPerCyl	; update remaining blocks on this cylinder
				bne 	@HeadLoop			; loop through all of the heads

@Start			jsr 	BlockToPhysAddr		; convert highest block on this cylinder
				beq 	@CylinderLoop		; process next cylinder if end not reached
				jsr		CreateTrackCache	; create a cache using the new Format
				stz		<WritingForFmt		; formatting complete, not writing for format now
				lda 	#noErr				; indicate format succeeded
				jmp 	SWIMDriveStatus		; finish up by returning the drive status
				endwith
				endwith
				endproc

				seg 	'constants'
DefaultInterleave proc
;		table of interleave factors indexed by DiskFormat
				dc.b	1					;  0 - uncheckedFormat
				dc.b	1					;  1 - unknownFormat
				dc.b	1					;  2 - HD20Format
				dc.b	-1					;  3 - GCR400Kformat (get interleave from FmtByte)
				dc.b	-1					;  4 - GCR800Kformat (get interleave from FmtByte)
				dc.b	1					;  5 - MFM720Kformat
				dc.b	1					;  6 - MFM1440Kformat
				dc.b	1					;  7 - GCRonHDformat
				dc.b	1					;  8 - reserved for future use
				dc.b	1					;  9 - reserved for future use
				dc.b	1					; 10 - reserved for future use

				entry	DefaultGapSize
DefaultGapSize
;		table of inter sector gap/sync group sizes indexed by DiskFormat
				dc.b	0					;  0 - uncheckedFormat
				dc.b	0					;  1 - unknownFormat
				dc.b	0					;  2 - HD20Format
				dc.b	GCRDefaultSyncCount ;  3 - GCR400Kformat
				dc.b	GCRDefaultSyncCount ;  4 - GCR800Kformat
				dc.b	MFM720KDataGapSize	;  5 - MFM720Kformat
				dc.b	MFM1440KDataGapSize ;  6 - MFM1440Kformat
				dc.b	MFM1440KDataGapSize ;  7 - GCRonHDformat
				dc.b	0					;  8 - reserved for future use
				dc.b	0					;  9 - reserved for future use
				dc.b	0					; 10 - reserved for future use

				entry	DefaultFmtByte
DefaultFmtByte
;		table of format byte / block size indexed by DiskFormat
				dc.b	0					;  0 - uncheckedFormat
				dc.b	0					;  1 - unknownFormat
				dc.b	0					;  2 - HD20Format
				dc.b	$02 				;  3 - GCR400Kformat 2-1 interleave
				dc.b	$22 				;  4 - GCR800Kformat 2-1 interleave
				dc.b	BlockSize/256		;  5 - MFM720Kformat
				dc.b	BlockSize/256		;  6 - MFM1440Kformat
				dc.b	BlockSize/256		;  7 - GCRonHDformat
				dc.b	0					;  8 - reserved for future use
				dc.b	0					;  9 - reserved for future use
				dc.b	0					; 10 - reserved for future use
				endproc
				title	'IOP SWIM Driver - SWIM Format Verify'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMFormatVerify
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		SetUpDrive, GetDriveStatus, WaitDriveReady, FlushTrackCache,
;				BlockToPhysAddr, Seek, FindCacheEntry, SkipSectorData, ReadSectorHdr,
;				ReadSectorData
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Verify that the disk is correctly formatted and all blocks
;				can be read without errors.
;
;_______________________________________________________________________

				seg 	'code'
SWIMFormatVerify proc
				with	SWIMVarsZ
				with	SWIMVars
				with	SectorBuffers
				jsr 	SetUpDrive			; select and power up the disk drive
				bne 	@Done				; if error powering up

				lda 	#>ReqDriveStatus	; get page number of status message buffer
				ldx 	<CurrentDrive		; get the drive number
				jsr 	GetDriveStatus		; get the status for disk size

				jsr 	WaitDriveReady		; wait for motor speed and heads to settle
				bne 	@Done				; if error powering up

				jsr		FlushTrackCache		; Flush the cache to force reads without retries
				lda 	#1					; setup to dec (last block is disk size - 1)
				sta 	<PhysSectsPerCyl	; setup decrement value
				lda 	RCVData\
						+StatDiskSizeB1		; get high byte of disk size
				sta 	<LogicalBlockH		; setup highest logical block number
				lda 	RCVData\
						+StatDiskSizeB0		; get low byte of disk size
				bra 	@Start				; start verifying

@HdrErr			bne		@Done				; if sector out of range, die with verErr
@VerErr 		lda 	#verErr 			; verify failed, sector not found
@Done			jmp 	SWIMReqDone 		; return the error

@CylinderLoop	jsr 	BlockToPhysAddr		; convert highest block on this cylinder
				bne 	@Done				; die on errors

				ldy 	<PhysCylinder		; get the cylinder number
				jsr 	Seek				; start the seek to the cylinder
				bne 	@Done				; die on errors
				jsr 	WaitDriveReady		; wait for the seek to complete
				bne 	@Done				; die on errors

@HeadLoop		lda 	<PhysHead			; get the head we want
				sta 	<SeekingHead		; pass it to ReadSectorHdr
				jsr		FindCacheEntry		; search the cache for this track
				lda		CacheBufferNum,x	; get starting buffer number for this track
				sta		<BaseBufferNum		; remember the starting buffer for this track
				lda 	<PhysSectsPerHead	; max sectors needed
				sta 	<SectorsNeeded		; remember how many sectors are needed
				lda		#WrongSectsMax		; number of wrong sectors allowed before error
				sta		<WrongSectsLeft		; number of wrong sectors remaining before error
				bra		@VerifyLoop			; start reading the track

@SkipSector		jsr 	SkipSectorData		; give ADB some time
@VerifyLoop 	dec 	<WrongSectsLeft		; count the block
				beq 	@VerErr 			; if count exhausted, die with verErr
				jsr 	ReadSectorHdr		; look for a sector
				bcs		@HdrErr				; die on all errors

				assert	ReadDataValid=$80	; sign bit is valid bit
				bmi		@SkipSector			; if already in cache, just ignore it

				jsr 	ReadSectorData		; read the data into the cache
				beq		@readOK				; if successfull read, update sector info

				cpa		<DMARetryStatus		; see if it was a DMA timeout error
				beq		@VerifyLoop			; if so, just re-try it without ints during DMA
				bra 	@Done				; if real I/O error, handle sector data errors

@readOK			ldx		<CacheMRUPtr		; get index to current cache entry
				dec		CacheInvalidCount,x	; decrement the invalid entry count

				ldx		<SectBufferNum		; get the buffer number for this sector
				lda		#ReadDataValid		; mark the buffer data valid
				sta		BufferStatus,x		; set ReadDataValid, clear IORequested

				dec 	<SectorsNeeded	 	; decrement blocks to find
				bne 	@VerifyLoop 		; if more needed, continue searching

				dec 	<PhysHead			; go on to the next head
				bpl 	@HeadLoop			; loop through all of the heads

				lda 	<LogicalBlockL		; get last block of this cylinder
@Start			sec 						; setup for subtract
				sbc 	<PhysSectsPerCyl	; subtract out the cylinder size
				sta 	<LogicalBlockL		; update low byte
				lda 	<LogicalBlockH		; get high byte
				sbc 	#0					; subtract high byte
				sta 	<LogicalBlockH		; update high byte
				bge 	@CylinderLoop		; continue verify if more cylinders

				lda 	#noErr				; verify succeeded
				bra 	@Done				; return the error
				endwith
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM Get Raw Track Data'

;_______________________________________________________________________
;
;  Routine: 	jmp 	SWIMGetRawData
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		SetUpDrive, Seek, WaitDriveReady, CreateTrackCache, AdrAndSense,
;				Sense, ReadSectorData, ReadHdrSetup, MakeBCPBreq, WaitBCPBdone,
;				ReadSectorHdr, SetupBCPB
;  Called by:	SWIM Request Dispatcher
;
;  Function:	Reads Raw data from the disk, starting at a user selectable point.
;
;_______________________________________________________________________

				seg 	'code'
SWIMGetRawData	proc
				with	SWIMVarsZ
				with	SWIMVars
				with	SectorBuffers

;	The read loop would average about 30.643 clocks per byte (15.647µsec) which doesn't
;	allow 5% speed variation in MFM mode (16µsec - 5% = 15.2µsec).
;	To allow better margin for speed variation, self modifying code is used in the loop
;	to get the average byte time down to 29.647 clocks (15.138µsec)

FasterReads		equ		1					; enable faster (and self modifying) read loop

				jsr 	SetUpDrive			; select and power up the disk drive
				bne 	RawIOError			; if error powering up

				jsr		SetupByteCount		; get the byte count, make sure it is in range

				clc							; indicate data buffer
				ldy		<DataBCPB			; get the BCPB index
				ldx		#ReqRawDataAddress\
						-RCVData			; offset of start of address field
				lda		#>(DataBuffers+(RawDataBufferNum*BlockSize)+RawByteCountMax+$FF)
				jsr		SetupRawBCPB		; copy the ReqRawDataAddress
				sty		<CurDataBCPB		; setup data buffer copy request

				sec							; indicate clock buffer
				ldy		<TagBCPB			; get the BCPB index
				ldx		#ReqRawClockAddress\
						-RCVData			; offset of start of address field
				lda		#>(DataBuffers+(RawClockBufferNum*BlockSize)+RawByteCountMax/8+$FF)
				jsr		SetupRawBCPB		; copy the ReqRawClockAddress
				sty		<CurTagBCPB			; setup clock buffer copy request

				lda 	#paramErr			; default to Parameter Error if out of range
				ldx		ReqRawSearchMode+0	; check high byte of search mode
				bne		RawIOError			; must be zero
				ldx		ReqRawSearchMode+1	; check low byte of search mode
				cpx		#4					; must be 0…3
				bge		RawIOError			; param error if out of range

				ldx		ReqRawHead			; get the requested head number
				stx		<SeekingHead		; remember which head we want
				cpx		CurHeadsPerCyl		; check against max heads
				bge		RawIOError			; param error if out of range

				ldy 	<CurrentDrive		; get the active drive number
				ldx 	DiskFormat,y		; see what format we have
				stz		<RawIOFmtMFM		; $00 if GCR
				cpx		#MFM1440Kformat		; check for MFM (1440K)
				beq		@mfmDisk			; if MFM, adjust sector number
				cpx		#MFM720Kformat		; check for MFM (720K)
				bne		@formatFound		; if not MFM, use GCR numbering
@mfmDisk		dec		ReqRawSector		; MFM sectors are 1 based, we need zero based
				dec		<RawIOFmtMFM		; $FF if MFM
@formatFound

				ldx		ReqRawCylinder		; get high byte of cylinder
				bne		RawIOError			; param error if out of range
				ldy		ReqRawCylinder+1	; get low byte of cylinder
				jsr		Seek				; move the heads
				bne		RawIOError			; die on errors
				jsr		WaitDriveReady		; wait for the seek to complete
				beq		StartSearching		; if successful, search for the starting point

RawIOError		ldx		#4-1				; loop count/index
@errLoop		stz		ReqRawByteCount,x	; indicate that no bytes were transferred
				dex							; update loop count/index
				bpl		@errLoop			; clear all 4 bytes of the byte count
RawIODone		ldx 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				stx 	wZeroes 			; clear action and write mode (read done)
				stz		CurSectsPerHead		; disable prefetching, sects per head unknown
				pha							; save error code
				jsr		CreateTrackCache	; cache is trash, re-create it
				pla							; restore error code, set conditions
				jmp 	SWIMReqDone 		; return the error



StartSearching	ldx		ReqRawSearchMode+1	; check low byte of search mode
				assert	SearchImmediate=0
				beq		ReadImmediate		; if SearchImmediate, select the head and start reading

				assert	SearchForAddress=1
				dex
				beq		FindAddress			; if SearchForAddress, starting reading after sect hdr

				assert	SearchForData=2
				dex
				beq		FindData			; if SearchForData, starting reading after sect data

				assert	SearchForIndex=3
FindIndex		lda		<RawIOFmtMFM		; see if MFM
				beq		ReadImmediate		; if GCR, no index pulse, searchImmediate

				stz		<LoopCountH			; init outer loop counter 65K times ± 256
				lda		#rIndexPulseAdr		; sense the index signal
				jsr		AdrAndSense			; sense it
@indexLoop		tax							; remember index signal from last sample
				jsr		Sense				; test the index signal
				beq		@noIndex			; if index not active then keep waiting
				txa							; index is active, see if inactive last time
				beq		ReadImmediate		; if inactive to active transition, start reading
@noIndex		iny							; update low byte of timeout counter
				bne		@indexLoop			; if time remaining, keep checking
				inc		<LoopCountH			; update high byte of timeout counter
				bne		@indexLoop			; if time ramaining, keep checking
				lda		#noIndexErr			; timed out searching for index
ToRawIOError	bra		RawIOError			; return the error


FindAddress		jsr		FindSector			; search for the sector
				bcs		RawIOError			; if not found, report the error
				lda		<RawIOFmtMFM		; see if MFM or GCR
				beq		ContinueReading		; if GCR, continue reading, without re-syncing
				bra		ReadImmediate		; if MFM, re-sync, and start reading


FindData		jsr		FindSector			; search for the sector
				bcs		RawIOError			; if not found, report the error
				lda		#>(DataBuffers+\
						(RawTempBufferNum*BlockSize))	; page number of temp buffer
				sta		<DataBufPtr+1		; setup page number for data to be ignored
				sta		<TagBufPtr+1		; dump the tags there too.
				jsr 	ReadSectorData		; read the data, ignore errors


ReadImmediate	jsr		ReadHdrSetup		; select the head, start shifting in data
ContinueReading	ldx		<TagBCPB			; get the BCPB index
				lda		BCPBiopAddrH,x		; get high byte of clock buffer start address
				sta		<TagBufPtr+1		; setup high byte of buffer pointer
				lda		BCPBiopAddrL,x		; get low byte of clock buffer start address
				sta		<TagBufPtr			; setup low byte of buffer pointer

				ldx		<DataBCPB			; get the BCPB index
				lda		BCPBiopAddrH,x		; get high byte of data buffer start address
				ldy		BCPBiopAddrL,x		; get low byte of data buffer start address
	if FasterReads then
				sta		StoreDataInst+2		; setup high byte of buffer pointer
	else
				sta		<DataBufPtr+1		; setup high byte of buffer pointer
				stz		<DataBufPtr			; setup low byte of buffer pointer
	endif

				ldx		#$01				; constant to cause carry out after 8 shifts
				stx		<LoopCountH			; initialize shift temporary to $01

				txa							; outer loop counter
				bit 	rData				; empty a garbage byte out of the FIFO,
				bit 	rError				; read and clear errors
@findByte		bit		rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bmi		ReadLoopStart		; if so, go ahead and start reading
				inc		<LoopCountL			; update inner loop counter
				bne		@findByte			; loop until a byte is found
				ina							; update outer loop counter
				bne		@findByte			; loop until a byte is found, or timeout
				lda		#noNybErr			; indicate that no bytes were found
				bra		ToRawIOError		; return the error

WaitNextByte	lda		rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	WaitNextByte		; loop until byte is ready
				assert	MarkInFIFO=$01
				lsr		a					; carry := mark/data indicator
				lda		rMark				; read the mark/data byte
	if FasterReads then
StoreDataInst	sta		$FF00,y				; store the raw data (page number gets modified)
	else
				sta		(<DataBufPtr),y		; store the raw data
	endif
				rol		<LoopCountH			; shift in the mark/data indicator
				iny							; update the buffer offset
				bcc		WaitNextByte		; loop until a group of 8 bytes completes

				bne		@DataPageOK			; if page boundary not reached
	if FasterReads then
				inc		StoreDataInst+2		; if page crossing, update the page number
	else
				inc		<DataBufPtr+1		; if page crossing, update the page number
	endif
@DataPageOK
				lda		<LoopCountH			; get the mark/data indicators for 8 bytes
				stx		<LoopCountH			; re-initialize to $01 for next pass
				sta		(<TagBufPtr)		; store the indicators
				inc		<TagBufPtr			; update the buffer offset
				bne		WaitNextByte		; loop until page crossing

				inc		<TagBufPtr+1		; if page crossing, update the page number
ReadLoopStart	lda		<TagBufPtr+1		; get the updated page number
				cpa		#>(DataBuffers+\
						(RawClockBufferNum*BlockSize)+\
						RawByteCountMax/8+$FF)	; see if end has been reached
				bne		WaitNextByte		; keep looping until end of buffer

				ldy		<CurDataBCPB		; setup data buffer copy request
				jsr		SendData			; copy the buffer
				ldy		<CurTagBCPB			; setup clock buffer copy request
				jsr		SendData			; copy the buffer
				lda		#noErr				; indicate success
				jmp		RawIODone			; if all done, return success


SendData		bmi		@noData				; if no buffer, skip it
				phy							; save the BCPB index
				jsr 	MakeBCPBreq 		; issue the copy request
				ply							; restore the BCPB index
				jmp 	WaitBCPBdone 		; wait for it to complete and return
@noData			rts							; all done


FindSector		inc		<DMARetryStatus		; force 1 re-try to prevent ints during DMA
				lda		#WrongSectsMax+1	; successful read, re-init Wrong Sector Count
				sta		<WrongSectsLeft		; number of wrong sectors remaining before Recal
				lda 	#$FF				; allow any sector number
				sta 	<PhysSectsPerHead	; setup for sector number range checking
@tryAgain		lda		#sectNFErr			; assume it isn't found
				sec							; return carry := 1, to indicate error
				dec		<WrongSectsLeft		; update the retry count
				bmi		@done				; if no retries left, report the error
				jsr 	ReadSectorHdr		; read the next sector header that comes along
				bcs 	@tryAgain			; if any error, try again
				lda		<SectHdrSector		; get the sector number that was found
				eor		ReqRawSector		; see if it is the one that we want
				bne		@tryAgain			; if not, try the next one
@done			rts							; all done



SetupByteCount	lda		ReqRawByteCount+0	; get high byte of longword byte count
				ora		ReqRawByteCount+1	; get next byte of longword byte count
				bne		@ReduceByteCount	; if too big, reduce it
				ldy		ReqRawByteCount+3	; get low byte of longword byte count
				cpy		#<RawByteCountMax	; compare to max
				ldx		ReqRawByteCount+2	; get middle byte of longword byte count
				txa							; setup for compare
				sbc		#>RawByteCountMax	; compare to max
				blt		@ByteCountOK		; if less than max then use it, else use max
@ReduceByteCount
				stz		ReqRawByteCount+0	; clear high byte of longword byte count
				stz		ReqRawByteCount+1	; clear next byte of longword byte count
				ldx		#>RawByteCountMax	; get high byte of max byte count
				ldy		#<RawByteCountMax	; get low byte of max byte count
@ByteCountOK	stx		ReqRawByteCount+2	; setup middle byte of longword byte count
				sty		ReqRawByteCount+3	; setup low byte of longword byte count
				tya							; get the low byte
				clc							; setup for addition
				adc		#$07				; round up to mod 8 boundary
				and		#$F8				; clear low bits
				bcc		@rounded			; if no carry to propagate, store the count
				inx							; propagate the carry
@rounded		stx		<BlocksToDoH		; setup high byte of loop counter
				sta		<BlocksToDoL		; setup low byte of loop counter
				rts							; all done


SetupRawBCPB	php							; save the flags
				pha							; save the ending page number
				lda		#0					; ending page offset is always zero
				sec							; setup for subtraction
				sbc		<BlocksToDoL		; subtract the byte count
				sta		BCPBiopAddrL,y		; remember the page offset (page aligned)
				pla							; get the page number again
				sbc		<BlocksToDoH		; subtract the byte count
				sta		BCPBiopAddrH,y		; remember the page number
				lda		#bcIOPtoHOST		; setup the command
				jsr		SetupBCPB			; copy the ReqRawClockAddress
				ldx		<BlocksToDoH		; high byte of byte count
				lda		<BlocksToDoL		; low byte of byte count

				lsr		<BlocksToDoH		; shift byte count right by 3 bits (div by 8)
				ror		<BlocksToDoL		; shift low byte
				lsr		<BlocksToDoH
				ror		<BlocksToDoL
				lsr		<BlocksToDoH
				ror		<BlocksToDoL

				plp							; restore the carry flag
				phy							; see if buffer valid
				ply
				bmi		@done				; if not, don't need to setup byte count
				bcs		@storeCount			; if clock buffer, store rounded count div 8

				ldx		ReqRawByteCount+2	; for data buffer, use the actual byte count
				lda		ReqRawByteCount+3

@storeCount		sta		BCPBbyteCountL,y	; setup low byte of byte count
				txa							; get high byte
				sta		BCPBbyteCountH,y	; setup high byte of byte count
@done			rts							; all set up
				endwith
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Create Track Cache'

;_______________________________________________________________________
;
;  Routine: 	jsr 	CreateTrackCache
;  Inputs:		CurrentDrive	- active drive number
;  Outputs: 	CurHeadsPerCyl	- Number of heads per cylinder for this disk
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		FlushTrackCache
;  Called by:	SWIMFormat, SWIMGetRawData, SetUpDrive
;
;  Function:	Creates a track cache for the current drive, allocating buffer
;				space according to the format of the disk in the drive.
;
;_______________________________________________________________________

				seg 	'code'
CreateTrackCache proc
				entry	BlockXlateTable
				entry	BlockXlateHeadsPerCyl
				entry	BlockXlateSectsPerHead
				with	SWIMVarsZ
				with	SWIMVars

				ldy 	<CurrentDrive		; get the active drive number
				ldx 	DiskFormat,y		; see what format to read
				ldy 	BlockXlateTable,x	; Get the translate table index
				lda		BlockXlateHeadsPerCyl,y	; get number of heads per cylinder for this format
				sta		CurHeadsPerCyl		; remember it for prefetching

				ldx		#-1					; cache index
				lda		#TotalBuffers		; index of last buffer + 1
				sec							; setup for subtract (remains set within loop)
				bra		@BufferStart		; enter the loop
@BufferLoop		pha							; save the buffer number
				txa							; get current element index
				inx							; point to the next cache entry
				sta		CacheNextMRU,x		; initialize the link
				pla							; restore the buffer number
				sta		CacheBufferNum,x	; initialize the starting buffer number
@BufferStart	sbc		BlockXlateSectsPerHead,y	; allocate a track buffer
				bge		@BufferLoop			; loop until all buffer space used

				stx		<CacheMRUPtr		; make this the new first element
* (fall into)	bra		FlushTrackCache		; now invalidate all of the elements
				title	'IOP SWIM Driver - Flush Track Cache  /  Flush If Disabled'

;_______________________________________________________________________
;
;  Routine: 	jsr 	FlushTrackCache
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, n, z
;  Calls:		none
;  Called by:	SWIMFormatVerify, CreateTrackCache, FlushIfDisabled
;
;  Function:	Flushes the existing track cache for the current drive by
;				invalidating all of the cache entries.
;
;_______________________________________________________________________

				entry	FlushTrackCache

FlushTrackCache	ldx		#MaxCacheEntries-1	; index of last entry / loop counter
				lda		#-1					; invalid count
@invalidLoop	sta		CacheInvalidCount,x	; mark all entries invalid
				dex							; index through each element
				bpl		@invalidLoop		; loop until all done
				rts							; all done



;_______________________________________________________________________
;
;  Routine: 	jsr 	FlushIfDisabled
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, n, z
;  Calls:		FlushTrackCache
;  Called by:	SWIMCacheControl, SetUpDrive
;
;  Function:	Checks to see if Track Cacheing is enabled, and if not, calls
;				FlushTrackCache to invalidate it.
;
;_______________________________________________________________________

				entry	FlushIfDisabled

FlushIfDisabled	lda		CacheEnabled		; see if cache is enabled
				beq		FlushTrackCache		; if disabled, flush it
				rts							; if enabled, leave it alone
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Find Cache Entry'

;_______________________________________________________________________
;
;  Routine: 	jsr 	FindCacheEntry
;  Inputs:		PhysCylinder	- Physical Cylinder number
;				PhysHead		- Physical Head number
;				PhysSectsPerHead- Physical Number of Sectors per head on this Cylinder
;  Outputs: 	CacheMRUPtr		- Index of cache entry for this Cylinder/Head
;				X				- loaded with CacheMRUPtr
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		none
;  Called by:	SetupTrackInfo, SWIMFormatVerify
;
;  Function:	Searches the track cache for an entry matching this cyl/head.
;				If it is not found in the cache, an entry will be created in the
;				cache (replacing an invalid entry, or the least recently used
;				entry if all entries were valid).  The cache entry for this
;				track will be updated to be the most recently used entry.
;
;_______________________________________________________________________

				seg 	'code'
FindCacheEntry	proc
				with	SWIMVarsZ
				with	SWIMVars
				with	SectorBuffers

				ldy		#-1					; indicate that no invalid entries found
				lda		<CacheMRUPtr		; search the cache in MRU order
				bra		@searchStart		; enter the search loop

@invalid		tay							; remember the last invalid entry found
@searchNext		lda		CacheNextMRU,x		; get index of next entry to search
				bmi		@searchEnd			; if end of list, exit
@searchStart	tax							; setup index to current entry to check
				bit		CacheInvalidCount,x	; see if entry is valid
				bmi		@invalid			; if invalid, don't bother with compare
				lda		CacheCylinder,x		; get the cylinder number of the cache entry
				cpa		<PhysCylinder		; see if it matches
				bne		@searchNext			; if not, go on to next entry
				lda		CacheHead,x			; get the head number of the cache entry
				cpa		<PhysHead			; see if it matches
				bne		@searchNext			; if not, go on to next entry
				bra		@match				; if both match, we found the entry

;	No match was found, so we need to create an entry, and reclaim an existing entry.
;	We will first try to reclaim an invalid entry, but if all entries were valid,
;	we will reclaim the least recently used valid entry.

@searchEnd		tya							; see if any invalids found
				bmi		@createEntry		; if not, then just use the last entry
				tax							; if invalid found, use it
@createEntry	lda		<PhysCylinder		; get the Cylinder for the entry
				sta		CacheCylinder,x		; update the cache entry
				lda		<PhysHead			; get the Head for the entry
				sta		CacheHead,x			; update the cache entry
				lda		<PhysSectsPerHead	; get the sector count
				sta		CacheInvalidCount,x	; all of the sectors are invalid

;	invalidate the buffer status of each sector on this track

				tay							; setup loop counter
				clc							; setup for addition
				adc		CacheBufferNum,x	; point past end of buffer list
				phx							; preserve cache index
				tax							; setup status index
@statusLoop		dex							; point to previous entry
				stz		BufferStatus,x		; clear the status
				dey							; decrement the buffer count
				bne		@statusLoop			; loop for all buffers
				plx							; restore cache index

;	We now have a valid entry in the cache, and we must promote it to be the head
;	of the Most Recently Used (MRU) list.

@match			cpx		<CacheMRUPtr		; see if already MRU
				beq		@done				; of so, we're all done
				txa							; setup index to search for
				ldy 	#-1 				; loop counter/index
@MRUloop		iny 						; inc counter/index
				cpa 	CacheNextMRU,y		; look for entry that points to new MRU
				bne 	@MRUloop			; loop until it is found

				lda 	CacheNextMRU,x		; get successor of new MRU
				sta 	CacheNextMRU,y		; remove new MRU entry from chain
				lda 	<CacheMRUPtr		; get pointer to old MRU entry
				sta 	CacheNextMRU,x		; new MRU entry points to old MRU entry
				stx 	<CacheMRUPtr		; mark new entry as MRU
@done			rts							; return with valid MRU entry
				endwith
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Prefetch Into Cache'

;_______________________________________________________________________
;
;  Routine: 	jsr 	PrefetchIntoCache
;  Inputs:		CacheEnabled	- Track cache enabled flag
;				CurrentDrive	- Drive that is currently spinning (zero if none)
;				LastActiveDrive	- Drive that was most recently spinning (zero if none)
;				PrefetchActive	- Flag indicating that prefetcher is active
;				DriverActive	- Flag indicating that driver is active
;				RequestPending	- Flag indicating that a request is pending
;				CurSectsPerHead	- Sectors per head for current track (zero if unknown)
;				CurHeadsPerCyl	- Number of heads per cylinder for this disk
;				CacheMRUPtr		- Index of cache entry for this Cylinder/Head
;  Outputs: 	Prefetch data is read into the cache (validating the entries)
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		WaitDriveReady, SkipSectorData, ReadSectorHdr, ReadSectorData
;  Called by:	PollingTask
;
;  Function:	Reads sectors from all of the heads on the current cylinder
;				of the spinning disk drive, into the track cache.
;
;				The heads are read in Cache MRU order.  Invalid tracks in
;				the cache will be validated with any remaining track that
;				were not already in the cache (without moving the head).
;
;_______________________________________________________________________

				seg 	'code'
PrefetchIntoCache	proc
				with	SWIMVarsZ
				with	SWIMVars
				with	SectorBuffers
				entry	SkipSectorData

				lda		CacheEnabled		; see if cache is enabled
				beq		PrefetchDone		; if disabled, don't prefetch
				lda		<CurrentDrive		; see which drive is spining
				beq		PrefetchDone		; if none, quit
				cpa		<LastActiveDrive	; see if it matches last active drive
				beq		PrefetchTrack		; if so, find a track to prefetch
PrefetchDone	stz		PrefetchActive		; indicate that prefetcher is idle
				stz		<DriverActive		; let driver run
				lda		#$FF				; mask to check all bits
				trb		RequestPending		; see if a request is pending
				beq		@exit				; if not, just return
				jmp		HandleRCVMsg2		; if pending, go run it
@exit			rts


PrefetchTrack	lda		CurSectsPerHead		; get sectors per head for this track
				beq		PrefetchDone		; if invalid, don't prefetch
				sta		<PhysSectsPerHead	; remember it for ReadSectorHdr
				lda		#-1					; starting head - 1
@nextHead		ina							; try next higher head
				cpa		CurHeadsPerCyl		; see if still in range
				bge		PrefetchDone		; exit if all heads searched
				sta		<SeekingHead		; remember head to read from
				ldy		#-1					; indicate that no invalid entries found
				lda		<CacheMRUPtr		; search the cache in MRU order
				bra		@searchStart		; enter the search loop

@invalid		tay							; remember the last invalid entry found
@searchNext		lda		CacheNextMRU,x		; get index of next entry to search
				bmi		@searchEnd			; if end of list, exit
@searchStart	tax							; setup index to current entry to check
				bit		CacheInvalidCount,x	; see if entry is valid
				bmi		@invalid			; if invalid, don't bother with compare
				lda		<SeekingCylinder	; get the current cylinder number
				cpa		CacheCylinder,x		; see if it matches
				bne		@searchNext			; if not, go on to next entry
				lda		CacheInvalidCount,x	; see if any invalid entries need to be filled
				bne		@searchFound		; if so, fill this cache entry
				lda		CacheHead,x			; get the head number of the cache entry
				cpa		<SeekingHead		; see if it matches
				beq		@nextHead			; if so, go on to next head
				bra		@searchNext			; otherwise, go on to next entry

@searchEnd		tya							; see if any invalids found
				bmi		PrefetchDone		; if not, then just use the last entry
				tax							; if invalid found, use it
				lda		<SeekingCylinder	; get the current cylinder number
				sta		CacheCylinder,x		; update the cylinder in case we use this entry
				lda		<SeekingHead		; get the head number to fill
				sta		CacheHead,x			; validate the head
				lda		<PhysSectsPerHead	; get number of sectors for this head
				sta		CacheInvalidCount,x	; mark all of the sectors invalid

				tay							; setup loop counter
				clc							; setup for addition
				adc		CacheBufferNum,x	; point past end of buffer list
				phx							; preserve cache index
				tax							; setup status index
@invalidateLoop	dex							; point to previous entry
				stz		BufferStatus,x		; clear the status
				dey							; decrement the buffer count
				bne		@invalidateLoop		; loop for all buffers
				plx							; restore cache index

@searchFound	lda		CacheHead,x			; get the head of the matching entry
				sta		<SeekingHead		; mark it as the head to read
				lda		CacheBufferNum,x	; get starting buffer number for this track
				sta		<BaseBufferNum		; remember the starting buffer for this track
				stx		PrefetchCachePtr	; remember the cache entry pointer

				lda		#$FF				; give lots of sectors left
				sta		<WrongSectsLeft		; to force sleep
				sta		PrefetchActive		; indicate that we are sleeping during prefetch
				sta		<DriverActive		; driver is active while waiting ready
				jsr		WaitDriveReady		; make sure the drive is ready
				bne		@toPrefetchDone		; if not, don't bother prefetching
				stz		<DriverActive		; let driver run during sleep
				jsr		SkipSectorData		; sleep to give time to other tasks
				lda		<CurrentDrive		; see which drive is spining
				beq		@toPrefetchDone		; if none, quit
				cpa		<LastActiveDrive	; see if it matches last active drive
				bne		@toPrefetchDone		; if not, quit

				lda		#$FF				; awake again, driver active
				sta		<DriverActive		; mark active in case of ints during DMA
				jsr 	ReadSectorHdr		; look for a sector
				bcs		@nextSect			; ignore all errors

				assert	ReadDataValid=$80	; sign bit is valid bit
				bmi		@nextSect			; if already in cache, just ignore it

				jsr 	ReadSectorData		; read the data into the cache
				bne		@nextSect			; ignore all errors
				ldx		PrefetchCachePtr	; get index to current cache entry
				dec		CacheInvalidCount,x	; decrement the invalid entry count

				ldx		<SectBufferNum		; get the buffer number for this sector
				lda		#ReadDataValid		; mark the buffer data valid
				sta		BufferStatus,x		; set ReadDataValid, clear IORequested
@nextSect		jmp		PrefetchIntoCache	; loop until timeout
@toPrefetchDone	jmp		PrefetchDone
				endwith
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Get Drive Status'

;_______________________________________________________________________
;
;  Routine: 	jsr 	GetDriveStatus
;  Inputs:		A - High byte of StatusMsg address
;				X - DriveNumber
;  Outputs: 	none
;  Destroys:	A, X, Y, n, z, c
;  Cycles:		693..757
;  Calls:		TranslateStatus (internal routine)
;  Called by:	SWIMDriveStatus, PollingTask, SWIMFormat, SWIMFormatVerify
;
;  Function:	Given a drive number and a pointer to a Message Buffer,
;				this routine returns the status information for that drive
;				and the disk that may be inserted in that drive.
;
;_______________________________________________________________________

				seg 	'code'
GetDriveStatus	proc
				entry	StatusTables
				entry	DefaultStatus
				entry	DriveStatusPtrs
				entry	MediaStatusPtrs
				entry	FmtStatusPtrs
				entry	DriveAttributes
				with	SWIMVarsZ
				with	SWIMVars
				ldy 	<StatusMsgPtr+1		; save old status message pointer
				phy 						; push it onto the stack
				sta 	<StatusMsgPtr+1		; create new status message pointer

				lda 	DiskFormat,x		; get the disk format for the disk
				pha 						; save it for later
				lda 	MediaKind,x 		; get the media kind for the disk
				pha 						; save it for later
				lda 	DriveKind,x 		; get the drive kind for the drive
				pha 						; save it for later

				lda 	CylinderValid,x		; get the current Cylinder (high byte)
				ldy 	#StatTrackH 		; setup the index into the status message
				sta 	(<StatusMsgPtr),y	; move it to the status message

				lda 	CurrentCylinder,x	; get the current Cylinder (low byte)
				ldy 	#StatTrackL 		; setup the index into the status message
				sta 	(<StatusMsgPtr),y	; move it to the status message

				lda 	ErrorCountH,x		; get the error count (high byte)
				ldy 	#StatDiskErrorsH	; setup the index into the status message
				sta 	(<StatusMsgPtr),y	; move it to the status message

				lda 	ErrorCountL,x		; get the error count (low byte)
				ldy 	#StatDiskErrorsL	; setup the index into the status message
				sta 	(<StatusMsgPtr),y	; move it to the status message

				ldy 	PhysDriveNumber,x	; get the Physical Drive number
				lda 	DriveAttributes,y	; get the drive attributes
				ldy 	#StatDriveAttr		; setup the index into the status message
				sta 	(<StatusMsgPtr),y	; move it to the status message

				lda 	DriveFlags,x		; get the Drive Flags
				and 	#WriteEnabledFlag	; see if write enabled
				beq 	UpdateWrProt		; branch if write protected (a = 0)
				lda 	#$01				; return $00 if write enabled
UpdateWrProt	dea 						; return $FF if write protected
				ldy 	#StatWrProtected	; setup the index into the status message
				sta 	(<StatusMsgPtr),y	; move it to the status message

				ldx 	#DefaultStatus-StatusTables ; get ptr to defaults table
				jsr 	TranslateStatus 	; translate the Default

				ply 						; get the drive kind for the drive
				ldx 	DriveStatusPtrs,y	; find start of table for this drive kind
				jsr 	TranslateStatus		; translate the Drive status

				ply 						; get the media kind for the disk
				ldx 	MediaStatusPtrs,y	; find start of table for this media kind
				jsr 	TranslateStatus		; translate the Format status

				ply 						; get the disk format for the drive
				ldx 	FmtStatusPtrs,y		; find start of table for this disk format
				jsr 	TranslateStatus		; translate the Format status

				ply 						; pop old status message pointer
				sty 	<StatusMsgPtr+1		; restore it
				rts 						; all done

XlateLoop		inx 						; point to data byte
				lda 	StatusTables,x		; get the data byte
				sta 	(<StatusMsgPtr),y	; move it to the status message
				inx 						; point to next index byte
TranslateStatus ldy 	StatusTables,x		; get the message index byte
				bne 	XlateLoop			; loop through all bytes
				rts 						; all done
				endwith
				endwith
				endproc

				seg 	'constants'
StatusConstants proc

				entry	StatusTables
				entry	DefaultStatus
				entry	DriveStatusPtrs
				entry	MediaStatusPtrs
				entry	FmtStatusPtrs
				entry	DriveAttributes
				entry	FmtIndexTable
StatusTables

; Default status values returned
DefaultStatus	dc.b	StatDiskInPlace,$01
				dc.b	StatInstalled,$01
				dc.b	StatTwoSidedFmt,$FF
				dc.b	StatDriveInfoB3,$00
				dc.b	StatDriveInfoB2,$00
				dc.b	StatMfmDrive,$00
				dc.b	StatMfmDisk,$00
				dc.b	StatMfmFormat,$00
				dc.b	StatDiskCtlr,$FF
				dc.b	StatCurrentFmtH,$00
				dc.b	StatFmtAllowedH,$00
				dc.b	StatDiskSizeB3,$00
				dc.b	StatDiskSizeB2,$00
				dc.b	StatIconFlags,$00
				dc.b	StatSpare,$00
				dc.b	$00
				

; Tables based on Drive Kind

; unknown drive kind info
NoDrive 		dc.b	StatInstalled,$FF
				dc.b	StatSides,$00
				dc.b	StatNewIntFace,$00
				dc.b	StatDriveType,noDriveKind
				dc.b	$00

; HD-20 drive kind info
HD20Drive		dc.b	StatSides,$00
				dc.b	StatNewIntFace,$00
				dc.b	StatDriveType,HD20DriveKind
				dc.b	StatFmtAllowedL,$01
				dc.b	StatCurrentFmtL,$01
				dc.b	StatIconFlags,$03
				dc.b	$00

; 400K GCR drive kind info
SSGCRDrive		dc.b	StatSides,$00
				dc.b	StatNewIntFace,$00
				dc.b	StatDriveType,SSGCRDriveKind
				dc.b	StatFmtAllowedL,$02
				dc.b	$00

; 400K / 800K GCR drive kind info
DSGCRDrive		dc.b	StatSides,$FF
				dc.b	StatNewIntFace,$FF
				dc.b	StatDriveType,DSGCRDriveKind
				dc.b	StatFmtAllowedL,$06
				dc.b	$00

; 400K / 800K GCR, 720K / 1440K MFM drive kind info
DSMFMGCRDrive	dc.b	StatSides,$FF
				dc.b	StatNewIntFace,$FF
				dc.b	StatDriveType,DSMFMGCRDriveKind
				dc.b	StatMfmDrive,$FF
				dc.b	StatFmtAllowedL,$0E
				dc.b	$00


; Tables based on Media Kind

; no media kind info
NoMedia 		dc.b	StatDiskInPlace,$00
				dc.b	StatFmtAllowedL,$00
				dc.b	StatCurrentFmtL,$00
				dc.b	$00

LoDenMedia		dc.b	StatCurrentFmtL,$02
				dc.b	$00

HiDenMedia		dc.b	StatFmtAllowedL,$10
				dc.b	StatCurrentFmtL,$10
HD20Media		dc.b	$00


; Tables based on Disk Format Kind

; unknown format kind info
NoFmt			dc.b	StatDiskInPlace,$02
; unchecked format kind info
UnCheckedFmt	dc.b	StatTwoSidedFmt,$00
				dc.b	StatDiskSizeB1,$00
				dc.b	StatDiskSizeB0,$00
				dc.b	$00

; HD-20 format kind info
HD20Fmt 		dc.b	StatDiskInPlace,$08
				dc.b	StatTwoSidedFmt,$00
				dc.b	StatDiskSizeB1,$98			; $9835 = 38965
				dc.b	StatDiskSizeB0,$35
				dc.b	$00

; 400K GCR format kind info
GCR400KFmt		dc.b	StatDiskInPlace,$02
				dc.b	StatTwoSidedFmt,$00
				dc.b	StatDiskSizeB1,$03			; $0320 = 800
				dc.b	StatDiskSizeB0,$20
				dc.b	$00

; 800K GCR format kind info
GCR800KFmt		dc.b	StatDiskInPlace,$02
				dc.b	StatCurrentFmtL,$04
				dc.b	StatDiskSizeB1,$06			; $0640 = 1600
				dc.b	StatDiskSizeB0,$40
				dc.b	$00

; 720K MFM format kind info
MFM720KFmt		dc.b	StatDiskInPlace,$02
				dc.b	StatMfmDisk,$FF
				dc.b	StatCurrentFmtL,$08
				dc.b	StatDiskSizeB1,$05			; $05A0 = 1440
				dc.b	StatDiskSizeB0,$A0
				dc.b	$00

; 1440K MFM format kind info
MFM1440KFmt 	dc.b	StatDiskInPlace,$02
				dc.b	StatMfmDisk,$FF
				dc.b	StatMfmFormat,$FF
				dc.b	StatDiskSizeB1,$0B			; $0B40 = 2880
				dc.b	StatDiskSizeB0,$40
				dc.b	$00

; 400K/800K GCR on HD media format kind info
GCRonHDFmt		dc.b	StatDiskInPlace,$03
				dc.b	StatMfmDisk,$FF
				dc.b	StatMfmFormat,$FF
				dc.b	StatFmtAllowedL,$10
				dc.b	StatCurrentFmtL,$10
				dc.b	StatDiskSizeB1,$0B			; $0B40 = 2880
				dc.b	StatDiskSizeB0,$40
				dc.b	$00

DriveStatusPtrs dc.b	NoDrive-StatusTables		; 0 - no drive
				dc.b	NoDrive-StatusTables		; 1 - unspecified drive
				dc.b	SSGCRDrive-StatusTables		; 2 - Single Sided 400K GCR Drive
				dc.b	DSGCRDrive-StatusTables		; 3 - Double Sided 400K/800K GCR Drive
				dc.b	DSMFMGCRDrive-StatusTables	; 4 - Double Sided GCR / MFM Drive
				dc.b	NoDrive-StatusTables		; 5 - unspecified drive
				dc.b	NoDrive-StatusTables		; 6 - unspecified drive
				dc.b	HD20Drive-StatusTables		; 7 - HD-20 drive
				dc.b	NoDrive-StatusTables		; 8 - unspecified drive

MediaStatusPtrs dc.b	NoMedia-StatusTables		; 0 - No Media kind
				dc.b	LoDenMedia-StatusTables		; 1 - unknown media kind
				dc.b	HD20Media-StatusTables		; 2 - HD-20 media
				dc.b	LoDenMedia-StatusTables		; 3 - Low Density media
				dc.b	HiDenMedia-StatusTables		; 4 - High Density media

FmtStatusPtrs	dc.b	UnCheckedFmt-StatusTables	;  0 - uncheckedFormat
				dc.b	NoFmt-StatusTables			;  1 - unknownFormat
				dc.b	HD20Fmt-StatusTables		;  2 - HD20Format
				dc.b	GCR400KFmt-StatusTables 	;  3 - GCR400Kformat
				dc.b	GCR800KFmt-StatusTables 	;  4 - GCR800Kformat
				dc.b	MFM720KFmt-StatusTables 	;  5 - MFM720Kformat
				dc.b	MFM1440KFmt-StatusTables	;  6 - MFM1440Kformat
				dc.b	GCRonHDFmt-StatusTables 	;  7 - GCRonHDformat

DriveAttributes 		; table of drive attributes, indexed by PhysDriveNumber
				dc.b	%00000000			; drive 0, non-existent
				dc.b	%00000000			; drive 1, primary int removable
				dc.b	%00001000			; drive 2, secondary int removable
				dc.b	%00000000			; drive 3, non-existent
				dc.b	%00000000			; drive 4, non-existent
				dc.b	%00000101			; drive 5, primary ext fixed HD-20
				dc.b	%00001101			; drive 6, secondary ext fixed HD-20
				dc.b	%00001101			; drive 7, secondary ext fixed HD-20
				dc.b	%00001101			; drive 8, secondary ext fixed HD-20

FmtIndexTable	; table of formats indexed by offset into StatCurrentFmt/StatFmtAllowed
				dc.b	HD20Format			;  0 - HD20Format
				dc.b	GCR400Kformat		;  1 - GCR400Kformat
				dc.b	GCR800Kformat		;  2 - GCR800Kformat
				dc.b	MFM720Kformat		;  3 - MFM720Kformat
				dc.b	MFM1440Kformat		;  4 - MFM1440Kformat
				dc.b	-1					;  5 - end of list

				endproc
				title	'IOP SWIM Driver - Check Static Attributes'

;_______________________________________________________________________
;
;  Routine: 	jsr 	CheckStaticAttr
;  Inputs:		X - Drive Number
;  Outputs: 	none
;  Destroys:	A, Y, n, z, c
;  Calls:		AdrAndSense
;  Called by:	PollingTask, SetUpDrive
;
;  Function:	Checks the static attributes of the currently selected
;				drive, and updates the per-drive data structures to
;				reflect the current state of the attributes.  Currently
;				tested attributes are MediaKind, and WriteEnabled.
;
;_______________________________________________________________________

				seg 	'code'
CheckStaticAttr proc
				with	SWIMVars

				lda 	#LoDenMediaKind 	; assume low density media
				sta 	MediaKind,x 		; indicate disk is in place
				lda 	DriveKind,x 		; check for multi-density drive
				cpa 	#DSMFMGCRDriveKind	; see if SuperDrive
				bne 	CheckWrEnable		; if not superdrive, media density is correct
				lda 	#r1MegMediaAdr		; check for 1 or 2 megabyte media
				jsr 	AdrAndSense 		; test the media kind
				bne 	CheckWrEnable		; if low density media
				lda 	#HiDenMediaKind		; indicate High Density media in drive
				sta 	MediaKind,x 		; update media kind

CheckWrEnable	lda 	DriveFlags,x		; get the flags
				ora 	#WriteEnabledFlag	; assume write enabled
				sta 	DriveFlags,x		; update flags
				lda 	#rNoWrProtectAdr	; check write enabled/protected
				jsr 	AdrAndSense 		; test the write protect
				bne 	Done				; if write enabled
				lda 	DriveFlags,x		; get the flags
				and 	#$FF-WriteEnabledFlag	; reset write enabled
				sta 	DriveFlags,x		; update flags
Done			rts 						; all done
				endwith
				endproc
				title	'IOP SWIM Driver - Set Up Drive'

;_______________________________________________________________________
;
;  Routine: 	jsr 	SetUpDrive
;  Inputs:		A - OpenStatus error code
;				n, z - status of the OpenStatus flag
;  Outputs: 	A, n, z - error status code
;  Destroys:	A, X, Y, n, z
;  Calls:		FlushIfDisabled, TurnOffDrive, TurnOnDrive, LoadSWIMparams,
;				CheckStaticAttr, WaitTMPBdone, StartReadyTimer, ReCalibrate,
;				ReadSectorHdr, CreateTrackCache
;  Called by:	SWIMEject, SWIMRead, SWIMReadVerify, SWIMWrite, SWIMFormat,
;				SWIMFormatVerify, SWIMGetRawData
;
;  Function:	Selects and Powers up the specified drive.	If the disk in
;				this drive had not been powered up before, it will also
;				perform a re-calibrate, and determine the format kind of
;				the disk.  The drive will be turned off by the polling
;				task when it resumes and the timeout counter expires, or
;				when TurnOffDrive is explicitly called to turn it off.
;
;_______________________________________________________________________

				seg 	'code'
SetUpDrive		proc
				entry	FmtSearchOrder
				entry	FmtByteValues
				entry	FmtByteMasks
				with	SWIMVarsZ
				with	SWIMVars
				bne 	SetUpDone			; return openErr if SWIM Driver not initialized
				ldy 	ReqDriveNumber		; see what drive is requested
				beq 	NotCurrentDrive		; zero is not a valid drive number
				cpy 	<CurrentDrive		; see if already powered up
				bne 	NotCurrentDrive		; if not currently powered drive
				ldy 	#MotorTimeoutCount	; setup count of polls to wait
				sty 	<MotorTimingOut		; start timing out motor when polling resumes
SetUpDone		tay 						; preserve error code
				jsr		FlushIfDisabled		; flush the cache if it is disabled
				tya 						; restore error code, set flags
				rts 						; drive already set up, just return

NotCurrentDrive lda 	#nsDrvErr			; return no such drive error if out of range
				cpy 	#MaxDriveNumber+1	; see if in range
				bge 	SetUpDone			; return nsDrvErr if out of range

				lda 	#noDriveErr 		; return no drive error if drive not installed
				ldx 	DriveKind,y 		; get the drive kind
				beq 	SetUpDone			; return noDriveErr if not installed

				jsr 	TurnOffDrive		; turn off the old drive (whatever was selected)
				jsr 	TurnOnDrive 		; turn on the new drive
				bne 	SetUpDone			; return error if couldn't turn on drive

				txa 						; get drive number
				eor 	<LastActiveDrive	; see if same drive as last successful access
				beq 	SetUpDone			; return with success, no drive change

				stx 	<LastActiveDrive	; mark this drive as last active, assuming success
				ldy 	DiskFormat,x		; see if format has been determined
				beq 	@FirstTime			; unchecked format, first time disk is accessed
				jsr 	LoadSWIMparams		; Load params if format is known
				bra		@FmtChangeDone		; drive changed, so create a new cache

@FirstTime		ply 						; pop pcL
				sty 	<AsyncPcL			; save PcL
				ply 						; pop pcH
				sty 	<AsyncPcH			; save PcH

				jsr 	CheckStaticAttr		; update media kind and write enable

				ldy 	<ReadyTMPB			; get the TMPB index
				jsr 	WaitTMPBdone		; wait for it to time out
				ldx 	#>ChuckingDelay		; get high byte of Disk Chucking wait time
				lda 	#<ChuckingDelay		; get low byte of Disk Chucking wait time
				jsr 	StartReadyTimer		; start the timer

				stz 	FmtSearchIndex		; initialize the index
@SearchLoop 	ldy 	FmtSearchIndex		; get the index
				inc 	FmtSearchIndex		; update the index
				lda 	FmtSearchOrder,y	; get the format to try
				ldx 	<CurrentDrive		; get the drive number
				sta 	DiskFormat,x		; setup the disk format
				eor 	#unknownFormat		; are we done
				beq 	@SearchDone 		; unknown format found, exit

				jsr 	ReCalibrate 		; establish head position
				bne 	@SearchError		; return with error if couldn't recalibrate

				stz 	<SeekingHead		; always read from size zero
				lda 	#$FF				; allow any sector number
				sta 	<PhysSectsPerHead	; setup for sector number range checking
				jsr 	ReadSectorHdr		; try to read a sector address
				bcs 	@SearchLoop 		; if couldn't read, try next format

				ldy 	FmtSearchIndex		; get the updated index
				lda 	<SectHdrFmtKind		; get the disk format found
				eor 	FmtByteValues-1,y	; compare to expected value
				and 	FmtByteMasks-1,y	; get only the valid bits
				beq 	@SearchDone 		; if format correct, exit with success
				bra 	@SearchLoop 		; otherwise, try next format

@SearchError	ldx 	<CurrentDrive		; get the drive number
				stz 	DiskFormat,x		; indicate unchecked format, couldn't re-cal

@SearchDone 	ldy 	<AsyncPcH			; get PcH
				phy 						; restore PcH
				ldy 	<AsyncPcL			; get PcL
				phy 						; restore PcL
@FmtChangeDone	pha							; preserve the error code
				jsr		CreateTrackCache	; create a cache based on the new format
				pla 						; restore error code, set flags
				rts 						; all done
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Check Drive Mode'

;_______________________________________________________________________
;
;  Routine: 	jsr 	CheckDriveMode
;  Inputs:		X - Drive Number
;  Outputs: 	z - 0 (bne) if drive mode change needed
;				z - 1 (beq) if no drive mode change needed
;				A - command for AdrAndStrb to change drive mode,
;					only valid when z indicates change is needed
;  Destroys:	Y, v, c, n, z
;  Calls:		AdrAndSense
;  Called by:	TurnOnDrive, ReCalibrate
;
;  Function:	Checks to see if the drive mode (GCR/MFM) needs to be changed
;				for the currently selected drive, and returns the command
;				to change the drive mode, or an indication that no change
;				is needed.
;
;_______________________________________________________________________

				seg 	'code'
CheckDriveMode	proc
				with	SWIMVars
				lda 	DriveKind,x 		; check the drive kind
				cpa 	#DSMFMGCRDriveKind	; see if SuperDrive
				bne 	ModeIsCorrect		; only superdrive supports GCR and MFM

				lda 	#wMFMModeOnAdr		; assume MFM format
				ldy 	DiskFormat,x		; check the format
				cpy 	#MFM720Kformat		; 720K is only lo-den MFM format
				beq 	CheckMode			; if 720K MFM disk

				cpy 	#GCRonHDformat		; GCR data on HD media needs GCR mode
				beq 	NeedGCRMode 		; if GCR data on hi-density disk

				ldy 	MediaKind,x 		; check the media
				cpy 	#HiDenMediaKind		; High density media is almost always MFM
				beq 	CheckMode			; if High Density Media

NeedGCRMode 	lda 	#wGCRModeOnAdr		; otherwise set to GCR format

CheckMode		pha 						; save desired mode
				lda 	#rMFMModeOnAdr		; prepare to see what mode the drive is in
				jsr 	AdrAndSense 		; read the status
				bne 	InMFMMode			; if in MFM mode

InGCRMode		pla 						; restore desired mode
				cpa 	#wGCRModeOnAdr		; we're in GCR mode, see if we want to be
				rts 						; return with A = desired mode, z = mode is correct

InMFMMode		pla 						; restore desired mode
				cpa 	#wMFMModeOnAdr		; we're in MFM mode, see if we want to be
				rts 						; return with A = desired mode, z = mode is correct

ModeIsCorrect	lda 	#0					; set z, indicate mode is correct
				rts 						; return
				endwith
				endproc
				title	'IOP SWIM Driver - Turn On Drive'

;_______________________________________________________________________
;
;  Routine: 	jsr 	TurnOnDrive
;  Inputs:		ReqDriveNumber - Drive Number
;  Outputs: 	A, n, z - error status code
;				X, CurrentDrive - Drive number, (only when noErr)
;  Destroys:	Y, v, c
;  Calls:		DiskSelect, AdrAndSense, TurnOffDrive, CheckDriveMode, AdrAndStrb
;				CancelTMPBtimer, StartReadyTimer
;  Called by:	SetUpDrive, ReCalibrate 
;
;  Function:	Selects and Powers up the specified drive, returns error
;				status if no disk in drive.
;				The caller must call WaitDriveReady to wait for the drive
;				to attain proper motor speed.
;
;_______________________________________________________________________

				seg 	'code'
TurnOnDrive 	proc
				with	SWIMVarsZ
				with	SWIMVars
				ldy 	ReqDriveNumber		; see what drive is requested
				ldx 	PhysDriveNumber,y	; get the new physical drive number
				jsr 	DiskSelect			; select and enable the new drive
				lda 	#rNoDiskInPlAdr		; prepare to see if there is a disk
				jsr 	AdrAndSense 		; read the drive status bit
				beq 	HasMedia			; turn drive on, if there is a disk in the drive
				jsr 	TurnOffDrive		; turn off and disable the drive if no diskette
				lda 	#OffLinErr			; indicate that the disk was off line
				rts 						; all done

HasMedia		ldx 	ReqDriveNumber		; see what drive is requested
				stx 	<CurrentDrive		; mark new drive as powered
				lda 	#MotorTimeoutCount	; setup count of polls to wait
				sta 	<MotorTimingOut		; start timing out motor when polling resumes

				lda 	#rMotorOffAdr		; prepare to test the drive motor status
				jsr 	AdrAndSense 		; see if it is off
				beq 	AlreadyOn			; if already on

;		we must setup the drive mode every time the drive is turned on, because
;		it forgets what mode it was in when it gets turned off.
				jsr 	CheckDriveMode		; see if mode needs to change
				beq 	TurnItOn			; no change needed, just turn on motor
				jsr 	AdrAndStrb			; otherwise, set the drive mode first

TurnItOn		lda 	#wMotorOnAdr		; prepare to turn the drive motor on
				jsr 	AdrAndStrb			; turn it on
				ldy 	<ReadyTMPB			; get the TMPB index
				jsr 	CancelTMPBtimer 	; cancel the timer in case it was running
				ldx 	#>MotorOnDelay		; get high byte of Disk Motor On wait time
				lda 	#<MotorOnDelay		; get low byte of Disk Motor On wait time
				jsr 	StartReadyTimer 	; start the timer

				lda 	#>MotorOnSettle		; get high byte of settle time
				sta 	SettleTimeH 		; set high byte of Motor speed settle time
				lda 	#<MotorOnSettle		; get low byte of settle time
				sta 	SettleTimeL 		; set low byte of Motor speed settle time

AlreadyOn		ldx 	<CurrentDrive		; return current drive number
				lda 	#noErr				; return successful status
				rts 						; all done
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Turn Off Drive'

;_______________________________________________________________________
;
;  Routine: 	jsr 	TurnOffDrive
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, Y, n, z
;  Calls:		AdrAndStrb, CancelTMPBtimer
;  Called by:	SWIMInitialize, PollingTask, SWIMEject, SetUpDrive, TurnOnDrive,
;				WaitDriveReady, ReCalibrate
;
;  Function:	Immediatly turns off the motor and disables the drive
;				that was currently enabled.
;
;_______________________________________________________________________

				seg 	'code'
TurnOffDrive	proc
				with	SWIMVarsZ
				with	SWIMVars
				stz 	<CurrentDrive		; indicate that no drive is powered
				stz 	<MotorTimingOut		; indicate timeout expired
				lda 	#wMotorOffAdr		; motor off drive command
				jsr 	AdrAndStrb			; send the command to the drive
				lda 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				sta 	wZeroes 			; clear action and write mode (prepare for motoroff)
				lda 	#MotorOn+Drive1Enabled\
						+Drive2Enabled		; prepare to disable the drive
				sta 	wZeroes 			; clear the enables
				stz 	SettleTimeH 		; ignore any pending settle time
				stz 	SettleTimeL 		; ignore any pending settle time
				ldy 	<ReadyTMPB			; get the TMPB index
				jmp 	CancelTMPBtimer		; cancel the timer and return
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Wait Drive Ready'

;_______________________________________________________________________
;
;  Routine: 	jsr 	WaitDriveReady
;  Inputs:		none
;  Outputs: 	A, n, z - error status code
;  Destroys:	A, X, Y, n, z
;  Calls:		WaitTMPBdone, AdrAndSense, CancelTMPBTimer, StartTMPBTimer,
;				StartReadyTimer, WaitTMPBdone,TurnOffDrive
;  Called by:	SWIMEject, SWIMRead, SWIMReadVerify, SWIMWrite, SWIMFormat,
;				SWIMFormatVerify, SWIMGetRawData, PrefetchIntoCache, ReCalibrate,
;				Seek
;
;  Function:	Polls the currently selected drive, waiting for it to become
;				ready. If it doesn't happen within 1 second, it will be turned
;				off, and an error status will be returned.
;
;_______________________________________________________________________

				seg 	'code'
WaitDriveReady	proc
				with	SWIMVarsZ
				with	SWIMVars
				pla 						; pop pcL
				plx 						; pop pcH
				ldy 	<ReadyTMPB			; get the TMPB index
				jsr 	WaitTMPBdone		; wait for initial delay to time out
				phx 						; push pcH
				pha 						; push pcL

				lda 	#<0-ReadyTimeoutCount	; low byte of max polls counter
				sta 	WaitReadyCount			; initialize low byte of counter
				lda 	#>0-ReadyTimeoutCount	; high byte of max polls counter
				sta 	WaitReadyCount+1		; initialize high byte of counter

WaitReadyLoop	lda 	#rNotReadyAdr		; prepare to test ready
				jsr 	AdrAndSense 		; test drive ready
				bne 	NotReady			; if not ready, sleep and try again

				lda 	SettleTimeH 		; get high byte of Motor speed settle time
				ora 	SettleTimeL 		; get low byte of Motor speed settle time
				bne 	StartSettleTimer	; start timer if needed
*				lda 	#noErr				; return No Error status if drive is ready
				rts 						; return with success

StartSettleTimer
				ldy 	SettledTMPB 		; get the TMPB index
				jsr 	CancelTMPBTimer		; cancel any old timeout
				ldy 	SettledTMPB 		; get the TMPB index
				ldx 	SettleTimeH 		; get high byte of Motor speed settle time
				lda 	SettleTimeL 		; get low byte of Motor speed settle time
				jsr 	StartTMPBTimer		; start the timer
				stz 	SettleTimeH 		; remember that timer was started
				stz 	SettleTimeL 		; remember that timer was started
				lda 	#noErr				; return No Error status if drive is ready
				rts 						; return with success

NotReady		ldx 	#>ReadyPollRate		; get high byte of poll rate
				lda 	#<ReadyPollRate		; get low byte of poll rate
				jsr 	StartReadyTimer		; start the timer
				pla 						; pop pcL
				plx 						; pop pcH
				ldy 	<ReadyTMPB			; get the TMPB index
				jsr 	WaitTMPBdone		; wait for it to time out
				phx 						; push pcH
				pha 						; push pcL

				inc 	WaitReadyCount		; increment the timeout counter
				bne 	WaitReadyLoop		; no carry out, check drive ready
				inc 	WaitReadyCount+1	; increment high byte of counter
				bne 	WaitReadyLoop		; count not exhausted, check drive ready
				jsr 	TurnOffDrive		; timed out, turn off the drive
				lda 	#spdAdjErr			; timed out, indicate error
				rts 						; return with error
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Wait Motor Settled'

;_______________________________________________________________________
;
;  Routine: 	jsr 	WaitMotorSettled
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	Y, n, z
;  Calls:		WaitTMPBdone
;  Called by:	SWIMWrite, SWIMFormat
;
;  Function:	Waits for the MotorSettled timer to expire indicating that
;				the drive motor speed is within 1.5% of tolerance.
;				WaitDriveReady should have already been called to ensure that
;				the motor was up to speed, and this extra wait is only needed
;				during Format and Writes where it is desireable to keep motor
;				speed variation to a minimum to improve data reliability.
;
;_______________________________________________________________________

				seg 	'code'
WaitMotorSettled  proc
				with	SWIMVars
				ldy 	SettledTMPB 		; get the TMPB index
				jmp 	WaitTMPBdone		; wait for settle time to expire
				endwith
				endproc
				title	'IOP SWIM Driver - Start Ready Timer'

;_______________________________________________________________________
;
;  Routine: 	jsr 	StartReadyTimer
;  Inputs:		A - low byte of sleep count
;				X - high byte of sleep count
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		StartTMPBTimer
;  Called by:	SetUpDrive, TurnOnDrive, WaitDriveReady, SleepShort, SleepLong,
;				ReCalibrate, Seek, ReadMFMData, WriteDataBuffer
;
;  Function:	Starts the Ready wait timer using the supplied values.
;
;_______________________________________________________________________

				seg 	'code'
StartReadyTimer proc
				with	SWIMVarsZ
				with	SWIMVars
				stz		<ADBActivityMask	; give disk priority next time
				ldy 	<ReadyTMPB			; get the TMPB index
				sta 	TMPBtimeL,y 		; store low byte into the TMPB
				txa 						; get high byte of poll rate
				sta 	TMPBtimeH,y 		; store it into the TMPB
				jmp 	StartTMPBTimer		; start the timer, and return
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - SleepShort / SleepLong'

;_______________________________________________________________________
;
;  Routine: 	jsr 	SleepShort, SleepLong
;  Inputs:		A - low byte of sleep count
;				X - high byte of sleep count (SleepLong only, > 33.333333 ms)
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		StartReadyTimer, WaitTMPBdone
;  Called by:	SWIMEject, SkipSectorData, FmtMFMTrack
;
;  Function:	Sleeps for the specified amount of time.
;
;_______________________________________________________________________

				seg 	'code'
SleepShort		proc
				entry	SleepLong
				with	SWIMVarsZ
				with	SWIMVars
				ldx 	#0					; high byte is zero (0 .. 33.333333 ms)
SleepLong		jsr 	StartReadyTimer		; start the timer
				ldy 	<ReadyTMPB			; get the TMPB index
				jmp 	WaitTMPBdone		; return after it times out
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Re-Calibrate heads to cylinder 0'

;_______________________________________________________________________
;
;  Routine: 	jsr 	ReCalibrate
;  Inputs:		CurrentDrive - active drive number
;  Outputs: 	A - error status code
;				n,z - based on value returned in A
;				SeekingCylinder - 0 (cylinder number that we are seeking)
;  Destroys:	A, X, Y, n, z, c
;  Calls:		InitSWIMChip, TurnOnDrive, CheckDriveMode, AdrAndStrb, WaitTMPBdone,
;				StartReadyTimer, WaitDriveReady, Seek, AdrAndSense, TurnOffDrive
;  Called by:	SWIMEject, SWIMRead, SWIMReadVerify, SWIMWrite, SWIMFormat, SetUpDrive
;
;  Function:	Re-initializes the disk controller chip, and moves the disk
;				heads back to cylinder zero, reporting any errors that may
;				occur.	This will attempt to get all of the hardware into a
;				known state, when first powering up, or when recovering from
;				an error.
;
;_______________________________________________________________________

				seg 	'code'
ReCalibrate 	proc
				with	SWIMVarsZ
				with	SWIMVars
				pla 						; pop pcL
				sta 	ReCalPcL			; save pcL
				pla 						; pop pcH
				sta 	ReCalPcH			; save pcH

				ldx 	<CurrentDrive		; get current drive number
				stz 	CurrentCylinder,x	; indicate current Cylinder is zero
				stz 	<SeekingCylinder	; indicate which cylinder we are seeking
				jsr 	InitSWIMChip		; Re-Initialize the disk controller chip
				bne 	ReCalErrA			; return with error status if couldn't init
				jsr 	TurnOnDrive 		; re-select and power the drive
				bne 	ReCalErrA			; return with error status if couldn't turn on

				jsr 	CheckDriveMode		; see if mode needs to change
				beq 	ReCalStart			; if mode is OK, don't change is

				jsr 	AdrAndStrb			; change the drive mode

				ldy 	<ReadyTMPB			; get the TMPB index
				jsr 	WaitTMPBdone		; wait for initial delay to time out

;		when switching modes with the motor running, (which will cause a motor speed
;		change), Ready is not immediatly correct, so we must delay before checking ready,
;		otherwise it will appear that the drive is Ready, even though the motor is not
;		up to speed.  We will delay as if the motor was just turned on.
				ldx 	#>MotorOnDelay		; get high byte of Disk Motor On wait time
				lda 	#<MotorOnDelay		; get low byte of Disk Motor On wait time
				jsr 	StartReadyTimer		; start the timer

				lda 	#>MotorOnSettle		; get high byte of settle time
				sta 	SettleTimeH 		; set high byte of Motor speed settle time
				lda 	#<MotorOnSettle		; get low byte of settle time
				sta 	SettleTimeL 		; set low byte of Motor speed settle time

ReCalStart		jsr 	WaitDriveReady		; wait for the motor to be ready
				bne 	ReCalErrA			; return error if motor not ready

				ldx 	<CurrentDrive		; get current drive number
				stz 	CylinderValid,x		; indicate current Cylinder is valid
				lda 	#LastCylinder		; worst case is that we are at the end
				sta 	CurrentCylinder,x	; make beleive that we are there
				ldy 	#0					; we want to go to cylinder zero
				jsr 	Seek				; seek to zero
				jsr 	WaitDriveReady		; wait for the heads to move
				bne 	ReCalErrA			; return error if couldn't seek

				ldx 	#noErr				; setup success code, just in case
				lda 	#rNotTrack0Adr		; see if we found cylinder zero
				jsr 	AdrAndSense 		; test the flag
				beq 	ReCalErrX			; return success if cylinder zero was found

				ldx 	#tk0BadErr			; setup error code, just in case

ReCalErrX		txa 						; get error code
ReCalErrA		ldx 	<CurrentDrive		; get current drive number
				sta 	CylinderValid,x		; indicate current Cylinder is zero (if no err)
				sta 	CurrentCylinder,x	; indicate current Cylinder is zero (if no err)
				tay 						; test error code
				beq 	ReCalDone			; no error, restore pc and return
				stz 	DiskFormat,x		; indicate un-checked format kind
				pha 						; preserve error code
				jsr 	TurnOffDrive		; couldn't re-cal, turn off the drive
				pla 						; restore error code
ReCalDone		ldx 	ReCalPcH			; get pcH
				phx 						; restore pcH
				ldx 	ReCalPcL			; get pcL
				phx 						; restore pcL
				tax 						; test error code, set flags
				rts 						; all done
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Seek heads to cylinder'

;_______________________________________________________________________
;
;  Routine: 	jsr 	Seek
;  Inputs:		CurrentDrive - active drive number
;				Y - Desired cylinder number
;  Outputs: 	A - error status code
;				n,z - based on value returned in A
;				SeekingCylinder - cylinder number that we are seeking
;				CurSectsPerHead	- Zero, to indicate unknown
;  Destroys:	X, Y, n, z, c
;  Calls:		AdrAndStrb, AdrAndSense, Sense, Pulse, WaitTMPBdone,
;				StartReadyTimer, LoadSWIMparams
;  Called by:	SWIMEject, SWIMRead, SWIMReadVerify, SWIMWrite, SWIMFormat,
;				SWIMFormatVerify, SWIMGetRawData, ReCalibrate
;
;  Function:	Sends the Steps Pulses to position the disk heads to the
;				desired cylinder.  WaitDriveReady must be called to wait
;				for the heads to actually move, and settle.
;
;_______________________________________________________________________

				seg 	'code'
Seek			proc
				with	SWIMVarsZ
				with	SWIMVars

				stz		<DMARetryStatus		; new track, allow ints during DMA requests
				stz		CurSectsPerHead		; new track, don't prefetch unless ReadSectorHdr called
				sty 	<SeekingCylinder	; indicate which cylinder we are seeking
				lda 	#ParamErr			; return paramater error if cylinder too high
				cpy 	#LastCylinder+1		; check for out of range
				bge 	SeekDone			; if out of range, return with error

				ldx 	<CurrentDrive		; get the active drive number
				lda 	CylinderValid,x		; see if cylinder is valid
				bne 	SeekDone			; if invalid, return with error

				lda 	CurrentCylinder,x	; get current cylinder
				sta		<StepCount			; setup current cylinder for subtraction
				tya 						; get desired cylinder
				sta 	CurrentCylinder,x	; update current cylinder (to desired)
				sec 						; setup carry for subtract
				sbc 	<StepCount			; step count := desired - current
				beq 	SeekDone			; no seek needed if on cylinder (A=0, noErr)

				ldy 	#wDirNextAdr		; set direction away from cylinder zero
				bge 	SeekStart			; start seeking ahead if positive

				eor 	#$FF				; prepare to negate
				ina 						; step count := current - desired
				ldy 	#wDirPrevAdr		; set direction towards cylinder zero

SeekStart		sta 	<StepCount			; save step count
				tya 						; get the step direction
				jsr 	AdrAndStrb			; set the step direction

				lda 	#rStepOffAdr		; see if last step pulse accepted
				jsr 	AdrAndSense 		; test the flag
				ldx 	#1					; 1 poll, must be right the first time

;  The drive can accept step pulses spaced at 72µsec max. We turn step on, and the drive
;  will turn it off when it is ready to accept the next step pulse.  There appears to be
;  an undocumented drive spec which causes the drive to ignore step pulses if they are sent
;  less than 18µsec after the drive indicated that it was OK to sent the next step.  To avoid
;  this problem, we add an additional short (but not > 72µs) delay before sending each step.

SeekLoop		jsr 	Sense				; see if OK to send a step pulse
				beq 	SeekPoll			; wait until OK to send a step pulse

				dec 	<StepCount			; decrement the loop counter
				bmi 	SeekWait			; start waiting when no more left to do

				ldx 	#4					; ≈10µsec additional delay (to wait at least 18µsec)
StepDelay		dex 						; decrement delay count
				bne 	StepDelay			; wait a short time before sending step pulse

				assert	rStepOffAdr=wStepOnAdr
				jsr 	Pulse				; send a step pulse
				ldx 	#10+1				; allow 10 polls before timeout

SeekPoll		dex 						; decrement poll count
				bne 	SeekLoop			; try again until count runs out

				lda 	#cantStepErr		; couldn't send a step pulse
SeekError		ldx 	<CurrentDrive		; get the active drive number
				sta 	CylinderValid,x		; invalidate the cylinder
				sta 	CurrentCylinder,x	; setup cylinder number too
SeekDone		tax 						; test error code, set flags
				rts 						; all done

SeekWait		pla 						; pop pcL
				plx 						; pop pcH
				ldy 	<ReadyTMPB			; get the TMPB index
				jsr 	WaitTMPBdone		; wait for prior waits to time out
				phx 						; push pcH
				pha 						; push pcL
				ldx 	#>SeekDelay 		; get high byte of Seek initial wait time
				lda 	#<SeekDelay 		; get low byte of Seek initial wait time
				jsr 	StartReadyTimer		; start the timer
				jsr 	LoadSWIMparams		; setup params based upon new cylinder
				bne 	SeekError			; if couldn't load params
				rts 						; return with success
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Block To Physical Address'

;_______________________________________________________________________
;
;  Routine: 	jsr 	BlockToPhysAddr
;  Inputs:		CurrentDrive	- active drive number
;				LogicalBlockH/L - Logical Block Number
;  Outputs: 	A - error status code
;				n,z - based on value returned in A
;				PhysCylinder	- Physical Cylinder number
;				PhysHead		- Physical Head number
;				PhysSector		- Physical Sector number
;				PhysCylSector	- Physical Sector number relative to beginning of cylinder
;				PhysSectsPerHead- Physical Number of Sectors per head on this Cylinder
;				PhysSectsPerCyl - Physical Number of Sectors on this Cylinder
;  Destroys:	X, Y, n, z, c
;  Calls:		none
;  Called by:	ReadWriteSetUp, SetupTrackInfo, SWIMFormat, SWIMFormatVerify
;
;  Function:	Converts a logical block number into a physical disk address,
;				and returns the Physical Cylinder, Head, and Sector numbers,
;				along with the total count of sectors on this head.  Range
;				checking is also performed to assure that the Block Number is
;				not beyond the end of the disk.
;
;				NOTE: The sector number returned will always be adjusted
;				so that it is zero based, even though it may not be
;				zero based on the disk (MFM is 1 based, GCR is 0 based)
;
;_______________________________________________________________________

				seg 	'code'
BlockToPhysAddr proc
				entry	BlockXlateTable
				entry	BlockXlateHeadsPerCyl
				entry	BlockXlateSectsPerHead
				entry	BlockXlateSectsPerCyl
				entry	BlockXlateCylBias
				entry	BlockXlateBlockLimit
				with	SWIMVarsZ
				with	SWIMVars
				ldy 	<CurrentDrive		; get the active drive number
				ldx 	DiskFormat,y		; see what format to read
				ldy 	BlockXlateTable,x	; Get the translate table index
				ldx 	<LogicalBlockL		; get low byte of block number
				lda 	<LogicalBlockH		; get high byte of block number
				sta 	<PhysSector 		; update high byte of block number
				bra 	@SearchStart		; start searching for the cylinder group

@SearchLoop 	txa 						; get low byte of block number
				sec 						; setup carry for subtraction
				sbc 	BlockXlateBlockLimit+0,y ; compare to / subtract limit
				tax 						; save updated block number
				lda 	<PhysSector 		; get high byte of block number
				sbc 	BlockXlateBlockLimit+1,y ; compare to / subtract limit
				bcc 	@SearchDone 		; exit if block number below limit
				sta 	<PhysSector 		; update high byte of block number
				tya							; point to next record (y := y + 5)
				adc		#5-1				; (carry set from above)
				tay							; y := y + 5
@SearchStart	lda 	BlockXlateSectsPerHead,y	; see if end if list
				bne 	@SearchLoop 		; not the end, keep on searching

				lda 	#paramErr			; indicate block number too big for this format
				rts 						; return with error status

@SearchDone 	lda 	BlockXlateSectsPerCyl,y ; get sectors per cylinder
				sta 	<PhysSectsPerCyl	; save sectors per cylinder
				lda 	BlockXlateSectsPerHead,y; get sectors per head
				sta 	<PhysSectsPerHead	; save sectors per head
				lda 	BlockXlateCylBias,y ; get starting cylinder number
				sta 	<PhysCylBias		; save starting cylinder number
				txa 						; get low byte of block offset
				adc 	BlockXlateBlockLimit+0,y ; undo subtract from loop

				asl 	a					; shift to setup loop
				sta 	<PhysCylinder		; Low byte of numerator, quotient
				lda 	<PhysSector 		; get high byte of block number
				ldy 	#8-1				; divide loop counter
@CylinderLoop	rol 	a					; shift high byte of numerator
				cpa 	<PhysSectsPerCyl	; compare to denominator
				bcc 	@CylinderShift		; if N < D, don't subtract, just shift
				sbc 	<PhysSectsPerCyl	; if N >= D, subtract denominator
@CylinderShift	rol 	<PhysCylinder		; shift in quotient bit
				dey 						; decrement loop counter
				bpl 	@CylinderLoop		; loop through all 8 bits

				sta		<PhysCylSector		; sector number relative to beginning of cylinder
				sec 						; setup carry for subtraction
@HeadLoop		iny 						; move on to next head
				tax 						; save sector number for exit
				sbc 	<PhysSectsPerHead	; adjust sector number
				bcs 	@HeadLoop			; if sector >= PhysSectsPerHead, loop

				sty 	<PhysHead			; update head number
				stx 	<PhysSector 		; update sector number
				lda 	<PhysCylinder		; get cylinder offset
				adc 	<PhysCylBias		; bias by starting cylinder number
				sta 	<PhysCylinder		; update cylinder number
				lda 	#noErr				; indicate success
				rts 						; all done
				endwith
				endwith
				endproc

				seg 	'constants'
BlockXlateTable proc
				entry	BlockXlateHeadsPerCyl
				entry	BlockXlateSectsPerHead
				entry	BlockXlateSectsPerCyl
				entry	BlockXlateCylBias
				entry	BlockXlateBlockLimit

				dc.b	UnknownTable-TableBase	;  0 - uncheckedFormat
				dc.b	UnknownTable-TableBase	;  1 - unknownFormat
				dc.b	UnknownTable-TableBase	;  2 - HD20Format
				dc.b	GCR400KTable-TableBase	;  3 - GCR400Kformat
				dc.b	GCR800KTable-TableBase	;  4 - GCR800Kformat
				dc.b	MFM720KTable-TableBase	;  5 - MFM720Kformat
				dc.b	MFM1440KTable-TableBase ;  6 - MFM1440Kformat
				dc.b	GCRonHDTable-TableBase	;  7 - GCRonHDformat
				dc.b	UnknownTable-TableBase	;  8 - reserved for future use
				dc.b	UnknownTable-TableBase	;  9 - reserved for future use
				dc.b	UnknownTable-TableBase	; 10 - reserved for future use

BlockXlateHeadsPerCyl
				dc.b	1					; 1 head per cylinder
TableBase
GCR400KTable
BlockXlateSectsPerHead
				dc.b	12					; 12 sectors per head
BlockXlateSectsPerCyl
				dc.b	12*1				; 12 sectors per cylinder (1 head)
BlockXlateCylBias
				dc.b	0*16				; starting at cylinder zero (first group of 16)
BlockXlateBlockLimit
				dc.w	12*1*16 			; 12 sectors, 1 head, 16 cylinders

				dc.b	11,11*1,1*16
				dc.w	11*1*16

				dc.b	10,10*1,2*16
				dc.w	10*1*16

				dc.b	9,9*1,3*16
				dc.w	9*1*16

				dc.b	8,8*1,4*16
				dc.w	8*1*16

				dc.b	0


				dc.b	2					; 2 head per cylinder
GCRonHDTable
GCR800KTable	dc.b	12,12*2,0*16
				dc.w	12*2*16

				dc.b	11,11*2,1*16
				dc.w	11*2*16

				dc.b	10,10*2,2*16
				dc.w	10*2*16

				dc.b	9,9*2,3*16
				dc.w	9*2*16

				dc.b	8,8*2,4*16
				dc.w	8*2*16

				dc.b	0


				dc.b	2					; 2 head per cylinder
MFM720KTable	dc.b	9,9*2,0*80
				dc.w	9*2*80

				dc.b	0


				dc.b	2					; 2 head per cylinder
MFM1440KTable	dc.b	18,18*2,0*80
				dc.w	18*2*80

				dc.b	0


				dc.b	1					; 1 head per cylinder
UnknownTable	dc.b	8,8*1,0*1			; 1 cylinder, 1 head, 8 sectors
				dc.w	8*1*1				; let blocks 0..7 translate, and die in
				dc.b	0					; read sector header with no address mark
				endproc
				title	'IOP SWIM Driver - Encoding / Decoding Tables'

				seg 	'PageConsts'
DiskDataXlate	proc

; Nibblizing Table ($00 thru $3F  converted to nibbles $96 thru $FF)
; table is repeated 4 times, since upper 2 bits are not masked to zero

				entry	EncodeGCRnibble
EncodeGCRnibble dc.b	$96,$97,$9A,$9B 	; x x 0 0 0 0 x x
				dc.b	$9D,$9E,$9F,$A6 	; x x 0 0 0 1 x x
				dc.b	$A7,$AB,$AC,$AD 	; x x 0 0 1 0 x x
				dc.b	$AE,$AF,$B2,$B3 	; x x 0 0 1 1 x x
				dc.b	$B4,$B5,$B6,$B7 	; x x 0 1 0 0 x x
				dc.b	$B9,$BA,$BB,$BC 	; x x 0 1 0 1 x x
				dc.b	$BD,$BE,$BF,$CB 	; x x 0 1 1 0 x x
				dc.b	$CD,$CE,$CF,$D3 	; x x 0 1 1 1 x x
				dc.b	$D6,$D7,$D9,$DA 	; x x 1 0 0 0 x x
				dc.b	$DB,$DC,$DD,$DE 	; x x 1 0 0 1 x x
				dc.b	$DF,$E5,$E6,$E7 	; x x 1 0 1 0 x x
				dc.b	$E9,$EA,$EB,$EC 	; x x 1 0 1 1 x x
				dc.b	$ED,$EE,$EF,$F2 	; x x 1 1 0 0 x x
				dc.b	$F3,$F4,$F5,$F6 	; x x 1 1 0 1 x x
				dc.b	$F7,$F9,$FA,$FB 	; x x 1 1 1 0 x x
				dc.b	$FC,$FD,$FE,$FF 	; x x 1 1 1 1 x x

				dc.b	$96,$97,$9A,$9B 	; x x 0 0 0 0 x x
				dc.b	$9D,$9E,$9F,$A6 	; x x 0 0 0 1 x x
				dc.b	$A7,$AB,$AC,$AD 	; x x 0 0 1 0 x x
				dc.b	$AE,$AF,$B2,$B3 	; x x 0 0 1 1 x x
				dc.b	$B4,$B5,$B6,$B7 	; x x 0 1 0 0 x x
				dc.b	$B9,$BA,$BB,$BC 	; x x 0 1 0 1 x x
				dc.b	$BD,$BE,$BF,$CB 	; x x 0 1 1 0 x x
				dc.b	$CD,$CE,$CF,$D3 	; x x 0 1 1 1 x x
				dc.b	$D6,$D7,$D9,$DA 	; x x 1 0 0 0 x x
				dc.b	$DB,$DC,$DD,$DE 	; x x 1 0 0 1 x x
				dc.b	$DF,$E5,$E6,$E7 	; x x 1 0 1 0 x x
				dc.b	$E9,$EA,$EB,$EC 	; x x 1 0 1 1 x x
				dc.b	$ED,$EE,$EF,$F2 	; x x 1 1 0 0 x x
				dc.b	$F3,$F4,$F5,$F6 	; x x 1 1 0 1 x x
				dc.b	$F7,$F9,$FA,$FB 	; x x 1 1 1 0 x x
				dc.b	$FC,$FD,$FE,$FF 	; x x 1 1 1 1 x x

				dc.b	$96,$97,$9A,$9B 	; x x 0 0 0 0 x x
				dc.b	$9D,$9E,$9F,$A6 	; x x 0 0 0 1 x x
				dc.b	$A7,$AB,$AC,$AD 	; x x 0 0 1 0 x x
				dc.b	$AE,$AF,$B2,$B3 	; x x 0 0 1 1 x x
				dc.b	$B4,$B5,$B6,$B7 	; x x 0 1 0 0 x x
				dc.b	$B9,$BA,$BB,$BC 	; x x 0 1 0 1 x x
				dc.b	$BD,$BE,$BF,$CB 	; x x 0 1 1 0 x x
				dc.b	$CD,$CE,$CF,$D3 	; x x 0 1 1 1 x x
				dc.b	$D6,$D7,$D9,$DA 	; x x 1 0 0 0 x x
				dc.b	$DB,$DC,$DD,$DE 	; x x 1 0 0 1 x x
				dc.b	$DF,$E5,$E6,$E7 	; x x 1 0 1 0 x x
				dc.b	$E9,$EA,$EB,$EC 	; x x 1 0 1 1 x x
				dc.b	$ED,$EE,$EF,$F2 	; x x 1 1 0 0 x x
				dc.b	$F3,$F4,$F5,$F6 	; x x 1 1 0 1 x x
				dc.b	$F7,$F9,$FA,$FB 	; x x 1 1 1 0 x x
				dc.b	$FC,$FD,$FE,$FF 	; x x 1 1 1 1 x x

				dc.b	$96,$97,$9A,$9B 	; x x 0 0 0 0 x x
				dc.b	$9D,$9E,$9F,$A6 	; x x 0 0 0 1 x x
				dc.b	$A7,$AB,$AC,$AD 	; x x 0 0 1 0 x x
				dc.b	$AE,$AF,$B2,$B3 	; x x 0 0 1 1 x x
				dc.b	$B4,$B5,$B6,$B7 	; x x 0 1 0 0 x x
				dc.b	$B9,$BA,$BB,$BC 	; x x 0 1 0 1 x x
				dc.b	$BD,$BE,$BF,$CB 	; x x 0 1 1 0 x x
				dc.b	$CD,$CE,$CF,$D3 	; x x 0 1 1 1 x x
				dc.b	$D6,$D7,$D9,$DA 	; x x 1 0 0 0 x x
				dc.b	$DB,$DC,$DD,$DE 	; x x 1 0 0 1 x x
				dc.b	$DF,$E5,$E6,$E7 	; x x 1 0 1 0 x x
				dc.b	$E9,$EA,$EB,$EC 	; x x 1 0 1 1 x x
				dc.b	$ED,$EE,$EF,$F2 	; x x 1 1 0 0 x x
				dc.b	$F3,$F4,$F5,$F6 	; x x 1 1 0 1 x x
				dc.b	$F7,$F9,$FA,$FB 	; x x 1 1 1 0 x x
				dc.b	$FC,$FD,$FE,$FF 	; x x 1 1 1 1 x x


; transform [A7] [A6] [A5] [A4] [A3] [A2] [A1] [A0]
;		to	[00] [00] [A7] [A6] [00] [00] [00] [00]

				entry	EncodeGCRHighA
EncodeGCRHighA	dcb.b	64,%00000000		; 0 0 X X X X X X
				dcb.b	64,%00010000		; 0 1 X X X X X X
				dcb.b	64,%00100000		; 1 0 X X X X X X
				dcb.b	64,%00110000		; 1 1 X X X X X X


; transform [B7] [B6] [B5] [B4] [B3] [B2] [B1] [B0]
;		to	[00] [00] [00] [00] [B7] [B6] [00] [00]

				entry	EncodeGCRHighB
EncodeGCRHighB	dcb.b	64,%00000000		; 0 0 X X X X X X
				dcb.b	64,%00000100		; 0 1 X X X X X X
				dcb.b	64,%00001000		; 1 0 X X X X X X
				dcb.b	64,%00001100		; 1 1 X X X X X X


; transform [C7] [C6] [C5] [C4] [C3] [C2] [C1] [C0]
;		to	[00] [00] [00] [00] [00] [00] [C7] [C6]

				entry	EncodeGCRHighC
EncodeGCRHighC	dcb.b	64,%00000000		; 0 0 X X X X X X
				dcb.b	64,%00000001		; 0 1 X X X X X X
				dcb.b	64,%00000010		; 1 0 X X X X X X
				dcb.b	64,%00000011		; 1 1 X X X X X X



; Denibblizing Table (nibbles $96 thru $FF converted to $00 thru $3F)
; $FF means illegal nibble:

				entry	DecodeGCRnibble
DecodeGCRnibble dcb.b	$94,$FF 			; $00..$93 are illegal
				dc.b	$FF,$FF,$00,$01 	;	 ,	 ,$96,$97
				dc.b	$FF,$FF,$02,$03 	;	 ,	 ,$9A,$9B
				dc.b	$FF,$04,$05,$06 	;	 ,$9D,$9E,$9F
				dc.b	$FF,$FF,$FF,$FF 	;	 ,	 ,	 ,
				dc.b	$FF,$FF,$07,$08 	;	 ,	 ,$A6,$A7
				dc.b	$FF,$FF,$FF,$09 	;	 ,	 ,	 ,$AB
				dc.b	$0A,$0B,$0C,$0D 	; $AC,$AD,$AE,$AF
				dc.b	$FF,$FF,$0E,$0F 	;	 ,	 ,$B2,$B3
				dc.b	$10,$11,$12,$13 	; $B4,$B5,$B6,$B7
				dc.b	$FF,$14,$15,$16 	;	 ,$B9,$BA,$BB
				dc.b	$17,$18,$19,$1A 	; $BC,$BD,$BE,$BF
				dc.b	$FF,$FF,$FF,$FF 	;	 ,	 ,	 ,
				dc.b	$FF,$FF,$FF,$FF 	;	 ,	 ,	 ,
				dc.b	$FF,$FF,$FF,$1B 	;	 ,	 ,	 ,$CB
				dc.b	$FF,$1C,$1D,$1E 	;	 ,$CD,$CE,$CF
				dc.b	$FF,$FF,$FF,$1F 	;	 ,	 ,	 ,$D3
				dc.b	$FF,$FF,$20,$21 	;	 ,	 ,$D6,$D7
				dc.b	$FF,$22,$23,$24 	;	 ,$D9,$DA,$DB
				dc.b	$25,$26,$27,$28 	; $DC,$DD,$DE,$DF
				dc.b	$FF,$FF,$FF,$FF 	;	 ,	 ,	 ,
				dc.b	$FF,$29,$2A,$2B 	;	 ,$E5,$E6,$E7
				dc.b	$FF,$2C,$2D,$2E 	;	 ,$E9,$EA,$EB
				dc.b	$2F,$30,$31,$32 	; $EC,$ED,$EE,$EF
				dc.b	$FF,$FF,$33,$34 	;	 ,	 ,$F2,$F3
				dc.b	$35,$36,$37,$38 	; $F4,$F5,$F6,$F7
				dc.b	$FF,$39,$3A,$3B 	;	 ,$F9,$FA,$FB
				dc.b	$3C,$3D,$3E,$3F 	; $FC,$FD,$FE,$FF


; transform [00] [00] [A7] [A6] [B7] [B6] [C7] [C6]
;		to	[A7] [A6] [00] [00] [00] [00] [00] [00]

				entry	DecodeGCRHighA
DecodeGCRHighA	dcb.b	16,%00000000		; 0 0 0 0 X X X X
				dcb.b	16,%01000000		; 0 0 0 1 X X X X
				dcb.b	16,%10000000		; 0 0 1 0 X X X X
				dcb.b	16,%11000000		; 0 0 1 1 X X X X


; transform [00] [00] [A7] [A6] [B7] [B6] [C7] [C6]
;		to	[B7] [B6] [00] [00] [00] [00] [00] [00]

				entry	DecodeGCRHighB
DecodeGCRHighB	dcb.b	4,%00000000 		; 0 0 0 0 0 0 X X
				dcb.b	4,%01000000 		; 0 0 0 0 0 1 X X
				dcb.b	4,%10000000 		; 0 0 0 0 1 0 X X
				dcb.b	4,%11000000 		; 0 0 0 0 1 1 X X
				dcb.b	4,%00000000 		; 0 0 0 1 0 0 X X
				dcb.b	4,%01000000 		; 0 0 0 1 0 1 X X
				dcb.b	4,%10000000 		; 0 0 0 1 1 0 X X
				dcb.b	4,%11000000 		; 0 0 0 1 1 1 X X
				dcb.b	4,%00000000 		; 0 0 1 0 0 0 X X
				dcb.b	4,%01000000 		; 0 0 1 0 0 1 X X
				dcb.b	4,%10000000 		; 0 0 1 0 1 0 X X
				dcb.b	4,%11000000 		; 0 0 1 0 1 1 X X
				dcb.b	4,%00000000 		; 0 0 1 1 0 0 X X
				dcb.b	4,%01000000 		; 0 0 1 1 0 1 X X
				dcb.b	4,%10000000 		; 0 0 1 1 1 0 X X
				dcb.b	4,%11000000 		; 0 0 1 1 1 1 X X


; transform [00] [00] [A7] [A6] [B7] [B6] [C7] [C6]
;		to	[C7] [C6] [00] [00] [00] [00] [00] [00]

				entry	DecodeGCRHighC
DecodeGCRHighC	dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 0 0 0 0 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 0 0 0 1 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 0 0 1 0 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 0 0 1 1 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 0 1 0 0 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 0 1 0 1 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 0 1 1 0 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 0 1 1 1 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 1 0 0 0 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 1 0 0 1 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 1 0 1 0 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 1 0 1 1 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 1 1 0 0 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 1 1 0 1 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 1 1 1 0 X X
				dc.b	%00000000,%01000000,%10000000,%11000000 ; 0 0 1 1 1 1 X X


				entry	SectorTemplates
SectorTemplates

				entry	GCRAddrMark
GCRAddrMark 	dc.b	$D5,$AA,$96 		; GCR address mark byte sequence
				assert	GCRAddrMarkSize=(*-GCRAddrMark)

				entry	GCRAddrSlipMark
GCRAddrSlipMark dc.b	$DE,$AA,$FF 		; GCR address bit slip mark byte sequence
				assert	GCRAddrSlipMarkSize=(*-GCRAddrSlipMark)

				entry	GCRGapSync
GCRGapSync		dc.b	$FF,$3F,$CF,$F3,$FC,$FF ; GCR gap sync byte sequence
				assert	GCRGapSyncSize=(*-GCRGapSync)
				
				entry	GCRDataMark
GCRDataMark 	dc.b	$D5,$AA,$AD 		; GCR data mark byte sequence
				assert	GCRDataMarkSize=(*-GCRDataMark)

				entry	GCRDataSlipMark
GCRDataSlipMark dc.b	$DE,$AA 			; GCR data bit slip mark byte sequence
				entry	GCRGapValue
GCRGapValue 	dc.b	$FF,$FF 			; GCR gap is string of FF's
				assert	GCRDataSlipMarkSize=(*-GCRDataSlipMark)

				entry	GCRFmtFillValue
GCRFmtFillValue dc.b	$96 				; GCR format data is string of 96's (00's)


				entry	MFMIndexMark
MFMIndexMark	dc.b	$C2,$C2,$C2,$FC 	; MFM index mark byte sequence
				assert	MFMIndexMarkSize=(*-MFMIndexMark)

				entry	MFMAddrMark
MFMAddrMark 	dc.b	$A1,$A1,$A1,$FE 	; MFM address mark byte sequence
				assert	MFMAddrMarkSize=(*-MFMAddrMark)

				entry	MFMDataMark
MFMDataMark 	dc.b	$A1,$A1,$A1,$FB 	; MFM data mark byte sequence
				assert	MFMDataMarkSize=(*-MFMDataMark)

				entry	MFMGapValue
MFMGapValue 	dc.b	$4E 				; MFM gap is string of 4E's

				entry	MFMSyncValue
MFMSyncValue	dc.b	$00 				; MFM sync is string of 00's

				entry	MFMFmtFillValue
MFMFmtFillValue dc.b	$F6 				; MFM format data is string of F6's


				entry	FmtSearchOrder
FmtSearchOrder	dc.b	GCR800Kformat		; try 800K first
				dc.b	MFM1440Kformat		; then 1440K
				dc.b	GCR400Kformat		; then 400K
				dc.b	MFM720Kformat		; then 720K
				dc.b	GCRonHDformat		; and finally 400K/800K on HD media
				dc.b	unknownFormat		; list terminator

				entry	FmtByteValues
FmtByteValues	dc.b	$22 				; 800K GCR (2 sided)
				dc.b	$02 				; 1440K (block size = 2*256)
				dc.b	$02 				; 400K GCR (1 sided)
				dc.b	$02 				; 720K (block size = 2*256)
				dc.b	$00 				; 400K/800K GCR on HD media (ignore)

				entry	FmtByteMasks
FmtByteMasks	dc.b	$20 				; 800K GCR, ignore interleave info
				dc.b	$FF 				; 1440K, use whole byte
				dc.b	$20 				; 400K GCR, ignore interleave info
				dc.b	$FF 				; 720K, use whole byte
				dc.b	$00 				; 400K/800K GCR on HD media, ignore byte


				entry	RequiredMedia
RequiredMedia	dc.b	unknownMediaKind	;  0 - uncheckedFormat
				dc.b	unknownMediaKind	;  1 - unknownFormat
				dc.b	HD20MediaKind		;  2 - HD20Format
				dc.b	LoDenMediaKind		;  3 - GCR400Kformat
				dc.b	LoDenMediaKind		;  4 - GCR800Kformat
				dc.b	LoDenMediaKind		;  5 - MFM720Kformat
				dc.b	HiDenMediaKind		;  6 - MFM1440Kformat
				dc.b	HiDenMediaKind		;  7 - GCRonHDformat
				dc.b	unknownMediaKind	;  8 - reserved for future use
				dc.b	unknownMediaKind	;  9 - reserved for future use
				dc.b	unknownMediaKind	; 10 - reserved for future use

				entry	HeadSelectDecode
HeadSelectDecode
				dc.b	rRdData0Adr 		; select head 0
				dc.b	rRdData1Adr 		; select head 1

	if (*-SectorTemplates) > 64 then
				aerror	'DiskDataXlate constants overflowed page boundary'
	elseif (*-SectorTemplates) < 64 then
				dcb.b	64-(*-SectorTemplates),0	; pad out rest of page
	endif
				endproc
				title	'IOP SWIM Driver - Read Sector Header'

;_______________________________________________________________________
;
;  Routine: 	jsr 	ReadSectorHdr
;  Inputs:		CurrentDrive	- active drive number
;				SeekingCylinder	- Desired cylinder number
;				SeekingHead		- Desired head number
;				PhysSectsPerHead- Upper limit for sector numbers
;				BaseBufferNum	- Starting buffer number for this track
;  Outputs: 	c - true if an error occured
;				A - error status code (if carry set)
;				n,z - based on value returned in A (if carry set)
;				n,v - buffer status for this sector (if carry clear)
;				SectHdrCylinder - Cylinder number from sector header
;				SectHdrHead 	- Head number from sector header
;				SectHdrSector	- Sector number from sector header
;				SectHdrFmtKind	- Format Kind / Block Size from sector header
;				SectHdrChkSumH/L- Checksum from sector header
;				SectHdrHandshake- Handshake register after last byte
;				SectHdrError	- Error register after last byte
;				AdrSearchCountH/L - number of bytes found before header
;				SectBufferNum	- Buffer number for the sector that was found
;				DataBufPtr		- Address of DATA buffer for this sector
;				TagBufPtr		- Address of TAG buffer for this sector
;				CurSectsPerHead	- copy of PhysSectsPerHead
;  Destroys:	X, Y, n, z, c
;  Calls:		ReadUnknownHdr, ReadGCRHdr, ReadLoDenMFMHdr, ReadHiDenMFMHdr
;  Called by:	SWIMRead, SWIMReadVerify, SWIMWrite, SWIMFormatVerify, SWIMGetRawData,
;				PrefetchIntoCache, SetUpDrive, FmtGCRTrack
;
;  Function:	Reads the sector header information for the next sector
;				found on the specified head, on the current cylinder.
;
;				NOTE: The sector number returned will always be adjusted
;				so that it is zero based, even though it may not have been
;				zero based on the disk (MFM is 1 based, GCR is 0 based)
;
;				NOTE: If a sector is found that is greater than or equal to
;				PhysSectsPerHead, it should be skipped.  Carry will be set,
;				but the error status returned will be noErr, since the header
;				was read correctly, but there is no buffer for the data.
;
;_______________________________________________________________________

				seg 	'code'
ReadSectorHdr	proc
				entry	ReadHdrDecode
				entry	ReadHdrSetup
				entry	ReadUnknownHdr
				entry	ReadGCRHdr
				entry	ReadLoDenMFMHdr
				entry	ReadHiDenMFMHdr
				entry	SetupBufferPtrs
				with	SWIMVarsZ
				with	SWIMVars
				with	SectorBuffers

				stz 	<DataBufPtr+0		; setup data buffer offset
				ldy 	<CurrentDrive		; get the active drive number
				ldx 	DiskFormat,y		; see what format to read
				lda 	RequiredMedia,x		; see what media is required
				cpa 	MediaKind,y 		; see if we have correct media
				bne 	ReadUnknownHdr		; if wrong kind of media
				lda		<PhysSectsPerHead	; get number of sectors per head
				sta		CurSectsPerHead		; remember PhysSectsPerHead for prefetching
				txa 						; get the DiskFormat
				asl 	a					; compute the table index
				tax 						; load the index register
				stx 	<SectHdrFmtIndex	; save for Read / Write SectorData
				lda		<WrongSectsLeft		; see how many sectors we can skip
				cpa		#WrongSectsMax/2	; see if more than half used
				blt		@ReadHdr			; if so, don't consider skipping sectors
				lda		<ADBActivityMask	; see if ADB deserves priority
				ldy		#GiveADBTimeMask	; assume that it doesn't, 
				sty		<ADBActivityMask	; give ADB priority next time
				and		<ADBActivity		; check against the ADB activity history
				beq		@ReadHdr			; if not, just read it

;	ADB should be given some time, so read the header, and return a buffer status that
;	indicates that the buffer has valid data and no I/O is requested for this sector.

				jsr		@ReadHdr			; read the sector header
				bcs		@exit				; if error, return the error status
				assert	ReadDataValid=$80	; sign bit is valid bit
				assert	IORequested=$40		; overflow bit is requested bit
				clv							; indicate that no I/O is requested
				lda		#ReadDataValid		; indicate that the buffer is valid
@exit			rts							; all done

@ReadHdr		jmp 	(ReadHdrDecode,x)	; dispatch to the routine

ReadUnknownHdr	stz 	<AdrSearchCountH	; indicate that no bytes were searched
				stz 	<AdrSearchCountL	; indicate that no bytes were searched
				lda 	#noAdrMkErr 		; unsupported format, return with error
				sec							; set carry to indicate error
				rts 						; all done

ReadHdrSetup	ldy 	<SeekingHead		; get the desired head number
				lda 	HeadSelectDecode,y	; get the head select command
				jsr 	AdrAndSense 		; select the disk head
ReStartRead 	lda 	rError				; read and clear any pending errors
				lda 	#StartAction+WriteMode	; prepare to clear action and write mode
				sta 	wZeroes 			; clear action and write mode (prepare for read)
				lda 	#ClearFIFO			; prepare to clear the FIFO
				sta 	wOnes				; toggle it to one
				sta 	wZeroes 			; and back to zero to clear it
				lda 	rError				; read and clear errors again
				lda 	#StartAction		; prepare to start reading
				sta 	wOnes				; start shifting in the read data
				rts
				title	'IOP SWIM Driver - Read Sector Header  GCR'

ReadGCRHdr		jsr 	ReadHdrSetup		; select the head and start the data transfer
				stz 	<SectHdrChkSumH 	; high byte of checksum is always 0 in GCR


;		see if we can read any data, by trying to read 3 bytes

;		When reading immediatly after a write, the drive may suppress read data for
;		up to 620µsec, so we will wait at least that long for the first byte to be valid.
;		We look for 3 bytes @16.34µsec per byte brings the total wait to 669µsec.
;		The polling loop takes 11 clocks per iteration @1.9584MHz, which is 5.617µsec.
;		So we compute the timeout as 669/5.617 = 119 iterations, and add in another 10%
;		for good measure to get 131.  It doesn't hurt for it to be too big, it would just
;		take longer to get noNybErr which should never occur unless there is a hardware error.

				ldx 	#131				; timeout counter
				ldy 	#3					; bytes to find counter
@pollAnyBytes	lda 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bmi 	@gotAnyByte 		; if byte is ready
				dex 						; dec timeout
				bne 	@pollAnyBytes		; poll for the byte
				stz 	<AdrSearchCountL	; update lower byte of the search counter
				stz 	<AdrSearchCountH	; update upper byte of the search counter
				lda 	#noNybErr			; if timed out searching for a nibble
				bra 	@ErrorReturn		; no bytes found, return with error

@gotAnyByte 	lda 	rData				; get the data byte, to empty the FIFO
				dey 						; update count of bytes left to find
				bne 	@pollAnyBytes		; wait for more bytes

;		now search for the address mark byte sequence

				lda 	#>-AddrSearchMax	; high byte of search byte count
				sta 	<AdrSearchCountH	; initialize the timeout counter
				lda 	#<-AddrSearchMax	; low byte of search byte count
@markSearch 	ldx 	#0					; index into address mark sequence
@nextMark		ldy 	GCRAddrMark,x		; get expected byte value
				ina 						; update lower byte of the timeout counter
				bne 	@pollAddrMark		; check the address byte
				inc 	<AdrSearchCountH	; update upper byte of the timeout counter
				bne 	@pollAddrMark		; if address mark not found on disk

@AdrMarkTimeOut lda 	#<AddrSearchMax+3	; count of bytes searched before error
				sta 	<AdrSearchCountL	; update lower byte of the search counter
				lda 	#>AddrSearchMax+3	; count of bytes searched before error
				sta 	<AdrSearchCountH	; update upper byte of the search counter
				lda 	#noAdrMkErr 		; address mark not found within timeout period
@ErrorReturn	ldx 	rError				; read error reg for logic analyzer
				ldy 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				tax 						; set flags based upon error code
				sty 	wZeroes 			; clear action and write mode (read done)
				sec							; set carry to indicate error
				rts 						; return with error

@pollAddrMark	bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollAddrMark		; loop until byte is ready
				cpy 	rData				; see if correct byte found
				bne 	@markSearch 		; if not, point back to start of search string
				inx 						; point to next search byte
				cpx 	#GCRAddrMarkSize	; see if all bytes searched
				blt 	@nextMark			; keep searching if not the final byte
*				sec 						; carry is left set by CPX above
				adc 	#<AddrSearchMax-1	; update count of bytes preceeding header
				sta 	<AdrSearchCountL	; update lower byte of the search counter
				lda 	<AdrSearchCountH	; get upper byte of the search counter
				adc 	#>AddrSearchMax-1	; update count of bytes preceeding header
				sta 	<AdrSearchCountH	; update upper byte of the search counter

;		get the fields of the sector header

@pollCylByte	bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollCylByte		; loop until byte is ready
				ldx 	rData				; get the low cylinder number byte
				lda 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ChkSumError		; return checksum error if illegal encoding
				asl 	a					; [0] [C5] [C4] [C3] [C2] [C1] [C0] [0]
				asl 	a					; [C5] [C4] [C3] [C2] [C1] [C0] [0] [0]
				sta 	<SectHdrCylinder	; update low byte of cylinder number
				lda 	DecodeGCRnibble,x	; denibblize it again, initialize the checksum

@pollSectByte	bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollSectByte		; loop until byte is ready
				ldx 	rData				; get the sector number byte
				ldy 	DecodeGCRnibble,x	; denibblize it
				sty 	<SectHdrSector		; update sector number
				eor 	DecodeGCRnibble,x	; add it to checksum
				bmi 	@ChkSumError		; return checksum error if illegal encoding
				cpy		<PhysSectsPerHead	; see if it is in range
				blt 	@pollHeadByte		; keep going if in range

				jsr		@pollHeadByte		; read rest of header
				bcs		@TooBigDone			; if any other error, return it instead
				lda		#noErr				; header read OK, but sect num too big
				sec							; set carry to indicate error
@TooBigDone		rts							; all done

@ChkSumError	lda 	#badCksmErr 		; sector header checksum error
				bra 	@ErrorReturn		; return with checksum error

@pollHeadByte	bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollHeadByte		; loop until byte is ready
				ldx 	rData				; get the Head number (and high bits of cyl)
				ldy 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ChkSumError		; return checksum error if illegal encoding
				sty 	<SectHdrHead		; [0] [0] [H] [C10] [C9] [C8] [C7] [C6]
				eor 	DecodeGCRnibble,x	; add it to checksum
				lsr 	<SectHdrHead		; [0] [0] [0] [H] [C10] [C9] [C8] [C7]
				ror 	<SectHdrCylinder	; [C6] [C5] [C4] [C3] [C2] [C1] [C0] [0]

@pollFmtByte	bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollFmtByte		; loop until byte is ready
				ldx 	rData				; get the Format Kind
				ldy 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ChkSumError		; return checksum error if illegal encoding
				sty 	<SectHdrFmtKind		; update Format Kind
				eor 	DecodeGCRnibble,x	; add it to checksum
				lsr 	<SectHdrHead		; [0] [0] [0] [0] [H] [C10] [C9] [C8]
				ror 	<SectHdrCylinder	; [C7] [C6] [C5] [C4] [C3] [C2] [C1] [C0]

@pollChkSumByte bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollChkSumByte		; loop until byte is ready
				ldx 	rData				; get the checksum
				ldy 	DecodeGCRnibble,x	; denibblize it
				sty 	<SectHdrChkSumL		; update the checksum
				eor 	DecodeGCRnibble,x	; add it to checksum
				bne 	@ChkSumError		; if checksum is bad

				lda 	<SectHdrHead		; [0] [0] [0] [0] [H] [C10] [C9] [C8]
				cpa 	#%00001000			; carry := head
				and 	#%00000111			; [0] [0] [0] [0] [0] [C10] [C9] [C8]
				rol 	a					; [0] [0] [0] [0] [C10] [C9] [C8] [H]
				sta 	<SectHdrHead		; [0] [0] [0] [0] [C10] [C9] [C8] [H]

;		we assume that cylinder numbers use at most 8 bits, and that the upper
;		3 bits will be zero for correct cylinders, so when we compare with the
;		head, we will also be checking the upper 3 bits of cylinder for zero.
				eor 	<SeekingHead		; see if we found the correct head
				bne 	@SeekError			; if wrong head or cylinder
				lda 	<SectHdrCylinder	; get the cylinder number we found
				eor 	<SeekingCylinder	; see if we found the correct cylinder
				bne 	@SeekError			; if wrong cylinder

@checkSlipMarks
				lda 	<SectHdrSector		; get the sector we found
*				clc							; setup for addition (cleared by ROL above)
				adc		<BaseBufferNum		; compute the buffer number for this sector
				sta		<SectBufferNum		; save the buffer number for this sector
				asl		a					; data buffers take 2 pages
*				clc							; setup for addition (cleared by ASL above)
				adc		#>DataBuffers		; compute the data buffer page number
				sta		<DataBufPtr+1		; setup data buffer page number

;		Blindly read the first slip mark byte (58 clocks after previous byte read)
				lda 	rData				; get the first slip mark byte
				cpa 	GCRAddrSlipMark+0	; check expected first slip mark byte
				bne 	@SlipMarkError		; return with error if bad slip mark

				lda 	GCRAddrSlipMark+1	; get expected second slip mark byte
@pollSlipMark2	ldx 	rHandshake			; get final handshake register
				assert	Dat1Byte=$80
				bpl 	@pollSlipMark2		; loop until second slip byte is ready
				cpa 	rData				; check the second slip mark byte
				bne 	@SlipMarkError		; return with error if bad slip mark
				stx 	<SectHdrHandshake	; save the handshake reg
				ldx 	rError				; get the final error register
				stx 	<SectHdrError		; save the Error reg

				ldx		<SectBufferNum		; get the buffer number for this sector
				lda		TagPtrs,x			; get the tag buffer pointer for this sector
				asl		a					; unpack 9th bit into carry
				sta		<TagBufPtr			; setup low byte of tag pointer
				lda		#0					; setup to add in tag addr carry
				adc		#>TagBuffers		; add in starting page of tag buffers
				sta		<TagBufPtr+1		; setup page number
*				clc							; clear carry to indicate success

				assert	ReadDataValid=$80	; sign bit is valid bit
				assert	IORequested=$40		; overflow bit is requested bit
				bit		BufferStatus,x		; see if the buffer is needed
				rts 						; all done

@SlipMarkError	ldx 	rError				; read error reg for logic analyzer
				ldy 	#StartAction+WriteMode	; prepare to clear action and write mode
				sty 	wZeroes 			; clear action and write mode (read done)
				lda 	#badBtSlpErr		; sector header slip mark error
				sec							; set carry to indicate error
				rts							; return with error

@SeekError		jsr 	@checkSlipMarks		; make sure the header is valid first
				bcs 	@SeekErrorDone		; other errors have priority over seek error
				lda 	#seekErr			; if valid sector header, report the seek error
				sec							; set carry to indicate error
@SeekErrorDone	rts 						; return with error
				title	'IOP SWIM Driver - Read Sector Header  MFM'

ReadLoDenMFMHdr ldx 	<CurrentDrive		; get the active drive number
				lda 	DriveKind,x 		; check drive kind
				cpa 	#DSMFMGCRDriveKind	; see if drive supports MFM formats
				beq 	ReadMFMHdr			; if correct drive, join common code
				lda 	#noAdrMkErr 		; can't read MFM address marks with this drive
				sec							; set carry to indicate error
				rts 						; return with error
				
ReadHiDenMFMHdr
ReadMFMHdr		jsr 	ReadHdrSetup		; select the head and start the data transfer

;		search for the address mark byte sequence, with timeout.

				lda 	#>-AddrSearchMax	; high byte of search byte count
				sta 	<AdrSearchCountH	; initialize the timeout counter
				ldy 	#<-AddrSearchMax	; low byte of search byte count
				ldx 	#0					; index into address marks table
				lda 	#noNybErr			; no data found on disk
				sta 	<SectHdrError		; default error if no bytes found

@pollAnyBytes	lda 	#MarkInFIFO 		; test for mark byte also
				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bmi 	@gotAnyByte 		; if byte is ready

				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bmi 	@gotAnyByte 		; if byte is ready

				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bmi 	@gotAnyByte 		; if byte is ready

				nop 						; extra 2 clocks to make loop 32 clocks (16µsec)
				txa 						; see if any bytes of mark found
				beq 	@updateTimeout		; if not, just keep on polling

@ReStartSearch	jsr 	ReStartRead 		; flush the FIFO, and restart search for mark
				ldx 	#0					; index into address marks table
				lda 	#noAdrMkErr 		; address mark not found within timeout period
				sta 	<SectHdrError		; error code if bytes were found
				bra 	@updateTimeout		; start counting again

@gotAnyByte 	beq 	@gotDataByte		; if not a mark byte
				lda 	rMark				; get the mark byte
				eor 	MFMAddrMark,x		; check against expected value
				bne 	@ReStartSearch		; if not a match, start over
				inx 						; point to next search byte
				cpx 	#MFMAddrMarkSize	; see if this was the final byte
				beq 	@ReStartSearch		; final byte should not be a mark byte
@updateTimeout	iny 						; update lower byte of the timeout counter
				bne 	@pollAnyBytes		; check for next byte
				inc 	<AdrSearchCountH	; update upper byte of the timeout counter
				bne 	@pollAnyBytes		; check for next byte

@AdrMarkTimeOut lda 	#<AddrSearchMax		; count of bytes searched before error
				sta 	<AdrSearchCountL	; update lower byte of the search counter
				lda 	#>AddrSearchMax		; count of bytes searched before error
				sta 	<AdrSearchCountH	; update upper byte of the search counter
				lda 	<SectHdrError		; get proper error code
@ErrorReturn	ldx 	rError				; read error reg for logic analyzer
				ldy 	#StartAction+WriteMode	; prepare to clear action and write mode
				tax 						; set flags based upon error code
				sty 	wZeroes 			; clear action and write mode (read done)
				sec							; set carry to indicate error
				rts 						; return with error

@gotDataByte	lda 	rData				; get the data byte
				cpa 	MFMAddrMark,x		; check against expected value
				bne 	@ReStartSearch		; if not a match, start over
				cpx 	#MFMAddrMarkSize-1	; see if this was the final byte
				blt 	@ReStartSearch		; only final byte should be a data byte

				tya 						; get lower byte of the search counter
*				sec 						; carry is left set by CPX above
				adc 	#<AddrSearchMax-1-4	; update count of bytes preceeding header
				sta 	<AdrSearchCountL	; update lower byte of the search counter
				lda 	<AdrSearchCountH	; get upper byte of the search counter
				adc 	#>AddrSearchMax-1-4	; update count of bytes preceeding header
				sta 	<AdrSearchCountH	; update upper byte of the search counter

;		get the fields of the sector header

				ldx 	#0					; timeout counter
				lda 	#MarkInFIFO 		; test for mark byte also
@pollCylByte	dex 						; decrement timeout counter
				beq 	@HdrByteTimeOut		; if timed out waiting for a byte
				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollCylByte		; loop until byte is ready
				bne 	@ChkSumError		; if mark byte, report as checksum error
				ldy 	rData				; get the low cylinder number byte
				sty 	<SectHdrCylinder	; update low byte of cylinder number

@pollHeadByte	dex 						; decrement timeout counter
				beq 	@HdrByteTimeOut		; if timed out waiting for a byte
				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollHeadByte		; loop until byte is ready
				bne 	@ChkSumError		; if mark byte, report as checksum error
				ldy 	rData				; get the Head number
				sty 	<SectHdrHead		; update the Head number
				cpy 	<SeekingHead		; see if we found the correct head
				bne 	@SeekError			; if wrong head or cylinder
				ldy 	<SectHdrCylinder	; get the cylinder number we found
				cpy 	<SeekingCylinder	; see if we found the correct cylinder
				beq 	@pollSectByte		; if correct cylinder

@SeekError		jsr 	@pollSectByte		; make sure the header is valid first
				bcs 	@SeekErrorDone		; other errors have priority over seek error
				lda 	#seekErr			; if valid sector header, report the seek error
				sec							; set carry to indicate error
@SeekErrorDone	rts 						; return with error

@HdrByteTimeOut lda 	#noNybErr			; timeout while polling for a byte
				bra 	@ErrorReturn		; return with timeout error

@ChkSumError	lda 	#badCksmErr 		; sector header checksum error
				bra 	@ErrorReturn		; return with checksum error

@pollSectByte	dex 						; decrement timeout counter
				beq 	@HdrByteTimeOut		; if timed out waiting for a byte
				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollSectByte		; loop until byte is ready
				bne 	@ChkSumError		; if mark byte, report as checksum error
				ldy 	rData				; get the sector number
				dey 						; adjust to zero based numbering
				sty 	<SectHdrSector		; update sector number
				cpy		<PhysSectsPerHead	; see if it is in range
				blt 	@pollFmtByte		; keep going if in range

				jsr		@pollFmtByte		; read rest of header
				bcs		@TooBigDone			; if any other error, return it instead
				lda		#noErr				; header read OK, but sect num too big
				sec							; set carry to indicate error
@TooBigDone		rts							; all done

@pollFmtByte	dex 						; decrement timeout counter
				beq 	@HdrByteTimeOut		; if timed out waiting for a byte
				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollFmtByte		; loop until byte is ready
				bne 	@ChkSumError		; if mark byte, report as checksum error
				ldy 	rData				; get the (block size) Format Kind
				sty 	<SectHdrFmtKind		; update Format Kind

@pollChkSumH	dex 						; decrement timeout counter
				beq 	@HdrByteTimeOut		; if timed out waiting for a byte
				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollChkSumH		; loop until byte is ready
				bne 	@ChkSumError		; if mark byte, report as checksum error
				ldy 	rData				; get the first checksum byte
				sty 	<SectHdrChkSumH		; update the first checksum byte

				ldy 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
@pollChkSumL	dex 						; decrement timeout counter
				beq 	@HdrByteTimeOut		; if timed out waiting for a byte
				lda 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollChkSumL		; loop until byte is ready
				ldx 	rData				; get the second checksum byte
				sty 	wZeroes 			; clear action and write mode (read done)
				bit 	#MarkInFIFO 		; test for mark byte
				bne 	@ChkSumError		; if mark byte, report as checksum error
				stx 	<SectHdrChkSumL 	; update the second checksum byte
				ldx 	rError				; get the final error register
				stx 	<SectHdrError		; save the Error reg
				sta 	<SectHdrHandshake	; save the handshake reg
				and 	#rErrorValid+CRCNotZero ; check for errors
				bne 	@ChkSumError 		; if bad CRC, return with header checksum error

SetupBufferPtrs	lda 	<SectHdrSector		; get the sector we found
				clc							; setup for addition
				adc		<BaseBufferNum		; compute the buffer number for this sector
				sta		<SectBufferNum		; save the buffer number for this sector
				tax							; setup for indexing by buffer number
				asl		a					; data buffers take 2 pages
*				clc							; setup for addition (cleared by ASL above)
				adc		#>DataBuffers		; compute the data buffer page number
				sta		<DataBufPtr+1		; setup data buffer page number

				lda		TagPtrs,x			; get the tag buffer pointer for this sector
				asl		a					; unpack 9th bit into carry
				sta		<TagBufPtr			; setup low byte of tag pointer
				lda		#0					; setup to add in tag addr carry
				adc		#>TagBuffers		; add in starting page of tag buffers
				sta		<TagBufPtr+1		; setup page number
*				clc							; clear carry to indicate success

				assert	ReadDataValid=$80	; sign bit is valid bit
				assert	IORequested=$40		; overflow bit is requested bit
				bit		BufferStatus,x		; see if the buffer is needed
				rts 						; all done
				endwith
				endwith
				endwith
				endproc

				seg 	'WordConsts'
ReadHdrDecode	proc
				dc.w	ReadUnknownHdr		;  0 - uncheckedFormat
				dc.w	ReadUnknownHdr		;  1 - unknownFormat
				dc.w	ReadUnknownHdr		;  2 - HD20Format
				dc.w	ReadGCRHdr			;  3 - GCR400Kformat
				dc.w	ReadGCRHdr			;  4 - GCR800Kformat
				dc.w	ReadLoDenMFMHdr 	;  5 - MFM720Kformat
				dc.w	ReadHiDenMFMHdr 	;  6 - MFM1440Kformat
				dc.w	ReadGCRHdr			;  7 - GCRonHDformat
				dc.w	ReadUnknownHdr		;  8 - reserved for future use
				dc.w	ReadUnknownHdr		;  9 - reserved for future use
				dc.w	ReadUnknownHdr		; 10 - reserved for future use
				endproc
				title	'IOP SWIM Driver - Read Sector Data'

;_______________________________________________________________________
;
;  Routine: 	jsr 	ReadSectorData
;  Inputs:		CurrentDrive	- active drive number
;				TagBufPtr		- pointer to 12 byte tag buffer
;				DataBufPtr		- pointer to 512 byte data buffer
;				Expects to be called after ReadSectorHdr
;  Outputs: 	A - error status code
;				n,z - based on value returned in A
;  Destroys:	X, Y, n, z, c
;  Calls:		ReadUnknownData, ReadGCRData, ReadMFMData, RdWrGCRonHDData
;  Called by:	SWIMRead, SWIMReadVerify, SWIMWrite, SWIMFormatVerify,
;				SWIMGetRawData, PrefetchIntoCache
;
;  Function:	Reads the sector Tag and Data information for the current
;				sector on the selected head, on the current cylinder.
;
;				NOTE: The data buffer must be page aligned, and the tag
;				buffer must not span across pages.
;_______________________________________________________________________

				seg 	'code'
ReadSectorData	proc
				entry	ReadDataDecode
				entry	ReadUnknownData
				entry	ReadGCRData
				entry	RdWrGCRonHDData
				entry	ReadMFMData
				with	SWIMVarsZ
				with	SWIMVars

				ldx 	<SectHdrFmtIndex	; get index from ReadSectorHdr
				jmp 	(ReadDataDecode,x)	; dispatch to the routine

ReadUnknownData lda 	#noDtaMkErr 		; unsupported format, return with error
				rts 						; all done
				title	'IOP SWIM Driver - Read Sector Data - GCR'

;		we enter with action still set, and read mode, from ReadGCRHdr, but
;		the FIFO has probably overrun, and errors may have been set.
;		The shift register must be left running, in order to properly sync
;		on the GCR sync pattern that preceeds the data mark sequence.

RdWrGCRonHDData ldx 	rError				; read error reg for logic analyzer
				ldy 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				sty 	wZeroes 			; clear action and write mode (read done)
				lda 	#<gcrOnMfmErr		; indicate improperly formatted disk
				rts 						; return with error

ReadGCRData 	lda 	rData				; empty garbage byte out of the FIFO,
				lda 	rError				; read and clear errors

;		now search for the data mark byte sequence

				assert	DataSearchMax<128	; it fits in 1 byte
				lda 	#-DataSearchMax		; search byte count
@markSearch 	ldx 	#0					; index into address marks table
@nextMark		ldy 	GCRDataMark,x		; get expected byte value
				ina 						; update the timeout counter
				beq 	@DataMarkTimeOut	; error if count expired

@pollDataMark	bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollDataMark		; loop until byte is ready
				cpy 	rData				; see if correct byte found
				bne 	@markSearch 		; if not, point back to start of search string
				inx 						; point to next search byte
				cpx 	#GCRDataMarkSize	; see if all bytes searched
				blt 	@nextMark			; keep searching if not the final byte

;		get the fields of the data mark

				lda 	#TagSize/3			; 12 tag bytes, read 3 per loop
				sta 	<LoopCountL 		; initialize tag loop counter
				stz 	<GCRCkSumA			; initialize checksum A
				stz 	<GCRCkSumB			; initialize checksum B
				lda 	#0					; initialize checksum C

@pollSectByte	bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollSectByte		; loop until byte is ready
				ldx 	rData				; get the sector number byte
				ldy 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ChkSumError		; checksum error if illegal encoding
@CheckSectNum	cpy 	<SectHdrSector		; check sector number
				beq 	@ReadTagStart		; if match read tag, else didn't find mark

@DataMarkTimeOut
				lda 	#noDtaMkErr 		; data mark not found within timeout period
				bra 	@ErrorReturn		; return with error status
				title	'IOP SWIM Driver - Read Sector Data - GCR Tags'

;		read the tag data bytes

@ReadTagLoop	ldx 	rData				; get the upper bits nibble
				ldy 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ChkSumError		; checksum error if illegal encoding

;		rotate high bit of CkSumC into carry and low bit of CkSumC
				cpa 	#$80				; carry := CheckSumC [7]
				rol 	a					; [6] [5] [4] [3] [2] [1] [0] [7]
				sta 	<GCRCkSumC			; update CheckSum C

				ldx 	rData				; get ByteA'
				lda 	DecodeGCRnibble,x	; denibblize it
				if		CheckEncodings then
				bmi 	@ChkSumError		; checksum error if illegal encoding
				else
*				bmi 	@ChkSumError		; no time to check for illegal encoding
				endif
				eor 	DecodeGCRHighA,y	; insert upper 2 bits of ByteA'
				eor 	<GCRCkSumC			; ByteA := ByteA' xor CkSumC
				sta 	(<TagBufPtr)		; store the first byte
				adc 	<GCRCkSumA			; CkSumA := CkSumA + ByteA + carry
				sta 	<GCRCkSumA			; update CheckSum A
				inc 	<TagBufPtr			; update buffer pointer

				ldx 	rData				; get ByteB'
				lda 	DecodeGCRnibble,x	; denibblize it
				if		CheckEncodings then
				bmi 	@ChkSumError		; checksum error if illegal encoding
				else
*				bmi 	@ChkSumError		; no time to check for illegal encoding
				endif
				eor 	DecodeGCRHighB,y	; insert upper 2 bits of ByteB'
				eor 	<GCRCkSumA			; ByteB := ByteB' xor CkSumA
				sta 	(<TagBufPtr)		; store the second byte
				adc 	<GCRCkSumB			; CkSumB := CkSumB + ByteB + carry
				sta 	<GCRCkSumB			; update CheckSum B
				inc 	<TagBufPtr			; update buffer pointer

				ldx 	rData				; get ByteC'
				lda 	DecodeGCRnibble,x	; denibblize it
				if		CheckEncodings then
				bmi 	@ChkSumError		; checksum error if illegal encoding
				else
*				bmi 	@ChkSumError		; no time to check for illegal encoding
				endif
				eor 	DecodeGCRHighC,y	; insert upper 2 bits of ByteC'
				eor 	<GCRCkSumB			; ByteC := ByteC' xor CkSumB
				sta 	(<TagBufPtr)		; store the third byte
				adc 	<GCRCkSumC			; CkSumC := CkSumC + ByteC + carry

				dec 	<LoopCountL 		; update loop counter
				beq 	@ReadDataStart		; if done with tag, read data field

				inc 	<TagBufPtr			; update buffer pointer

@ReadTagStart	bit 	rHandshake			; see if 2 bytes are available
				assert	Dat2Bytes=$40
				bvs 	@ReadTagLoop		; read the bytes if they are available

				ldx 	#BytePollMax		; setup timeout counter
@PollTagLoop	bit 	rHandshake			; check again
				assert	Dat2Bytes=$40
				bvs 	@ReadTagLoop		; read the bytes if they are available
				dex 						; decrement timeout
				bne 	@PollTagLoop		; poll until full, or timeout

@NoNybError 	lda 	#noNybErr			; bytes not coming in fast enough
@ErrorReturn	ldx 	rError				; read error reg for logic analyzer
				ldy 	#StartAction+WriteMode	; prepare to clear action and write mode
				sty 	wZeroes 			; clear action and write mode (read done)
				tax 						; set flags based upon error code
				rts 						; return with error

@ChkSumError	lda 	#badDCksum			; sector data checksum error
				bra 	@ErrorReturn		; return with checksum error
				title	'IOP SWIM Driver - Read Sector Data - GCR Data'

;		read the sector data bytes

@ReadDataLoop	ldx 	rData				; get the upper bits nibble
				ldy 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ChkSumError		; checksum error if illegal encoding

;		rotate high bit of CkSumC into carry and low bit of CkSumC
				cpa 	#$80				; carry := CheckSumC [7]
				rol 	a					; [6] [5] [4] [3] [2] [1] [0] [7]
				sta 	<GCRCkSumC			; update CheckSum C

				ldx 	rData				; get ByteA'
				lda 	DecodeGCRnibble,x	; denibblize it
				if		CheckEncodings then
				bmi 	@ChkSumError		; checksum error if illegal encoding
				else
*				bmi 	@ChkSumError		; no time to check for illegal encoding
				endif
				eor 	DecodeGCRHighA,y	; insert upper 2 bits of ByteA'
				eor 	<GCRCkSumC			; ByteA := ByteA' xor CkSumC
				sta 	(<DataBufPtr)		; store the byte
				adc 	<GCRCkSumA			; CkSumA := CkSumA + ByteA + carry
				sta 	<GCRCkSumA			; update CheckSum A

				ldx 	rData				; get ByteB' (empty FIFO before page cross delay)

				inc 	<DataBufPtr 		; update buffer pointer
				beq 	@DataCrossPage		; if last byte of first page
@CrossPageDone

				lda 	DecodeGCRnibble,x	 ; denibblize it
				if		CheckEncodings then
				bmi 	@ChkSumError		; checksum error if illegal encoding
				else
*				bmi 	@ChkSumError		; no time to check for illegal encoding
				endif
				eor 	DecodeGCRHighB,y	; insert upper 2 bits of ByteB'
				eor 	<GCRCkSumA			; ByteB := ByteB' xor CkSumA
				sta 	(<DataBufPtr)		; store the byte
				adc 	<GCRCkSumB			; CkSumB := CkSumB + ByteB + carry
				sta 	<GCRCkSumB			; update CheckSum B
				inc 	<DataBufPtr 		; update buffer pointer
				beq 	@ReadDataExit		; if last data byte transferred

				ldx 	rData				; get ByteC'
				lda 	DecodeGCRnibble,x	; denibblize it
				if		CheckEncodings then
				bmi 	@ChkSumError		; checksum error if illegal encoding
				else
*				bmi 	@ChkSumError		; no time to check for illegal encoding
				endif
				eor 	DecodeGCRHighC,y	; insert upper 2 bits of ByteC'
				eor 	<GCRCkSumB			; ByteC := ByteC' xor CkSumB
				sta 	(<DataBufPtr)		; store the byte
				adc 	<GCRCkSumC			; CkSumC := CkSumC + ByteC + carry
				inc 	<DataBufPtr 		; update buffer pointer

@ReadDataStart	bit 	rHandshake			; see if 2 bytes are available
				assert	Dat2Bytes=$40
				bvs 	@ReadDataLoop		; read the bytes if they are available

				ldx 	#BytePollMax		; setup timeout counter
@PollDataLoop	bit 	rHandshake			; check again
				assert	Dat2Bytes=$40
				bvs 	@ReadDataLoop		; read the bytes if they are available
				dex 						; decrement timeout
				bne 	@PollDataLoop		; poll until full, or timeout
				bra 	@NoNybError 		; return error if no bytes found

@DataCrossPage	inc 	<DataBufPtr+1		; update upper byte of buffer pointer
				bra 	@CrossPageDone		; re-join loop
				title	'IOP SWIM Driver - Read Sector Data - GCR Tail'

@ToChkSumError	bra 	@ChkSumError		; jump through here for short displacements

@ReadDataExit	ldx 	rData				; get the upper bits of checksum nibbles
				ldy 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ToChkSumError		; checksum error if illegal encoding
				inc 	<DataBufPtr+1		; update upper byte of data buffer pointer
				inc 	<TagBufPtr			; update tag buffer pointer

@pollCkSumBytes bit 	rHandshake			; see if 2 bytes are available
				assert	Dat2Bytes=$40
				bvc 	@pollCkSumBytes		; loop until 2 bytes are ready

				ldx 	rData				; get checksum byte A
				lda 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ToChkSumError		; checksum error if illegal encoding
				eor 	DecodeGCRHighA,y	; insert upper 2 bits
				eor 	<GCRCkSumA			; compare it to computed value
				bne 	@ToChkSumError		; checksum error if doesn't match

				ldx 	rData				; get checksum byte B
				lda 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ToChkSumError		; checksum error if illegal encoding
				eor 	DecodeGCRHighB,y	; insert upper 2 bits
				eor 	<GCRCkSumB			; compare it to computed value
				bne 	@ToChkSumError		; checksum error if doesn't match

				ldx 	rData				; get checksum byte C
				lda 	DecodeGCRnibble,x	; denibblize it
				bmi 	@ToChkSumError		; checksum error if illegal encoding
				eor 	DecodeGCRHighC,y	; insert upper 2 bits
				eor 	<GCRCkSumC			; compare it to computed value
				bne 	@ToChkSumError		; checksum error if doesn't match


				ldy 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
@pollSlipMarks	bit 	rHandshake			; see if 2 bytes are available
				assert	Dat2Bytes=$40
				bvc 	@pollSlipMarks		; loop until bytes are ready

				lda 	rData				; get the first slip mark byte
				eor 	GCRDataSlipMark+0	; compare to expected value
				bne 	@SlipMarkError		; return with error if bad slip mark

				lda 	rData				; get the second slip mark byte
				eor 	GCRDataSlipMark+1	; compare to expected value
				beq 	@SlipMarksDone		; return with success if good slip mark
@SlipMarkError	lda 	#badDBtSlp			; sector data slip mark error
@SlipMarksDone	ldx 	rError				; read error reg for logic analyzer
				sty 	wZeroes 			; clear action and write mode (read done)
				tax 						; set flags based upon error code
				rts 						; all done
				title	'IOP SWIM Driver - Read Sector Data - MFM'

				entry	PollDMADone

ReadMFMData		lda 	rError				; read and clear any pending errors
				lda 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				sta 	wZeroes 			; clear action and write mode (prepare for read)
				lda 	#ClearFIFO			; prepare to clear the FIFO
				sta 	wOnes				; toggle it to one
				sta 	wZeroes 			; and back to zero to clear it
				lda 	rError				; read and clear errors again
				lda 	#StartAction		; prepare to start reading
				sta 	wOnes				; start shifting in the read data

;		fill in the tag info (while SWIM is looking for "bytes of zeros" sync field)

				lda 	<SectHdrCylinder	; get cylinder number from sector header
				ldy 	#0					; index to first tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				lda 	<SectHdrHead		; get head number from sector header
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				lda 	<SectHdrSector		; get head number from sector header
				ina 						; convert back to one based numbering
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				lda 	<SectHdrFmtKind 	; get block size from sector header
				ora 	#$20				; modified to look double sided
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				lda 	<SectHdrChkSumH 	; get first checksum byte from sector header
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				lda 	<SectHdrChkSumL 	; get second checksum byte from sector header
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				lda 	<SectHdrError		; get rError value from sector header read
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				lda 	<SectHdrHandshake	; get rHandshake value from sector header read
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

	if UseDmaHardware then
				lda 	#>(BlockSize-1)		; high byte of DMA count
				sta		DMA1XferCountH		; setup high byte of transfer count
				lda 	#<(BlockSize-1)		; low byte of DMA count
				sta		DMA1XferCountL		; setup low byte of transfer count
				lda		#1					; first byte of buffer is polled, rest is DMA
				sta		DMA1RAMAddressL		; setup low byte of buffer address
				lda		<DataBufPtr+1		; get high byte of buffer address
				sta		DMA1RAMAddressH		; setup high byte of buffer address
	endif

;		now search for the data mark byte sequence

				assert	DataSearchMax<128	; it fits in 1 byte
				lda 	#DataSearchMax-1	; search byte count
				sta 	<LoopCountL 		; initialize the byte search count
				ldx 	#0					; index into data marks table
@GetNextByte	ldy 	#BytePollMax		; approximate loop count for 1 byte time
				lda 	#MarkInFIFO 		; test for mark byte also
@PollByteLoop	bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bmi 	@GotNextByte		; process the byte if one was found
				dey 						; decrement byte timeout counter
				bne 	@PollByteLoop		; keep polling if count not expired
				dec 	<LoopCountL 		; update the search count
				bpl 	@GetNextByte		; loop if still more bytes to search

@ByteTimeOut	lda 	#noNybErr			; timeout while polling for a byte
@ErrorReturn	ldx 	rError				; read error reg for logic analyzer
				ldy 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				sty 	wZeroes 			; clear action and write mode (read done)
				tax 						; set flags based upon error code
				rts 						; return with error

@ChkSumError	lda 	#badDCkSum			; sector data checksum error
				bra 	@ErrorReturn		; return with checksum error


@GotNextByte	stz 	<LoopCountL 		; force any new timeouts to cause errors
				beq 	@GotDataByte		; if not a mark byte
				lda 	rMark				; get the mark byte
				eor 	MFMDataMark,x		; check against expected value
				bne 	@NoDataMark 		; if not a match, report error
				inx 						; point to next search byte
				cpx 	#MFMDataMarkSize	; see if this was the final byte
				blt 	@GetNextByte		; get next byte if not final byte
;											; final byte should not be a mark byte
@NoDataMark 	lda 	#noDtaMkErr 		; data mark not found within timeout period
				bra 	@ErrorReturn		; return with error status

@GotDataByte	lda 	rData				; get the data byte
				eor 	MFMDataMark,x		; check against expected value
				bne 	@NoDataMark 		; if not a match, report error
				cpx 	#MFMDataMarkSize-1	; see if this was the final byte
				blt 	@NoDataMark 		; only final byte should be a data byte


;		get the sector data

	if UseDmaHardware then
				ldx		#<(rData*IOA1+DMADIR1+DMAEN1)
@WaitSectorByte	bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@WaitSectorByte		; loop until byte is ready
				lda 	rData				; get the data byte
				stx		DMA1Control			; start the DMA transfer
				sta 	(<DataBufPtr)		; store the first byte

				lda		<DMARetryStatus		; see if any DMA timeouts on this track
				bne		@pollDMA			; if so, don't allow interrupts

				lda 	#<DMAxferDelay		; delay to give ADB some time during DMA xfer
				ldx 	#>DMAxferDelay		; high byte
				jsr 	StartReadyTimer 	; start the timer
				pla 						; pop pcL
				plx 						; pop pcH
				ldy 	<ReadyTMPB			; get the TMPB index
				jsr 	WaitTMPBdone		; give ADB some time while DMA xfer in progress
				phx 						; push pcH
				pha 						; push pcL
				lda		#0					; allow interrupts during poll
@pollDMA		jsr		PollDMADone			; wait for transfer to complete
				bne		@ErrorReturn		; return retry status if DMA timeout
	else
				ldx 	#BlockSize/256		; page counter 2 pages = 512 bytes

@GetSectorByte	lda 	#MarkInFIFO 		; test for mark byte also
				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bmi 	@GotSectorByte		; if byte is ready

				ldy 	#BytePollMax		; approximate loop count for 1 byte time
@SectorByteLoop dey 						; decrement byte timeout counter
				beq 	@ByteTimeOut		; return with timeout error if count expired
				bit 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@SectorByteLoop		; keep polling if byte not ready

@GotSectorByte	bne 	@ChkSumError		; if mark byte, report as checksum error
				lda 	rData				; get the data byte
				sta 	(<DataBufPtr)		; store the byte
				inc 	<DataBufPtr 		; update buffer pointer
				bne 	@GetSectorByte		; keep going if not page crossing

				inc 	<DataBufPtr+1		; update buffer pointer page number
				dex 						; update page counter
				bne 	@GetSectorByte		; keep going if not the last page
	endif


;		get the sector checksum

				ldx 	#BytePollMax*2		; poll timeout counter for 2 bytes
@pollChkSumH	dex 						; decrement timeout counter
				beq 	@ByteTimeOut		; if timed out waiting for a byte
				lda 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollChkSumH		; loop until byte is ready
				bit 	#MarkInFIFO 		; test for mark byte
				bne 	@ChkSumError		; if mark byte, report as checksum error

				lda 	rData				; get the first checksum byte
				ldy 	#8					; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

@pollChkSumL	dex 						; decrement timeout counter
				beq 	@ByteTimeOut		; if timed out waiting for a byte
				lda 	rHandshake			; see if byte is available
				assert	Dat1Byte=$80
				bpl 	@pollChkSumL		; loop until byte is ready
				bit 	#MarkInFIFO 		; test for mark byte
				bne 	@ChkSumError		; if mark byte, report as checksum error
				tax 						; save the handshake register value

				lda 	rData				; get the second checksum byte
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				lda 	rError				; get the final error register
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				txa 						; get the final handshake register
				iny 						; point to next tag byte
				sta 	(<TagBufPtr),y		; save it in the tag bytes

				ldy 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				and 	#rErrorValid\
						+CRCNotZero			; check for errors
				beq 	@ChkSumDone 		; if checksum is good
				lda 	#badDCkSum			; sector data checksum error
@ChkSumDone 	sty 	wZeroes 			; clear action and write mode (read done)
				rts 						; all done
				endwith
				endwith
				endproc

				seg 	'WordConsts'
ReadDataDecode	proc
				dc.w	ReadUnknownData 	;  0 - uncheckedFormat
				dc.w	ReadUnknownData 	;  1 - unknownFormat
				dc.w	ReadUnknownData 	;  2 - HD20Format
				dc.w	ReadGCRData 		;  3 - GCR400Kformat
				dc.w	ReadGCRData 		;  4 - GCR800Kformat
				dc.w	ReadMFMData 		;  5 - MFM720Kformat
				dc.w	ReadMFMData 		;  6 - MFM1440Kformat
				dc.w	RdWrGCRonHDData 	;  7 - GCRonHDformat
				dc.w	ReadUnknownData 	;  8 - reserved for future use
				dc.w	ReadUnknownData 	;  9 - reserved for future use
				dc.w	ReadUnknownData 	; 10 - reserved for future use
				endproc
				title	'IOP SWIM Driver - Write Utilities'

;_______________________________________________________________________
;
;  Routine: 	jsr 	WriteByte
;  Inputs:		A				- byte to be written
;				SwimWriteReg	- pointer SWIM register to use
;  Outputs: 	none
;  Destroys:	n, v, z
;  Calls:		??
;  Called by:	??
;
;  Function:	Waits for room for a byte to be available in the FIFO, and
;				then writes the byte to the FIFO, using the SWIM register
;				specified by SwimWriteReg.
;_______________________________________________________________________

;_______________________________________________________________________
;
;  Routine: 	jsr 	WriteRepeated
;  Inputs:		A				- byte to be written
;				X				- number of times to be repeated
;				SwimWriteReg	- pointer SWIM register to use
;  Outputs: 	X - zero
;  Destroys:	n, v, z
;  Calls:		??
;  Called by:	??
;
;  Function:	Waits for room for a byte to be available in the FIFO, and
;				repeatedly writes the byte to the FIFO, using the SWIM register
;				specified by SwimWriteReg.
;_______________________________________________________________________

;_______________________________________________________________________
;
;  Routine: 	jsr 	WriteTemplate
;  Inputs:		A				- byte to be written
;				X				- number of bytes to write
;				Y				- starting offset from SectorTemplates
;				SwimWriteReg	- pointer SWIM register to use
;  Outputs: 	X - zero
;				Y - incremented to point past last byte written
;  Destroys:	A, n, v, z
;  Calls:		??
;  Called by:	??
;
;  Function:	Waits for room for a byte to be available in the FIFO, and
;				repeatedly writes the byte string template to the FIFO, using
;				the SWIM register specified by SwimWriteReg.
;_______________________________________________________________________

;_______________________________________________________________________
;
;  Routine: 	jsr 	WriteMFMCRC
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	n, v, z
;  Calls:		??
;  Called by:	??
;
;  Function:	Waits for room for a byte to be available in the FIFO, and
;				then writes to the wCRC register causing 2 CRC bytes to be
;				written in MFM mode.
;_______________________________________________________________________

;_______________________________________________________________________
;
;  Routine: 	jsr 	WriteDataBuffer
;  Inputs:		A				- byte to be written before buffer
;				X				- low byte of byte count
;				Y				- high byte of byte count
;				DataBufPtr		- starting PAGE address of data buffer
;				SwimWriteReg	- pointer SWIM register to use
;  Outputs: 	A - error status code
;				n,z - based on value returned in A
;  Destroys:	n, v, z
;  Calls:		??
;  Called by:	??
;
;  Function:	Writes the contents of the data buffer, whose address and
;				length are passed in as parameters, to the disk. 
;_______________________________________________________________________

				seg 	'code'
WriteUtilities	proc
				entry	WriteByte
				entry	WriteRepeated
				entry	WriteTemplate
				entry	WriteMFMCRC
				entry	WriteDataExit
				entry	WriteDataBuffer
				entry	PollDMADone
				with	SWIMVarsZ
				with	SWIMVars

WrRepeatedPoll	bit 	rHandshake			; see if we have room in the FIFO
				assert	Dat1Byte=$80
				bpl 	WrRepeatedPoll		; wait until there is room for one byte
				sta 	(<SwimWriteReg)		; write the byte
WriteRepeated	dex 						; decrement the byte count
				bne 	WrRepeatedPoll		; loop if not the last byte

											; otherwise fall into WriteByte for last byte
WriteByte		bit 	rHandshake			; see if we have room in the FIFO
				assert	Dat1Byte=$80
				bpl 	WriteByte			; wait until there is room for one byte
				sta 	(<SwimWriteReg)		; write the byte
				rts 						; all done


WriteTemplate	lda 	SectorTemplates,y	; get the byte
				iny 						; point to next byte of the template
				dex 						; decrement the byte count
				beq 	WriteByte			; exit through WriteByte for last byte
WrTemplatePoll	bit 	rHandshake			; see if we have room in the FIFO
				assert	Dat1Byte=$80
				bpl 	WrTemplatePoll		; wait until there is room for one byte
				sta 	(<SwimWriteReg)		; write the byte
				bra 	WriteTemplate		; loop through all of the bytes


WriteMFMCRC 	bit 	rHandshake			; see if room is available in the FIFO
				assert	Dat1Byte=$80
				bpl 	WriteMFMCRC 		; wait until there is room
				stz 	wCRC				; writing any value causes 2 CRC bytes to write
				rts 						; all done


WriteDataExit	ldx 	#4					; 4 bytes to flush shiftreg and 2 FIFOs
				jsr 	WriteRepeated		; write the gap bytes (only 1 will get to disk)

				lda 	rHandshake			; prepare to check rErrorValid bit
				ldy 	#StartAction+WriteMode	; prepare to clear action and write mode
				sty 	wZeroes 			; clear action and write mode (write done)
				ldx 	rError				; read and clear the final error register
				and 	#rErrorValid		; check handshake register for any errors
				beq 	@Done				; if no errors during write
				lda 	#wrUnderrun 		; report any write error as an underrun error
@Done			rts 						; all done


	if UseDmaHardware then
WriteDataBuffer	stx		DMA1XferCountL		; setup low byte of transfer count
				sty		DMA1XferCountH		; setup high byte of transfer count
				stz		DMA1RAMAddressL		; setup low byte of buffer address (page aligned)
				ldx		<DataBufPtr+1		; get high byte of buffer address
				stx		DMA1RAMAddressH		; setup high byte of buffer address
				ldx		#<(wData*IOA1+DMAEN1)
@pollLoop		bit 	rHandshake			; see if room is available in the FIFO
				assert	Dat1Byte=$80
				bpl 	@pollLoop			; wait until there is room
				sta 	(<SwimWriteReg)		; write it to the disk
				stx		DMA1Control			; start the DMA transfer

				lda		<DMARetryStatus		; see if any DMA timeouts on this track
				bne		@pollDMA			; if so, don't allow interrupts

				lda 	#<DMAxferDelay		; delay to give ADB some time during DMA xfer
				ldx 	#>DMAxferDelay		; high byte
				jsr 	StartReadyTimer		; start the timer
				pla 						; pop pcL
				plx 						; pop pcH
				ldy 	<ReadyTMPB			; get the TMPB index
				jsr 	WaitTMPBdone		; give ADB some time while DMA xfer in progress
				phx 						; push pcH
				pha 						; push pcL
				lda		#0					; allow interrupts during poll
@pollDMA		bra		PollDMADone			; wait for transfer to complete (and return status)
	else
WriteDataBuffer	bit 	rHandshake			; see if room is available in the FIFO
				assert	Dat1Byte=$80
				bpl 	WriteDataBuffer		; wait until there is room
				sta 	(<SwimWriteReg) 	; write it to the disk
				txa							; test the low byte of the count
				beq		@decCountH			; if zero, decrement the high byte
@nextByte		lda 	(<DataBufPtr)		; get the byte
				inc 	<DataBufPtr 		; update buffer pointer
				bne 	@pollLoop			; keep going if not page crossing
				inc 	<DataBufPtr+1		; update buffer pointer page number
@pollLoop		bit 	rHandshake			; see if room is available in the FIFO
				assert	Dat1Byte=$80
				bpl 	@pollLoop			; wait until there is room
				sta 	(<SwimWriteReg)		; write it to the disk
				dex							; decrement low byte of count
				bne		@nextByte			; loop through all of the bytes
@decCountH		dey							; decrement high byte of count
				bpl		@nextByte			; loop until entire count exhausted
				lda		#noErr				; indicate success
				rts							; all done
	endif
				endwith
				endwith
				endproc
	if UseDmaHardware then
				title	'IOP SWIM Driver - Poll DMA Done'

;_______________________________________________________________________
;
;  Routine: 	jsr 	PollDMADone
;  Inputs:		A	- if non-zero don't enable interrupts
;					- if zero enable interrupts for all but last page
;  Outputs: 	A	- DMARetryStatus (non-zero if timeout during DMA)
;  Destroys:	A, X, Y, n, v, c, z
;  Calls:		HandleDMA1Int
;  Called by:	ReadMFMData, WriteDataBuffer
;
;  Function:	Waits for a DMA transfer to complete, enabling interrupts
;				if desired.  Checks to make sure that bytes to transfer
;				periodicaly, returning an error if DMA appears hung.
;_______________________________________________________________________

				seg 	'code'
PollDMADone		proc
				with	SWIMVarsZ
				with	SWIMVars

				tax							; test ints disable flag
				bne		@noInts				; if so, don't allow interrupts
				bra		@checkDMA			; see how the DMA is doing (no ints first time)

@addrChanged	ldx		#0					; reset timeout counter
				ldy		DMA1XferCountL		; get the low byte of the DMA address counter
@intsLoop		cli							; let interrupts have a chance
				sei							; lock out interrupts
@checkDMA		lda		<DMARetryStatus		; see if DMA timeout while waiting
				bne		@done				; if so, return the DMA Retry Status
				lda		DMA1XferCountH		; see if more than a page left
				beq		@noInts				; if not, shut down interrupts, loop for end of xfer
				cpy		DMA1XferCountL		; see if address counter changed
				bne		@addrChanged		; if so, reset the timeout
				inx							; update timeout
				bne		@intsLoop			; if no timeout, allow ints and keep polling
				bra		HandleDMA1Int		; if timed out, kill the xfer and return the error

@noInts			lda		#DMA1INT
				bit		InterruptReg		; see if xfer complete
				bne		HandleDMA1Int		; if so, it's too soon, no ints next time

				ldx		#$00				; reset timeout
@waitNext		ldy		DMA1XferCountL		; get low byte of count
@waitLoop		bit		InterruptReg		; see if xfer complete
				bne		@waitDone			; if so, the wait is over
				dex							; update timeout count
				bne		@waitLoop			; if no timeout, update keep waiting
				cpy		DMA1XferCountL		; see if count changed
				bne		@waitNext			; if count change, restart timeout
				bra		HandleDMA1Int		; DMA timed out, kill it

@waitDone		stz		DMA1Control			; stop the DMA transfer
				sta		InterruptReg		; clear the interrupt

				lda		#noErr				; indicate success
@done			rts							; all done

				export	HandleDMA1Int
HandleDMA1Int	stz		DMA1Control			; stop the DMA transfer
				lda		#DMA1INT
				sta		InterruptReg		; clear the interrupt
				lda 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				sta 	wZeroes 			; clear action and write mode (write done)
				lda 	rError				; read and clear the final error register
				inc		<DMARetryStatus		; indicate that DMA request needs to be retried
				lda		<DMARetryStatus		; return the retry count as the status
				cpa		#2					; see if second offense
				blt		@done				; if not, return count as error code
				lda 	#noNybErr			; otherwise indicate timeout while polling for a byte
@done			rts							; return from the interrupt
				endwith
				endwith
				endproc
	endif
				title	'IOP SWIM Driver - Write GCR Data Loop'

;_______________________________________________________________________
;
;  Routine: 	jmp 	WrGCRdataStart
;
;  Function:	This is the internal data transfer loop used by
;				WriteGCRData.  In order for this loop to execute in the
;				correct number of cycles, it must reside within page zero,
;				and it contains some self modifing code (no opcodes are
;				modified, just immediate operands, and the low byte of
;				an absolute address operand.)  This code is designed to
;				run in lock step with the swim chip, and does not contain
;				any code to check the handshake register, because if we
;				are in sync when we enter, we will stay in sync throughout
;				the loop.
;_______________________________________________________________________

				seg 	'zcode'
WrGCRdataLoop	proc
				entry	WrGCRdataStart		; entry label
				entry	WriteGCRtail		; exit label
				entry	GCRWrByteC			; modified low byte of an abs address
				entry	GCRWrCkSumA 		; modified immediate field
				entry	GCRWrCkSumB 		; modified immediate field
				entry	GCRWrCkSumC 		; modified immediate field
				with	SWIMVarsZ
				with	SWIMVars

				eor 	(<DataBufPtr)		; ByteC' := ByteC xor CkSumB
				sta 	<GCRWrByteC+1		; save ByteC'

				lda 	EncodeGCRHighA,x	; get high bits of ByteA'
				ora 	EncodeGCRHighB,y	; insert high bits of ByteB'
GCRWrByteC		ora 	EncodeGCRHighC		; insert high bits of ByteC'
; T=+65 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = ByteC'
				sta 	<HighNibble+1		; save high bits nibble
HighNibble		lda 	EncodeGCRnibble 	; encode the high bits nibble
				sta 	wData				; write out the high bits nibble
; T=+76 FIFO1 = empty,	FIFO2 = HiBits, ShiftReg = ByteC'

				lda 	EncodeGCRnibble,x	; encode the ByteA' nibble
				sta 	wData				; write out the ByteA' nibble
; T=+84 FIFO1 = ByteA', FIFO2 = HiBits, ShiftReg = ByteC'

				lda 	<GCRWrByteC+1		; restore ByteC'
				eor 	<GCRWrCkSumB+1		; remove CkSumB to get ByteC
GCRWrCkSumC 	adc 	#<GCRWrCkSumC+1		; CkSumC := CkSumC + ByteC + carry
				cpa 	#$80				; carry := CheckSumC [7]
				rol 	a					; [6] [5] [4] [3] [2] [1] [0] [7]
; T=+96 FIFO1 = empty,	FIFO2 = ByteA', ShiftReg = HiBits
				sta 	<GCRWrCkSumC+1		; update checksum C
				inc 	<DataBufPtr 		; point to next data byte

; T=+8	FIFO1 = empty,	FIFO2 = ByteA', ShiftReg = HiBits
WrGCRdataStart	eor 	(<DataBufPtr)		; ByteA' := ByteA xor CkSumC
				tax 						; save ByteA'
				eor 	<GCRWrCkSumC+1		; remove CkSumC to get ByteA
GCRWrCkSumA 	adc 	#GCRWrCkSumA+1		; CkSumA := CkSumA + ByteA + carry
				sta 	<GCRWrCkSumA+1		; update checksum A
				inc 	DataBufPtr			; point to next data byte
				bne 	@CrossPageDone		; if not cross page
				inc 	<DataBufPtr+1		; update high byte on page crossing
@CrossPageDone
;T=+127 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = ByteA'
; on the bread board, accesses to the swim chip cause the cycle to be
; stretched by 0.25 clocks, and since we make 4 accesses in this loop,
; the total stretch time is 1.0 clocks, which makes this loop exactly
; 128 clocks. (except for the one iteration which crosses a page boundary,
; which is 132 clocks)

; T=0…7 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = ByteA'
				lda 	EncodeGCRnibble,y	; encode the ByteB' nibble
				sta 	wData				; write out the ByteB' nibble
; T=+8	FIFO1 = empty,	FIFO2 = ByteB', ShiftReg = ByteA'

				ldy 	<GCRWrByteC+1		; get ByteC'
				lda 	EncodeGCRnibble,y	; encode the ByteC' nibble
				sta 	wData				; write out the ByteC' nibble
; T=+19 FIFO1 = ByteC', FIFO2 = ByteB', ShiftReg = ByteA'

				lda 	<GCRWrCkSumA+1		; restore checksum A
				eor 	(<DataBufPtr)		; ByteB' := ByteB xor CkSumA
				tay 						; save ByteB'
				eor 	<GCRWrCkSumA+1		; remove CkSumA to get ByteB
; T=+32 FIFO1 = empty,	FIFO2 = ByteC', ShiftReg = ByteB'
GCRWrCkSumB 	adc 	#GCRWrCkSumB+1		; CkSumB := CkSumB + ByteB + carry
				sta 	<GCRWrCkSumB+1		; update checksum B
				inc 	<DataBufPtr 		; point to next data byte
				bne 	WrGCRdataLoop		; loop until end of page 2 (512 bytes)
				jmp 	WriteGCRtail		; write out last 2 bytes and checksum
; T=+51 FIFO1 = empty,	FIFO2 = ByteC', ShiftReg = ByteB'
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - Write Sector Data'

;_______________________________________________________________________
;
;  Routine: 	jsr 	WriteSectorData
;  Inputs:		CurrentDrive	- active drive number
;				TagBufPtr		- pointer to 12 byte tag buffer
;				DataBufPtr		- pointer to 512 byte data buffer
;				SectHdrSector	- sector number that we are about to write
;				Expects to be called after ReadSectorHdr
;  Outputs: 	A - error status code
;				n,z - based on value returned in A
;  Destroys:	X, Y, n, z, c
;  Calls:		WriteUnknownData, WriteGCRData, WriteMFMData, RdWrGCRonHDData
;  Called by:	SWIMWrite
;
;  Function:	Writes the sector Tag and Data information for the current
;				sector on the selected head, on the current cylinder.
;
;				NOTE: The data buffer must be page aligned, and the tag
;				buffer must not span across pages.
;_______________________________________________________________________

				seg 	'code'
WriteSectorData proc
				entry	WriteDataDecode
				entry	WriteUnknownData
				entry	WriteGCRData
				entry	WriteGCRFmtData
				entry	WriteGCRtail
				entry	WriteMFMData
				with	SWIMVarsZ
				with	SWIMVars

				ldx 	<SectHdrFmtIndex	; get index from ReadSectorHdr
				jmp 	(WriteDataDecode,x) ; dispatch to the routine

WriteUnknownData
				lda 	#noDtaMkErr 		; unsupported format, return with error
				rts 						; all done
				title	'IOP SWIM Driver - Write Sector Data - GCR'

WriteGCRData
*				lda 	rData				; empty garbage byte out of the FIFO,
*				lda 	rError				; read and clear any pending errors
				lda 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				sta 	wZeroes 			; clear action and write mode
				lda 	#WriteMode			; prepare to enter write mode
				sta 	wOnes				; change to write mode
				ldy 	#ClearFIFO			; prepare to clear the FIFO
				sty 	wOnes				; toggle it to one
				sty 	wZeroes 			; and back to zero to clear it
;		FIFO1 = empty,	FIFO2 = empty,	ShiftReg = empty
				lda 	rError				; read and clear errors again

				ldx 	#StartAction		; prepare to start writing
				lda 	#$FF				; byte 1 of 6 byte sync field
				sta 	wData				; write it to the FIFO
;		FIFO1 = empty,	FIFO2 = $FF,	ShiftReg = empty

WriteGCRFmtData
				lda 	#$3F				; byte 2 of 6 byte sync field
				sta 	wData				; write it to the FIFO

;		FIFO1 = $3F,	FIFO2 = $FF,	ShiftReg = empty
				lda 	(<TagBufPtr)		; get byte 1 of 12 byte tag field
				sta 	<WrTagBytesA+3		; save ByteA' (byte 1)
				sta 	<GCRWrCkSumA+1		; CkSumA := ByteA

				assert	ClearFIFO=1			; y = 1 from "ldy 	#ClearFIFO" above
*				ldy 	#1					; point to next tag byte
				eor 	(<TagBufPtr),y		; ByteB' := ByteB xor CkSumA (byte 2 of 12)

				stx 	wOnes				; turn on Action to start shifting out the write data
; T=0	FIFO1 = empty,	FIFO2 = $3F,	ShiftReg = $FF

				sta 	<WrTagBytesB+3		; save ByteB' (byte 2)
				eor 	<GCRWrCkSumA+1		; remove CkSumA to get ByteB
				sta 	<GCRWrCkSumB+1		; CkSumB := ByteB
				iny 						; point to next tag byte

				eor 	(<TagBufPtr),y		; ByteC' := ByteC xor CkSumB (byte 3 of 12)
				sta 	<WrTagBytesC+3		; save ByteC' (byte 3)
				eor 	<GCRWrCkSumB+1		; remove CkSumB to get ByteC
				cpa 	#$80				; carry := CheckSumC [7]
				rol 	a					; [6] [5] [4] [3] [2] [1] [0] [7]
				sta 	<GCRWrCkSumC+1		; initialize checksum C
				iny 						; point to next tag byte

				ldx 	#$CF				; byte 3 of 6 byte sync field
; T=32	FIFO1 = empty,	FIFO2 = empty,	ShiftReg = $3F
				stx 	wData				; write it to the FIFO
; T=36	FIFO1 = empty,	FIFO2 = $CF,	ShiftReg = $3F
				ldx 	#$F3				; byte 4 of 6 byte sync field
@bugPoll		bit 	rHandshake			; wait for 1 byte available in the FIFO
; T=0…7 FIFO1 = empty,	FIFO2 = $CF,	ShiftReg = $3F
				assert	Dat1Byte=$80
				bpl 	@bugPoll			; wait until there is room for 1
				stx 	wData				; write it to the FIFO
; T=48	FIFO1 = $F3,	FIFO2 = $CF,	ShiftReg = $3F

				eor 	(<TagBufPtr),y		; ByteA' := ByteA xor CkSumC (byte 4 of 12)
				sta 	<WrTagBytesA+2		; save ByteA' (byte 4)
				eor 	<GCRWrCkSumC+1		; remove CkSumC to get ByteA
				adc 	<GCRWrCkSumA+1		; CkSumA := CkSumA + ByteA + carry
; T=64	FIFO1 = empty,	FIFO2 = $F3,	ShiftReg = $CF
				sta 	<GCRWrCkSumA+1		; update checksum A
				iny 						; point to next tag byte

				eor 	(<TagBufPtr),y		; ByteB' := ByteB xor CkSumA (byte 5 of 12)
				sta 	<WrTagBytesB+2		; save ByteB' (byte 5)
				eor 	<GCRWrCkSumA+1		; remove CkSumA to get ByteB
				adc 	<GCRWrCkSumB+1		; CkSumB := CkSumB + ByteB + carry
				sta 	<GCRWrCkSumB+1		; update checksum B
				iny 						; point to next tag byte

				eor 	(<TagBufPtr),y		; ByteC' := ByteC xor CkSumB (byte 6 of 12)
				sta 	<WrTagBytesC+2		; save ByteC' (byte 6)
; T=96	FIFO1 = empty,	FIFO2 = empty,	ShiftReg = $F3
				eor 	<GCRWrCkSumB+1		; remove CkSumB to get ByteC
				adc 	<GCRWrCkSumC+1		; CkSumC := CkSumC + ByteC + carry
				cpa 	#$80				; carry := CheckSumC [7]
				rol 	a					; [6] [5] [4] [3] [2] [1] [0] [7]
				sta 	<GCRWrCkSumC+1		; update checksum C
				iny 						; point to next tag byte

				ldx 	#$FC				; byte 5 of 6 byte sync field
				stx 	wData				; write it to the FIFO
; T=115 FIFO1 = empty,	FIFO2 = $FC,	ShiftReg = $F3
				ldx 	#$FF				; byte 6 of 6 byte sync field
				stx 	wData				; write it to the FIFO
; T=121 FIFO1 = $FF,	FIFO2 = $FC,	ShiftReg = $F3

				eor 	(<TagBufPtr),y		; ByteA' := ByteA xor CkSumC (byte 7 of 12)
; T=128 FIFO1 = empty,	FIFO2 = $FF,	ShiftReg = $FC
				sta 	<WrTagBytesA+1		; save ByteA' (byte 7)
				eor 	<GCRWrCkSumC+1		; remove CkSumC to get ByteA
				adc 	<GCRWrCkSumA+1		; CkSumA := CkSumA + ByteA + carry
				sta 	<GCRWrCkSumA+1		; update checksum A
				iny 						; point to next tag byte

				eor 	(<TagBufPtr),y		; ByteB' := ByteB xor CkSumA (byte 8 of 12)
				sta 	<WrTagBytesB+1		; save ByteB' (byte 8)
				eor 	<GCRWrCkSumA+1		; remove CkSumA to get ByteB
				adc 	<GCRWrCkSumB+1		; CkSumB := CkSumB + ByteB + carry
				sta 	<GCRWrCkSumB+1		; update checksum B
				iny 						; point to next tag byte

; T=160 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = $FF
				ldx 	GCRDataMark+0		; byte 1 of 3 byte data mark sequence
				stx 	wData				; write it to the FIFO
; T=167 FIFO1 = empty,	FIFO2 = $D5,	ShiftReg = $FF
				ldx 	GCRDataMark+1		; byte 2 of 3 byte data mark sequence
				stx 	wData				; write it to the FIFO
; T=175 FIFO1 = $AA,	FIFO2 = $D5,	ShiftReg = $FF

				eor 	(<TagBufPtr),y		; ByteC' := ByteC xor CkSumB (byte 9 of 12)
				sta 	<WrTagBytesC+1		; save ByteC' (byte 9)
				eor 	<GCRWrCkSumB+1		; remove CkSumB to get ByteC
				adc 	<GCRWrCkSumC+1		; CkSumC := CkSumC + ByteC + carry
; T=192 FIFO1 = empty,	FIFO2 = $AA,	ShiftReg = $D5
				cpa 	#$80				; carry := CheckSumC [7]
				rol 	a					; [6] [5] [4] [3] [2] [1] [0] [7]
				sta 	<GCRWrCkSumC+1		; update checksum C
				iny 						; point to next tag byte

				eor 	(<TagBufPtr),y		; ByteA' := ByteA xor CkSumC (byte 10 of 12)
				sta 	<WrTagBytesA+0		; save ByteA' (byte 10)
				eor 	<GCRWrCkSumC+1		; remove CkSumC to get ByteA
				adc 	<GCRWrCkSumA+1		; CkSumA := CkSumA + ByteA + carry
				sta 	<GCRWrCkSumA+1		; update checksum A
				iny 						; point to next tag byte

				eor 	(<TagBufPtr),y		; ByteB' := ByteB xor CkSumA (byte 11 of 12)
; T=224 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = $AA
				sta 	<WrTagBytesB+0		; save ByteB' (byte 11)
				eor 	<GCRWrCkSumA+1		; remove CkSumA to get ByteB
				adc 	<GCRWrCkSumB+1		; CkSumB := CkSumB + ByteB + carry
				sta 	<GCRWrCkSumB+1		; update checksum B
				iny 						; point to next tag byte

				ldx 	GCRDataMark+2		; byte 3 of 3 byte data mark sequence
				stx 	wData				; write it to the FIFO
; T=244 FIFO1 = empty,	FIFO2 = $AD,	ShiftReg = $AA

				eor 	(<TagBufPtr),y		; ByteC' := ByteC xor CkSumB (byte 12 of 12)
				sta 	<WrTagBytesC+0		; save ByteC' (byte 12)

				ldy 	<SectHdrSector		; sector number that we are about to write
; T=256 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = $AD
				ldx 	EncodeGCRnibble,y	; encode the ByteC' nibble
				stx 	wData				; write out the ByteC' nibble
; T=263 FIFO1 = empty,	FIFO2 = Sector, ShiftReg = $AD

				eor 	<GCRWrCkSumB+1		; remove CkSumB to get ByteC
				adc 	<GCRWrCkSumC+1		; CkSumC := CkSumC + ByteC + carry
				cpa 	#$80				; carry := CheckSumC [7]
				rol 	a					; [6] [5] [4] [3] [2] [1] [0] [7]
				sta 	<GCRWrCkSumC+1		; update checksum C

				ldx 	#4-1				; get loop counter and index
@WriteTagsLoop	ldy 	<WrTagBytesA,x		; get ByteA'
				lda 	EncodeGCRHighA,y	; get high bits of ByteA'
; T=288 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = Sector
; T=+32 FIFO1 = empty,	FIFO2 = ByteC', ShiftReg = ByteB'
				ldy 	<WrTagBytesB,x		; get ByteB'
				ora 	EncodeGCRHighB,y	; insert high bits of ByteB'
				ldy 	<WrTagBytesC,x		; get ByteC'
				ora 	EncodeGCRHighC,y	; insert high bits of ByteC'
				tay 						; get high bits nibble
				lda 	EncodeGCRnibble,y	; encode the high bits nibble
				sta 	wData				; write out the high bits nibble
; T=312 FIFO1 = empty,	FIFO2 = HiBits, ShiftReg = Sector
; T=+58 FIFO1 = HiBits, FIFO2 = ByteC', ShiftReg = ByteB'

				ldy 	<WrTagBytesA,x		; get ByteA'
				lda 	EncodeGCRnibble,y	; encode the ByteA' nibble
; T=320 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = HiBits
; T=+66 FIFO1 = empty,	FIFO2 = HiBits, ShiftReg = ByteC'
				sta 	wData				; write out the ByteA' nibble
; T=324 FIFO1 = empty,	FIFO2 = ByteA', ShiftReg = HiBits
; T=+70 FIFO1 = ByteA', FIFO2 = HiBits, ShiftReg = ByteC'

				ldy 	<WrTagBytesB,x		; get ByteB'
				lda 	<WrTagBytesC,x		; get ByteC'
				sta 	<GCRWrByteC+1		; save ByteC'
				dex 						; decrement the index / loop count
				bmi 	@WriteTagsDone		; if no more tags to do

@WriteTagsPoll	bit 	rHandshake			; wait for 2 bytes available in the FIFO
; T=0…7 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = ByteA'
				assert	Dat2Bytes=$40
				bvc 	@WriteTagsPoll		; wait until there is room for 2
				lda 	EncodeGCRnibble,y	; encode the ByteB' nibble
				sta 	wData				; write out the ByteB' nibble
; T=+10 FIFO1 = empty,	FIFO2 = ByteB', ShiftReg = ByteA'

				ldy 	<GCRWrByteC+1		; get ByteC'
				lda 	EncodeGCRnibble,y	; encode the ByteC' nibble
				sta 	wData				; write out the ByteC' nibble
; T=+24 FIFO1 = ByteC', FIFO2 = ByteB', ShiftReg = ByteA'

				bra 	@WriteTagsLoop		; loop through all tag bytes

@WriteTagsDone	bit 	rHandshake			; wait for 1 bytes available in the FIFO
; T=0…7 FIFO1 = empty,	FIFO2 = ByteA', ShiftReg = HiBits
				assert	Dat1Byte=$80
				bpl 	@WriteTagsDone		; wait until there is room for 1
				lda 	<GCRWrCkSumC+1		; setup checksum C
				jmp 	WrGCRdataStart		; done with tags, start writing the data

; T=+51 FIFO1 = empty,	FIFO2 = ByteC', ShiftReg = ByteB'
WriteGCRtail	lda 	EncodeGCRHighA,x	; get high bits of ByteA'
				ora 	EncodeGCRHighB,y	; insert high bits of ByteB'
				phx 						; save ByteA'
				tax 						; load index with high bits nibble
; T=+64 FIFO1 = empty,	FIFO2 = empty,	ShiftReg = ByteC'
				lda 	EncodeGCRnibble,x	; encode the high bits nibble
				sta 	wData				; write out the high bits nibble
; T=+72 FIFO1 = empty,	FIFO2 = HiBits, ShiftReg = ByteC'
				plx 						; restore ByteA'

				lda 	EncodeGCRnibble,x	; encode the ByteA' nibble
				sta 	wData				; write out the ByteA' nibble
; T=+84 FIFO1 = ByteA', FIFO2 = HiBits, ShiftReg = ByteC'

				lda 	GCRDataSlipMark+1	; byte 2 of 2 byte slip mark
				pha 						; stack it for later

				lda 	GCRDataSlipMark+0	; byte 1 of 2 byte slip mark
; T=+95 FIFO1 = empty,	FIFO2 = ByteA', ShiftReg = HiBits
				pha 						; stack it for later

				ldx 	<GCRWrCkSumC+1		; get checksum byte C
				lda 	EncodeGCRnibble,x	; encode the byte C nibble
				pha 						; stack it for later

				lda 	EncodeGCRnibble,y	; encode the ByteB' nibble
				sta 	wData				; write out the ByteB' nibble
; T+116 FIFO1 = ByteB', FIFO2 = ByteA', ShiftReg = HiBits

				ldy 	<GCRWrCkSumB+1		; get checksum byte B
				lda 	EncodeGCRnibble,y	; encode the byte B nibble
				pha 						; stack it for later

; T+126 FIFO1 = empty,	FIFO2 = ByteB', ShiftReg = ByteA'
				lda 	EncodeGCRHighC,x	; get high bits of byte C
				ora 	EncodeGCRHighB,y 	; insert high bits of byte B

				ldx 	<GCRWrCkSumA+1		; get checksum byte A
				ldy 	EncodeGCRnibble,x	; encode the byte A nibble
				phy 						; stack it for later

				ora 	EncodeGCRHighA,x	; insert high bits of byte A
				tay 						; get high bits nibble
				lda 	EncodeGCRnibble,y	; encode the high bits nibble
				sta 	wData				; write out the high bits nibble
; T+155 FIFO1 = ChkHi,	FIFO2 = ByteB', ShiftReg = ByteA'

				ldx 	#5					; number of stacked bytes
@TailLoop		pla 						; get the byte from the stack
				jsr 	WriteByte			; write it out
				dex 						; update byte count
				bne 	@TailLoop			; loop until all bytes written

;  Then finally add a few gap bytes so that the SlipMark
;  will be fully written before writing is disabled...

				lda 	GCRGapValue 		; get the gap value
				bit		<WritingForFmt		; see if we are formatting
				bmi		@FmtExit			; if so, exit differently
				jmp 	WriteDataExit		; flush the FIFO to disk

@FmtExit		jsr		WriteByte			; write out 2 GCR gap bytes
				jmp		WriteByte			; and return
				title	'IOP SWIM Driver - Write Sector Data - MFM'

WriteMFMData

;  De-select RdData0/1 to de-select index pulse during write to prevent noise/crosstalk
;  on the RdData line from interfering with WrData and causing a bad write.  This is a
;  workaround for a noisy external drive cable.  Instead we select somthing that will be
;  a constant high.

				assert	(rMFMDriveAdr+HeadSelect)=rMFMModeOnAdr
				lda 	#rMFMDriveAdr\
						++ph0123en			; select rMFMDriveAdr/rMFMModeOnAdr
				sta 	wPhase				; change the phase lines, but not HeadSelect.

				lda 	rError				; read and clear any pending errors
				lda 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				sta 	wZeroes 			; clear action and write mode
				lda 	#WriteMode			; prepare to enter write mode
				sta 	wOnes				; change to write mode
				lda 	#ClearFIFO			; prepare to clear the FIFO
				sta 	wOnes				; toggle it to one
				sta 	wZeroes 			; and back to zero to clear it
				lda 	rError				; read and clear errors again

;		Save the return PC for the Async call to WriteDataBuffer

				pla 						; pop pcL
				sta 	<AsyncPcL			; save PcL
				pla 						; pop pcH
				sta 	<AsyncPcH			; save PcH

;		Delay until we are close to 22 bytes past the end of the address field before
;		starting the write.

				ldx 	#MFMreWriteDelay	; get the delay loop counter
@reWriteWait	dex 						; decrement the counter
				bne 	@reWriteWait		; loop until count exhausted

;		write out the "bytes of zeroes" sync field

				assert	MFMSyncSize>2		; assume that Sync will more than fill the FIFO
				lda 	MFMSyncValue		; get the sync value
				sta 	(<SwimWriteReg)		; fill the first FIFO byte
				sta 	(<SwimWriteReg)		; fill the second FIFO byte before setting action

				ldx 	#StartAction		; prepare to start writing
				stx 	wOnes				; turn on Action to start shifting out the write data

				ldx 	#MFMSyncSize-2		; number of remaining sync bytes to write
				jsr 	WriteRepeated		; write out the remaining sync bytes


;		write out the data mark byte sequence

				ldx 	#MFMDataMarkSize-1	; number of MARK bytes to write
				ldy 	#MFMDataMark-SectorTemplates
				assert	(wData+1)=wMark
				inc 	<SwimWriteReg		; point to wMark
				jsr 	WriteTemplate		; write the data mark mark bytes

				assert	(wMark-1)=wData
				dec 	<SwimWriteReg		; point back to wData
				lda 	SectorTemplates,y	; get final byte


;		write the sector data

*				ldx 	#<BlockSize			; x := zero from WriteTemplate
				ldy 	#>BlockSize			; page counter 2 pages = 512 bytes
				jsr		WriteDataBuffer		; write out the data buffer
				bne		@DMAerror			; if timeout during DMA, report the error


;		End the sector data with 2 CRC bytes...

				jsr 	WriteMFMCRC 		; write the 2 CRC bytes

;		Restore the return PC

				lda 	<AsyncPcH			; get PcH
				pha 						; restore PcH
				lda 	<AsyncPcL			; get PcL
				pha 						; restore PcL

;  Then finally add a few gap bytes so that the CRC
;  will be fully written before writing is disabled...

				lda 	MFMGapValue 		; get the gap value
				jmp 	WriteDataExit		; flush the FIFO to disk

@DMAerror		ldx 	<AsyncPcH			; get PcH
				phx 						; restore PcH
				ldx 	<AsyncPcL			; get PcL
				phx 						; restore PcL
				tax							; set conditions to indicate DMA retry
				rts 						; return DMA retry count in reg-A
				endwith
				endwith
				endproc

				seg 	'WordConsts'
WriteDataDecode proc
				dc.w	WriteUnknownData	;  0 - uncheckedFormat
				dc.w	WriteUnknownData	;  1 - unknownFormat
				dc.w	WriteUnknownData	;  2 - HD20Format
				dc.w	WriteGCRData		;  3 - GCR400Kformat
				dc.w	WriteGCRData		;  4 - GCR800Kformat
				dc.w	WriteMFMData		;  5 - MFM720Kformat
				dc.w	WriteMFMData		;  6 - MFM1440Kformat
				dc.w	RdWrGCRonHDData 	;  7 - GCRonHDformat
				dc.w	WriteUnknownData	;  8 - reserved for future use
				dc.w	WriteUnknownData	;  9 - reserved for future use
				dc.w	WriteUnknownData	; 10 - reserved for future use
				endproc
				title	'IOP SWIM Driver - Skip Sector Data'

;_______________________________________________________________________
;
;  Routine: 	jsr 	SkipSectorData
;  Inputs:		none
;				Expects to be called after ReadSectorHdr
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Calls:		SkipUnknownData, SkipGCRData, SkipMFMData, SleepShort
;  Called by:	SWIMRead, SWIMReadVerify, SWIMWrite, SWIMFormatVerify,
;				PrefetchIntoCache
;
;  Function:	Skips the sector Tag and Data information for the current
;				sector whose header has just been read.
;
;_______________________________________________________________________

				seg 	'code'
SkipSectorData	proc
				entry	SkipDataDecode
				entry	SkipUnknownData
				entry	SkipGCRData
				entry	SkipMFMData
				with	SWIMVarsZ
				with	SWIMVars

				lda		<WrongSectsLeft		; see how many sectors we can skip
				cpa		#WrongSectsMax/2	; see if more than half used
				blt		DontSkip			; if so, don't give up the CPU
				ldx 	<SectHdrFmtIndex	; get index from ReadSectorHdr
				jmp 	(SkipDataDecode,x)	; dispatch to the routine

SkipGCRData		lda		#GCRSectorDataDelay-TypicalADBDelay-TMPB1ms
				jmp 	SleepShort			; give ADB some time

SkipUnknownData		 						; unsupported format, use MFM timing
SkipMFMData		lda		#MFMSectorDataDelay-TypicalADBDelay-TMPB1ms
				jmp 	SleepShort			; give ADB some time

DontSkip		rts 						; unsupported format, just return
				endwith
				endwith
				endproc

				seg 	'WordConsts'
SkipDataDecode	proc
				dc.w	SkipUnknownData		;  0 - uncheckedFormat
				dc.w	SkipUnknownData		;  1 - unknownFormat
				dc.w	SkipUnknownData		;  2 - HD20Format
				dc.w	SkipGCRData			;  3 - GCR400Kformat
				dc.w	SkipGCRData			;  4 - GCR800Kformat
				dc.w	SkipMFMData			;  5 - MFM720Kformat
				dc.w	SkipMFMData			;  6 - MFM1440Kformat
				dc.w	SkipGCRData		 	;  7 - GCRonHDformat
				dc.w	SkipUnknownData		;  8 - reserved for future use
				dc.w	SkipUnknownData		;  9 - reserved for future use
				dc.w	SkipUnknownData		; 10 - reserved for future use
				endproc
				title	'IOP SWIM Driver - Format Disk Track'

;_______________________________________________________________________
;
;  Routine: 	jsr 	FormatTrack
;  Inputs:		
;  Outputs: 	A - error status code
;				n,z - based on value returned in A
;  Destroys:	X, Y, n, z, c
;  Calls:		FmtUnknownTrack, FmtGCRTrack, FmtMFMTrack
;  Called by:	SWIMFormat
;
;  Function:	Formats a Cylinder on the current drive.
;
;_______________________________________________________________________

				seg 	'code'
FormatTrack 	proc
				entry	FmtTrackDecode
				entry	FmtUnknownTrack
				entry	FmtGCRTrack
				entry	FmtMFMTrack
				with	SWIMVarsZ
				with	SWIMVars
				with	SectorBuffers

				ply 						; pop pcL
				sty 	<AsyncPcL			; save PcL
				ply 						; pop pcH
				sty 	<AsyncPcH			; save PcH

				ldy 	<CurrentDrive		; get the active drive number
				ldx 	DiskFormat,y		; see what format to read
				lda 	RequiredMedia,x 	; see what media is required
				cpa 	MediaKind,y 		; see if we have correct media
				bne 	FmtUnknownTrack		; if wrong kind of media

				txa 						; get the DiskFormat
				asl 	a					; compute the table index
				tax 						; load the index register
				jmp 	(FmtTrackDecode,x)	; dispatch to the routine

FmtUnknownTrack lda 	#paramErr			; unsupported format, return with error
FmtTrackDone	tax							; save the error code
				lda 	<AsyncPcH			; restore PcH
				pha 						; push pcH
				lda 	<AsyncPcL			; restore PcL
				pha 						; push pcL

				lda		<BaseBufferNum		; get base from last head formatted
				clc							; setup for addition
				adc		<PhysSectsPerHead	; point to the first buffer for this head
				sta		<BaseBufferNum		; update the base buffer number

				txa 						; restore error code, set flags
				rts 						; all done

HdrFields		record	0,increment
GCRCkSum
MFMBlkSize		ds.b	1
GCRFmtKind
MFMSector		ds.b	1
GCRHeadCyl
MFMHead 		ds.b	1
GCRSector
MFMCylinder 	ds.b	1
GCRCylinder
MFMLastAddrMark ds.b	1
				endr
				with	HdrFields

WriteHdrInfo	lda 	<SectHdrInfo+5-1	; get the first byte to write
@Poll			bit 	rHandshake			; see if we have room in the FIFO
				assert	Dat2Bytes=$40
				bvc 	@Poll				; wait until there is room for two bytes
				sta 	(<SwimWriteReg)		; write the byte
				lda 	<SectHdrInfo+5-2	; get the second byte to write
				sta 	(<SwimWriteReg)		; write the byte

				jsr		SetupBufferPtrs		; setup DataBufPtr and TagBufPtr for this sector

				lda 	<SectHdrInfo+5-3	; get the third byte to write
				sta 	(<SwimWriteReg) 	; blindly write the byte

				lda 	<SectHdrInfo+5-4	; get the fourth byte to write
				sta 	(<SwimWriteReg)		; blindly write the byte

				lda 	<SectHdrInfo+5-5	; get the final byte to write
				jmp		WriteByte			; write it out


;	Create a buffer filled with the sector format pattern so that DMA can be used to blast them out

FillDataBuffer	bit		<CurDataBCPB		; see if optional data buffer exists
				bpl		@dataDone			; if so, don't fill buffer
				ldx		#>DataBuffers		; get the starting page number
				stx		<DataBufPtr+1		; initialize high byte of buffer pointer
				ldx		#>DataBufSize		; number of pages to initialize
@fillerLoop		sta		(<DataBufPtr),y		; initialize the next byte of the buffer
				iny							; update the page offset
				bne		@fillerLoop			; loop through the page
				inc		<DataBufPtr+1		; update high byte of buffer pointer
				dex							; update page count
				bne		@fillerLoop			; loop through all of the pages
@dataDone		rts							; all done


FillTagBuffer	sta		TagBufStart,y		; initialize the first page of the buffer
				sta		TagBufEnd-$100,y	; initialize the last page of the buffer
				iny							; update the page offset
				bne		FillTagBuffer		; loop through the page
				rts							; all done


FmtStartWrite	ldy		<PhysHead			; get the desired head number
				bne		@start				; if not the first one, skip initialization
				stz		<BaseBufferNum		; reset buffer number for first head of new cylinder
				ldy 	<PhysCylinder		; get the desired cylinder number
				bne		@start				; if not the first one, skip initialization
				stz		<DataBufPtr			; initialize low byte of buffer pointer
				bcc		@GCRgap				; if GCR mode, setup GCR gap sync buffer

;	Create a buffer filled with MFM gap bytes so that DMA can be used to blast them out

@MFMgap			lda 	MFMFmtFillValue 	; get the data value to write
				jsr		FillDataBuffer		; fill the data buffer with the format pattern
				lda 	MFMGapValue 		; get the gap byte value
				jsr		FillTagBuffer		; fill the buffer with the gap value
				lda		#$FF				; MFM doesn't need tags, so don't fetch them next time
				sta		<CurTagBCPB			; see if optional tag buffer exists
				bra		@start				; go start the track format

;	Create a buffer filled with GCR sync groups so that DMA can be used to blast them out

@GCRgap			lda		#0					; zero's if encoding on the fly, 96 if raw data
				bit		<CurTagBCPB			; see if optional tag buffer exists
				bpl		@fillGCR			; if so, use zero's as the data buffer pattern
				jsr		FillTagBuffer		; initialize the tags to zero's if no buffer exists
				lda 	GCRFmtFillValue		; get the data value to write
				bit		<CurDataBCPB		; see if optional data buffer exists
				bpl		@fillGCR			; if so, use encode the data on the fly, using write code
				stz		<WritingForFmt		; if no tag or data buffers, use DMA to blast the constant patterh
@fillGCR		jsr		FillDataBuffer		; fill the data buffer with the format pattern
				ldx		#>GCRGapBuffer		; point to the data buffer
				stx		<DataBufPtr+1		; initialize high byte of buffer pointer
				ldx		#GCRInitialSyncCount; number of sync groups to put into buffer
@syncGroupLoop	ldy		#GCRGapSyncSize-1	; loop count and index for each sync group
@syncCopyLoop	lda		GCRGapSync,y		; get a byte of sync
				sta		(<DataBufPtr),y		; copy it into the buffer
				dey							; update the loop counter / index
				bpl		@syncCopyLoop		; copy all of the bytes in the sync group
				lda		<DataBufPtr			; get the low byte of the pointer
				clc							; setup carry for addition
				adc		#GCRGapSyncSize		; point to next sync group in the buffer
				sta		<DataBufPtr			; update the low byte of the pointer
				bcc		@updateCount		; if no carry, upper byte is OK
				inc		<DataBufPtr+1		; if carry, update page number
@updateCount	dex							; update the sync group count
				bne		@syncGroupLoop		; loop through all of the sync groups

@start			stz		<DataBufPtr			; initialize low byte of buffer pointer
				ldy 	<PhysHead			; get the desired head number
				lda 	HeadSelectDecode,y	; get the head select command
				jsr 	AdrAndSense 		; select the disk head
				lda 	rError				; read and clear any pending errors
				lda 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				sta 	wZeroes 			; clear action and write mode
				lda 	#WriteMode			; prepare to enter write mode
				sta 	wOnes				; change to write mode
				lda 	#ClearFIFO			; prepare to clear the FIFO
				sta 	wOnes				; toggle it to one
				sta 	wZeroes 			; and back to zero to clear it
				lda 	rError				; read and clear errors again
				lda 	#$FF				; garbage bytes to write initially
				sta 	wData				; write it to the FIFO
				sta 	wData				; write it to the FIFO
				ldx 	#StartAction		; prepare to start writing
				stx 	wOnes				; turn on Action to start shifting out the write data
				jmp		WriteByte			; fill the fifo with yet another byte and return
				title	'IOP SWIM Driver - Format Track - GCR'

FmtGCRTrack 	lda 	<PhysCylinder		; get the current cylinder
				tax 						; save a copy
				and 	#$3F				; mask to six bits
				sta 	<SectHdrInfo\
						+GCRCylinder		; setup low half of cylinder number
				stz 	<SectHdrInfo\
						+GCRSector			; start with sector zero
				lda 	<PhysHead			; get the head number
				sta 	<SeekingHead		; save head for ReadSectorHdr later
				beq 	@HeadOK 			; if zero use it
				lda 	#$20				; otherwise, set high bit of nibble
@HeadOK 		ora 	EncodeGCRHighC,x	; position and insert upper 2 bits of cylinder
				sta 	<SectHdrInfo\
						+GCRHeadCyl			; setup the side/upper cylinder bits
				lda 	<FmtByteBlkSize		; get format byte
				and		#$20				; get just the sides bit
				ora 	<Interleave 		; include interleave
				sta 	<SectHdrInfo\
						+GCRFmtKind			; setup the format kind
				eor 	<SectHdrInfo\
						+GCRCylinder		; compute checksum
				eor 	<SectHdrInfo\
						+GCRHeadCyl			; compute checksum
				sta 	Sect0CkSum			; remember the checksum for sector zero

				ldy 	#5-1				; loop counter / index
@EncodeHdrLoop	ldx 	<SectHdrInfo,y		; get a byte to encode
				lda 	EncodeGCRnibble,x	; encode it
				sta 	SectHdrInfo,y		; replace it
				dey 						; decrement counter / index
				bne 	@EncodeHdrLoop		; encode all remaining bytes

				clc							; indicate GCR format
				jsr 	FmtStartWrite		; select the head and start writing
				stz 	<PhysSector 		; start sector index at zero
				lda		#>GCRGapBuffer		; point to gap sync data buffer
				sta		<DataBufPtr+1		; initialize high byte of buffer pointer
				ldx 	#<(GCRInitialSyncCount\
						*GCRGapSyncSize)	; low byte of byte count
				ldy 	#>(GCRInitialSyncCount\
						*GCRGapSyncSize)	; high byte of byte count
				lda 	#$FF				; garbage bytes to write initially
				jsr		WriteDataBuffer		; write out the data buffer
				bne		FmtGCRTrack			; if error due to DMA timeout, try re-formatting the track
@NextSector 	lda 	<SectorGapSize		; add the inter sector sync count
				sta 	<LoopCountL 		; setup Sync loop counter
@FmtSyncs		ldx 	#GCRGapSyncSize 	; number of bytes to write
				ldy 	#GCRGapSync\
						-SectorTemplates 	; offset of gap sync template
				jsr 	WriteTemplate		; write a gap sync group
				dec 	<LoopCountL 		; update the gap sync loop count
				bne 	@FmtSyncs			; write all of the sync groups

				ldy 	#GCRAddrMark\
						-SectorTemplates	; offset of addr mark template
				ldx 	#GCRAddrMarkSize	; number of bytes to write
				jsr 	WriteTemplate		; write the address mark

				ldx 	<PhysSector 		; get sector index
				lda 	<SectorMap,x		; get the sector number
				sta		<SectHdrSector		; save the sector number for buffer indexing
				tay 						; save for later
				eor 	Sect0CkSum			; compute the new checksum
				tax 						; setup to encode
				lda 	EncodeGCRnibble,x	; encode the checksum
				sta 	<SectHdrInfo\
						+GCRCkSum			; save computed checksum
				lda 	EncodeGCRnibble,y	; encode the sector number
				sta 	<SectHdrInfo\
						+GCRSector			; save encoded sector number

				jsr 	WriteHdrInfo		; write out the sector header info
				ldy 	#GCRAddrSlipMark\
						-SectorTemplates	; offset of addr slip mark template
				ldx 	#GCRAddrSlipMarkSize+1	; the end of the sector header, first byte of gap sync
				jsr 	WriteTemplate		; write the end of the header, partial gap sync

				bit		<WritingForFmt		; see if we have optional data to write
				bpl		@FmtDefaultGCR		; if not, just write out the default data

*				ldx		#0					; still zero from WriteTemplate above
				ldy 	#1					; point to next tag byte
				jsr		WriteGCRFmtData		; write out the remaining gap, and sector data
				bra		@sectorDataDone		; get back in sync with default format code

@FmtDefaultGCR	ldx 	#GCRGapSyncSize-1+\	; remainder of gap sync before the sector data
						GCRDataMarkSize 	; the beginning of the sector data
				jsr 	WriteTemplate		; write the end of the header, gap, begining of data

				lda 	<SectHdrInfo\
						+GCRSector			; get the encoded sector number

@ByteCount		set 	(TagSize\
						+BlockSize+3)		; tag, data, and 3 byte checksum
@ByteCount		set 	(@ByteCount*8+5)/6	; number of bytes after encoding (rounded up)

				ldx 	#<@ByteCount		; low byte of byte count
				ldy 	#>@ByteCount		; high byte of byte count
				jsr		WriteDataBuffer		; write out the data buffer
				bne		@toFmtGCRTrack		; if error due to DMA timeout, try re-formatting the track

				ldy 	#GCRDataSlipMark\
						-SectorTemplates	; offset of addr slip mark template
				ldx 	#GCRDataSlipMarkSize; the end of the sector data
				jsr 	WriteTemplate		; write the end of the sector data

@sectorDataDone	inc 	<PhysSector 		; update the sector index
				lda 	<PhysSector 		; get the updated index
				cpa 	<PhysSectsPerHead	; see if entire track formatted
				blt 	@NextSector 		; if not, go onto next sector (x=0)

				lda 	GCRGapValue 		; get the gap value
				jsr 	WriteDataExit		; flush the fifo and shift register
				bne 	@ErrExit			; exit if error detected

				jsr 	ReadSectorHdr		; try to find sector 1
				bcc		@readOK				; if no error, go check the sector number
				tay 						; save error code
				bne 	@DecSync			; if any error, try again with less sync
@readOK			ldy 	#fmt1Err			; assume "can't find sector 0 after format"
				lda 	<SectHdrSector		; see if sector zero found
				bne 	@DecSync			; if not sect zero, try again with less sync
				ldy 	#fmt2Err			; assume "can't get enough sync"

				lda 	<AdrSearchCountL	; get low byte of count
				clc 						; setup for addition
				adc 	#(GCRWriteToRead*5)\
						/(16*6)				; account for dead time
				sta 	<AdrSearchCountL	; update low byte of count
				lda 	<AdrSearchCountH	; get high byte of count
				adc 	#0					; add in carry out
				ldx 	#8-1				; divide loop counter
				asl 	<AdrSearchCountL	; setup for divide
@DivideLoop 	rol 	a					; shift high byte of numerator
				cpa 	#5					; compare to denominator
				bcc 	@DivideShift		; if N < D, don't subtract, just shift
				sbc 	#5					; if N >= D, subtract denominator
@DivideShift	rol 	<AdrSearchCountL	; shift in quotient bit
				dex 						; update loop counter
				bpl 	@DivideLoop 		; loop through all quotient bits
				lda 	<AdrSearchCountL	; get final quotient

				sec 						; setup for subtraction
				sbc 	<SectorGapSize		; subtract num of syncs we were using
				cpa 	#-1 				; see if gap was too short
				bmi 	@DecSync			; if too short, try again with less sync
				ldy 	#noErr				; enough sync, format will succeed
				lsr 	a					; divide excess bytes by 2
				cpa 	<PhysSectsPerHead	; see if SectorGapSize cound inc by 2
				blt 	@Exit				; exit if gap sync still reasonable
				inc 	<SectorGapSize		; use more sync next time
@Exit			tya 						; return the error code
@ErrExit		jmp 	FmtTrackDone		; all done

@DecSync		dec 	<SectorGapSize		; use less sync next time
				lda 	<SectorGapSize		; get sync size
				cpa 	#GCRMinSyncCount	; see if still reasonable
				blt 	@Exit				; if too small, return with error
@toFmtGCRTrack	jmp 	FmtGCRTrack 		; otherwise, try again with less sync
				title	'IOP SWIM Driver - Format Track - MFM'

FmtMFMTrack 	lda 	<FmtByteBlkSize		; get format byte
				and		#$1F				; get just the block size
				sta 	<SectHdrInfo\
						+MFMBlkSize			; setup block size
				lda 	MFMAddrMark+\
						MFMAddrMarkSize-1	; get final byte of address mark field
				sta 	<SectHdrInfo\
						+MFMLastAddrMark 	; setup final byte of address mark field
				lda 	<PhysCylinder		; get the current cylinder
				sta 	<SectHdrInfo\
						+MFMCylinder		; setup cylinder number
				lda 	<PhysHead			; get the head number
				sta 	<SectHdrInfo\
						+MFMHead 			; setup head number
				bne 	@StartFormat		; if not side zero, start immediatly

;		on side zero, sleep while the disk is rotating.  We get here after the seek completes
;		which is about 3 sector times, and we want to start writing about 2 sectors before
;		index goes high, so we want to delay PhysSectsPerHead-5 sector times.

				lda 	<PhysSectsPerHead	; total number of sectors
				sec 						; prepare carry for subtract
				sbc 	#3+2				; subtract off seek and pre-index gap times
				tay 						; setup multiply loop counter
				ldx 	#0					; initialize high byte of sleep time
				lda 	#SectorDelay		; delay time for 1 sector
@RotateMultCLC	clc 						; initialize carry
@RotateMult 	dey 						; decrement loop counter
				beq 	@RotateWait 		; loop for all remaining sectors
				adc 	#SectorDelay		; add delay for next sector
				bcc 	@RotateMult 		; loop through remaining sectors
				inx 						; propagate carry into upper byte
				bra 	@RotateMultCLC		; clear carry, loop through remaining sectors
@RotateWait 	jsr 	SleepLong			; wait for the disk to rotate

@StartFormat	sec							; indicate MFM format
				jsr 	FmtStartWrite		; select the head and start writing
				ldx 	<PhysHead			; see which side we are on
				bne 	@FindIndex			; if side zero, write 768 bytes before checking index

				lda		#>MFMGapBuffer		; point to gap data buffer
				sta		<DataBufPtr+1		; initialize high byte of buffer pointer
				ldx 	#$80				; low byte of byte count
				ldy 	#$01				; high byte of byte count (1.5 pages, 384 bytes)
				lda 	MFMGapValue 		; get the gap byte value
				jsr		WriteDataBuffer		; write out the data buffer
				bne		@StartFormat		; if error due to DMA timeout, try re-formatting the track

				lda		#>MFMGapBuffer		; point to gap data buffer
				sta		<DataBufPtr+1		; initialize high byte of buffer pointer
				ldx 	#$80				; low byte of byte count
				ldy 	#$01				; high byte of byte count (1.5 pages, 384 bytes)
				lda 	MFMGapValue 		; get the gap byte value
				jsr		WriteDataBuffer		; write out the data buffer
				bne		@StartFormat		; if error due to DMA timeout, try re-formatting the track

@FindIndex		stz 	<LoopCountH 		; setup index timeout high byte, low byte in x
				ldy 	MFMGapValue 		; get the gap byte value
				lda 	#DriveRdData		; setup to test index pulse
				sta 	<PhysSector 		; indicate that low pulse not found yet

@FindIndexLoop	inx 						; update low byte of timeout
				bne 	@FindIndexWait		; if no overflow, wait for a byte
				inc 	<LoopCountH 		; update index timeout high byte
				bne 	@FindIndexWait		; if no overflow, wait for a byte

@IndexTimeout	lda 	#noIndexErr 		; index pulse not found during MFM format
				ldy 	#StartAction\
						+WriteMode			; prepare to clear action and write mode
				sty 	wZeroes 			; clear action and write mode (write done)
				jmp 	FmtTrackDone		; return with the error

@FindIndexWait	bit 	rHandshake			; see if there is room
				assert	Dat1Byte=$80
				bpl 	@FindIndexWait		; wait for room
				sty 	wData				; write out a gap byte
				bne 	@IndexHigh			; index is high, check for low to high transition
				stz 	<PhysSector 		; indicate that index low has been seen
				bra 	@FindIndexLoop		; go on to next byte
@IndexHigh		bit 	<PhysSector 		; see if index low has been seen
				bne 	@FindIndexLoop		; if not, go on to next byte

;  De-select RdData0/1 to de-select index pulse during format to prevent noise/crosstalk
;  on the RdData line from interfering with WrData and causing a bad write.  This is a
;  workaround for a noisy external drive cable.  Instead we select somthing that will be
;  a constant high.

				assert	(rMFMDriveAdr+HeadSelect)=rMFMModeOnAdr
				lda 	#rMFMDriveAdr\
						++ph0123en			; select rMFMDriveAdr/rMFMModeOnAdr
				sta 	wPhase				; change the phase lines, but not HeadSelect.

				ldx 	#MFMInitialGapSize	; size of gap after physical index
				lda 	MFMGapValue 		; get the gap byte value
				jsr 	WriteRepeated		; write out the gap field

				ldx 	#MFMSyncSize		; number of sync bytes to write
				lda 	MFMSyncValue		; get the sync value
				jsr 	WriteRepeated		; write out the sync field bytes

				ldx 	#MFMIndexMarkSize-1 ; number of MARK bytes to write
				ldy 	#MFMIndexMark-SectorTemplates
				assert	(wData+1)=wMark
				inc 	<SwimWriteReg		; point to wMark
				jsr 	WriteTemplate		; write the index mark mark bytes

				assert	(wMark-1)=wData
				dec 	<SwimWriteReg		; point back to wData
				lda 	SectorTemplates,y	; get final byte
				jsr 	WriteByte			; write it out

				ldx 	#MFMIndexMarkGapSize; size of gap after index mark field
@NextSector 	lda 	MFMGapValue 		; get the gap byte value
				jsr 	WriteRepeated		; write out the gap field

				ldx 	#MFMSyncSize		; number of sync bytes to write
				lda 	MFMSyncValue		; get the sync value
				jsr 	WriteRepeated		; write out the sync field bytes

				ldx 	#MFMAddrMarkSize-1	; number of MARK bytes to write
				ldy 	#MFMAddrMark-SectorTemplates
				assert	(wData+1)=wMark
				inc 	<SwimWriteReg		; point to wMark
				jsr 	WriteTemplate		; write the addr mark mark bytes
				assert	(wMark-1)=wData
				dec 	<SwimWriteReg		; point back to wData

				ldx 	<PhysSector 		; get sector index
				lda 	<SectorMap,x		; get the zero based sector number
				sta		<SectHdrSector		; save the sector number for buffer indexing
				ina 						; MFM sector numbers are one based
				sta 	<SectHdrInfo\
						+MFMSector			; save header sector number
				jsr 	WriteHdrInfo		; write out the sector header info
				jsr 	WriteMFMCRC 		; write out the sector header CRC

				ldx 	#MFMAddrMarkGapSize ; number of gap bytes to write
				lda 	MFMGapValue 		; get the gap value to write
				jsr 	WriteRepeated		; write out the gap field


;		write out the sector data

				ldx 	#MFMSyncSize		; number of sync bytes to write
				lda 	MFMSyncValue		; get the sync value
				jsr 	WriteRepeated		; write out the sync field bytes

				ldx 	#MFMDataMarkSize-1	; number of MARK bytes to write
				ldy 	#MFMDataMark-SectorTemplates
				assert	(wData+1)=wMark
				inc 	<SwimWriteReg		; point to wMark
				jsr 	WriteTemplate		; write the data mark mark bytes

				assert	(wMark-1)=wData
				dec 	<SwimWriteReg		; point back to wData
				lda 	SectorTemplates,y	; get final byte

*				ldx 	#<BlockSize			; low byte of byte count
				ldy 	#>BlockSize			; high byte of byte count
				jsr		WriteDataBuffer		; write out the data buffer
				bne		@ToStartFormat		; if DMA timeout, re-write the track with ints disabled
				jsr 	WriteMFMCRC 		; write the 2 CRC bytes

				ldx 	<SectorGapSize		; number of gap bytes to write
				inc 	<PhysSector 		; update the sector index
				lda 	<PhysSector 		; get the updated index
				cpa 	<PhysSectsPerHead	; see if entire track formatted
				blt 	@NextSector 		; if not, go onto next sector

				lda 	MFMGapValue 		; get the gap value to write
				ldy 	<PhysHead			; see if this is side zero
				beq 	@TrackDone			; don't write gap after last sector on side zero

				ldx 	#4					; write 4 bytes to flush the FIFO and shift reg
				jsr 	WriteRepeated		; write out the gap field

				assert	(rRdData0Adr+HeadSelect)=rRdData1Adr
				ldx 	#rRdData0Adr\
						++ph0123en			; select rRdData0Adr/rRdData1Adr
				stx 	wPhase				; change the phase lines, but not HeadSelect.

				ldx 	#>(-MFMFinalGapSize); get high byte of negated timeout
				sta 	<LoopCountH 		; initialize timeout high byte
				ldx 	#<(-MFMFinalGapSize); get low byte of negated timeout
				tay 						; setup gap value
				lda 	#DriveRdData		; setup to test index pulse
@PadTrack		bit 	rHandshake			; see if we have room in the FIFO
				assert	Dat1Byte=$80
				bpl 	@PadTrack			; wait until there is room for one byte
				bne 	@PadTrackDone		; if index found, stop padding
				sty 	wData				; write the byte
				inx 						; update timeout low byte
				bne 	@PadTrack			; loop until index or timeout
				inc 	<LoopCountH 		; update timeout high byte
				bne 	@PadTrack			; loop until index or timeout
				jmp 	@IndexTimeout		; return timeout error, if index not found

@PadTrackDone	tya 						; setup gap value

@TrackDone		jsr 	WriteDataExit		; flush the fifo and shift register
				bne 	@Exit				; if error, exit now
				
*				lda 	#0					; initialize loop counter (already zero)
@HeadSwitchLoop dea 						; allow a short delay before head switch
				bne 	@HeadSwitchLoop 	; loop 256 times for 1280 clocks, ≈650µsec
*				lda 	#noErr				; indicate format succeeded (already zero)

@Exit			jmp 	FmtTrackDone		; all done
@ToStartFormat	jmp		@StartFormat
				endwith
				endwith
				endwith
				endproc

				seg 	'WordConsts'
FmtTrackDecode	proc
				dc.w	FmtUnknownTrack 	;  0 - uncheckedFormat
				dc.w	FmtUnknownTrack 	;  1 - unknownFormat
				dc.w	FmtUnknownTrack 	;  2 - HD20Format
				dc.w	FmtGCRTrack 		;  3 - GCR400Kformat
				dc.w	FmtGCRTrack 		;  4 - GCR800Kformat
				dc.w	FmtMFMTrack 		;  5 - MFM720Kformat
				dc.w	FmtMFMTrack 		;  6 - MFM1440Kformat
				dc.w	FmtUnknownTrack 	;  7 - GCRonHDformat
				dc.w	FmtUnknownTrack 	;  8 - reserved for future use
				dc.w	FmtUnknownTrack 	;  9 - reserved for future use
				dc.w	FmtUnknownTrack 	; 10 - reserved for future use
				endproc
				title	'IOP SWIM Driver - Initialize SWIM chip'

;_______________________________________________________________________
;
;  Routine: 	jsr 	InitSWIMChip
;  Inputs:		none
;  Outputs: 	A - error status code
;				n,z - based on value returned in A
;  Destroys:	A, X, Y, n, z, c
;  Calls:		LoadSWIMparams
;  Called by:	SWIMInitialize, ReCalibrate
;
;  Function:	Initializes the SWIM chip, and switches it into ISM mode.
;				Returns an error indication if initialization failed.
;
;_______________________________________________________________________

				seg 	'code'
InitSWIMChip	proc

; Initialize the SWIM chip, and force ISM mode

				ldy 	#3					; retry count
initSWIMloop	dey 						; decrement retry count
				beq 	initSWIMerr 		; if retry count exceeded
**??**			lda 	mtrOff				; Disable the interface in case we're in IWM
				lda 	#startAction\
						+WriteMode			; and turn off ACTION/WRITE in case we're not
				sta 	wZeroes
				lda 	#ph0123en+ph0+ph2
				sta 	wPhase
				cpa 	rPhase				; see if we can write/read phase lines
				bne 	inIWMmode			; couldn't, must be IWM mode
				lda 	#ph0123en+ph1+ph2
				sta 	wPhase
				cpa 	rPhase				; see if we can write/read phase lines
				bne 	inIWMmode			; couldn't, must be IWM mode
				lda 	#ph0123en+ph0+ph1+ph2
				sta 	wPhase
				cpa 	rPhase				; see if we can write/read phase lines
				bne 	inIWMmode			; couldn't, must be IWM mode

inISMmode		lda 	#IWMtimerOverride\
						+IWMtimer2X			; set timer kill and 16mhz timer
				sta 	wIWMConfig			; for IWM timer
				lda 	#Select35			; clear all bits except 3.5 select (ADB out)
				and 	rSetup				; read and clear the setup bits
**??**			ora 	#IBMDrive			; write using pulses, not transitions
				sta 	wSetup				; init the setup register
				eor 	rSetup				; make sure it was written OK
				beq 	LoadSWIMparams		; load some parameters, if compared OK
initSWIMerr 	lda 	#initIWMErr 		; return error code
				rts 						; return

inIWMmode		lda 	q6H 				; set IWM to sense mode
				lda 	#$1F				; mask for mode bits
				and 	q7L 				; sense the mode register
				bit 	q6L 				; and back to read mode
				cpa 	#IWMmode			; see if correct mode is set
				beq 	IWMmodeSet			; if correct
				ldx 	#0					; loop retry count
initIWMloop 	dex 						; decrement count
				beq 	initSWIMerr 		; if timed out
				lda 	mtrOff				; change the IWM mode now
				lda 	q6H 				; set IWM to sense state
				lda 	#$3f				; mask for sense, mode bits
				and 	q7L 				; sense the mode register
				bit 	#$20				; test enables
				bne 	initIWMloop 		; if interface not disabled yet
				cpa 	#IWMmode			; see if low bits match
				beq 	IWMmodeSet			; if correct
				lda 	#IWMmode			; try setting it again
				sta 	q7H 				; set to IWM mode
				lda 	q7L 				; set IWM back to sense state
				bra 	initIWMloop 		; try again

IWMmodeSet		lda 	q6L 				; change back to read mode
				lda 	q7L 				; select the mode register
				lda 	mtrOff
				lda 	q6H
				lda 	#$40+IWMmode		; "magic sequence" bit 6 = 1, 0, 1, 1
				sta 	q7H 				; put the chip in ISM mode bit 6 = 1
				eor 	#$40				; clear bit 6
				sta 	q7H 				; put the chip in ISM mode bit 6 = 0
				eor 	#$40				; set bit 6
				sta 	q7H 				; put the chip in ISM mode bit 6 = 1
				sta 	q7H 				; put the chip in ISM mode bit 6 = 1
				jmp 	initSWIMloop		; verify that it is now an ISM
				endproc
				title	'IOP SWIM Driver - Load SWIM parameters'

;_______________________________________________________________________
;
;  Routine: 	jsr 	LoadSWIMparams
;  Inputs:		CurrentDrive - active drive number
;  Outputs: 	A - error status code
;				n,z - based on value returned in A
;  Destroys:	A, X, Y, n, z, c
;  Cycles:		348 - if same param set as last call, first cylinder zone
;				613 - if new param set, first cylinder zone
;				+15 cycles per additional cylinder zone searched
;  Calls:		none
;  Called by:	SetUpDrive, Seek, InitSWIMChip
;
;  Function:	Loads the SWIM parameter RAM with the correct parameters
;				for the current disk format, checks to see if they were
;				loaded correctly, and reports any errors
;
;_______________________________________________________________________

				seg 	'code'
LoadSWIMparams	proc
				entry	SWIMparamIndex
				entry	SWIMcylTable
				entry	SWIMparams
				with	SWIMVarsZ
				with	SWIMVars

				lda 	#5					; retry count
				pha 						; save retry count

				ldx 	<CurrentDrive		; get the drive number
				ldy 	DiskFormat,x		; get the disk format
				lda 	SWIMparamIndex,y	; get the parameter table index
				tay 						; setup the table index
CylinderLoop	lda 	SWIMcylTable,y		; get upper bound of this cylinder range
				iny 						; point to parameter index
				iny 						; point to next cylinder range
				cpa 	CurrentCylinder,x	; compare to current cylinder
				blt 	CylinderLoop		; loop until proper range found
				ldx 	SWIMcylTable-1,y	; get parameter table index
				cpx 	LastParamSet		; see if same as last time
				stx 	LastParamSet		; mark current index as most recent
				beq 	checkParams 		; if a match, just compare first

ReLoadParams	plx 						; get retry count
				dex 						; count the retry
				bmi 	LoadParamErr		; retries exhausted, return error
				phx 						; save the retry count
				ldx 	LastParamSet		; get current index
				lda 	#startAction\
						+WriteMode			; need to write to wZeroes
				sta 	wZeroes 			; to reset parameter ram ptr
				ldy 	#16 				; loop counter
loadParamLoop	lda 	SWIMparams,x		; get the parameter value
				sta 	wParams 			; store it in the param ram
				dex 						; point to next param
				dey 						; decrement the loop count
				bne 	loadParamLoop		; process next parameter

				ldx 	LastParamSet		; get current index
checkParams 	lda 	#startAction\
						+WriteMode			; need to write to wZeroes
				sta 	wZeroes 			; to reset parameter ram ptr
				ldy 	#16 				; loop counter
checkParamLoop	lda 	SWIMparams,x		; get the parameter value
				eor 	rParams 			; compare it
				bne 	ReLoadParams		; if it didn't compare
				dex 						; point to next param
				dey 						; decrement the loop count
				bne 	checkParamLoop		; process next parameter

				lda 	#$FF-(TransSpaceDis*1+IBMDrive*1+EnableECM*1+GCRMode*1)
				and 	rSetup				; get bits that don't change
				ora 	SWIMparams,x		; insert bits that do change
				sta 	wSetup				; setup the SWIM chip modes
				eor 	rSetup				; compare it
				bne 	ReLoadParams		; if it didn't compare

				plx 						; pop retry count
				tax 						; return noErr, and update flags
				rts 						; all done

LoadParamErr	lda 	#initIWMErr 		; return error code
				rts 						; return
				endwith
				endwith
				endproc
				title	'IOP SWIM Driver - SWIM parameters'

				seg 	'constants'
SWIMparamConsts proc					; SWIM chip parameter constants

				entry	SWIMcylTable
SWIMcylTable
GCRtable		dc.b	$ff,GCRparams1-SWIMparams+16

MFMtable		dc.b	$ff,MFMparams1-SWIMparams+16
				dc.b	$ff,MFMparams2-SWIMparams+16	; unused, but just in case we need it
				dc.b	$ff,MFMparams1-SWIMparams+16	; unused, but just in case we need it

SPAREtable		dc.b	$ff,SPAREparams-SWIMparams+16	; unused, but just in case we need it

; table indexed by disk format kind

				entry	SWIMparamIndex
SWIMparamIndex	dc.b	GCRtable-SWIMcylTable	;  0 - uncheckedFormat (use GCR)
				dc.b	GCRtable-SWIMcylTable	;  1 - unknownFormat (use GCR)
				dc.b	GCRtable-SWIMcylTable	;  2 - HD20Format (use GCR)
				dc.b	GCRtable-SWIMcylTable	;  3 - GCR400Kformat
				dc.b	GCRtable-SWIMcylTable	;  4 - GCR800Kformat
				dc.b	MFMtable-SWIMcylTable	;  5 - MFM720Kformat
				dc.b	MFMtable-SWIMcylTable	;  6 - MFM1440Kformat
				dc.b	GCRtable-SWIMcylTable	;  7 - GCRonHDformat
				dc.b	SPAREtable-SWIMcylTable ;  8 - reserved for future use
				dc.b	SPAREtable-SWIMcylTable ;  9 - reserved for future use
				dc.b	SPAREtable-SWIMcylTable ; 10 - reserved for future use


				entry	SWIMparams
SWIMparams

;  Parameter register values.
;
;  The timing values Time1, Time0, xSx, xLx and Min have a resolution to
;  the nearest half clock, so their actual register values are doubled so
;  that bits 1-7 become the integer part and bit 0 becomes the ".5" part.
;  xSx and xLx are reduced by 2 clocks, and Min is reduced by 3 clocks to
;  take into account gate delays in the SWIM.

;  For a 15.6672MHz input clock
;		with 16.33usec byte times (400K, 800K GCR):
;  Cell times 2.0424836, 4.0849672, 6.1274508 microseconds
;		GCR params1 (more accuratly reflect IWM operation)
;		peak shift = 0, pre-comp = 0.
GCRparams1		dc.b	(TransSpaceDis*1+IBMDrive*0+EnableECM*0+GCRMode*1)		; Setup Reg
				dc.b	(32-2)*2+0			; [F] Time1 = 32.0	(2.0424836 uSec)
				dc.b	(8*16)+8			; [E] Early/Norm = 8 / 8
				dc.b	(32-2)*2+0			; [D] Time0 = 32.0	(2.0424836 uSec)
				dc.b	(8*16)+8			; [C] Late/Norm = 8 / 8
				dc.b	(32-2)*2+0			; [B] LLS = 32.0
				dc.b	(32-2)*2+0			; [A] LLL = 32.0
				dc.b	(32-2)*2+0			; [9] LSS = 32.0
				dc.b	(32-2)*2+0			; [8] LSL = 32.0
				dc.b	(32-2)*2+0			; [7] CSLS = 32.0
				dc.b	(32-2)*2+0			; [6] RPT = 32.0
				dc.b	(32-2)*2+0			; [5] SLS = 32.0
				dc.b	(32-2)*2+0			; [4] SLL = 32.0
				dc.b	(32-2)*2+0			; [3] SSS = 32.0
				dc.b	(32-2)*2+0			; [2] SSL = 32.0
				dc.b	64					; [1] Mult = 64
				dc.b	(16-3)*2+0			; [0] Min = 16.0

;  For a 15.6672MHz input clock
;		with 16usec byte times (720K, 1440K MFM):
;  Cell times 2.0105698, 2.9998978, 3.9892258 microseconds
;		MFM params1
MFMparams1		dc.b	(TransSpaceDis*0+IBMDrive*1+EnableECM*0+GCRMode*0)		; Setup Reg
				dc.b	(31-2)*2+1			; [F] Time1 = 31.5	(2.0105698 uSec)
				dc.b	$57 				; [E] Early/Norm = $57
				dc.b	(15-2)*2+1			; [D] Time0 = 15.5	(0.9893280 uSec)
				dc.b	$97 				; [C] Late/Norm = $97
				dc.b	(14-2)*2+1			; [B] LLS = 14.5
				dc.b	(14-2)*2+1			; [A] LLL = 14.5
				dc.b	(25-2)*2+1			; [9] LSS = 25.5
				dc.b	(25-2)*2+1			; [8] LSL = 25.5
				dc.b	(15-2)*2+1			; [7] CSLS = 15.5
				dc.b	(15-2)*2+1			; [6] RPT = 15.5
				dc.b	(14-2)*2+0			; [5] SLS = 14.0
				dc.b	(14-2)*2+0			; [4] SLL = 14.0
				dc.b	(25-2)*2+0			; [3] SSS = 25.0
				dc.b	(25-2)*2+0			; [2] SSL = 25.0
				dc.b	65					; [1] Mult = 65
				dc.b	(15-3)*2+0			; [0] Min = 15.0

;  For a 15.6672MHz input clock
;		with 16usec byte times (720K, 1440K MFM):
;  Cell times 2.0105698, 2.9998978, 3.9892258 microseconds
;		MFM params2
MFMparams2		dc.b	(TransSpaceDis*0+IBMDrive*1+EnableECM*0+GCRMode*0)		; Setup Reg
				dc.b	(31-2)*2+1			; [F] Time1 = 31.5	(2.0105698 uSec)
				dc.b	$57 				; [E] Early/Norm = $57
				dc.b	(15-2)*2+1			; [D] Time0 = 15.5	(0.9893280 uSec)
				dc.b	$97 				; [C] Late/Norm = $97
				dc.b	(14-2)*2+1			; [B] LLS = 14.5
				dc.b	(14-2)*2+1			; [A] LLL = 14.5
				dc.b	(25-2)*2+1			; [9] LSS = 25.5
				dc.b	(25-2)*2+1			; [8] LSL = 25.5
				dc.b	(15-2)*2+1			; [7] CSLS = 15.5
				dc.b	(15-2)*2+1			; [6] RPT = 15.5
				dc.b	(14-2)*2+0			; [5] SLS = 14.0
				dc.b	(14-2)*2+0			; [4] SLL = 14.0
				dc.b	(25-2)*2+0			; [3] SSS = 25.0
				dc.b	(25-2)*2+0			; [2] SSL = 25.0
				dc.b	65					; [1] Mult = 65
				dc.b	(15-3)*2+0			; [0] Min = 15.0

; spare table entry, for possible future use
;		SPARE params
SPAREparams 	dc.b	(TransSpaceDis*1+IBMDrive*0+EnableECM*0+GCRMode*1)		; Setup Reg
				dc.b	(00-2)*2+0			; [F] Time1 = 00.0	(0.0000000 uSec)
				dc.b	(0*16)+0			; [E] Early/Norm = 0 / 0
				dc.b	(00-2)*2+0			; [D] Time0 = 00.0	(0.0000000 uSec)
				dc.b	(0*16)+0			; [C] Late/Norm = 0 / 0
				dc.b	(00-2)*2+0			; [B] LLS = 00.0
				dc.b	(00-2)*2+0			; [A] LLL = 00.0
				dc.b	(00-2)*2+0			; [9] LSS = 00.0
				dc.b	(00-2)*2+0			; [8] LSL = 00.0
				dc.b	(00-2)*2+0			; [7] CSLS = 00.0
				dc.b	(00-2)*2+0			; [6] RPT = 00.0
				dc.b	(00-2)*2+0			; [5] SLS = 00.0
				dc.b	(00-2)*2+0			; [4] SLL = 00.0
				dc.b	(00-2)*2+0			; [3] SSS = 00.0
				dc.b	(00-2)*2+0			; [2] SSL = 00.0
				dc.b	00					; [1] Mult = 00
				dc.b	(00-3)*2+0			; [0] Min = 00.0

;  For a 15.6672MHz input clock
;		with 16.33usec byte times (400K, 800K GCR):
;  Cell times 2.0424836, 4.0849672, 6.1274508 microseconds
;		GCR params2 (computed using formula, with 100ns Peak Shift, 125ns PreComp)
GCRparams2		dc.b	(TransSpaceDis*1+IBMDrive*0+EnableECM*0+GCRMode*1)		; Setup Reg
				dc.b	(32-2)*2+0			; [F] Time1 = 32.0	(2.0424836 uSec)
				dc.b	(6*16)+8			; [E] Early/Norm = 6 / 8
				dc.b	(32-2)*2+0			; [D] Time0 = 32.0	(2.0424836 uSec)
				dc.b	(10*16)+8			; [C] Late/Norm = 10 / 8
				dc.b	(30-2)*2+1			; [B] LLS = 30.5
				dc.b	(31-2)*2+0			; [A] LLL = 31.0
				dc.b	(32-2)*2+0			; [9] LSS = 32.0
				dc.b	(33-2)*2+1			; [8] LSL = 33.5
				dc.b	(30-2)*2+0			; [7] CSLS = 30.0
				dc.b	(24-2)*2+0			; [6] RPT = 24.0
				dc.b	(31-2)*2+0			; [5] SLS = 31.0
				dc.b	(30-2)*2+1			; [4] SLL = 30.5
				dc.b	(30-2)*2+1			; [3] SSS = 30.5
				dc.b	(32-2)*2+0			; [2] SSL = 32.0
				dc.b	64					; [1] Mult = 64
				dc.b	(16-3)*2+0			; [0] Min = 16.0
				endproc
				title	'IOP SWIM Driver - Address and Sense / Sense'

;_______________________________________________________________________
;
;  Routine: 	jsr 	AdrAndSense
;  Inputs:		A - drive address [0,0, HeadSelect, 0,0, ca2,ca1,ca0]
;  Outputs: 	z - 1 (BEQ) if DriveRdData line was zero
;				z - 0 (BNE) if DriveRdData line was one
;  Destroys:	A, Y, n, v
;  Cycles:		35 / 37
;  Calls:		none
;  Called by:	PollingTask, SWIMEject, SWIMGetRawData, CheckStaticAttr,
;				CheckDriveMode, TurnOnDrive, WaitDriveReady, ReCalibrate, Seek
;
;  Function:	Sets up the disk register address for the currently enabled
;				disk and reads the DriveRdData line for that register.
;
;_______________________________________________________________________

;_______________________________________________________________________
;
;  Routine: 	jsr 	Sense
;  Inputs:		none
;  Outputs: 	z - 1 (BEQ) if DriveRdData line was zero
;				z - 0 (BNE) if DriveRdData line was one
;  Destroys:	A, n, v
;  Cycles:		12
;  Calls:		none
;  Called by:	SWIMGetRawData, Seek
;
;  Function:	Reads the DriveRdData line for the last addressed register.
;
;_______________________________________________________________________

				seg 	'code'
AdrAndSense 	proc
				ldy 	#ph0123en+ca1+ca0	; set ca1 and ca0 high
				sty 	wPhase				; before changing HeadSelect
				ldy 	#HeadSelect 		; prepare to change HeadSelect
				bit 	#HeadSelect 		; test new head select value
				beq 	ResetHeadSelect 	; if new value is zero
				sty 	wOnes				; otherwise set head select
				bra 	HeadSelectDone		; join common code
ResetHeadSelect sty 	wZeroes 			; reset head select
HeadSelectDone	ora 	#ph0123en			; set the phase line enables
				sta 	wPhase				; write the phase lines
				entry	Sense
Sense			lda 	#DriveRdData		; prepare to test bit
				and 	rHandshake			; test the DriveRdData bit
				rts 						; all done
				endproc
				title	'IOP SWIM Driver - Address and Strobe / Pulse'

;_______________________________________________________________________
;
;  Routine: 	jsr 	AdrAndStrb
;  Inputs:		A - drive address [0,0, HeadSelect, 0,0, ca2,ca1,ca0]
;  Outputs: 	none
;  Destroys:	A, Y, n, z
;  Cycles:		41 / 43
;  Calls:		none
;  Called by:	PollingTask, SWIMEject, TurnOnDrive, TurnOffDrive,
;				ReCalibrate, Seek
;
;  Function:	Sets up the disk register address for the currently enabled
;				disk and pulses the ph3 line, causing the data from ca2 to
;				be written to the addressed register.
;
;_______________________________________________________________________

;_______________________________________________________________________
;
;  Routine: 	jsr 	Pulse
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, n, z
;  Cycles:		25
;  Calls:		none
;  Called by:	Seek
;
;  Function:	pulses the ph3 line, causing the data from ca2 to
;				be written to the last addressed register.
;
;_______________________________________________________________________

				seg 	'code'
AdrAndStrb		proc
				ldy 	#ph0123en+ca1+ca0	; set ca1 and ca0 high
				sty 	wPhase				; before changing HeadSelect
				ldy 	#HeadSelect 		; prepare to change HeadSelect
				bit 	#HeadSelect 		; test new head select value
				beq 	ResetHeadSelect 	; if new value is zero
				sty 	wOnes				; otherwise set head select
				bra 	HeadSelectDone		; join common code
ResetHeadSelect sty 	wZeroes 			; reset head select
HeadSelectDone	ora 	#ph0123en			; set the phase line enables
				sta 	wPhase				; write the phase lines
doPulse 		ora 	#ph0123en+ph3		; prepare to set ph3 bit
				sta 	wPhase				; turn ph3 on
				eor 	#ph3				; prepare to reset ph3 bit
				sta 	wPhase				; turn ph3 off
				rts 						; all done

				entry	Pulse
Pulse			lda 	rPhase				; get the old phase lines
				bra 	doPulse 			; finish the pulse
				endproc
				title	'IOP SWIM Driver - Disk Select'

;_______________________________________________________________________
;
;  Routine: 	jsr 	DiskSelect
;  Inputs:		X - Drive Number
;  Outputs: 	z - 1 (BEQ) if illegal drive number
;				z - 0 (BNE) if legal drive number
;  Destroys:	A, Y, n, v, c
;  Cycles:		22 clocks for internal drive
;				36 or 71 clocks for unknown drive
;				45 clocks if PassThru doesn't work
;				54 clocks + 37 clocks per HD20 for external SONY drive
;				76 clocks + 37 clocks per prior HD20 for HD20 drives
;  Calls:		none
;  Called by:	SWIMInitialize, PollingTask, SWIMEject, TurnOnDrive
;
;  Function:	Asserts the ENABLE line for the specified drive.
;
;_______________________________________________________________________

				seg 	'code'
DiskSelect		proc
				with	SWIMVars
				cpx 	#intDriveNumber		; check for internal drive
				bne 	chkExternal 		; if not internal drive
				lda 	#Drive2Enabled		; prepare to disable the ext drive
				sta 	wZeroes 			; disable external drive
				lda 	#Drive1Enabled\
						+MotorOn			; prepare to enable int drive
				sta 	wOnes				; enable the internal drive
				rts 						; all done

chkExternal 	lda 	#ph0123en\
						++rNoDriveAdr		; address the drive exists reg
				sta 	wPhase				; write the phase lines
				lda 	#HeadSelect 		; prepare to reset head select
				sta 	wZeroes 			; HeadSelect is 0 for rNoDriveAdr
				lda 	PassThruBroken		; see if pass-thru connector works
				beq 	usePassThru 		; if you've got it, use it

; if PassThru is not working, then external drive port can only have a single HD20
; connected to it.
				cpx 	#FirstHD20DriveNumber	; check for 1st HD20
				bne 	unknownDrive		; if not 1st HD20, give up
				lda 	#Drive1Enabled		; prepare to disable the int drive
				sta 	wZeroes 			; disable internal drive
				lda 	#Drive2Enabled\
						+MotorOn			; prepare to enable ext drive
				sta 	wOnes				; enable the external drive
				rts 						; all done

usePassThru 	lda 	#Drive1Enabled		; prepare to disable the int drive
				sta 	wZeroes 			; disable internal drive
				lda 	#Drive2Enabled\
						+MotorOn			; prepare to enable ext drive
				sta 	wOnes				; enable the external drive
				cpx 	#extDriveNumber		; check for external drive
				bne 	chkHD20 			; if not external, check for HD20s
				ldy 	#maxHD20Cnt 		; Try all HD20s + 1 ext SONY drive
				bra 	searchExtDrive		; start looking for it

; the following sequence will cause the next drive on the chain to be enabled
searchLoop		lda 	#ph0123en++rNoDriveAdr++ph3
				sta 	wPhase				; pulse phase 3 high
				eor 	#ph3				; and then
				sta 	wPhase				; low again
				eor 	#ph1				; reset phase 1
				sta 	wPhase				; pulse phase 1 low
				eor 	#ph1				; and then
				sta 	wPhase				; high again
searchExtDrive	lda 	#DriveRdData		; prepare to test sense line
				bit 	rHandShake			; test the sense line (rNoDrive)
				beq 	loopExit			; exit when SONY drive found
				dey 						; decrement the loop counter
				bpl 	searchLoop			; try next drive on the chain
loopExit		tay 						; return z <- false
				rts 						;  all done

chkHD20 		lda 	#MotorOn			; prepare to pulse the drive enable
				sta 	wZeroes 			; disable the drive
				sta 	wOnes				; enable the drive
				txa 						; get drive number
				sec 						; set carry for subtract
				sbc 	#FirstHD20DriveNumber	; make drive number 0 based
				tay 						; load search counter
				bge 	searchExtDrive		; start searching for it
unknownDrive	lda 	#0					; return z <- true
				rts 						; all done
				endwith
				endproc
				title	'IOP SWIM Driver - Find Drive Kind'

;_______________________________________________________________________
;
;  Routine: 	jsr 	FindDriveKind
;  Inputs:		none
;  Outputs: 	A - Disk Drive Kind
;  Destroys:	X, Y, n, z, c
;  Cycles:		116
;  Calls:		none
;  Called by:	SWIMInitialize
;
;  Function:	Determines what kind of drive is currently selected, by
;				examining 4 of the disk status values returned by the drive.
;
;
; Name			HeadSel ca2 	ca1 	ca0 	400K	800K	HD20	SuperDr NoDrive
;
; Revised		1		1		1		1		0		1		1		x		1
; /DrvIn		0		1		1		1		0		0		1		0		1
; /SingleSide	0		1		1		0		0		1		1		1		1
; SuperDrive	0		1		0		1		0		0		0		1		1
;_______________________________________________________________________

				seg 	'code'
FindDriveKind	proc
				entry	DriveKindXlate
				ldy 	#ph0123en\
						+ca2+ca1+ca0		; prepare to test REVISED status
				sty 	wPhase				; set the phase lines
				ldx 	#HeadSelect 		; prepare to set HeadSelect
				stx 	wOnes				; Set HeadSelect
				lda 	rHandshake			; prepare to test bit
				stx 	wZeroes 			; Clear HeadSelect
				ldx 	#%00010000			; init drive kind index / loop counter
				bra 	start

loop			sty 	wPhase				; set the phase lines
				dey 						; decrement ca2..ca0 phase values
				lda 	rHandshake			; prepare to test bit
start			and 	#DriveRdData		; isolate the DriveRdData bit
				cpa 	#DriveRdData		; Carry := DriveRdData bit
				txa 						; get result
				rol 	a					; shift next bit into result
				tax 						; restore result
				bcc 	loop				; loop until last bit shifted in

				lda 	DriveKindXlate,x 	; translate statuses to drive kind
				rts 						; all done
				endproc

				seg 	'constants'
DriveKindXlate	proc
				dc.b	SSGCRDriveKind		; 0 0 0 0 - 400K GCR drive
				dc.b	unknownDriveKind	; 0 0 0 1 - unknown
				dc.b	unknownDriveKind	; 0 0 1 0 - unknown
				dc.b	DSMFMGCRDriveKind	; x 0 1 1 - SuperDrive
				dc.b	unknownDriveKind	; 0 1 0 0 - unknown
				dc.b	unknownDriveKind	; 0 1 0 1 - unknown
				dc.b	unknownDriveKind	; 0 1 1 0 - unknown
				dc.b	unknownDriveKind	; 0 1 1 1 - unknown
				dc.b	unknownDriveKind	; 1 0 0 0 - unknown
				dc.b	unknownDriveKind	; 1 0 0 1 - unknown
				dc.b	DSGCRDriveKind		; 1 0 1 0 - 800K GCR drive
				dc.b	DSMFMGCRDriveKind	; x 0 1 1 - SuperDrive
				dc.b	unknownDriveKind	; 1 1 0 0 - unknown
				dc.b	unknownDriveKind	; 1 1 0 1 - unknown
				dc.b	HD20DriveKind		; 1 1 1 0 - HD-20 drive
				dc.b	noDriveKind 		; 1 1 1 1 - No drive installed
				endproc
				end

