;	Ultimate 1MB/SIDE (DS1305) RTC driver for SpartaDOS 3.x
;	FJC, 2019

;.def SIDE

	org $3000
	
VGETTD	equ $FFC0
VSETTD	equ $FFC3
DATER	equ 13
TIMER	equ 16
COMTAB	equ $0A
MEMLO	equ $02E7

portb	equ $D301

iccom	equ $0342
icbadr	equ $0344
icptl	equ $0346
icpth	equ $0347
icblen	equ $0348
icaux1	equ $034A
icaux2	equ $034B
ciov	equ $e456

ptr1	equ $80
ptr2	equ $82
ptr3	equ $84

; bitmasks

OR0     equ     1
OR1     equ     2
OR2     equ     4
OR3     equ     8

.ifdef SIDE
RTCBase		equ $D5E2
.else
RTCBase		equ $D3E2
.endif

SR      equ     RTCBase	; Ultimate RTC
SW      equ     RTCBase
CE      equ     OR0
SCK     equ     OR1
MOSI    equ     OR2
MISO    equ     OR3


ldax .macro	" "	; load a,x pair
	.if :1 = '#'
		lda #< :2
		ldx #> :2
	.else
		lda :2
		ldx :2+1
	.endif
	.endm


; look for hardware, check DOS version, relocate driver and adjust TD vectors

.proc Start
	lda $700
	cmp #'S'
	bne notSparta
	lda $701
	cmp #$40
	bcs notSparta
	cmp #$30
	bcs isSparta
	
notSparta
	ldax #txtNotSparta
	jmp PutString
	
isSparta
	mva #$55 RTCBuf.Sec
	lda #$B0
	jsr SPI	; write $55 to NVRAM address $30
	lda #$30
	jsr SPI	; read it back
	lda RTCBuf.Sec
	cmp #$55
	beq GotHardware
	ldax #txtNoHardware
	jmp PutString
	
GotHardware
	lda #<DriverCode
	sec
	sbc MEMLO
	sta Diff
	lda #> DriverCode
	sbc MEMLO+1
	sta Diff+1
	
	ldx #.len[RelocTable]-2
Loop1
	lda RelocTable,x
	sta ptr1
	lda RelocTable+1,x
	sta ptr1+1
	
	ldy #0
	lda (ptr1),y
	sec
	sbc Diff
	sta (ptr1),y
	iny
	lda (ptr1),y
	sbc Diff+1
	sta (ptr1),y
	
	cpx #0
	beq Move
	dex
	dex
	jmp Loop1

Move
	ldy #0	; move driver code to bottom of free RAM
	mwa #DriverCode ptr1
	mwa MEMLO ptr2
	mva #<[EndDriverCode-DriverCode] ptr3
	mvx #>[EndDriverCode-DriverCode] ptr3+1
	beq @+
Loop2
	lda (ptr1),y
	sta (ptr2),y
	iny
	bne Loop2
	inc ptr1+1
	inc ptr2+1
	dex
	bne Loop2
@
	ldx ptr3	; lsb of size
	beq Done
Loop3
	lda (ptr1),y
	sta (ptr2),y
	iny
	dex
	bne Loop3
Done
	adw MEMLO #[EndDriverCode-DriverCode]
	
	dec portb
	mwa pGetTD VGETTD+1
	mwa pSetTD VSETTD+1
	inc portb
	
	ldax #txtInstalled	; fall into PutString
.endp


.proc PutString
	sta icbadr
	stx icbadr+1
	ldx #$ff
	sta icblen
	inx
	mva #9 iccom
	jmp ciov
.endp

pSetTD
.def :w0
	.word SetTD
pGetTD
.def :w1
	.word GetTD
	
.local RelocTable
	.word w0,w1,w2+1,w3+1,w4+1,w5+1,w6+1,w7+1,w8+1,w9+1
	.word w10+1,w11+1,w12+1,w13+1,w14+1,w15+1,w16+1,w17+1,w18+1,w19+1
	.word w20+1,w21+1,w22+1,w23+1,w24+1,w25+1,w26+1,w27+1,w28+1,w29+1
	.word w30+1,w31+1,w32+1,w33+1,w34+1,w35+1,w36+1,w37+1,w38+1,w39+1
	.word w40+1,w41+1,w42+1,w43+1,w44+1,w45+1,w46+1,w47+1,w48+1,w49+1
	.word w50+1,w51+1,w52+1,w53+1;,w54+1,w55+1,w56+1,w57+1,w58+1,w59+1
.endl


txtNotSparta
	.byte 'Requires SpartaDOS 3.x!',$9B
txtNoHardware
	.byte 'DS1305 not found!',$9B
txtInstalled
.ifdef SIDE
	.byte 'SIDE clock installed',$9B
.else
	.byte 'Ultimate clock installed',$9B
.endif

Diff	.ds 2

;	Actual resident driver code starts here

DriverCode

;
;	Read date/time
;

.proc ReadTD
	lda #0	; read date and time into SPI Buffer
	beq SPI
.endp

;
;	Write date/time
;

.proc WriteTD
	lda #$80
.endp


//
//	RTC transfer
//	Pass 0 for read, 128 for write (address should always be 0)
//   

.proc SPI
.def :w2
	sta RTCBuf.Adr	; first byte is address & mode 

	lda #0 		; ready Y as index into string
	sta SW		; reset SPI
	tay
	lda #CE		; CE on, start transfer
	sta SW
        
ByteLoop
.def :w3
	lda RTCBuf.Adr,y	; get a byte to write
