;**************************************************************
; Cromemco 16FDC and 64FDC Z80-Compatible CP/M Boot Loader    *
; Written by Martin Eberhard 11 April 2021                    *
;                                                             *
; REVISION HISTORY                                            *
; 1.01  9 May 2021  M. Eberhard                               *
;   Rename to be CroBoot                                      *
; 1.00  11 April 2017  M. Eberhard                            *
;   Ported from Eberhard's CCS 2422 MEBOOT version 3.00       *
;                                                             *
;**************************************************************
; Load CP/M from disk into RAM starting at address CCP,       *
; starting with track 0, sector 2 and continuing until BSIZE  *
; bytes are loaded, where BSIZE is a multiple of 256 bytes,   *
; and does not allow the load to wrap around enough to  wipe  *
; out this code. Retry forever on errors.                     *
;                                                             *
; This program works automatically with minidisks and 8"      *
; disks by alternately trying to boot as a (full-speed) 8"    *
; disk and as a (half-speed) minidisk. Track 0 must be        *
; single-density, 128 bytes per sector. Subsequent system     *
; tracks must be double-density, 256 bytes per sector. (This  *
; is true even for single-density disks, to make the CP/M     *
; image fit in the reserved system tracks. Note that PerSci   *
; single-sided drives - the PerSci 270 and 277 - cannot       *
; reliably read double-density data.)                         *
;                                                             *
; The read loop here is not fast enough to read double-       *
; density tracks on an 8" (or other 500 kbits/sec) drive, if  *
; the CPU is set for 2 MHz.                                   *
;                                                             *
; This program loads from side 0 only on double-sided disks.  *
;                                                             *
; This program assumes that the 16FDC/64FDC ADCMD and serial  *
; port registers were already set up with the correct disk    *
; side and baud rate etc. (This is reasonable because RDOS-II *
; on the 16FDC controller just loaded this program.)          *
;                                                             *
; No assumptions are made about any RAM variables or register *
; values that might have been passed by the boot monitor.     *
;                                                             *
; This program resides in track 0, sector 1 if disk A, and    *
; gets loaded (by the boot monitor) into RAM at  80h. Its     *
; maximum length is 128 bytes (one sector), and it should not *
; write into the Transient Program Area (TPA), from 100h up   *
; to the CCP. so that any loaded program won't be damaged by  *
; a cold-boot.                                                *
;                                                             *
; Since sector 1 of track 0 (where this boot loader resides)  *
; is not part of the CP/M image, and since track 0 sectors    *
; are 128 bytes long, track 0 will end on an odd 128-byte     *
; boundary. Subsequent tracks are 256 bytes/sector, so the    *
; last sector may be 128 bytes too long. With a 63K CP/M      *
; build, these last 128 bytes will wrap around and write into *
; the first 128 bytes of RAM, just below this boot loader.    *
; This is no problem because the BIOS rebuilds this area      *
; during its boot process.                                    *
;                                                             *
; This code assembles with Digital Research's ASM.COM. Z-80   *
; instructions are done the hard way, with DB and DW pseudo-  *
; operations.                                                 *
;**************************************************************

;---------------------------
;This code'S revision number
;---------------------------
BOOTVER	equ	0101h		;high byte = major rev number
				;low byte=2-digit minor rev
;------------
;CP/M Equates
;------------
MSIZE	equ	63		;CP/M "memory size" in k-bytes

BSIZE	equ	2000h		;size of CP/M to load

BIAS	equ	(MSIZE-20)*1024
CCP	equ	3400H+BIAS
BIOS	equ	CCP+1600H	;Base address of BIOS
MAXMEM	equ	CCP+BSIZE

BOOT	equ	80h		;load/execution address
;-----------------
;Sectors per Track
;-----------------
SPT5	equ	18	;128 bps SD & 256 bps DD minidisk SPT
SPT8	equ	26	;128 bps SD & 256 bps DD 8" disk SPT
;-------------------------------------------------------
;Cromemco 16FDC and 64FDC floppy disk controller equates
;--------------------------------------------------------

;Port addresses

