;
; Copyright (c) 2018 Yosuke Sugahara. All rights reserved.
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions
; are met:
; 1. Redistributions of source code must retain the above copyright
;    notice, this list of conditions and the following disclaimer.
; 2. Redistributions in binary form must reproduce the above copyright
;    notice, this list of conditions and the following disclaimer in the
;    documentation and/or other materials provided with the distribution.
;
; THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
; IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
; BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
; AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
; SUCH DAMAGE.
;
;
; LUNA XP multiplexed device firmware
;
; used language:
;  zasm 4.1
;  http://k1.spdns.de/Develop/Projects/zasm
;
; XP memory map
;
; type : SH, PR, IN, NC
;     SH: host shared memory, 64kB, PA 00000 - 0FFFF
;     PR: private memory, 32kB, PA 28000-2FFFF
;     IN: HD647180 internal 512 bytes memory
;     NC: not connected (00 or FF or image readable, maybe)
;
; start end type desc
;  0000 00FF SH RESET/RST etc.
;  0100 01FF SH shared variables
;  0200 0FFF SH resident program
;  1000 7FFF SH PAM/PCM buffer 28K
;  8000 8FFF SH PSG buffer 4K
;  9000 9FFF SH LPR buffer 4K
;  A000 DFFF SH FDC buffer 16K
;  E000 EFFF PR program/stack
;  F000 FDFF NC bus error (00 or FF)
;  FE00 FFDF IN PAM player
;  FFE0 FFFF IN interrupt vector
;
; shared variable area
;  0100    XPBUS
;  0110    TIME
;  0120    PAM
;  0130    PCM
;  0140    PSG
;  0150    SPK
;  0160    LPR
;  0170    FDC
;  0180    SIO0
;  0190    SIO1
; device ID = bit 7-4
;
; XP internal device usage
;  PRT0  device dispatcher/TIME
;  PRT1  PCM
;  PT2   unused
;  ASCI0 SIO0
;  ASCI1 SIO1	本体表記の関係で、入れ替える?
;
; READY-CMD-RESULT-RUN プロトコル
; XP 視点
; READY
;   コマンドを受け付けできるとき != 0
;   受付できないとき 0
; CMD
;   ホスト側が送信してくるコマンド
;   コマンドなしは 0
;   XP がコマンドを受け付けると、READY=0 CMD=0 の順で、XP が 0 にする
; RESULT
;   コマンド実行結果。
;   RESULT=x READY=1 の順で書き込む。
;   ハードリセット、またはホストが 0 クリアする。
; RUN
;   コマンド実行中は != 0
;   実行していないときは 0
;   実行完了時、RESULT=x RUN=0 READY=1 の順で書き込む。
;
; 通常シーケンス
;  READY を上げる
;  CMD の立ち上がりを待つ
;  READY を下げる
;  RUN を上げる
;  CMD を下げる
;  実行
;  RESULT を書く
;  RUN を下げる
;  READY を上げる
;
; ホスト視点
; 実行完了を待つとき
;  while (READY == 0);	// 受付可能待ち
;  RESULT=0;		// 結果クリア
;  CMD=x;		// コマンド送信
;  while (RESULT == 0);	// 実行完了待ち
;  if (RESULT==ERROR) error();	// エラー確認
;

;
; XPBUS
;  +0.b READY
;  +1.b CMD
;  +2.b RESULT
;  +3.b RUN
;
;  +4.b STAT_RESET
;        ファームウェア転送で 0　にされる。
;        リセット位置の実行のたびに +1
;        すなわち何もなければ 1 になっている。
;  +5.3 align
;  +8.w PRT0_TIMER
;        ==256(1200Hz)
;  +A.w INTR1_DEV
;        bitmap of INTR1 device ID
;  +C.w INTR5_DEV
;        bitmap of INTR5 device ID
;
; TIME
;  +0.b READY
;  +1.b CMD
;  +2.b RESULT
;  +3.b RUN
;
;  +4.w TIMECOUNTER
;
; PAM
;  +0.b READY
;  +1.b CMD
;  +2.b RESULT
;  +3.b RUN
;
;  +4.b ENC
;        エンコードフォーマット識別子。
;  +5.b REPT
;        REPT 回数。
;  +6.w CYCLE_CLK
;        基準クロック数
;        クエリで返される。
;  +8.b REPT_CLK
;        1 REPT あたりのクロック数。
;        クエリで返される。
;  +9.b REPT_MAX
;        REPT に設定できる最大値。
;        クエリで返される。
;
;  +E.w STAT_PTR
;
; PCM
;  +0.b READY
;  +1.b CMD
;  +2.b RESULT
;  +3.b RUN
;  +4.b ENC
;  +6.w PRT1_TIMER
;        PCM >=10(30.72kHz,200clk)
;
;  +E.w STAT_PTR
;
; PSG
;  +0.b READY
;  +1.b CMD
;  +2.b RESULT
;  +3.b RUN
;
; SPK
;  +0.b READY
;  +1.b CMD
;  +2.b RESULT
;  +3.b RUN
;
;  +4.b VOL
;        PSG ボリュームレジスタ値。
;  +6.w FREQ
;        PSG FREQ レジスタ値。
;  +8.w TIME
;        1200Hz 単位の持続時間。
;  +A.w REMAIN
;        内部変数：残り時間。
;
; LPR
;  TBD.
; FDC
;  TBD.
;
; SIO0
;  +0.b READY
;  +1.b CMD
;  +2.b RESULT
;  +3.b RUN
;				; 送受信のバッファリングも検討したが
;				; わりに合わない
;  +4.b TXCMD
;  +5.b TXSTAT
;  +6.b TX
;  +A.b RXCMD
;  +B.b RXSTAT
;  +C.b RX
;
; SIO1
;  +0.b READY
;  +1.b CMD
;  +2.b RESULT
;  +3.b RUN
;				; 送受信のバッファリングも検討したが
;				; わりに合わない
;  +4.b TXCMD
;  +5.b TXSTAT
;  +6.b TX
;  +A.b RXCMD
;  +B.b RXSTAT
;  +C.b RX

	.Z180

; ######## device ID

#define DEVID_XPBUS	0
#define DEVID_TIME	1
#define DEVID_PAM	2
#define DEVID_PCM	3
#define DEVID_PSG	4
#define DEVID_SPK	5
#define DEVID_LPR	6
#define DEVID_FDC	7
#define DEVID_SIO0	8
#define DEVID_SIO1	9
; ######## define

#define PAM_CMD_START	1
#define PAM_CMD_QUERY	2

#define PAM_ENC_PAM2A	1
#define PAM_ENC_PAM2B	2
#define PAM_ENC_PAM3A	3
#define PAM_ENC_PAM3B	4
#define PAM_ENC_PAM1P	5

#define PCM_CMD_START	1

#define PCM_ENC_PCM1	1
#define PCM_ENC_PCM2	2
#define PCM_ENC_PCM3	3

