;
; SwiftLink/Turbo-232 v0.90 device driver, by Craig Bruce, 14-Apr-1998.
;
; This software is Public Domain.  It is in Buddy assembler format.
;
; This device driver uses the SwiftLink RS-232 Serial Cartridge, available from
; Creative Micro Designs, Inc, and also supports the extensions of the Turbo232
; Serial Cartridge.  Both devices are based on the 6551 ACIA chip.  It also
; supports the "hacked" SwiftLink with a 1.8432 MHz crystal.
;
; The code assumes that the kernal + I/O are in context.  On the C128, call
; it from Bank 15.  On the C64, don't flip out the Kernal unless a suitable
; NMI catcher is put into the RAM under then Kernal.  For the SuperCPU, the
; interrupt handling assumes that the 65816 is in 6502-emulation mode.
;
;--------------------------------------------------------------------------
;
; Adapted for the use with the cc65 runtime library by
; Ullrich von Bassewitz (uz@musoftware.de) 02-May-1999.
;
; All external functions are C callable, the return value is an error code.
;


   	.importzp     	ptr1, ptr2, tmp1, tmp2
   	.importzp	acia, RecvBuf, SendBuf
   	.import	      	popa, popax
	.import		sys_bank, restore_bank
   	.export	       	_rs232_init, _rs232_params, _rs232_done, _rs232_get
   	.export	      	_rs232_put, _rs232_pause, _rs232_unpause, _rs232_status
	.export		k_rs232

   	.include	"cbm510.inc"


;----------------------------------------------------------------------------
;
; Global variables
;

.bss
DropCnt:     	.res  	4      	; Number of bytes lost from rx buffer full
Initialized: 	.res	1      	; Flag indicating driver is initialized
Stopped:     	.res   	1      	; Flow-stopped flag
RtsOff:		.res	1      	;
Errors:		.res	1      	; Number of bytes received in error, low byte
BaudCode:    	.res   	1      	; Current baud in effect

; Segment, the RS232 buffers are in
BufferSeg		= 2

; UART register offsets
RegData     		= 0    	; Data register
RegStatus   		= 1     ; Status register
RegCommand  		= 2     ; Command register
RegControl  		= 3     ; Control register

; Error codes. Beware: The codes must match the codes in the C header file
ErrNotInitialized 	= $01
ErrBaudTooFast    	= $02
ErrBaudNotAvail   	= $03
ErrNoData         	= $04
ErrOverflow       	= $05


.code

;----------------------------------------------------------------------------
;
; unsigned char __fastcall__ rs232_init (char hacked);
; /* Initialize the serial port, install the interrupt handler. The parameter
;  * must be true (non zero) for a hacked swiftlink and false (zero) otherwise.
;  */
;

_rs232_init:
       	bit 	Initialized    	;** shut down if started
       	bpl 	@L1
       	pha
       	jsr 	_rs232_done
       	pla

; Initialize buffers & control

@L1:	lda 	#0
   	sta 	RecvHead
      	sta 	SendHead
   	sta 	RecvTail
   	sta 	SendTail
   	sta 	Errors
   	sta 	Stopped
   	lda 	#255
       	sta    	RecvFreeCnt
      	sta 	SendFreeCnt

; Set default to 2400-8N1, enable interrupts

	jsr	sys_bank		; Switch indirect to system bank

	ldy	#RegData
   	lda	(acia),y
	ldy	#RegStatus
   	lda	(acia),y
      	lda 	#$18
	ldy	#RegControl
   	sta    	(acia),y

      	lda 	#$01
      	sta 	RtsOff
      	ora 	#$08
	ldy	#RegCommand
       	sta    	(acia),y
      	lda 	#$06
      	sta 	BaudCode

	jsr	restore_bank

      	lda 	#$ff
      	sta 	Initialized
      	lda	#$00
   	tax
      	rts

;----------------------------------------------------------------------------
;
; unsigned char __fastcall__ rs232_params (unsigned char params, unsigned char parity);
; /* Set the port parameters. Use a combination of the #defined values above. */
;
; Set communication parameters.
;
; baud rates              stops     word    |   parity
; ---------------------   -----     -----   |   ---------
; $00=50     $08=9600     $00=1     $00=8   |   $00=none
; $01=110    $09=19200    $80=2     $20=7   |   $20=odd
; $02=134.5  $0a=38400              $40=6   |   $60=even
; $03=300    $0b=57600              $60=5   |   $A0=mark
; $04=600    $0c=115200                     |   $E0=space
; $05=1200   $0d=230400
; $06=2400   $0e=future
; $07=4800   $0f=future
;

