;****************************************************************************
;
;  CP/M cold boot loader for CP/M 2.2 on the Polymorphic single density
;    controller
;
;    This code is loaded from track 0 sector 0 into RAM by the BOOTMOD
;    PROM. This loader, in turn, loads the CP/M image into high RAM.
;
;    Version History
;
;    Ver     Date	  Description
;    ---   ---------	---------------------
;    1.0    02/13/18	Original, Mike Douglas     
;
;--------------------------------------------------------------------------
;
;    To patch changes made to this loader into a CP/M image saved from
;    MOVCPM (e.g., CPMxx.COM), use the following commands:
;
;	A>DDT CPMxx.COM
;	-IBOOT.HEX
;	-R800
;	-G0	    	(Go zero, not "oh")
;	A>SYSGEN
;
;****************************************************************************

;------------------------
; CP/M size & locations
;------------------------
MEMSIZE	equ	48		;set memory size in K bytes
BIOSLEN	equ	1100h		;length of the BIOS
CCPLEN	equ	0800h		;CPM 2.2 fixed length
BDOSLEN	equ	0E00h		;CPM 2.2 fixed length

CCPBASE	equ	MEMSIZE*1024 - BIOSLEN - BDOSLEN - CCPLEN
BIOSBAS	equ	(CCPBASE+CCPLEN+BDOSLEN) ;base address of this BIOS

;------------------------
; Disk information
;------------------------
NUMTRK	equ	35		;number of tracks on the disk
NUMSEC	equ	10		;physical sectors per track
SECLEN	equ	256		;physical sector length
SECCNT	equ	NUMSEC*3-1	;three CP/M boot tracks less boot sector

;-----------------------------
; Poly-88 single density FDC 
;-----------------------------
; Polymorphic Sector Format
;
; 10 bytes 00h
;  1 byte  sector number with msb set
;  1 byte  track number
;  256 bytes data
;  2 bytes 16-bit checksum
;-----------------------------
SYNC	equ	0E6h	;disk sync character

DCBASE	equ	20h	;disk controller base I/O address

DSKSCTL	equ	DCBASE+00h	;6852 Status/Control Register 1
			;6852 status bits:
RDA	equ	01h	;RX data available
TDRA	equ	02h	;TX data register available
DCDN	equ	04h	;active-low DCD
CTSN	equ	08h	;active-low CTS
TUF	equ	10h	;transmit underflow
RXOVRN	equ	20h	;receiver overrun
PE	equ	40h	;parity error
IRQ	equ	80h	;interrupt request

			;Control Register 1 Commands
CR2SEL	equ	0Bh	;select Control Reg 2, reset things
CR3SEL	equ	40h	;select Control Reg 3
SYNSEL	equ	80h	;select sync-code register
IDLE68	equ	0C0h	;no command, select data register
RSET68	equ	0CBh	;reset things, select data register

			;Control Register 2 Commands
CR2RES	equ	18h	;8 bits, no parity (see data sheet)

			;Control Register 2 Commands
CR3RES	equ	0Ch	;clear status, internal sync,
			;..2-sync chr mode

DSKSDAT	equ	DCBASE+01h	;6852 Data/Control

DSKSEL	equ	DCBASE+08h	;8255 Port A (output), drive select
			;<1:0> = Unit number
			;0 means no unit selected

DSKSEC	equ	DCBASE+09h	;8255 Port B (input), sector register
SECMASK	equ	0Fh	;bits 3:0 are the current sector
TRACK0	equ	10h	;bit 4 is track 0 detect
WPROT	equ	20h	;bit 5 is write protect

DSKCTL	equ	DCBASE+0Ah	;8255 Port C write label
DSKSTAT	equ	DCBASE+0Ah	;8255 Port C read label
			;<0> = Sector pulse interrupt
			;      (Mode 1 interrupt output)
			;<1> = Sector pulse
			;      (Mode 1 input buffer full)
			;<2> = Sector int enable (internal write)
			;      (Mode 1 data strobe input)
			;<3> = Motor On
			;<4> = Enable Write
			;<5> = Enable Read
			;<6> = Direction Select
			;<7> = Step

SIMASK	equ	01h	;sector interrupt mask
SPMASK	equ	02h	;sector pulse mask
MOTMASK	equ	08h	;motor on/off mask

DSKCMD	equ	DCBASE+0Bh	;8255 Control 
			;Disk control commands:
SINTOFF	equ	04h	;Port C:2 (sector interrupt) off
SINTON	equ	05h	;Port C:2 (sector interrupt) on
MOTOFF	equ	06h	;Port C:3 (spindle motor) off
MOTON	equ	07h	;Port C:3 (spindle motor) on
WRDIS	equ	08h	;Port C:4 (enable write) off
WREN	equ	09h	;Port C:4 (enable write) on
RDDIS	equ	0Ah	;Port C:5 (enable read) off
RDEN	equ	0Bh	;Port C:5 (enable read) on
STPOUT	equ	0Ch	;Port C:6 low  (step out)
STPIN	equ	0Dh	;Port C:6 high (step in)
UNSTEP	equ	0Eh	;Port C:7 low (cancel step pulse)
STEP	equ	0Fh	;Port C:7 high (step)
INIT82	equ	86h	;initialize 8255:
			;  Group A: Mode 0, output
			;  Group B: Mode 1, input
			;  Port C set up for output on all bits
			;    but some bits forced to input for
			;    mode 1 handshake signals
	
;--------------------------------------------------------------------------
;  Entry point. It is assumed the boot PROM just loaded this code so
;    a valid stack pointer is set up, interrupts are disabled, the
;    drive is selected, on track zero, with the motor running, up to
;    speed, and about to reach the sector pulse for sector one.
;--------------------------------------------------------------------------
	org	0100h