#define SPK_CMD_START	1
#define SPK_CMD_STOP	2
#define SPK_CMD_KEEP	3


; #### RESULT
#define XPLX_R_OK		1
#define XPLX_R_ERROR_PARAM	254
#define XPLX_R_UNKNOWN_CMD	255


; ######## switch
; 0 = USE STAT_PTR for userland test mode
; 1 = USE HOSTINTR for kernel (normal)
#define USE_INTR	1

; ######## constants
; xp to host level 1 interrupt port
HOSTINTR1	.EQU	0B0H
; xp to host level 5 interrupt port
HOSTINTR5	.EQU	0A0H

; PAM use HOSTINTR5
PAM_HOSTINTR	.EQU	HOSTINTR5
; PCM use HOSTINTR5
PCM_HOSTINTR	.EQU	HOSTINTR5

; I/O PORT
TMDR0L	.EQU	0CH
TMDR0H	.EQU	0DH
RLDR0L	.EQU	0EH
RLDR0H	.EQU	0FH
TCR	.EQU	10H
TMDR1L	.EQU	14H
TMDR1H	.EQU	15H
RLDR1L	.EQU	16H
RLDR1H	.EQU	17H

PSG_ADR	.EQU	83H		; PSG address (out)
PSG_DAT	.EQU	82H		; data output
PSG_IN	.EQU	83H		; data input (in)

INITIAL_SP:	.EQU	01000H
PRIVATE_SP:	.EQU	0F000H

; ######## macros

ADD_HL_A:	.MACRO
	ADD	A,L
	LD	L,A
	JR	NC,$ + 3
	INC	H
	.ENDM

WAIT3	.MACRO
	NOP
	.ENDM

WAIT4	.MACRO
	LD	A,A
	.ENDM

WAIT6	.MACRO
	NOP
	NOP
	.ENDM

WAIT7	.MACRO
	LD	A,A			; 4+3=7
	NOP
	.ENDM

WAIT8	.MACRO
	LD	A,A			; 4*2=8
	LD	A,A
	.ENDM

WAIT9	.MACRO
	NOP				; 3*3=9
	NOP
	NOP
	.ENDM

WAIT10	.MACRO
	LD	A,A			; 4+3*2=10
	NOP
	NOP
	.ENDM

WAIT11	.MACRO
	LD	A,A			; 4*2+3=11
	LD	A,A
	NOP
	.ENDM

WAIT12	.MACRO
	LD	A,A			; 4*3=12
	LD	A,A
	LD	A,A
	.ENDM

WAIT13	.MACRO
	LD	A,A			; 4+3*3=13
	NOP
	NOP
	NOP
	.ENDM

WAIT16	.MACRO
	LD	A,A
	LD	A,A
	LD	A,A
	LD	A,A
	.ENDM

WAIT17	.MACRO
	LD	A,A			; 4*2+3*3=17
	LD	A,A
	NOP
	NOP
	NOP
	.ENDM

WAIT19	.MACRO
	LD	A,A			; 4*4+3=19
	LD	A,A
	LD	A,A
	LD	A,A
	NOP
	.ENDM

; ######## RESET/RST
	.ORG	0000H
RESET:
	JP	ENTRY

	.ORG	0038H
INT0:
	JP	INTR_INT0

	.ORG	0066H
NMI:
	RETN

	.ORG	0080H
DEBUG0::	.DB	0
DEBUG1::	.DB	0
DEBUG2::	.DB	0
DEBUG3::	.DB	0
DEBUG4::	.DB	0
DEBUG5::	.DB	0
DEBUG6::	.DB	0
DEBUG7::	.DB	0
DEBUG8::	.DB	0
DEBUG9::	.DB	0
DEBUG10::	.DB	0

	.ORG	00FCH
XPLX_MAGIC::			; MAGIC
	.DB	"XPLX"

; ######## shared variables
; XPBUS
	.ORG	0100H
XPLX_VAR_BASE::
XPBUS_READY::
	.DB	0
XPBUS_CMD::
	.DB	0
XPBUS_RESULT::
	.DB	0
XPBUS_RUN::
	.DB	0

XPBUS_STAT_RESET::		; reset count
	.DB	0
	.DB	0,0,0		; reserved

XPBUS_PRT0_TIMER::		; PRT0 TIMER TLDR (devices dispatch)
	.DW	256
XPBUS_INTR1_DEV::		; HOSTINTR1 device
	.DW	0
XPBUS_INTR5_DEV::		; HOSTINTR5 device
	.DW	0

; TIME
	.ORG	0110H
TIME_READY::
	.DB	0
TIME_CMD::
	.DB	0
TIME_RESULT::
	.DB	0
TIME_RUN::
	.DB	0
TIME_TIMECOUNTER::		; timecounter (TBD.)
	.DW	0

; PAM
	.ORG	0120H
PAM_READY::
	.DB	0
PAM_CMD::
	.DB	0
PAM_RESULT::
	.DB	0
PAM_RUN::
	.DB	0

PAM_ENC::
	.DB	0
PAM_REPT::
	.DB	0
PAM_CYCLE_CLK::
	.DW	0
PAM_REPT_CLK::
	.DB	0
PAM_REPT_MAX::
	.DB	0

	.DB	0,0,0,0		; reserved
PAM_STAT_PTR::
	.DW	0

; PCM
	.ORG	0130H
PCM_READY::
	.DB	0
PCM_CMD::
	.DB	0
PCM_RESULT::
	.DB	0
PCM_RUN::
	.DB	0

PCM_ENC::
	.DB	0
	.DB	0		; reserved
PCM_PRT1_TIMER::			; PRT1 TIMER TLDR (PCM)
	.DW	0

	.DB	0,0,0,0,0,0	; reserved
PCM_STAT_PTR::
	.DW	0

; PSG
	.ORG	0140H
PSG_READY::
	.DB	0
PSG_CMD::
	.DB	0
PSG_RESULT::
	.DB	0
PSG_RUN::
	.DB	0

; SPK
	.ORG	0150H
SPK_READY::
	.DB	0
SPK_CMD::
	.DB	0
SPK_RESULT::
	.DB	0
SPK_RUN::
	.DB	0

SPK_VOL::
	.DB	0
	.DB	0		; reserved
SPK_FREQ::
	.DW	0
SPK_TIME::
	.DW	0
SPK_REMAIN::
	.DW	0

; LPR
	.ORG	0160H
LPR_READY::
	.DB	0
LPR_CMD::
	.DB	0
LPR_RESULT::
	.DB	0
LPR_RUN::
	.DB	0
	; TBD.

LPR_CMD_START	.EQU	1

; FDC
	.ORG	0170H
FDC_READY::
	.DB	0
FDC_CMD::
	.DB	0
FDC_RESULT::
	.DB	0
FDC_RUN::
	.DB	0
; TBD.

FDC_CMD_START	.EQU	1