_rs232_params:
       	jsr	CheckInitialized	;** check initialized
       	bcc    	@L1
 	rts

; Save new parity

@L1:   	and    	#%11100000
       	ora 	#%00000001
       	sta 	tmp2

; Set baud/parameters

       	jsr	popa
       	sta	tmp1
       	and 	#$0f
       	tax
       	lda 	Bauds,x
       	cmp 	#$ff
       	bne 	@L5
       	lda 	#ErrBaudNotAvail
       	bne	@L9

@L5:   	jsr	sys_bank		; Indirect segment to system bank
       	tax
       	lda 	tmp1
       	and 	#$0f
       	sta 	BaudCode
       	lda 	tmp1
       	and 	#%11100000
       	ora 	#%00010000
       	sta 	tmp1
       	txa
       	and 	#$0f
       	ora 	tmp1
       	ldy	#RegControl
       	sta	(acia),y

; Set new parity

@L7:   	lda 	tmp2
       	sta 	RtsOff
       	ora 	#%00001000
       	ldy	#RegCommand
       	sta	(acia),y
       	jsr	restore_bank   		; Restore indirect bank
       	lda	#0
@L9:   	ldx	#0
       	rts

.rodata
Bauds:
   .byte $01,$03,$04,$06,$07,$08,$0a,$0c,$0e,$0f,$ff,$ff,$ff,$ff,$ff,$ff
     ;in:  0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
     ;baud50 110 134   3   6  12  24  48  96  19  38  57 115 230 exp exp
     ;out masks: $0F=Baud, val$FF=err
.code

;----------------------------------------------------------------------------
;
; unsigned char __fastcall__ rs232_done (void);
; /* Close the port, deinstall the interrupt hander. You MUST call this function
;  * before terminating the program, otherwise the machine may crash later. If
;  * in doubt, install an exit handler using atexit(). The function will do
;  * nothing, if it was already called.
;  */
;


_rs232_done:
     	bit 	Initialized  		;** check initialized
       	bpl 	@L9

; Stop interrupts, drop DTR

     	lda 	RtsOff
     	and 	#%11100010
     	ora 	#%00000010
	ldx	IndReg
	ldy	#$0F
	sty	IndReg			; Set indirect to system bank
	ldy	#RegCommand
     	sta    	(acia),y
	stx	IndReg			; Restore old indirect bank

; Flag uninitialized

@L9:   	lda 	#$00
     	sta 	Initialized
     	tax
     	rts

;----------------------------------------------------------------------------
;
; unsigned char __fastcall__ rs232_get (char* B);
; /* Get a character from the serial port. If no characters are available, the
;  * function will return RS_ERR_NO_DATA, so this is not a fatal error.
;  */
;

_rs232_get:
   	jsr 	CheckInitialized	; Check if initialized
   	bcc 	@L1
   	rts

; Check for bytes to send

@L1:  	sta	ptr1
 	stx	ptr1+1	    		; Store pointer to received char
	ldx 	SendFreeCnt
   	cpx 	#$ff
   	beq 	@L2
   	lda 	#$00
   	jsr    	TryToSend

; Check for buffer empty

@L2:  	lda 	RecvFreeCnt
   	cmp 	#$ff
   	bne 	@L3
   	lda	#ErrNoData
   	ldx 	#0
   	rts

; Check for flow stopped & enough free: release flow control

@L3:  	ldx 	Stopped
   	beq 	@L4
   	cmp 	#63
   	bcc 	@L4
   	lda 	#$00
   	sta 	Stopped
   	lda 	RtsOff
   	ora 	#%00001000
	ldx	IndReg
	ldy	#$0F  			; Set indirect to system bank
	sty	IndReg
	ldy	#RegCommand
   	sta	(acia),y
	stx	IndReg

; Get byte from buffer

@L4:	ldx	IndReg
	lda	#BufferSeg		; Set indirect to buffer bank
	sta	IndReg
	ldy	RecvHead
	lda	(RecvBuf),y
	stx	IndReg			; Restore indirect bank
      	inc 	RecvHead
   	inc 	RecvFreeCnt
       	ldx 	#$00
   	sta    	(ptr1,x)
       	txa		    		; Return code = 0
   	rts

