;*********************************************************************
;
; MPU401 Module --  
; Contains Assembly routines used for MPU-401 emulation
; The only entry points include:
;     proc_gpr1w() - mpu-401 data port write
;     proc_gpr1r() - mpu-401 data port read
;     proc_gpr2w() - mpu-401 command port read
;     proc_gpr2r() - mpu-401 status port read
;
; This is what I know so far about mpu-401...
;
; PORTS: (read/write from application point of view)
; 330: Data port
;   read  - data from mpu-401
;   write - data to   mpu-401
; 331: Status port
;   read  - status
;   write - command
;
; Sending command to mpu-401:
;   Read status port (331) see if bit 6 == 1 , read again if it is (255x)
;   Output data to data port (331)
;   Read status port (331) see if bit 7 == 1 , read again if it is (255x)
;   Read Data port (330) test for acknowledge byte - fail if not ACK
;
; Sending Data to mpu-401:
;   Read status port (331) see if bit 6 == 1 , read again if it is (255x)
;   Output data to data port (330)
;   
; Getting data from mpu-401:
;   Read status port (331) see if bit 7 == 1 , read again if it is (255x)
;   Read data port (330) to get data byte
;
;*********************************************************************
.386

INCLUDE sbosdefs.inc
INCLUDE gf1hware.inc

_DATA     segment word public use16 'DATA'
_DATA     ends
_BSS      segment word public use16 'BSS'
_BSS      ends
_TEXT     segment byte public use16 'CODE'
_TEXT     ends
DGROUP    group    _DATA,_BSS,_TEXT

;---------------------------------------------------------------------
; LIST OF PUBLICS
;---------------------------------------------------------------------
public proc_gpr1w
public proc_gpr1r
public proc_gpr2w
public proc_gpr2r
public _mpu_voices
public _mpu_channels
public _voice_is_playing

;---------------------------------------------------------------------
; LIST OF EXTERNS
;---------------------------------------------------------------------
extrn _voice:word
extrn _notes_playing:dword
extrn force_sb_irq:near
extrn _parse_midi:near
extrn mpu_full_reset:near
extrn reset_2xf:near
extrn _mixer_mask:byte
extrn _regctl_mask:byte
;
; All needed ports
;
extrn _reg_mixer:word
extrn _reg_sb2x6:word
extrn _reg_irq_status:word
extrn _reg_dram_io:word
extrn _reg_adlib_status:word
extrn _reg_adlib_data:word
extrn _reg_sb2xC:word
extrn _reg_2xC_write:word
extrn _reg_sb2xE:word
extrn _reg_status_read:word
extrn _reg_2xFcontrol:word
extrn _reg_2xBindexed:word
extrn _reg_index:word
extrn _reg_data_high:word
extrn _reg_data_low:word
extrn _reg_388_read:word
extrn _reg_389:word
extrn _mpu401_port:word
extrn _mpu401_irq:byte
IFDEF RATIONAL_FLY
extrn nmi_port:word
ENDIF

;
; Debugging data space
;
IFDEF DEBUG
extrn _add_to_debug_table:near
ENDIF

_TEXT           segment byte public use16 'CODE'
assume          cs:DGROUP, ds:DGROUP, ss:DGROUP

status_mask db 080h ; not ready to send - ready to receive
mpu_status db 0
 
_mpu_channels db 8*16 dup(0)
_mpu_voices   db 8*32 dup(0)

MPU_VERSION equ 1
MPU_WANT_TO_SEND equ 2

;---------------------------------------------------------------------
;
; force_401_irq - this function will dispatch to the apps interrupt handler
;          for for MPU401 stuff
;
;---------------------------------------------------------------------
force_401_irq proc near
    cmp _mpu401_irq,0
    je irq_bypass       ; don't generate irq if it's a 0
    push ax
    push dx
    mov dx,_reg_index    ; should use equate here ....
    mov al,IW_IEIRQI
    out dx,al
    mov dx,_reg_data_high
    in al,dx
    and al,NOT 02h        ; lets start low
    out dx,al
    or al,02h            ; then toggle high ...
    out dx,al
    and al, NOT 02h        ; and back low again
    out dx,al
    pop dx
    pop ax

