
IFNDEF	STANDALONE
STANDALONE	equ	0	; Default to not building COM file.
ENDIF


	IDEAL

	INCLUDE "PROLOGUE.MAC"

SEGMENT _TEXT	BYTE PUBLIC	'CODE'
	ASSUME	CS:_TEXT,DS:_TEXT,ES:_TEXT
IF	STANDALONE
	org	100h
ENDIF
START:
IF	STANDALONE
	jmp	DoDetect
ENDIF

SMALL_MODEL	EQU	0

MACRO	CPROC	NAME		; MACRO TO ESTABLISH A C CALLABLE PROCEDURE.
	PUBLIC	_&NAME
IF	SMALL_MODEL
PROC	_&NAME	NEAR
ELSE
PROC	_&NAME	FAR
ENDIF
	ENDM

_IO_ADDX       DW   0H		; DEFAULT I/O ADDRESS.
_INTR_NUM      DB   0		; DEFAULT IS INTERUPT #7

INT2	DD	?	; HOLDS ADDRESS OF ORIGINAL INTERRUPT VECTORS.
INT3	DD	?	; WHICH WE STEAL TO PERFORM AUTODECTION.
INT5	DD	?
INT7	DD	?
INT10	DD	?

WAIT_TIME        EQU    0200H
DMA_VOICE_IN     EQU    45H
DMA_VOICE_OUT    EQU    49H

DSP_ID_CMD              EQU    0E0H
DSP_VER_CMD             EQU    0E1H
DSP_VI8_CMD             EQU    24H
DSP_VO8_CMD             EQU    14H
DSP_VO2_CMD             EQU    17H
DSP_VO4_CMD             EQU    75H
DSP_VO25_CMD            EQU    77H
DSP_MDAC1_CMD           EQU    61H
DSP_MDAC2_CMD           EQU    62H
DSP_MDAC3_CMD           EQU    63H
DSP_MDAC4_CMD           EQU    64H
DSP_MDAC5_CMD           EQU    65H
DSP_MDAC6_CMD           EQU    66H
DSP_MDAC7_CMD           EQU    67H
DSP_TIME_CMD            EQU    40H
DSP_SILENCE_CMD         EQU    80H
DSP_PAUSE_DMA_CMD       EQU    0D0H
DSP_ONSPK_CMD           EQU    0D1H
DSP_OFFSPK_CMD          EQU    0D3H
DSP_CONT_DMA_CMD        EQU    0D4H
DSP_INTRQ_CMD           EQU    0F2H

CMS_TEST_CODE            EQU         0C6H
RESET_TEST_CODE          EQU         0AAH

CMS_EXIST                EQU         1
FM_MUSIC_EXIST           EQU         2
CTV_VOICE_EXIST          EQU         4

FM_WAIT_TIME             EQU         40H

;; SOUNDBLASTER DETECTION CODE.....................

PORTPOSSIBILITIES	DW	220H,210H,230H,240H,250H,260H

PIC0_val	db	?
PIC1_val	db	?

Macro	SavePic
	in	al,0A1h
	mov	[cs:PIC1_val],al
	in	al,21h
	mov	[cs:PIC0_val],al
	endm

Macro	RestorePic
	mov	al,[cs:PIC1_val]
	out	0A1h,al
	mov	al,[cs:PIC0_val]
	out	21h,al
	endm


;; INITIALIZE THE SOUND BLASTER.
;;
;;	ON ENTRY: NOTHING.
;;	   EXIT:  1 -> SOUNDBLASTESR FOUND.
;;		  0 -> NO SOUNDBLASTER DETECTED.
CPROC	DetectBlaster
	ARG	BASEADR:DWORD,IRQ:DWORD
	PENTER	0
	PushCREGS

	MOV	AX,CS
	MOV	DS,AX
	MOV	ES,AX

	LEA	DI,[PORTPOSSIBILITIES]
	MOV	CX,6		; TRY ALL 6 POSSIBILITIES.
@@LOP:	MOV	AX,[DI] 	; GET PORT POSSIBILITY.
	MOV	[_IO_ADDX],AX	; SAVE IN IO_ADDX
	PUSH	CX
	PUSH	DI
	CALL	DETECTBLASTER	      ; SEE IF THERE
	POP	DI
	POP	CX
	JZ	@@FOUND
	ADD	DI,2		; TRY NEXT
	LOOP	@@LOP		;
	XOR	AX,AX
	JMP   SHORT @@ERR	; EXIT, WITH ERROR CONDITION.
