; simple_dma_support.asm - Tutorial example of DSP DMA read and write support.

 if 0 ;; ************************* BEGIN LONG COMMENT ********************

THIS EXAMPLE IS SIMPLE AND INTENDED FOR ILLUSTRATING THE DSP DMA PROTOCOL!

It does not double-buffer, and it BLOCKS WAITING FOR ALL HOST I/O.  This is
an extremely wasteful thing to do.  See fast_dma_support.asm for a less
simple and more efficient example (using double-buffered, interrupt-driven
I/O).

The file ioequ.asm, Motorola's standard "I/O equates" file, must be included
before this one.  


EXPORTED ROUTINES

This file defines four I/O macros for the including program to invoke:

	readWordDMA   - read next word from input DMA stream (host to DSP)
	writeWordDMA  - write next word to output DMA stream (DSP to host)

	readWordHost  - read next word from host (programmed I/O)
	writeWordHost - write next word to host (programmed I/O)

Words are supplied from and written to the I/O buffers.  When the input
buffer is empty, or the output buffer is full, the DSP initiates a DMA
buffer transfer by writing a command word to the host.  Two index registers
(defined before including this file) are reserved for I/O service.  
In addition, registers B and X0 are modified (and not saved).

The subroutine 

	dma_reset   - initialize DMA buffer pointers and status bits

should be called before using the I/O routines.  The routine

	dma_stop    - flush output buffer and hang forever

may be called when the computation is finished to flush any data remaining
output data and halt.  Input data termination is indicated by the carry
bit being set on return from readWordDMA.


CONFIGURATION CONSTANTS

The including program must define configuration constants such as in the
following example.  The example uses the top 4K words of external memory for
the input and output buffers.

In other programming examples, host flag HF0 is used to tell the DSP there 
is no more input data.  In this example, an ABORT host command is implemented.
HF0 will also work.

;---------------------- Begin DMA Configuration Constants --------------------
;
DMA_READ_SIZE	equ	$0800	; Size of each DMA transfer in 
DMA_WRITE_SIZE	equ	$0800	; Size of each DMA transfer out
READ_BUF1	equ	$3000	; Input buffer
WRITE_BUF1	equ	$3800	; Output buffer
Y_DMASTAT	equ	$0	; On-chip Y memory word used for status bits
Y_SYSCALL	equ	$1	; On-chip Y memory word used for syscall arg

	define	R_DMA_IN 'R6'	; Dedicated to DMA in
	define	N_DMA_IN 'N6'	; Unused
	define	M_DMA_IN 'M6'	; -1

	define	R_DMA_OUT 'R7'	; Dedicated to DMA out
	define	N_DMA_OUT 'N7'	; Unused
	define	M_DMA_OUT 'M7'	; -1

BUG56_VERSION 	set 1		; 1 if loading into Bug56, 0 to try for real
DEBUG_VERSION 	set 1		; 1 to emit extra error-checking code
;
;----------------------- End DMA Configuration Constants ---------------------


MAIN PROGRAM EXAMPLE
;
; Example program which reads a shift count and applies it to the data stream
;
	include '/usr/lib/dsp/smsrc/ioequ.asm'	  ; standard equates
	include 'dma_configuration_constants.asm' ; configure DMA as above
	if BUG56_VERSION
	  define START_ADDRESS '$80'	; Make room for Bug56's degmon monitor
	else
	  define START_ADDRESS '$40'	; Make room for interrupt vectors
	  org p:0			; Reset vector (Bug56 will not set it)
reset	  jmp start
	endif
	org p:START_ADDRESS
start	jmp main
	include 'simple_dma_support.asm'
main	jsr dma_reset
	readWordHost N0		; get a shift count via programmed I/O
loop	readWordDMA A		; get next input sample
	jcs dma_stop		; set when aborted by HF0 or ABORT HC
	rep N0			; do the shift
	  lsl A			;   by desired number of bits
	writeWordDMA A		; output word
	jmp loop		; and loop back for next

 endif ;; ************************** END LONG COMMENT *********************