; SIO0
	.ORG	0180H
SIO0_READY::
	.DB	0
SIO0_CMD::
	.DB	0
SIO0_RESULT::
	.DB	0
SIO0_RUN::
	.DB	0

SIO0_TXCMD::
	.DB	0
SIO0_TXSTAT::
	.DB	0
SIO0_TX::
	.DB	0
	.DS	3
SIO0_RXCMD::
	.DB	0
SIO0_RXSTAT::
	.DB	0
SIO0_RX::
	.DB	0

; SIO1
	.ORG	0190H
SIO1_READY::
	.DB	0
SIO1_CMD::
	.DB	0
SIO1_RESULT::
	.DB	0
SIO1_RUN::
	.DB	0

SIO1_TXCMD::
	.DB	0
SIO1_TXSTAT::
	.DB	0
SIO1_TX::
	.DB	0
	.DS	3
SIO1_RXCMD::
	.DB	0
SIO1_RXSTAT::
	.DB	0
SIO1_RX::
	.DB	0


; ######## Bootstrap program
	.ORG	0200H
ENTRY:
	DI
	LD	SP,INITIAL_SP

				; inc reset count
	LD	HL, XPBUS_STAT_RESET
	INC	(HL)

				; initial devices
				; READY=0
	XOR	A
	LD	(XPBUS_READY),A
	LD	(TIME_READY),A
	LD	(PAM_READY),A
	LD	(PCM_READY),A
	LD	(PSG_READY),A
	LD	(SPK_READY),A
	LD	(LPR_READY),A
	LD	(FDC_READY),A
	LD	(SIO0_READY),A
	LD	(SIO1_READY),A

	LD	A,1
	LD	(DEBUG0),A

				; init XP internal devices
				; internal I/O address = 00H - 3FH
	LD	A,00H		; IOA7[7]=0 IOSTP[5]=0
ICR	.EQU	3FH
	OUT0	(ICR),A

				; memory wait = 0
				; I/O wait = 3
				; no DMA
	LD	A,20H		; MWI[76]=0 IWI[54]=2(3wait) DMS[32]=0 DIM[10]=0
DCNTL	.EQU	32H
	OUT0	(DCNTL),A
				; disable refresh
	LD	A,03H		; REFE[7]=0 REFW[6]=0 CYC[10]=3(80state)
RCR	.EQU	36H
	OUT0	(RCR),A

	LD	A,2
	LD	(DEBUG0),A

				; prepare memory map
				; MMU
CBR	.EQU	38H
BBR	.EQU	39H
CBAR	.EQU	3AH
				; Common0: VA=0000H -> PA=00000H SH
				; Bank   : VA=E000H -> PA=28000H PR
				; Common1: VA=F000H -> PA=FF000H IN
	LD	A,0FEH
	OUT0	(CBAR),A
	LD	A,0F0H
	OUT0	(CBR),A
	LD	A,1AH
	OUT0	(BBR),A

	LD	A,3
	LD	(DEBUG0),A

				; internal RAM addressing
				; for no-wait access
				; PA=FxxxxH にしたらノーウェイトになった。
				; PA=0xxxxH だとウェイトが入った。
				; ほかのアドレスは未調査。
				; built-in RAM VA=FE00H PA=FFE00H
	LD	A,0F0H
RMCR	.EQU	51H
	OUT0	(RMCR),A
				; disable external interrupt
				; TODO: if use "Host to XP" interrupt, change here
	LD	A,00H		; TRAP[7]=0 ITE2[2]=0 ITE1[1]=0 ITE0[0]=0
ITC	.EQU	34H
	OUT0	(ITC),A
				; Interrupt Vector Low = E
				; I = FFH
				; Interrupt Vector Address = FFE0H
	LD	A,0E0H
IL	.EQU	33H
	OUT0	(IL),A
	LD	A,0FFH
	LD	I,A
				; interrupt mode 1
	IM	1

	LD	A,4
	LD	(DEBUG0),A

	CALL	INIT_PSG

	; TODO
	; INIT FDC
	; INIT LPR
	; INIT SIO

				; INIT PRT0,1
				; TIE1[5]=TIE0[4]=0
				; TOC1[3]=TOC0[2]=0
				; TDE1[1]=TDE0[0]=0
	LD	A,00H
	OUT0	(TCR),A
				; prepare PRT0
	LD	HL,(XPBUS_PRT0_TIMER)
	OUT0	(RLDR0L),L
	OUT0	(TMDR0L),L
	OUT0	(RLDR0H),H
	OUT0	(TMDR0H),H
				; TIE0, TID0 ON
				; TIE0[4]=1 TDE0[0]=1
	LD	A,11H
	OUT0	(TCR),A

				; copy to private memory
	LD	HL,PROG_ORG
	LD	DE,PRIVATE_RAM
	LD	BC,PROG_ORG_LEN
	LDIR
				; interrupt vector copy to internal memory
	LD	HL,VECTOR_ORG
	LD	DE,VECTOR
	LD	BC,VECTOR_ORG_LEN
	LDIR

	LD	A,5
	LD	(DEBUG0),A
				; jump to XPBUS
	JP	XPBUS

; initialize PSG registers
; break all regs
INIT_PSG:
				; init PSG
				; PSG R0-R6 All 00H
	LD	A,0
	LD	B,7
	LD	C,PSG_DAT
	LD	D,0
PSG_CLEAR_06:
	OUT	(PSG_ADR),A
	OUT	(C),D
	INC	A
	DJNZ	PSG_CLEAR_06
				; PSG mixer
				; tone = off, noise = off
				; IOA, IOB = output
	LD	A,7
	LD	D,0FFH
	OUT	(PSG_ADR),A
	OUT	(C),D
				; PSG volume and envelope
				; PSG R8-R15 all 0
	LD	A,8
	LD	B,8
	LD	D,0
PSG_CLEAR_8F:
	OUT	(PSG_ADR),A
	OUT	(C),D
	INC	A
	DJNZ	PSG_CLEAR_8F
				; TODO: PSG I/O Port
	RET

; ######## buffers
	.PHASE	1000H
PAM_BUF::
PCM_BUF::
	.DEPHASE
	.PHASE 08000H
PAM_BUF_LEN::	.EQU	$-PAM_BUF
PCM_BUF_LEN::	.EQU	$-PCM_BUF
PSG_BUF::
	.DEPHASE
	.PHASE 09000H
PSG_BUF_LEN::	.EQU	$-PSG_BUF
LPR_BUF::
	.DEPHASE
	.PHASE 0A000H
LPR_BUF_LEN::	.EQU	$-LPR_BUF
FDC_BUF::
	.DEPHASE

; ######## private memory program
	.PHASE 0E000H
FDC_BUF_LEN::	.EQU	$-FDC_BUF

PROG_ORG:	.EQU	$$
PRIVATE_RAM:

