*****************************************************************************
*
*  PC2Flop - Write floppy with image received through a serial port
*
*	This program is for a SWTPC 6800 with a DC-x floppy controller.
*	It writes a 5.25" floppy with an FDOS disk image received 
*	through an MP-S serial port in any one of the SWTPC 6800 I/O
*	ports. FDOS disks are SSSD, 35 tracks with 256 byte sectors.
*	Sectors are numbered 0-9 with no interleaving.
*
*	The XMODEM protocol is used for the serial transfer. The SWTBUG
*	or MIKBUG PROM must be present in the computer. If using the
*	console port for the XMODEM transfer, error messages and prompts
*	won't be seen once the XMODEM transfer is started. This makes it
*	difficult to notice and determine disk I/O problems. A second
*	serial port for the XMODEM transfer is the ideal configuration,
*	though not required.
*
*	Written by Mike Douglas
*
*	Rev	 Date	    Desc
*	1.0	01/15/22    Original
*
*	1.1	01/26/22    Eliminate unused double-sided logic. Restore
*			    drive to track zero on exit.
*
*	1.2	02/19/24    Fix verify retry bug that was missing the
*			    # in front of LDAA #RDTRIES. Retry write
*			    failures (not just verify of write) due to
*			    issues found with some 3.5" drives.
*
*****************************************************************************

* SWTBUG (MIKBUG) equates

OUTEEE	equ	$E1D1		;SWTBUG display character
INEEE	equ	$E1AC		;SWTBUG input character
PDATA1	equ	$E07E		;SWTBUG display message
dispMsg	equ	PDATA1
STACK	equ	$A07F		;stack in SWTBUG RAM
CONTRL	equ	$E0E3		;re-enter SWTBUG

* Disk equates

NUMTRK	equ	35		;number of tracks on disk
NUMSEC	equ	10		;number of sectors per track
SECLEN	equ	256		;sector length as transferred
MINDRV	equ	0		;first drive number
MAXDRV	equ	3		;max drive number 
TRKLEN	equ	NUMSEC*SECLEN	;length of track in bytes

WRTRIES	equ	4
RDTRIES	equ	5

* XMODEM equates

PKTLEN	equ	128		;128 byte xmodem packet length
SOH	equ	$01
EOT	equ	$04
ACK	equ	$06
NAK	equ	$15
EOF	equ	$1A		;ctrl-z character

* MP-S serial board equates

SIORDRF	equ	$01		;receive data register full
SIOTDRE	equ	$02		;transmit data register empty

* Misc equates

CR	equ	$0D		;ASCII for carriage return
LF	equ	$0A		;ASCII for line feed
DEL	equ	$7F		;ASCII DEL
BS	equ	$08		;ASCII backspace
CTRLC	equ	$03		;ASCII for control-c

* Layout of raw sector in format buffer.

	org	0
	rmb	6		;gap bytes
idMark	rmb	1		;address mark
idTrk	rmb	1		;LSB of track number
	rmb	1
idSec	rmb	1		;sector number
idSize	rmb	1		;sector length code
idCrc	rmb	1		;CRC mark
	rmb	17		;gap bytes
dataMrk	rmb	1		;data mark
	rmb	256		;sector data
dataCrc	rmb	1		;CRC on sector data
	rmb	13		;gap bytes

RSECLEN	equ	*		;raw sector length
FMTLEN	equ	RSECLEN*NUMSEC+$100	;track space for format

	org	$0100		;load address
*-----------------------------------------------------------------------------
*   Initialize for transfer
*-----------------------------------------------------------------------------
start	lds	#STACK		;initialize stack pointer
	ldaa	#$FF		;set invalid drive number
	staa	drvNum

* Display welcome message, then get the drive number

	ldx	#mWelcom	;display welcome message
	jsr	dispMsg

getDrv	ldx	#mDrive		;display drive number prompt
	jsr	dispMsg

	jsr	rcvCon		;get byte from the console
	oraa	#$20		;upper case letters to lower case
	cmpa	#'x'		;exit requested?
	bne	chkDrv		;no, check the drive number entered
	jmp	pgmExit		;else, exit as requested

chkDrv	suba	#MINDRV+'0'	;ASCII 1st drive goes to binary 0
	cmpa	#MAXDRV-MINDRV+1   ;validate <= max drive
	bcc	getDrv		;invalid drive number, prompt again
	
	adda	#MINDRV		;restore offset of 1st drive (if any) 
	staa	drvNum		;save the drive number to use

	clr	trkNum		;seek to track zero to see if
	jsr	dSelect		;   write protected
	jsr	dSeek
	beq	iniVars		;not write protected
	jmp	seekErr		;else, disply error

