#include "memmap.i65"

; SPC Player
; SPC700 transfer and IO routines by Shay Green <gblargg@gmail.com>

spcplayer:
	php
	sep #$30 : .as : .xs

	ldx #$0a	; Check if SPC header is present
-
	lda @SPC_HEADER,x
	cmp @text_spcid,x
	beq +
	jmp spc_exit
+
	dey
	bne -

	rep #$10 : .xl	; Now draw lots of stuff

	stz bar_wl
	dec bar_wl
	stz bar_xl
	dec bar_xl
	stz bar_yl
	dec bar_yl
	jsr backup_screen

	lda #^text_spcplay	; Loading window
	sta window_tbank
	ldx #!text_spcplay
	stx window_taddr
	lda @spcplay_win_x
	sta window_x
	lda @spcplay_win_y
	sta window_y
	lda @spcplay_win_w
	sta window_w
	lda @spcplay_win_h
	sta window_h
	jsr draw_window

	lda #^text_spcload	; Loading text
	ldx #!text_spcload
	sta print_bank
	stx print_src
	stz print_pal
	lda #29
	sta print_count
	lda #17
	sta print_y
	lda #17
	sta print_x
	jsr hiprint

	stz isr_done
-
	lda isr_done		; Wait until text is being printed...
	beq -

	jsr spc700_load		; Load SPC into SPC700

	lda #^text_spcplay
	sta window_tbank
	ldx #!text_spcplay
	stx window_taddr
	lda @spcstart_win_x
	sta window_x
	lda @spcstart_win_y
	sta window_y
	lda @spcstart_win_w
	sta window_w
	lda @spcstart_win_h
	sta window_h
	jsr draw_window

	lda #^text_spcstarta
	ldx #!text_spcstarta
	sta print_bank
	stx print_src
	lda #$01
	sta print_pal
	lda #30
	sta print_count
	lda #15
	sta print_y
	lda #17
	sta print_x
	jsr hiprint

	lda #^text_spcstartb
	ldx #!text_spcstartb
	sta print_bank
	stx print_src
	lda #$01
	sta print_pal
	lda #07
	sta print_count
	lda #17
	sta print_y
	lda #12
	sta print_x
	jsr hiprint

	lda #$fe
	ldx #$004e
	sta print_bank
	stx print_src
	stz print_pal
	lda #32
	sta print_count
	lda #17
	sta print_y
	lda #20
	sta print_x
	jsr hiprint

	lda #^text_spcstartc
	ldx #!text_spcstartc
	sta print_bank
	stx print_src
	lda #$01
	sta print_pal
	lda #07
	sta print_count
	lda #18
	sta print_y
	lda #12
	sta print_x
	jsr hiprint

	lda #$fe
	ldx #$002e
	sta print_bank
	stx print_src
	stz print_pal
	lda #32
	sta print_count
	lda #18
	sta print_y
	lda #20
	sta print_x
	jsr hiprint

	lda #^text_spcstartd
	ldx #!text_spcstartd
	sta print_bank
	stx print_src
	lda #$01
	sta print_pal
	lda #07
	sta print_count
	lda #19
	sta print_y
	lda #12
	sta print_x
	jsr hiprint

	lda #$fe
	ldx #$00b0
        sta longptr+2
	sta print_bank
        stx longptr
        ldy #$00
        lda [longptr], y
        cmp #$41
        bpl +
        inx
+	stx print_src
	stz print_pal
	lda #32
	sta print_count
	lda #19
	sta print_y
	lda #20
	sta print_x
	jsr hiprint

spc_playloop:
	lda isr_done	; SPC player loop
	lsr
	bcc spc_playloop
	jsr printtime
	stz isr_done

	jsr read_pad
	lda #$80
	and pad1trig+1
	bne spc_key_b
	bra spc_playloop

spc_key_b:
	rep #$20 : .al
	tsc
	sta saved_sp	; Save SP for later re-entry
	lda #$fa50	; Write reset signature
	sta @warm_signature
	sep #$20 : .as

	sei		; Blank screen & issue CMD_RESET command to Microcontroller...
	stz $2100	; ...this is required, because there is no other way to stop S-SMP & S-DSP
	lda #CMD_RESET
	sta @MCU_CMD
-
	bra -		; At this point, the SNES waits for an external reset from the Microcontroller

spc_exit:		; Return from player in case of wrong SPC file data
	plp
	rts

;---------------------------------------
spc700_load:
	php
	sep #$20 : .as
	rep #$10 : .xl

	sei			; Disable NMI & IRQ
	stz $4200		; The SPC player code is really timing sensitive ;)
	jsr upload_dsp_regs	; Upload S-DSP registers
	jsr upload_high_ram	; Upload 63.5K of SPC700 ram
	jsr upload_low_ram	; Upload rest of ram
	jsr restore_final	; Restore SPC700 state & start execution

	lda #$81		; VBlank NMI + Auto Joypad Read
	sta $4200		; enable V-BLANK NMI
	cli
	plp
	rts