.def :w4
	sta Temp1		; save byte

	ldx #8			; eight bits...
BitLoop
	lda #CE
.def :w5
	asl Temp1	; put output bit to C
	scc
	ora #MOSI	; set line if bit is set

	pha		; save output setting

	ora #SCK	; set clock cycle
	sta SW		; go

	clc
	lda SR		; get input
	and #MISO	; is input ?
	seq
	sec
.def :w6
	rol Temp2	; fetch bit

	pla
	sta SW		; restore (finish clock cycle)

	dex
	bne BitLoop	; next bit

.def :w7
	lda Temp2
.def :w8
	sta RTCBuf.Adr,y	; save response
	iny
	cpy #8		; all bytes transferred ?
	bcc ByteLoop	; next byte
        
	lda #0		; turn off, stop transfer
	sta SW
	rts
.endp
	
	
	
	
	
.proc SetTD
.def :w9
	jsr ReadTD	;read clock to ensure RTC buffer is up to date

	ldx #5	; copy DATER and TIMER into buffer
	ldy #DATER+5
@
	lda (comtab),y
.def :w10
	sta bufs,x
	dey
	dex
	bpl @-

	ldx #2	; get decimal hour
.def :w11
	jsr GetBCDVal	; convert to BCD
.def :w12
	sta RTCBuf.Hrs
.def :w13
	jsr GetBCDVal	; get minutes
.def :w14
	sta RTCBuf.Min
.def :w15
	jsr GetBCDVal	; get seconds
.def :w16
	sta RTCBuf.Sec

.def :w17
	lda bufs+2	; get year
	sec		; and figure out day of the week
	sbc #84		; use 1984 as base year
.def :w18
	sta Temp2
	lsr @		; div by 4 to get # leap years
	lsr @
	clc		; add in leap years
.def :w19
	adc Temp2
.def :w20
	adc Bufs	; add in # days (date)
.def :w21
	ldx Bufs+1	; get month as index
.def :w22
	adc MonOff-1,x	; get mod 7 count in desired month
.def :w23
	sta Temp2

.def :w24
	lda Bufs+2	; check if >= Mar 1 of a leap year
	and #3
	bne ycrdx
	cpx #3
	bcc nocrdx

ycrdx
.def :w25
	inc Temp2		; add 1 unless leap year and < Mar 1

nocrdx
.def :w26
	lda Temp2		; take mod 7
resub
	cmp #7
	bcc gday
	sbc #7
	bcs resub

gday
	adc #1		; we want 1-7 for week index
.def :w27
	jsr ConvertHexToBCD	; convert to BCD
.def :w28
	sta RTCBuf.Day	; set day of week

	ldx #2		; now copy Bufs as BCD
@
.def :w29
	lda Bufs,x
.def :w30
	jsr ConvertHexToBCD
.def :w31
	sta RTCBuf.Date,x
	dex
	bpl @-
.def :w32
	jsr WriteTD
	clc
	rts
.endp



	
.proc GetTD
.def :w33
	jsr ReadTD
	ldx #2
	ldy #0
@
.def :w34
	lda RTCBuf.Sec,x
.def :w35
	and RTCMask,x
.def :w36
	jsr ConvertBCDToHex
.def :w37
	sta Bufs+3,y
	iny
	dex
	bpl @-
	
	ldx #2		; read date
@
.def :w38
	lda RTCBuf+5,x
.def :w39
	jsr ConvertBCDToHex
.def :w40
	sta Bufs,x
	dex
	bpl @-
	
	ldx #5	; copy DATER and TIMER into buffer
	ldy #DATER+5
@
.def :w41
	lda bufs,x
	sta (comtab),y
	dey
	dex
	bpl @-

	clc
	rts
RTCMask
	.byte $FF,$FF,$3F
.endp



//
//	Convert BCD to hex
//	Pass value in A, hex returned in A
//

.proc ConvertBCDToHex
.def :w42
	sty Temp2+1
	tay
	lsr	; get tens digit
	lsr
	lsr
	lsr
.def :w43
	sta Temp2
	asl
	asl	; * 4
.def :w44
	adc Temp2	; * 5
	asl	; * 10
.def :w45
	sta Temp2
	tya	; get units
	and #$0f
.def :w46
	adc Temp2
.def :w47
	ldy Temp2+1
	rts
.endp
	


.proc GetBCDVal
	inx
.def :w48
	lda Bufs,x	; fall into Hex to BCD conversion
.endp

//
//	Convert Hex to BCD
//	Pass value in A, BCD returned in A
//

.proc ConvertHexToBCD
	sed
.def :w49
	sta Temp2	; input value
	lda #0
.def :w50
	sta Temp2+1	; output value
	ldy #8
@
.def :w51
	asl Temp2	; shift input value left one bit
.def :w52
	adc Temp2+1
.def :w53
	sta Temp2+1
	dey
	bne @-
	cld
	rts
.endp

monoff
	.byte 0,3,3,6,1,4	; Mod 7 division for day of week calc
    .byte 6,2,5,0,3,5	; (days-in-month - 28) with carry over to next month (also mod 7)
	
Temp1	.ds 1
Temp2	.ds 2
bufs	.ds 8

	.local RTCBuf
Adr		.ds 1	; always 0
Sec		.ds 1
Min		.ds 1
Hrs		.ds 1
Day		.ds 1
Date	.ds 1
Month	.ds 1
Year	.ds 1
	.endl
	
EndDriverCode


	run Start
