;**************************************************************
; CCS 2422 Z80-Compatible CP/M Boot Loader                    *
; Written by Martin Eberhard 26 May 2010                      *
;                                                             *
; REVISION HISTORY                                            *
; 3.00  17 August 2017  M. Eberhard                           *
;   Rewrite to stop loading after the last needed sector,     *
;   and don't scribble at all in the transient program area.  *
;                                                             *
;**************************************************************
; 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 and wipe out     *
; this code. Retry forever on errors.                         *
;                                                             *
; This program works automatically with minidisks and 8"      *
; disks. Track 0 must be single-density, 128 bytes per        *
; sector. Subsequent tracks must be double-density, 256 bytes *
; per sector. (This is true even for single-density disks, to *
; avoid CCS's 'RLOCBIOS.COM' kluge.)                          *
;                                                             *
; This program loads from side 0 only on double-sided disks.  *
;                                                             *
; This program assumes that the CCS 2422 BCTRL and DCTRL      *
; registers are already set up with the correct selected      *
; disk, side, and size. (This is reasonable because the       *
; 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 the selected   *
; disk, 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 to 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 becuuse CP/M rebuilds this area during   *
; its boot process.                                           *
;                                                             *
; This code assembles with Digital Research's ASM.COM.        *
;**************************************************************

;---------------------------
;This BOOT'S revision number
;---------------------------
BOOTVER	equ	0300h		;high byte = major rev number
				;low byte=2-digit minor rev
;------------
;CP/M Equates
;------------
MSIZE	equ	63		;CP/M "memory sise" 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
;---------------------------------------
;CCS 2422 floppy disk controller equates
;---------------------------------------

;Port addresses

BCTRL	equ	4		;Control port 2
BSTAT	equ	4		;status port 2
DSTAT	equ	30h		;disk status port
DCMMD	equ	DSTAT		;disk command port
DTRCK	equ	DSTAT+1		;track port
DSCTR	equ	DSTAT+2		;sector port
DDATA	equ	DSTAT+3		;data port
DFLAG	equ	DSTAT+4		;disk flag port
DCTRL	equ	DSTAT+4		;disk control port
BBANK	equ	40h		;bank select port
				;(deselects MOSS 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 1 (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

;Control register 2 (BCTRL) output bits

BCEJECT	equ	00000100b	;remote eject control (PerSci)
BCFSEEK	equ	00010000b	;fast seek: voicecoil drives
BCSIDE0	equ	01000000b	;0=side 1, 1=side 0
BCROMEN	equ	10000000b	;enable onboard ROM

;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

;Status register 1 (DFLAG) input bits

DFINTRQ	equ	00000001b	;WD1793 INTRQ signal, active high
DFDS1	equ	00000010b	;DS1 in control reg 1
DFDS2	equ	00000100b	;DS2 in control reg 1
DFDS3	equ	00001000b	;DS3 in control reg 1
DFDS4	equ	00010000b	;DS4 in control reg 1
DFSHLD	equ	00100000b	;WD1793 HLD signal, active high
DFAUTOB	equ	01000000b	;autoboot jumper, 0=autoboot
DFSDRQ	equ	10000000b	;WD1793 DRQ signal, 1=ready

;Status reg 2 (BSTAT) input bits

BTRK0	equ	00000001b	;when on track 0: 1=8" disk
BMINI	equ	00000010b	;-DCTRL reg bit: 1=Minidisk
BWPRT	equ	00000100b	;0=disk is write-protected
BSIDE0	equ	00001000b	;SIDE0 bit in ctrl reg 2
BINDEX	equ	00010000b	;index signal, active low
BDDEN	equ	00100000b	;DDEN signal in ctrl reg 1
BSIDED	equ	01000000b	;0=selected disk is 2-sided
;-----------------------------------------------------
;Equates for 8250-based UART on the CCS 2810 CPU board
;-----------------------------------------------------
BASE10	equ	20h		;base address of 2810 I/O

SDATA	equ	BASE10		;data port
SINTEN	equ	SDATA+1		;interrupt enable port
SIDENT	equ	SDATA+2		;interrupt ID port
SLCTRL	equ	SDATA+3		;line control port
SMDMCT	equ	SDATA+4		;modem control port
SLSTAT	equ	SDATA+5		;line status port
SMSTAT	equ	SDATA+6		;modem status port

;Line status register bits

RXRDY	equ	00000001b	;Rx data ready
TXMTY	equ	00100000b	;Tx buffer empty
;----------------------------
;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 unless this is an auto-boot
;(With auto-boot, the UART may not be initialized.)
;--------------------------------------------------
	in	DFLAG		;CCS autoboot in progress?
	ani	DFAUTOB		;boot jumper bit
	db	JRZ,ABOOT-$-1	;jump if autoboot

	lxi	h,BOTMSG
	mov	b,m		;byte count

PMSG:	in	SLSTAT		;wait for port
	ani	TXMTY
	db	JRZ,(PMSG-$-1)mod 256

	inx	h
	mov	a,m
	out	SDATA
	db	DJNZ,(PMSG-$-1)mod 256
ABOOT:
;--------------------------------------------------------------
;Restore disk to track 0 and see if the drive is a minidisk.
;Set up d with mini/maxi, autowait, drive select (as it was
;when this program was invoked), motor on, and single-density.
;Set up b with track 0 sector count, c with initial sector
;number=2, e with SPT for all further tracks, and hl with DMA
;address.
;--------------------------------------------------------------
RETRY:	mvi	a,CCRSTOR	;Restore disk
	call	DOCMD		;Returns a=drive select bits

	ani	0Fh		;Just the drive select bits
	ori	DCMDMO+DCAUTOW	;Autowait and motor on
	mov	d,a		;d=DCTRL value from here on

	lxi	b,SPT5*256+1	;b: assume mini for the moment
				;c: start with sector 2

	in	BSTAT		;BTRK0 bit is 0 for minidisks
	rar			;Slide BTRK0 into carry

	db	JRNC,ITSMIN-$-1	;mini: we are set up
	mvi	b,SPT8		;8" disk SPT	
	dw	SET4D		;Set DCMAXI in DCTRL value
ITSMIN:
	mov	e,b		;remember SPT for later tracks
	dcr	b		;we skip sector 1 on track 0

	lxi	h,CCP		;Load CP/M into RAM here
;-------------------------------------
;Disable MOSS PROM, enable Bank 0 RAM
;-------------------------------------
	mov	a,c		;c=1: Select RAM bank 0
	out	BBANK		;Turn off MOSS PROM
;-------------------------------------
;Read sectors until hl reaches its max
;  b=sector count
;  c=previous sector sector number
;  d=DCTRL value
;  e=SPT constant
;  hl=DMA address
;-------------------------------------
SECLUP:	in	DSTAT		;clear INTRQ

	inr	c		;next sector
	mov	a,c		;sector number
	out	DSCTR

	push	b		;sector count and sector number

	mov	a,d		;enable auto-wait, set density
	out	DCTRL

;Set b=SPT 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 (SPT) from sector c into RAM at hl


	mvi	a,CCRDSEC	;read sector command
	out	DCMMD		;kick off read sector

	mvi	c,DDATA		;port address
	dw	INIR		;read sector

	pop	b		;sector count and sector number

;Wait for sector read to complete

	call	DCWAIT

;Check sector-read status and retry entire load on error

	in	DSTAT			;disk status
	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) mod 256

;next track, start with sector 1. All but track 0 are DD.

	dw	SET6D		;DCDDEN bit for double density
	mvi	a,CCSTEPI
	call	DOCMD

	mov	c,b		;b=0: start with sector 1
	mov	b,e		;reload sector counter with SPT
	db	JR,(SECLUP-$-1) mod 256
	
;***Subroutine********************************************
;Issue a command to the WD1793 and wait for it to complete
;On Entry:
;  a=WD1793 command
;On Exit:
;  a<3:0>=drive select bits
;*********************************************************
DOCMD:	out	DCMMD

;Fall into DCWAIT

;***Subroutine************************
;Wait for a WD1793 command to complete
;On Exit:
;  a<3:0>=drive select bits
;*************************************
DCWAIT:	in	DFLAG
	rar			;Test WD1793 INTRQ flag
	db	JRNC,(DCWAIT-$-1) mod 256
	ret
;**********************
;Version Message string
;**********************
BOTMSG:	db	BMLEN		;String length

	db ' MEBoot '
	db ((BOOTVER AND 0F00h)/256)+'0','.'
	db ((BOOTVER AND 0F0h)/16)+'0',(BOOTVER AND 0Fh) +'0'

BMLEN	equ	$-BOTMSG-1

	end	BOOT
