;************************************************************************
;
;  CP/M 2.2 Bootloader for MFA Modular Computer
;
;    	This loader is located in sector 1 of track 0, side 0. It
;       loads the CP/M image into high RAM and jumps to the cold
;       start BIOS entry vector. The CP/M image is located in sectors
;	2-8(9), side 0, track 0, then continuing on sectors 1-8(9),
;	on side 1 of track 0.
;
;	This code is loaded into RAM at address 0100h by the PROM
;	boot loader.
;
;       To patch an update to this loader into a CP/M image created
;	and saved after running MOVCPM, use the following commands:
;
;	   A>DDT CPM64.COM    (specify the saved CP/M image file)
;	   -IBOOT.HEX
;	   -R800	      (places the loader at 900h in RAM)
;	   -G0		      (return to CP/M to save or SYSGEN)
;
;	Ver	  Date		     Description
;	---	----------	---------------------------------------
;	1.0 	01/10/2019	Mike Douglas, Original
;
;************************************************************************

MEMSIZE	equ	64		;set memory size in K bytes

; Drive type selection. Only one type can be true (1)

DISK720	equ	1		;720K disks
DISK320	equ	0		;320K disks

; CP/M equates

CCPLEN	equ	0800h		;CPM 2.2 fixed length
BDOSLEN	equ	0E00h		;CPM 2.2 fixed length
BIOSLEN	equ	1A00h		;length of BIOS

CPMBASE	equ	MEMSIZE*1024 - BIOSLEN - BDOSLEN - CCPLEN
BIOSBAS	equ	(CPMBASE+CCPLEN+BDOSLEN) ;base address of the BIOS

; Drive equates

 if DISK720
SPT	equ	9		;sectors per track
STRTSEC	equ	2		;CP/M image starts at this sector
NUMSECS	equ	17		;number of sectors to read
 endif

 if DISK320
SPT	equ	8		;sectors per track
STRTSEC	equ	2		;CP/M image starts at this sector
NUMSECS	equ	15		;number of sectors to read
 endif

; Floppy disk controller equates (WD1793)

FDCBASE	equ	0C0H		;controller base address
FDCCMD	equ	FDCBASE		;1793 command register
FDCSTAT	equ	FDCBASE		;1793 status register
FDCTRK	equ	FDCBASE+1	;1793 track register
FDCSEC	equ	FDCBASE+2	;1793 sector register
FDCDATA	equ	FDCBASE+3	;1793 data in/out register
FDCSEL	equ	FDCBASE+4	;FDC drive select port
FDCWAIT	equ	FDCBASE+8	;FDC stall/wait port

; Commands for 1793

STPRATE	equ	0		;6ms step rate (1mhz clock)

cRESTOR	equ	008h+STPRATE	;restore, head load, no verify
cSEEK	equ	018h+STPRATE	;seek, head load, no verify
cSTPIN	equ	058h+STPRATE	;step in, update track reg, head loaded
cSTPOUT	equ	078h+STPRATE	;step out, update track reg, head loaded
cREAD	equ	080h		;read, head assumed settled
cWRITE	equ	0A8h		;write, head assumed settled
cRDADDR	equ	0C0h		;read address marker, assume head settled
cRESET	equ	0D0h		;reset 1793 to idle

cSTPTGL	equ	020h		;xor toggles between step in and out
cWAITHD	equ	004h		;bit for read/write to wait for head settle

; Status bits from 1793

sNOTRDY	equ	80h		;1=not ready
sWRPROT	equ	40h		;1=write protected
sHDLOAD	equ	20h		;1=head is loaded
sSKERR	equ	10h		;1=seek error
sCRCERR	equ	08h		;1=CRC error
sTRACK0	equ	04h		;1=on track zero
sINDEX	equ	02h		;1=index detected
sBUSY	equ	01h		;1=1793 busy

sSECERR	equ	10h		;1=sector not found
sWRTFLT	equ	20h		;1=write fault
sDLOST	equ	04h		;1=lost data 

; Combined status flag masks for status of a seek, read, and write

smTRK0	equ	sSKERR+sNOTRDY+sBUSY+sTRACK0
smSEEK	equ	sSKERR+sNOTRDY+sBUSY
smREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY+sBUSY
smWRITE	equ	smREAD+sWRPROT+sWRTFLT

; Drive select register parameters

