mirror of
https://github.com/cc65/cc65.git
synced 2024-12-25 17:29:50 +00:00
2c975d3642
Up to now static drivers were created via co65 from dynamic drivers. However there was an issue with that approach: The dynamic drivers are "o65 simple files" which obligates that they start with the 'code' segment. However dynamic drivers need to start with the module header - which is written to. For dynamic drivers this isn't more than a conceptual issue because they are always contain a 'data' segment and may therefore only be loaded into writable memory. However when dynamic drivers are converted to static drivers using co65 then that issue becomes a real problem as then the 'code' segment may end up in non-writable memory - and thus writing to the module header fails. Instead of changing the way dynamic drivers work I opted to rather make static driver creation totally independent from dynamic drivers. This allows to place the module header in the 'data' segment (see 'module.mac').
505 lines
14 KiB
ArmAsm
505 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"
|
|
|
|
.macpack module
|
|
|
|
|
|
; ------------------------------------------------------------------------
|
|
; Header. Includes jump table
|
|
|
|
module_header _c128_swlink_ser
|
|
|
|
; 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
|
|
|
|
|