* Track length and number of tracks are fixed for this 
*   version of FLEX

iniVars	ldx	#TRKLEN		;init track length 
	stx	trkLen
	ldaa	#NUMTRK		;init number of tracks
	staa	numTrk
	jsr	sizeRam		;see how much RAM we can use

* Prompt whether or not to format the disk

	ldx	#mFormat	;display format prompt
	jsr	dispMsg
	
	jsr	rcvCon		;get drive number response
	oraa	#$20		;upper to lower case, nums not affected
	cmpa	#'y'		;format requested?
	bne	getPort		;no
	jsr	format		;else, go format disk
	jsr	dMotOff		;turn off the motor

* Get the serial port to use for the transfer

getPort	ldx	#mPort		;display port number prompt
	jsr	dispMsg

	jsr	rcvCon		;get byte from the console
	oraa	#$20		;upper case letters to lower case
	cmpa	#'x'		;exit requested?
	bne	chkPort		;no, check the port number entered
	jmp	pgmExit		;else, exit as requested

chkPort	suba	#'0'		;subtract ACII offset
	cmpa	#5		;must be 0-4
	bcc	getPort		;invalid port, re-enter

	asla			;convert to port address
	asla
	staa	portAdr+1	;LSB of port address
	ldab	#$80		;MSB of port address
	stab	portAdr

	ldx	portAdr		;initialize transfer port
	cmpa	#$04		;using console?
	beq	skipRst		;yes, skip port reset
	
	ldaa	#3		;reset serial port
	staa	0,x

skipRst	ldaa	#$15		;init ACIA to 8N1
	staa	0,x

* Prompt the user to insert the disk to write, initialize, then start
*    sending NAK to start the XMODEM reception.
		
	ldx	#mStart		;display start file transfer prompt
	jsr	dispMsg

	ldaa	#1		;init xmodem block number to one
	staa	blkNum
	
	ldx	#trkBuf		;trkBuf is initial starting point for
	stx	rcvPtr		;   XMODEM receive

	clr	trkNum		;starting on track 0
	clr	eotFlag		;no EOT yet

	ldaa	#ACK		;init exit status to good (ACK)
	staa	ackEot

	ldaa	#NAK		;send starting NAK after purging input data
	jsr	purgSio

*-----------------------------------------------------------------------------
*  mainLp - Receive as many packets as will fit into RAM and then write
*      tracks to disk. Repeat until entire disk is written. 
*-----------------------------------------------------------------------------
mainLp	bsr	bufPkts		;buffer as many packets as possible

	ldaa	rcvPtr+1	;compare rcvPtr-trkBuf
	suba	#trkBuf&$FF
	ldaa	rcvPtr
	sbca	#trkBuf/256
	bcs	allDone		;nothing received but an EOT

	ldx	#trkBuf		;writing will start from here
	stx	trkPtr

* trkLoop - write tracks until all disk tracks have been written or all
*    the tracks in the buffer have been written.

	jsr	dSelect		;select drive and start motor
		
trkLoop	jsr	wrtTrk		;write a track 

	inc	trkNum		;increment track number
	ldaa	trkNum
	cmpa	numTrk		;done all tracks on the disk?
	beq	allDone		;yes, we're all done

	ldaa	rcvPtr+1	;compare rcvPtr-start of next track
	suba	trkPtr+1
	tab			;save LSB in B for later

	ldaa	rcvPtr		;MSB
	sbca	trkPtr
	bcc	trkLoop		;trkPtr < rcvPtr, keep writing

* All the buffered tracks have been written. Move any bytes left over
*    from the xmodem receive to the start of trkbuf. Adjust rcvPtr
*    to the end of that data which is where reception of the next
*    packet will begin.

* At this point A:B = rcvPtr-trkPtr which is (left over byte count)-PKTLEN

	ldaa	eotFlag		;did EOT occur in last packet?
	bne	allDone		;yes, transfer is done

	ldx	#trkBuf		;X->start of track buffer
	stx	rcvPtr		;assume receive will here next time

	addb	#PKTLEN		;compute B=left over byte count
	beq	mvDone		;nothing to move, exit

moveLp	ldx	trkPtr		;X->leftover byte to move
	ldaa	0,x
	inx
	stx	trkPtr

	ldx	rcvPtr		;X->moving to start of trkBuf
	staa	0,x
	inx	
	stx	rcvPtr

	decb			;decrement bytes to move
	bne	moveLp

mvDone	jsr	dMotOff		;turn off drive motor
	ldaa	#ACK		;ACK the last packet
	jsr	sndByte
	jmp	mainLp		;go receive more packets