pDRIVE0	equ	001h		;1=drive 0 select
pDRIVE1	equ	002h		;1=drive 1 select
pDENS	equ	004h		;1=single density, 0=double density
pSIDE	equ	008h		;1=side, 0=side 0

; 8251 equates

SIO1ST	equ	0A1h		;console status port
SIO1DT	equ	0A0h		;console data port
TBE	equ	1		;transmit buffer entry
RDA	equ	2		;receive data available

; Misc Equates

FDCINTV	equ	02Ch		;RST5.5 interrupt vector for FDC
SIM	equ	030h		;8085 SIM opcode
CR	equ	13		;ASCII carriage return
LF	equ	10		;ASCII line feed
STACK	equ	0100h		;local stack pointer


;------------------------------------------------------------------
;  Entry point: Assumes the drive is selected, still spinning,
;     and on track 0. Read sectors 2-8(9) on side 0, then 1-8(9)
;     on side 1 into RAM starting at CPMBASE.
;------------------------------------------------------------------
	org	0100h

	di			;just in case
	lxi	sp,STACK	;establish new stack pointer

	mvi	a,JMP		;set up the FDC interrupt vector
	sta	FDCINTV
	lxi	h,fdcIsr	;ISR address
	shld	FDCINTV+1

	lxi	h,CPMBASE	;HL->start of CP/M in RAM
	mvi	b,NUMSECS	;B=total number of sectors to load
	mvi	c,STRTSEC	;C=starting sector of CP/M image
	mvi	d,0		;D=side we’re on

cpmLoad	mvi	e,3		;E=retry counter

retry	mvi	a,pDRIVE0	;A=drive to select
	ora	d		;include which side to read	
	out	FDCSEL		;reselect drive to keep motor running

	push	h		;save address for retry
	mov	a,c		;set sector to read
	out	FDCSEC
	
	call	readSec		;execute the read
	ani	smREAD		;check for error
	jz	goodSec		;good read

	pop	h		;restore destination pointer	
	dcr	e		;decrement retry counter
	jnz	retry		;loop if more retries

	call	dspMsg
	db	CR,LF,'Loading of CP/M failed',CR,LF,0

	xra	a		;deselect drives and exit
	out	FDCSEL

	di
die	jmp	die

	
goodSec	pop	psw		;get rid of saved HL
	inr	c		;next sector
	mov	a,c
	cpi	SPT+1		
	jnz	readNxt		;not yet, keep going
	
	mvi	c,1		;resume at sector 1
	mvi	d,pSIDE		;  on side 1
	
readNxt	dcr	b		;decrement total sector count
	jnz	cpmLoad		;continue with next sector
	
	jmp	BIOSBAS		;jump to BIOS cold start vector	
	
;------------------------------------------------------------------
; readSec - Issue read sector command then go into a loop receiving
;    the bytes of the sector into the buffer pointed to by HL. The
;    ISR returns control directly to the caller of this subroutine
;    when the 1793 signals completion of the read operation.
;------------------------------------------------------------------
readSec	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	mvi	a,cREAD		;issue read sector command
	out	FDCCMD

rsLoop	out	FDCWAIT		;wait for data byte
	in	FDCDATA		;get byte
	mov	m,a		;save in memory
	inx	h
	jmp	rsLoop		;interrupt will exit this loop

;------------------------------------------------------------------
; fdcIsr - Interrupt service routine when the 1793 signals command
;    completion. The interrupted address is removed from the stack
;    and control returns to the caller of the interrupted routine.
;    The status from the 1793 is returned in A.
;------------------------------------------------------------------
fdcIsr	pop	psw		;remove ISR return address

wtBusy	in	FDCSTAT		;get 1793 status
	rrc			;make sure it's not busy
	jc	wtBusy		;shouldn't happen, but be safe

	rlc			;store status in A
	ret			;return to caller of command

;------------------------------------------------------------------
; dspMsg - display null-terminated in-line message.
;------------------------------------------------------------------
dspMsg	pop	h		;hl->string to display

dmLoop	mov	a,m		;a=next character to display
	inx	h		;move to next character
	ora	a		;null terminator?
	jz	dmExit		;yes, exit

	mov	b,a		;save character in B

dmWait	in	SIO1ST		;wait for OK to transmit
	ani	TBE
	jz	dmWait

	mov	a,b		;A=character to send
	out	SIO1DT		;send it
	jmp	dmLoop		;get next character

dmExit	pchl			;all done, return past string

	end


