;
; 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    $