;---------------------------------------
; Uploads DSP registers and some other setup code
upload_dsp_regs:

; ---- Begin upload

	ldy #$0002
	jsr spc_begin_upload

; ---- Upload loader

	ldx #$0000
-
	lda @loader,x
	jsr spc_upload_byte
	inx
	cpy #31		; size of loader
	bne -

; ---- Upload SP, PC & PSW

	lda @SPC_HEADER+43
	jsr spc_upload_byte
	lda @SPC_HEADER+38
	jsr spc_upload_byte
	lda @SPC_HEADER+37
	jsr spc_upload_byte
	lda @SPC_HEADER+42
	jsr spc_upload_byte

; ---- Upload DSP registers

	ldx #$0000
-
	lda @SPC_DSP_REGS,x
	jsr spc_upload_byte
	inx
	cpx #128
	bne -

; --- Upload fixed values for $F1-$F3

	ldy #$00F1
	jsr spc_next_upload

	lda #$80        ; stop timers
	jsr spc_upload_byte
	lda #$6c        ; get dspaddr set for later
	jsr spc_upload_byte
	lda #$60
	jsr spc_upload_byte

; ---- Upload $f8-$1ff

	ldy #$00F8
	jsr spc_next_upload

	ldx #$00F8
-
	lda @SPC_DATA,x
	jsr spc_upload_byte
	inx
	cpx #$200
	bne -

; ---- Execute loader

	ldy #$0002
	jsr spc_execute
	rts
;---------------------------------------
upload_high_ram:

	ldy #$0002
	jsr spc_begin_upload

; ---- Upload transfer routine

	ldx #$0000
-
	lda @transfer,x
	jsr spc_upload_byte
	inx
	cpy #43		; size of transfer routine
	bne -

	ldx #$023f	; prepare transfer address

; ---- Execute transfer routine

	ldy #$0002
	sty APUIO2
	stz APUIO1
	lda APUIO0
	inc
	inc
	sta APUIO0
; Wait for acknowledgement
-
	cmp APUIO0
	bne -

; ---- Burst transfer of 63.5K using custom routine

outer_transfer_loop:
	ldy #$003f		; 3
inner_transfer_loop:
	lda @SPC_DATA,x		; 5 |
	sta APUIO0		; 4 |
	lda @SPC_DATA+$40,x	; 5 |
	sta APUIO1		; 4 |
	lda @SPC_DATA+$80,x	; 5 |
	sta APUIO2		; 4 |
	lda @SPC_DATA+$C0,x	; 5 |
	sta APUIO3		; 4 |
	tya			; 2 >> 38 cycles
-
	cmp APUIO3		; 4 |
	bne -			; 3 |
	dex			; 2 |
	dey			; 2 |
	bpl inner_transfer_loop	; 3 >> 14 cycles

	rep #$21 : .al		; 3 |
	txa			; 2 |
	adc #$140		; 3 |
	tax			; 2 |
	sep #$20 : .as		; 3 |
	cpx #$003f		; 3 |
	bne outer_transfer_loop	; 3 >> 19 cycles

	rts

;---------------------------------------
upload_low_ram:

; ---- Upload $0002-$00EF using IPL

	ldy #$0002
	jsr spc_begin_upload

	ldx #$0002
-
	lda @SPC_DATA,x
	jsr spc_upload_byte
	inx
	cpx #$00F0
	bne -
	rts
;---------------------------------------
; Executes final restoration code
restore_final:
	jsr start_exec_io ; prepare execution from I/O registers

	stz $420d	; SPC700 I/O code requires SLOW timing

; ---- Restore first two bytes of RAM

	lda @SPC_DATA
	xba
	lda #$e8	; MOV A,#@SPC_DATA
	tax
	jsr exec_instr
	ldx #$00C4	; MOV $00,A
	jsr exec_instr

	lda @SPC_DATA+1
	xba
	lda #$e8	; MOV A,#@SPC_DATA+1
	tax
	jsr exec_instr
	ldx #$01C4	; MOV $01,A
	jsr exec_instr

; ---- Restore SP

	lda @SPC_HEADER+43
	sec
	sbc #3
	xba
	lda #$cd	; MOV X,#@SPC_HEADER+43
	tax
	jsr exec_instr
	ldx #$bd	; MOV SP,X
	jsr exec_instr

; ---- Restore X

	lda @SPC_HEADER+40
	xba
	lda #$cd	; MOV X,#@SPC_HEADER+40
	tax
	jsr exec_instr

; ---- Restore Y

	lda @SPC_HEADER+41
	xba
	lda #$8d	; MOV Y,#@SPC_HEADER+41
	tax
	jsr exec_instr