XPBUS:
	LD	A,6
	LD	(DEBUG0),A

	LD	SP,PRIVATE_SP

				; devices READY=1
	LD	A,1
	LD	(XPBUS_READY),A
	LD	(TIME_READY),A
	LD	(PAM_READY),A
	LD	(PCM_READY),A
	LD	(PSG_READY),A
	LD	(SPK_READY),A
	LD	(LPR_READY),A
	LD	(FDC_READY),A
	LD	(SIO0_READY),A
	LD	(SIO1_READY),A

				; wait for PRT0
	EI
XPBUS_LOOP:
	HALT
	JR	XPBUS_LOOP

INTR_PRT0:
; #### Periodic devices
; 1200Hz
; ここから呼び出される DISPATCH ルーチンは、
; o. A にコマンドが入っている
; o. AF, HL は破壊して良い。
; o. EI 状態で呼ばれることに注意。
; o. EI 状態でリターンすること。
; o. 裏レジスタは PCM 専用。
; o. PAM 以外、0.83 msec 以内にリターンすること。

	PUSH	AF
	PUSH	HL

	LD	A,7
	LD	(DEBUG0),A
				; reset PRT0 interrupt
	IN0	F,(TCR)
	IN0	F,(TMDR0L)
				; first EI, for PRT1
	EI

TIMECOUNTER_INCR:
				; timecounter
	LD	HL,(TIME_TIMECOUNTER)
	INC	HL
	LD	(TIME_TIMECOUNTER),HL

; #### XPBUS devices dispatcher

DEVICES_DISPATCH:
	LD	A,(XPBUS_CMD)
	OR	A
	CALL	NZ,XPBUS_DISPATCH

	LD	A,(PAM_CMD)
	OR	A
	CALL	NZ,PAM_DISPATCH

	LD	A,(PCM_CMD)
	OR	A
	CALL	NZ,PCM_DISPATCH

	LD	A,(PSG_CMD)
	OR	A
	CALL	NZ,PSG_DISPATCH

	LD	A,(SPK_CMD)
	OR	A
	CALL	NZ,SPK_DISPATCH

	LD	A,(LPR_CMD)
	OR	A
	CALL	NZ,LPR_DISPATCH

	LD	A,(FDC_CMD)
	OR	A
	CALL	NZ,FDC_DISPATCH

	LD	A,(SIO0_CMD)
	OR	A
	CALL	NZ,SIO0_DISPATCH

	LD	A,(SIO1_CMD)
	OR	A
	CALL	NZ,SIO1_DISPATCH

	LD	A,8
	LD	(DEBUG0),A

	POP	HL
	POP	AF
	RETI

; #### XPBUS

XPBUS_DISPATCH:
	; not implemented
	XOR	A
	LD	(XPBUS_CMD),A
	LD	A,XPLX_R_UNKNOWN_CMD
	LD	(XPBUS_RESULT),A
	RET

; #### TIME

TIME_DISPATCH:
	; not implemented
	XOR	A
	LD	(TIME_CMD),A
	LD	A,XPLX_R_UNKNOWN_CMD
	LD	(TIME_RESULT),A
	RET

; #### PAM は末尾

; #### PCM driver core

; PCM 割り込みは裏レジスタを専有します。
; メインルーチン側では裏レジスタを使用してはいけません。

; #### PCM play start
PCM_DISPATCH:
	CP	PCM_CMD_START
	JR	Z,PCM_START

	LD	A,XPLX_R_UNKNOWN_CMD
PCM_ERROR:
	LD	(PCM_RESULT),A
	RET

PCM_START:
				; if READY==0 return
	LD	A,(PCM_READY)
	OR	A
	RET	Z
				; check ENC
	LD	A,(PCM_ENC)
	DEC	A
	JR	Z,PCM_START_OK	; PCM1 = 1
	DEC	A
	JR	Z,PCM_START_OK	; PCM2 = 2
	DEC	A
	JR	Z,PCM_START_OK	; PCM3 = 3

	LD	A,XPLX_R_ERROR_PARAM
	JR	PCM_ERROR

PCM_START_OK:
				; A = 0
	LD	(PCM_READY),A
	LD	(PCM_CMD),A


				; prepare vector
	DI
				; set PRT1 vector
	LD	HL,PCM_INTR
	LD	(VEC_PRT1),HL
				; prepare register
	EXX

	CALL	INIT_PSG

				; make interrupt handler
	LD	A,(PCM_ENC)
	DEC	A
	JR	Z,PCM_SET_PCM1
	DEC	A
	JR	Z,PCM_SET_PCM2
PCM_SET_PCM3:
	LD	HL,PCM3
	JR	PCM_SET
PCM_SET_PCM2:
	LD	HL,PCM2
	JR	PCM_SET
PCM_SET_PCM1:
	LD	HL,PCM1
PCM_SET:
	LD	(PCM_INTR_JMP),HL

	LD	HL,PCM_BUF
	LD	BC,0800H + PSG_ADR
	LD	DE,0709H

	EXX

				; TIE1, TDE1 OFF
	IN0	A,(TCR)
	AND	0DDH		; TIE1[5]=0 TDE1[1]=0
	OUT0	(TCR),A
				; prepare PRT1
	LD	HL,(PCM_PRT1_TIMER)
	OUT0	(RLDR1L),L
	OUT0	(RLDR1H),H
	OUT0	(TMDR1L),L
	OUT0	(TMDR1H),H
				; TIE1, TID1 ON
	OR	22H		; TIE1[5]=1 TDE1[5]=1
	OUT0	(TCR),A

	EI

	LD	A,1
	LD	(PCM_RUN),A

	RET



; #### PCM interrupt handler

PCM_INTR:
				; PRT1 interrupt
	EX	AF,AF
	EXX
				; interrupt acknowledge
				; reset PRT1 Interrupt
	IN0	F,(TCR)
	IN0	F,(TMDR1L)

				; ジャンプ先は書き換えられる
PCM_INTR_JMP:	.EQU	$+1
	JP	PCM1

PCM_INTR_NEXT:
	RLCA
	JR	C,PCM_RELOAD
				; inc ptr after reload check
	INC	HL
	RLCA
	JR	C,PCM_STAT
	RLCA
	JR	NC,PCM_NORMAL

; PCM RESET attention
; in: HL = EXIT address
PCM_RESET:
				; PRT1 intr stop
	IN0	A,(TCR)
				; TIE1,TDE1 OFF
	AND	0DDH		; TIE1[5]=0 TDE1[1]=0
	OUT0	(TCR),A
				; PLAY STOP
	XOR	A
	LD	(PCM_RUN),A
	LD	A,XPLX_R_OK
	LD	(PCM_RESULT),A
	LD	(PCM_READY),A

	JR	PCM_EXIT

; PCM common code

PCM_RELOAD:
	LD	HL,PCM_BUF
PCM_STAT:
#if USE_INTR
	OUT	(PCM_HOSTINTR),A