* allDone - The disk is completely written. ACK the final packet (or EOT),
*     display the "transfer complete" message and start the program over.

allDone	ldaa	ackEot		;ACK or EOT is here
	jsr	sndByte

	ldx	#mDone		;assume no error
	cmpa	#ACK		;ACK means no error
	beq	noErr

	ldx	#mDskErr	;else, write error message

noErr	jsr	dispMsg
	jsr	dMotOff		;turn off drive motor
	jmp	getDrv		;start over asking for a drive
	
*-----------------------------------------------------------------------------
* bufPkts - Receive XMODEM packets until we fill all the track buffer
*   space available. Reception starts at the address pointed to by
*   rcvPtr. Reception is stopped when the last packet exactly fills
*   or crosses the end of the last track spot in RAM. rcvPtr is updated
*   on exit to point to the start of the last packet received. The disk
*   write routines use this pointer to know where data ends and then
*   update rcvPtr to point where to begin receiving new packets.
*-----------------------------------------------------------------------------
bufPkts	bsr	rcvPkt		;receive a packet
	cmpa	#EOT		;exit due to EOT?
	beq	bufDone		;yes, buffering is done

	ldaa	rcvPtr+1	;compute rcvPtr-bufEnd
	suba	bufEnd+1
	ldaa	rcvPtr
	sbca	bufEnd
	bcc	bufDone		;rcvPtr > bufEnd, no more room

	ldaa	#ACK		;ACK the packet
	jsr	sndByte

	jmp	bufPkts		;buffer more packets

* bufDone - no more room for packets. Update rcvPtr to point to the 
*   start of the last packet received so the disk write routines know
*   where data ends.

bufDone	ldaa	rcvPtr+1	;subtract PKTLEN from rcvPtr
	suba	#PKTLEN
	staa	rcvPtr+1

	ldaa	rcvPtr		;MSB
	sbca	#0
	staa	rcvPtr

	rts

*-----------------------------------------------------------------------------
* rcvPkt - receive an xmodem format 128 byte packet. rcvPtr points to
*     where the packet should be placed. rcvPtr is updated after each
*     packet is received.
*-----------------------------------------------------------------------------
nakPkt	ldx	pktStrt		;reset pointer to start of packet
	stx	rcvPtr
	
	ldaa	#NAK		;NAK the packet
	jsr	purgSio		;purge receive for 1s then xmit NAK

* Wait for SOH or EOT

rcvPkt	ldx	rcvPtr		;save the start of packet address
	stx	pktStrt

	ldaa	#3		;3 second timeout for SOH
	bsr	rcvByte		
	beq	nakPkt		;timeout

	cmpa	#SOH		;SOH received?
	beq	haveSoh

	cmpa	#EOT		;EOT to say we're done?
	bne	rcvPkt		;no, keep loooking

	staa	eotFlag		;EOT flag is true
	rts			;exit

* Have SOH. Now receive block number and not block number

haveSoh	bsr	rcvByt1		;get the block number (1s timeout)
	beq	nakPkt		;timeout, NAK and start over
	
	staa	rcvBlk		;save the received block number
	
	bsr	rcvByt1		;get not block number
	beq	nakPkt		;timeout, NAK and start over

	staa	rcvNBlk		;save not block number

* Receive the 128 byte data portion of the packet

	ldaa	#PKTLEN		;set transfer length
	staa	byteCnt
	clr	chkSum		;init checksum

pktLoop	bsr	rcvByt1		;get data byte
	beq	nakPkt		;timeout

	ldx	rcvPtr		;X->where to put next byte
	staa	0,x		;save the byte
	inx			;increment rcvPtr
	stx	rcvPtr

	adda	chkSum		;update checksum
	staa	chkSum
	
	dec	byteCnt		;decrement byte counter
	bne	pktLoop		;and repeat

* Verify the checksum

	bsr	rcvByt1		;get data byte
	beq	nakPkt		;timeout

	cmpa	chkSum		;checksum match
	bne	nakPkt		;no, packet error

* Checksum good, verify block numbers

	ldaa	rcvNBlk		;verify rcvBlk = not(rcvNBlk)
	coma
	cmpa	rcvBlk		;match?
	bne	nakPkt		;no, packet error

	cmpa	blkNum		;rcvBlk = expected bklNum?
	bne	nakPkt		;no, packet error
	
	inca			;increment expected block number
	staa	blkNum

	clra			;return zero status
	rts

*-----------------------------------------------------------------------------
* purgSio - Wait for 1 second of idle time on receive line, then transmit the
*	character passed in A (usually ACK or NAK). Clobbers B.
*-----------------------------------------------------------------------------
purgSio	psha			;save character to send

