;/////////////////////////////////////////////////////////////////////////////
;// sbx.asm
;//
;// Creative Labs SoundBlaster 2.0+ DSP driver module
;//
;// 10/08/1999	fOSSiL		Initial version
;// 28/10/1999	fOSSiL		All debug code nuked; better timing added
;//				IRQ verification
;// 2000/01/14	The Owl		nasm port
;// 2000/01/17	The Owl		nasm port
;// 2000/06/21	The Owl		dword aligned data variables


%include "util.mac"


global DspBase
global DspIRQ
global DspDMA
global DspIs16
global DspStereo
global DspRate
global DspVer

global DspName

global DspPlay
global DspCheckInt
global DspStop
global DspPause
global DspResume
global DspDetect
global DspReset
global DspRead
global DspWrite


bits 32


segment _LDATA

DspName		db	'Creative Labs SoundBlaster',0

	align 4
DspBase		dd	0	; IO base addr
DspIRQ		db	0	;
DspDMA		db	0	; DRQ channel
DspIs16		db	0	; card capability
DspStereo	db	0	; card capability
DspRate		dd	0	; maximum playback sampling rate
DspVer		dw	0	; chip version

Is16bitPlay	db 0
IsHSDmaPlay	db 0

DSP_REG_RESET	EQU 6
DSP_REG_MADDR	EQU 4
DSP_REG_MDATA	EQU 5
DSP_REG_READ	EQU 0Ah
DSP_REG_WRITE	EQU 0Ch
DSP_REG_W_STAT	EQU 0Ch
DSP_REG_R_STAT	EQU 0Eh
DSP_REG_I16_ACK	EQU 0Fh

DSP_READY	EQU 0AAh

DSP_CMD_VERSION		EQU 0E1h
DSP_CMD_BLOCK_SIZE	EQU 048h
DSP_CMD_SPEAKER_ON	EQU 0D1h
DSP_CMD_TIME_CONST	EQU 040h
DSP_CMD_OUT_RATE	EQU 041h
DSP_CMD_AUTO_INIT_8	EQU 01Ch
DSP_CMD_AUTO_INIT_8_HS	EQU 090h
DSP_CMD_AUTO_INIT_16	EQU 0B6h
DSP_CMD_STOP_8		EQU 0DAh
DSP_CMD_STOP_16		EQU 0D9h
DSP_CMD_PAUSE_8		EQU 0D0h
DSP_CMD_RESUME_8	EQU 0D4h
DSP_CMD_PAUSE_16	EQU 0D5h
DSP_CMD_RESUME_16	EQU 0D6h

DSP_PARAM_MONO		EQU 000h
DSP_PARAM_STEREO	EQU 020h
DSP_PARAM_SIGNED	EQU 010h

DSP_MREG_IRQ	EQU 80h
DSP_MREG_DMA	EQU 81h
DSP_MREG_ISTAT	EQU 82h

DETECT_START	EQU 210h
DETECT_STEP	EQU 10h
DETECT_COUNT	EQU 6

BIT_STEREO	EQU 16
BIT_16BIT	EQU 17

IRQ_16BIT_IO	EQU 2
IRQ_8BIT_IO	EQU 1


segment _LTEXT

DspPlay:
; EBX = sampling rate
; ECX = stereo << 16 | block size in bytes
; no testing will be done for invalid params

	call	DspReset	; just in case =)

; turn the speaker on
	mov	al, DSP_CMD_SPEAKER_ON
	call	DspWrite

	cmp	word [DspVer], 400h
	jb	.timeconst

; ver 4.x supports better timing
	mov	al, DSP_CMD_OUT_RATE
	call	DspWrite
	mov	al, bh
	call	DspWrite
	mov	al, bl
	call	DspWrite
	jmp	short .iotype

; output time constant
; const = 256 - 1000000 / Sampling freq
; for below 4.0 we dont play stereo
.timeconst:
	mov	al, DSP_CMD_TIME_CONST
	call	DspWrite
	mov	eax, 1000000
	xor	edx, edx
	idiv	ebx
	neg	al
	call	DspWrite

.iotype:	
	xor	ebx, ebx	; 8-bit mode is default

; no one will play 8-bit stereo on 4.0+
; at least i hope so =)
	bt	ecx, BIT_16BIT
	jc	.ver4_16

	mov	al, DSP_CMD_BLOCK_SIZE
	call	DspWrite
	jmp	short .block

.ver4_16:
; program for 16bit xfer
	mov	al, DSP_CMD_AUTO_INIT_16
	call	DspWrite
	mov	al, DSP_PARAM_SIGNED
	bt	ecx, BIT_STEREO		; check if Stereo
	jnc	.mono

	or	al, DSP_PARAM_STEREO

.mono:
	call	DspWrite
	inc	bl		; 16-bit mode

.block:
	mov	[Is16bitPlay], bl

	movzx	eax, cx
	bt	ecx, BIT_16BIT
	jnc	.not16

	shr	eax, 1

.not16:
	dec	eax
	call	DspWrite
	shr	eax, 8
	call	DspWrite

	xor	ebx, ebx	; non-HighSpeed DMA is default

	bt	ecx, BIT_16BIT
	jc	.done

	mov	al, DSP_CMD_AUTO_INIT_8

	cmp	word [DspVer], 201h
	jb	.start_8

	mov	al, DSP_CMD_AUTO_INIT_8_HS
	inc	bl		; HighSpeed DMA mode

.start_8:
	call	DspWrite

.done:
	mov	[IsHSDmaPlay], bl
	retn


DspCheckInt:
; CF=0 if should handle
; CF=1 if interrupt is not ours

	cmp	word [DspVer], 400h
	jb	.oldchip
	
