;***************************************************************
; Boot loader for Altair CP/M 2.2 on the 88-HDSK Hard Disk
;
; The hard disk bootloader ROM (HDBL) loads this program into
; memory at address zero. This program then reads CP/M
; into memory and jumps to the BIOS cold start vector.
;
; This code looks at the front panel switches to determine
; which hard disk platter to boot from. If A11 is down, boot
; is from the removable platter. If A11 is up, boot is from
; the fixed platter.
;
; Written by Mike Douglas including code borrowed from HDBL
;     written by Martin Eberhard
;
; Revision History
;   1.0    4/22/2014 	Initial Release
;   1.1    4/27/2014	Update for sector skewing
;   1.2    6/06/2014 	Change boot switch from A8 to A11
;   1.3	   4/15/2020	Move CP/M size equates into this file
;
;***************************************************************
MEMSIZE	equ	48		;memory size in K bytes

; CPM location equates

CCPLEN	equ	0800h		;CPM 2.2 fixed
BDOSLEN	equ	0E00h		;CPM 2.2 fixed
BIOSLEN	equ	0F00h		;BIOS length in bytes
CPMLEN	equ	CCPLEN+BDOSLEN+BIOSLEN	;total length in bytes

CCPBASE	equ	MEMSIZE*1024-CPMLEN
BIOSBAS	equ	CCPBASE+CCPLEN+BDOSLEN  ;base address of the BIOS

SECTCNT	equ	(CPMLEN/256)-1		;num sectors to load
STRTSEC	equ	2			;CP/M starts at sector 2

; 88-HDSK ports (The interface board is actually an 88-4PIO.)

CREADY	equ	0A0h		;IN: Ctlr ready for command (bit7)
CSTAT	equ	0A1h		;IN: error flags, reset CREADY
ACSTA	equ	0A2h		;IN: Command Ack (bit 7)
ACMD	equ	0A3h		;IN: reset Command Ack
				;OUT: Command high byte/initiate
CDSTA	equ	0A4h		;IN: data/stat availablr at CDATA
CDATA	equ	0A5h		;IN: Disk data or status from Ctlr
ADSTA	equ	0A6h		;IN: ADATA Port Available (bit 7)
ADATA	equ	0A7h		;OUT: Command low byte

; 88-HDSK ACMD:ADATA Commands

CSEEK	equ	00h		;Bits 15:12 = 0000b
				;Bits 11:10 = Unit #
				;Bits  9:0  = Cylinder #

CRDSEC	equ	30h		;Bits 15:12 = 0011b
				;Bits 11:10 = Unit #
				;Bits  9:8  = Buffer #
				;Bit   7:6  = Platter #
				;Bits    5  = Side #
				;Bits  4:0  = Sector #

CSIDE	equ	020h	  	;Side select for CRDSEC
CFPLTR	equ	0C0h	 	;platter mask for CRDSEC
CUNIT	equ	00Ch	  	;Unit mask for CSEEK & CRDSEC

CRDBUF	equ	50h		;Bits 15:12 = 0101b
				;Bits 11:10 = not used
				;Bits  9:8  = buffer #
				;Bits  7:0  = # bytes to transfer
				;(00 means 256)
HDSPT	equ	24		;Sectors per track
ERMASK	equ	7Fh		;all the actual error bits

; Misc equates

SENSESW	equ	0FFh		;front sense switch port
PLATMSK	equ	08h		;mask to get platter switch A11	
INITPIO	equ	0		;don't init the 4PIO since HDBL did

	org	0
;---------------------------------------------------------------------
;  Initialize hard disk interface (via 88-4PIO), then load CP/M
;     and jump to the cold start entry address.
;---------------------------------------------------------------------
start	lxi	sp,CCPBASE	;stack grows down from lowest cpm address
	di
if INITPIO			;already initialized by the HDBL
	xra	a
	out	0a0h		;Select port 0-A DDR 
	out	0a2h		;Select port 0-B DDR
	out	0a4h		;Select port 1-A DDR
	out	0a6h		;Select port 1-B DDR
	out	0a1h		;Port 0-A is an input port
	out	0a5h		;Port 1-A is an input port

	cma
	out	0a3h		;Port 0-B is an output port
	out	0a7h		;Port 1-B is an output port

	mvi	a,2Ch		;set up input port handshakes
	out	0a0h
	out	0a4h
	out	0a6h		;also output port 1-B handshakes

	mvi	a,24h		;set up port 0-B handshakes
	out	0a2h

	in	CSTAT		;clear Controller Ready bit