;
;---------------------- DSP <--> host communication -------------------------
;
SC_W_REQ	equ	$020002	  ;"Sys call" requesting DMA write on chan 2
DM_R_REQ	equ	$050001	  ;"DSP message" requesting DMA read on chan 1
DM_W_REQ	equ	$040002	  ;message requesting DMA write on channel 2 
VEC_R_DONE	equ	$0024	  ;host command indicating dma read complete
VEC_W_DONE	equ	$0028	  ;host command indicating dma write complete
VEC_SYS_CALL	equ	$002C	  ;host command indicating sys-call int coming
VEC_ABORT 	equ 	$002E	  ;host command indicating end of input data

;
;------------------------- Bit fields in the status word ----------------------
;
B_DMA_DONE	equ	0	  ;indicates dma complete
B_SYS_CALL	equ	1	  ;indicates sys call has been received
B_ABORTING	equ	2	  ;indicates abort host command received
B_LAST_W_BUF	equ	3	  ;set after abort during last input buf
B_IDLE   	equ	4	  ;abort complete (looked at using Bug56)
;
;------------------------------- Interrupt vectors ----------------------------
;
dma_s_saved_lc	set *

	org	p:VEC_R_DONE
	bset	#B_DMA_DONE,y:Y_DMASTAT
	nop

	org	p:VEC_W_DONE
	bset	#B_DMA_DONE,y:Y_DMASTAT
	nop

	org	p:VEC_SYS_CALL
	jsr	>sys_call

	org	p:VEC_ABORT
 	bset 	#B_ABORTING,y:Y_DMASTAT
	nop

	org	p:dma_s_saved_lc

; sys_call - field a request from the kernel
; A "system call" is a host command followed by one int written to the DSP.
; In the future, the int may specify more ints to follow.
; arg = 24bits = (8,,16) = (op,datum)
; 	where op = 1 for read and 2 for write
;	and datum is currently not used.
;
sys_call
	jclr	#m_hrdf,x:m_hsr,sys_call	;buzz until int received
	movep	x:m_hrx,y:Y_SYSCALL		;int specifying operation
	bset	#B_SYS_CALL,y:Y_DMASTAT		;set flag to say we got this
	rti
	
;
; writeWordDMA src - puts sample in "src" to the output stream. 
; For fastest behavior, src should be A (sample in A1).
; A whole buffer is accumulated before requesting DMA. 
; The routine blocks until the DMA complete. 
;
; The A,B and X0 registers are modified.
; The routine also uses and depends on R_DMA_OUT.
; Assumes R_DMA_OUT is initially set to point to output buffer.
; External SRAM is used for buffering.
;
writeWordDMA macro source
	if "source"!='A'
	  move source,A
	endif
	jsr putWordDMA				;do in-line expansion later
	endm

putWordDMA
	move	A,y:(R_DMA_OUT)+		;put done! check if buffer full
	move	#>WRITE_BUF1,B			;write-buffer address
	move	#>DMA_WRITE_SIZE,X0		;buffer length
	add	X0,B				;add to get last address + 1
	move	R_DMA_OUT,X0			;current output sample pointer
	sub	X0,B   #>WRITE_BUF1,X0		; B - X0 = end+1 - ptr
	jgt	pw_continue			; if positive, cruise, else dma
	  ;; 
	  ;; Send a buffer to the host via "DSP-initiated DMA"
	  ;; 
pw_dma_r  move	X0,R_DMA_OUT			;reset to buffer start
	  bclr	#B_DMA_DONE,y:Y_DMASTAT		;reset end-of-dma indicator bit
_htdeBuzz jclr	#m_htde,x:m_hsr,_htdeBuzz 	;wait until we can write host
	  movep	#DM_R_REQ,x:m_htx		;send "read request" DSP msg
_hf1Buzz  jclr	#m_hf1,x:m_hsr,_hf1Buzz 	;HF1 means DMA is set up to go

	  do #DMA_WRITE_SIZE,_dmaLoop		;service the DMA transfer