@@FOUND:
	MOV	AX,1		; FOUND SOUND BLASTER.
	LES	DI,[BASEADR]
	MOV	AX,[_IO_ADDX]
	STOSW
	LES	DI,[IRQ]
	XOR	AX,AX	;
	MOV	AL,[_INTR_NUM]	; RETURN INTERRUPT NUMBER FOUND.
	STOSW
@@ERR:
	PopCREGS
	PLEAVE
	RET
	ENDP


;; ON ENTRY: _IO_ADDX = TO THE ADDRESS TO SEARCH AT.
;;    EXIT: ZERO CONDITION -> SOUND BLASTER FOUND, HERE.
;;	    NON-ZERO CONDITION, SOUND BLASTER NOT FOUND.
PROC	DETECTBLASTER  NEAR
	CALL   RESET_DSP		; RESET THE DSP
	JNZ    @@ID90			;
	CALL   VERIFY_IO_CHK
	JNZ    @@ID90
	CALL   CHK_DSP_VERSION
	JNZ    @@ID90
	CALL   VERIFY_INTR
	JNZ    @@ID90
	SUB    AX,AX
@@ID90:
	RET
	ENDP

;; VERIFY THIS IO ADDRESS.
PROC	VERIFY_IO_CHK	NEAR
       MOV    BX,2
       MOV    AL,DSP_ID_CMD
       MOV    DX,[_IO_ADDX]
       ADD    DX,0CH
       CALL   WRITE_DSP_TIME
       JC     @@VIO90

       MOV    AL,0AAH
       CALL   WRITE_DSP_TIME
       JC     @@VIO90

       CALL   READ_DSP_TIME
       JC     @@VIO90

       CMP    AL,055H
       JNE    @@VIO90

       SUB    BX,BX

@@VIO90:
       MOV    AX,BX
       OR     AX,AX
       RET
       ENDP


;; HERE, WE VERIFY THIS INTERRUPT.
PROC	VERIFY_INTR	NEAR
       cli
       SavePic
       MOV	AL,2
       LEA	DX,[DUMMY_DMA_INT2]
       LEA	BX,[INT2]		; STORAGE FOR OLD ADDRESS.
       CALL	SETUP_INTERRUPT
       MOV	AL,3
       LEA	DX,[DUMMY_DMA_INT3]
       LEA	BX,[INT3]
       CALL	SETUP_INTERRUPT
       MOV	AL,5
       LEA	DX,[DUMMY_DMA_INT5]
       LEA	BX,[INT5]
       CALL	SETUP_INTERRUPT
       MOV	AL,7
       LEA	DX,[DUMMY_DMA_INT7]
       LEA	BX,[INT7]
       CALL	SETUP_INTERRUPT
       MOV	AL,10
       LEA	DX,[DUMMY_DMA_INT10]
       LEA	BX,[INT10]
       CALL	SETUP_INTERRUPT
       sti
       MOV	[_INTR_NUM],0
       MOV	DX,[_IO_ADDX]
       ADD	DX,0CH
       MOV	AL,DSP_INTRQ_CMD
       CALL	WRITE_DSP
       SUB	AX,AX
       MOV	CX,WAIT_TIME*4
@@VI10:
       CMP	[_INTR_NUM],0
       JNZ	@@VI90
       LOOP	@@VI10
       MOV	AX,3
@@VI90:
       PUSH    AX
       cli
       MOV     AL,2
       LEA     BX,[INT2]
       CALL    RESTORE_INTERRUPT
       MOV     AL,3
       LEA     BX,[INT3]
       CALL    RESTORE_INTERRUPT
       MOV     AL,5
       LEA     BX,[INT5]
       CALL    RESTORE_INTERRUPT
       MOV     AL,7
       LEA     BX,[INT7]
       CALL    RESTORE_INTERRUPT
       MOV     AL,10
       LEA     BX,[INT10]
       CALL    RESTORE_INTERRUPT
       RestorePic
       sti
       POP     AX
       OR      AX,AX
       RET
       ENDP



PROC	CHK_DSP_VERSION 	NEAR
       MOV    AL,DSP_VER_CMD
       MOV    DX,[_IO_ADDX]
       ADD    DL,0CH
       CALL   WRITE_DSP
       CALL   READ_DSP
       MOV    AH,AL
       CALL   READ_DSP
       MOV    BX,1
       CMP    AX,101H
       JB     @@CDV90
       SUB    BX,BX
