************************************************************************
*			     C. A. M. D.
************************************************************************
* CMU Amiga Midi Driver - Carnegie Mellon University
* 1988			- Commodore Amiga	
*	
* Design & Development	- Roger B. Dannenberg	
*			- Jean-Christophe Dhellemmes
*			- Bill Barton				
*        - Kjetil S. Matheussen
* Copyright 1989 Carnegie Mellon University	
************************************************************************
*
* mmp.asm - CAMD MIDI Device Driver for the Triple Play Plus (TPP) and
*           the Quadra Play (QP) midi interfaces.
*
************************************************************************
* Date        | Change
*-----------------------------------------------------------------------
* 06-Jan-1990 : Created (BB)
*-----------------------------------------------------------------------
* 12-Apr-2000 : Added support for Triple Play Plus and Quadra Play.
*             : Released under GPL (KSM)
************************************************************************
*Copyright 2000 Kjetil S. Matheussen
*
*This program is free software; you can redistribute it and/or
*modify it under the terms of the GNU General Public License
*as published by the Free Software Foundation; either version 2
*of the License, or (at your option) any later version.
*
*This program is distributed in the hope that it will be useful,
*but WITHOUT ANY WARRANTY; without even the implied warranty of
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*GNU General Public License for more details.
*
*You should have received a copy of the GNU General Public License
*along with this program; if not, write to the Free Software
*Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
************************************************************************


      nolist
	include "exec/types.i"
	include "exec/execbase.i"           ; for FlushDevice()
	include "exec/macros.i"
	include "hardware/cia.i"
	include "hardware/custom.i"
	include "hardware/intbits.i"
	include "midi/camddevices.i"
	include "resources/misc.i"
      list


Version  equ 36
Revision equ 0
Ports	 equ 4


	section driver,data

	    ; external
	xref	_AbsExecBase
	xref	_custom
	xref	_ciab


	section driver,code

	xdef	_main
	xdef	_sendmidibyte

****************************************************************
*
*   Standard MIDI Device driver header
*
****************************************************************

; code at start of file in case anyone tries to execute us as a program

;	entry	FalseStart
_main:
FalseStart
	moveq	#-1,d0
	rts

MDD ; struct MidiDeviceData
	dc.l	MDD_Magic	; mdd_Magic
	dc.l	Name		; mdd_Name
	dc.l	IDString	; mdd_IDString
	dc.w	Version 	; mdd_Version
	dc.w	Revision	; mdd_Revision
	dc.l	Init		; mdd_Init
	dc.l	Expunge 	; mdd_Expunge
	dc.l	OpenPort	; mdd_OpenPort
	dc.l	ClosePort	; mdd_ClosePort
	dc.b	Ports		; mdd_NPorts
	dc.b	0		; mdd_Flags

Name	    dc.b    'mmp',0
IDString    dc.b    'Internal serial port MIDI device driver for TPP and QP',0
	    ds.w    0	; force word alignment
VersionString:
	dc.b	'$VER: MMP - TPP/QP driver for CAMD First beta release.',0,0

****************************************************************
*
*   Serial Port Stuff
*
****************************************************************

SerFreq_NTSC equ    3579545
SerFreq_PAL equ     3546895

MIDIBPS     equ     31250

SetSerPer   macro   ; freq
	move.w	#(\1+MIDIBPS/2)/MIDIBPS-1,SerPer    ; bits 0-14: period.  bit 15: 9/8 bit selection
	endm


INTF_CLR    equ 0
	BITDEF	INT,SET,INTB_SETCLR

	    ; serdatr register bit def's (not found in any include file)

	BITDEF	SERDAT,OVRUN,15
	BITDEF	SERDAT,RBF,14
	BITDEF	SERDAT,TBE,13
	BITDEF	SERDAT,TSRE,12

    STRUCTURE SerInt,IS_SIZE
	UWORD	si_pad0
	APTR	si_OldInt
	APTR	si_Data
	APTR	si_Code
	LABEL	SerInt_Size


	section driver,data	    ; data

MiscBase    ds.l    1		    ; misc.resource pointer
SerPer	    ds.w    1		    ; serper value
OldIntEna   ds.w    1		    ; original value of TBE and RBF bits

