;
; Serial driver for the C64 using a Swiftlink or Turbo-232 cartridge.
;
; Ullrich von Bassewitz, 2003-04-18
;
; The driver is based on the cc65 rs232 module, which in turn is based on
; Craig Bruce device driver for the Switftlink/Turbo-232.
;
; 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.
;

        .include        "zeropage.inc"
        .include        "ser-kernel.inc"
        .include        "ser-error.inc"
	.include	"c64.inc"


; ------------------------------------------------------------------------
; Header. Includes jump table

.segment        "JUMPTABLE"

; Driver signature

        .byte   $73, $65, $72           ; "ser"
        .byte   SER_API_VERSION         ; Serial API version number

; Jump table.

        .word   INSTALL
        .word   UNINSTALL
        .word   OPEN
        .word   CLOSE
        .word   GET
        .word   PUT
        .word   STATUS
        .word   IOCTL
        .word   IRQ

;----------------------------------------------------------------------------
; I/O definitions

ACIA   	        = $DE00
ACIA_DATA       = ACIA+0        ; Data register
ACIA_STATUS     = ACIA+1        ; Status register
ACIA_CMD        = ACIA+2        ; Command register
ACIA_CTRL       = ACIA+3        ; Control register

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

; We reuse the RS232 zero page variables for the driver, since the ROM
; routines cannot be used together with this driver.
RecvHead        = $B5           ; Head of receive buffer
RecvTail        = $BD           ; Tail of receive buffer
RecvFreeCnt     = $F7           ; Number of bytes in receive buffer
SendHead        = $F8           ; Head of send buffer
SendTail        = $F9           ; Tail of send buffer
SendFreeCnt     = $FA           ; Number of bytes free in send buffer

.bss
Stopped:     	.res   	1      	; Flow-stopped flag
RtsOff:		.res	1      	;

; Send and receive buffers: 256 bytes each
RecvBuf:	.res	256
SendBuf:	.res	256

.data
NmiContinue: 	.byte  	$4c    	; JMP instruction for NMI save -- continue
NmiSave:     	.res   	2      	; normal NMI handler

.rodata

; Tables used to translate RS232 params into register values

BaudTable:                      ; bit7 = 1 means setting is invalid
        .byte   $FF             ; SER_BAUD_45_5
        .byte   $FF             ; SER_BAUD_50
        .byte   $FF             ; SER_BAUD_75
        .byte   $FF             ; SER_BAUD_110
        .byte   $FF             ; SER_BAUD_134_5
        .byte   $02             ; SER_BAUD_150
        .byte   $05             ; SER_BAUD_300
        .byte   $06             ; SER_BAUD_600
        .byte   $07             ; SER_BAUD_1200
        .byte   $FF             ; SER_BAUD_1800
        .byte   $08             ; SER_BAUD_2400
        .byte   $09             ; SER_BAUD_3600
        .byte   $0A             ; SER_BAUD_4800
        .byte   $0B             ; SER_BAUD_7200
        .byte   $0C             ; SER_BAUD_9600
        .byte   $0E             ; SER_BAUD_19200
        .byte   $0F             ; SER_BAUD_38400
        .byte   $FF             ; SER_BAUD_57600
        .byte   $FF             ; SER_BAUD_115200
        .byte   $FF             ; SER_BAUD_230400

BitTable:
        .byte   $60             ; SER_BITS_5
        .byte   $40             ; SER_BITS_6
        .byte   $20             ; SER_BITS_7
        .byte   $00             ; SER_BITS_8

StopTable:
        .byte   $00             ; SER_STOP_1
        .byte   $80             ; SER_STOP_2

ParityTable:
        .byte   $00             ; SER_PAR_NONE
        .byte   $20             ; SER_PAR_ODD
        .byte   $60             ; SER_PAR_EVEN
        .byte   $A0             ; SER_PAR_MARK
        .byte   $E0             ; SER_PAR_SPACE

.code

;----------------------------------------------------------------------------
; INSTALL routine. Is called after the driver is loaded into memory. If
; possible, check if the hardware is present.
; Must return an SER_ERR_xx code in a/x.

INSTALL:

; Deactivate DTR and disable 6551 interrupts

      	lda     #%00001010
       	sta    	ACIA_CMD

; Set up the nmi vector

   	lda 	NMIVec
   	ldy 	NMIVec+1
   	sta 	NmiSave+0
   	sty 	NmiSave+1
   	lda 	#<NmiHandler
   	ldy 	#>NmiHandler