purge	bsr	rcvByt1		;wait for 1 second without activity
	bne	purge

	pula			;get back the character to send
*				;fall through to sndByte

*-----------------------------------------------------------------------------
* sndByte - send the byte in A through the serial port. Clobbers B.
*-----------------------------------------------------------------------------
sndByte	stx	ioSaveX		;preserve X
	ldx	portAdr		;X->serial port
		
sbLoop	ldab	0,x		;wait for OK to transmit
	andb	#SIOTDRE
	beq	sbLoop	

	staa	1,x		;send the character
	ldx	ioSaveX		;restore X
	rts

*-----------------------------------------------------------------------------
* rcvByte - Receive a byte from the serial port with a timeout specified
*     in seconds in A. If a timeout occurs, zero is returned in A and the
*     zero flag is true. Otherwise, the character is returned in A and zero 
*     flag is false. Clobbers A, B, X
*-----------------------------------------------------------------------------
rcvByt1	ldaa	#1		;one second timeout
*				;fall into rcvByte
rcvByte	ldx	#29412		;one second, 34 cycles per loop

rbLoop	stx	ioSaveX		;(6) preserve counter
	ldx	portAdr		;(5) X->serial port
	
	ldab	0,x		;(4) see if a new character is present
	andb	#SIORDRF	;(2)
	bne	rbRead		;(4) new character present

	ldx	ioSaveX		;(5) restore counter
	dex			;(4) decrement timeout
	bne	rbLoop		;(4)

	deca			;decrement seconds count
	bne	rcvByte

	rts			;timeout, exit with Z true
	
rbRead	ldaa	1,x		;A=byte received
	inx			;ensure Z is false
	rts

*-----------------------------------------------------------------------------
* wrtTrk - Write NUMSEC sectors to the current track from a trkBuf as
*   pointed to by trkPtr. After the track is written, trkPtr is updated
*   by the length of a track to point to the next track buffer. 
*-----------------------------------------------------------------------------
wrtTrk	jsr	dSeek		;seek to track in trkNum
	bne	seekErr		;seek error or write protected
	
	ldaa	#WRTRIES	;init write retry counter
	staa	wrRetry
	
rtryWrt	clr	secIdx		;start with 1st sector on track
	ldaa	#NUMSEC		;set number of sectors to write
	staa	secCnt
	
wrtLoop	jsr	dSecAdr		;get sector number and buffer address
	jsr	dWrite		;write the sector
	bne	wrtErr		;write failed

	dec	secCnt		;decrement sector count
	bne	wrtLoop		;loop until all sectors written

* Verify the track just written

	clr	secIdx		;start with 1st sector on track
	ldaa	#NUMSEC		;B=number of sectors to verify
	staa	secCnt

vfyLoop	jsr	dSecAdr		;get sector number and buffer address
	jsr	dVerify
	bne	vfyRtry		;error, goto retry logic

vfyNext	dec	secCnt		;decrement sector count
	bne	vfyLoop		;loop until all setors verified
	
wrtDone	ldaa	trkPtr+1	;increment tkrPtr by TRKLEN or 2*TRKLEN
	adda	trkLen+1
	staa	trkPtr+1
	
	ldaa	trkPtr		;MSB
	adca	trkLen
	staa	trkPtr

	rts

* wrtErr - error on the write itself

wrtErr	ldaa	#cRESET		;1771 reset command
	jsr	dCmdWt		;issue command and wait
	dec	wrRetry
	bne	rtryWrt
	bra	wrtFail

* vfRtry - verify failed, retry reads followed by a re-write
*   of the track.

vfyRtry	ldaa	#RDTRIES	;init read retry counter
	staa	rdRetry

retryLp	jsr	dVerify		;attempt to verify again
	beq	vfyNext		;worked - move on to next sector

	dec	rdRetry		;decrement the read retry counter
	bne	retryLp

* Read retries expired, decrement the write retry count and re-write
*   if the count is not expired

	dec	wrRetry		;decrement the write retry counter
	bne	rtryWrt

	bra	vfyErr		;display the error and exit	
	
*-----------------------------------------------------------------------------
* Seek error. This is either an actual seek error or the disk is
*    write protected. 
*-----------------------------------------------------------------------------
seekErr	ldx	#mWrtPrt	;write protect error message
	bita	#sWRPROT	;write protect?
	bne	errMsg		;display error and start over
	
	ldx	#mSkErr		;seek error message
	bra	errMsg		;display error and start over
		
*-----------------------------------------------------------------------------
* Disk write verify failure. Display the track and sector with the error. 
*    Restart the program.
*-----------------------------------------------------------------------------
wrtFail	ldx	#mNoSec		;X->"sector not found, not formatted?"
	jsr	dispMsg