; ---- Restore DSP FLG register

	lda @SPC_DSP_REGS+$6c
	xba
	lda #$e8	; MOV A,#@SPC_DSP_REGS+$6c
	tax
	jsr exec_instr
	ldx #$f3C4	; MOV $f3,A -> $f2 has been set-up before by SPC700 loader
	jsr exec_instr

; ---- Restore DSP KON register

	lda #$4C
	xba
	lda #$e8	; MOV A,#$4c
	tax
	jsr exec_instr
	ldx #$f2C4	; MOV $f2,A
	jsr exec_instr
	lda @SPC_DSP_REGS+$4C
	xba
	lda #$e8	; MOV A,#@SPC_DSP_REGS+$4c
	tax
	jsr exec_instr
	ldx #$f3C4	; MOV $f3,A
	jsr exec_instr

; ---- Restore DSP register address

 	lda @SPC_DATA+$F2
	xba
	lda #$e8	; MOV A,#@SPC_DATA+$F2
	tax
	jsr exec_instr
	ldx #$f2C4	; MOV dest,A
	jsr exec_instr

; ---- Restore CONTROL register

	lda @SPC_DATA+$F1
	and #$CF	; don't clear input ports
	xba
	lda #$e8	; MOV A,#@SPC_DATA+$F1
	tax
	jsr exec_instr
	ldx #$f1C4  	; MOV $F1,A
	jsr exec_instr

;---- Restore A

	lda @SPC_HEADER+39
	xba
	lda #$e8	; MOV A,#@SPC_HEADER+39
	tax
	jsr exec_instr

;---- Restore PSW and PC

	ldx #$7F00	; NOP; RTI
	stx APUIO0
	lda #$FC	; Patch loop to execute instruction just written
	sta APUIO3

;---- restore IO ports $f4 - $f7

	rep #$20 : .al
	lda @SPC_DATA+$F4
	tax
	lda @SPC_DATA+$F6
	sta APUIO2
	stx APUIO0	; last to avoid overwriting RETI before run
	sep #$20 : .as

	lda #$01
	sta $420d	; restore FAST CPU operation
	rts
;---------------------------------------
spc_begin_upload:

	sty APUIO2	; Set address

	ldy #$BBAA	; Wait for SPC
-
	cpy APUIO0
	bne -

	lda #$CC	; Send acknowledgement
	sta APUIO1
	sta APUIO0

-       		; Wait for acknowledgement
	cmp APUIO0
	bne -

	ldy #0		; Initialize index
	rts
;---------------------------------------
spc_upload_byte:
	sta APUIO1

	tya		; Signal it's ready
	sta APUIO0
-     			; Wait for acknowledgement
	cmp APUIO0
	bne -

	iny

	rts
;---------------------------------------
spc_next_upload:
	sty APUIO2

; Send command
; Special case operation has been fully tested.
	lda APUIO0
	inc
	inc
	bne +
	inc
+
	sta APUIO1
	sta APUIO0

; Wait for acknowledgement
-
	cmp APUIO0
	bne -

	ldy #0
	rts
;---------------------------------------
spc_execute:
	sty APUIO2

	stz APUIO1

	lda APUIO0
	inc
	inc
	sta APUIO0

; Wait for acknowledgement
-
	cmp APUIO0
	bne -

	rts
;---------------------------------------
start_exec_io:
; Set execution address
	ldx #$00F5
	stx APUIO2

	stz APUIO1      ; NOP
	ldx #$FE2F      ; BRA *-2

; Signal to SPC that we're ready
	lda APUIO0
	inc
	inc
	sta APUIO0

; Wait for acknowledgement
-
	cmp APUIO0
	bne -

; Quickly write branch
	stx APUIO2

	rts
;---------------------------------------
exec_instr:
; Replace instruction
	stx APUIO0
	lda #$FC
	sta APUIO3      ; 30

        ; SPC BRA loop takes 4 cycles, so it reads
        ; the branch offset every 4 SPC cycles (84 master).
        ; We must handle the case where it read just before
        ; the write above, and when it reads just after it.
        ; If it reads just after, we have at least 7 SPC
        ; cycles (147 master) to change restore the branch
        ; offset.

        ; 48 minimum, 90 maximum
        ora #0
        ora #0
        ora #0
        nop
        nop
        nop

        ; 66 delay, about the middle of the above limits
	phd ;4
	pld ;5

        ; Give plenty of extra time if single execution
        ; isn't needed, as this avoids such tight timing
        ; requirements.

;	phd ;4
;	pld ;5
;	phd ;4
;	pld ;5

        ; Patch loop to skip first two bytes
	lda #$FE        ; 16
	sta APUIO3      ; 30

        ; 38 minimum (assuming 66 delay above)
	phd ; 4
	pld ; 5

        ; Give plenty of extra time if single execution
        ; isn't needed, as this avoids such tight timing
        ; requirements.

	phd
	pld
	phd
	pld
	rts