SetNMI: sta 	NMIVec
   	sty 	NMIVec+1

; Done, return an error code

        lda     #<SER_ERR_OK
        tax                     ; A is zero
      	rts

;----------------------------------------------------------------------------
; UNINSTALL routine. Is called before the driver is removed from memory.
; Must return an SER_ERR_xx code in a/x.

UNINSTALL:

; Stop interrupts, drop DTR

      	lda     #%00001010
       	sta    	ACIA_CMD

; Restore NMI vector and return OK

   	lda 	NmiSave+0
      	ldy 	NmiSave+1
	jmp	SetNMI

;----------------------------------------------------------------------------
; PARAMS routine. A pointer to a ser_params structure is passed in ptr1.
; Must return an SER_ERR_xx code in a/x.

OPEN:

; Check if the handshake setting is valid

        ldy	#SER_PARAMS::HANDSHAKE	; Handshake
        lda     (ptr1),y
        cmp	#SER_HS_HW		; This is all we support
        bne	InvParam

; Initialize buffers

        jsr     InitBuffers

; Set the value for the control register, which contains stop bits, word
; length and the baud rate.

        ldy     #SER_PARAMS::BAUDRATE
        lda     (ptr1),y                ; Baudrate index
        tay
        lda     BaudTable,y             ; Get 6551 value
        bmi     InvBaud	   		; Branch if rate not supported
        sta     tmp1

        ldy	#SER_PARAMS::DATABITS	; Databits
        lda     (ptr1),y
        tay
        lda     BitTable,y
        ora     tmp1
        sta     tmp1

        ldy	#SER_PARAMS::STOPBITS	; Stopbits
        lda     (ptr1),y
        tay
        lda     StopTable,y
        ora     tmp1
        ora	#%00010000		; Receiver clock source = baudrate
	sta	ACIA_CTRL

; Set the value for the command register. We remember the base value in
; RtsOff, since we will have to manipulate ACIA_CMD often.

        ldy    	#SER_PARAMS::PARITY	; Parity
        lda     (ptr1),y
        tay
        lda     ParityTable,y
	ora	#%00000001		; DTR active
	sta	RtsOff
       	ora    	#%00001000		; Enable receive interrupts
       	sta	ACIA_CMD

; Done

        lda     #<SER_ERR_OK
        tax                             ; A is zero
       	rts

; Invalid parameter

InvParam:
	lda	#<SER_ERR_INIT_FAILED
	ldx	#>SER_ERR_INIT_FAILED
	rts

; Baud rate not available

InvBaud:
        lda     #<SER_ERR_BAUD_UNAVAIL
        ldx     #>SER_ERR_BAUD_UNAVAIL
        rts

;----------------------------------------------------------------------------
; CLOSE: Close the port, disable interrupts and flush the buffer. Called
; without parameters. Must return an error code in a/x.
;

CLOSE:

; Stop interrupts, drop DTR

      	lda     #%00001010
       	sta    	ACIA_CMD

; Initalize buffers. Returns zero in a

        jsr     InitBuffers

; Return OK

        lda     #<SER_ERR_OK
        tax                             ; A is zero
       	rts

;----------------------------------------------------------------------------
; GET: Will fetch a character from the receive buffer and store it into the
; variable pointer to by ptr1. If no data is available, SER_ERR_NO_DATA is
; return.
;

GET:    ldx 	SendFreeCnt             ; Send data if necessary
       	inx                             ; X == $FF?
   	beq 	@L1
   	lda 	#$00
   	jsr    	TryToSend

; Check for buffer empty

@L1:  	lda 	RecvFreeCnt             ; (25)
   	cmp 	#$ff
   	bne 	@L2
   	lda	#<SER_ERR_NO_DATA
   	ldx 	#>SER_ERR_NO_DATA
   	rts

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

@L2:  	ldx 	Stopped                 ; (34)
   	beq 	@L3
   	cmp 	#63
   	bcc 	@L3
   	lda 	#$00
   	sta 	Stopped
   	lda 	RtsOff
   	ora 	#%00001000
   	sta	ACIA_CMD

; Get byte from buffer

@L3:  	ldx 	RecvHead                ; (41)
       	lda 	RecvBuf,x
      	inc 	RecvHead
   	inc 	RecvFreeCnt
       	ldx 	#$00                    ; (59)
   	sta    	(ptr1,x)
       	txa				; Return code = 0
   	rts