vfyErr	ldx	#errTrk		;X->where to put ASCII track number
	ldaa	trkNum		;A=track with error
	jsr	bin2dec		;store ASCII track number

	ldx	#errSec		;X->where to put ASCII sector number
	ldaa	secNum		;A=sector with error
	jsr	bin2dec	

	ldx	#mDskErr	;display the completed error message
errMsg	jsr	dispMsg	

	jsr	dMotOff		;turn off drive motor
	lds	#STACK		;re-initialize stack pointer
	jmp	getDrv		;start over	

*-----------------------------------------------------------------------------
* rcvCon - Receive a character from the console device, echo it, then
*   wait for a CR. Exits program if Ctrl-c typed. Returns with invalid
*   character (null) if BS or DEL pressed after 1st character
*   Returns character in a, clobbers b.
*-----------------------------------------------------------------------------
rcvCon	jsr	INEEE		;use SWTBUG to get character from console
	cmpa	#CTRLC		;abort requested?
	beq	pgmExit		;yes, exit
	
	cmpa	#CR		;return pressed?
	beq	rcExit		;yes, exit
	
	tab			;save character in B

* Wait for CR, then return 1st character typed

rcWtCr	jsr	INEEE		;use SWTBUG to get character from console
	cmpa	#CTRLC		;abort requested?
	beq	pgmExit		;yes
	
	cmpa	#DEL		;delete?
	beq	rcExit		;yes, return DEL character
	
	cmpa	#BS		;backspace?
	beq	rcExit		;yes, return the backspace
	
	cmpa	#CR		;return pressed?
	bne	rcWtCr		;no, keep waiting
	
	tba			;return 1st character typed in A
rcExit	rts

*------------------------------------------------------------------
* sizeRam - determine how much RAM we have for buffering tracks.
*   Sets the bufEnd variable which points to end address of 
*   the last possible track buffer + 1
*------------------------------------------------------------------
sizeRam	ldx	#trkBuf+$100	;look for end of RAM starting
	stx	saveX		;    at trkBuf+$100

ramLoop	ldaa	0,x		;A=current RAM content
	inc	0,x		;change value in RAM
	ldab	0,x		;B=new value from RAM
	staa	0,x		;restore original value
	
	cba			;did RAM change?
	beq	ramEnd		;no, so end of RAM found
	
	inc	saveX		;increment to next page
	ldx	saveX		;X->next page in RAM	
	bpl	ramLoop		;loop until $80xx reached

* ramEnd - end of RAM found. Subtract 256 bytes from this to make room
*   for xmodem packet overflow at the end. Determine the end address + 1 
*   of the last track buffer that will fit in RAM. Store in bufEnd

ramEnd	ldab	saveX		;B=1st page not RAM
	decb			;subtract 256 bytes 
	ldx	#trkBuf		;start saveX at trkBuf
	stx	saveX

bfEndLp	ldaa	saveX+1		;increment saveX by track length
	staa	bufEnd+1	;bufEnd has previous value of saveX
	adda	trkLen+1
	staa	saveX+1

	ldaa	saveX		;MSB
	staa	bufEnd		;bufEnd has previous value of saveX
	adca	trkLen
	staa	saveX

	cba			;saveX MSB < RAM end MSB?
	bcs	bfEndLp		;yes, still more room,

	rts			;exit with bufEnd properly set

*--------------------------------------------------------------
* pgmExit - Exit program to SWTBUG
*--------------------------------------------------------------
pgmExit	ldaa	drvNum		;drive selected yet?
	bmi	exitJmp		;no, just exit
	
	jsr	dSelect		;select drive, restore to track 0
	jsr	dRestor

exitJmp	jmp	CONTRL		;exit to SWTBUG

*--------------------------------------------------------------
* bin2dec - Binary byte in A to 2 ASCII digits at (X)
*--------------------------------------------------------------
bin2dec	ldab	#' '		;assume zero supressed
	suba	#10		;value less than 10?
	bcs	do1s		;yes, leading blank
	ldab	#'1'		;have one ten already

loop10	suba	#10		;count 10s
	bcs	do1s		;done with 10s, do 1s
	incb
	bra	loop10

do1s	stab	0,x		;save 10's digit
	adda	#$3a		;add back 10 plus '0' ASCII offset
	staa	1,x		;save 1's digit
	rts

*---------------------------------------------------------------------
* format - Format disk
*---------------------------------------------------------------------
format	ldx	#mFmting	;display "formatting..."
	jsr	dispMsg

	jsr	dSelect		;select drive in drvNum
	jsr	dRestor		;restore to track 0
	beq	fmtInit		;no error, go init format buffer
	jmp	seekErr		;else, probably write protect error