irq_bypass:
    ret
force_401_irq endp

;---------------------------------------------------------------------
; status_send_on()
;---------------------------------------------------------------------
status_send_on proc near
    and status_mask, NOT 80h ; enable ready to send
    mov al,status_mask
    mov bl,2
    call near ptr write_mpu      ; write the new data
    ret
status_send_on endp

;---------------------------------------------------------------------
; status_send_off()
;
; Turn off data send ready
;---------------------------------------------------------------------
status_send_off proc near
    or status_mask,80h    
    mov al,status_mask
    mov bl,2
    call near ptr write_mpu
    ret
status_send_off endp

;---------------------------------------------------------------------
; send_ack()
;
; Do all that is necessary to send the app an ack
;---------------------------------------------------------------------
send_ack proc near
    mov al,0feh ; ACK
    mov bl,1
    call near ptr write_mpu ; write the new data
    call near ptr status_send_on
    ret
send_ack endp

IFDEF DEBUG
last_channel db 0
ENDIF

;---------------------------------------------------------------------
; proc_gpr1w()
; PORT 330 WRITE TRAP
;
; This procedure handles writes to the MPU-401 Data Port
;---------------------------------------------------------------------
proc_gpr1w proc near
    mov dx,_mpu401_port          ; load data register
IFDEF RATIONAL_FLY
    mov nmi_port,dx              ; save which port caused it ...
ENDIF
    mov bl,1                     ; select general register 1
    call near ptr read_mpu       ; byte is in al
    xor ah,ah

IFDEF DEBUG
IFDEF SELECTIVE
    test al,80h  
    je not_command
    mov last_channel,al
    and last_channel,0fh
not_command:
    cmp last_channel,1
    jne done_debug_401
ENDIF
    mov dl,GPR1W_CODE
    call near ptr _add_to_debug_table
done_debug_401:
ENDIF
    call near ptr _parse_midi

    call near ptr reset_2xf    ; reset IRQ
    ret
proc_gpr1w endp

;---------------------------------------------------------------------
; proc_gpr1r()
; PORT 330 READ TRAP
;
; This procedure handles reads from the MPU-401 Data Port
;---------------------------------------------------------------------
proc_gpr1r proc near
    mov dx,_mpu401_port          ; load data register
IFDEF RATIONAL_FLY
    mov nmi_port,dx              ; save which port caused it ...
ENDIF

IFDEF DEBUG
    in al,dx                     ; read data - generate IRQ - clear after this
    mov dl,GPR1R_CODE
    call near ptr _add_to_debug_table
ENDIF

IFDEF NEVER
    test mpu_status,MPU_VERSION
    je not_version
    and mpu_status,NOT MPU_VERSION
    mov al,15h              ; BCD
    mov bl,1
    call near ptr write_mpu ; write the new data
    call near ptr status_send_on
    jmp done_330_read
not_version:
ENDIF

    call near ptr status_send_off; done reading
done_330_read:
    call near ptr reset_2xf      ; reset IRQ *AFTER* inport 330 above
    ret
proc_gpr1r endp

;---------------------------------------------------------------------
; proc_gpr2w()
; PORT 331 WRITE TRAP
;
; This procedure handles writes to the MPU-401 Command Port
;---------------------------------------------------------------------
proc_gpr2w proc near
    mov dx,_mpu401_port          ; data port addr
    inc dx                       ; bump to status/cmd register
IFDEF RATIONAL_FLY
    mov nmi_port,dx              ; save which port caused it ...
ENDIF
    mov bl,2                   ; select general register 1
    call near ptr read_mpu     ; byte is in al
IFDEF DEBUG
    mov dl,GPR2W_CODE
    call near ptr _add_to_debug_table
ENDIF
    ;
    ; Implemented Smart Mode MPU-401 Commands
    ;   - FF - Full Reset
    ;   - 3F - Enter Uart Mode
    ;   - D0 - Want to Send Data
    ;   - AC - Request Version - NEVER'd out...
    ;