; read mixer reg IRQ status
	mov	edx, [DspBase]
	add	edx, byte DSP_REG_MADDR
	mov	al, DSP_MREG_ISTAT
	out	dx, al
	call	delay

	inc	edx
	in	al, dx
	call	delay

	cmp	byte [Is16bitPlay], 1
	jne	.not16

; we are playing 16bit
	test	al, IRQ_16BIT_IO
	jz	.notours
	
; ack 4.0 style
	mov	edx, [DspBase]
	add	edx, byte DSP_REG_I16_ACK
	in	al, dx
	call	delay
	clc
	retn

.not16:
	test	al, IRQ_8BIT_IO
	jnz	.oldchip	; it is 8-bit io int, ack it

.notours:
	stc			; int not ours
	retn
	
.oldchip:
; just ack the irq by reading stat reg
	mov	edx, [DspBase]
	add	edx, byte byte DSP_REG_R_STAT
	in	al, dx
	call	delay
	clc
	retn


DspStop:
	cmp	byte [IsHSDmaPlay], 1
	jne	notHSDma

; for HighSpeed DMA we have to issue a RESET to stop
	call	DspReset
	retn

notHSDma:
	mov	al, DSP_CMD_STOP_8
	cmp	byte [Is16bitPlay], 1
	jne	.not16

	mov	al, DSP_CMD_STOP_16

.not16:
	call	DspWrite
	retn


DspPause:
	cmp	byte [IsHSDmaPlay], 1
	jne	.can_pause

	stc
	retn

.can_pause:
	mov	al, DSP_CMD_PAUSE_8
	cmp	byte [Is16bitPlay], 1
	jne	.not16

	mov	al, DSP_CMD_PAUSE_16

.not16:
	call	DspWrite
	retn


DspResume:
	cmp	byte [IsHSDmaPlay], 1
	jne	.can_resume

	stc
	retn

.can_resume:
	mov	al, DSP_CMD_RESUME_8
	cmp	byte [Is16bitPlay], 1
	jne	.not16

	mov	al, DSP_CMD_RESUME_16

.not16:
	call	DspWrite
	retn


DspDetect:
	mov	ecx, DETECT_COUNT
	mov	dword [DspBase], DETECT_START

.next:
	call	DspReset
	jnc	.found

	add	dword [DspBase], byte DETECT_STEP
	loop	.next

; not found
	mov	dword [DspBase], 0
	stc
	retn

.found:
; get DSP verision
	mov	al, DSP_CMD_VERSION
	call	DspWrite
	call	DspRead
	mov	bh, al		; major ver
	call	DspRead
	mov	bl, al		; minor ver

	mov	word [DspVer], bx

	cmp	bh, 4
	jb	.Not4

	mov	byte [DspIs16], 1
	mov	byte [DspStereo], 1
	mov	dword [DspRate], 44100

; read IRQ and DMA from mixer regs
	mov	edx, [DspBase]
	add	edx, byte DSP_REG_MADDR
	mov	al, DSP_MREG_IRQ
	out	dx, al
	call	delay

	inc	edx
	in	al, dx
	call	delay

	dec	edx

	and	al, 0fh	; mask out reserved bits - only 4 LSBs used
	xor	ecx, ecx
	dec	ecx

@@
	inc	ecx
	shr	al, 1
	jnc	@B

	mov	al, [irqs + ecx]
	mov	[DspIRQ], al
	
	mov	al, DSP_MREG_DMA
	out	dx, al
	call	delay

	inc	edx
	in	al, dx
	call	delay

	and	al, 0ebh		; mask out reserved bits
	xor	cl, cl
	dec	cl
	test	al, 0e0h		; check if 16bit DMA is available
	jz	@F

.dma16:	; skip to 16-bit DMA bits
	shr	al, 5
	add	cl, 5

@@
	inc	cl
	shr	al, 1
	jnc	@B

	mov	[DspDMA], cl
	clc
	retn

.Not4:
	mov	byte [DspIRQ], 5
	mov	byte [DspDMA], 1
	mov	byte [DspIs16], 0
	mov	byte [DspStereo], 0

	mov	eax, 44100
	cmp	bx, 201h
	jae	.setrate

	mov	eax, 23000

.setrate:
	mov	[DspRate], eax

	clc
	retn


segment _LDATA
irqs	db	2,5,7,10


segment _LTEXT
DspReset:
	mov	edx, [DspBase]
	add	edx, byte DSP_REG_RESET
	mov	al, 1
	out	dx, al
	mov	al, 0

.delay:
	dec	al
	jnz	.delay

	out	dx, al

	call	DspRead
	jc	.SB

.gotit:
	cmp	al, DSP_READY
	je	.SB

	stc

.SB:
	retn


DspRead:
	push	ecx

	mov	edx, [DspBase]
	add	edx, byte DSP_REG_R_STAT
	mov	ecx, 10000

; wait until ready or timeout
.delay:
	in	al, dx
	test	al, 80h
	jnz	.avail

	loop	.delay

; timeout, no data
	stc
	pop	ecx
	retn

.avail:
	add	edx, byte (DSP_REG_READ - DSP_REG_R_STAT)
	in	al, dx

	pop	ecx
	clc
	retn


DspWrite:
	push	ecx
	push	eax

	mov	edx, [DspBase]
	add	edx, byte DSP_REG_WRITE
	mov	ecx, 10000

; wait until ready
.delay:
	in	al, dx
	test	al, 80h
	jz	.avail

	loop	.delay

; timeout, can't write
	pop	eax
	pop	ecx
	stc
	retn

.avail:
	pop	eax
	out	dx, al
	pop	ecx
	clc
	retn

delay:
	push	ecx
	mov	ecx,16
	loop	$
	pop	ecx
	retn