USTAT	equ	00h		;UART status port
UBAUD	equ	00h		;UART Baud rate
UDATA	equ	01h		;UART data port
UCMD	equ	02h		;UART command
INTADR	equ	03h		;Interrupt address
INTMSK	equ	03h		;Interrupt mask

ADCMD	equ	04h		;Auxiliary disk command
ASTAT	equ	04h		;Auxiliary disk status
DSTAT	equ	30h		;disk status port
DCMMD	equ	30h		;disk command port
DTRCK	equ	31h		;track port
DSCTR	equ	32h		;sector port
DDATA	equ	33h		;data port
DFLAG	equ	34h		;disk flag port
DCTRL	equ	34h		;disk control port
BBANK	equ	40h		;bank select port
				;(deselects RDOS-II PROM)
;WD1793 commands

RSTRCM	equ	00000000b	;(I) restore
SEEKCM	equ	00010000b	;(I) seek
STEPCM	equ	00100000b	;(I) step
STPICM	equ	01000000b	;(I) step-in
STPOCM	equ	01100000b	;(I) step-out
RSECCM	equ	10000000b	;(II) read sector
WSECCM	equ	10100000b	;(II) write sector
RADRCM	equ	11000000b	;(III) read address
RTRKCM	equ	11100000b	;(III) read track
WTRKCM	equ	11110000b	;(III) write track
FINTCM	equ	11010000b	;(IV) force interrupt

;WD1793 command options (1st letter matches WD1793 data sheet)

RS03MS	equ	00000000b	;(I) 3 mS step rate
RS06MS	equ	00000001b	;(I) 6 mS step rate
RS10MS	equ	00000010b	;(I) 10 mS step rate
RS15MS	equ	00000011b	;(I) 15 mS step rate
VERTRK	equ	00000100b	;(I) verify on dest track
HDLOAD	equ	00001000b	;(I) head load
UPDTTR	equ	00010000b	;(I) update track reg
F1SCMP	equ	00000010b	;(II)enable side compare
F2SCS1	equ	00001000b	;(II)side compare, side 1
MULTCM	equ	00010000b	;(II) read/write multiple
ED15MS	equ	00000100b	;(II & III) settling delay

;Composite WD1793 commands used in this program

CCRSTOR	equ	RSTRCM+HDLOAD+RS15MS		;restore
CCSEEK	equ	SEEKCM+HDLOAD+RS15MS		;seek
CCSTEPI	equ	STPICM+HDLOAD+UPDTTR+RS15MS	;step-in
CCRDSEC	equ	RSECCM+F1SCMP			;read sector
CCWRSEC	equ	WSECCM+F1SCMP			;write sector
CCRDADD	equ	RADRCM+ED15MS			;read address

;Control register (DCTRL) output bits

DCDS1	equ	00000001b	;drive select 1
DCDS2	equ	00000010b	;drive select 2
DCDS3	equ	00000100b	;drive select 3
DCDS4	equ	00001000b	;drive select 4
DCMAXI	equ	00010000b	;0=Minidisk, 1=8" disk
DCMDMO	equ	00100000b	;Minidisk motor on
DCDDEN	equ	01000000b	;double density enable
DCAUTOW	equ	10000000b	;autowait

;Auxiliary Disk Command (ADCMD) output bits
;(All bits are active-low)

BCSIDE0	equ	00000010b	;0=side 1, 1=side 0
BCTRLO	equ	00000100b	;-Control Out (test)
BRESTOR	equ	00001000b	;-Restore (PerSci, 1FDC only)
BCFSEKN	equ	00010000b	;-Fast Seek (PerSci, 16FDC only)
BDSO	equ	00100000b	;-Drive Select Override (test)
BCEJECT	equ	01000000b	;-Remote eject (PerSci, 16FDC only)

;WD1793 status (DSTAT) after a command