fmtInit	clr	trkNum		;start on track 0
	ldx	#trkBuf		;x->buffer for track format data

clrLoop	clr	0,x		;init entire buffer to zero
	inx	
	cpx	#trkBuf+FMTLEN
	bne	clrLoop
	
* fmtLoop - Loop through each track writing the track buffer to
*    disk after updating ID fields in each sector for each track.
		
fmtLoop	ldx	#trkBuf+2	;+2 gives 8 byte gap 1 like NEWDISK
	clr	secIdx		secIdx goes from 0 to 9

secLoop	bsr	secData		;insert sector data
	inc	secIdx		;repeat for all 10 sectors
	ldaa	secIdx
	cmpa	#NUMSEC
	bne	secLoop

	jsr	dWrtTrk		;write the formatted track
	inc	trkNum		;increment track number
	ldaa	trkNum
	cmpa	numTrk		;last track done?
	beq	fmtDone		;yes, exit

	jsr	dSeek		;else, seek to next track
	bra	fmtLoop		;repeat

fmtDone	ldx	#mFmtDon	;x->"done" message
	jmp	dispMsg		;display "done" and exit

* secData - Update the ID record for each sector in the track

secData	ldaa	#$FE		;$FE = ID address mark
	staa	idMark,x

	ldaa	#1		;sector size flag (256 bytes)
	staa	idSize,x

	ldaa	trkNum		;set track in ID record
	staa	idTrk,x

	ldab	secIdx		;no translation, secNum=secIdx
	stab	idSec,x		;set sector in ID record

	ldaa	#$FB		;place the data mark
	staa	dataMrk,x

	ldaa	#$F7		;generate CRCs
	staa	idCrc,x		;ID record CRC

	ldab	#128		;increment X by 128
	jsr	addB2X
	staa	dataCrc-128,x	;data CRC
	
	ldab	#RSECLEN-128	;b=length of raw sector left to add

* addB2X - Add B to X

addB2X	stx	saveX		;increment x by raw sector length
	addb	saveX+1		;compute LSB
	stab	saveX+1
	bcc	addExit		;done if no carry

	inc	saveX		;else increment MSB

addExit	ldx	saveX		;return result in X
	rts

*---------------------------------------------------------------------
* Disk I/O subroutines
*---------------------------------------------------------------------
DRVREG	equ	$8014 		;drive select register
CMDREG	equ	$8018 		;1771 command register
TRKREG	equ	$8019 		;1771 track register
SECREG	equ	$801A 		;1771 sector register
DATAREG	equ	$801B 		;1771 data register

* Select register requates

rMOTOFF	equ	$80		;1=motor off
rSIDE1	equ	$40		;1=side 1 (top)

* 1771 Commands

cREAD	equ	$88		;read, head assumed settled
cWRITE	equ	$A8		;write, head assumed settled
cWAITHD	equ	$04		;bit to wait for head settle

cRESTOR	equ	$0B		;restore to track 0, load head
cSEEK	equ	$1B		;seek to track, load head
cWRTRK	equ	$F4		;write track (for formatting disk)
cRESET	equ	$D0		;reset 1771 to idle

* 1771 Status Masks

sNOTRDY	equ	$80		;1=drive not ready
sWRPROT	equ	$40		;1=write protected
sHEADLD	equ	$20		;1=head is loaded
sSKERR	equ	$10		;1=seek error
sCRCERR	equ	$08		;1=CRC error
sTRACK0	equ	$04		;1=on track zero
sINDEX	equ	$02		;1=index detected
sBUSY	equ	$01		;1=1771 busy

sSECERR	equ	$10		;1=sector not found
sWRTFLT	equ	$20		;1=write fault
sDLOST	equ	$04		;1=lost data
sDRQ	equ	$02		;1=data reques

* Error masks for status of a seek, read, write

eSEEK	equ	sSKERR+sWRPROT+sNOTRDY
eREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY
eVERIFY	equ	sSECERR+sCRCERR+sNOTRDY
eWRITE	equ	eREAD+sWRPROT+sWRTFLT

*--------------------------------------------------------------
* dWrite
*    Write sector specified by secNum from secPtr
*--------------------------------------------------------------
dWrite	ldx	secPtr		;X->sector buffer

	ldaa	secNum		;give sector number to the 1771
	staa	SECREG

	ldaa	#cWRITE		;1771 write command
	oraa	fWaitHd		;include possible wait head bit
	clr	fWaitHd		;off for subsequent writes
	sei			;disable interrupts
	staa	CMDREG		;issue command to 1771
	jsr	dCmdDly		;ensure command processing time