endif

; Read SECTCNT sectors from disk, starting at STRTSEC into memory starting
;    at CCPBASE. The CP/M image fits entirely in cylinder zero. A seek
;    to cylinder zero is performed just in case.

	lxi	h,(CSEEK shl 8)	;HL=seek command unit 0 cylinder 0
	call	hdCmd		;seek to cylinder 0

; determine boot drive to use based on A11 switch

	in	SENSESW		;read sense switches
	ani	PLATMSK		;isolate drive bit (A11)
	rlc			;put drive into bit 6
	rlc
	rlc
	mov	b,a		;B=platter bit in head field

; initialize for the boot loop

	lxi	h,hdSkew	;HL->skew table
	lxi	d,STRTSEC	;DE=starting sector number
	dad	d		;HL->starting sector in translation tbl
	mvi	c,SECTCNT	;C=number of sectors to load
	lxi	d,CCPBASE	;DE=destination address in memory

; bootLp - loop through and read SECTCNT sectors

bootLp	mov	a,m		;A=physical sector from hdTran
	ora	a		;set flags
	jp	samSide		;not 0ffh, still on same side
	lxi	h,hdSkew	;HL->logical sector 0 in skew table
	mov	a,b		;set 1's bit in head (bottom now)
	ori	CSIDE
	mov	b,a		;B updated with bottom head bit
	mov	a,m		;get physical setor
samSide	ora	b		;A=drive,surface,sector
	push	h		;save HL
	mvi	h,CRDSEC	;H=high byte of read sector command
	call	hdCmdA		;issue sector read (low byte in A)
	call	hdRdBuf		;read the buffer into (DE)
	pop	h		;restore HL
	inx	h		;next sector in translate table
	dcr	c		;c=remaining sector count
	jnz	bootLp

; Enter BIOS at cold start entry address

	jmp	BIOSBAS

;---------------------------------------------------------------------
; hdCmd - Issue the disk command in HL to the controller and then 
;    wait for the controller to complete the command.
;
; On Entry at hdCmd
;   hl = complete command
; On entry at hdCmdA
;   h = high byte of command
;   a = low byte of command
;
; On Exit:
;   Status returned in A, zero flag matches content of A.
;   All other registers preserved. The command is completed
;   and the controller is ready.
;---------------------------------------------------------------------
hdCmd	mov	a,l		;low byte of command 1st
hdCmdA	out	ADATA
	in	CSTAT		;reset CRDY flag just in case
	in	ACMD		;clear CMDACK in ACSTA
	mov	a,h		;A=command high byte
	out	ACMD		;issue command

hdWait	in	CREADY		;Is the controller done? 
	rlc			;look at msb=CRDY
	jnc	hdWait		;keep waiting

	in	CSTAT		;reset CRDY flag
	ani	ERMASK		;and get A=error code
	rz			;No errors: happy return

; error - turn on INTE light and hang here

	ei			;INTE is error indicator light
die	jmp	die		;die here, INTE lit

;---------------------------------------------------------------------
; hdRdBuf - Read buffer zero from the hard disk into memory pointed
;    to by DE. Clobbers A,L. DE updated as data moved.
;---------------------------------------------------------------------
hdRdBuf	in	CDATA		;reset CDA in CDSTA
	in	ACMD		;clear CMDACK in ACSTA
	xra	a		;A=0=transfer 256 bytes
	mov	l,a		;init L to zero as well
	out	ADATA
	mvi	a,CRDBUF	;issue Read Buffer zero cmd
	out	ACMD

; Wait for data to be ready

hdRdWt	in	CDSTA
	rlc			;msb=CDA
	jnc	hdRdWt

; Move 256 bytes of buffer data into memory at DE

hdRdLp	in	CDATA		;get a data byte
	stax	d		;write it to RAM
	inx	d		;next address
	dcr	l
	jnz	hdRdLp		;until done (L=0)
	ret

;-----------------------------------------------------------------------
; Hard disk skew table. The entire disk is written with a skew of 7 
;-----------------------------------------------------------------------
hdSkew	db	00,07,14,21,04,11,18,01,08,15,22,05
	db	12,19,02,09,16,23,06,13,20,03,10,17,0ffh

	end