#else
	LD	(PCM_STAT_PTR),HL
#endif
PCM_NORMAL:
PCM_EXIT:
	EXX
	EX	AF,AF
	EI
	RETI

; #### PCM core code

PCM1:
				; PSG REG=8
	OUT	(C),B
				; read attention or CH0
	LD	A,(HL)
	OUT	(PSG_DAT),A
	JP	PCM_INTR_NEXT

PCM2:
	LD	D,(HL)
	INC	HL
	LD	A,(HL)

	OUT	(C),B
	OUT0	(PSG_DAT),D
	OUT	(C),E
	OUT	(PSG_DAT),A
	JP	PCM_INTR_NEXT

PCM3:
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	INC	HL
	LD	A,(HL)

	PUSH	HL
	LD	HL,090AH
	OUT	(C),B
	OUT0	(PSG_DAT),E
	OUT	(C),H
	OUT0	(PSG_DAT),D
	OUT	(C),L
	OUT	(PSG_DAT),A
	POP	HL
	JP	PCM_INTR_NEXT

; #### SPK
SPK_DISPATCH:
	CP	SPK_CMD_START
	JR	Z,SPK_START
	CP	SPK_CMD_STOP
	JR	Z,SPK_STOP
	CP	SPK_CMD_KEEP
	JR	Z,SPK_KEEP

	LD	A,XPLX_R_UNKNOWN_CMD
	LD	(SPK_RESULT),A
	RET

SPK_START:
	LD	A,(SPK_READY)
	OR	A
	RET	Z

	XOR	A
	LD	(SPK_READY),A
				; next to CMD_KEEP
	LD	A,SPK_CMD_KEEP
	LD	(SPK_CMD),A
	LD	A,1
	LD	(SPK_RUN),A

				; set REMAIN
	LD	HL,(SPK_TIME)
	LD	(SPK_REMAIN),HL

	DI
				; PSG CH3 FREQ
	LD	HL,(SPK_FREQ)
	LD	A,4
	OUT0	(PSG_ADR),A
	OUT0	(PSG_DAT),L
	LD	A,5
	OUT0	(PSG_ADR),A
	OUT0	(PSG_DAT),H
				; PSG CH3 VOL
	LD	A,10
	OUT	(PSG_ADR),A
	LD	A,(SPK_VOL)
	OUT	(PSG_DAT),A
				; save PSG R7
	LD	A,7
	OUT0	(PSG_ADR),A
	IN	A,(PSG_IN)
	LD	(SPK_PSGR7),A
				; PSG CH3 TONE ON
	AND	0FBH
	OUT	(PSG_DAT),A

	JR	SPK_EXIT

SPK_STOP:
	LD	A,(SPK_READY)
	OR	A
	RET	Z

SPK_STOP_CORE:
	XOR	A
	LD	(SPK_READY),A
	LD	(SPK_CMD),A

	DI
				; restore PSG R7
	LD	A,7
	OUT	(PSG_ADR),A
	LD	A,(SPK_PSGR7)
	OUT	(PSG_DAT),A
				; PSG CH3 VOL=0
	LD	A,10
	OUT	(PSG_ADR),A
	XOR	A
	OUT	(PSG_DAT),A

	LD	(SPK_RUN),A

	JR	SPK_EXIT

SPK_KEEP:
				; REMAIN == 0, then stop
	LD	HL,(SPK_REMAIN)
	LD	A,H
	OR	L
	JR	Z,SPK_STOP_CORE

	DEC	HL
	LD	(SPK_REMAIN),HL

SPK_EXIT:
	EI
	LD	A,XPLX_R_OK
	LD	(SPK_RESULT),A
	LD	(SPK_READY),A
	RET

SPK_PSGR7:
	.DB	0

; ######## PSG
PSG_DISPATCH:
	; not implemented
	XOR	A
	LD	(PSG_CMD),A
	LD	A,XPLX_R_UNKNOWN_CMD
	LD	(PSG_RESULT),A
	RET
; ######## LPR
LPR_DISPATCH:
	; not implemented
	XOR	A
	LD	(LPR_CMD),A
	LD	A,XPLX_R_UNKNOWN_CMD
	LD	(LPR_RESULT),A
	RET
; ######## FDC
FDC_DISPATCH:
	; not implemented
	XOR	A
	LD	(FDC_CMD),A
	LD	A,XPLX_R_UNKNOWN_CMD
	LD	(FDC_RESULT),A
	RET

; ######## SIO
SIO0_DISPATCH:
	; not implemented
	XOR	A
	LD	(SIO0_CMD),A
	LD	A,XPLX_R_UNKNOWN_CMD
	LD	(SIO0_RESULT),A
	RET

SIO1_DISPATCH:
	; not implemented
	XOR	A
	LD	(SIO1_CMD),A
	LD	A,XPLX_R_UNKNOWN_CMD
	LD	(SIO1_RESULT),A
	RET

INTR_INT0:
INTR_ASCI0:
INTR_ASCI1:
				; TBD
	EI
	RETI

; #### PAM play start

PAM_DISPATCH:
	CP	PAM_CMD_START
	JR	Z,PAM_START
	CP	PAM_CMD_QUERY
	JR	Z,PAM_QUERY

	XOR	A
	LD	(PAM_CMD),A
	LD	A,XPLX_R_UNKNOWN_CMD
	LD	(PAM_RESULT),A
	RET

; PAM ENC -> PAM Driver MAP address
; OUT: HL = MAP address
; if error, direct return to main routine
PAM_ENC_MAP:
	LD	A,(PAM_ENC)
	OR	A
	JR	Z,PAM_ERROR_ENC
	DEC	A

	CP	PAM_DRIVER_MAP_LEN / 16		; 16 bytes / entry
	JP	NC,PAM_ERROR_ENC

	ADD	A,A		; A *= 16
	ADD	A,A
	ADD	A,A
	ADD	A,A

	LD	HL,PAM_DRIVER_MAP
	ADD_HL_A
	RET

PAM_ERROR_ENC:
	POP	HL		; discard caller PC
PAM_ERROR_PARAM:
	LD	A,XPLX_R_ERROR_PARAM
	LD	(PAM_RESULT),A
	RET			; return to main

PAM_QUERY:
	CALL	PAM_ENC_MAP	; get ENC to MAP

	LD	A,(PAM_READY)
	OR	A
	RET	Z

	XOR	A
	LD	(PAM_READY),A
	LD	(PAM_CMD),A

	PUSH	BC
	PUSH	DE

	LD	BC,12		; MAP offset 12 = CYCLE_CLK
	ADD	HL,BC

				; CYCLE_CLK, REPT_CLK, REPT_MAX
	LD	DE,PAM_CYCLE_CLK
	LD	BC,4
	LDIR

	POP	DE
	POP	BC

	LD	A,XPLX_R_OK
	LD	(PAM_RESULT),A
	LD	(PAM_READY),A
	RET


