home *** CD-ROM | disk | FTP | other *** search
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ; $Id: mac-mdr.a,v 1.6 89/05/06 17:13:33 lee Exp $
- ;
- ; midi driver
- ;
- ; also includes a millisecond relative-time counter with 5 mS resolution
- ;
- ; Steven A. Falco - moss!saf
- ; 3/6/87
- ; $Log: mac-mdr.a,v $
- ; Revision 1.6 89/05/06 17:13:33 lee
- ; rel. to comp.sources.misc
- ;
- ;
- ; portions taken from article in nov 85 mactutor by Kirk Austin
- ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;
- BRANCH SHORT ; use short displacements
- CASE ON ; be case-sensitive
- STRING PASCAL ; use pascal-style strings
- ;
- ; timer constants
- ;
- TIME_INC EQU 5 ; granularity of timer
- MS5 EQU 3917 ; VIA tics per 5 ms (1.2766 uS each)
- ;
- T1VEC EQU 24 ; lvl1 trap table offset
- MAX_BYTE EQU $FF ; like it says
- VIAT1EN EQU $C0 ; enable timer 1 interrupts
- VIAT1DI EQU $40 ; diable timer 1 interrupts
- VIARUN EQU $40 ; make T1 free-run
- VIASTOP EQU $3F ; put T1 in single-shot mode
- ;
- ; queue lengths
- ;
- TxQSize EQU $100 ; transmit buffer length
- RxQSize EQU $100 ; receive buffer length
- ;
- ; queue state flags
- ;
- midi_em EQU $FF ; queue is empty
- midi_ne EQU $00 ; queue is not empty
- ;
- ; The following are stack offsets given that an item is passed
- ; by the MPW C compiler and LINK/UNLK is used. DANGER!!! MPW C
- ; always passes in a long value.
- ;
- ; Note that in Pascal, a byte is passed as a short rather than a long...
- ; so Get_Byte would be $9. Also, in C, the caller cleans up, while in
- ; Pascal the callee cleans up.
- ;
- Get_Byte EQU $000B
- Get_Short EQU $000A
- Get_Long EQU $0008
- ;
- ; select one based on oscillator frequency used in hardware
- ;
- ;C_baud EQU %01000100 ; 500 KHz
- C_baud EQU %10000100 ; 1 MHz
- ;C_baud EQU %11000100 ; 2 MHz
- ;
- ; misc controls in SCC chip
- ;
- No_W_Req EQU 0
- Rx_off EQU %11000000
- Tx_off EQU %01100010
- TRxC EQU %00101000
- Brgen_off EQU 0
- Rx_on EQU %11000001
- Tx_on EQU %01101010
- DCD_int EQU %00001000
- RstExtSt EQU %00010000
- EnInt EQU %00010011
- MIntEn EQU %00001010
- EnMoInt EQU %00000001
- No_Vec EQU %00000010
- aReset EQU %10000000
- rstTBE EQU $28
- rstEERR EQU $30
- NoData EQU $FFFF
- ;
- ; misc offsets in trap table
- ;
- arxIO EQU 24
- atxIO EQU 16
- aspRC EQU 28
- ;
- ; SR masks for interrupt control
- ;
- spl3 EQU $0300 ;set interrupt level to 3
- ;
- ; SCC registers
- ;
- SCC0 EQU 0
- SCC1 EQU 1
- SCC2 EQU 2
- SCC3 EQU 3
- SCC4 EQU 4
- SCC5 EQU 5
- SCC6 EQU 6
- SCC7 EQU 7
- SCC8 EQU 8
- SCC9 EQU 9
- SCC10 EQU 10
- SCC11 EQU 11
- SCC12 EQU 12
- SCC13 EQU 13
- SCC14 EQU 14
- SCC15 EQU 15
- ;
- ; macro to enqueue a byte to be transmitted (can't use a subroutine
- ; 'cause we expect the stack to be in a certain state!)
- ;
- MACRO
- TxEnqueue
- MOVE TxByteIn(A5),D0 ; where to put it (offset)
- LEA TxQueue(A5),A2 ; base of queue
- MOVE.B Get_Byte(A6),0(A2,D0) ; copy it from the stack to the queue
- ADDQ #1,D0 ; bump offset
- CMP #TxQSize,D0 ; did it wrap?
- BNE @1 ; nope
- MOVE #0,D0 ; you betcha'
- @1 MOVE D0,TxByteIn(A5) ; replace it
- ENDM
- ;
- ; macro for delay (SCC is slow compared to 68000)
- ;
- MACRO
- Delay
- MOVE.L (SP),(SP)
- ENDM
- ;
- ; macro to load an SCC register (double moves are on purpose - slow down)
- ;
- MACRO
- Lscc &sreg,&val
- MOVE.B &sreg,D0
- MOVE.B D0,(A0)
- Delay
- MOVE.B &val,D0
- MOVE.B D0,(A0)
- Delay
- ENDM
- ;
- ; debug traps for Macsbug
- ;
- Dbgb OPWORD $A9FF ; breakpoint
- Dbgs OPWORD $ABFF ; print string and breakpoint
- ;
- ; debug macro
- ;
- MACRO
- Dbs &string
- ; PEA &string
- ; Dbgs
- ENDM
- ;
- ; chip addresses and offsets
- ;
- PRINT OFF
- INCLUDE 'SysEqu.a'
- PRINT ON
- ;
- ; this code must always be resident so we load it in Main segment to
- ; guarantee that. (Ever call an interrupt handler that wasn't resident?)
- ;
- SEG 'Main'
- ;
- ; global area
- ;
- EXPORT (midi_txst, midi_rxst, midi_time):DATA
- ;
- CtlOffset DS.W 1 ; channel control offset
- DataOffset DS.W 1 ; channel data offset
- ChnReset DS.B 1 ; SCC channel reset select
- RxIntOffset DS.W 1 ; for dispatch table
- TxIntOffset DS.W 1 ; likewise
- SpecRecCond DS.W 1 ; and one more
- ;
- TxQueue DS.B TxQSize ; Tx data queue
- midi_txst DS.B 1 ; the queue state
- TxByteIn DS.W 1 ; offset for enqueue
- TxByteOut DS.W 1 ; offset for dequeue
- ;
- RxQueue DS.B RxQSize ; and the Rx queue
- midi_rxst DS.B 1 ; the queue state
- RxByteIn DS.W 1 ; offset for enqueue
- RxByteOut DS.W 1 ; offset for dequeue
- ;
- midi_time DS.L 1 ; mS relative-time value
- old_via_v DS.L 1 ; old VIA vector
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ; The following are interrupt handlers. The system saves registers
- ; D0-D3 and A0-A3 so we don't...
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;
- ; handler for time increment task
- ;
- up_it PROC ENTRY
- MOVEM.L A5,-(SP) ; we also use D0-D1/A0
- MOVE.L CurrentA5,A5 ; we need the globals
- ;
- Dbs #'time interrupt'
- ;
- MOVE.L VIA,A0 ; via base register
- MOVE.L #vT1C,D0 ; low byte of counter
- MOVE.B 0(A0,D0),D1 ; reading clears the interrupt
- ;
- MOVEQ #TIME_INC,D0 ; how much?
- ADD.L D0,midi_time(A5) ; kick it up
- ;
- MOVEM.L (SP)+,A5
- RTS
- ENDP
- ;
- ; interrupt handler for received characters
- ;
- RxIntHand PROC ENTRY
- MOVEM.L A5,-(SP) ; we also use D0-D1/A0-A2
- MOVE.L CurrentA5,A5 ; we need the globals
- MOVE.L SCCRd,A0 ; hardware addresses
- MOVE.L SCCWr,A1
- ;
- Dbs #'rx interrupt'
- ;
- MOVE DataOffset(A5),D0 ; ... offset
- MOVE.B 0(A0,D0),D1 ; read the data - it's here given the interrupt
- Delay
- LEA RxQueue(A5),A2 ; base of destination queue
- MOVE RxByteIn(A5),D0 ; ... offset
- MOVE.B D1,0(A2,D0) ; store it
- MOVE.B #midi_ne,midi_rxst(A5) ; guaranteed not empty
- ADDQ #1,D0 ; we filled one
- CMP #RxQSize,D0 ; wrapped it?
- BNE @1 ; not yet
- MOVE #0,D0 ; yes - reset to top
- ;
- @1 MOVE D0,RxByteIn(A5) ; replace the offset
- ;
- MOVEM.L (SP)+,A5 ; restore context
- RTS ; back to user
- ENDP
- ;
- ; interrupt handler for transmitted characters
- ;
- TxIntHand PROC ENTRY
- MOVEM.L A5,-(SP) ; we also use D0-D1/A0-A2
- MOVE.L CurrentA5,A5
- MOVE.L SCCRd,A0 ; read base
- MOVE.L SCCWr,A1 ; write base
- ;
- Dbs #'tx interrupt'
- ;
- TST.B midi_txst(A5) ; is there something to write?
- BEQ @1 ; no - pity
- MOVE CtlOffset(A5),D0 ; offset to control
- MOVE.B #rstTBE,0(A1,D0) ; we saw the empty...
- Delay
- BRA TxIExit ; nothing to be done
- ;
- @1 MOVE TxByteOut(A5),D0 ; offset of something to send
- LEA TxQueue(A5),A2 ; base of queue
- MOVE DataOffset(A5),D1 ; offset to hardware
- MOVE.B 0(A2,D0),0(A1,D1) ; move it out
- Delay
- ADDQ #1,D0 ; we used it up
- CMP #TxQSize,D0 ; wrapped?
- BNE @2 ; no...
- MOVE #0,D0 ; 'fraid so
- ;
- @2 MOVE D0,TxByteOut(A5) ; remember position
- MOVE TxByteIn(A5),D1 ; see about the input side
- CMP D0,D1 ; now empty?
- BNE TxIExit ; no - just exit
- MOVE.B #midi_em,midi_txst(A5) ; it's empty
- ;
- TxIExit
- MOVEM.L (SP)+,A5 ; be polite
- RTS
- ENDP
- ;
- ; a stub for special conditions - we sort-of handle them
- ;
- Stub PROC ENTRY
- MOVEM.L A5,-(SP) ; we also use A0-A1
- MOVE.L CurrentA5,A5
- MOVE.L SCCRd,A0 ; read base
- MOVE.L SCCWr,A1 ; write base
- ;
- Dbs #'stub interrupt'
- ;
- MOVE CtlOffset(A5),D0 ; offset to control
- MOVE.B #rstEERR,0(A1,D0) ; we saw the botch, clear it
- Delay
- MOVEM.L (SP)+,A5
- RTS
- ENDP
- ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ; The following routines interface to MPW C. It considers D0-D1/A0-A1 as
- ; scratch. So do we. Returned values are placed in D0 (32 bits always)
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;
- ; init routine - must call this first to set up stuff!
- ;
- midi_init PROC EXPORT
- MOVE #aCtl,CtlOffset(A5)
- MOVE #aData,DataOffset(A5)
- MOVE.B #aReset,ChnReset(A5)
- MOVE #arxIO,RxIntOffset(A5)
- MOVE #atxIO,TxIntOffset(A5)
- MOVE #aspRC,SpecRecCond(A5)
- ;
- MOVE SR,-(SP) ; save interrupts
- ; we use D0-D1/A0-A1
- ORI #spl3,SR ; interrupts of 4 or better only
- ;
- Dbs #'init routine'
- ;
- MOVE.L SCCRd,A1 ; get base address
- ADD CtlOffset(A5),A1 ; ... control
- MOVE.B (A1),D0 ; dummy read to clear SCC WR0
- Delay
- ;
- ; set up SCC channel A
- ;
- MOVE.L SCCWr,A0 ; another base address
- ADD CtlOffset(A5),A0 ; ... control
- Lscc #SCC9,ChnReset(A5) ; reset channel
- Lscc #SCC4,#C_baud ; set clock rate
- Lscc #SCC1,#No_W_Req ; no write request
- Lscc #SCC3,#Rx_off ; turn it off
- Lscc #SCC5,#Tx_off ; him too
- Lscc #SCC9,#No_Vec ; not vectored interrupts
- Lscc #SCC11,#TRxC ; set clock source
- Lscc #SCC14,#Brgen_off ; no break gen
- Lscc #SCC3,#Rx_on ; now turn channel back on
- Lscc #SCC5,#Tx_on ; these on too
- Lscc #SCC15,#DCD_int ; Mr. Mousie on
- Lscc #SCC0,#RstExtSt ; reset ext status
- Lscc #SCC0,#RstExtSt ; reset ext status
- Lscc #SCC1,#EnInt ; enable interrupts
- Lscc #SCC9,#MIntEn ; master interrupt
- ;
- ; install trap vectors
- ;
- MOVE.L #Lvl2DT,A0 ; dispatch table
- MOVE RxIntOffset(A5),D0 ; ... offset
- LEA RxIntHand,A1 ; pointer to handler
- MOVE.L A1,0(A0,D0) ; load the pointer into the dispatch table
- MOVE TxIntOffset(A5),D0 ; ... offset
- LEA TxIntHand,A1 ; another pointer
- MOVE.L A1,0(A0,D0) ; load it
- MOVE SpecRecCond(A5),D0 ; ... offset
- LEA Stub,A1 ; dummy handler
- MOVE.L A1,0(A0,D0) ; load it
- ;
- ; clear queues
- ;
- CLR RxByteIn(A5) ; clear circ. buffer pointers
- CLR RxByteOut(A5)
- MOVE.B #midi_em,midi_rxst(A5) ; mark it empty
- CLR TxByteIn(A5)
- CLR TxByteOut(A5)
- MOVE.B #midi_em,midi_txst(A5) ; mark it empty
- ;
- ; get the timer going
- ;
- CLR.L midi_time(A5) ; start clean
- ;
- ; save old interrupt vector - write ours
- MOVE.L #Lvl1DT,A0 ; via trap table base
- MOVEQ #T1VEC,D0 ; offset to t1 trap
- MOVE.L 0(A0,D0),old_via_v(A5) ; save the old one
- LEA up_it,A1 ; our trap handler
- MOVE.L A1,0(A0,D0) ; set our handler in place
- ;
- MOVE.L VIA,A0 ; via base register
- ;
- ; set the mode of operation - free running
- MOVE.L #vACR,D0 ; offset to ACR
- MOVE.B 0(A0,D0),D1 ; read old acr value
- AND.B #VIASTOP,D1 ; drop any existing T1 bits
- OR.B #VIARUN,D1 ; set the free-run bit
- MOVE.B D1,0(A0,D0) ; put it back
- ;
- ; set the latches and counters
- MOVE.L #vT1C,D0 ; low byte of timer
- MOVE.B #(MS5 ** MAX_BYTE),0(A0,D0) ; write low byte
- MOVE.L #vT1CH,D0 ; high byte of timer
- MOVE.B #((MS5 >> 8) ** MAX_BYTE),0(A0,D0) ; write high byte
- ;
- MOVE.L #vIER,D0 ; enable/disable register
- MOVE.B #VIAT1EN,0(A0,D0) ; enable that interrupt!
- ;
- MOVE (SP)+,SR ; interrupts on
- RTS
- ENDP
- ;
- ; midi_tx - send a byte to the midi port (we expect the stack to contain a long)
- ;
- midi_tx PROC EXPORT
- LINK A6,#0 ; remember where our argument is
- MOVE SR,-(SP) ; save interrupts
- MOVEM.L A2,-(SP) ; we also use D0/A0-A1
- ORI #spl3,SR ; interrupts of 4 or better only
- ;
- Dbs #'transmit routine'
- ;
- TST.B midi_txst(A5) ; check the flag
- BNE TxQE ; queue empty - send direct
- ; something here - just add to queue
- TxEnqueue
- BRA TxExit ; go home
- ;
- TxQE ; must write directly to device (maybe)
- MOVE.L SCCRd,A0 ; read base
- MOVE.L SCCWr,A1 ; write base
- MOVE CtlOffset(A5),D0 ; ... offset
- BTST.B #txBE,0(A0,D0) ; is the device ready
- BNE FirstByte ; yes - do direct write
- ; device busy - add to queue - interrupt will wake up
- ; handler and send it out when the device is free
- TxEnqueue
- MOVE.B #midi_ne,midi_txst(A5) ; now it's not empty
- BRA TxExit ; done
- ;
- FirstByte ; it really wants this byte...
- MOVE DataOffset(A5),D0 ; ... offset
- Delay
- MOVE.B Get_Byte(A6),0(A1,D0) ; copy it from the stack to the device
- Delay
- ;
- TxExit ; clean up the world
- MOVEM.L (SP)+,A2 ; restore registers
- MOVE (SP)+,SR ; and interrupts
- UNLK A6
- RTS ; in C, the caller cleans up
- ENDP
- ;
- ; midi_rx - get a byte from the port (leave it in D0 as a long)
- ; We return -1 if nothing is available
- ;
- midi_rx PROC EXPORT
- ; don't need link 'cause nothing is passed in
- MOVE SR,-(SP) ; save the interrupts
- MOVEM.L D2/A2,-(SP) ; we also use D0-D1/A0-A1
- ORI #spl3,SR ; interrupts of 4 or better only
- ;
- Dbs #'receive routine'
- ;
- TST.B midi_rxst(A5) ; any data available?
- BEQ @1 ; yes
- MOVE.W #NoData,D0 ; no-data flag
- EXT.L D0 ; sign-extend it
- BRA RxExit ; C returns stuff in D0
- ;
- @1 MOVE RxByteOut(A5),D2 ; ... offset
- LEA RxQueue(A5),A2 ; and base
- CLR.L D0 ; start off empty
- MOVE.B 0(A2,D2),D0 ; get the byte
- ADDQ #1,D2 ; up the offset
- CMP #RxQSize,D2 ; wrapped around?
- BNE @2 ; not yet
- MOVE #0,D2 ; yes - reset it to the top
- ;
- @2 MOVE D2,RxByteOut(A5) ; and save it
- MOVE RxByteIn(A5),D1 ; now look at the input side
- CMP D2,D1 ; is the queue now empty?
- BNE RxExit ; no - don't worry about it
- MOVE.B #midi_em,midi_rxst(A5) ; yes - set the flag
- ;
- RxExit
- MOVEM.L (SP)+,D2/A2 ; restore registers
- MOVE (SP)+,SR ; and interrupts
- RTS
- ENDP
- ;
- ; reset the modem port - must call before quitting because the trap
- ; vectors will be invalid once we exit!!!
- ;
- midi_reset PROC EXPORT
- MOVE #aCtl,CtlOffset(A5) ; prepare globals
- MOVE #aData,DataOffset(A5)
- MOVE.B #aReset,ChnReset(A5)
- MOVE #arxIO,RxIntOffset(A5)
- MOVE #atxIO,TxIntOffset(A5)
- MOVE #aspRC,SpecRecCond(A5)
- ;
- MOVE SR,-(SP) ; save interrupts
- ; we use A0
- ORI #spl3,SR ; interrupts of 4 or better only
- ;
- Dbs #'reset routine'
- ;
- MOVE.L SCCWr,A0 ; base for writing
- ADD CtlOffset(A5),A0 ; and the offset
- ;
- Lscc #SCC9,ChnReset(A5) ; reset channel
- Lscc #SCC15,#DCD_int ; Mr. Mousie stays on
- Lscc #SCC0,#RstExtSt ; reset ext status
- Lscc #SCC0,#RstExtSt ; reset ext status
- Lscc #SCC1,#EnMoInt ; enable mouse interrupts
- Lscc #SCC9,#MIntEn ; master interrupt
- ;
- ; pull out the plug on the timer
- ;
- ; first disable interrupts
- MOVE.L VIA,A0 ; base of via
- MOVE.L #vIER,D0 ; interrupt control reg.
- MOVE.B #VIAT1DI,0(A0,D0) ; kill the interrupts
- ;
- ; next set back to single shot mode
- MOVE.L #vACR,D0 ; offset to ACR
- MOVE.B 0(A0,D0),D1 ; read old acr value
- AND.B #VIASTOP,D1 ; drop any existing T1 bits
- MOVE.B D1,0(A0,D0) ; put it back
- ;
- ; set the latches to 0
- MOVE.L #vT1C,D0 ; lower latch/counter
- CLR.B 0(A0,D0) ; clear it too
- MOVE.L #vT1CH,D0 ; offset to upper latch/counter
- CLR.B 0(A0,D0) ; clear it
- ;
- ; restore old trap vector
- MOVE.L #Lvl1DT,A0 ; dispatch table
- MOVEQ #T1VEC,D0 ; vector offset
- MOVE.L old_via_v(A5),0(A0,D0) ; reset the vector
- ;
- ; We don't turn the other vectors back to where they were...
- ; We also don't leave any of our interrupts enabled so it's OK
- ;
- MOVE (SP)+,SR ; interrupts on
- RTS
- ENDP
- ;
- END
-
-