dwLoop	ldaa	CMDREG		;A=1771 status
	bita	#sBUSY		;write still in progress>?
	beq	dwDone		;no, write has finished

	bita	#sDRQ		;ready for data?
	beq	dwLoop		;no, keep waiting
	
	ldaa	0,x		;get next byte
	staa	DATAREG		;give it to the 1771
	inx
	bra	dwLoop

dwDone	cli			;re-enable interrupts
	anda	#eWRITE		;test and return write error bits
	rts

*--------------------------------------------------------------
* dWrtTrk
*    Write a 1771 full track from trkBuf for formatting
*--------------------------------------------------------------
dWrtTrk	ldx	#trkBuf		;X->formatted track buffer

	ldaa	#cWRTRK		;1771 write track command
	sei			;disable interrupts
	staa	CMDREG		;issue command to 1771
	jsr	dCmdDly		;ensure command processing time

dwtLoop	ldaa	CMDREG		;A=1771 status
	bita	#sBUSY		;write still in progress>?
	beq	dwtDone		;no, write has finished

	bita	#sDRQ		;ready for data?
	beq	dwtLoop		;no, keep waiting
	
	ldaa	0,x		;get next byte
	staa	DATAREG		;give it to the 1771
	inx
	bra	dwtLoop

dwtDone	cli			;re-enable interrupts
	anda	#eWRITE		;test and return write error bits
	rts
	
*--------------------------------------------------------------
* dVerify
*    Verify sector specified by secNum by validating its CRC
*--------------------------------------------------------------
dVerify	ldaa	secNum		;give sector number to the 1771
	staa	SECREG

	ldaa	#cREAD		;1771 read command
	bsr	dCmdWt		;issue command and wait
	anda	#eVERIFY	;test and return verify errors
	rts
	
*--------------------------------------------------------------
* dSeek
*    Seek to track trkNum. Head is loaded and the wait head
*    bit (E) is set in the fWaitHd flag. If trkNum is track
*    zero (will be the 1st seek in this program), then the 
*    restore command is used to establish the track number
*    in the 1771. The returned status includes the write 
*    protect bit.
*--------------------------------------------------------------
dSeek	ldaa	#cWAITHD	;next I/O must wait on head status
	staa	fWaitHd
	
	ldaa	trkNum		;A=desired track
	beq	dRestor		;if track 0 use restore instead
	
	staa	DATAREG		;give track to the 1771
	ldaa	#cSEEK		;A=seek to track and load head
	bsr	dCmdWt		;issue command and wait for it
	anda	#eSEEK		;status mask includes write protect
	rts
	
*--------------------------------------------------------------
* dRestore
*    Issue and wait on restore to track zero. The returned
*    status includes the write protect bit.
*--------------------------------------------------------------
dRestor	ldaa	#cRESTOR	;A=restore to track 0 and load head
	bsr	dCmdWt		;issue command and wait for it
	anda	#eSEEK		;status mask includes write protect
	rts
	
*--------------------------------------------------------------
* dCmdWt
*    Write sector specified by secNum from secPtr
*--------------------------------------------------------------
dCmdWt	staa	CMDREG		;issue command from A
	bsr	dCmdDly		;ensure command processing time
	
dWtCmd	ldaa	CMDREG		;poll for busy bit to go false
	bita	#sBUSY
	bne	dWtCmd	

	rts
	
*--------------------------------------------------------------
* dSelect
*    Select the drive in drvNum. Wait 1s for motor startup.
*--------------------------------------------------------------
dSelect	ldaa	CMDREG		;start motor
	ldaa	drvNum		;select target drive
	staa	DRVREG
	
	ldx	#0		;wait 1s for motor start up
motrDly	inx			;(4)
	dex			;(4)
	dex			;(4)
	bne	motrDly		;(4)
	
	rts

*--------------------------------------------------------------
* dMotOff
*    Wait for head to unload, then turn off drive motor
*--------------------------------------------------------------
dMotOff	ldx	#$C000		;delay 3/4s to give time for 
	jsr	motrDly		;  head unload (deselects drive)

	ldaa	drvNum		;turn off drive motor
	oraa	#rMOTOFF
	staa	DRVREG
	rts

*--------------------------------------------------------------
* dCmdDly
*    Wait 76us (including the calling JSR) to ensure a 1771
*    command is accepted and the status register can be read
*--------------------------------------------------------------
dCmdDly	ldaa	#10		;10*6=60us in loop

dlyLoop	deca
	bne	dlyLoop
	rts
	