MPD0 ; struct MidiPortData
		XREF _ActivateXmit3
	    dc.l    _ActivateXmit3
MPD1 ; struct MidiPortData
		XREF _ActivateXmit2
	    dc.l    _ActivateXmit2
MPD2 ; struct MidiPortData
		XREF _ActivateXmit1
	    dc.l    _ActivateXmit1
MPD3 ; struct MidiPortData
		XREF _ActivateXmit0
	    dc.l    _ActivateXmit0



SerXmitInt
	    dc.l    0		    ; ln_Succ
	    dc.l    0		    ; ln_Pred
	    dc.b    NT_INTERRUPT    ; ln_Type
	    dc.b    0		    ; ln_Pri
	    dc.l    Name	    ; ln_Name
	    dc.l    XCodeandData ; is_Data
		XREF _tbeinterrupt
	    dc.l    _tbeinterrupt     ; is_Code
	    dc.w    0		    ; si_pad0
	    dc.l    0		    ; si_OldInt
XCodeandData:
si_Data0:	    dc.l    0		    ; si_Data0
si_Code0:	    dc.l    0		    ; si_Code0
si_Data1:	    dc.l    0		    ; si_Data1
si_Code1:	    dc.l    0		    ; si_Code1
si_Data2:	    dc.l    0		    ; si_Data2
si_Code2:	    dc.l    0		    ; si_Code2
si_Data3:	    dc.l    0		    ; si_Data3
si_Code3:	    dc.l    0		    ; si_Code3

SerRecvInt
	    dc.l    0		    ; ln_Succ
	    dc.l    0		    ; ln_Pred
	    dc.b    NT_INTERRUPT    ; ln_Type
	    dc.b    0		    ; ln_Pri
	    dc.l    Name	    ; ln_Name
	    dc.l    RCodeandData ; is_Data
	    dc.l    RecvHandler     ; is_Code
	    dc.w    0		    ; si_pad0
	    dc.l    0		    ; si_OldInt
	    dc.l    0		    ; si_Data
	    dc.l    0		    ; si_Code
RCodeandData:
Rsi_Data0:		dc.l		0
Rsi_Code0:		dc.l		0

	section driver,code

****************************************************************
*
*   MidiDeviceData Functions
*
****************************************************************

****************************************************************
*
*   Init
*
*   FUNCTION
*	Gets called by CAMD after being LoadSeg'ed.
*
*   INPUTS
*	None
*
*   RESULTS
*	TRUE if successful, FALSE on failure.
*
****************************************************************


init_reg reg a6

Init
	movem.l init_reg,-(sp)

	move.l	_AbsExecBase,a6 	    ; A6 = SysBase



**********!!!!!! NB !!!!!!**************************************

*****Hack to make SAS/C find execbase without using startup-code.
*****I guess there are better ways to do this... ;)
***** !!!!!!!!!!!!!!!! Beware this point if you recompile.

	XREF		_AllocSignal
	lea		_AllocSignal,a0
	move.l	4(a0),a0
	move.l	a6,(a0)

**********   EndHack      **************************************


	    ; get misc.resource pointer
	lea	MiscName,a1		    ; open misc.resource
	JSRLIB	OpenResource
	move.l	d0,MiscBase
	beq	init_ret

	    ; calculate SerPer
	cmp.b	#50,VBlankFrequency(a6)     ; is PAL?
	beq	init_pal
	SetSerPer SerFreq_NTSC		    ; set our local SerPer variable (not custom.serper)
	bra	init_serperdone
init_pal
	SetSerPer SerFreq_PAL		    ; set out local SerPer variable
init_serperdone

	moveq	#1,d0			    ; return TRUE
init_ret
	movem.l (sp)+,init_reg
	rts


****************************************************************
*
*   Expunge
*
*   FUNCTION
*	Gets called by CAMD immediately before being
*	UnLoadSeg'ed.
*
*   INPUTS
*	None
*
*   RESULTS
*	None
*
****************************************************************

Expunge
	rts