SBUSY	equ	00000001b	;(All) busy
SCRCERR	equ	00001000b	;(All) CRC error
SWRPROT	equ	01000000b	;(All) write protect
SNOTRDY	equ	10000000b	;(All) drive not ready
SINDEX	equ	00000010b	;(I) index
STRACK0	equ	00000100b	;(I) track 0
SSEEKER	equ	00010000b	;(I) seek error
SHEADLD	equ	00100000b	;(I) head loaded
SDATRQ	equ	00000010b	;(II & III) data request state
SLOSTD	equ	00000100b	;(II & III) lost data error
SRNFER	equ	00010000b	;(II & III) record not found
SRCTYF	equ	00100000b	;(II & III) record type fault
SWFALT	equ	00100000b	;(II & III) Write fault

;Disk Flag Register (DFLAG) input bits

DEOJ	equ	00000001b	;WD1793 INTRQ signal, active high
DAWTO	equ	00000010b	;Autowait Timeout
DMOTO	equ	00000100b	;Motor Timeout
DMOTON	equ	00001000b	;Motor On
DINHIN	equ	00010000b	;-Inhibit Init
DFSHLD	equ	00100000b	;WD1793 HLD, active high
DFAUTOB	equ	01000000b	;autoboot jumper, 0=autoboot
DFSDRQ	equ	10000000b	;WD1793 DRQ signal, 1=ready

;Auxiliary Status Register (ASTAT) input bits

BSW8	equ	00000001b	;Switch 8
BSW7	equ	00000010b	;Switch 7
BSW6	equ	00000100b	;Switch 6
BSW5	equ	00001000b	;Switch 5
BSIP	equ	01000000b	;Seek in Progress (PerSci, 16FDC only)
BRTC	equ	10000000b	;512 m,Sec reat-time clock

;Equates for TMS5501-based UART on the 16FDC/64FDC disk controller

;USTAT bits

RXRDY	equ	01000000b	;Rx data ready
TXMTY	equ	10000000b	;Tx buffer empty

;UCMD bits

HIBAUD	equ	00010000b	;enable high baud
URESET	equ	00000001b	;Reset UART

;Modem status register bits

RTSSTA	equ	00010000b	;RTS bit
DTRSTA	equ	00100000b	;DTR bit

;----------------------------
;Some Z80 instruction opcodes
;----------------------------
JR	equ	18h
JRNZ	equ	20h
JRZ	equ	28h
JRNC	equ	30h
JRC	equ	38h
DJNZ	equ	10h

;2-byte instructions have bytes reversed for dw

BIT7A	equ	07FCBh		;BIT 7,A
BIT4D	equ	062CBh		;BIT 4,D
BIT6D	equ	072CBh		;BIT 6,D
SET4D	equ	0E2CBh		;SET 4,D
SET6D	equ	0F2CBh		;SET 6,D
RES4D	equ	0A2CBh		;RES 4,D
SET4E	equ	0E3CBh		;SET 4,E
INI	equ	0A2EDh
INIR	equ	0b2EDh
;--------------
;ASCII equates
;--------------
CR	equ	0Dh
LF	equ	0Ah
;------------------------
;Start of executable code
;------------------------
	org	BOOT	
;---------------------------------------------------
;Set up stack that will overwrite the signon message
;---------------------------------------------------
	lxi	sp,100h
;---------------------
;Print booting message
;---------------------
	lxi	h,BOTMSG
	mov	b,m		;byte count

PMSG:	in	USTAT		;wait for port
	rlc			;TXMTY-->carry
	db	JRNC,(PMSG-$-1) xor 0FF00h

	inx	h
	mov	a,m
	out	UDATA
	db	DJNZ,(PMSG-$-1) xor 0FF00h
;--------------------------------------------------------------
;(Re)try to load CP/M.
;Assume maxidisk (high data rate) at first. With each retry,
;toggle maxi/mini bit and SPT8/SPT5. Set up related counters
;according to this try's maxi/mini state. Track 0 is always
;SD 128-byte sectors, and subsequent boot tracks are always DD
;256-byte sectors.
;--------------------------------------------------------------
	lxi	d,(DCDS1+DCMDMO+DCAUTOW)*256+SPT5
				;try maxi to start out