PAM_START:
	CALL	PAM_ENC_MAP	; get ENC to MAP

	LD	A,15
	ADD_HL_A		; HL points REPT_MAX

	LD	A,(PAM_REPT)
	CP	(HL)
	JR	Z,PAM_START_OK	; == OK
	JR	C,PAM_START_OK	; < OK
	JR	PAM_ERROR_PARAM

PAM_START_OK:
	LD	A,(PAM_READY)
	OR	A
	RET	Z

	XOR	A
	LD	(PAM_READY),A
	LD	(PAM_CMD),A

				; never normal return
				; PAM never EI
	DI
	CALL	INIT_PSG

	CALL	PAM_ENC_MAP	; re- get ENC to MAP

				; copy to internal RAM
	LD	DE,PAM_DRIVER

	LD	SP,HL		; SP = top of Map entry
	POP	HL		; HEAD
	POP	BC		; HEAD_LEN
	LDIR

	LD	A,(PAM_REPT)
	INC	A		; DEC is not change CY


PAM_REPT_LOOP:
	POP	HL		; REPT
	POP	BC		; REPT_LEN

	DEC	A		; DEC is not change CY
	JR	Z,PAM_REPT_END

	LDIR

	DEC	SP
	DEC	SP
	DEC	SP
	DEC	SP
	JR	PAM_REPT_LOOP
PAM_REPT_END:

	POP	HL		; TAIL
	POP	BC		; TAIL_LEN
	LDIR

				; buffer pointer
	LD	HL,PAM_BUF
#if USE_INTR
#else
	LD	(PAM_STAT_PTR),HL
#endif
				; prefetch
	LD	SP,HL			; 4
	POP	DE

; I/O WAIT 3 -> 2
; PSG の address / write 時間 は 300ns なので、
; 1.8432 clock あればよいので 1 wait から設定できるはずだが、
; 2 wait に設定すると out 命令が 12 clock となり、
; 共有メモリに対する POP の 9+3=12 clock と一致して
; クロック整合を取りやすくなるため、2 wait に設定する。
; なお PSG の read は 400ns 必要なため、2 wait だとあやしい。
; また HOSTINTR 用の I/O への out で wait を満足するかどうかは
; 未確定だけど、HOSTINTR 機構はデータバスの値ではなく
; アドレスへの出力で動作するため、ウェイトに関係なく動作すると
; 期待してみる。
	LD	A,10H		; IWI[54]=1(2wait)
	OUT0	(DCNTL),A

	LD	A,1
	LD	(PAM_RUN),A

	LD	A,8
	OUT	(PSG_ADR),A
	LD	C,PSG_DAT

	JP	PAM_DRIVER

PAM_RESET:
				; XPBUS に制御を戻す
	LD	SP,PRIVATE_SP

; I/O WAIT 2 -> 3
	LD	A,20H		; IWI[54]=2(3wait)
	OUT0	(DCNTL),A

	CALL	INIT_PSG

	XOR	A
	LD	(PAM_RUN),A

	LD	A,XPLX_R_OK
	LD	(PAM_RESULT),A
	LD	(PAM_READY),A

	JP	XPBUS

PAM_DRIVER_MAP:
				; 16 bytes / entry
	DW	PAM2A_HEAD_ORG
	DW	PAM2A_HEAD_LEN
	DW	PAM2A_REPT_ORG
	DW	PAM2A_REPT_LEN
	DW	PAM2A_TAIL_ORG
	DW	PAM2A_TAIL_LEN
	DW	204		;CYCLE_CLK
	DB	36		;REPT_CLK
	DB	37		;REPT_MAX

	DW	PAM2B_HEAD_ORG
	DW	PAM2B_HEAD_LEN
	DW	PAM2B_REPT_ORG
	DW	PAM2B_REPT_LEN
	DW	PAM2B_TAIL_ORG
	DW	PAM2B_TAIL_LEN
	DW	152		;CYCLE_CLK
	DB	24		;REPT_CLK
	DB	57		;REPT_MAX

	DW	PAM3A_HEAD_ORG
	DW	PAM3A_HEAD_LEN
	DW	PAM3A_REPT_ORG
	DW	PAM3A_REPT_LEN
	DW	PAM3A_TAIL_ORG
	DW	PAM3A_TAIL_LEN
	DW	298		;CYCLE_CLK
	DB	51		;REPT_CLK
	DB	24		;REPT_MAX

	DW	PAM3B_HEAD_ORG
	DW	PAM3B_HEAD_LEN
	DW	PAM3B_REPT_ORG
	DW	PAM3B_REPT_LEN
	DW	PAM3B_TAIL_ORG
	DW	PAM3B_TAIL_LEN
	DW	136		;CYCLE_CLK
	DB	36		;REPT_CLK
	DB	38		;REPT_MAX



PAM_DRIVER_MAP_LEN:	.EQU	$-PAM_DRIVER_MAP

	.DEPHASE



; ######## PAM drivers
	.PHASE 0FE00H
				; all PAM drivers have same address=0FE00H
PAM_DRIVER:
	.DEPHASE

; #### PAM2A

	.PHASE 0FE00H
PAM2A_HEAD_ORG:	.EQU	$$
PAM2A_HEAD:
PAM2A:
				; PAM2A
				; 12+0:12+12 = 1:2 PAM
				; PAM 36clk 170.667kHz
				; output PAM wave = normal 5 + antinoise 1

				; 1 PAM cycle = 204 clk

				; 6.144E6 / (204 + 36*n)

				; sampling freqs:
				;  0: 30118
				; 37:  4000

				; no STAT for first time
	JP	PAM2A_LOOP

PAM2A_RELOAD:
	OUT	(C),E
	OUT	(C),D
	LD	SP,PAM_BUF		;9
	WAIT3

PAM2A_STAT:
#if USE_INTR
	OUT	(C),E
	OUT	(C),D
	OUT	(PAM_HOSTINTR),A		;10+2
#else
				; STAT_PTR モードでの遅れはしょうがない
	OUT	(C),E
	OUT	(C),D
	LD	(PAM_STAT_PTR),SP		;19+3
#endif

PAM2A_NORMAL:
	OUT	(C),E
	OUT	(C),D
				; prefetch
	POP	DE			;9+3

	OUT	(C),L
	OUT	(C),H
				; うまくいくかはわからない
				; 本来 wait 12 だが PAM 遷移ノイズを
				; 低減するため待たない
PAM2A_LOOP:
				; prefetched DE
	OUT	(C),E
	OUT	(C),D
				; HL = DE for save current sample
	LD	L,E			;4
	LD	H,D			;4
				; A = attention
	LD	A,E			;4

PAM2A_HEAD_LEN:	.EQU	$-PAM2A_HEAD

PAM2A_REPT_ORG:	.EQU	$$
PAM2A_REPT:
	OUT	(C),E
	OUT	(C),D
	WAIT12