@@CDV90:
       MOV    AX,BX
       OR     AX,AX
       RET
       ENDP

;;

;------------------------------------------------------------------------;
; WRITE_DSP WRITES AL TO THE SOUND BLASTER AFTER WAITING FOR LAST COMMAND
; TO COMPLETE
;------------------------------------------------------------------------;
PROC	WRITE_DSP	NEAR
	PUSH	CX		; STUFF WITH CX IS NEW TIMEOUT CODE FOR V2.0
	MOV	CX,-1
	MOV	AH,AL
@@WD10: DEC	CX		; IF TIMEOUT, EXIT LOOP
	JZ	@@WD11

	IN	AL,DX
	OR	AL,AL
	JS	@@WD10		; WAIT WHILE HIGH BIT ON, BUSY.
@@WD11:
	MOV	AL,AH		; GET BYTE TO SEND.
	OUT	DX,AL		; SEND IT
	POP	CX		; RESTORE CALLER'S CX REGISTER.
	RET
	ENDP

PROC	WRITE_DSP_TIME	NEAR
	PUSH	CX		; SAVE CALLER'S CX REGISTER.

	MOV	CX,WAIT_TIME	; APPLICATION WAIT TIME (DANGEROUS FOR VERY FAST MACHINES?)
	MOV	AH,AL		; SAVE CHARACTRER TO SEND IN AH.

@@WDT10:
	IN	AL,DX
	OR	AL,AL
	JNS	@@WDT20
	LOOP	@@WDT10
	STC
	JMP SHORT @@WDT90
@@WDT20:
	MOV	AL,AH
	OUT	DX,AL		; SEND THE DAMNED THING.
	CLC
@@WDT90:
	POP	CX		; RESTORE CALLER'S CX REGISTER.
	RET
	ENDP



PROC	READ_DSP_TIME	NEAR
       PUSH   CX
       PUSH   DX

       MOV    DX,[_IO_ADDX]
       ADD    DL,0EH

       MOV    CX,WAIT_TIME

@@RDT10:
       IN     AL,DX
       OR     AL,AL
       JS     @@RDT20

       LOOP   @@RDT10
       STC
       JMP    SHORT @@RDT90

@@RDT20:
       SUB    DL,4
       IN     AL,DX
       CLC

@@RDT90:
       POP    DX
       POP    CX
       RET
	ENDP


PROC	READ_DSP	NEAR
       PUSH   DX
       MOV    DX,[_IO_ADDX]
       ADD    DL,0EH
       SUB    AL,AL

@@RD10:
       IN     AL,DX
       OR     AL,AL
       JNS    @@RD10

       SUB    DL,4
       IN     AL,DX

       POP    DX
       RET
	ENDP


PROC	RESET_DSP	NEAR
	MOV	DX,[_IO_ADDX]
	ADD	DL,6

	MOV	AL,1		    ;JCM (USE THIS INSTEAD)
	OUT	DX,AL		    ;JCM
                                        ;JCM
	MOV	CX,20		    ;JCM
@@WAIT: IN	AL,DX		     ;JCM  ;WAIT > 3 US
	LOOP	@@WAIT		   ;JCM
                                        ;JCM
	MOV	AL,0		    ;JCM  ;DROP RESET
	OUT	DX,AL		    ;JCM

	MOV	CL,20H

@@RDSP10:
       CALL   READ_DSP_TIME
       CMP    AL,0AAH
       JE     @@RDSP20
       DEC    CL
       JNZ    @@RDSP10
       MOV    AX,2
       JMP    SHORT @@RDSP90
@@RDSP20:
       SUB    AX,AX
@@RDSP90:
       OR     AX,AX
       RET
       ENDP


;-------------------------------------------------
; entry: AL = INTERRUPT NUM                      |
;        DX = new vector ofs, seg is alway CS    |
;        BX = offset of store buffer             :
;-------------------------------------------------
Proc	SETUP_INTERRUPT near
       PUSH   BX
       PUSH   CX
       PUSH   DX

	xor	ah,ah		; Zero high byte.
       MOV    CL,AL                    ; preserve interrupt number for use
       cmp     al,8
       jb      @@calc_vect
	add	al,60h	       ; index slcae PIC vectors if IRQ > 7