****************************************************************
*
*   OpenPort
*
*   FUNCTION
*	Open a MIDI port.
*
*   INPUTS
*	D0.b - Port number (0-3 for this driver)
*	A0 - Xmit function
*	A1 - Recv function
*	A2 - Data
*
*   RESULT
*	D0 - pointer to MidiPortData structure.
*
****************************************************************

opened:	dc.l 0

op_reg	reg a6/d4/a3/a5

OpenPort
	movem.l op_reg,-(sp)

	move.l	d0,d4

	move.l	_AbsExecBase,a6 	    ; A6 = SysBase

		    ; set SerXmitInt
	cmp.b		#0,d0
	bne.s		n1

;n0:
	move.l	a0,si_Code3
	move.l	a2,si_Data3
	lea		MPD0,a3

   ; set SerRecvInt
	move.l	a1,Rsi_Code0
	move.l	a2,Rsi_Data0

   ; set RBF interrupt vector
	movem.l	a0/a1/d0/d1,-(sp)
		lea		_custom,a0		    ; A0 = custom

		move.w	#INTF_RBF,d1	    ; D1 = RBF mask and int disable

	    ; save original RBF intena settings
		move.w	intenar(a0),d0
		and.w		d1,d0			    ; mask with RBF mask
		move.w	d0,OldIntEna

		move.w	d1,intena(a0)               ; disable TBE interrupts
		move.w	d1,intreq(a0)               ; clear pending TBE interrupts
		move.w	SerPer,serper(a0)           ; set serper

		moveq		#INTB_RBF,d0
		lea		SerRecvInt,a1

		JSRLIB	SetIntVector
		move.l	d0,SerRecvInt+si_OldInt     ; save old vector
		move.w	#INTF_SET!INTF_RBF,_custom+intena   ; enable RBF
	movem.l	(sp)+,a0/a1/d0/d1


	bra.s		ns
n1:
	cmp.b		#1,d0
	bne.s		n2
	move.l	a0,si_Code2
	move.l	a2,si_Data2
	lea		MPD1,a3
	bra.s		ns
n2:
	cmp.b		#2,d0
	bne.s		n3
	move.l	a0,si_Code1
	move.l	a2,si_Data1
	lea		MPD2,a3
	bra.s		ns
n3:
	move.l	a0,si_Code0
	move.l	a2,si_Data0
	lea		MPD3,a3

ns:
	JSRLIB	Forbid

	add.l		#1,opened

	cmp.l		#1,opened
	beq.s		notopened

	JSRLIB	Permit
	bra		allreadyopened

notopened:

	JSRLIB	Permit


	bsr		AllocSerial
	ext.l		d0
	beq		op_ret

	lea		_custom,a0		    ; A0 = custom
	move.w	#INTF_TBE,d1	    ; D1 = TBE mask and int disable

	; save original TBE intena settings
	move.w	intenar(a0),d0
	and.w		d1,d0			    ; mask with TBE mask
	move.w	d0,OldIntEna

	move.w	d1,intena(a0)               ; disable TBE and RBF interrupts
	move.w	d1,intreq(a0)               ; clear pending TBE and RBF interrupts
	move.w	SerPer,serper(a0)           ; set serper

		    ; set TBE interrupt vector
	moveq		#INTB_TBE,d0
	lea		SerXmitInt,a1
	JSRLIB	SetIntVector
	move.l	d0,SerXmitInt+si_OldInt     ; save old vector


	move.w	#INTF_SET!INTF_TBE,_custom+intena   ; enable TBE
	bset.b	#CIAB_COMDTR,_ciab+ciapra   ; turn on port 0 as default
	bset.b	#CIAB_COMRTS,_ciab+ciapra   ; -----------""------------

	XREF		_startportwaithandletask
	bsr		_startportwaithandletask

allreadyopened:
	move.l	a3,d0			    ; return pointer to MidiPortData

op_ret
	movem.l (sp)+,op_reg
	rts



****************************************************************
*
*   ClosePort
*
*   FUNCTION
*	Close a MIDI port.
*
*   INPUTS
*	D0.b - Port number (0-3 for this driver).
*
*   RESULT
*	None
*
****************************************************************