PAM2A_REPT_LEN:	.EQU	$-PAM2A_REPT

PAM2A_TAIL_ORG:	.EQU	$$
PAM2A_TAIL:
				; このブロックは動的再配置されるので
				; このブロック"への"ジャンプは困難
				; "からの"ジャンプは可能。
	OUT	(C),E
	OUT	(C),D
	RLCA
				; attention bit
				; bit7=1, reload
				; must be JP
	JP	C,PAM2A_RELOAD		; jump=9 no=6

	WAIT3
	OUT	(C),E
	OUT	(C),D
	RLCA				; 3
				; bit6=1, stat
				; must be JP
	JP	C,PAM2A_STAT		; jump=9 no=6

	WAIT3
	OUT	(C),E
	OUT	(C),D
	RLCA				; 3
				; bit5=0, normal
				; must be JP
	JP	NC,PAM2A_NORMAL		; jump=9 no=6
				; attention=001, reset
	JP	PAM_RESET
PAM2A_TAIL_LEN:	.EQU	$-PAM2A_TAIL

				; cycle
				; 5 * (12*3) + 12*2 = 204

	.DEPHASE

; #### PAM2B

	.PHASE 0FE00H
				; all PAM drivers have same address=0FE00H
PAM2B_HEAD_ORG:	.EQU	$$
PAM2B_HEAD:
PAM2B:
				; PAM2B
				; averaged 1:1 PAM
				; wait (4,7), (3,9), (9,12), (12,0)
				; phase wait 28:28
				; clk  35, 36, 45, 36
				; PAM 176, 171, 137, 171 kHz
				; output PAM wave = 4

				; 1 PAM cycle = 152 clk

				; 6.144E6 / (152 + 24*n)

				; sampling freqs:
				;  0: 40421
				; 57:  4042

				; no STAT for first time
	JP	PAM2B_LOOP

PAM2B_RELOAD:
	OUT	(C),E
	LD	SP,PAM_BUF		;9

PAM2B_STAT:
#if USE_INTR
	OUT	(C),D
	OUT	(PAM_HOSTINTR),A		;10+2
#else
				; STAT_PTR モードでの遅れはしょうがない
	OUT	(C),D
	LD	(PAM_STAT_PTR),SP		;19+3
#endif

PAM2B_NORMAL:
	OUT	(C),E
				; prefetch
	POP	DE			;9+3
	OUT	(C),B
PAM2B_LOOP:
				; prefetched DE
	OUT	(C),E
				; A = attention
	LD	A,E			;4
	OUT	(C),D
				; B = save D
	LD	B,D			;4
	WAIT3

PAM2B_HEAD_LEN:	.EQU	$-PAM2B_HEAD

PAM2B_REPT_ORG:	.EQU	$$
PAM2B_REPT:
	OUT	(C),E
	OUT	(C),D
PAM2B_REPT_LEN:	.EQU	$-PAM2B_REPT

PAM2B_TAIL_ORG:	.EQU	$$
PAM2B_TAIL:
				; このブロックは動的再配置されるので
				; このブロック"への"ジャンプは困難
				; "からの"ジャンプは可能。
	OUT	(C),E
	RLCA				;3
	OUT	(C),D
				; attention bit
				; bit7=1, reload
				; must be JP
	JP	C,PAM2B_RELOAD		; jump=9 no=6

	RLCA				; 3
	OUT	(C),E
				; bit6=1, stat
				; must be JP
	JP	C,PAM2B_STAT		; jump=9 no=6

	RLCA				; 3
	OUT	(C),D
	WAIT3
				; bit5=0, normal
				; must be JP
	JP	NC,PAM2B_NORMAL		; jump=9 no=6
				; attention=001, reset
	JP	PAM_RESET
PAM2B_TAIL_LEN:	.EQU	$-PAM2B_TAIL

				; cycle
				; 4 * 12*2 + (4+7 + 3+9 + 9+12 + 12+0) = 152

	.DEPHASE

; #### PAM3A

	.PHASE 0FE00H
PAM3A_HEAD_ORG:	.EQU	$$
PAM3A_HEAD:
PAM3A:
				; PAM3A
				; 12+0:12+3:12+12 = 4:5:8 PAM
				; PAM 51clk 120.471kHz
				; output PAM wave = normal 5 + antinoise 1

				; 1 PAM cycle = 298 clk

				; 6.144E6 / (298 + 51*n)

				; sampling freqs:
				; 0: 20617
				; 24: 4037

				; prefetch
	POP	AF
	LD	B,A
				; no STAT for first time
	JP	PAM3A_LOOP

PAM3A_RELOAD:
	OUT	(C),L
	OUT	(C),H
	WAIT3
	OUT	(C),B
	LD	SP,PAM_BUF		;9
	WAIT3

PAM3A_STAT:
#if USE_INTR
	OUT	(C),L
	OUT	(C),H
	WAIT3
	OUT	(C),B
	OUT	(PAM_HOSTINTR),A		;10+2
#else
				; STAT_PTR モードでの遅れはしょうがない
	OUT	(C),L
	OUT	(C),H
	WAIT3
	OUT	(C),B
	LD	(PAM_STAT_PTR),SP		;19+3
#endif

PAM3A_NORMAL:
	OUT	(C),L
	OUT	(C),H
	WAIT3
	OUT	(C),B
				; prefetch
	POP	DE			;9+3

	OUT	(C),L
	OUT	(C),H
	WAIT3
	OUT	(C),B
				; prefetch
	POP	AF			;9+3

	OUT	(C),L
	OUT	(C),H
	WAIT3
	OUT	(C),B
				; うまくいくかはわからない
				; 本来 wait 12 だが PAM 遷移ノイズを
				; 低減するのも含めて4clkだけ消費する
	LD	B,A			;4
PAM3A_LOOP:
				; prefetched DE, A=B

PAM3A_HEAD_LEN:	.EQU	$-PAM3A_HEAD

PAM3A_REPT_ORG:	.EQU	$$
PAM3A_REPT:
	OUT	(C),E
	OUT	(C),D
	WAIT3
	OUT	(C),B
	WAIT12
PAM3A_REPT_LEN:	.EQU	$-PAM3A_REPT

PAM3A_TAIL_ORG:	.EQU	$$
PAM3A_TAIL:
				; このブロックは動的再配置されるので
				; このブロック"への"ジャンプは困難
				; "からの"ジャンプは可能。
	OUT	(C),E
	OUT	(C),D
	EX	DE,HL			;3
	OUT	(C),B
	RLCA
				; attention bit
				; bit7=1, reload
				; must be JP
	JP	C,PAM3A_RELOAD		; jump=9 no=6

	WAIT3
	OUT	(C),L
	OUT	(C),H
	WAIT3
	OUT	(C),B
	RLCA				; 3
				; bit6=1, stat
				; must be JP
	JP	C,PAM3A_STAT		; jump=9 no=6

	WAIT3
	OUT	(C),L
	OUT	(C),H
	WAIT3
	OUT	(C),B
	RLCA				; 3
				; bit5=0, normal
				; must be JP
	JP	NC,PAM3A_NORMAL		; jump=9 no=6
				; attention=001, reset
	JP	PAM_RESET