start	mvi	a,STPIN		;step direction is in
	out	DSKCMD

	xra	a		;A=0
	sta	trkNum		;track zero
	sta	strtSec		;starting sector-1 on track zero
	lxi	d,ccpBase	;DE->load address of track zero data

	call	readSec		;read rest of track 0 secs 1-9
	call	stpRead		;step and read track secs 0-9
	call	stpRead		;step and read track secs 0-9

	jmp	BIOSBAS		;enter at BIOS cold start entry point

;---------------------------------------------------------------------------
;  stpRead - Issue a track step inward then read the next track
;---------------------------------------------------------------------------
stpRead	mvi	a,STEP		;step in to next track
	out	DSKCMD
	dcr	a		;clear step bit
	out	DSKCMD

	mvi	c,3		;C=three sector pulses for head settle

; rdTrack - Read an entire track starting at strtSec+1

rdTrack	lda	trkNum		;A=track number we're on
	lxi	d,ccpBase	;DE->load address of track zero data
	ora	a		;on track zero?
	jz	havAddr		;yes, have the load address

	lxi	d,ccpBase+(NUMSEC-1)*SECLEN  ;DE->load address of track 1 data
	dcr	a		;on track one?
	jz	havAddr		;yes, have the load address

	lxi	d,ccpBase+(2*NUMSEC-1)*SECLEN  ;DE->load address of track 2 data

havAddr	in	DSKSEC		;clear previous sector interrupt
	lxi	h,strtSec	;HL->starting sector minus 1
	mov	b,m		;B=sector to hunt for (start-1)

secWait	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	secWait		;no, keep waiting

	in	DSKSEC		;clear sector interrupt
	dcr	c		;decrement sector pulse counter
	jnz	secWait

	inr	c		;C=1

	ani	SECMASK		;A=sector number alone
	cmp	b		;B has starting sector minus 1
	jnz	secWait		;wait for starting sector

; readSec - Read a sector knowing the next sector pulse to come is the
;    the sector we want to read.

readSec	mvi	a,RSET68	;reset sync, Tx, Rx
	out	DSKSCTL		;6852 Control Register 1
	mvi	a,IDLE68	;..and release them
	out	DSKSCTL		;6852 Control Register 1 

	mvi	a,RDEN		;enable read on the controller
	out	DSKCMD

	lxi	h,0		;HL=initial checksum
	di			;disable interrupts

; Wait for next sector pulse

rdSecWt	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	rdSecWt		;no, keep waiting

; Wait up to 1.75ms for start of data

	mvi	c,83		;C=timeout counter (83*39 cycles)

rdWait1	dcr	c		;(5) timeout?
	jz	rdError		;(10) yes, preamble error abort

	in	DSKSCTL		;(10) A=6852 status
	rrc			;(4) data byte present?
	jnc	rdWait1		;(10) Wait for data

; Read and verify sector and track from disk

	in	DSKSDAT		;read disk data 1st byte (sector)
	mov	b,a		;B=sector
	in	DSKSDAT		;read disk data 2nd byte (track)
	mov	c,a		;C=track

	in	DSKSEC		;A=sector from controller
	ani	SECMASK		;get the sector bits alone
	ori	80h		;set the flag bit
	cmp	b		;sector match?
	jnz	rdError		;no, exit
	
	lda	trkNum		;A=expected track
	cmp	c		;match the track we found?
	jnz	rdError		;no, exit
	
	mvi	a,128		;128 pairs of bytes/sector

; rdLoop - Read sector data into memory at (DE)

rdLoop	push	psw		;save loop counter on stack

rdWait2	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	rdWait2		;no, keep waiting
	
	in	DSKSDAT		;read disk data even byte
	mov	c,a		;C=even byte
	stax	d		;write to buffer
	inx	d
	
	in	DSKSDAT		;read disk data odd byte
	mov	b,a		;B=odd byte
	stax	d		;write to buffer
	inx	d

	dad	b		;update 16-bit checksum

	pop	psw		;A=byte pair count
	dcr	a		;decrement loop count
	jnz	rdLoop		;loop through sector data

; Read and verify 1's complement 16-bit checksum from disk

rdWait3	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	rdWait3		;no, keep waiting

	in	DSKSDAT		;read low byte of checksum
	mov	c,a		;C=checksum low byte
	in	DSKSDAT		;read high byte of checksum
	mov	b,a		;BC=1's complement checksum from disk
	
	dad	b		;add to positive computed checksum
	inx	h		;plus 1 should give zero

	mov	a,h		;test for HL=0
	ora	l		;test A and L for zero
	jnz	rdError		;checksum doesn't match

	mvi	a,RDDIS		;disable read
	out	DSKCMD

	in	DSKSEC		;A=sector we just read
	ani	SECMASK		;A=sector number alone
	cpi	NUMSEC-1	;all sectors read?
	jnz	readSec		;no, read another

	ei			;allow interrupts

	lxi	h,trkNum	;increment to next track
	inr	m

	mvi	a,NUMSEC-1	;starting sector-1 for tracks 1 and 2
	sta	strtSec

	ret			;track done, exit

; rdError - Read error. Disable the read hardware and attempt to re-read 
;    the entire track we're on.

rdError	mvi	a,RDDIS		;disable read
	out	DSKCMD
	
	ei			;enable interrupts
	mvi	c,1		;C=number of sector pulses to wait + 1
	jmp	rdTrack		;attempt to re-read the entire track

;-------------------------------------------------------------------------
; Data Area
;-------------------------------------------------------------------------
trkNum	ds	1		;track number we're on
strtSec	ds	1		;starting sector-1 for the current track
	end