RETRY:	mvi	a,CCRSTOR	;Restore disk
	call	DOCMD

	mov	a,d
	ani	DCDDEN xor 0FFh	;Set SD mode for track 0
	cmp	d		;were we double-density?
				;..which implies track 1
	db	JRNZ,RTRY1	;y: MAXI/MINI is ok

	xri	DCMAXI		;toggle DCMAXI
	mov	d,a

	mvi	a,SPT8 xor SPT5	;toggle SPT
	xra	e
	mov	e,a

RTRY1:	mov	b,e		;e=SPT
	dcr	b		;skip track 0 sector 1
	mvi	c,1

	lxi	h,CCP		;Load CP/M into RAM here

;Disable RDOS-II PROM, enable Bank 0 RAM

	mov	a,c		;c=1: Select RAM bank 0
	out	BBANK		;Turn off RDOS-II PROM
;-------------------------------------
;Read sectors until h passes its max
;  b=sector count
;  c=previous sector sector number
;  d=DCTRL value
;  e=SPT constant
;  hl=DMA address
;-------------------------------------
SECLUP:	inr	c		;next sector
	mov	a,c		;sector number
	out	DSCTR

	push	b		;sector count and sector number

	mvi	c,DDATA		;port address

	mov	a,d		;enable auto-wait, set density
	out	DCTRL

;Set b=BPS to 128 for track 0 (which is single-density), and
;256 (encoded as 0) for other tracks (which are double-density)

	cma			;so DCDDEN bit is 0 for DD
	ani	DCDDEN		;This is bit 6
	rlc			;move it to bit 7
	mov	b,a		;b=0 for DD, 80h for SD

;Read b bytes (BPS) from sector c into RAM at hl

	mvi	a,CCRDSEC	;read sector command
	out	DCMMD		;kick off read sector

RSLOOP:	in	DFLAG		;(11)Enable auto-wait
	dw	INI		;(16)read byte, write to RAM,
				;..inc address, dec byte count
	db	JRNZ,(RSLOOP-$-1) xor 0FF00h
				;(12)
	pop	b		;sector count and sector number

;Wait for sector read to complete

	call	DCWAIT		;Returns DSTAT

;Check sector-read status. If error, toggle maxi/mini
;(and related SPT) and retry entire load

	ani	SNOTRDY+SRNFER+SCRCERR+SLOSTD+SRCTYF
	db	JRNZ,(RETRY-$-1) xor 0FF00h

;Jump to loaded CP/M if we've loaded enough code

	mov	a,h		;Done loading?
	cpi	MAXMEM/256
	jz	BIOS		;y: execute it

;Next sector on this track, if there is one

	db	DJNZ,(SECLUP-$-1) xor 0FF00h

;next track, start with sector 1. All but track 0 are DD.

	mvi	a,CCSTEPI	;step in one track
	call	DOCMD

	dw	SET6D		;DCDDEN bit for double density

	mov	c,b		;b=0: start with sector 1
	mov	b,e		;reload sector counter with SPT

	db	JR,(SECLUP-$-1)  xor 0FF00h
	
;***Subroutine********************************************
;Issue a command to the WD1793 and wait for it to complete
;On Entry:
;  a=WD1793 command
;On Exit:
;  a=DSTAT
;  1793 INTRQ is cleared
;*********************************************************
DOCMD:	out	DCMMD

;Fall into DCWAIT

;***Subroutine************************
;Wait for a WD1793 command to complete
;On Exit:
;  a=DSTAT
;  1793 INTRQ is cleared
;  Auto-wait is disabled
;*************************************
DCWAIT:	in	DFLAG
	rar			;Test WD1793 INTRQ flag
	db	JRNC,(DCWAIT-$-1)  xor 0FF00h
				;INTRQ clears auto-wait

	in	DSTAT		;disk status, clear INTRQ
	ret
;**********************
;Version Message string
;**********************
BOTMSG:	db	BMLEN		;String length

	db 'CroBoot '
	db ((BOOTVER AND 0F00h)/256)+'0','.'
	db ((BOOTVER AND 0F0h)/16)+'0',(BOOTVER AND 0Fh) +'0'

BMLEN	equ	$-BOTMSG-1

	end	BOOT