cp_reg	reg a6

ClosePort
	movem.l cp_reg,-(sp)
	tst.b		d0
	bne		closeport_not0

		move.w	#INTF_CLR!INTF_RBF,_custom+intena  ; disable RBF
		move.w	#INTF_CLR!INTF_RBF,_custom+intreq  ; clear pending RBF requests

	   ; restore original RBF interrupt vector
		moveq	#INTB_RBF,d0
		move.l	SerRecvInt+si_OldInt,a1
		JSRLIB	SetIntVector

closeport_not0:
	JSRLIB	Forbid
	sub.l		#1,opened
	tst.l		opened
	beq		closeports
	JSRLIB	Permit
	bra.s		dontcloseports
closeports:
	JSRLIB	Permit

	move.l	_AbsExecBase,a6 	    ; A6 = SysBase

	bsr	WaitSerial

	move.w	#INTF_CLR!INTF_TBE,_custom+intena  ; disable TBE
	move.w	#INTF_CLR!INTF_TBE,_custom+intreq  ; clear pending TBE requests

		    ; restore original TBE interrupt vector
	moveq	#INTB_TBE,d0
	move.l	SerXmitInt+si_OldInt,a1
	JSRLIB	SetIntVector

	move.w	OldIntEna,d0
	beq		cp_nointena
	or.w		#INTF_SET,d0
	move.w	d0,_custom+intena
cp_nointena

	bsr	FreeSerial

	XREF		_removeportwaithandletask
	bsr		_removeportwaithandletask

dontcloseports:
	movem.l (sp)+,cp_reg
	rts



****************************************************************
*
*   WaitSerial
*
*   FUNCTION
*	Wait until Amiga serial port TSRE bit is set.  This
*	indicates that the serial shift register is empty.
*
*   INPUTS
*	None
*
*   RESULTS
*	None
*
*   NOTE
*	This function busy waits!!!  It should only be called
*	once the transmit queue has emptied (i.e. TBE has
*	already been set) such that no more than 320us is
*	wasted here.
*
****************************************************************

ws_reg reg

WaitSerial
	movem.l d0,-(sp)

ws_loop
	move.w	_custom+serdatr,d0
	btst.l	#SERDATB_TSRE,d0
	beq	ws_loop

	movem.l (sp)+,d0
	rts



****************************************************************
*
*   Serial Interrupt Handlers
*
****************************************************************


****************************************************************
*
*   _sendmidibyte
*
*   FUNCTION
*	Calls the general transmit handler and expects a byte in
*	D0 and a last byte flag in D1. Then sends out D0 to amiga serial port.
*  The funcion could have been included in the midilowlevel.c file,
*  but since the call-function returns to d1, its best to write it in assembler.
*
*   INPUTS
*	A0 - Code
*  A3 - Custom
*  A2 - Data
*
*   RESULTS
*	Last byte flag (d1->d0)
*
*
****************************************************************

bl:	dc.l	$220000c
df:	dc.l	0

_sendmidibyte:


	jsr		(a0)						; returns byte in D0, last byte flag in D1

	or.w		#$100,d0		  			; add stop bit
	move.w	d0,serdat(a3)			; send byte

;	move.l	bl,a0
;	move.w	d0,(a0)+
;	move.w	d1,(a0)+
;	move.l	a0,bl


	moveq		#0,d0
	move.b	d1,d0

	not.b		d0
	rts

sl:
;	moveq		#0,d0
;	rts



****************************************************************
*
*   RecvHandler
*
*   FUNCTION
*	Amiga serial port receive interrupt handler.  Called
*	by RBF interrupt vector.  Calls the CAMD receive
*	handler with received data until there are no more bytes
*	pending in the serial receive hardware.
*
*   INPUTS
*	A0 - custom
*	A1 - pointer to SerInt.Data
*	A6 - SysBase
*
*   RESULTS
*	None
*
*   NOTE
*	A5 is scratch.
*
****************************************************************

srh_reg reg a2

RecvHandler
	movem.l srh_reg,-(sp)

	movem.l (a1),a2/a5
	move.w	serdatr(a0),d0