try_reset:
    cmp al,0ffh            ; full reset
    jne try_uart
    jmp do_reset           ; do reset and leave
try_uart:
    cmp al,03fh            ; enter uart mode - xwing doesn't do reset
    jne try_wanttosend
do_reset:
    call near ptr mpu_full_reset
    jmp just_leave
try_wanttosend:
    cmp al,0d0h            ; want to send data
    jne try_version
    ; WANT TO SEND DATA code here......
    jmp just_leave
try_version:
IFDEF NEVER
    cmp al,0ach            ; request version
    jne just_leave
    or mpu_status,MPU_VERSION
    ;jmp just_leave
ENDIF

just_leave:
    call near ptr send_ack
    call near ptr force_401_irq
    call near ptr reset_2xf           ; reset IRQ
    ret
proc_gpr2w endp

;---------------------------------------------------------------------
; proc_gpr2r()
; PORT 331 READ TRAP
;
; This procedure handles reads from the MPU-401 Status Port
;---------------------------------------------------------------------
proc_gpr2r proc near
IFDEF NEVER ; reads are disabled in the loader -- setup_sbos()
    xor status_mask, 80h ; enable ready to send
    mov al,status_mask
    mov bl,2
    call near ptr write_mpu      ; write the new data

    mov dx,_mpu401_port          ; data port addr
    inc dx                       ; bump to status/cmd register
IFDEF RATIONAL_FLY
    mov nmi_port,dx              ; save which port caused it ...
ENDIF
IFDEF DEBUG
    in al,dx                     ; read status - generate IRQ - clear after this
    mov dl,GPR2R_CODE
    call near ptr _add_to_debug_table
ENDIF
ENDIF
    call near ptr reset_2xf      ; reset IRQ *AFTER* inport 331 above
    ret
proc_gpr2r endp

;---------------------------------------------------------------------
; read_mpu()
;
; Reads data in an mpu401 port written by the application 
; Returns the value in al
;
; The port is specified in the bl register
; BL = 1   General Register 1 - MPU-401 Data port
; BL = 2   General Register 2 - MPU-401 Command port
;---------------------------------------------------------------------
read_mpu proc near

    call near ptr rwmpu_setup     ; common code between read and write procs
    in  al,dx                     ; get data already written

    xor ah,ah                     ; clear the rest of ax
    ret
read_mpu endp

;---------------------------------------------------------------------
; write_mpu()
;
; Sends al to an mpu-401 port for reading by the application
;
; The port is specified in the bl register
; BL = 1   General Register 1 - MPU-401 Data port
; BL = 2   General Register 2 - MPU-401 Status port
;---------------------------------------------------------------------
write_mpu proc near
    push ax                       ; save parameter
    call near ptr rwmpu_setup     ; common code between read and write procs
    pop ax                        ; get parameter
    out dx,al                     ; send data to be read
    ret
write_mpu endp

;---------------------------------------------------------------------
; rwmpu_setup()
;
; Common code between read and write procedures 
;
; Sets up control register and mixer
; Leaves dx with address of 2xB
;
; EXPECTS:
;    bl to be General register number
;
; DESTROYS:   ax
;---------------------------------------------------------------------
rwmpu_setup proc near
    mov dx,_reg_2xFcontrol        ; control register
    mov al,_regctl_mask           ; get control register mask
    or  al,bl                     ; General register
    out dx,al                     ; send it - set index to bl
    mov dx,_reg_mixer             ; mixer
    mov al,_mixer_mask            ; get mixer mask
    out dx,al                     ; send it to enable write to 2xB
    mov dx,_reg_2xBindexed        ; General register bl
    ret
rwmpu_setup endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; voice_is_playing - Returns TRUE if the selected voice is currently playing
;
; DESCRIPTION:
;   'voice' is the voice number of the requested voice status
;
; RETURNS: int
;   TRUE:  if voice is playing
;   FALSE: if voice is not playing
;
;-------------------------------------------------------------------------------
_voice_is_playing proc near
    movzx eax,_voice
    bt dword ptr _notes_playing,eax
    lahf
    and ax,0100h 
    ret
_voice_is_playing endp

_TEXT ends
    end