;----------------------------------------------------------------------------
;
; unsigned char __fastcall__ rs232_put (char B);
; /* Send a character via the serial port. There is a transmit buffer, but
;  * transmitting is not done via interrupt. The function returns
;  * RS_ERR_OVERFLOW if there is no space left in the transmit buffer.
;  */
;

_rs232_put:
   	jsr 	CheckInitialized	; Check initialized
   	bcc 	@L1
   	rts

; Try to send

@L1:  	ldx 	SendFreeCnt
   	cpx 	#$ff
   	beq 	@L2
   	pha
   	lda 	#$00
   	jsr 	TryToSend
   	pla

; Put byte into send buffer & send

@L2:  	ldx 	SendFreeCnt
   	bne 	@L3
   	lda 	#ErrOverflow
   	ldx	#$00
   	rts

; There is enough room (character still in A)

@L3:	ldx	IndReg
       	ldy    	#BufferSeg     		; Set indirect to buffer segment
	sty	IndReg
	ldy	SendTail
    	sta 	(SendBuf),y
	stx	IndReg			; Restore indirect bank
    	inc 	SendTail
    	dec 	SendFreeCnt
   	lda 	#$ff
   	jsr 	TryToSend
   	lda	#$00
   	tax
       	rts

;----------------------------------------------------------------------------
;
; unsigned char __fastcall__ rs232_pause (void);
; /* Assert flow control and disable interrupts. */
;

_rs232_pause:
; Check initialized
   	jsr 	CheckInitialized
   	bcc 	@L1
      	rts

; Assert flow control

@L1:	lda 	RtsOff
   	sta 	Stopped
	jsr	sys_bank    		; Set indirect to system bank
	ldy	#RegCommand
   	sta	(acia),y

; Delay for flow stop to be received

   	ldx 	BaudCode
   	lda 	PauseTimes,x
   	jsr 	DelayMs

; Stop rx interrupts

   	lda 	RtsOff
   	ora 	#$02
	ldy	#RegCommand
	sta	(acia),y
       	jsr    	restore_bank		; Restore indirect segment
   	lda	#0
   	tax
   	rts


.rodata
; Delay times: 32 byte-receive times in milliseconds, or 100 max.
; Formula = 320,000 / baud
PauseTimes:
       	.byte 100,100,100,100,100,100,100,067,034,017,009,006,003,002,001,001
          ;in:  0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
          ;baud50 110 134   3   6  12  24  48  96  19  38  57 115 230 exp exp
.code

;----------------------------------------------------------------------------
;
; unsigned char __fastcall__ rs232_unpause (void);
; /* Re-enable interrupts and release flow control */
;

_rs232_unpause:
; Check initialized
     	jsr 	CheckInitialized
   	bcc 	@L1
   	rts

; Re-enable rx interrupts & release flow control

@L1:  	lda 	#$00
    	sta 	Stopped
    	lda 	RtsOff
    	ora 	#%00001000
	ldx	IndReg
	ldy	#$0F
	sty	IndReg	       		; Set indirect to system bank
	ldy	#RegCommand
	sta	(acia),y
	stx	IndReg			; Restore indirect bank

; Poll for stalled char & exit

   	jsr 	PollReceive
   	lda	#0
   	tax
   	rts

;----------------------------------------------------------------------------
;
; unsigned char __fastcall__ rs232_status (unsigned char* status,
;					   unsigned char* errors);
; /* Return the serial port status. */
;

_rs232_status:
 	sta    	ptr2
 	stx    	ptr2+1
 	jsr    	popax
 	sta    	ptr1
 	stx    	ptr1+1
   	jsr    	CheckInitialized
       	bcs    	@L9

; Get status

	ldx	IndReg			; Save indirect segment
	lda	#$0F
	sta	IndReg			; Set system bank as indirect segment
	ldy	#RegStatus
	lda	(acia),y		; Read status register
	stx	IndReg
 	ldy    	#0
 	sta    	(ptr1),y
    	jsr    	PollReceive  		; bug-recovery hack
 	lda    	Errors
	ldy	#0
       	sta    	(ptr2),y
       	tya
 	tax
@L9:	rts

;----------------------------------------------------------------------------
;
; RS232 interrupt handler.
; The RS232 handler will be called with	the system bank as indirect bank
; and all registers saved.
;

k_rs232:
	ldy	#RegStatus
       	lda    	(acia),y       		; check for byte received
     	and 	#$08
     	beq 	@L9	       		; Nothing to receive
       	lda    	(acia),y       		; check for receive errors
    	and 	#$07
       	beq    	@L1
    	inc 	Errors