_dmaBuzz    jclr  #m_htde,x:m_hsr,_dmaBuzz	;DMA hardware handshake
	    if DEBUG_VERSION
	      jsclr #m_dma,x:m_hsr,dma_error	;reality check
	    endif
	    movep  y:(R_DMA_OUT)+,x:m_htx	;send values
_dmaLoop
_doneBuzz  jset	#B_DMA_DONE,y:Y_DMASTAT,_endDMA ;terminating host command
	    jclr  #m_htde,x:m_hsr,_doneBuzz	;wait until we can write host
	    movep #0,x:m_htx			;send 0s until DMA completion 
	    jmp	  _doneBuzz
_endDMA   jset	#m_hf1,x:m_hsr,_endDMA		;make sure HF1 is low
	  move	X0,R_DMA_OUT			;reset to start for refill
pw_continue
	rts


;check_abort - set carry bit if aborting and input is finished
;
check_abort
	jclr	#m_hf0,x:m_hsr,no_hf0		;old abort signal
 	bset 	#B_ABORTING,y:Y_DMASTAT
no_hf0 	btst 	#B_LAST_W_BUF,y:Y_DMASTAT	;aborted AND last buf read
	rts					;result in carry bit

;
;dma_stop - flush last buffer, signal to host and terminate execution.
;	    It is important that there be no pending W_REQ.
; 	    Due to bug #10138, sending an R_REQ while the driver is in
;	    penddmaout0 mode will cause the R_REQ to be ignored.  This
;	    of course is going to make the double-buffered, interrupt-driven
;	    I/O case a lot harder.
dma_stop
	bclr	#3,x:m_hcr		;clear HF2 to indicate idle state
	bset	#4,x:m_hcr		;set HF3 to indicate idle state
 	bset 	#B_IDLE,y:Y_DMASTAT	;for Bug56
idle	jclr	#m_htde,x:m_hsr,idle	;buzz forever here until we can write
	clr	A			;send 0s
	writeWordDMA A			; until host has all it wants
	jmp	idle			;buzz

;
; readWordDMA dest - returns the next sample from input stream in register A1
; and if dest!=A, moves A to dest.
; When input buffer is empty, blocks until DMA is successful. 
; Must be called once for each sample in a sample-frame.  
; On return, the carry bit is set if there is no more input, and
; A1 is undefined. If A1 is valid, carry will be clear.
;
; This routine destroys the b and x0 registers.
; Assumes R_DMA_IN is initially set to point to input buffer.
; External RAM is used for data buffering.
;

readWordDMA macro dest
	jsr getWordDMA				; do in-line expansion later
	if "dest"!='A'
	  move A,dest
	endif
	endm

getWordDMA
	move   #>READ_BUF1,x0			;read-buffer start address
	move 	R_DMA_IN,b			;current pointer
	sub	x0,b				;b-x0 == ptr-base
	jgt	gw_next_samp			;>0 if working on a buffer
	  ;; 
	  ;; Get a buffer from the host via "DSP-initiated DMA"
	  ;; 
	  jset  #B_LAST_W_BUF,y:Y_DMASTAT,gw_continue ;did last buf already
	  bclr	#B_DMA_DONE,y:Y_DMASTAT		;reset end-of-dma indicator bit
	  bclr	#B_SYS_CALL,y:Y_DMASTAT		;reset "sys-call pending" bit
_htdeBuzz jclr	#m_htde,x:m_hsr,_htdeBuzz 	;wait until we can talk to host
	  movep	#DM_W_REQ,x:m_htx		;send "write request" DSP msg