;----------------------------------------------------------------------------
; PUT: Output character in A.
; Must return an error code in a/x.
;

PUT:

; Try to send

        ldx 	SendFreeCnt
       	inx                             ; X = $ff?
   	beq 	@L2
   	pha
   	lda 	#$00
   	jsr 	TryToSend
   	pla

; Put byte into send buffer & send

@L2:  	ldx 	SendFreeCnt
   	bne 	@L3
   	lda 	#<SER_ERR_OVERFLOW      ; X is already zero
   	rts

@L3:  	ldx 	SendTail
    	sta 	SendBuf,x
    	inc 	SendTail
    	dec 	SendFreeCnt
   	lda 	#$ff
   	jsr 	TryToSend
   	lda	#<SER_ERR_OK
   	tax
       	rts

;----------------------------------------------------------------------------
; STATUS: Return the status in the variable pointed to by ptr1.
; Must return an error code in a/x.
;

STATUS: lda    	ACIA_STATUS
       	ldx    	#0
 	sta    	(ptr1,x)
 	txa                             ; SER_ERR_OK
        rts

;----------------------------------------------------------------------------
; IOCTL: Driver defined entry point. The wrapper will pass a pointer to ioctl
; specific data in ptr1, and the ioctl code in A.
; Must return an error code in a/x.
;

IOCTL:  lda     #<SER_ERR_INV_IOCTL     ; We don't support ioclts for now
        ldx     #>SER_ERR_INV_IOCTL
       rts

;----------------------------------------------------------------------------
; IRQ: Not used on the C64
;

IRQ     = $0000

;----------------------------------------------------------------------------
;
; NMI handler
; C128 NMI overhead=76 cycles: int=7, maxLatency=6, ROMenter=33, ROMexit=30
; C64  NMI overhead=76 cycles: int=7, maxLatency=6, ROMenter=34, ROMexit=29
;
; timing: normal=76+43+9=128 cycles, assertFlow=76+52+9=137 cycles
;
; C128 @ 115.2k: 177 cycles avail (fast)
; C64  @  57.6k: 177 cycles avail, worstAvail=177-43? = 134
; SCPU @ 230.4k: 868 cycles avail: for a joke!
;

NmiHandler:
     	pha
       	lda    	ACIA_STATUS     ;(4) ;status ;check for byte received
     	and 	#$08           	;(2)
       	beq    	@L9  		;(2*)
     	cld
     	txa
     	pha
     	tya
     	pha
@L1:	lda 	ACIA_DATA       ;(4)  data  ;get byte and put into receive buffer
    	ldy 	RecvTail 	;(4)
    	ldx 	RecvFreeCnt  	;(4)
      	beq 	@L3  		;(2*) Jump if no space in receive buffer
    	sta 	RecvBuf,y 	;(5)
    	inc 	RecvTail        ;(6)
    	dec 	RecvFreeCnt     ;(6)
    	cpx 	#33            	;(2)  check for buffer space low
       	bcc    	@L2            	;(2*)
    	jmp 	NMIEXIT         ;(3)

; Assert flow control

@L2:  	lda 	RtsOff       	;(3) assert flow control if buffer space too low
    	sta 	ACIA_CMD	;(4) command
    	sta 	Stopped      	;(3)
@L3:    jmp 	NMIEXIT      	;(3)

@L9:	pla
	jmp 	NmiContinue

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

.proc   TryToSend

   	sta 	tmp1	       	; Remember tryHard flag
@L0:   	lda 	SendFreeCnt
    	cmp 	#$ff
    	beq 	@L3	       	; Bail out

; Check for flow stopped

@L1:	lda 	Stopped
       	bne    	@L3	       	; Bail out

; Check that swiftlink is ready to send

@L2:   	lda	ACIA_STATUS
    	and 	#$10
    	bne 	@L4
    	bit 	tmp1	       	;keep trying if must try hard
       	bmi 	@L0
@L3:	rts

; Send byte and try again

@L4:  	ldx 	SendHead
    	lda 	SendBuf,x
    	sta	ACIA_DATA
    	inc 	SendHead
    	inc 	SendFreeCnt
    	jmp 	@L0

.endproc


;----------------------------------------------------------------------------
; Initialize buffers

InitBuffers:
        ldx 	#0
        stx     Stopped
       	stx 	RecvHead
    	stx 	RecvTail
      	stx 	SendHead
    	stx 	SendTail
        dex                             ; X = 255
       	stx    	RecvFreeCnt
      	stx 	SendFreeCnt
        rts