PAM3A_TAIL_LEN:	.EQU	$-PAM3A_TAIL

				; cycle
				; 5 * (12*3+3+12) + (12*3+3+4) = 298

	.DEPHASE

; #### PAM3B

	.PHASE 0FE00H
PAM3B_HEAD_ORG:	.EQU	$$
PAM3B_HEAD:
PAM3B:
				; PAM3B
				; approx 1:1:1
				; wait (9,9,12), (12,12,10)
				; phase wait 21:21:22
				; clk 66, 70
				; PAM 93, 88 kHz
				; output PAM wave = 2

				; 1 PAM cycle = 136 clk

				; 6.144E6 / (136 + 36*n)

				; sampling freqs:
				; 0: 45176
				; 38: 4085

				; prefetch
	POP	AF
	LD	B,A
	RLCA
				; no STAT for first time
	JP	PAM3B_LOOP

PAM3B_RELOAD:
	OUT	(C),D
	LD	SP,PAM_BUF		;9

PAM3B_STAT:
#if USE_INTR
	OUT	(C),B
	OUT	(PAM_HOSTINTR),A		;10+2
#else
				; STAT_PTR モードでの遅れはしょうがない
	OUT	(C),B
	LD	(PAM_STAT_PTR),SP		;19+3
#endif

PAM3B_NORMAL:
	OUT	(C),E
				; prefetch
	POP	HL			;9+3

	OUT	(C),D
				; prefetch
	POP	AF			;9+3

	OUT	(C),B
	EX	DE,HL			;3
	LD	B,A			;4
	RLCA				;3
PAM3B_LOOP:
				; prefetched DE,B A=RLCA-ed flag

PAM3B_HEAD_LEN:	.EQU	$-PAM3B_HEAD

PAM3B_REPT_ORG:	.EQU	$$
PAM3B_REPT:
	OUT	(C),E
	OUT	(C),D
	OUT	(C),B
PAM3B_REPT_LEN:	.EQU	$-PAM3B_REPT

PAM3B_TAIL_ORG:	.EQU	$$
PAM3B_TAIL:
				; このブロックは動的再配置されるので
				; このブロック"への"ジャンプは困難
				; "からの"ジャンプは可能。
	OUT	(C),E
				; attention bit
				; bit7=1, reload
				; must be JP
	JP	C,PAM3B_RELOAD		; jump=9 no=6

	RLCA				; 3
	OUT	(C),D
				; bit6=1, stat
				; must be JP
	JP	C,PAM3B_STAT		; jump=9 no=6

	RLCA				; 3
	OUT	(C),B
	WAIT3
				; bit5=0, normal
				; must be JP
	JP	NC,PAM3B_NORMAL		; jump=9 no=6
				; attention=001, reset
	JP	PAM_RESET
PAM3B_TAIL_LEN:	.EQU	$-PAM3B_TAIL


	.DEPHASE

; #### PAM1P

	.PHASE	0FE00H
PAM1P_HEAD_ORG:	.EQU	$$
PAM1P_HEAD:
PAM1P:
				; PAM1P
				; PAM1P は正確にはPCMだが
				; 動作方式はPAMに近いためこちら。
				; Polyphase PCM

				; 1 cycle = 87 clk
				; 6.144E6 / (87 + 3*n)

				; sampling freqs:
				; 0: 70621
				; 255: 7420

	LD	HL,PAM_BUF		;9

	LD	C,PSG_ADR
				; initial CH0
	LD	A,8
	OUT	(PSG_ADR),A
				; rotated next CH
	LD	B,9
	LD	DE,080AH

				; no STAT for first time
	JP	PAM1P_LOOP

PAM1P_RELOAD:
	LD	HL,PAM_BUF		;9

PAM1P_STAT:
#if USE_INTR
	OUT	(PAM_HOSTINTR),A		;10+2
#else
				; STAT_PTR モードでの遅れはしょうがない
	LD	(PAM_STAT_PTR),HL		;16+3
#endif

PAM1P_NORMAL:
				; rotate B,E,D
	LD	A,B			;4
	LD	B,E			;4
	LD	E,D			;4
	LD	D,A			;4
	OUT	(C),B			;10+2

PAM1P_LOOP:

	LD	A,(HL)			;6+3
	INC	HL			;4

	OUT	(PSG_DAT),A			;10+2

PAM1P_HEAD_LEN:	.EQU	$-PAM1P_HEAD

PAM1P_REPT_ORG:	.EQU	$$
PAM1P_REPT:
	WAIT3
PAM1P_REPT_LEN:	.EQU	$-PAM1P_REPT

PAM1P_TAIL_ORG:	.EQU	$$
PAM1P_TAIL:
				; このブロックは動的再配置されるので
				; このブロック"への"ジャンプは困難
				; "からの"ジャンプは可能。
	RLCA				;3
				; attention bit
				; bit7=1, reload
				; must be JP
	JP	C,PAM1P_RELOAD		; jump=9 no=6

	RLCA				; 3
				; bit6=1, stat
				; must be JP
	JP	C,PAM1P_STAT		; jump=9 no=6

	RLCA				; 3
	WAIT3
				; bit5=0, normal
				; must be JP
	JP	NC,PAM1P_NORMAL		; jump=9 no=6
				; attention=001, reset
	JP	PAM_RESET
PAM1P_TAIL_LEN:	.EQU	$-PAM1P_TAIL

				; cycle
				; 63 + 12 + 12 = 87

	.DEPHASE

PROG_ORG_LEN:	.EQU	$$-PROG_ORG

; #### interrupt vector
	.PHASE	0FFE0H
VECTOR_ORG:	.EQU	$$
VECTOR:

VEC_INT1:
	DW	INTR_IGN
VEC_INT2:
	DW	INTR_IGN
VEC_PRT0:
	DW	INTR_PRT0
VEC_PRT1:
	DW	INTR_IGN
VEC_DMAC0:
	DW	INTR_IGN
VEC_DMAC1:
	DW	INTR_IGN
VEC_SIO:
	DW	INTR_IGN
VEC_ASCI0:
	DW	INTR_ASCI0
VEC_ASCI1:
	DW	INTR_ASCI1
VEC_PT2IN:
	DW	INTR_IGN
VEC_PT2OUT:
	DW	INTR_IGN
VEC_PT2OVF:
	DW	INTR_IGN
			; 本当はここはベクタテーブルだが
			; 使われることはないので押し込む。
INTR_IGN:
	EI
	RETI

VECTOR_ORG_LEN:	.EQU	$$-VECTOR_ORG

	.DEPHASE
XPLX_FIRMWARE_LEN::	.EQU	$