;;
;; Note: There is no way to abort the write.  The host MUST send the DMA
;; data after a W_REQ.  This makes the end-game harder.  When data for the DSP
;; runs out on the host side, the user is sent a "region completed" message.
;; However, by this time the DSP has normally requested the next input buffer,
;; and the driver is sitting in state penddmaout0, waiting for a region.
;; There is currently no API in the driver for aborting this state.  Therefore,
;; the user must hold the last DMA buffer's worth in a separate last region.
;; The DSP is sent the abort signal BEFORE the final region
;; is sent, and the DSP must not request any more input buffers.
;; Note that it is easier when the DSP is given an exact sample count at the
;; beginning, or if the DSP never terminates and the host keeps sending
;; buffers of zeros until it gets what it wants from the DSP.
;;
_hcBuzz	  jsr	check_abort			;check HF0
 	  jclr 	#B_ABORTING,y:Y_DMASTAT,_no_abort
	  bset  #B_LAST_W_BUF,y:Y_DMASTAT	;this is last input buffer
_no_abort move #SC_W_REQ,x0			;Expected sys_call
	  jclr #B_SYS_CALL,y:Y_DMASTAT,_hcBuzz	;wait for "sys_call" host cmd
	  move y:Y_SYSCALL,b			; single int arg to host cmd
	  cmp x0,b				; see if it's what we expected
	  jne _hcBuzz				; if not, ignore it
	  bclr #B_SYS_CALL,y:Y_DMASTAT		;got it

	  ; HF1 need not be checked since syscall implies DMA data follows

	  do #DMA_READ_SIZE,_dmaLoop		;service the DMA transfer
 	  
_dmaBuzz    jclr  #m_hrdf,x:m_hsr,_dmaBuzz	;DMA hardware handshake
	    movep  x:m_hrx,y:(R_DMA_IN)+	;receive values
	    if DEBUG_VERSION
	      jsclr #m_dma,x:m_hsr,dma_error	;reality check
	    endif
_dmaLoop
	  move   #>READ_BUF1,x0			;read-buffer start address
	  move	x0,R_DMA_IN			;reset to start for consumption

_doneBuzz jset	#B_DMA_DONE,y:Y_DMASTAT,gw_next_samp ;wait for terminating hc
	    jclr  #m_hrdf,x:m_hsr,_doneBuzz	;if DMA is still coming at us
	    movep x:m_htx,x0			;  receive it to drain its pipe
	    jmp	  _doneBuzz
gw_next_samp
	move	y:(R_DMA_IN)+,a			;the requested read

	move	#>READ_BUF1,b			;read-buffer address
	move	#>DMA_READ_SIZE,x0		;buffer length
	add	x0,b				;add to get last address + 1
	move	R_DMA_IN,x0			;current output sample pointer
	sub	x0,b   #>READ_BUF1,x0		;b - x0 = end+1 - ptr
	jgt	gw_continue			; if positive, cruise, else dma
	  move	x0,R_DMA_IN			;set to start to trigger refill
gw_continue
	btst  #B_LAST_W_BUF,y:Y_DMASTAT		;set carry bit appropriately
	rts
;
; dma_reset - executed when DSP boots up
;
dma_reset
dma_start
	bset #m_hcie,x:m_hcr 		; enable host commands
	move #READ_BUF1,R_DMA_IN	; Initialize input pointer
	move #WRITE_BUF1,R_DMA_OUT	; Initialize output pointer
	move #-1,M_DMA_IN		; Linear addressing
	move M_DMA_IN,M_DMA_OUT		;   for input and output
	move #0,X0
	move X0,y:Y_DMASTAT		; Clear DMA state
	rts
;
; Simple host-interface i/o
;
; "writeWordHost source" writes word in source to the host interface.
; "readWordHost dest" reads word in host interface to dest.
;

writeWordHost macro source
	if !BUG56_VERSION
_whbuzz	jclr	#m_htde,x:m_hsr,_whbuzz
	endif
        movep  	source,x:m_hrx
	endm	

readWordHost macro dest
	if !BUG56_VERSION
_rhbuzz	jclr	#m_hrdf,x:m_hsr,_rhbuzz
	endif
        movep  	x:m_hrx,dest
	endm	

	    if DEBUG_VERSION
dma_error	ori #1,mr ; raise level to 1 (lock out host interrupts)
		bset	#m_hf3,x:m_hcr	; abort code = HF2 and HF3
		bset	#m_hf2,x:m_hcr
dma_abort	jmp 	dma_abort
	    endif