@@calc_vect:
       ADD    AL,8                     ; calculate interrupt vector addx
       SHL    Ax,1
       SHL    Ax,1
       MOV    DI,AX

       PUSH   ES                       ; setup and preserve interrupt

       SUB    AX,AX
       MOV    ES,AX
       MOV    AX,[ES:DI]
       MOV    [cs:BX],AX               ;JCM
       MOV    [ES:DI],DX

       MOV    AX,[ES:DI+2]
       MOV    [cs:BX+2],AX             ;JCM
       MOV    [ES:DI+2],CS

       POP    ES

	mov	bx,1
	shl	bx,cl
	not	bx
	in	al,0a1h
	and	al,bh
	out	0a1h,al
	in	al,21h
	and	al,bl
	out	21h,al

       STI
       POP    DX
       POP    CX
       POP    BX
       RET
       endp


;-------------------------------------------------
; entry: AL = INTERRUPT NUM                      |
;        BX = offset to stored addx              |
;-------------------------------------------------
Proc	RESTORE_INTERRUPT	near
       MOV    CL,AL
       mov     al,cl	       ; Get back interrupt number.
       xor     ah,ah
       cmp	al,8
       jb	@@calc_vect
       add	al,60h		; index slave PIC if IRQ > 7
@@calc_vect:
       ADD    AL,8                      ; calculate interrupt vector addx
       SHL    Ax,1
       SHL    Ax,1
       MOV    DI,AX
       PUSH   ES                       ; restore interrupt vector
       SUB    AX,AX
       MOV    ES,AX
       MOV    AX,[cs:BX]               ;JCM
       MOV    [ES:DI],AX
       MOV    AX,[cs:BX+2]             ;JCM
       MOV    [ES:DI+2],AX
       POP    ES
       RET
       endp

PROC	DUMMY_ISR	FAR
LABEL	DUMMY_DMA_INT2	WORD
       PUSH   DX
       MOV    DL,2
       JMP    SHORT @@OUT
LABEL	DUMMY_DMA_INT3	WORD
       PUSH   DX
       MOV    DL,3
       JMP    SHORT @@OUT
LABEL	DUMMY_DMA_INT5	WORD
       PUSH   DX
       MOV    DL,5
       JMP    SHORT @@OUT
LABEL	DUMMY_DMA_INT7	WORD
       PUSH   DX
       MOV    DL,7
       jmp	short @@OUT
LABEL	DUMMY_DMA_INT10 WORD
	push	dx
	mov	dl,10
	jmp short @@OUT
@@OUT:
       PUSH   AX
       MOV    [CS:_INTR_NUM],DL
       MOV    DX,[_IO_ADDX]
       ADD    DX,0EH
       IN     AL,DX
       MOV    AL,20H
       OUT    20H,AL		; SEND A NON SPECIFIC EOI
       cmp	[cs:_INTR_NUM],7
       jle	@@DONE
       out	0a0h,al 	; Acknowledge secondary PIC
@@DONE:
       POP    AX
       POP    DX
       IRET
       ENDP

IF	STANDALONE
SBADR dw      ?
SBIRQ	  dw	  ?
msg0	db	"No SoundBlaster detected.",13,10,'$'
msg1    db      "SoundBlaster detected at base address: ",'$'
msg2	db	"H IRQ: ",'$'
msg3	db	"H.",13,10,'$'
Proc    DoDetect        near
	mov	ax,cs
	mov	ds,ax
	mov	es,ax
	push	ax
	lea	bx,[SBIRQ]
	push	bx
	push	ax
	lea	bx,[SBADR]
	push	bx
	push	ax
	call	near ptr _DetectBlaster
	add	sp,8		; Balance stack.
	or	ax,ax		; SoundBlaster detected?
	jnz	@@FOUND
	Message msg0
	mov	ax,1		; Return Error Level 1, not SBLASTER
	DOSTerminate
@@FOUND:
	Message msg1
	mov	bx,[SBADR]
	mov	al,bh
	and	al,0Fh
	HexOut
	mov	al,bl
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	HexOut
	mov	al,bl
	and	al,0Fh
	HexOut

	Message msg2

	mov	ax,[SBIRQ]
	HexOut

	Message msg3

	mov	ax,0
	DOSTerminate
	endp

ENDIF

	ENDS
	END	START