srh_loop
	move.w	#INTF_CLR!INTF_RBF,intreq(a0)   ; clear the interrupt request

	jsr	(a5)

	lea	_custom,a0		; see if RBF is still set
	move.w	serdatr(a0),d0
	btst.l	#SERDATB_RBF,d0
	bne	srh_loop

	movem.l (sp)+,srh_reg
	rts



****************************************************************
*
*   misc.resource serial stuff
*
****************************************************************

****************************************************************
*
*   AllocSerial
*
*   FUNCTION
*	Allocate misc.resource channels for the serial port.
*	Call FlushDevice("serial.device") on failure and try
*	again.
*
*   INPUTS
*	A6 - SysBase
*
*   RESULT
*	D0.w - TRUE on success.
*
****************************************************************

;as_reg	reg

AllocSerial

	JSRLIB	Forbid			    ; remain atomic

	bsr	AttemptSerMisc		    ; attempt to get serial misc.resource channels
	tst.w	d0
	bne	as_done 		    ; if OK, return

	lea	SerialName,a1		    ; otherwise, FlushDevice("serial.device")
	bsr	FlushDevice
	bsr	AttemptSerMisc		    ; and try the serial misc.resource stuff again

as_done
	move.w	d0,-(sp)
	JSRLIB	Permit
	move.w	(sp)+,d0

	rts


****************************************************************
*
*   AttemptSerMisc
*
*   FUNCTION
*	Attempt to allocate misc.resource channels for the
*	serial port.
*
*   INPUTS
*	None
*
*   RESULT
*	D0.w - TRUE on success.
*
****************************************************************

asm_reg reg a6

AttemptSerMisc
	movem.l asm_reg,-(sp)

	move.l	MiscBase,a6		    ; A6 = MiscBase

	moveq	#MR_SERIALBITS,d0	    ; alloc SERIALBITS
	lea	Name,a1
	JSRLIB	AllocMiscResource
	tst.l	d0			    ; if return is non-zero, it's in use
	bne	asm_fail

	moveq	#MR_SERIALPORT,d0	    ; alloc SERIALPORT
	lea	Name,a1
	JSRLIB	AllocMiscResource
	tst.l	d0			    ; if return is zero, we got it
	beq	asm_ok

	moveq	#MR_SERIALBITS,d0	    ; free SERIALBITS on SERIALPORT failure
	JSRLIB	FreeMiscResource

asm_fail
	moveq	#0,d0
	bra	asm_ret

asm_ok
	moveq	#1,d0

asm_ret
	movem.l (sp)+,asm_reg
	rts



****************************************************************
*
*   FreeSerial
*
*   FUNCTION
*	Free misc.resource channels for the serial port.
*
*   INPUTS
*	None
*
*   RESULT
*	None
*
****************************************************************

fs_reg	reg a6

FreeSerial
	movem.l fs_reg,-(sp)

	move.l	MiscBase,a6		    ; A6 = MiscBase

	moveq	#MR_SERIALBITS,d0	    ; free SERIALBITS
	JSRLIB	FreeMiscResource

	moveq	#MR_SERIALPORT,d0	    ; free SERIALPORT
	JSRLIB	FreeMiscResource

	movem.l (sp)+,fs_reg
	rts



****************************************************************
*
*   FlushDevice
*
*   FUNCTION
*	Force expunge of a Device.
*
*   INPUTS
*	A1 - Name of device.
*	A6 - SysBase
*
*   RESULT
*	None
*
*   NOTE
*	Assumes caller has surrounded this call with
*	Forbid/Permit
*
****************************************************************

fd_reg	reg

FlushDevice
;	movem.l fd_reg,-(sp)

	lea	DeviceList(a6),a0
	JSRLIB	FindName
	tst.l	d0			    ; if not in the device list, skip
	beq	fd_ret

	move.l	d0,a1			    ; otherwise force expunge
	JSRLIB	RemDevice

fd_ret
;	movem.l (sp)+,fd_reg
	rts


MiscName MISCNAME
SerialName dc.b 'serial.device',0

XmitFunction:
	dc.l	0

	end