*--------------------------------------------------------------
* dSecAdr
*    Convert the sector index (secIdx) into the sector number
*    on the disk (secNum) and a pointer into the track buffer
*    (secPtr). 
*--------------------------------------------------------------
dSecAdr	ldaa	secIdx		;no translation, secNum=secIdx
	staa	secNum
	
	adda	trkPtr		;compute MSB of secPtr into trkBuf
	staa	secPtr
	ldaa	trkPtr+1	;LSB is the same (256 byte sectors)
	staa	secPtr+1

	inc	secIdx		;increment secIdx for next sector
	rts	

*---------------------------------------------------------------------
* Message constants
*---------------------------------------------------------------------
mWelcom	fcb	CR,LF,LF
	fcc	'===== PC to Floppy Image Transfer, Version 1.2 ====='
	fcb	CR,LF,LF
	fcc	'Writes a SWTPC 5.25" disk with an FDOS disk image'
	fcb	CR,LF
	fcc	'received through an MP-S serial port using XMODEM.'
	fcb	EOT

mDrive	fcb	CR,LF,LF
	fcc	'Insert and specify destination drive (0-3) or X to exit: '
	fcb	EOT

mPort	fcb	CR,LF,LF
	fcc	'Specify the port to use for file transfer'
	fcb	CR,LF
	fcc	'  0) MP-S at $8000'
	fcb	CR,LF
	fcc	'  1) MP-S at $8004 (console port)'
	fcb	CR,LF
	fcc	'  2) MP-S at $8008'
	fcb	CR,LF
	fcc	'  3) MP-S at $800C'
	fcb	CR,LF
	fcc	'  4) MP-S at $8010'
	fcb	CR,LF
	fcc	'Enter port or X to exit: '
	fcb	EOT

mFormat	fcb	CR,LF,LF
	fcc	'Format the target disk? '
	fcb	EOT

mFmting	fcb	CR,LF
	fcc	'  Formatting...'
	fcb	EOT

mFmtDon	fcb	CR,LF
	fcc	'  Format complete'
	fcb	EOT

mFmtErr	fcb	CR,LF,LF
	fcc	'Formatting failed'
	fcb	CR,LF,EOT

mStart	fcb	CR,LF,LF
	fcc	'Start XMODEM send operation from the PC now...'
	fcb	EOT

mDone	fcb	CR,LF,LF
	fcc	'Transfer complete'
	fcb	CR,LF,EOT

mNoTrk0	fcb	CR,LF,LF
	fcc	'Seek to track 0 failed'
	fcb	CR,LF,EOT

mNoSec	fcb	CR,LF,LF
	fcc	'Sector not found - disk not formatted?'
	fcb	EOT

mDskErr	fcb	CR,LF,LF
	fcc	'Write failure on track '
errTrk	fcc	'xx, sector '
errSec	fcc	'xx. Disk creation aborted.'
	fcb	CR,LF,EOT

mWrtPrt	fcb	CR,LF,LF
	fcc	'Disk is write protected'
	fcb	CR,LF,EOT

mSkErr	fcb	CR,LF,LF
	fcc	'Restore or seek error'
	fcb	CR,LF,EOT

*---------------------------------------------------------------------
*  Data area
*---------------------------------------------------------------------

* Disk Variables

numTrk	rmb	1		;number of tracks on the disk
trkLen	rmb	2		;length of a track

drvNum	rmb	1		;drive number to use
trkNum	rmb	1		;track number to read
secIdx	rmb	1		;ordinal sector number
secNum	rmb	1		;current physical sector number
secCnt	rmb	1		;count of sectors to process

trkPtr	rmb	2		;pointer into trkBuf at track boundaries
secPtr	rmb	2		;pointer to current sector in trkBuf
bufEnd	rmb	2		;end address + 1 of last track spot in RAM

fWaitHd	rmb	1		;wait for head load bit
wrRetry	rmb	1		;write retry counter
rdRetry	rmb	1		;read retry counter

* XMODEM Variables

portAdr	rmb	2		;serial port address
blkNum	rmb	1		;current xmodem block number
rcvBlk	rmb	1		;block number received
rcvNBlk	rmb	1		;NOT block number received
rcvPtr	rmb	2		;pointer for start of XMODEM send
pktStrt	rmb	2		;address of start of packet
chkSum	rmb	1		;packet checksum
byteCnt	rmb	1		;count of bytes to send
ackEot	rmb	1		;ACK if no error, EOT if error
eotFlag	rmb	1		;non-zero if EOT received

* Misc Variables

saveX	rmb	2		;generic save location for X
ioSaveX	rmb	2		;save X in I/O subroutines

* Track buffer runs from here to the end of memory

	rmb	PKTLEN		;space for pre-pended unsent data
trkBuf	equ	*

	org	$A048		;start address for "G" command
	fdb	start

	end



