1
0
mirror of https://github.com/cc65/cc65.git synced 2024-07-05 21:29:03 +00:00
cc65/libsrc/c128/ser/c128-swlink.s
Oliver Schmidt d6c3bd29ac Renamed JUMPTABLE and cleaned up module.cfg.
This change was suppsed to fix the issue that the former JUMPTABLE is merked as 'ro' while it is actually written to in several scenarios. When drivers are converted using co65 and then compiled into ROMs the JUMPTABLE isn't copied to RAM and therefore the write operations in question fail.

However unfortunately I didn't succeed in changing that :-( Just setting the former JUMPTABLE to 'rw' broke the drivers. So I placed the DATA segment directly after the former JUMPTABLE segment. This made the drivers converted with co65 work again - obviously after changing libsrc/Makefile:235 from '--code-label' to '--data-label'. But the actual dynamic drivers still didn't work as the former JUMPTABLE wasn't placed as the beginning of the loaded file anymore. That effect could be changed by exchanging src/ld65/o65.c:1391 with src/ld65/o65.c:1394 but doing so broke the drivers again :-((
2014-05-01 21:44:39 +02:00

503 lines
14 KiB
ArmAsm

;
; Serial driver for the C128 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 "c128.inc"
; ------------------------------------------------------------------------
; Header. Includes jump table
.segment "HEADER"
; Driver signature
.byte $73, $65, $72 ; "ser"
.byte SER_API_VERSION ; Serial API version number
; Library reference
.addr $0000
; 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 (may also use $A0F
; and following in case of problems).
RecvHead := $A7 ; Head of receive buffer
RecvTail := $A8 ; Tail of receive buffer
RecvFreeCnt := $A9 ; Number of bytes in receive buffer
SendHead := $AA ; Head of send buffer
SendTail := $AB ; Tail of send buffer
SendFreeCnt := $B4 ; Number of bytes free in send buffer
Stopped := $B5 ; Flow-stopped flag
RtsOff := $B6 ;
; Send and receive buffers: 256 bytes each
RecvBuf := $0C00 ; Use the ROM buffers
SendBuf := $0D00
.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
;----------------------------------------------------------------------------
; Interrupt stub that is copied into low RAM. The startup code uses a special
; memory configuration with just kernal and I/O enabled (anything else is RAM).
; The NMI handler in ROM will switch back to a configuration where just the
; low 16K RAM are accessible. So we have to copy a smal piece of code into
; low RAM that enables the cc65 configuration and then jumps to the real NMI
; handler.
NmiStubOrig := *
.org $1150 ; BASIC graphics area
.proc NmiStub
lda #MMU_CFG_CC65 ; Bank 0 with kernal ROM...
sta MMU_CR ; ...enable
jsr NmiHandler ; Call the actual NMI handler
lda #$00 ; Get ROM config...
sta MMU_CR ; ...and enable it
Vector := *+1
.byte $4C ; Jump to the saved IRQ vector
.endproc
.reloc
;----------------------------------------------------------------------------
; 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
; Copy the NMI stub into low memory
ldy #.sizeof (NmiStub)-1
@L1: lda NmiStubOrig,y
sta NmiStub,y
dey
bpl @L1
; Set up the nmi vector
lda NMIVec
ldy NMIVec+1
sta NmiStub::Vector+0
sty NmiStub::Vector+1
lda #<NmiStub
ldy #>NmiStub
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 NmiStub::Vector+0
ldy NmiStub::Vector+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 C128
;
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!
;
; Note: Because of the C128 banking, a small stub has to go into low memory,
; since the ROM NMI entry point switches to a configuration, where only the
; low 16K of RAM are visible. The entry code switches into the standard cc65
; configuration (I/O + 16K kernal) and then jumps here. Registers are already
; saved by the ROM code.
NmiHandler:
lda ACIA_STATUS ;(4) ;status ;check for byte received
and #$08 ;(2)
beq @L9 ;(2*)
cld
lda ACIA_DATA ;(4) data ;get byte and put into receive buffer
ldy RecvTail ;(4)
ldx RecvFreeCnt ;(4)
beq @L9 ;(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*)
rts
; Assert flow control
@L2: lda RtsOff ;(3) assert flow control if buffer space too low
sta ACIA_CMD ;(4) command
sta Stopped ;(3)
@L9: rts
;----------------------------------------------------------------------------
; 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