@L1:   	ldy	#RegData
	lda 	(acia),y       		; get byte and put into receive buffer
    	ldx 	RecvFreeCnt
      	beq 	@L3
	ldy	#BufferSeg
	sty	IndReg
       	ldy    	RecvTail
	sta	(RecvBuf),y		; Store received character
	lda	#$0F
	sta	IndReg			; Restore indirect segment
    	inc 	RecvTail
    	dec 	RecvFreeCnt
    	cpx 	#33            	       	; check for buffer space low
       	bcs    	@L9

; Assert flow control

@L2:  	lda 	RtsOff         	       	; assert flow control if buffer space too low
	ldy	#RegCommand
    	sta 	(acia),y
    	sta 	Stopped
	rts

; Drop this char

@L3:	inc 	DropCnt+0    		;not time-critical
    	bne 	@L9
    	inc 	DropCnt+1
    	bne 	@L9
    	inc 	DropCnt+2
    	bne 	@L9
    	inc 	DropCnt+3
@L9:   	rts


;----------------------------------------------------------------------------
;
; CheckInitialized  -  internal check if initialized
; Set carry and an error code if not initialized, clear carry and do not
; change any registers if initialized.
;

CheckInitialized:
      	bit 	Initialized
      	bmi 	@L1
      	lda 	#ErrNotInitialized
      	ldx	#0
      	sec
      	rts

@L1:  	clc
      	rts

;----------------------------------------------------------------------------
; Try to send a byte. Internal routine. A = TryHard

TryToSend:
   	sta 	tmp1  	       	; Remember tryHard flag
	ldx	IndReg		; Save indirect segment
	lda	#$0F
	sta	IndReg		; Set system segment as indirect segment
@L0:   	lda 	SendFreeCnt
    	cmp 	#$ff
    	beq 	@L3   	       	; Bail out

; Check for flow stopped

@L1:	lda 	Stopped
       	bne    	@L3   	       	; Bail out

; Check that the UART is ready to send

@L2:   	ldy	#RegStatus
  	lda	(acia),y
    	and 	#$10
    	bne 	@L4
    	bit 	tmp1  	       	; Keep trying if must try hard
       	bmi 	@L0
@L3:   	stx	IndReg		; Restore indirect segment
  	rts

; Send byte and try again

@L4:   	lda	#BufferSeg
	sta	IndReg
	ldy	SendHead
	lda	(SendBuf),y
	ldy	#$0F
	sty	IndReg
	ldy	#RegData
	sta	(acia),y
    	inc 	SendHead
    	inc 	SendFreeCnt
    	jmp 	@L0


;----------------------------------------------------------------------------
;
; PollReceive - poll for rx char
;   This function is useful in odd cases where the 6551 has a character in
;   it but it fails to raise an NMI.  It might be edge-triggering conditions?
;   Actually, I'm not entirely sure that this condition can still arrise, but
;   calling this function does no harm.
;

PollReceive:
	ldx	IndReg			; Save indirect segment
	lda	#$0F
	sta	IndReg			; Set system bank as indirect segment
	ldy	#RegStatus
	lda	(acia),y
      	and	#$08
      	beq 	@L9
	lda	(acia),y		; Read a second time? ###
	and	#$08
      	beq 	@L9
	ldy	#RegData
	lda	(acia),y
       	ldy 	RecvFreeCnt
      	beq 	@L9
	ldy	#BufferSeg
	sty	IndReg
       	ldy 	RecvTail
      	sta	(RecvBuf),y
      	inc 	RecvTail
      	dec 	RecvFreeCnt
@L9:  	stx	IndReg			; Restore indirect segment
	rts

;----------------------------------------------------------------------------
;
;  DelayMs : delay for given number of milliseconds
;    This implementation isn't very rigerous; it merely delays for the
;    approximate number of clock cycles for the processor speed.
;    Algorithm:
;       repeat for number of milliseconds:
;          repeat for number of MHz of cpu speed:
;             delay for 1017 clock cycles
;

DelayMs:			;( .A=milliseconds )
@L1:   	ldy    	#1 		; 1MHz
@L2:   	ldx 	#203   		;(2)
@L3:   	dex 	       		;(2)
      	bne 	@L3		;(3) // 1017 cycles
      	dey
      	bne 	@L2
      	sec
      	sbc 	#1
      	bne 	@L1
      	rts

.end